ERC-2266 Atomic Swap based American Call Option (Atomic Swap-based American Call Option Contract Standard)
Last Call · Vaults & DeFi · created 2019-08-17
A standard that implements, as a smart contract, an American Call Option trade in which a premium is exchanged using an Atomic Swap, where two parties swap tokens without a trusted intermediary.
Why it is needed
An atomic swap using a Hash Time Lock Contract (HTLC) lets two parties swap tokens without an intermediary, but it gives the party who initiates the trade an optionality: they can freely choose whether to go through with the exchange over a period of hours. The initiator can watch exchange rate movements during that window and only complete the trade when it favors them, simply abandoning it otherwise. This is effectively the same as a financial American call option, yet the initiator pays no premium for that option, leaving the counterparty unilaterally disadvantaged. Conversely, properly harnessing this optionality makes it possible to construct a call option product without any trusted third party. This standard defines a common way for protocols to implement this by locking the premium into the contract alongside the swap, removing the arbitrage opportunity and preventing either side from maliciously locking up the other's funds.
What to implement
function setup(bytes32 secretHash, address payable initiator, address tokenA, address tokenB, uint256 initiatorAssetAmount, address payable participant, uint256 participantAssetAmount, uint256 premiumAmount) public payable: Sets up the swap contract. Specifies the two parties, the tokens to be exchanged, the amounts, and the premium amount.function initiate(bytes32 secretHash, uint256 assetRefundTime) public payable: The initiator deposits and locks the token they intend to sell, entering into the contract.function fillPremium(bytes32 secretHash, uint256 premiumRefundTime) public payable: The initiator deposits and locks the premium.function participate(bytes32 secretHash, uint256 assetRefundTime) public payable: The participant deposits and locks the token they intend to sell, entering into the contract.function redeemAsset(bytes32 secret, bytes32 secretHash) public: Presents the secret, the preimage of the hashlock, to claim the token locked by the counterparty.function refundAsset(bytes32 secretHash) public: After the timelock expires, reclaims the token they themselves locked.function redeemPremium(bytes32 secretHash) public: The participant claims the premium. This can only be called after the participant has already called participate and the participant's token has either been redeemed or refunded.function refundPremium(bytes32 secretHash) public: After the timelock expires, the initiator reclaims the premium.event SetUp(bytes32 secretHash, address initiator, address participant, address tokenA, address tokenB, uint256 initiatorAssetAmount, uint256 participantAssetAmount, uint256 premiumAmount);: Signals that one party has set up the contract via setup().event Initiated(uint256 initiateTimestamp, bytes32 secretHash, address initiator, address participant, address initiatorAssetToken, uint256 initiatorAssetAmount, uint256 initiatorAssetRefundTimestamp);: Signals that the initiator has deposited and locked the token to be exchanged via initiate().event Participated(uint256 participateTimestamp, bytes32 secretHash, address initiator, address participant, address participantAssetToken, uint256 participantAssetAmount, uint256 participantAssetRefundTimestamp);: Signals that the participant has deposited and locked the token to be exchanged via participate().event PremiumFilled(uint256 fillPremiumTimestamp, bytes32 secretHash, address initiator, address participant, address premiumToken, uint256 premiumAmount, uint256 premiumRefundTimestamp);: Signals that the initiator has deposited and locked the premium via fillPremium().event InitiatorAssetRedeemed(uint256 redeemTimestamp, bytes32 secretHash, bytes32 secret, address redeemer, address assetToken, uint256 amount);: Signals that the secret was presented before the timelock expired, and the counterparty took the initiator's asset.event ParticipantAssetRedeemed(uint256 redeemTimestamp, bytes32 secretHash, bytes32 secret, address redeemer, address assetToken, uint256 amount);: Signals that the secret was presented before the timelock expired, and the counterparty took the participant's asset.event InitiatorAssetRefunded(uint256 refundTimestamp, bytes32 secretHash, address refunder, address assetToken, uint256 amount);: Signals that the timelock expired and the initiator reclaimed their own asset.event ParticipantAssetRefunded(uint256 refundTimestamp, bytes32 secretHash, address refunder, address assetToken, uint256 amount);: Signals that the timelock expired and the participant reclaimed their own asset.event PremiumRedeemed(uint256 redeemTimestamp,bytes32 secretHash,address redeemer,address token,uint256 amount);: Signals that the participant claimed the premium. This means the asset was either redeemed via the secret or refunded to the participant after the timelock expired.event PremiumRefunded(uint256 refundTimestamp, bytes32 secretHash, address refunder, address token, uint256 amount);: Signals that the participant ultimately never joined, the premium's timelock expired, and the premium was refunded to the initiator.
In depth
Terminology and party definitions
The initiator is the party who posts the swap offer, and the participant is the party who agrees to and joins that offer. asset is the amount of tokens to be exchanged, and premium is the amount of tokens the initiator pays the participant as the price of the option. redeem is the act of claiming the counterparty's token, and refund is the act of reclaiming one's own token after the timelock expires. secret is a random string chosen by the initiator, the preimage of a hash, and secretHash is its hash value used to construct the HTLC. timelock is the deadline before which redeem is possible and after which only refund is possible.
Storage layout: four mappings keyed by secretHash
The contract maintains four mappings keyed by bytes32. swap stores the swap contract's metadata such as the parties and tokens, initiatorAsset stores the amount, timelock, and state of the asset the initiator is selling, participantAsset stores the amount, timelock, and state of the asset the participant is selling, and premium stores the amount, timelock, and state of the premium deposited by the initiator. Each contract uses a distinct secretHash, and the same secretHash links all four mappings into a single contract.
Locking phase: setup, initiate, fillPremium, participate
setup(secretHash, initiator, tokenA, tokenB, initiatorAssetAmount, participant, participantAssetAmount, premiumAmount) creates the contract's skeleton. After that, the initiator locks the asset via initiate(secretHash, assetRefundTime) and the premium via fillPremium(secretHash, premiumRefundTime), and the participant locks their own asset via participate(secretHash, assetRefundTime). Per the Rationale, the participant should decide whether to participate only after the initiator has locked the token and set the timelock, and the initiator should decide whether to proceed with the swap only after the participant has locked theirs.
Settlement phase: redeemAsset and refundAsset
redeemAsset(secret, secretHash) is a function that presents the secret, the preimage of the hashlock, to claim the counterparty's token. When the initiator uses this function to claim the participant's token, the secret is revealed on chain, so the participant can likewise use the same secret to claim the initiator's token. If the timelock expires, either party can use refundAsset(secretHash) to reclaim the token they themselves locked. Redemptions and refunds are recorded respectively via the InitiatorAssetRedeemed, ParticipantAssetRedeemed, InitiatorAssetRefunded, and ParticipantAssetRefunded events.
Premium settlement rules
redeemPremium(secretHash) can only be used by the participant, and only after the participant has already called participate and the participant's token has either been redeemed or refunded. In other words, whether the initiator exercised the option and revealed the secret, or failed to exercise it so the participant was refunded after the timelock expired, a participant who joined receives the premium either way. Conversely, refundPremium(secretHash) is a function that lets the initiator, who completed initiate but whose participant ultimately never joined, reclaim the premium after the premium's timelock expires. This structure guarantees payment for the optionality while preventing malicious locking of funds.
Compatibility and implementation
This proposal maintains full backwards compatibility. Because it only adds functionality without changing the behavior of existing standards, it does not affect existing token standards. The original text also provides an example implementation (Example.sol). The status of this document is Last Call, with a listed deadline of December 31, 2020.
Things to watch out for
- initiateTimestamp must cover the entire process: The initiateTimestamp set when the initiator locks the asset should be chosen to cover the entire swap process.
- Must not participate before the premium is deposited: The participant should never call participate before the premium has been deposited.