Skip to main content

atomic-swap

Introduction

As explained in BEP3, Hash Timer Locked Contract(HTLC) has been used for Atomic Swap and cross payment channels between different blockchains. BEP3 defines native transactions to support HTLC on BNB Beacon Chain and also proposes the standard infrastructure and procedure to use HTLC for inter-chain atomic swap to easily create and use pegged token. During the swap process, the related fund will be locked to a purely-code-controlled escrow account. A purely-code-controlled escrow account is a kind of account which is derived from a hard-coded string in BNB Beacon Chain protocol. This kind of account doesn't have its own private key and it's only controlled by code of the protocol. The code for calculating escrow account is the same that is used in cosmos-sdk:

AtomicSwapCoinsAccAddr = sdk.AccAddress(crypto.AddressHash([]byte("BinanceChainAtomicSwapCoins")))

The account for mainnet is: bnb1wxeplyw7x8aahy93w96yhwm7xcq3ke4f8ge93u and the account for testnet is: tbnb1wxeplyw7x8aahy93w96yhwm7xcq3ke4ffasp3d. Once the swap is claimed or refunded, the fund will be transferred from the purely-code-controlled escrow account to client accounts.

Commands

Hash Timer Locked Transfer

Hash Timer Locked Transfer (HTLT) is a new transaction type on BNB Beacon Chain, to serve as HTLC in the first step of Atomic Swap,

Parameters

NameTypeDescriptionOptional
FromAddressSender address, where the asset is fromNo
recipient-addrAddressReceiver address, where the asset is to, if the proper condition meets.No
recipient-other-chainbytesa byte array, maximum 32 bytes, in any proper encoding. leave it empty for single chain swapYes
sender-other-chainbytesa byte array, maximum 32 bytes, in any proper encoding. leave it empty for single chain swapYes
RandomNumberHash32 byteshash of a random number and timestamp, based on SHA256. If left out, a random value will be generatedTrue
Timestampint64Supposed to be the time of sending transaction, counted by second. It should be identical to the one in swap contract. If left out, current timestamp will be used.No
OutAmountCoinssimilar to the Coins in the original Transfer defined in BEP2, assets to swap outNo
ExpectedIncomestringExpected income from swap counter party, example: "100:BNB" or "100:BNB,10000:BTCB-1DE" The amount needs to be bumped by e^8No
HeightSpanint64number of blocks to wait before the asset may be returned to From if not claimed via Random. The number must be larger than or equal to 360 (>2 minutes), and smaller than 518400 (< 48 hours)No
CrossChainboolSpecify if the HTLT is for cross chain atomic swapTrue, the default value is False

Outputs

NameTypeDescription
Random number32 bytes
Timestampint64
Random number hash32 bytes
Swap ID32 bytes

Examples

  1. Swap between BEP2 tokens
  • On testnet:

Command line

./tbnbcli token HTLT --recipient-addr <recipient-addr> --amount 100:BNB --expected-income <expectedIncome> --height-span <heightSpan> --from <from-addr> --chain-id Binance-Chain-Ganges --trust-node --node http://data-seed-pre-0-s3.bnbchain.org:80

Javascript

  const client = new BncClient("https://testnet-dex.binance.org")
const privateKey = crypto.getPrivateKeyFromMnemonic(mnemonic)
client.setPrivateKey(privateKey)
const from = "tbnb1hgm0p7khfk85zpz5v0j8wnej3a90w709zzlffd"// sender address
const recipient = "tbnb1prrujx8kkukrcrppklggadhuvegfnx8pemsq77"// recipient address
const randomNumber = "e8eae926261ab77d018202434791a335249b470246a7b02e28c3b2fb6ffad8f3"// 32 bytes random number
const timestamp = Math.floor(Date.now()/1000)// take the current timestamp
const randomNumberHash = calculateRandomNumberHash(randomNumber, timestamp)
const amount = [{
denom: "BNB",
amount: 100
}]
const expectedIncome = "100:BNB"// expected income
const heightSpan = 400// height span
const res = client.swap.HTLT(from, recipient, "", "", randomNumberHash, timestamp, amount, expectedIncome, heightSpan, false)

Example output:

Please take a note of returned swapID:

