Crypto-to-Crypto Transactions
Send tokens from your organisation wallet to up to 30 beneficiary wallets in one on-chain batch payment on an EVM network
A crypto-to-crypto transaction (type: CRYPTO) pays tokens to one or more beneficiary wallets. You register the transaction with the API, then your own wallet executes it on-chain by calling the Skyline batch payments contract. Skyline sees the on-chain event and completes the transaction.
There is no quote, no commission and no accept call.
Read Transactions first for statuses, preconditions and errors.
Supported networks: EVM networks. Crypto-to-crypto on Solana is not currently supported through the API.
Prerequisites
- An organisation wallet holding the token. See Wallets.
- A beneficiary wallet for each payee, registered with
POST /walletsand approved by screening. See Wallets and Receivers. - An API user with the
authorizedrole, 2FA off, in anACTIVEorganisation.
Step 1: Look Up the Ids
| Call | What you take from it |
|---|---|
GET /networks/client-visible | An EVM network whose features includes CRYPTO. Keep its _id, chainId and contracts.batchPayments.address. |
GET /tokens | The token's isoCode. In its ERC20Address array, the entry whose chainId matches the network gives the token contract address. |
GET /receivers/me?paymentType=crypto | Receivers that have wallets, with their wallets. Keep each beneficiary wallet's _id. |
GET /users/me | selectedOrganisation.wallets: your organisation wallet on that network. |
The batch payments contract address is set per network. Always read it from the network object. Do not hardcode it.
Step 2: Create the Transaction
POST /transactions
{
"type": "CRYPTO",
"token": "USDC",
"network": "6650a1f2c3d4e5f6a7b8c9d0",
"walletAddress": "0x4b1c2d3e4f5a6b7c8d9e0f1a2b3c4d5e6f7a8b9c",
"payments": [
{
"type": "CRYPTO",
"wallet": "6650c3f4e5a6b7c8d9e0f1a2",
"tokenIsoCode": "USDC",
"amount": "1500",
"reason": "Invoice 88"
}
]
}Transaction fields:
| Field | Required | Description |
|---|---|---|
type | Yes | CRYPTO. |
token | Yes | The token isoCode you send. |
network | Yes | The network _id. |
walletAddress | Yes | Your organisation wallet. Must be lowercase. See below. |
payments | Yes | 1 to 30 payments. |
Payment fields:
| Field | Required | Description |
|---|---|---|
type | Yes | CRYPTO. |
wallet | Yes | The beneficiary wallet _id. |
tokenIsoCode | Yes | The token the beneficiary receives. Use the same value as token. |
amount | Yes | The token amount, as a decimal string. |
reason | Yes | A payment reference. |
instructions | No | Free-text instructions. |
Send walletAddress in lowercase. Skyline compares the address that sent the on-chain payment, in lowercase, to the stored walletAddress. A checksummed address never matches, and the transaction never completes.
The response is the transaction in DRAFT. tokensToSend is the sum of the payment amounts. There is no commission.
Step 3: Validate
POST /transactions/:id/validate returns 200 with an empty body when the transaction is valid.
Step 4: Check the Allowance and Approve
POST /transactions/:id/check-allowance returns expectedAllowance, amountAllowed, hasAllowance and needsReset, as in Crypto-to-Fiat. For crypto-to-crypto, expectedAllowance is the sum of the payment amounts, with no margin.
If hasAllowance is false, call ERC-20 approve(spender, amount) from your organisation wallet:
- Spender: the network's
contracts.batchPayments.address. - Amount:
expectedAllowancein base units, using the token's decimals. - When
needsResetistrue: approve0first, wait for the receipt, then approve the amount.
Call check-allowance again until hasAllowance is true.
Step 5: Submit
POST /transactions/:id/submit with no body.
The transaction moves to WAITING_ACCEPTANCE. Nothing is priced and nothing expires. The on-chain payment only completes a transaction in this status, so submit before you send it.
Step 6: Verify the Accept Binding
Before your wallet signs anything, confirm the transaction has not changed:
GET /transactions/:id/review-contextreturnsacceptBinding.hash.POST /transactions/:id/verify-accept-binding:
{
"acceptBindingHash": "9f2c4e7a1b3d5f6e8a0c2e4f6a8b0d2e4f6a8c0e2a4c6e8f0a2b4d6f8a0c2e4f"
}It returns 204 when the transaction matches, or 409 ACCEPT_BINDING_MISMATCH when it changed. It changes nothing. It needs the same role and organisation status as accept.
Step 7: Send the Batch Payment On-Chain
From the organisation wallet in walletAddress, call sendBatchPayments on the batch payments contract (contracts.batchPayments.address):
function sendBatchPayments(
string _transactionId,
address _token,
(address to, uint256 amount)[] _payments
) external;| Argument | Value |
|---|---|
_transactionId | The transaction _id, as a string. |
_token | The token contract address for this network, from ERC20Address. |
_payments | One entry per item in the transaction's payments array, as returned by the API: to = payment.wallet.address, amount = payment.amount in base units. |
Convert amount to base units with the token's decimals(). For example, 1500 of a 6-decimal token is 1500000000.
Pay exactly what the transaction's payments array says. Read it from GET /transactions/:id just before you build the call.
Do not call POST /transactions/:id/accept. For crypto-to-crypto on EVM it returns 400 Crypto transactions are not allowed in this method and moves the transaction to FAILED. If that happens, call submit again to return it to WAITING_ACCEPTANCE.
Step 8: Track to Completion
The contract emits BatchPaymentSent(string transactionId, address indexed sender). Skyline listens for it and, when sender matches walletAddress, moves the transaction to COMPLETED and records the txHash.
- Check your own receipt. A reverted transaction emits no event, and the Skyline transaction stays in
WAITING_ACCEPTANCE. - Poll
GET /transactions/:idevery 30 to 60 seconds untilstatusisCOMPLETED. - If the receipt succeeded but the transaction does not complete, check that
walletAddressis lowercase and that the call came from that wallet.
Endpoint Reference
| Endpoint | Method | Purpose |
|---|---|---|
| /transactions | POST | Create the transaction |
| /transactions/:id | PUT | Replace the transaction body |
| /transactions/:id/validate | POST | Validate |
| /transactions/:id/check-allowance | POST | Read the required and current allowance |
| /transactions/:id/submit | POST | Move to WAITING_ACCEPTANCE |
| /transactions/:id/review-context | GET | Read the accept binding hash |
| /transactions/:id/verify-accept-binding | POST | Confirm nothing changed before signing |
| /transactions/:id | GET | Read status |
Crypto-to-Fiat Transactions
Pay a receiver's bank account in fiat from stablecoins in your organisation wallet, from lookups to token approval, quote, accept and settlement
On-Ramp Transactions
Buy tokens with a bank transfer and receive them in your organisation wallet, from lookups and bank details to confirming the transfer and tracking delivery