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:
- Agree that one party begins the atomic swap.
- Exchange their Swapperd addresses for the relevant tokens.
Before responding to an atomic swap, swapperd assumes that the two parties:
- Exchange the
timeLock
andsecretHash
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:
- Bitcoin: "bitcoin", "btc", "xbt"
- Ether: "ethereum", "eth", "ether"
- WrappedBitcoin: "wrappedbtc", "wbtc", "wrappedbitcoin"
- Ren: "ren", "republictoken", "republic token"
- TrueUSD: "tusd", "trueusd", "true-usd"
- DigixGoldToken: "digix-gold-token", "dgx", "dgt"
- ZeroEx: "zerox", "zrx", "0x"
- OmiseGo: "omisego", "omg", "omise-go"
- CircleUSD: "usdc", "usd-coin", "usdcoin"
- MakerDAI: "dai", "maker-dai", "makerdai"
- GeminiUSD: "gusd", "gemini-dollar", "geminidollar"
- Paxos: "pax", "paxosstandardtoken", "paxos-standard-token"
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:
- The price is equal to or better than the initial price.
- The updated send value is less than or equal to the initial value.
- The updated receive value is greater than or equal to the initial minimum receive value.
- The updated token pair is same as the initial token pair.
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! |