Random number: 927c1ac33100bdbb001de19c626a05a7c3c11304fc825f5eabb22e741507711b
Timestamp: 1568792486
Random number hash: 5768702259ee55983378d7b8207890c666648264524b9dada551386f832ba6b1
Password to sign with 'guest':
Committed at block 39984169 (
tx hash: B5A3DD92A40E98745BBE9F608944FE5511B81071B34E9947A754A04A5F378A85,
response: {
Code:0
Data:[77 137 139 200 85 141 170 77 129 116 134 215 169 59 119 178 200 47 206 194 18 58 191 74 30 183 210 82 18 55 236 205]
Log:Msg 0: swapID: 4d898bc8558daa4d817486d7a93b77b2c82fcec2123abf4a1eb7d2521237eccd
Info: GasWanted:0 GasUsed:0
...
)

Besides, the Data field in the committed result is the byte array of swapID:

Data:[77 137 139 200 85 141 170 77 129 116 134 215 169 59 119 178 200 47 206 194 18 58 191 74 30 183 210 82 18 55 236 205]

swapID: 4d898bc8558daa4d817486d7a93b77b2c82fcec2123abf4a1eb7d2521237eccd
  1. Swap from BNB Beacon Chain to Ethereum
  • Clients send HTLT on BNB Beacon Chain on testnet:

Command line:

./tbnbcli token HTLT --from <from-addr> --chain-id Binance-Chain-Ganges  --height-span <heightSpan> --amount <amount> --expected-income <expectedIncome> --recipient-addr <deputy-bep2-addr>  --recipient-other-chain <client ethereum address>  --cross-chain --trust-node --node http://data-seed-pre-0-s3.bnbchain.org:80

Javascript:

  const client = new BncClient("https://testnet-dex.binance.org")
const privateKey = crypto.getPrivateKeyFromMnemonic(mnemonic)
client.setPrivateKey(privateKey)
const from = "tbnb1hgm0p7khfk85zpz5v0j8wnej3a90w709zzlffd"// sender address
const recipient = "tbnb1prrujx8kkukrcrppklggadhuvegfnx8pemsq77"// recipient address
const recipientOtherChain="0x37B8516a0F88E65D677229b402ec6C1e0E333004"//client ethereum address
const randomNumber = "e8eae926261ab77d018202434791a335249b470246a7b02e28c3b2fb6ffad8f3"// 32 bytes random number
const timestamp = Math.floor(Date.now()/1000)// take the current timestamp
const randomNumberHash = calculateRandomNumberHash(randomNumber, timestamp)
const amount = [{
denom: "BNB",
amount: 100
}] // swap out token amount
const expectedIncome = "100:BNB"// expected income
const heightSpan = 400 // height span
const res = client.swap.HTLT(from, recipient, recipientOtherChain, "", randomNumberHash, timestamp, amount, expectedIncome, heightSpan, true)
  1. Swap from Ethereum to BNB Beacon Chain

Note: Once cross-chain is true, --recipient-other-chain must not be empty

  • Deputy send HTLT on BNB Beacon Chain on testnet:

Command line:

./tbnbcli token HTLT --from  <from-addr> --chain-id Binance-Chain-Ganges --height-span  <heightSpan>  --amount <amount> --expected-income <expectedIncome> --recipient-other-chain <deputy ethereum address> --sender-other-chain <client ethereum address> --recipient-addr <client bep2 address> --cross-chain --trust-node --node http://data-seed-pre-0-s3.bnbchain.org:80

Javascript:

  const client = new BncClient("https://testnet-dex.binance.org")
const privateKey = crypto.getPrivateKeyFromMnemonic(mnemonic)
client.setPrivateKey(privateKey)
const from = "tbnb1hgm0p7khfk85zpz5v0j8wnej3a90w709zzlffd"// sender address
const recipient = "tbnb1prrujx8kkukrcrppklggadhuvegfnx8pemsq77"// recipient address
const recipientOtherChain="0xfA5E36a04EeF3152092099F352DDbe88953bB540"//client ethereum address
const senderOtherChain="0x37B8516a0F88E65D677229b402ec6C1e0E333004" //client ethereum address
const randomNumberHash = "6632eda86c4f19190c8a986e188526eee865e1ce2758ba59c8bf45e20ffa3bb5" //deputy get this value from the event log of swap contract
const timestamp = 1571383800 //deputy get this value from the event log of swap contract
const amount = [{
denom: "BNB",
amount: 100
}] // swap out token amount
const expectedIncome = "100:BNB"// expected income
const heightSpan = 400 // height span
const res = client.swap.HTLT(from, recipient, recipientOtherChain, senderOtherChain, randomNumberHash, timestamp, amount, expectedIncome, heightSpan, true)

Deposit HTLT

Deposit Hash Timer Locked Transfer is to lock new BEP2 asset to an existed HTLT which is for single chain atomic swap.

Parameters

NameTypeDescriptionOptional
FromAddressSender address, where the assets are fromNo
SwapID32 bytesID of previously created swap, hex encodingNo
AmountCoinsThe swapped out amount BEP2 tokens, example: "100:BNB" or "100:BNB,10000:BTCB-1DE"No

Examples

  • On testnet:

Command line:

./tbnbcli token deposit --swap-id <swapID>  --amount 10000:TEST-599 --from <from-key> --chain-id Binance-Chain-Ganges --trust-node --node http://data-seed-pre-0-s3.bnbchain.org:80

Javascript:

  const client = new BncClient("https://testnet-dex.binance.org")
const privateKey = crypto.getPrivateKeyFromMnemonic(mnemonic)
client.setPrivateKey(privateKey)
const from = "tbnb1hgm0p7khfk85zpz5v0j8wnej3a90w709zzlffd"// sender address
const swapID = "61daf59e977c5f718f5aaedeaf69ccbea1c376db5274a84bca88848696164ffe" // the ID of an existing swap
const amount = [{
denom: "TEST-599",
amount: 10000
}]
const res = client.swap.depositHTLT(from, swapID, amount)

Example output

Committed at block 39984686 (tx hash: AA118F7CFCB3FFF86EF5EED8D2B9ADEAC5D9F242497910DAA232BDE5F6A84C1E, response: {Code:0 Data:[] Log:Msg 0:  Info: GasWanted:0 GasUsed:0 Tags:[{Key:[115 101 110 100 101 114] Value:[116 98 110 98 49 110 107 120 57 57 52 113 118 113 109 113 103 107 53 55 118 103 117 113 104 54 122 106 108 97 99 113 122 120 100 107 117 101 53 122 106 121 120] XXX_NoUnkeyedLiteral:{} XXX_unrecognized:[] XXX_sizecache:0} {Key:[114 101 99 105 112 105 101 110 116] Value:[116 98 110 98 49 119 120 101 112 108 121 119 55 120 56 97 97 104 121 57 51 119 57 54 121 104 119 109 55 120 99 113 51 107 101 52 102 102 97 115 112 51 100] XXX_NoUnkeyedLiteral:{} XXX_unrecognized:[] XXX_sizecache:0} {Key:[97 99 116 105 111 110] Value:[100 101 112 111 115 105 116 72 84 76 84] XXX_NoUnkeyedLiteral:{} XXX_unrecognized:[] XXX_sizecache:0}] Codespace: XXX_NoUnkeyedLiteral:{} XXX_unrecognized:[] XXX_sizecache:0})

After the deposit, you may observe that the balance of sender is decreased. The amount in deposit transaction must be positive. Besides, you can query the swap by swapID and the in_amount must equal to the amount that you balance decreased.

Claim HTLT

Claim Hash Timer Locked Transfer is to claim the locked asset by showing the random number value that matches the hash. Each HTLT locked asset is guaranteed to be release once.

Parameters

NameTypeDescriptionOptional
FromAddressSender addressNo
SwapID32 bytesID of previously created swap, hex encodingNo
RandomNumber32 bytesThe random number to unlock the locked hash, 32 bytes, hex encodingNo

Examples

  • On testnet:

Command line:

./tbnbcli token claim --swap-id  <swapID> --random-number <random-number> --from <from-key> --chain-id Binance-Chain-Ganges --trust-node --node http://data-seed-pre-0-s3.bnbchain.org:80

Javascript:

  const client = new BncClient("https://testnet-dex.binance.org")
const privateKey = crypto.getPrivateKeyFromMnemonic(mnemonic)
client.setPrivateKey(privateKey)
const swapID = "61daf59e977c5f718f5aaedeaf69ccbea1c376db5274a84bca88848696164ffe" // the ID of an existing swap
const randomNumber = "e8eae926261ab77d018202434791a335249b470246a7b02e28c3b2fb6ffad8f3" // the random number generated in htlt
const res = client.swap.claimHTLT(from, swapID, randomNumber)

Example output:

Committed at block 39984971 (tx hash: 15B8625E0247DE54700D3C5C110BE0CE279D33CC13A73845F3E0305758A40902, response: {Code:0 Data:[] Log:Msg 0:  Info: GasWanted:0 GasUsed:0 Tags:[{Key:[115 101 110 100 101 114] Value:[116 98 110 98 49 119 120 101 112 108 121 119 55 120 56 97 97 104 121 57 51 119 57 54 121 104 119 109 55 120 99 113 51 107 101 52 102 102 97 115 112 51 100] XXX_NoUnkeyedLiteral:{} XXX_unrecognized:[] XXX_sizecache:0} {Key:[114 101 99 105 112 105 101 110 116] Value:[116 98 110 98 49 110 107 120 57 57 52 113 118 113 109 113 103 107 53 55 118 103 117 113 104 54 122 106 108 97 99 113 122 120 100 107 117 101 53 122 106 121 120] XXX_NoUnkeyedLiteral:{} XXX_unrecognized:[] XXX_sizecache:0} {Key:[115 101 110 100 101 114] Value:[116 98 110 98 49 119 120 101 112 108 121 119 55 120 56 97 97 104 121 57 51 119 57 54 121 104 119 109 55 120 99 113 51 107 101 52 102 102 97 115 112 51 100] XXX_NoUnkeyedLiteral:{} XXX_unrecognized:[] XXX_sizecache:0} {Key:[114 101 99 105 112 105 101 110 116] Value:[116 98 110 98 49 103 57 114 122 99 48 101 50 106 102 56 101 102 51 113 112 57 97 120 56 104 48 112 109 112 109 118 106 122 119 109 116 113 52 106 120 102 114] XXX_NoUnkeyedLiteral:{} XXX_unrecognized:[] XXX_sizecache:0} {Key:[97 99 116 105 111 110] Value:[99 108 97 105 109 72 84 76 84] XXX_NoUnkeyedLiteral:{} XXX_unrecognized:[] XXX_sizecache:0}] Codespace: XXX_NoUnkeyedLiteral:{} XXX_unrecognized:[] XXX_sizecache:0})

Refund HTLT

Refund Hash Timer Locked Transfer is to refund the locked asset after timelock is expired.

Parameters

NameTypeDescriptionOptional
FromAddressSender addressNo
SwapID32 bytesID of previously created swap, hex encodingNo

Examples

  • On testnet:

Command line:

./tbnbcli token refund --swap-id <swapID> --from <from-key>  --chain-id Binance-Chain-Ganges --trust-node --node http://data-seed-pre-0-s3.bnbchain.org:80

Javascript:

  const client = new BncClient("https://testnet-dex.binance.org")
const privateKey = crypto.getPrivateKeyFromMnemonic(mnemonic)
client.setPrivateKey(privateKey)
const swapID = "61daf59e977c5f718f5aaedeaf69ccbea1c376db5274a84bca88848696164ffe" // the ID of an existing swap
const res = client.swap.refundHTLT(from, swapID, randomNumber)

Common error:

  • Already complete
ERROR: {"codespace":8,"code":12,"abci_code":524300,"message":"Expected swap status is Open, actually it is Completed"}
  • Not expired
ERROR: {"codespace":8,"code":8,"abci_code":524296,"message":"Current block height is 40003412, the expire height (40013236) is still not reached"}

Query Atomic Swap

Query atomic swap allows you to search swap information by swapID

Parameters

NameTypeDescriptionOptional
SwapID32 bytesID of previously created swap, hex encodingNo

Examples

  • On testnet:
./tbnbcli token query-swap --swap-id <swapID> --chain-id Binance-Chain-Ganges --trust-node --node http://data-seed-pre-0-s3.bnbchain.org:80

Expected output

{
"from": "tbnb1g9rzc0e2jf8ef3qp9ax8h0pmpmvjzwmtq4jxfr",
"to": "tbnb1nkx994qvqmqgk57vguqh6zjlacqzxdkue5zjyx",
"out_amount": [
{
"denom": "BNB",
"amount": "100"
}
],
"in_amount": [
{
"denom": "TEST-599",
"amount": "10000"
}
],
"expected_income": "10000:TEST-599",
"recipient_other_chain": "",
"random_number_hash": "5768702259ee55983378d7b8207890c666648264524b9dada551386f832ba6b1",
"random_number": "927c1ac33100bdbb001de19c626a05a7c3c11304fc825f5eabb22e741507711b",
"timestamp": "1568792486",
"cross_chain": false,
"expire_height": "39994169",
"index": "53",
"closed_time": "1568792927",
"status": "Completed"
}

Query Atomic Swap ID By Recipient

Query atomic swap ID allows you to search swap history of an recipient. As this is a heavy query interface, some public nodes might close this query interface.

Parameters

NameTypeDescriptionOptional
recipient-addrAddressSwap recipient addressNo

Examples

  • On testnet:
./tbnbcli token query-swapIDs-by-recipient  --recipient-addr <address> --chain-id Binance-Chain-Ganges --trust-node --node http://data-seed-pre-0-s3.bnbchain.org:80

Example output:

[
"4d898bc8558daa4d817486d7a93b77b2c82fcec2123abf4a1eb7d2521237eccd",
"e7cc2e2eb025cc4617ff0bb84fcffc973d7ba34f15dbc51383fe3543ff143e9c"
]

Query Atomic Swap ID By Creator

Query atomic swap ID allows you to search swap history of an initiator. As this is a heavy query interface, some public nodes might close this query interface.

Parameters

NameTypeDescriptionOptional
creator-addrAddressSwap creator addressNo

Examples

  • On testnet:
./tbnbcli token query-swapIDs-by-creator  --creator-addr <address> --chain-id Binance-Chain-Ganges --trust-node --node http://data-seed-pre-0-s3.bnbchain.org:80

Example output:

[
"7341d4ea0519af90d98f60fee45fdc7e385621875ea982bc8caf1fd7a49af8c3",
"290664c1e8123966d8f9050fdc9d93e94b0e51b36e2e2a6978e492d3796423f1",
"b260dad3cf63e558fe102a050afbe52d5dd2e30c7db76da33d02ce5f85d07fcf",
"2b532bf9171c4d33d80fc4a8d6603581a86345b41552337482224d8476fcf5f7",
"20d22bbfa579520f0ba79cd176fb2b06aa8dbe5b0a6ba8c9b761129f6a42a94c"
]

Fees

Transaction TypePay in Non-BNB AssetPay in BNBExchange (DEX) Related
HTLTN/A0.000375 BNBY
depositHTLTN/A0.000375 BNBY
claimHTLTN/A0.000375 BNBY
refundHTLTN/A0.000375 BNBY

Workflows

Preparations

  1. Deploy smart-contract which supports Atomic Peg Swap (APS), there is already one example for Ethereum
  2. Deploy deputy process for handling swap activities by token owners, there is an existing open-source solution here: https://github.com/bnb-chain/bep3-deputy
  3. Issue and transfer enough tokens

Testnet Deployment

Swap Tokens from Ethereum to BNB Beacon Chain

image-20190918193751444

1. Approve Swap Transaction

Go to this page and approve some amount of tokens.

  • Function: Approve
  • Parameters:
    • _spender: address of the smartcontract, which is 0x12DCBf79BE178479870A473A99d91f535ed960AD
    • _value: approved amount, should be bumped by e^10

Note: Please approve more than 1 token. In the following example, 100 PPC token was approved:

Example of approve 100 PPC on ropsten testnet

2. Call HTLT function From Ethereum

Go to smartcontract and call HTLT function

  • Function: htlt
  • Parameters:
    • _randomNumberHash: SHA256(randomNumber||timestamp), randomNumber is 32-length random byte array
    • _timestamp: it should be about 10 mins span around current timestamp
    • _heightSpan: it's a customized filed for deputy operator. it should be more than 200 for this deputy.
    • _recipientAddr: deputy address on Ethereum, it's 0x1C002969Fe201975eD8F054916b071672326858e for this one
    • _bep2SenderAddr: omit this field with 0x0
    • _bep2RecipientAddr: Decode your testnet address from bech32 encoded to hex, for example: 0xc41f2a85e1d3629637de1222017dce46c6c8e4b9
    • _outAmount: approved amount, should be bumped by e^10
    • _bep2Amount: _outAmount * exchange rate, the default rate is 1

Example of htlt transaction

3. Deputy Call HTLT on BNB Beacon Chain

Then, Deputy will send HTLT transaction here

4. Claim HTLT on BNB Beacon Chain

  • Get the swapID on BNB Beacon Chain
./tbnbcli token query-swapIDs-by-recipient  --recipient-addr tbnb1cs0j4p0p6d3fvd77zg3qzlwwgmrv3e9e63423w --chain-id Binance-Chain-Ganges --trust-node --node http://data-seed-pre-0-s3.bnbchain.org:80
[
"12aacc3bdc2cef97e8e45cc9b409796df57904a4e9c76863ad8420ff75f13128"
]

You can also get swapID by calculateSwapID in javascript-sdk. It requires three parameters:

NameTypeDescriptionExample
randomNumberHashstringrandomNumberHash in client HTLT transaction on Ethereum5a3728a8f4ecb8b4cb0b983a9441b7d69f95229c4aa531e6e3827d7c19beac82
senderstringdeputy bep2 addresstbnb1pk45lc2k7lmf0pnfa59l0uhwrvpk8shsema7gr
senderOtherChainstringclient ethereum address0x133d144f52705ceb3f5801b63b9ebccf4102f5ed
  • Query the swap by swapID
{
"from": "tbnb1pk45lc2k7lmf0pnfa59l0uhwrvpk8shsema7gr",
"to": "tbnb1cs0j4p0p6d3fvd77zg3qzlwwgmrv3e9e63423w",
"out_amount": [
{
"denom": "PPC-00A",
"amount": "9999999000"
}
],
"in_amount": null,
"expected_income": "",
"recipient_other_chain": "0x1C002969Fe201975eD8F054916b071672326858e",
"random_number_hash": "5a3728a8f4ecb8b4cb0b983a9441b7d69f95229c4aa531e6e3827d7c19beac82",
"random_number": "",
"timestamp": "1569497984",
"cross_chain": true,
"expire_height": "41380567",
"index": "1947",
"closed_time": "",
"status": "Open"
}
  • Verify parameters in the swap:

    • random_number_hash must equal to the randomNumberHash in client HTLT transaction on ethereum
    • to must equals to client wallet address
    • timestamp must equal to the timestamp in client HTLT transaction on ethereum
    • out_amount should be reasonable. Please note that the decimals of bep2 tokens is 8, the out_amount should be something around 10000000000:PPC, deputy will deduct some fees.
    • expire_height must not be passed and should be enough for send claim transaction
  • Send claim transaction on BNB Beacon Chain

./tbnbcli token claim --swap-id  12aacc3bdc2cef97e8e45cc9b409796df57904a4e9c76863ad8420ff75f13128  --random-number <random-number> --from <from-key>  --chain-id Binance-Chain-Ganges --trust-node --node http://data-seed-pre-0-s3.bnbchain.org:80

Example of claim tx on testnet

5. Deputy Claim ERC20 Token

Deputy will claim ERC20 tokens afterwards with claim transaction

6. Demo for Client APP: swap erc20 to bep2

This is a javascript implementation for client app to swap PPC to PPC-00A with deputy.

  const erc20ContractAddr = "0xd93395b2771914e1679155f3ea58c41d89d96098"
const swapContractAddr = "0x12DCBf79BE178479870A473A99d91f535ed960AD"

const deputyEthWalletAddr = "0x1C002969Fe201975eD8F054916b071672326858e"
const deputyBNBWalletAddr = "tbnb1pk45lc2k7lmf0pnfa59l0uhwrvpk8shsema7gr"

const clientEthWalletAddr = "0xfA5E36a04EeF3152092099F352DDbe88953bB540"
const clientEthWalletKey = new Buffer("89A0F0E0732ACAA7AD37C9E6D7A9798ECCE6940C63FF0290A58B1C1C1697486A", "hex")

const clientBnbWalletAddr = "tbnb17vwyu8npjj5pywh3keq2lm7d4v76n434pwd8av"
const clientBnbWalletMnemonic = "lawsuit margin siege phrase fabric matrix like picnic day thrive correct velvet stool type broom upon flee fee ten senior install wrestle soap sick"

const web3 = new Web3(new Web3.providers.HttpProvider("https://ropsten.infura.io/v3/1c5b38a27f92410cb5feb13b6efb2e14"))
const bnbClient = new BncClient("https://testnet-dex.binance.org")
await bnbClient.initChain()
bnbClient.setPrivateKey(crypto.getPrivateKeyFromMnemonic(clientBnbWalletMnemonic))
bnbClient.useDefaultSigningDelegate()
bnbClient.useDefaultBroadcastDelegate()
const bnbRPC = new rpcClient("https://data-seed-pre-2-s1.bnbchain.org/", "testnet")

const erc20Contract = new web3.eth.Contract([{"constant":true,"inputs":[],"name":"name","outputs":[{"name":"","type":"string"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[{"name":"_spender","type":"address"},{"name":"_value","type":"uint256"}],"name":"approve","outputs":[{"name":"success","type":"bool"}],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":false,"inputs":[{"name":"_evilUser","type":"address"}],"name":"addBlackList","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[],"name":"totalSupply","outputs":[{"name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[{"name":"_from","type":"address"},{"name":"_to","type":"address"},{"name":"_value","type":"uint256"}],"name":"transferFrom","outputs":[{"name":"success","type":"bool"}],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[],"name":"decimals","outputs":[{"name":"","type":"uint8"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[{"name":"_spender","type":"address"},{"name":"_addedValue","type":"uint256"}],"name":"increaseAllowance","outputs":[{"name":"success","type":"bool"}],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":false,"inputs":[],"name":"unpause","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[{"name":"account","type":"address"}],"name":"isPauser","outputs":[{"name":"","type":"bool"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[{"name":"_maker","type":"address"}],"name":"getBlackListStatus","outputs":[{"name":"","type":"bool"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"paused","outputs":[{"name":"","type":"bool"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[],"name":"renouncePauser","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[{"name":"who","type":"address"}],"name":"balanceOf","outputs":[{"name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[],"name":"renounceOwnership","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":false,"inputs":[{"name":"account","type":"address"}],"name":"addPauser","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":false,"inputs":[],"name":"pause","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[],"name":"owner","outputs":[{"name":"","type":"address"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"isOwner","outputs":[{"name":"","type":"bool"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"symbol","outputs":[{"name":"","type":"string"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[{"name":"_spender","type":"address"},{"name":"_subtractedValue","type":"uint256"}],"name":"decreaseAllowance","outputs":[{"name":"success","type":"bool"}],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":false,"inputs":[{"name":"_to","type":"address"},{"name":"_value","type":"uint256"}],"name":"transfer","outputs":[{"name":"success","type":"bool"}],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":false,"inputs":[{"name":"amount","type":"uint256"}],"name":"issue","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":false,"inputs":[{"name":"amount","type":"uint256"}],"name":"redeem","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[{"name":"_owner","type":"address"},{"name":"_spender","type":"address"}],"name":"allowance","outputs":[{"name":"remaining","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[{"name":"","type":"address"}],"name":"isBlackListed","outputs":[{"name":"","type":"bool"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[{"name":"_clearedUser","type":"address"}],"name":"removeBlackList","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":false,"inputs":[{"name":"newOwner","type":"address"}],"name":"transferOwnership","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":false,"inputs":[{"name":"_blackListedUser","type":"address"}],"name":"destroyBlackFunds","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"inputs":[{"name":"_initialSupply","type":"uint256"},{"name":"_name","type":"string"},{"name":"_symbol","type":"string"},{"name":"_decimals","type":"uint8"}],"payable":false,"stateMutability":"nonpayable","type":"constructor"},{"anonymous":false,"inputs":[{"indexed":false,"name":"amount","type":"uint256"}],"name":"Issue","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"name":"amount","type":"uint256"}],"name":"Redeem","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"name":"_blackListedUser","type":"address"},{"indexed":false,"name":"_balance","type":"uint256"}],"name":"DestroyedBlackFunds","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"name":"_user","type":"address"}],"name":"AddedBlackList","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"name":"_user","type":"address"}],"name":"RemovedBlackList","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"name":"account","type":"address"}],"name":"Paused","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"name":"account","type":"address"}],"name":"Unpaused","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"name":"from","type":"address"},{"indexed":true,"name":"to","type":"address"},{"indexed":false,"name":"value","type":"uint256"}],"name":"Transfer","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"name":"owner","type":"address"},{"indexed":true,"name":"spender","type":"address"},{"indexed":false,"name":"value","type":"uint256"}],"name":"Approval","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"name":"previousOwner","type":"address"},{"indexed":true,"name":"newOwner","type":"address"}],"name":"OwnershipTransferred","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"name":"account","type":"address"}],"name":"PauserAdded","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"name":"account","type":"address"}],"name":"PauserRemoved","type":"event"}],erc20ContractAddr)
const swapContract = new web3.eth.Contract([{"constant":true,"inputs":[],"name":"ERC20ContractAddr","outputs":[{"name":"","type":"address"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[{"name":"_swapID","type":"bytes32"}],"name":"isSwapExist","outputs":[{"name":"","type":"bool"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[{"name":"_swapID","type":"bytes32"}],"name":"refund","outputs":[{"name":"","type":"bool"}],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[{"name":"_randomNumberHash","type":"bytes32"},{"name":"_swapSender","type":"address"},{"name":"_bep2SenderAddr","type":"bytes20"}],"name":"calSwapID","outputs":[{"name":"","type":"bytes32"}],"payable":false,"stateMutability":"pure","type":"function"},{"constant":false,"inputs":[{"name":"_swapID","type":"bytes32"},{"name":"_randomNumber","type":"bytes32"}],"name":"claim","outputs":[{"name":"","type":"bool"}],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":false,"inputs":[{"name":"_randomNumberHash","type":"bytes32"},{"name":"_timestamp","type":"uint64"},{"name":"_heightSpan","type":"uint256"},{"name":"_recipientAddr","type":"address"},{"name":"_bep2SenderAddr","type":"bytes20"},{"name":"_bep2RecipientAddr","type":"bytes20"},{"name":"_outAmount","type":"uint256"},{"name":"_bep2Amount","type":"uint256"}],"name":"htlt","outputs":[{"name":"","type":"bool"}],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[{"name":"_swapID","type":"bytes32"}],"name":"claimable","outputs":[{"name":"","type":"bool"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[{"name":"_swapID","type":"bytes32"}],"name":"refundable","outputs":[{"name":"","type":"bool"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[{"name":"_swapID","type":"bytes32"}],"name":"queryOpenSwap","outputs":[{"name":"_randomNumberHash","type":"bytes32"},{"name":"_timestamp","type":"uint64"},{"name":"_expireHeight","type":"uint256"},{"name":"_outAmount","type":"uint256"},{"name":"_sender","type":"address"},{"name":"_recipient","type":"address"}],"payable":false,"stateMutability":"view","type":"function"},{"inputs":[{"name":"_erc20Contract","type":"address"}],"payable":false,"stateMutability":"nonpayable","type":"constructor"},{"anonymous":false,"inputs":[{"indexed":true,"name":"_msgSender","type":"address"},{"indexed":true,"name":"_recipientAddr","type":"address"},{"indexed":true,"name":"_swapID","type":"bytes32"},{"indexed":false,"name":"_randomNumberHash","type":"bytes32"},{"indexed":false,"name":"_timestamp","type":"uint64"},{"indexed":false,"name":"_bep2Addr","type":"bytes20"},{"indexed":false,"name":"_expireHeight","type":"uint256"},{"indexed":false,"name":"_outAmount","type":"uint256"},{"indexed":false,"name":"_bep2Amount","type":"uint256"}],"name":"HTLT","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"name":"_msgSender","type":"address"},{"indexed":true,"name":"_recipientAddr","type":"address"},{"indexed":true,"name":"_swapID","type":"bytes32"},{"indexed":false,"name":"_randomNumberHash","type":"bytes32"}],"name":"Refunded","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"name":"_msgSender","type":"address"},{"indexed":true,"name":"_recipientAddr","type":"address"},{"indexed":true,"name":"_swapID","type":"bytes32"},{"indexed":false,"name":"_randomNumberHash","type":"bytes32"},{"indexed":false,"name":"_randomNumber","type":"bytes32"}],"name":"Claimed","type":"event"}], swapContractAddr)

//--------------------------------------------
//Step1 approve erc20 to swap contract address
//--------------------------------------------
const approveData = erc20Contract.methods.increaseAllowance(swapContractAddr, 10000000000).encodeABI()
let nonce = await web3.eth.getTransactionCount(clientEthWalletAddr, 'pending')
let gasPrice = await web3.eth.getGasPrice()
let gasLimit = 3000000
let rawTx = {
nonce: web3.utils.toHex(nonce),
gasPrice: web3.utils.toHex(gasPrice),
gasLimit: web3.utils.toHex(gasLimit),
to: erc20ContractAddr,
value: '0x00',
data: approveData
}
var ethereumjs = require('ethereumjs-tx')
var signTx = new ethereumjs(rawTx)
signTx.sign(clientEthWalletKey)
var serializedTx = signTx.serialize();
web3.eth.sendSignedTransaction('0x' + serializedTx.toString('hex')).on('receipt', console.log)
await wait(20000)
//----------------------------------------------------------------------------
//Step2 call swap contract to send htlt transaction on Ethereum
//----------------------------------------------------------------------------
const randomNumber = "e8eae926261ab77d018202434791a335249b470246a7b02e28c3b2fb6ffad8f3"
const timestamp = Math.floor(Date.now()/1000)
const randomNumberHash = calculateRandomNumberHash(randomNumber, timestamp).toString("hex")
const heightSpan = 1000
const hexEncodingClientBNBaddr = '0x'+crypto.decodeAddress(clientBnbWalletAddr).toString("hex")
const amount = 10000000000 // 10000000000:PPC, decimal is 10
const expectedIncome = 99999000 //"99999000:PPC-00A", decimal is 8, deputy will deduct swap fee, the swap fee is 1000:PPC-00A

const htltData = swapContract.methods.htlt("0x"+randomNumberHash, timestamp, heightSpan, deputyEthWalletAddr, "0x0", hexEncodingClientBNBaddr, amount, expectedIncome).encodeABI()
nonce = await web3.eth.getTransactionCount(clientEthWalletAddr, 'pending')
gasPrice = await web3.eth.getGasPrice()
gasLimit = 3000000
rawTx = {
nonce: web3.utils.toHex(nonce),
gasPrice: web3.utils.toHex(gasPrice),
gasLimit: web3.utils.toHex(gasLimit),
to: swapContractAddr,
value: '0x00',
data: htltData
}
ethereumjs = require('ethereumjs-tx')
signTx = new ethereumjs(rawTx)
signTx.sign(clientEthWalletKey)
serializedTx = signTx.serialize();
web3.eth.sendSignedTransaction('0x' + serializedTx.toString('hex')).on('receipt', console.log)
await wait(20000)

//----------------------------------------------------------------------------
//Step3 query swap created by deputy on BNB Beacon Chain and verify swap parameters
//----------------------------------------------------------------------------
const swapID = calculateSwapID(randomNumberHash.replace("0x", ""), deputyBNBWalletAddr, clientEthWalletAddr).toString()
console.log(swapID)
let atomicSwapList = await bnbClient.getSwapByRecipient(clientBnbWalletAddr,1000, 0)
while (atomicSwapList.result.atomicSwaps[0].swapId != swapID) {
console.log("Waiting for the atomic swap created by deputy")
await wait(5000)
atomicSwapList = await bnbClient.getSwapByRecipient(clientBnbWalletAddr,1000, 0)
}
const atomicSwap = await bnbClient.getSwapByID(swapID)
console.log(atomicSwap)
const status = await bnbRPC.status()
expect(atomicSwap.result.toAddr).toBe(clientBnbWalletAddr)
expect(atomicSwap.result.randomNumberHash).toBe(randomNumberHash.replace("0x", ""))
expect(atomicSwap.result.timestamp).toBe(timestamp)
expect(atomicSwap.result.outAmount).toBe("99999000:PPC-00A")
expect(Number(atomicSwap.result.expireHeight)).toBeGreaterThan(Number(status.sync_info.latest_block_height)+100)

//----------------------------------------------------------------------------
//Step4 claim on BNB Beacon Chain
//----------------------------------------------------------------------------
const res = await bnbClient.swap.claimHTLT(clientBnbWalletAddr, swapID, randomNumber)
console.log(res)

//----------------------------------------------------------------------------
//If step3 or step4 are failed and the expire height on Ethereum is passed, try to call refund method on Ethereum
//----------------------------------------------------------------------------

Swap Tokens from BNB Beacon Chain to Ethereum

image-20190918193910521

1. Send HTLT Transaction from BNB Beacon Chain

Please read this section to generate a valid HTLT transaction. Please write down the randomNumber and randomNumberHash.

./tbnbcli token HTLT --from atomic --recipient-addr tbnb1pk45lc2k7lmf0pnfa59l0uhwrvpk8shsema7gr  --chain-id Binance-Chain-Ganges  --height-span 10000 --amount  9900000000:PPC-00A  --expected-income 9900000000:PPC  --recipient-other-chain 0x133D144F52705cEb3f5801B63b9EBcCF4102f5Ed  --cross-chain --trust-node --node http://data-seed-pre-0-s3.bnbchain.org:80
Random number: 4811959406ea3e69721d944d308880ec41323b7f89e51a78df3693348779315e
Timestamp: 1569578936
Random number hash: b03f256c9efdb97b9815faa1417e1da4cca7672e0bb26e4e7d9bfc82d0f1f15e
Committed at block 634510 (
tx hash: 9DEF124E12DE123BA1CC75AA6E68F20CC48EBBE9D7693CE4D0416267C6C0F159,
response: {
Code:0 Data:[229 50 241 60 76 91 112 146 93 68 100 222 83 84 180 133 181 151 241 174 93 125 132 82 245 198 5 66 0 123 32 113]
Log:Msg 0: swapID: f85dd907df0a5897927b949c0f9e2563d453ba698ff9941fed1ce91f8057afc2
...
)

Note: the swap amount must be positive.

Please write down the random number, random number hash, swapID and timestamp for next steps.

Example is here

Then, you can query the the swap by SwapID:

./tbnbcli token query-swap --swap-id f85dd907df0a5897927b949c0f9e2563d453ba698ff9941fed1ce91f8057afc2 --chain-id Binance-Chain-Ganges --trust-node --node http://data-seed-pre-0-s3.bnbchain.org:80

You can use this swapID for refund if the deputy doesn't send htlt transaction on ethereum with proper parameters.

2. Deputy Approve Tokens

You should see that Deputy has approve enough amount of tokens for atomic swap.

3. Deputy Send HTLT on Ethereum

You should see that Deputy has sent the htlt transaction afterwards

To get the swapID on Ethereum, you can check this page 0xd3bacf63906af5459ead39f27cae189e2f3e76fda34523714a4c61d76c79ee4e is the swapID on Ethereum.

4. Claim ERC20 Tokens on Ethereum

You should see that Deputy has already approved enough tokens and

In its event log, you should see the swapID. Before calling claim function on ethereum, clients should verify the parameters in the HTLT event.

  • _randomNumberHash must equal to the randomNumberHash in client HTLT transaction on BNB Beacon Chain
  • _recipientAddr must equal to client ethereum wallet address
  • _timestamp must equal to the timestamp in client HTLT transaction on BNB Beacon Chain
  • _outAmount should be reasonable. Please note that the decimals erc20 contract and deputy will deduct some fees.
  • _expireHeight must not be passed and should be enough for send claim transaction

Then, you can call the claim function:

  • Function: claim
  • Parameters:
    • _swapID: this has been obtained from event, you can also calculate it from calSwapID function in the contract. calSwapID(randomNumberHash, {deputy ethereum address}, {hex encoding client binance address})
    • _randomNumber: reveal your randomNumber

Example is here

5. Deputy Claim on BNB Beacon Chain

Claim HTLT transaction from Deputy is sent afterwards:

6. Demo for Client APP: swap bep2 to erc20

This is a javascript implementation of client app to swap PPC-00A to PPC with deputy.

  const erc20ContractAddr = "0xd93395b2771914e1679155f3ea58c41d89d96098"
const swapContractAddr = "0x12DCBf79BE178479870A473A99d91f535ed960AD"

const deputyEthWalletAddr = "0x1C002969Fe201975eD8F054916b071672326858e"
const deputyBNBWalletAddr = "tbnb1pk45lc2k7lmf0pnfa59l0uhwrvpk8shsema7gr"

const clientEthWalletAddr = "0xfA5E36a04EeF3152092099F352DDbe88953bB540"
const clientEthWalletKey = new Buffer("89A0F0E0732ACAA7AD37C9E6D7A9798ECCE6940C63FF0290A58B1C1C1697486A", "hex")

const clientBnbWalletAddr = "tbnb17vwyu8npjj5pywh3keq2lm7d4v76n434pwd8av"
const clientBnbWalletMnemonic = "lawsuit margin siege phrase fabric matrix like picnic day thrive correct velvet stool type broom upon flee fee ten senior install wrestle soap sick"

const web3 = new Web3(new Web3.providers.HttpProvider("https://ropsten.infura.io/v3/1c5b38a27f92410cb5feb13b6efb2e14"))
const bnbClient = new BncClient("https://testnet-dex.binance.org")
await bnbClient.initChain()
bnbClient.setPrivateKey(crypto.getPrivateKeyFromMnemonic(clientBnbWalletMnemonic))
bnbClient.useDefaultSigningDelegate()
bnbClient.useDefaultBroadcastDelegate()
const bnbRPC = new rpcClient("https://data-seed-pre-2-s1.bnbchain.org/", "testnet")

const erc20Contract = new web3.eth.Contract([{"constant":true,"inputs":[],"name":"name","outputs":[{"name":"","type":"string"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[{"name":"_spender","type":"address"},{"name":"_value","type":"uint256"}],"name":"approve","outputs":[{"name":"success","type":"bool"}],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":false,"inputs":[{"name":"_evilUser","type":"address"}],"name":"addBlackList","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[],"name":"totalSupply","outputs":[{"name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[{"name":"_from","type":"address"},{"name":"_to","type":"address"},{"name":"_value","type":"uint256"}],"name":"transferFrom","outputs":[{"name":"success","type":"bool"}],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[],"name":"decimals","outputs":[{"name":"","type":"uint8"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[{"name":"_spender","type":"address"},{"name":"_addedValue","type":"uint256"}],"name":"increaseAllowance","outputs":[{"name":"success","type":"bool"}],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":false,"inputs":[],"name":"unpause","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[{"name":"account","type":"address"}],"name":"isPauser","outputs":[{"name":"","type":"bool"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[{"name":"_maker","type":"address"}],"name":"getBlackListStatus","outputs":[{"name":"","type":"bool"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"paused","outputs":[{"name":"","type":"bool"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[],"name":"renouncePauser","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[{"name":"who","type":"address"}],"name":"balanceOf","outputs":[{"name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[],"name":"renounceOwnership","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":false,"inputs":[{"name":"account","type":"address"}],"name":"addPauser","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":false,"inputs":[],"name":"pause","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[],"name":"owner","outputs":[{"name":"","type":"address"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"isOwner","outputs":[{"name":"","type":"bool"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"symbol","outputs":[{"name":"","type":"string"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[{"name":"_spender","type":"address"},{"name":"_subtractedValue","type":"uint256"}],"name":"decreaseAllowance","outputs":[{"name":"success","type":"bool"}],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":false,"inputs":[{"name":"_to","type":"address"},{"name":"_value","type":"uint256"}],"name":"transfer","outputs":[{"name":"success","type":"bool"}],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":false,"inputs":[{"name":"amount","type":"uint256"}],"name":"issue","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":false,"inputs":[{"name":"amount","type":"uint256"}],"name":"redeem","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[{"name":"_owner","type":"address"},{"name":"_spender","type":"address"}],"name":"allowance","outputs":[{"name":"remaining","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[{"name":"","type":"address"}],"name":"isBlackListed","outputs":[{"name":"","type":"bool"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[{"name":"_clearedUser","type":"address"}],"name":"removeBlackList","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":false,"inputs":[{"name":"newOwner","type":"address"}],"name":"transferOwnership","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":false,"inputs":[{"name":"_blackListedUser","type":"address"}],"name":"destroyBlackFunds","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"inputs":[{"name":"_initialSupply","type":"uint256"},{"name":"_name","type":"string"},{"name":"_symbol","type":"string"},{"name":"_decimals","type":"uint8"}],"payable":false,"stateMutability":"nonpayable","type":"constructor"},{"anonymous":false,"inputs":[{"indexed":false,"name":"amount","type":"uint256"}],"name":"Issue","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"name":"amount","type":"uint256"}],"name":"Redeem","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"name":"_blackListedUser","type":"address"},{"indexed":false,"name":"_balance","type":"uint256"}],"name":"DestroyedBlackFunds","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"name":"_user","type":"address"}],"name":"AddedBlackList","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"name":"_user","type":"address"}],"name":"RemovedBlackList","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"name":"account","type":"address"}],"name":"Paused","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"name":"account","type":"address"}],"name":"Unpaused","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"name":"from","type":"address"},{"indexed":true,"name":"to","type":"address"},{"indexed":false,"name":"value","type":"uint256"}],"name":"Transfer","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"name":"owner","type":"address"},{"indexed":true,"name":"spender","type":"address"},{"indexed":false,"name":"value","type":"uint256"}],"name":"Approval","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"name":"previousOwner","type":"address"},{"indexed":true,"name":"newOwner","type":"address"}],"name":"OwnershipTransferred","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"name":"account","type":"address"}],"name":"PauserAdded","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"name":"account","type":"address"}],"name":"PauserRemoved","type":"event"}],erc20ContractAddr)
const swapContract = new web3.eth.Contract([{"constant":true,"inputs":[],"name":"ERC20ContractAddr","outputs":[{"name":"","type":"address"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[{"name":"_swapID","type":"bytes32"}],"name":"isSwapExist","outputs":[{"name":"","type":"bool"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[{"name":"_swapID","type":"bytes32"}],"name":"refund","outputs":[{"name":"","type":"bool"}],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[{"name":"_randomNumberHash","type":"bytes32"},{"name":"_swapSender","type":"address"},{"name":"_bep2SenderAddr","type":"bytes20"}],"name":"calSwapID","outputs":[{"name":"","type":"bytes32"}],"payable":false,"stateMutability":"pure","type":"function"},{"constant":false,"inputs":[{"name":"_swapID","type":"bytes32"},{"name":"_randomNumber","type":"bytes32"}],"name":"claim","outputs":[{"name":"","type":"bool"}],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":false,"inputs":[{"name":"_randomNumberHash","type":"bytes32"},{"name":"_timestamp","type":"uint64"},{"name":"_heightSpan","type":"uint256"},{"name":"_recipientAddr","type":"address"},{"name":"_bep2SenderAddr","type":"bytes20"},{"name":"_bep2RecipientAddr","type":"bytes20"},{"name":"_outAmount","type":"uint256"},{"name":"_bep2Amount","type":"uint256"}],"name":"htlt","outputs":[{"name":"","type":"bool"}],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[{"name":"_swapID","type":"bytes32"}],"name":"claimable","outputs":[{"name":"","type":"bool"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[{"name":"_swapID","type":"bytes32"}],"name":"refundable","outputs":[{"name":"","type":"bool"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[{"name":"_swapID","type":"bytes32"}],"name":"queryOpenSwap","outputs":[{"name":"_randomNumberHash","type":"bytes32"},{"name":"_timestamp","type":"uint64"},{"name":"_expireHeight","type":"uint256"},{"name":"_outAmount","type":"uint256"},{"name":"_sender","type":"address"},{"name":"_recipient","type":"address"}],"payable":false,"stateMutability":"view","type":"function"},{"inputs":[{"name":"_erc20Contract","type":"address"}],"payable":false,"stateMutability":"nonpayable","type":"constructor"},{"anonymous":false,"inputs":[{"indexed":true,"name":"_msgSender","type":"address"},{"indexed":true,"name":"_recipientAddr","type":"address"},{"indexed":true,"name":"_swapID","type":"bytes32"},{"indexed":false,"name":"_randomNumberHash","type":"bytes32"},{"indexed":false,"name":"_timestamp","type":"uint64"},{"indexed":false,"name":"_bep2Addr","type":"bytes20"},{"indexed":false,"name":"_expireHeight","type":"uint256"},{"indexed":false,"name":"_outAmount","type":"uint256"},{"indexed":false,"name":"_bep2Amount","type":"uint256"}],"name":"HTLT","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"name":"_msgSender","type":"address"},{"indexed":true,"name":"_recipientAddr","type":"address"},{"indexed":true,"name":"_swapID","type":"bytes32"},{"indexed":false,"name":"_randomNumberHash","type":"bytes32"}],"name":"Refunded","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"name":"_msgSender","type":"address"},{"indexed":true,"name":"_recipientAddr","type":"address"},{"indexed":true,"name":"_swapID","type":"bytes32"},{"indexed":false,"name":"_randomNumberHash","type":"bytes32"},{"indexed":false,"name":"_randomNumber","type":"bytes32"}],"name":"Claimed","type":"event"}], swapContractAddr)

//--------------------------------------------
//Step1 send htlt on BNB Beacon Chain
//--------------------------------------------
const randomNumber = "e8eae926261ab77d018202434791a335249b470246a7b02e28c3b2fb6ffad8f3"
const timestamp = Math.floor(Date.now()/1000)
const randomNumberHash = calculateRandomNumberHash(randomNumber, timestamp).toString("hex")
const heightSpan = 10000
const amount = [{
denom: "PPC-00A",
amount: 100000000
}]
const expectedIncome = "9999990000:PPC" //"9999990000:PPC", decimal is 10, deputy will deduct swap fee, the swap fee is 10000:PPC

bnbClient.swap.HTLT(clientBnbWalletAddr, deputyBNBWalletAddr, clientEthWalletAddr, "", randomNumberHash, timestamp, amount, expectedIncome, heightSpan, true)
await wait(1000)

//----------------------------------------------------------------------------
//Step2 query swap created by deputy on Ethereum and verify swap parameters
//----------------------------------------------------------------------------
const hexEncodingClientBNBaddr = '0x'+crypto.decodeAddress(clientBnbWalletAddr).toString("hex")
const swapID = await swapContract.methods.calSwapID("0x"+randomNumberHash, deputyEthWalletAddr, hexEncodingClientBNBaddr).call()
console.log(swapID)

let openSwap = await swapContract.methods.queryOpenSwap(swapID).call()
while (openSwap._randomNumberHash == '0x0000000000000000000000000000000000000000000000000000000000000000') {
console.log("Waiting for the atomic swap created by deputy")
await wait(5000)
openSwap = await swapContract.methods.queryOpenSwap(swapID).call()
}
let ethBlock = await web3.eth.getBlock('latest')
let ethLatestHeight = ethBlock.number
expect(openSwap._randomNumberHash).toBe("0x"+randomNumberHash)
expect(Number(openSwap._timestamp)).toBe(timestamp)
expect(Number(openSwap._outAmount)).toBe(9999990000)
expect(openSwap._recipient).toBe(clientEthWalletAddr)
expect(Number(openSwap._expireHeight)).toBeGreaterThan(Number(ethLatestHeight)+20)

//----------------------------------------------------------------------------
//Step3 claim on Ethereum
//----------------------------------------------------------------------------
const claimData = swapContract.methods.claim(swapID, "0x"+randomNumber).encodeABI()
let nonce = await web3.eth.getTransactionCount(clientEthWalletAddr, 'pending')
let gasPrice = await web3.eth.getGasPrice()
let gasLimit = 3000000
let rawTx = {
nonce: web3.utils.toHex(nonce),
gasPrice: web3.utils.toHex(gasPrice),
gasLimit: web3.utils.toHex(gasLimit),
to: swapContractAddr,
value: '0x00',
data: claimData
}
var ethereumjs = require('ethereumjs-tx')
var signTx = new ethereumjs(rawTx)
signTx.sign(clientEthWalletKey)
var serializedTx = signTx.serialize();
web3.eth.sendSignedTransaction('0x' + serializedTx.toString('hex')).on('receipt', console.log)
await wait(20000)

//----------------------------------------------------------------------------
//If step2 or step3 are failed and the expire height on BNB Beacon Chain is passed, try to send refundHTLT transaction on BNB Beacon Chain
//----------------------------------------------------------------------------

Swap between Several BEP2 tokens

image-20190918193422062

Swap between Several BEP2 tokens fails

image-20190918193518929