Skip to main content

Exchange

This guide explains the basic APIs and information required for integrating Alephium with a cryptocurrency exchange.

There is an integration prototype using Alephium's SDK if you prefer to read code instead.

Getting started

Local development network

To integrate with Alephium, an exchange must run a full node. Additionally, the exchange can also run explorer backend for debugging and additional indexing.

To create a local development network with explorer support, follow the instructions in the Local devnet guide. Once launched, Swagger UI can be accessed for the API interface of the full node and the explorer backend.

APIs

To keep the guide concise, relevant API queries will be provided using curl examples.

The web3 SDK contains generated Typescript APIs for both the full node and explorer backend.

Test wallet

caution

The node wallet is for testing the APIs of full node. To generate hot wallets for depositing, please check wallet generation.

Let's recover the test wallet by executing the following API. The test wallet has 1million ALPH for the address 1DrDyTr9RpRsQnDnXo2YRiPzPW4ooHX5LLoqXrqfMrpQH.

curl -X 'PUT' \
'http://127.0.0.1:22973/wallets' \
-H 'accept: application/json' \
-H 'Content-Type: application/json' \
-d '{
"password": "test",
"mnemonic": "vault alarm sad mass witness property virus style good flower rice alpha viable evidence run glare pretty scout evil judge enroll refuse another lava",
"walletName": "test"
}'

# Response
# {
# "walletName": "test"
# }

Get the public key of the address by querying:

curl -X 'GET' \
'http://127.0.0.1:22973/wallets/test/addresses/1DrDyTr9RpRsQnDnXo2YRiPzPW4ooHX5LLoqXrqfMrpQH' \
-H 'accept: application/json'

# Response
# {
# "address": "1DrDyTr9RpRsQnDnXo2YRiPzPW4ooHX5LLoqXrqfMrpQH",
# "publicKey": "0381818e63bd9e35a5489b52a430accefc608fd60aa2c7c0d1b393b5239aedf6b0",
# "group": 0,
# "path": "m/44'/1234'/0'/0/0"
# }

Transaction APIs

Create a transaction

Let's build a transaction to send 1.23 ALPH to address 1C2RAVWSuaXw8xtUxqVERR7ChKBE1XgscNFw73NSHE1v3.

# `fromPublicKey` is the public key of the wallet address

curl -X 'POST' \
'http://127.0.0.1:22973/transactions/build' \
-H 'accept: application/json' \
-H 'Content-Type: application/json' \
-d '{
"fromPublicKey": "0381818e63bd9e35a5489b52a430accefc608fd60aa2c7c0d1b393b5239aedf6b0",
"destinations": [
{
"address": "1C2RAVWSuaXw8xtUxqVERR7ChKBE1XgscNFw73NSHE1v3",
"attoAlphAmount": "1230000000000000000"
}
]
}'

# Response:
# {
# "unsignedTx": "00040080004e20c1174876e8000137a444479fa782e8b88d4f95e28b3b2417e5bc30d33a5ae8486d4a8885b82b224259c1e6000381818e63bd9e35a5489b52a430accefc608fd60aa2c7c0d1b393b5239aedf6b002c41111d67bb1bb000000a3cd757be03c7dac8d48bf79e2a7d6e735e018a9c054b99138c7b29738c437ec00000000000000000000c6d3c20ab5db74a5b8000000bee85f379545a2ed9f6cceb331288842f378cf0f04012ad4ac8824aae7d6f80a00000000000000000000",
# "gasAmount": 20000,
# "gasPrice": "100000000000",
# "txId": "a6c14ad03597ce96ebf78b336aded654395f38e0274c810183c4847d9af3d617",
# "fromGroup": 0,
# "toGroup": 1
# }

Sign a transaction

Let's sign the transaction id:

curl -X 'POST' \
'http://127.0.0.1:22973/wallets/test/sign' \
-H 'accept: application/json' \
-H 'Content-Type: application/json' \
-d '{
"data": "a6c14ad03597ce96ebf78b336aded654395f38e0274c810183c4847d9af3d617"
}'

# Response
# {
# "signature": "78a607ec26165b5a63d7e30a0c85657e8a0fe3b7efccdba78166e51b52c32c9020f921e0a29b6a436ec330c3b3eb2222ee851e718e3504b1a70d73ba45cd503c"
# }

Submit a transaction

Let's submit the transaction to the network:

# `unsignedTx` is from the response of transaction building
# `signature` is from the response of transaction signing

curl -X 'POST' \
'http://127.0.0.1:22973/transactions/submit' \
-H 'accept: application/json' \
-H 'Content-Type: application/json' \
-d '{
"unsignedTx": "00040080004e20c1174876e8000137a444479fa782e8b88d4f95e28b3b2417e5bc30d33a5ae8486d4a8885b82b224259c1e6000381818e63bd9e35a5489b52a430accefc608fd60aa2c7c0d1b393b5239aedf6b002c41111d67bb1bb000000a3cd757be03c7dac8d48bf79e2a7d6e735e018a9c054b99138c7b29738c437ec00000000000000000000c6d3c20ab5db74a5b8000000bee85f379545a2ed9f6cceb331288842f378cf0f04012ad4ac8824aae7d6f80a00000000000000000000",
"signature": "78a607ec26165b5a63d7e30a0c85657e8a0fe3b7efccdba78166e51b52c32c9020f921e0a29b6a436ec330c3b3eb2222ee851e718e3504b1a70d73ba45cd503c"
}'

# Response
# {
# "txId": "a6c14ad03597ce96ebf78b336aded654395f38e0274c810183c4847d9af3d617",
# "fromGroup": 0,
# "toGroup": 1
# }

You can also use the /transactions/build endpoint to build a token transfer transaction:

curl -X 'POST' \
'http://127.0.0.1:22973/transactions/build' \
-H 'accept: application/json' \
-H 'Content-Type: application/json' \
-d '{
"fromPublicKey": "0381818e63bd9e35a5489b52a430accefc608fd60aa2c7c0d1b393b5239aedf6b0",
"destinations": [
{
"address": "1C2RAVWSuaXw8xtUxqVERR7ChKBE1XgscNFw73NSHE1v3",
"attoAlphAmount": "10000000000000000",
"tokens": [
{
"id": "19246e8c2899bc258a1156e08466e3cdd3323da756d8a543c7fc911847b96f00",
"amount": "1000000000000000000"
}
],
}
]
}'

In addition to using the raw endpoint, you can also refer to this guide on how to use the @alephium/web3 SDK to build and send transactions.

The @alephium/web3 SDK also provides APIs to extract ALPH and token deposits from a transaction:

import { getALPHDepositInfo, getDepositInfo, getSenderAddress } from '@alephium/web3'

// extract ALPH deposit info from a transaction
const alphDepositInfo = getALPHDepositInfo(tx)
// [
// {
// targetAddress: '1khyjTYdKEyCSyg6SqyDf97Vq3EmSJF9zPugb3KYERP8',
// depositAmount: 1000000000000000000n
// }
// ]

// get the sender address of the deposit transaction
const senderAddress = getSenderAddress(tx)

// extract ALPH and token deposit info from a transaction
const depositInfo = getDepositInfo(tx)
// {
// alph: [
// {
// targetAddress: '1khyjTYdKEyCSyg6SqyDf97Vq3EmSJF9zPugb3KYERP8',
// depositAmount: 1000000000000000000n
// }
// ],
// tokens: [
// {
// tokenId: '19246e8c2899bc258a1156e08466e3cdd3323da756d8a543c7fc911847b96f00',
// targetAddress: '1khyjTYdKEyCSyg6SqyDf97Vq3EmSJF9zPugb3KYERP8',
// depositAmount: 1000000000000000000n
// }
// ]
// }

You can filter the deposit information sent to your exchange address by targetAddress and tokenId.

Fetching Fungible Token Metadata

You can use the API provided by the @alephium/web3 SDK to fetch fungible token metadata:

import { NodeProvider } from '@alephium/web3'

const tokenId = '1a281053ba8601a658368594da034c2e99a0fb951b86498d05e76aedfe666800'
const nodeProvider = new NodeProvider('http://127.0.0.1:12973')
const metadata = await nodeProvider.fetchFungibleTokenMetaData(tokenId)
// {
// symbol: '4159494e',
// name: '4159494e',
// decimals: 18,
// totalSupply: 1693518090594172274149304n
// }

The symbol and name are hex-encoded UTF-8 strings.

You can also use the raw endpoint to fetch fungible token metadata:

curl -X 'POST' \
'http://127.0.0.1:12973/contracts/multicall-contract' \
-H 'accept: application/json' \
-H 'Content-Type: application/json' \
-d '{
"calls": [
{
"group": 0,
"address": "vT49PY8ksoUL6NcXiZ1t2wAmC7tTPRfFfER8n3UCLvXy",
"methodIndex": 0
},
{
"group": 0,
"address": "vT49PY8ksoUL6NcXiZ1t2wAmC7tTPRfFfER8n3UCLvXy",
"methodIndex": 1
},
{
"group": 0,
"address": "vT49PY8ksoUL6NcXiZ1t2wAmC7tTPRfFfER8n3UCLvXy",
"methodIndex": 2
},
{
"group": 0,
"address": "vT49PY8ksoUL6NcXiZ1t2wAmC7tTPRfFfER8n3UCLvXy",
"methodIndex": 3
}
]
}'

All contracts that follow the fungible token standard can use this request to fetch the token metadata. This request calls the contract methods indexed at 0, 1, 2, and 3, which respectively return the token symbol, token name, token decimals, and token total supply.

This request uses the token address instead of the token ID. You can refer to the code below to convert the token ID to the token address:

const tokenAddress = base58.encode([0x03, ...hexToBytes(tokenId)])

The following includes part of the response information for simplicity:

{
"results": [
{
"type": "CallContractSucceeded",
"returns": [{ "type": "ByteVec", "value": "4159494e" }]
},
{
"type": "CallContractSucceeded",
"returns": [{ "type": "ByteVec", "value": "4159494e" }]
},
{
"type": "CallContractSucceeded",
"returns": [{ "type": "U256", "value": "18" }],
},
{
"type": "CallContractSucceeded",
"returns": [{ "type": "U256", "value": "1693518090594172274149304" }]
}
]
}

Block APIs

Get block hash with transaction ID

To get the block hash of a confirmed transaction, you can use the full node API:

curl -X 'GET' \
'http://127.0.0.1:22973/transactions/status?txId=a6c14ad03597ce96ebf78b336aded654395f38e0274c810183c4847d9af3d617' \
-H 'accept: application/json'

# Response
# {
# "type": "Confirmed",
# "blockHash": "1d616d33a7aadc3cf49f5db1cc484b22a642140673f66020c13dc7648b9382d1",
# "txIndex": 0,
# "chainConfirmations": 1,
# "fromGroupConfirmations": 1,
# "toGroupConfirmations": 0
# }

Get block with block hash

curl -X 'GET' \
'http://127.0.0.1:22973/blockflow/blocks-with-events/ecbc7a3115eb0da1f82902db226b80950e861ef8cbb6623ed02fc42a6eeb69cb' \
-H 'accept: application/json'

# Response
# {
# "block": {
# "hash": "ecbc7a3115eb0da1f82902db226b80950e861ef8cbb6623ed02fc42a6eeb69cb",
# "timestamp": 1231006505000,
# "chainFrom": 2,
# "chainTo": 3,
# "height": 0,
# ...
# },
# "events": []
# }

Polling for blocks

In Alephium, you can fetch all the blocks from all the chains for a given time interval because it is a sharded blockchain with multiple chains operating at different heights simultaneously.

curl -X 'GET' \
'http://127.0.0.1:22973/blockflow/blocks?fromTs=0&toTs=30' \
-H 'accept: application/json'

# Response: there are 16 chains, therefore 16 lists of block hashes
# {
# "blocks": [
# [],
# ...
# []
# ]
# }

You can retrieve blocks for each chain individually using this endpoint:

curl -X 'GET' \
'http://127.0.0.1:22973/blockflow/chain-info?fromGroup=2&toGroup=3' \
-H 'accept: application/json'

# Response
# {
# "currentHeight": 0
# }

curl -X 'GET' \
'http://127.0.0.1:22973/blockflow/hashes?fromGroup=2&toGroup=3&height=0' \
-H 'accept: application/json'

# Response
# {
# "headers": [
# "ecbc7a3115eb0da1f82902db226b80950e861ef8cbb6623ed02fc42a6eeb69cb"
# ]
# }

UTXO Management

Why UTXO management?

In practice, some miners tend to send mining rewards directly to exchange addresses, resulting in a large number of small-valued UTXOs in the exchange's hot wallets. However, due to the limited number of inputs that can be included in each transaction, withdrawals may fail if the hot wallet is filled with these small UTXOs.

How to consolidate small-valued UTXOs?

