NAV Navbar
shell

Introduction

Welcome to Swapperd! You can use Swapperd to execute cross-chain atomic swaps between Bitcoin, Ethereum, and ERC20 tokens, with more blockchain support coming soon.

Installation

Swapperd currently supports macOS and Ubuntu. Run the following command in a terminal:

curl https://git.io/test-swapperd -sSLf | sh

Swapperd installs itself as a system service. In the event of an unexpected shutdown, Swapperd automatically restarts, and on the first HTTP request, it resumes all pending atomic swaps.

A mnemonic is generated during the installation process. Swapperd uses this mnemonic, with the password provided in the swap request, to generate it's Bitcoin and Ethereum private keys on-demand.

Authentication

An example using HTTP Authentication:

curl -i     \
     -X GET \
     http://username:password@localhost:17927/balances

Swapperd protects all its endpoints with HTTP Basic Authentication.

Networks

Swapperd runs on two ports by default, Mainnet on 7927 and Testnet on 17927. We are working on adding a local environment, which will setup local swapperd and blockchain nodes for testing. This Local network would be using 27927.

Swaps

Executing an atomic swap requires two parties to participate in an interactive swapping process. This interactive swapping process will either result in both parties exchanging their tokens, or both parties keeping their tokens.

Before beginning an atomic swap, swapperd assumes that the two parties:

  1. Agree that one party begins the atomic swap.
  2. Exchange their Swapperd addresses for the relevant tokens.

Before responding to an atomic swap, swapperd assumes that the two parties:

  1. Exchange the timeLock and secretHash generated by the party that begins the atomic swap.

Swapperd handles all other interactions. Swapperd is built to be fault-tolerant and in the event of an unexpected shutdown, or unexpected errors, it retries actions as needed. All actions are idempotent and cannot result in an accidental double execution.

Supported Tokens:

Name Type Usage
sendToken TokenName The name of the token you want to send
receiveToken TokenName The name of the token you want to receive
sendAmount string The amount of token you want to send (decimal string)
receiveAmount string The amount of token you want to receive (decimal string)
shouldInitiateFirst bool Should the swapper initiate first
timeLock int64 Timelock of the initiating atomic swap (required when shouldInitiateFirst is false).
secretHash string Base64 encoding of the secret hash (required when shouldInitiateFirst is false).
sendTo string counter-party's sendToken address (required when doing an immediate swap).
receiveFrom string counter-party's receiveToken address (required when doing an immediate swap).
sendFee string (optional) sendToken transaction fee
receiveFee string (optional) receiveToken transaction fee
brokerFee int64 (optional) broker/matching fee in bips
brokerSendTokenAddr string (optional) broker's sendToken address
brokerReceiveTokenAddr string (optional) broker's receiveToken address
minimumReceiveAmount string (optional, default: "0") used when the delay is true, to check the updated swap details
delay bool (optional, default: false) set it to true if it is a delayed swap
delayCallbackURL string (optional) url to which swapperd can post the partial swap information to get it filled.
delayInfo JSON (optional) information required by your server behind delayCallbackURL to identify the user and the swap.

Beginning an atomic swap

Beginning an atomic swap by initiating first:

curl -i      \
     -X POST \
     -d '{
          "sendToken":"BTC",
          "receiveToken":"ETH",
          "sendAmount":"20000",
          "receiveAmount":"20000000000000000",
          "sendTo":"n2RcTmQu2PoRcfHn7uyfQ2jWVcmuHDQLnh",
          "receiveFrom":"0x780c6d20b6C59F4b5F3658A66E1e8ef22d61725D",
          "shouldInitiateFirst":true
        }' \
     http://username:password@localhost:17927/swaps

The response body is structured like this:

{
    "id": "c7t5VHbJdx2M0PtRS6G3lN2nrRH1fX0arUoZYkFBlvI=",
    "swap": {
      "sendToken": "ETH",
      "receiveToken": "BTC",
      "sendAmount": "20000000000000000",
      "receiveAmount": "20000",
      "sendTo": "0x5Ea5F67cC958023F2da2ea92231d358F2a3BbA47",
      "receiveFrom": "mzKgUBHX7xSkKiNrdnxTe6fJKAcvFri2Tc",
      "timeLock": 1548669277,
      "secretHash": "3YyU0uyS2TKWdAkVQpQWwTXRXcEkpjpya8atI2x+OTE=",
      "shouldInitiateFirst": false,
  },
    "signature": "qruvrzo2hpyCp76wdcmpo+E+5fZ42n2MNyA4sOjSCzgr8rYh4tLZ0vpKrGDqYptOXzYpakC+NKgPO51hFM7L4AE="
}

