Skip to main content

Atomic swaps with headless wallet

Introduction

This article is a tutorial to assist developers who already use Hathor headless wallet to get started with atomic swap. By the end of this tutorial, you will have performed your first atomic swap transaction in Hathor using Hathor headless wallet application.

Background

In Hathor, an atomic swap is a blockchain transaction with at least two remitter wallets transferring tokens. To know more about atomic swaps in Hathor, see About atomic swaps.

Here, we will cover all steps to perform the simplest possible atomic swap. To have a conceptual understanding of such kind of transaction, see Life cycle of atomic swap transactions.

Prerequisites

To execute this tutorial, you must meet the following prerequisites:

  • Hathor headless wallet \ge v0.18.0
  • Have two different wallets on Hathor Network testnet. This is needed because, as pointed out in the prior section, an atomic swap transaction has at least two remitter wallets, and in this tutorial, you will execute all steps required from all sides of the transaction.
  • One of these wallets must have at least X amount of token A, where X and A are from your choice.
  • The other must has at least Y amount of token B, where Y and B are from your choice.

Overview of the task

Let's suppose two users, Alice and Bob. They want to do the following exchange: Alice will transfer to Bob X amount of token A, whereas Bob will transfer to Alice Y amount of token B, where A and B are tokens within Hathor. They want to do it in a 100% trustless manner. Thus, they decide to perform an atomic swap transaction.

Besides the interaction with Hathor blockchain, such type of transaction requires also off-chain communication between Alice and Bob. Thus, they establish a communication channel of their choice. Let's say they chose to exchange messages in a private conversation on Telegram.

With their wallet applications, one of them will create a first partial transaction that will be shared with, and updated by the other until we have a transaction with all inputs and outputs.

Once Alice and Bob review and agree that the transaction perfectly matches their previous agreement, they will sign the transaction, and anyone may push it to Hathor Network.

Sequence of steps

As already said, you will represent both Alice and Bob parts in the process. To accomplish the task, you will follow these steps:

  1. Alice creates a first partial transaction proposal.
  2. Bob reviews Alice's partial transaction proposal.
  3. Bob updates Alice's partial transaction proposal.
  4. Alice reviews the updated partial transaction proposal.
  5. Alice generates her signature.
  6. Bob generates his signature.
  7. Bob signs and pushes the transaction proposal.

Task execution

Now, it's time to get your hands dirty. In this section, we will describe the steps in detail. You shall use one wallet for Alice's part of the process and the other for Bob's. Since you will be representing both of them, the off-chain communication channel won't be necessary.

tip

<Placeholders>: in the code samples of this article, as in all Hathor docs, <placeholders> are always wrapped by angle brackets < >. You shall interpret or replace a <placeholder> with a value according to the context. Whenever replacing a <placeholder> like this one with a value, do not wrap the value with quotes. Quotes, when necessary, will be indicated, wrapping the "<placeholder>" like this one.

Suppose Alice and Bob already reached a (yet) informal agreement of token exchange. At this point, any of them may create the first partial transaction. Let's suppose that Alice starts.

Step 1: Alice creates a first partial transaction proposal

With Alice's wallet, create the first partial transaction proposal, stating: "I want to transfer X token A and receive Y token B in address A1".

tip

The value of such variables in the API request follows the same standards as in regular transactions. The amounts X and Y must be represented by integer numbers whose two last digits are the cents — e.g., 10 HTR becomes '1000' in the API request; 10.50 HTR becomes '1050'. As for tokens A and B, these are represented by their UIDs.

Let <Xxx> and <Yyy> be the correct representation of X and Y, respectively. Let <A_UID> and <B_UID> be the respective UIDs of tokens A and B. And finally, if the API request does not provide an address A1, the wallet application will choose and assign a valid recipient address to Alice:

API request
curl -X POST \
-H 'X-Wallet-Id: alice' \
-H 'Content-Type: application/json' \
-d '{
"receive": {
"tokens": [
{
"value": <Yyy>,
"token": "<B_UID>"
}
]
},
"send": {
"tokens": [
{
"value": <Xxx>,
"token": "<A_UID>"
}
]
}
}' \
http://localhost:8000/wallet/atomic-swap/tx-proposal | jq

The API response provides Alice's partial transaction proposal in data. Furthermore, isComplete inform if the partial already has all data to be considered a complete transaction proposal, missing just the signatures:

API response
{
"success": true,
"data": "<alice_partial_tx_hex>",
"isComplete": false
}

Now, Alice would send <alice_partial_tx_hex> via Telegram.

Step 2: Bob reviews Alice's partial transaction proposal

Once Bob receives <alice_partial_tx_hex>, he will review if the received partial conforms with their prior agreement. Since Alice's partial is in hexadecimal format, to review it you first use Bob's wallet to decode it:

API request
curl -X POST \
-H 'X-Wallet-Id: bob' \
-H 'Content-Type: application/json' \
-d '{
"partial_tx": "<alice_partial_tx_hex>"
}' \
http://localhost:8000/wallet/decode | jq

In his review, Bob should check if value and token match their prior agreement regarding what and how much he should transfer to Alice:

API response
{
"success": true,
"tx":{
"tokens": [ ... ],
"inputs": [ ... ],
"outputs": [
{
"value": <Yyy>,
"tokenData": <token_info_integer>,
"token": "<B_UID>",
...
"type": "p2pkh",
"decoded": {
"address": "<alice_recipient_address>",
"timelock": null
}
}
]
}
}

