Atomic Swap transactions represent an exchange between two different tokens on the OMG Network. A standard exchange rate is 1:1 but you can set your own rate as well. Atomic swaps can be useful during trading operations, claiming community tokens, etc.
watcherUrl - the Watcher Info URL for defined environment (personal or from OMG Network).
plasmaContractAddress - CONTRACT_ADDRESS_PLASMA_FRAMEWORK for defined environment.
The above constants are defined for the Rinkeby environment. If you want to work on the Mainnet, check the Environments page.
3. Create helpers
BigNumber helper
BigNumber helper converts a given amount into a BigNumber. It helps when you want to do an atomic swap between ERC20 tokens that have different decimals. Most tokens have 18 decimals by default, however, some tokens have 6 (e.g. USDT), or even 0 decimals.
const amountToBN = (amount, decimals = 18) => {
const multiplier = new BigNumber(10).pow(decimals);
const subunit = new BigNumber(amount).times(multiplier).toFixed();
return new BN(subunit);
}
Fee helper
The fee helper retrieves the fee amount you need to pay during an atomic swap.
UTXO helper filters UTXOs that can be used during an atomic swap. Currently, the OMG Network has a limitation of only 4 inputs and 4 outputs, thus you can have a maximum of 3 payment inputs (sender, receiver, fee payer) and 1 fee input. For simplicity, we select UTXOs that are equal to the amount requested in a swap operation.
const getUsableUtxos = async (address, currency, spendAmount, filterEqual) => {
const utxos = await childChain.getUtxos(address);
const filteredUtxos = utxos
.filter(utxo => {
return utxo.currency.toLowerCase() === currency.toLowerCase();
})
.filter(utxo => {
const amount = BN.isBN(utxo.amount)
? utxo.amount
: new BN(utxo.amount.toString());
return filterEqual ? amount.eq(spendAmount) : amount.gte(spendAmount);
})
.map(utxo => {
const amount = BN.isBN(utxo.amount)
? utxo.amount
: new BN(utxo.amount.toString());
return {
...utxo, amount
}
});
if (!filteredUtxos.length) {
console.log(`There are no utxos that can cover a payment for ${spendAmount} '${currency}'`);
}
return filteredUtxos;
}
UTXO change helper
UTXO change helper checks if the provided UTXO (payment or fee) needs a change. If the change is needed, the helper creates and pushes an additional output to the existing array of transaction outputs.
const checkUtxoForChange = (address, utxo, amount, transactionBody) => {
if (!utxo || utxo.length === 0) {
throw new Error(`No UTXO provided for ${address}`);
} if (transactionBody.outputs.length > 4) {
throw new Error(`The provided transaction body has 4 outputs. You need to have at least 1 spare output to proceed.`);
} if (utxo.amount.gt(amount)) {
const changeAmount = utxo.amount.sub(amount);
const changeOutput = {
outputType: 1,
outputGuard: address,
currency: utxo.currency,
amount: changeAmount
}
transactionBody.outputs.push(changeOutput);
}
}
Signature helper
Signature helper signs and returns the inputs by the sender, receiver, and fee payer.
To construct an atomic swap transaction, you need to create a custom transaction body. It should contain details about the sender, receiver, fee, fee payer, additional metadata.
The process of submitting an atomic swap transaction is the same as submitting any other transaction, except you pass a custom transaction body created earlier. The transaction described below creates an atomic swap of 400 TUSDT (with 6 decimals) into 100 OMG (18 decimals) with an exchange rate of 4:1.
async function atomicSwap() {
const feeAmount = await getFeeAmount(feePayer.currency);
const amount = "400";
const swapAmountSender = amountToBN(amount, 6);
const feeUtxo = await getUsableUtxos(
feePayer.address,
feePayer.currency,
feeAmount,
false
);
const senderUtxo = await getUsableUtxos(
sender.address,
sender.currency,
swapAmountSender,
true
);
const exchangeRate = amountToBN(4, 6);
const tempSwapAmountReceiver = swapAmountSender.div(exchangeRate);
const swapAmountReceiver = amountToBN(tempSwapAmountReceiver, 18);
const receiverUtxo = await getUsableUtxos(
receiver.address,
receiver.currency,
swapAmountReceiver,
true
);
if (senderUtxo.length > 1) {
console.log("You can have only 1 sender UTXO to cover the atomic swap. To proceed with a transaction, please merge the selected UTXO first.");
}
if (receiverUtxo.length > 1) {
console.log("You can have only 1 receiver UTXO to cover the atomic swap. To proceed with a transaction, please merge the selected UTXO first.");
}
if (feeUtxo.length > 2) {
console.log("You can have only 2 fees UTXO to cover the atomic swap fee. To proceed with a transaction, please merge the selected UTXO first.");
}
else {
const atomicSwapBody = createTransactionBody(
sender,
senderUtxo[0],
receiver,
receiverUtxo[0],
feePayer,
feeAmount,
feeUtxo[0],
"atomic swap"
);
const typedData = OmgUtil.transaction.getTypedData(
atomicSwapBody,
plasmaContractAddress
);
const signatures = getSignatures(
typedData,
sender,
receiver,
feePayer
);
const signedTx = childChain.buildSignedTransaction(
typedData,
signatures
);
const receipt = await childChain.submitTransaction(signedTx);
console.log(receipt);
}
}