The beginning party sends an HTTP request to his Swapperd using the agreed addresses.

Swapperd validates the request and generates the responding swap object. The beginning party can send this response to the responding party. The responding party can check KYC (if they require it), by using the signature. Then they could start the responding swap with the given swap object.

The beginning party can check the status of their swap using the id returned by the swapperd.

HTTP Request

POST http://localhost:17927/swaps

Responding to an atomic swap

Respond to an atomic swap by initiating second:

curl -i      \
     -X POST \
     -d '{
        "sendToken": "ETH",
        "receiveToken": "BTC",
        "sendAmount": "20000000000000000",
        "receiveAmount": "20000",
        "sendTo": "0x5Ea5F67cC958023F2da2ea92231d358F2a3BbA47",
        "receiveFrom": "mzKgUBHX7xSkKiNrdnxTe6fJKAcvFri2Tc",
        "timeLock": 1548669277,
        "secretHash": "3YyU0uyS2TKWdAkVQpQWwTXRXcEkpjpya8atI2x+OTE=",
        "shouldInitiateFirst": false,
    }' \
    http://username:password@localhost:17927/swaps

The response body is structured like this:

{
  "id": "S1Jn5MTLBqD8M2lm6vYjt1n2qy7XlW7sjHyIY3eInNA=",
}

After receiving the response from the beginning party (and checking the KYC information if required), the responding party sends the swap object they received to their swapperd.

HTTP Request

POST http://localhost:17927/swaps

Starting a delayed swap

In some cases, we do not know both sides of an atomic swap, before committing to an atomic swap. An example use-case would be an exchange using swapperd, and an exchange would want to make sure that if it finds an order match the users execute the swap. To solve this problem, we introduced a feature called delayed swaps.

A user starts an atomic swap, and the swap request has a delayCallbackUrl which the swapperd keeps pinging with the relevant information. So once the counter-party is found the server pointed to by the delayCallbackUrl returns the swap blobs of both traders. The swappers of the traders check the new swap details, and they check the following:

If all the checks pass, the atomic swap goes through, if they do not the swap fails.

curl -i      \
     -X POST \
     -d '{
        "sendToken": "ETH",
        "receiveToken": "BTC",
        "sendAmount": "200000000000000000",
        "receiveAmount": "2000000",
        "minimumReceiveAmount": "1000000",

        "delay": true,
        "delayInfo": {
          "usecaseSpecificKey": "usecaseSpecificValue",
        },
        "delayCallbackUrl": "your_swapperd_callback_url"
    }' \
    http://username:password@localhost:17927/swaps

The swapperd pings your_swapperd_callback_url with the following request:

  {
      "sendToken": "ETH",
      "receiveToken": "BTC",
      "sendAmount": "200000000000000000",
      "receiveAmount": "2000000",
      "minimumReceiveAmount": "1000000",

      "delay": true,
      "delayInfo": {
        "usecaseSpecificKey": "usecaseSpecificValue",
      },
      "delayCallbackUrl": "your_swapperd_callback_url"
  }

Swapperd expects delayCallbackUrl to respond with one of the following responses.

Status Code Meaning Response
200 StatusOK -- Success A filled executable swap json object.
204 StatusNoContent -- Please try again after some time. Nothing.
410 StatusGone -- The swap is cancelled, stop requesting. Nothing.

HTTP Request

POST http://localhost:17927/swaps

Status of existing atomic swaps

curl -i     \
     -X GET \
     http://username:password@localhost:17927/swaps

The response body is structured like this:

