In order to communicate with Ledger’s back-end, you have to give us the mapping of the endpoints we need.
As you can see on the diagram What do we need?, there are 5 main endpoints needed for the swap:
- To get the list of available currencies: /currencies.
- To get the list of tradable pairs: /pairs.
- To query a rate: /quote.
- To create a signed swap binary payload /swap
- To query a swap status: /status.
You will find the details about each needed endpoint below.
GET /currencies
- Function: Returns a list of supported currencies.
- Input: –
- Output: Array of supported currencies, with information required to uniquely identify coins/tokens.
- Payload:
[
{
"id": "BTC",
"type": "coin",
"blockchain": "bitcoin",
"chainId": 1
},
{
"id": "USDC",
"type": "token",
"blockchain": "ethereum",
"chainId": 1,
"contract": "0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48"
}
]
GET /pairs
- Function: Returns a list of supported pairs. This endpoint is not required if all permutations returned by /currencies are supported.
- Input: –
- Output: Array of supported swap pairs, with supported quote type (
fixed
/ float
).
- Payload:
[
{
"from": "btc",
"to": "bat",
"tradeMethod":[
"fixed",
"float"
]
},
{
"from": "bat",
"to": "btc",
"tradeMethod":[
"fixed",
"float"
]
}
]
Fixed quote: The quote price is guaranteed until execution (or until end of quote validity period).
Float quote: The quote price is indicative only, real price is computed at execution time
POST /quote
- Function: Returns a quote for a pair and amount.
- Input:
from
: currency id in your system.
to
: currency id in your system.
amount
: amount requested by the user, without the gas fee.
- Output:
quoteId
: quote unique identifier (optional, only required for fixed quotes)
amount
: should be same as the input one (optional, only required for consistency check) (as an input coin floating amount)
amountTo
: estimated output amount that will be sent to user (as an output coin floating amount)
providerFees
: amount, expressed in currencyTo, that is paid to the provider.
referralFees
: amount, expressed in currencyTo, that is paid to Ledger (as a referral fee).
payoutNetworkFees
: estimated gas fees that will be used for the payout transaction (as an output coin floating amount)
tradeMethod
: fixed
or float
(optional, only required for consistency check)
expiry
: quote expiration timestamp (optional, only required for fixed quotes)
minAmountFrom
: minimum valid amount
for this pair (optional, only required if amount is too low)
maxAmountFrom
: maximum valid amount
for this pair (optional, only required if amount is too large)
- Payload:
- Success
{
"quoteId": "CC14E626-CF1B-4EDA-AF5E-766FFD5A3457",
"amount": "1",
"amountTo": "270.864632",
"providerFees": "0.0001",
"referralFees": "0.0001",
"payoutNetworkFees": "0.0002",
"tradeMethod": "float",
"expiry": "2022-04-04T09:10:51+0000" // ISO-8601 date format
}
All fees are expressed in output currency. For example, during a ETH to USDT on ethereum swap, the fees are expressed in USDT on ethereum.
Some requirements about the /quote endpoint:
- The quote must work without user auth. It can require a Ledger auth.
- The quote must be valid long enough (at least a few minutes).
POST /swap
- Function: Generates a secure binary payload for the nano in order to authorize the transaction. The provider should validate consistency between
quoteId
and other inputs (addresses, amounts and currencies).
- Input:
quoteId
: previously generated quote id
refundAddress
: user’s refund address. This address is generaly the fromAddress.
refundAddressExtraId
: tag or memo (optional).
payoutAddress
: user’s payout transaction address.
payoutAddressExtraId
: tag or memo (optional).
currencyFrom
: from currency id, using your identifiers (for checking purpose on your side only) (optional).
currencyTo
: to currency id, using you identifiers (for checking purpose on your side only) (optional).
payloadCurrencyFrom
: from currency id, using ledger’s referencial. This value has to be used for currency_from
field in the protobuf payload.
payloadCurrencyTo
: to currency id, using ledger’s referencial. This value has to used for currency_to
field in the protobuf payload.
amountToProvider
: amount of currencyFrom
that the provider expects to receive from client.
amountToWallet
: amount of currencyTo
that the provider agrees to send to the client in exchange from (optional for a float trade).
nonce
: value to use for device_transaction_id
field in the protobuf payload.
- Output:
swapId
: provider’s id of the ongoing swap transaction. This will be used by Ledger’s backend to retrieve the status of user swap transaction.
payinAddress
: provider’s address.
createdAt
: date of creation of the swap payload in ISO-8601 format.
providerFees
: amount, expressed in currencyTo, that is paid to the provider.
referralFees
: amount, expressed in currencyTo, that is paid to Ledger (as a referral fee).
payoutNetworkFees
: estimated gas fees that will be used for the payout transaction (as an output coin floating amount).
providerSig
:
payload
: protobuf payload (in a binary format)
signature
: payload signed in a JWS format
- Payload:
- Success
Refer to payload in the JWS signature section below.
{
"swapId":"SWAP-ID-165940",
"payinAddress":"0xa0b86991c627e936c1d19d4a2e90a2ce3606eb48",
"createAt": "2030-05-26T14:13:39",
"providerFees": "0.0001",
"referralFees": "0.0001",
"payoutNetworkFees": "0.0002",
"providerSig":
{
"payload":"CgUweGZmZhoFMHhmZmYqBTB4ZmZmOgNCVENCA0JBVEoIMTIwMDAwMDBSCDExNTAwMDAwWhF2ZXJ5IGxvbmd1ZSBub25jZQ==",
"signature": "MEUCIBRm4PrdgRw0aBwRepuOGGRmR/YPcCoyKNJ7UDjFO030AiEA/VT0anolum0a3X/9lGPeovZHqzeDG9brcUB4zhYmwbs="
}
}
- Error
{
code: "KYC_PENDING",
error: "Your KYC is under validation" ,
description: "Your KYC is under validation by an operator"
}
Here is a little diagram to explain how the payload
and the signature
are generated:

