DEV Community

yuzurush
yuzurush

Posted on • Edited on

Soroban Contracts 101: Atomic Swaps

Hi there! Welcome to my twelfth post of my series called "Soroban Contracts 101", where I'll be explaining the basics of Soroban contracts, such as data storage, authentication, custom types, and more. All the code that we're gonna explain throughout this series will mostly come from soroban-contracts-101 github repository.

In this twelfth post of the series, I'll be explaining an Atomic Swap example contract. Atomic swaps allow two parties to exchange assets without relying on a third party. For now Soroban-CLI didnt support multi soroban ops per tx and multi-auth yet, so this code cant be build and running yet, but this will give you an insight how atomic swap contract works.

The Contract Code

mod token {
    soroban_sdk::contractimport!(file = "../soroban_token_spec.wasm");
}

pub struct AtomicSwapContract;

#[contractimpl]
impl AtomicSwapContract {
    // Swap token A for token B atomically. Settle for the minimum requested price
    // for each party (this is an arbitrary choice to demonstrate the usage of
    // allowance; full amounts could be swapped as well).
    pub fn swap(
        env: Env,
        a: Address,
        b: Address,
        token_a: BytesN<32>,
        token_b: BytesN<32>,
        amount_a: i128,
        min_b_for_a: i128,
        amount_b: i128,
        min_a_for_b: i128,
    ) {
        // Verify preconditions on the minimum price for both parties.
        if amount_b < min_b_for_a {
            panic!("not enough token B for token A");
        }
        if amount_a < min_a_for_b {
            panic!("not enough token A for token B");
        }
        // Require authorization for a subset of arguments specific to a party.
        // Notice, that arguments are symmetric - there is no difference between
        // `a` and `b` in the call and hence their signatures can be used
        // either for `a` or for `b` role.
        a.require_auth_for_args(
            (token_a.clone(), token_b.clone(), amount_a, min_b_for_a).into_val(&env),
        );
        b.require_auth_for_args(
            (token_b.clone(), token_a.clone(), amount_b, min_a_for_b).into_val(&env),
        );

        // Perform the swap via two token transfers.
        move_token(&env, token_a, &a, &b, amount_a, min_a_for_b);
        move_token(&env, token_b, &b, &a, amount_b, min_b_for_a);
    }
}

fn move_token(
    env: &Env,
    token: BytesN<32>,
    from: &Address,
    to: &Address,
    approve_amount: i128,
    xfer_amount: i128,
) {
    let token = token::Client::new(&env, &token);
    let contract_address = env.current_contract_address();
    // This call needs to be authorized by `from` address. Since it increases
    // the allowance on behalf of the contract, `from` doesn't need to know `to`
    // at the signature time.
    token.increase_allowance(&from, &contract_address, &approve_amount);
    token.transfer_from(&contract_address, &from, to, &xfer_amount);
}
Enter fullscreen mode Exit fullscreen mode

Here's an explanation of the atomic swap contract code:

  • It imports the Soroban token contract as token
  • It defines an AtomicSwapContract struct
  • It implements a contractimpl for AtomicSwapContract with a swap function
  • The swap function:
  • Verifies that the minimum amounts can be met
  • Gets authorizations from both parties for their part of the swap
  • Calls move_token twice to perform the atomic swap via token transfers
  • The move_token function:
  • Creates a token client for the token being moved
  • Gets the contract address
  • Increases the allowance for the sending address
  • Performs a transfer from the contract to the recipient

The contract requires two parties (addresses a and b) to authorize a swap of two token types (token_A and token_B). Each party specifies:

  • The token they want to sell (either token A or token B)
  • The amount of the token they want to sell
  • The minimum amount of the other token they want to receive

The swap will only proceed if both parties' minimums can be met. The swap is performed atomically via two token transfers (token A to B then token B to A), with each party only authorizing one transfer (either the sending or receiving transfer).

The swap authorization uses Soroban's require_auth_for_args function, which requires authorization for a subset of the function arguments:

  • a authorizes (token A, token B, amount of token A, minimum token B)
  • b authorizes (token B, token A, amount of token B, minimum token A)

Note that the arguments are symmetric - a and b are interchangeable. This enables either party to be labeled as a or b, as long as the arguments are adjusted accordingly.
The authorizations ensure that the parties:

  • Cannot perform swaps unilaterally (without the other party)
  • Cannot reuse authorizations for swaps with different parameters

The swap itself is implemented via two token transfers (token A to B then token B to A) in the move_token helper function.
For each transfer:

The allowance for the sending contract is increased by the send amount
A transfer is performed from the sending contract to the recipient

This means the sending party only needs to authorize the allowance increase (which specifies the sending contract, not recipient) and not the actual transfer. The sending contract (this atomic swap contract) then performs the transfer to the recipient on behalf of the sender.

Conclusion

So in summary, the atomic swap contract demonstrates how to:

  • Require multi-party authorization for a swap/transaction
  • Use require_auth_for_args to get authorizations for subsets of arguments
  • Perform an atomic token swap via allowances and transfers

We will see more in the future when Soroban-CLI support multi soroban ops per tx and multi-auth. This contract code example showed There's still more room for Soroban as an smart contract platform to improve. Stay tuned for more post in this "Soroban Contracts 101" Series where we will dive deeper into Soroban Contracts and their functionalities.

Top comments (0)