Make an Atomic Swap
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.
To access network features from your application, use our official libraries:
Node
Browser
React Native
Requires Node >= 8.11.3 < 13.0.0
1
npm install @omisego/omg-js web3 bn.jsnpm install @omisego/omg-js ethereumjs-util eth-sig-util bn.js bignumber.js
Copied!
You can add omg-js to a website using a script tag:
1
<script src="https://unpkg.com/@omisego/browser-omg-js"></script>
Copied!
You can easily integrate omg-js with React Native projects. First, add this postinstall script to your project's package.json:
1
"scripts": {
2
"postinstall": "omgjs-nodeify"
3
}
4
Copy
Copied!
Then install the react native compatible library:
1
npm install @omisego/react-native-omg-js
Copied!

2. Import dependencies, define constants

1
import BN from "bn.js";
2
import BigNumber from "bignumber.js";
3
import { ChildChain, OmgUtil } from "@omisego/omg-js";
4
const ethUtil = require('ethereumjs-util');
5
const sigUtil = require('eth-sig-util');const childChain = new ChildChain({
6
watcherUrl: watcherUrl,
7
watcherProxyUrl: '',
8
plasmaContractAddress: plasmaContractAddress
9
});
10
const sender = {
11
address: '0x8CB0DE6206f459812525F2BA043b14155C2230C0',
12
privateKey: 'CD55F2A7C476306B27315C7986BC50BD81DB4130D4B5CFD49E3EAF9ED1EDE4F7',
13
currency: OmgUtil.hexPrefix("0xd92e713d051c37ebb2561803a3b5fbabc4962431")
14
}const receiver = {
15
address: '0xA9cc140410c2bfEB60A7260B3692dcF29665c254',
16
privateKey: 'E4F82A4822A2E6A28A6E8CE44490190B15000E58C7CBF62B4729A3FDC9515FD2',
17
currency: OmgUtil.hexPrefix("0x2d453e2a14a00f4a26714a82abfc235c2b2094d5")
18
}const feePayer = {
19
address: '0x001ebfBd3C6D6855bF4EF1a2Ec7f2a779B1028C2',
20
privateKey: '5444fec49a972a1c6264745610ef3c8107980540ff3d732af4c5ddb87c5f03c2',
21
currency: OmgUtil.transaction.ETH_CURRENCY
22
}const plasmaContractAddress = plasmaContractAddress;
Copied!
    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.
1
2
3
const amountToBN = (amount, decimals = 18) => {
4
const multiplier = new BigNumber(10).pow(decimals);
5
const subunit = new BigNumber(amount).times(multiplier).toFixed();
6
return new BN(subunit);
7
}
Copied!

Fee helper

The fee helper retrieves the fee amount you need to pay during an atomic swap.
1
2
const getFeeAmount = async (currency) => {
3
const fees = await childChain.getFees();
4
const selectedFee = fees['1'].find(fee => fee.currency === currency);
5
return BN.isBN(selectedFee.amount)
6
? selectedFee.amount
7
: new BN(selectedFee.amount.toString());
8
}
Copied!

UTXO helper

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.
1
2
const getUsableUtxos = async (address, currency, spendAmount, filterEqual) => {
3
const utxos = await childChain.getUtxos(address);
4
const filteredUtxos = utxos
5
.filter(utxo => {
6
return utxo.currency.toLowerCase() === currency.toLowerCase();
7
})
8
.filter(utxo => {
9
const amount = BN.isBN(utxo.amount)
10
? utxo.amount
11
: new BN(utxo.amount.toString());
12
return filterEqual ? amount.eq(spendAmount) : amount.gte(spendAmount);
13
})
14
.map(utxo => {
15
const amount = BN.isBN(utxo.amount)
16
? utxo.amount
17
: new BN(utxo.amount.toString());
18
return {
19
...utxo, amount
20
}
21
});
22
if (!filteredUtxos.length) {
23
console.log(`There are no utxos that can cover a payment for ${spendAmount} '${currency}'`);
24
}
25
return filteredUtxos;
26
}
Copied!

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.
1
2
const checkUtxoForChange = (address, utxo, amount, transactionBody) => {
3
if (!utxo || utxo.length === 0) {
4
throw new Error(`No UTXO provided for ${address}`);
5
} if (transactionBody.outputs.length > 4) {
6
throw new Error(`The provided transaction body has 4 outputs. You need to have at least 1 spare output to proceed.`);
7
} if (utxo.amount.gt(amount)) {
8
const changeAmount = utxo.amount.sub(amount);
9
const changeOutput = {
10
outputType: 1,
11
outputGuard: address,
12
currency: utxo.currency,
13
amount: changeAmount
14
}
15
transactionBody.outputs.push(changeOutput);
16
}
17
}
Copied!