{
  "swaps": [
    {
      "id": "DiqkYPg/2mCzGhlk7ENighXhFDSkSYJ9+qmmVHI3UrE=",
      "sendToken": "BTC",
      "receiveToken": "ETH",
      "sendAmount": "20000",
      "receiveAmount": "2000000000000",
      "sendCost": {
        "BTC": "0"
      },
      "receiveCost": {
        "ETH": "0"
      },
      "timestamp": 1548656666,
      "timeLock": 1548678266,
      "status": 5,
      "delay": false,
      "active": true
    }
  ]
}

HTTP Request

GET http://localhost:17927/swaps

Balances

All Tokens

curl -i     \
     -X GET \
     http://username:password@localhost:17927/balances

The response body is structured like this:

{
    "BTC": {
    "address": "mzKgUBHX7xSkKiNrdnxTe6fJKAcvFri2Tc",
    "decimals": 8,
    "balance": "19190614"
    },
    "WBTC": {
    "address": "0x5Ea5F67cC958023F2da2ea92231d358F2a3BbA47",
    "decimals": 18,
    "balance": "0"
  }
}

HTTP Request

GET http://localhost:17927/balances

Single Token

curl -i     \
     -X GET \
     http://username:password@localhost:17927/balances/btc

The response body is structured like this:

{
    "address": "mzKgUBHX7xSkKiNrdnxTe6fJKAcvFri2Tc",
    "decimals": 8,
    "balance": "19149414"
}

HTTP Request

GET http://localhost:17927/balances/{token}

Addresses

All Tokens

curl -i     \
     -X GET \
     http://username:password@localhost:17927/addresses

The response body is structured like this:

{
    "BTC": "mzKgUBHX7xSkKiNrdnxTe6fJKAcvFri2Tc",
    "DAI": "0x5Ea5F67cC958023F2da2ea92231d358F2a3BbA47",
    "DGX": "0x5Ea5F67cC958023F2da2ea92231d358F2a3BbA47",
    "ETH": "0x5Ea5F67cC958023F2da2ea92231d358F2a3BbA47",
    "GUSD": "0x5Ea5F67cC958023F2da2ea92231d358F2a3BbA47",
    "OMG": "0x5Ea5F67cC958023F2da2ea92231d358F2a3BbA47",
    "PAX": "0x5Ea5F67cC958023F2da2ea92231d358F2a3BbA47",
    "REN": "0x5Ea5F67cC958023F2da2ea92231d358F2a3BbA47",
    "TUSD": "0x5Ea5F67cC958023F2da2ea92231d358F2a3BbA47",
    "USDC": "0x5Ea5F67cC958023F2da2ea92231d358F2a3BbA47",
    "WBTC": "0x5Ea5F67cC958023F2da2ea92231d358F2a3BbA47",
    "ZRX": "0x5Ea5F67cC958023F2da2ea92231d358F2a3BbA47"
}

HTTP Request

GET http://localhost:17927/addresses

Single Token

curl -i     \
     -X GET \
     http://username:password@localhost:17927/addresses/eth

The response body is structured like this:

0x5Ea5F67cC958023F2da2ea92231d358F2a3BbA47

HTTP Request

GET http://localhost:17927/addresses/{token}

Transfers

Transferring funds

curl -i      \
     -X POST \
     -d '{
        "token": "BTC",
        "to": "mroqkoMK1L9ugjBbyJDh1B2kHmHPRzRjRS",
        "amount": "100000"
      }' \
     http://username:password@localhost:17927/transfers

HTTP Request

POST http://localhost:17927/transfers

Details of past transactions

curl -i      \
     -X GET \
     http://username:password@localhost:17927/transfers

The response body is structured like this:

{
    "transfers": [
    {
      "confirmations": 0,
      "timestamp": 1548828011,
      "to": "mroqkoMK1L9ugjBbyJDh1B2kHmHPRzRjRS",
      "from": "mzKgUBHX7xSkKiNrdnxTe6fJKAcvFri2Tc",
      "token": {
      "name": "BTC",
      "decimals": 8,
      "blockchain": "bitcoin"
      },
      "value": "100000",
      "txCost": {
      "BTC": "10000"
      },
      "txHash": "db7cf2e04d1fa5669323bcbba73b175938fa5dfe0d05ea1f216d151b7728f057"
      }
    ]
}

HTTP Request

GET http://127.0.0.1:17927/transfers