Step 3: Bob updates Alice's partial transaction proposal

Once Bob considers that Alice's partial is correct, he will update it. With Bob's wallet, append his part of the transaction proposal:

API request
curl -X POST \
-H 'X-Wallet-Id: bob' \
-H 'Content-Type: application/json' \
-d '{
"partial_tx": "<alice_partial_tx_hex>",
"receive": {
"tokens": [
{
"value": <Xxx>,
"token": "<A_UID>"
}
]
},
"send": {
"tokens": [
{
"value": <Yyy>,
"token": "<B_UID>"
}
]
}
}' \
http://localhost:8000/wallet/atomic-swap/tx-proposal | jq

As a result, you get a transaction proposal with data from both sides — i.e., a transaction proposal, missing only signatures — as stated by the field isComplete:

API response
{
"success": true,
"data": "<updated_partial_tx_hex>",
"isComplete": true
}

Now, Bob should send back the transaction proposal — now updated with his data — to Alice, namely <updated_partial_tx_hex>.

tip

At this point, as Bob already has a complete transaction proposal, he could even generate his signature and send it to Alice along with the updated transaction proposal (these will still be two different pieces of data). However, for this tutorial, we're going to wait until Alice has reviewed and agreed to the updated transaction proposal, as if ensuring that no one else will make changes before starting to generate signatures.

Step 4: Alice reviews the updated partial transaction proposal

Once Alice receives <updated_partial_tx_hex> via Telegram, she will review it. Use Alice's wallet to decode the transaction proposal:

API request
curl -X POST \
-H 'X-Wallet-Id: alice' \
-H 'Content-Type: application/json' \
-d '{
"partial_tx": "<updated_partial_tx_hex>"
}' \
http://localhost:8000/wallet/decode | jq

Alice should review the outputs of the transaction proposal. More specifically, she should check if token and value to be transferred to Bob match their prior agreement; and also check if Bob did not change the data of token, value to be transferred to her, and also timelock (null if she did not set one) and her recipient address:

API response
{
"success":true,
"tx": {
"tokens": [ ... ],
"inputs": [ ... ],
"outputs": [
{
"value": <Xxx>,
"tokenData": <token_info_integer>,
"token": "<A_UID>",
...
"type": "p2pkh",
"decoded": {
"address": "<bob_recipient_address>",
"timelock": null
}
},
{
"value": <Yyy>,
"tokenData": <token_info_integer>,
"token": "<B_UID>",
...
"type": "p2pkh",
"decoded": {
"address": "<alice_recipient_address>",
"timelock": null
}
}
]
}
}

Step 5: Alice generates her signature

Once Alice considers ok the transaction proposal content — namely, <updated_partial_tx_hex> —, she will sign it. With Alice's wallet, generate her signature:

API request
curl -X POST \
-H 'X-Wallet-Id: alice' \
-H 'Content-Type: application/json' \
-d '{
"partial_tx": "<updated_partial_tx_hex>"
}' \
http://localhost:8000/wallet/atomic-swap/tx-proposal/get-my-signatures | jq

The API response will give you Alice's signature:

API response
{
"success": true,
"signatures": "<alice_signature>"
}

Anyone with both signatures may sign and push the transaction proposal to Hathor Network. Since Alice first signed it, she decides to send Bob her signature.

Step 6: Bob generates his signature

Bob has just received Alice's signature. He still needs to generate his signature. With Bob's wallet, generate his signature:

API request
curl -X POST \
-H 'X-Wallet-Id: bob' \
-H 'Content-Type: application/json' \
-d '{
"partial_tx": "<updated_partial_tx_hex>"
}' \
http://localhost:8000/wallet/atomic-swap/tx-proposal/get-my-signatures | jq

The API response will give you Bob's signature:

API response
{
"success": true,
"signatures": "<bob_signature>"
}

Step 7: Bob signs and push the transaction proposal

With both signatures in hands, Bob just needs to sign the transaction proposal — i.e., append the signatures in <updated_partial_tx_hex> — and push it to Hathor Network. With Bob's wallet, you can execute both steps in a single API request:

API request
curl -X POST \
-H 'X-Wallet-Id: bob' \
-H 'Content-Type: application/json' \
-d '{
"partial_tx": "<updated_partial_tx_hex>",
"signatures": [
"<bob_signature>",
"<alice_signature>"
]
}' \
http://localhost:8000/wallet/atomic-swap/tx-proposal/sign-and-push | jq

If the transaction proposal is complete, signed, and in a valid state, it shall be validated and recorded into the blockchain:

API response
{
"success":true,
"inputs":[
...
],
"outputs":[
{
"value": <Yyy>,
...
},
{
"value": <Xxx>,
...
}
],
...
"tokens": [ ... ],
...
}

Task completed

At this point, the atomic swap transaction is completed. Take notice that, if along the process, another transaction is successfully registered (by Alice or Bob) into the blockchain, using any of the inputs previously assigned to this atomic swap, it will be considered invalid by Hathor Network during the validation process and discarded.

Finally, remember that once both signatures are generated, anyone may append them into the transaction proposal and push to the network.

Key takeaways

You have performed your first atomic swap transaction using Hathor wallet headless. Yet, the practice you did of two users exchanging only two tokens is just the simplest case of atomic swap. Furthermore, even for this simple case, there is room for customization — e.g., select the inputs, change address, timelock, etc.

What's next?