How do I await two async functions and handle the error?

I can’t find any clear answers around this.

Say I make a OTC token swap for ICRC1 tokens. I need to atomically transfer tokens from two parties.

Since I am making two calls to the respective ICRC1 canisters, how can i ensure that:

If both succeeds, state is committed (this is easy)

If one fails (Party A → Party B) or (Party B → Party A), no state commitments happen at all, no transfers occur at all.

From my understanding, if I simply

await icrc1_transfer(...)
await icrc1_transfer(...)

This can result in a case where the first transfer succeeds, and the second fails. This would leave this trade incomplete, as one of the transfers do not occur.

How do you make this atomic? This seems a bit like a glaring design pattern that I don’t know how to properly build around.

Hi @dave4506, and welcome to the forum!

I’m afraid I’m not aware of any way to do what you want atomically. Another way to do a similar thing could be the swap canister described in this example, where the swap canister is a trusted third party that takes ownership of the tokens to be swapped, and internally (not on the ledgers themselves) atomically swaps the ownership of the two parties, thereafter allowing the parties to individually withdraw the tokens that were part of the swap.

As mentioned above, the IC is not a VM with a single state like ETH, so these atomicity guarantees across canisters are not available.

See the security best practices in regards of handling inter canister calls securely with e.g. journaling: Security best practices: Inter-canister calls | Internet Computer