Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(atomic-swap): add atomic swap guide #41

Open
wants to merge 5 commits into
base: master
Choose a base branch
from
Open
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
97 changes: 97 additions & 0 deletions guides/0000-atomic-swap.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,97 @@
### What is atomic swap
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I prefer the template format. It seems more intuitive and easier to follow.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I've change the format. What do you think?


Atomic swap is a feature where two different users can exchange different tokens in the same transaction without any third party and any trust.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'd add that an atomic swap consists of a single transaction that is either executed or not. Hence, the parties do not have to trust each other. If Alice receives Bob's token, then Bob will certainly receives Alice's token.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I've added this to the summary. What do you think?


For instance, imagine you want to buy an NFT and pay with HTR. This can be done in a single transaction with no trust between the buyer and the seller.

### Operations flow

In the example described here we will assume that there is a central backend to coordinate the atomic swap. Let's assume we have two users (user1 and user2), each one with their own wallets (W1 and W2) in their devices and they want to swap token1 and token2, i.e. user1 will send token1 from W1 to W2 and user2 will send token2 from W2 to W1. See the image below.

![atomic swap wallets drawio](https://user-images.githubusercontent.com/3298774/145630746-ca4c0011-2e01-4574-8143-f3c64c214f4e.png)

The first thing to do is to define how many tokens will be exchanged, i.e. user1 will send X token1 to user2 and user2 will send Y token2 to user1. The atomic swap will happen in some steps that will be done in both wallets, where the users will need to exchange some information after each step. Below there is an image with each step and the explanation of them.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'd emphasize that the steps are supposed to be executed one after the other.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not all of them, right? There are steps that can be done concurrently.

That's why I added this in the diagram and it's clear with the number which steps can be done concurrently and the ones that have dependencies.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I feel we can add a sequence diagram to illustrate each step and the communication between independent wallets.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In the end of the guide level explanation there is a diagram. Do you prefer to have each step separated? I think I like more the explanation text, then the full diagram.


#### Step 1
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Before explaining each step in details, I'd add a summary like this:

  1. Wallet 1 and Wallet 2 agrees to exchange 3 X and 1 Y.
  2. Wallet 2 sends its inputs and change outputs to wallet 2.
  3. Wallet 1 generate an unsigned tx with all inputs and outputs.
  4. Wallet 1 signs its inputs (in my example, TxA.inputs[0] only).
  5. Note that the tx is not ready to be pushed yet.
  6. Wallet 1 sends bytes(tx) to Wallet 2.
  7. Wallet 2 receives bytes(tx) , decodes it, and check whether the inputs and ouputs are correct.
  8. Wallet 2 signs its inputs.
  9. Now tx is ready to be pushed.
  10. Wallet 2 pushes the transaction.
  11. Wallet 1 receives the transactions and confirms the swap.

Maybe it should in a sequence diagram.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe add an example as well.

For example, let TxA be a transaction. Let X and Y be two different tokens. Then,

TxA.inputs[0] = 10 X (wallet 1)
TxA.inputs[1] = 5 Y (wallet 2)
TxA.outputs[0] = 3 X (wallet 2)
TxA.outputs[1] = 7 X (wallet 1, change)
TxA.outputs[2] = 1 Y (wallet 1)
TxA.outputs[2] = 4Y (wallet 2, change)

In this case, TxA is an atomic swap of 3 X and 1 Y from wallet 1 and wallet 2.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I've added the summary but I've changed a bit. The example I decided to use is with a central coordinator because it's the request we had from a use case.

About the example, I guess it's confusing this way, we already have the code example in the reference level, I guess it's enough. What do you think?


User1 (that will send token1 and receive token2) will get utxos available of token1 and decide the ones that will use to send the amount needed to user2. Besides that, user1 will also decide the outputs that he wants to receive of token2 with amount and address. In this step it's important to also add any change output for token1, if needed. Wallet1 then sends this data to the backend service.

#### Step 2

Step 2 is similar to the first one and can happen concurrently. User2 will get the utxos available of token2 and decide the ones that will be used. He will also define the outputs with amount and address to receive token1 from user1. In this step it's important to also add any change output for token2, if needed. Then this data will also be sent to the backend.

#### Step 3

The data on step 1 and step 2 will be used to create a transaction data with all inputs and outputs from both wallets. With this transaction data, we can start signing the inputs. The signature step will happen in two parts, one for user1 and one for user2, and once again steps 4 and 5 can happen concurrently.

#### Step 4

User1 will receive the data with all inputs and outputs with none of the inputs signed. He will check that the outputs of token2 that he expects to receive are correct (amount, address and timelock) and will sign the inputs that belong to his wallet. After that he will send the signed inputs back to the backend.

#### Step 5

User2 will receive the data with all inputs and outputs with none of the inputs signed (if this step is being done concurrently with step 4). He will then check if the outputs of token1 that he expects to receive are correct (amount, address and timelock) and will sign the inputs that belong to his wallet. User1 don't need to worry about user2 changing the outputs before signing because, if that happens, his signature made on step 4 won't be valid anymore. After this step the transaction is ready for the final step.

#### Step 6

On step 6 the backend already has the full data (all outputs and inputs with the signatures) to finish the operation. The transaction will be completed with weight and timestamp, then will be sent to be mined and finally pushed to the network.

![atomic swap flow drawio](https://user-images.githubusercontent.com/3298774/145856624-19ebd557-f160-43d5-9267-60f55e6c4f74.png)

### Example code

This code below should work only with hathor wallet-lib v0.29.1 or above.

In the example code below we start two wallets, each one of them with a separated storage and we create a transaction that has inputs from both wallets and they exchange different tokens. In a real application, each wallet will be in a different device and this operation must be done asyncronously.

```
// You must call node in a folder that you have wallet-lib installed in node_modules
node

const hl = require('@hathor/wallet-lib')
const conn1 = new hl.Connection({network: 'mainnet', servers: ['https://node1.mainnet.hathor.network/v1a/']});
const conn2 = new hl.Connection({network: 'mainnet', servers: ['https://node1.mainnet.hathor.network/v1a/']});
const pin = '123';
const password = '123';
const w1 = new hl.HathorWallet({connection: conn1, password, pinCode: pin, seed: seed1});
const w2 = new hl.HathorWallet({connection: conn2, password, pinCode: pin, seed: seed2});

// Start wallets
w1.start()
w2.start()

// Verify is wallets are ready
w1.isReady()
w2.isReady()

// Get available utxos for each wallet and each token
w1.getUtxos({ token: token1 })
w2.getUtxos({ token: token2 })

// Create the tokens, inputs and outputs arrays with change outputs already
const tokens = [token1, token2]

// With the data from the get utxos method you will fill the inputs objects
// in the example below we will swap token1 from wallet1 with token2 from wallet2. token1 will be sent from a single utxo and token2 will be sent from 2 utxos.

const inputs = [{tx_id: txId1, index: index1, token: token1, address: address1}, {tx_id: txId2, index: index2, token: token2, address: address2}, {tx_id: txId3, index: index3, token: token2, address: address3}]

const outputs = [{address: address4, value: value1, token: token1, tokenData: 1}, {address: address5, value: value2, token: token2, tokenData: 2}]

const data = {tokens, inputs, outputs}
hl.transaction.completeTx(data);

// Sign inputs
const dataToSign = hl.transaction.dataToSign(data);
hl.storage.setStore(w1.store);
hl.transaction.signTx(data, dataToSign, pin);

hl.storage.setStore(w2.store);
hl.transaction.signTx(data, dataToSign, pin);

const transaction = hl.helpersUtils.createTxFromData(data, new hl.Network('mainnet'));
transaction.prepareToSend()

const sendTxObj = new hl.SendTransaction({ transaction })
await sendTxObj.runFromMining();
```