payload
: the trade parameters are assembled in a protobuf message. Then using the protobuf tools we do a binary encoding of the protobuf (Byte Array). Finally, with base64 encoding we get the payload
field.
signature
: From the binary encoding of the previous protobuf (Byte Array), we sign it with ES256 and the provider’s private key to get a Signature Byte Array. Finally, with base64 encoding we get the signature
.
A nonce field will be passed as a parameter of the /swap endpoint.
It is a 32 bytes nonce which is generated by the hardware wallet to avoid replay attacks.
It will be base 64 URL encoded before being sent to the /swap endpoint.
Protobuf message (payload)
Your Protobuf message should have the following structure:
syntax = "proto3";
package ledger_swap;
message NewTransactionResponse {
string payin_address = 1;
string payin_extra_id = 2;
string refund_address = 3;
string refund_extra_id = 4;
string payout_address = 5;
string payout_extra_id = 6;
string currency_from = 7;
string currency_to = 8;
bytes amount_to_provider = 9;
bytes amount_to_wallet = 10;
string device_transaction_id = 11; // nonce
}
Explanation of each fields:
payin_address
: provider address to receive payment
payin_extra_id
: eventual memo for the payment (stellar payment, for instance)
refund_address
: client address to receive back the payment funds in case the provider is not able to execute the swap for some unpredictable reasons
refund_extra_id
: eventual memo for the payment (stellar payment, for instance)
payout_address
: client address to receive the money resulting from a successful swap
payout_extra_id
: eventual memo for the payment (stellar payment, for instance)
currency_from
: must be set to value of payloadCurrencyFrom
from the /swap API request
currency_to
: must be set to value of payloadCurrencyTo
from the /swap API request
amount_to_provider
: amount of currency_from
that the provider expects to receive from client
amount_to_wallet
: amount of currency_to
that the provider agrees to send to the client in exchange from amount_to_provider
. This amount must also include the network fees that the provider will pay to send the crypto to the user.
device_transaction_id
: swap transaction nonce provided by client at initialization (must be set to value of nonce
from the API request)
Amounts must be in the lowest unit of the coin, encoded into a 16 bytes array in big endian.
Example:
- 1 BTC would be
0x5F5E100
(100000000 in hexadecimal). The smallest unit is a satoshi which is 10^-8
BTC.
So multiply 1 BTC by 10^8
→ 0x5F5E100
.
And 0x5F5E100
encoded into a 16 bytes array in big endian is [0x00, ... 0x00, 0x05, 0xF5, 0xE1, 0x00]
.
- 2 ETH would be
0x1BC16D674EC80000
(or 2000000000000000000). The smallest unit is a wei which is 10^-18
ETH.
So multiply 2 ETH by 10^18
→ 0x1BC16D674EC80000
.
And 0x1BC16D674EC80000
encoded into a 16 bytes array in big endian is [0x00, ... 0x00, 0x1B, 0xC1, 0x6D, 0x67, 0x4E, 0xC8, 0x00, 0x00]
.
Output field: providerSig
The real return value of the /swap endpoint is the providerSig
field with the JSON Web Signature (JWS) in compact form within:
providerSig.payload
- base 64 URL of the binary serialized protobuf message.NewTransactionResponse.
providerSig.signature
- base 64 URL of the ES256 signature of providerSig.payload. More details in the JWS signature and Protobuf message (payload) sections.
Signature usage
- Payload and Signature
From the provider to Ledger Live. The payload is a protobuf message containing the trade data. It is generated by the provider and sent to Ledger Live.
payload = {
payin_address, // address (of provider)
refund_address, // address (of customer)
payout_address, // address (of customer)
currency_from, // currency name (to provider)
currency_to,
amount_to_provider, // amount (to provider)
amount_to_wallet, // amount (to customer)
device_transaction_id,
…
}
R, S = Sign(payload, priv_key)
- Validate
Exchange app checks and validates payload (signature and content).
// Compare nonce in payload
Verify((R, S), payload, pub_key)
- Display
Exchange app request approval to user by displaying swap summary on screen.
Validate?
Send currency_from amount_to_provider
Receive currency_to amount_to_wallet
JWS-Signature
The output field is providerSig
is base on the flattened JWS JSON, without the optional protected
and header
parameter.
The algorithm supported by Ledger are ES256 with secp256k1 or secp256r1 curve. There is no need to specify them into the header
,
as we will store your public key with this information in our system.
As specified in the JWA spec, the signature is a concatenation of the (R, S) pair in a 64 octets array, and not the ASN1 format.
GET /status
- Function: Returns the status of an executed swap transaction.
- Input:
swapId
.
- Output:
status
:
FINISHED
: Trade has been completed successfully (user has received payout transaction).
EXPIRED
: Payin transaction was not received in time, trade is cancelled. User will be refunded if payin transaction is received afterwards.
ON_HOLD
: Trade has been put on hold (eg: for KYC reasons). User must contact support.
PENDING
: Trade is in progress (provider is waiting to receive payin transaction, or user is waiting to receive payout transaction)
REFUNDED
: Trade has been cancelled, refund transaction has been successfully received by user.
UNKNOWN
: Trade is in unknown state. User must contact support.
amount
: as soon as this information is known, this should contain the final amount transferred to the user in output currency
payinTransactionId
: as soon as this information is known, this should contain the payin transaction hash
payoutTransactionId
: as soon as this information is known, this should contain the payout transaction hash
providerFees
: fees amount that has been paid to the provider.
referralFees
: fees amount that has been paid to Ledger (as a referral fee).
payoutNetworkFees
: gas fees that has been be used for the payout transaction (as an output coin floating amount).
- Payload:
- Success
{
"status":"FINISHED",
"amount": 1.337,
"payinTransactionId": "0xfffffffffffff",
"payoutTransactionId": "0xfffffffffffff",
"providerFees": "0.0001",
"referralFees": "0.0001",
"payoutNetworkFees": "0.0002"
}