Info

Getting information about Swapperd

curl -i     \
     -X GET \
     http://username:password@localhost:17927/info

The response body is structured like this:

{
    "version": "v1.0.0-beta.7",
    "bootloaded": true,
    "supportedBlockchains": [
      "ethereum", "bitcoin", "erc20"
    ],
    "supportedTokens": [
        {
            "name": "BTC",
            "decimals": 8,
            "blockchain": "bitcoin"
        },
        {
            "name": "ETH",
            "decimals": 18,
            "blockchain": "ethereum"
        },
        {
            "name": "WBTC",
            "decimals": 8,
            "blockchain": "erc20"
        }
    ]
}

HTTP Request

GET http://localhost:17927/info

ID

Swapperd uses a separate ECDSA keypair to sign messages, to prove identity. This id can be connected to KYC details, which allows KYC verification for atomic swaps (the developer/counter-party can make sure that the user is KYCd before doing atomic swaps with them), This is an entirely optional feature.

Getting Swapperd's ID

This endpoint returns the base64 encoding of the public key.

curl -i     \
     -X GET \
     http://username:password@localhost:17927/id

The response body is structured like this:

BLUOP6KMR7jMhhLzpvuUk9lCHjNsb2hn0FPNV7fJ/rHisJBs7CapPkqPCnrkGWBb9xS3ThO3ftUX85zrbXs2r+M=

HTTP Request

GET http://localhost:17927/id

Getting Swapperd's ID as ethereum address

This endpoint returns the ethereum address generated from the identity key pair.

curl -i     \
     -X GET \
     http://username:password@localhost:17927/id/eth

The response body is structured like this:

0x80B7DF532FFDC5DF28D6bc98205b58daeE1E407f

HTTP Request

GET http://localhost:17927/id/eth

Sign

Swapperd uses the identity ECDSA keypair to sign messages, to prove identity. It supports signing base64, hex and JSON strings. This endpoint can be used to sign arbitrary messages, and no blockchain uses this key pair so it cannot lead to the loss of funds.

Getting Swapperd to sign a JSON message with the identity key pair.

Signing an arbitrary JSON object

curl -i     \
     -X POST \
     -d '{
        "hello": "world"
      }' \
     http://username:password@localhost:17927/sign/json

The response body is structured like this:

{
    "message": {
      "hello": "world"
    },
    "signature": "6JXLmjJMXSmmGO1zuYr7r3pTiQCRvAw8yr8wsv6r8MMM7r5508Kt0zmXjJECDoZ5IgLSo3T2ivZBAYoRjl6UPgA="
}

HTTP Request

GET http://localhost:17927/sign/json

Getting Swapperd to sign a hex message with the identity key pair.

Signing an arbitrary hex string

curl -i     \
     -X POST \
     -d '1234567890abcdef' \
     http://username:password@localhost:17927/sign/hex

The response body is structured like this:

{
    "message": "1234567890abcdef",
    "signature": "e759547d4fa1979260e84622ca732fa6149b95001dd33dd7aad50f1285334f605bb091616667cb7bf81e62d499ab23dac8336153fc85ce96690b01bee0620b4801"
    }

HTTP Request

GET http://localhost:17927/sign/hex

Getting Swapperd to sign a base64 message with the identity key pair.

Signing an arbitrary base64 string

curl -i     \
     -X POST \
     -d '1234567890abcdefghijklmNOPQRSTUVWXYZ' \
     http://username:password@localhost:17927/sign/base64

The response body is structured like this:

{
    "message": "1234567890abcdefghijklmNOPQRSTUVWXYZ",
    "signature": "Ey2OP+gS0ylrwxqJkMLbtita/dAuoxtUSF1vnUzMGiMLh/kDz3nBQ927ZE9XyfQqEeUWKEoicml+c59oJJ9jDQE="
}

HTTP Request

GET http://localhost:17927/sign/base64

Errors

Swapperd uses the following error codes:

Error Code Meaning
400 Bad Request -- The request is invalid.
401 Unauthorized -- The username or password is invalid.
404 Not Found -- The HTTP endpoint or method is invalid.
500 Internal Server Error -- Swapperd has encountered an unexpected error. Please submit a bug report!