Signature helper

Signature helper signs and returns the inputs by the sender, receiver, and fee payer.
1
2
const getSignatures = (typedData, sender, receiver, feePayer) => {
3
const toSign = OmgUtil.transaction.getToSignHash(typedData);
4
const senderSignature = getSignature(toSign, sender);
5
const receiverSignature = getSignature(toSign, receiver);
6
const feePayerSignature = getSignature(toSign, feePayer);
7
return [senderSignature, receiverSignature, feePayerSignature];
8
}
9
const getSignature = (toSign, signer) => {
10
const signature = ethUtil.ecsign(
11
toSign,
12
Buffer(signer.privateKey.replace('0x', ''), 'hex')
13
);
14
return sigUtil.concatSig(signature.v, signature.r, signature.s);
15
}
Copied!

3. Create a transaction body

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.
1
2
const createTransactionBody = (
3
sender,
4
senderUtxo,
5
receiver,
6
receiverUtxo,
7
feePayer,
8
feeAmount,
9
feeUtxo,
10
metadata
11
) => {
12
const encodedMetadata = metadata
13
? OmgUtil.transaction.encodeMetadata(metadata)
14
: OmgUtil.transaction.NULL_METADATA;
15
const transactionBody = {
16
inputs: [senderUtxo, receiverUtxo, feeUtxo],
17
outputs: [
18
{
19
outputType: 1,
20
outputGuard: receiver.address,
21
currency: sender.currency,
22
amount: senderUtxo.amount
23
},
24
{
25
outputType: 1,
26
outputGuard: sender.address,
27
currency: receiver.currency,
28
amount: receiverUtxo.amount
29
}
30
],
31
metadata: encodedMetadata
32
}
33
checkUtxoForChange(feePayer.address, feeUtxo, feeAmount, transactionBody);
34
return transactionBody;
35
}
Copied!

4. Submit an atomic swap

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.
1
2
async function atomicSwap() {
3
4
const feeAmount = await getFeeAmount(feePayer.currency);
5
const amount = "400";
6
const swapAmountSender = amountToBN(amount, 6);
7
const feeUtxo = await getUsableUtxos(
8
feePayer.address,
9
feePayer.currency,
10
feeAmount,
11
false
12
);
13
const senderUtxo = await getUsableUtxos(
14
sender.address,
15
sender.currency,
16
swapAmountSender,
17
true
18
);
19
20
const exchangeRate = amountToBN(4, 6);
21
const tempSwapAmountReceiver = swapAmountSender.div(exchangeRate);
22
const swapAmountReceiver = amountToBN(tempSwapAmountReceiver, 18);
23
const receiverUtxo = await getUsableUtxos(
24
receiver.address,
25
receiver.currency,
26
swapAmountReceiver,
27
true
28
);
29
if (senderUtxo.length > 1) {
30
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.");
31
}
32
if (receiverUtxo.length > 1) {
33
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.");
34
}
35
if (feeUtxo.length > 2) {
36
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.");
37
}
38
else {
39
40
const atomicSwapBody = createTransactionBody(
41
sender,
42
senderUtxo[0],
43
receiver,
44
receiverUtxo[0],
45
feePayer,
46
feeAmount,
47
feeUtxo[0],
48
"atomic swap"
49
);
50
const typedData = OmgUtil.transaction.getTypedData(
51
atomicSwapBody,
52
plasmaContractAddress
53
);
54
const signatures = getSignatures(
55
typedData,
56
sender,
57
receiver,
58
feePayer
59
);
60
const signedTx = childChain.buildSignedTransaction(
61
typedData,
62
signatures
63
);
64
const receipt = await childChain.submitTransaction(signedTx);
65
console.log(receipt);
66
}
67
}
Copied!
Last modified 8mo ago