If your exchange already has a proper UTXO management framework in place, you are in good shape. However, if you don't, there is a simple solution available. You can utilize the sweep endpoint to consolidate the small-valued UTXOs of a specific address. Please note that this feature is only accessible starting from full node 2.3.0.

# `maxAttoAlphPerUTXO` refers to the maximum amount of ALPH in the UTXOs to be consolidated.

curl -X 'POST' \
'http://127.0.0.1:22973/transactions/sweep-address/build' \
-H 'accept: application/json' \
-H 'Content-Type: application/json' \
-d '{
"fromPublicKey": "0381818e63bd9e35a5489b52a430accefc608fd60aa2c7c0d1b393b5239aedf6b0",
"toAddress": "1DrDyTr9RpRsQnDnXo2YRiPzPW4ooHX5LLoqXrqfMrpQH",
"maxAttoAlphPerUTXO": "100 ALPH"
}'

How to work with designated UTXOs?

To create transactions more efficiently, an exchange is recommended to store the set of UTXOs of their hot wallets and then provide specific UTXOs through the API.

curl -X 'POST' \
'http://127.0.0.1:22973/transactions/build' \
-H 'accept: application/json' \
-H 'Content-Type: application/json' \
-d '{
"fromPublicKey": "0381818e63bd9e35a5489b52a430accefc608fd60aa2c7c0d1b393b5239aedf6b0",
"destinations": [
{
"address": "1C2RAVWSuaXw8xtUxqVERR7ChKBE1XgscNFw73NSHE1v3",
"attoAlphAmount": "230000000000000000"
}
]
"utxos": [
{
"hint": 714242201,
"key": "3bfdeea82a5702cdd98426546d9eeecd744cc540aaffc5ec8ea998dc105da46f"
}
]
}'

hint and key for the UTXO are fetched from the first output of the first transaction we made. key is unique and can be used to index the UTXO.

curl -X 'GET' \
'http://127.0.0.1:22973/transactions/details/a6c14ad03597ce96ebf78b336aded654395f38e0274c810183c4847d9af3d617' \
-H 'accept: application/json'

# Response
# {
# "unsigned": {
# "txId": "a6c14ad03597ce96ebf78b336aded654395f38e0274c810183c4847d9af3d617",
# ...
# "fixedOutputs": [
# {
# "hint": 714242201,
# "key": "3bfdeea82a5702cdd98426546d9eeecd744cc540aaffc5ec8ea998dc105da46f",
# "attoAlphAmount": "1230000000000000000",
# "address": "1C2RAVWSuaXw8xtUxqVERR7ChKBE1XgscNFw73NSHE1v3",
# ...
# },
# {
# "hint": 933512263,
# "key": "087ee967733900cc7f7beada612ba514dd134ddffc2ad1b6ad8b6998915089c4",
# "attoAlphAmount": "999998768000000000000000",
# "address": "1DrDyTr9RpRsQnDnXo2YRiPzPW4ooHX5LLoqXrqfMrpQH",
# ...
# }
# ]
# },
# ...
# }

More Information

Wallet generation

To generate multiple addresses for users, you can use the HD-wallet in the web3 SDK.

Sharding

Alephium is a sharded blockchain and its addresses are split into 4 groups on the mainnet. However, one can:

  • Send ALPH to multiple addresses that belong to the same address group in a single transaction. All the destination addresses must belong to the same group.
  • Send ALPH from multiple addresses that belong to the same address group in a single transaction. All the sending addresses must belong to the same group.
  • Send ALPH from multiple addresses that belong to the same group to multiple addresses that belong to another group. All the sending addresses must belong to the same group, and all the destination addresses must belong to the same group too.

To get the group of an address, you can refer to the web3 SDK function groupOfAddress(address).

Gas computation

Alephium's transaction fees are determined by the amount of gas allocated and the gas price. A maximum gas amount of 5,000,000 can be assigned to each transaction. The default gas price is set at 1e11 attoALPH per gas unit. When conducting a simple transfer transaction, the gas amount can be computed using the following pseudo code:

txInputBaseGas = 2000
txOutputBaseGas = 4500
inputGas = txInputBaseGas * tx.inputs.length
outputGas = txOutputBaseGas * tx.outputs.length

txBaseGas = 1000
p2pkUnlockGas = 2060 // Currently there is only one signature

txGas = inputGas + outputGas + txBaseGas + p2pkUnlockGas
minimalGas = 20000

gas = max(minimalGas, txGas)