Building an Atomic Swap
What is an Atomic Swap?
An atomic swap is a token swapping mechanism that allows users to create swap orders that can then be fulfilled by other users. For example, if I have 10 Atom tokens, and I want 15 Vesta tokens, I can create an order for 15000000uvst and 10000000uatom. Then, if someone decides that want 10 Atom tokens and has 15 Vesta tokens, they can fulfil this order and the Vesta tokens will be sent to me, and they'll get my Atom tokens.
The Smart Contract
Defining Data-Structures
We first need to think about what each order will be composed of. Let's think about this, each order will need a
token wanted and a token offered, let's call these tokenIn
and tokenOut
. We will also need to keep track of who
made the order, let's call this creator
. And finally, we'll need to keep track of each order with an identifier, let's
use the hash of all other details and call it id
.
function Order(tokenIn, tokenOut, creator) {
let id = STD.crypto.sha256(tokenIn + tokenOut + creator)
return {
id: id,
tokenIn: tokenIn,
tokenOut: tokenOut,
creator: creator,
}
}
The code above will allow use to create an order object using the standard libraries crypto
library.
Data Permanence
We can't expect each order to happen within the same block as each-other, and as such we'll need a way to save the orders onto the chain. We can do this with a few helper functions:
function SaveOrder(order) {
STD.write(order.id, JSON.stringify(order))
}
function RemoveOrder(id) {
STD.delete(id)
}
function LoadOrder(id) {
let sOrder = STD.read(id)
return JSON.parse(sOrder)
}
Creating Orders
When creating orders, we need to allow users to specify the tokens wanted/offered as well as keep their offer in the contracts account as escrow. We then create the order and save it to the chain, nothing crazy here.
CONTRACT.functions.create = function(tIn, tOut) {
let order = Order(tIn, tOut, CTX.sender)
let ok = STD.bank.sendTokens(CONTRACT.address, tOut)
if (!ok) {
STD.panic("not enough balance of " + cost)
}
SaveOrder(order)
}
Fulfilling Orders
In order to fulfil orders, we ask the user for an order ID, then we make sure they have enough tokens to support the
swap. We then take the tokens from them, send them to the order creator
and send the tokens they gave the contract
earlier to the fulfiller. Finally, we delete the order to ensure nobody can try to fulfil it a second time.
CONTRACT.functions.fulfil = function(orderId) {
let order = LoadOrder(orderId)
let ok = STD.bank.sendTokens(CONTRACT.address, order.tokenIn)
if (!ok) {
STD.panic("not enough balance of " + order.tokenIn)
}
ok = STD.bank.withdrawTokens(order.creator, order.tokenIn)
if (!ok) {
STD.panic("not enough balance of " + order.tokenIn)
}
ok = STD.bank.withdrawTokens(CTX.sender, order.tokenOut)
if (!ok) {
STD.panic("not enough balance of " + order.tokenOut)
}
RemoveOrder(orderId)
}
Querying Data
If we want users to be able to view all the info about an order before fulfilling it, we can create a query like this.
CONTRACT.queries.show = function(orderId) {
let order = LoadOrder(orderId)
return JSON.stringify(order)
}
Interacting With The Contract
Now that you've built an atomic swap, you want to be able to interact with it. To do this you'll need two accounts with different tokens on each of them. (See Testing AtomSwap Script).
Creating Swap
vestad tx vm store ./examples/orderbook.js --from {account}
vestad tx vm instantiate book {code} "" --from {account}
Creating Order
vestad tx vm execute book create "{token_a},{token_b}" --from {account}
After executing this command you can find the ID by checking this commands output for your contract's storage.
vestad q tx list-romdata
Fulfilling Order
vestad tx vm execute book fulfil "{id}" --from {account}
Conclusion
And there you have it, a full atomic swap system build with JavaScript deployed on Vesta! Happy coding!