Managing Transactions
Deposit Accounts
A Koii account exists as soon as it has a balance, so setting up a deposit account is as simple as generating a keypair using our wallet tools.
We recommend using a unique deposit account for each of your users.
Direct users to the appropriate deposit address when they want to deposit KOII into your exchange.
Deposit accounts must be rent-exempt. This is done by making sure their balance is at least 2 years worth of rent in KOII.
To find the minimum rent-exempt amount you need for your deposit accounts, query getMinimumBalanceForRentExemption
or run the CLI command:
koii rent 0
Request
curl https://testnet.koii.network -X POST -H "Content-Type: application/json" -d '{
"jsonrpc": "2.0",
"id": 1,
"method": "getMinimumBalanceForRentExemption",
"params": [0]
}'
Response
{ "jsonrpc": "2.0", "result": 890880, "id": 1 }
Offline Accounts
If you are keeping keys offline for greater security, you can move KOII to hot accounts with offline signing.
Polling for Blocks
In order to track deposit accounts, you should poll for each confirmed block and inspect it for the necessary addresses. This can be done by sending a getBlocks
request to the RPC API, setting the start-slot
parameter to the last block you processed.
Request
curl https://testnet.koii.network -X POST -H "Content-Type: application/json" -d '{
"jsonrpc": "2.0",
"id": 1,
"method": "getBlocks",
"params": [160017005, 160017015]
}'
Response
{
"jsonrpc": "2.0",
"result": [
160017005, 160017006, 160017007, 160017012, 160017013, 160017014, 160017015
],
"id": 1
}
Gaps in the sequence of integers are expected; not every slot creates a block.
Once you have the blocks to be processed, you can request each block's contents with a getBlock
request.
Request
curl https://testnet.koii.network -X POST -H 'Content-Type: application/json' -d '{
"jsonrpc": "2.0",
"id": 1,
"method": "getBlock",
"params": [
166974442,
{
"encoding": "jsonParsed",
"maxSupportedTransactionVersion": 0,
"transactionDetails": "accounts",
"rewards": false
}
]
}'
Response
{
"jsonrpc": "2.0",
"result": {
"blockHeight": 157201607,
"blockTime": 1665070281,
"blockhash": "HKhao674uvFc4wMK1Cm3UyuuGbKExdgPFjXQ5xtvsG3o",
"parentSlot": 166974441,
"previousBlockhash": "98CNLU4rsYa2HDUyp7PubU4DhwYJJhSX9v6pvE7SWsAo",
"transactions": [
... (omitted)
{
"meta": {
"err": null,
"fee": 5000,
"postBalances": [
1110663066,
1,
1040000000
],
"postTokenBalances": [],
"preBalances": [
1120668066,
1,
1030000000
],
"preTokenBalances": [],
"status": {
"Ok": null
}
},
"transaction": {
"accountKeys": [
{
"pubkey": "9aE476sH92Vz7DMPyq5WLPkrKWivxeuTKEFKd2sZZcde",
"signer": true,
"source": "transaction",
"writable": true
},
{
"pubkey": "11111111111111111111111111111111",
"signer": false,
"source": "transaction",
"writable": false
},
{
"pubkey": "G1wZ113tiUHdSpQEBcid8n1x8BAvcWZoZgxPKxgE5B7o",
"signer": false,
"source": "lookupTable",
"writable": true
}
],
"signatures": [
"2CxNRsyRT7y88GBwvAB3hRg8wijMSZh3VNYXAdUesGSyvbRJbRR2q9G1KSEpQENmXHmmMLHiXumw4dp8CvzQMjrM"
]
},
"version": 0
},
... (omitted)
]
},
"id": 1
}
We provide the preBalances
and postBalances
fields so that the balance changes can be tracked without the need for parsing the entire transaction. The starting and ending balances are listed in Roe. The index position of the balances and the accountKeys
entries will match. For example, if you want to obtain the information for the first account listed in accountKeys
(index position 0), you can find the information at [0]
and postBalances[0]
.
If you need additional information about transactions that's unavailable through this endpoint, you can fetch the block in binary format and parse it with the Javascript SDK.
-
If you don't need information about the validator fees on each block, you can disable this information with
{"rewards": false}
. -
If you're just tracking balances and don't need metadata or detailed transaction information, you can disable it by setting
{"transactionDetails": "accounts"}
. This will speed up block fetching.
Fetching Address History
Querying the transaction history of a specific address can be useful for inspecting individual accounts for a period of time, but it should not be used for tracking all deposit addresses over all slots.
To query an address:
- Send a
getSignaturesForAddress
request to the RPC node.
Request
curl https://testnet.koii.network -X POST -H "Content-Type: application/json" -d '{
"jsonrpc": "2.0",
"id": 1,
"method": "getSignaturesForAddress",
"params": [
"3M2b3tLji7rvscqrLAHMukYxDK2nB96Q9hwfV6QkdzBN",
{
"limit": 3
}
]
}'
Response
{
"jsonrpc": "2.0",
"result": [
{
"blockTime": 1662064640,
"confirmationStatus": "finalized",
"err": null,
"memo": null,
"signature": "3EDRvnD5TbbMS2mCusop6oyHLD8CgnjncaYQd5RXpgnjYUXRCYwiNPmXb6ZG5KdTK4zAaygEhfdLoP7TDzwKBVQp",
"slot": 148697216
},
{
"blockTime": 1662064434,
"confirmationStatus": "finalized",
"err": null,
"memo": null,
"signature": "4rPQ5wthgSP1kLdLqcRgQnkYkPAZqjv5vm59LijrQDSKuL2HLmZHoHjdSLDXXWFwWdaKXUuryRBGwEvSxn3TQckY",
"slot": 148696843
},
{
"blockTime": 1662064341,
"confirmationStatus": "finalized",
"err": null,
"memo": null,
"signature": "36Q383JMiqiobuPV9qBqy41xjMsVnQBm9rdZSdpbrLTGhSQDTGZJnocM4TQTVfUGfV2vEX9ZB3sex6wUBUWzjEvs",
"slot": 148696677
}
],
"id": 1
}
- For each signature returned, get the transaction details by sending a
getTransaction
request.
Request
curl https://testnet.koii.network -X POST -H 'Content-Type: application/json' -d '{
"jsonrpc":"2.0",
"id":1,
"method":"getTransaction",
"params":[
"2CxNRsyRT7y88GBwvAB3hRg8wijMSZh3VNYXAdUesGSyvbRJbRR2q9G1KSEpQENmXHmmMLHiXumw4dp8CvzQMjrM",
{
"encoding":"jsonParsed",
"maxSupportedTransactionVersion":0
}
]
}'
Response
{
"jsonrpc": "2.0",
"result": {
"blockTime": 1665070281,
"meta": {
"err": null,
"fee": 5000,
"innerInstructions": [],
"logMessages": [
"Program 11111111111111111111111111111111 invoke [1]",
"Program 11111111111111111111111111111111 success"
],
"postBalances": [1110663066, 1, 1040000000],
"postTokenBalances": [],
"preBalances": [1120668066, 1, 1030000000],
"preTokenBalances": [],
"rewards": [],
"status": {
"Ok": null
}
},
"slot": 166974442,
"transaction": {
"message": {
"accountKeys": [
{
"pubkey": "9aE476sH92Vz7DMPyq5WLPkrKWivxeuTKEFKd2sZZcde",
"signer": true,
"source": "transaction",
"writable": true
},
{
"pubkey": "11111111111111111111111111111111",
"signer": false,
"source": "transaction",
"writable": false
},
{
"pubkey": "G1wZ113tiUHdSpQEBcid8n1x8BAvcWZoZgxPKxgE5B7o",
"signer": false,
"source": "lookupTable",
"writable": true
}
],
"addressTableLookups": [
{
"accountKey": "4syr5pBaboZy4cZyF6sys82uGD7jEvoAP2ZMaoich4fZ",
"readonlyIndexes": [],
"writableIndexes": [3]
}
],
"instructions": [
{
"parsed": {
"info": {
"destination": "G1wZ113tiUHdSpQEBcid8n1x8BAvcWZoZgxPKxgE5B7o",
"lamports": 10000000,
"source": "9aE476sH92Vz7DMPyq5WLPkrKWivxeuTKEFKd2sZZcde"
},
"type": "transfer"
},
"program": "system",
"programId": "11111111111111111111111111111111"
}
],
"recentBlockhash": "BhhivDNgoy4L5tLtHb1s3TP19uUXqKiy4FfUR34d93eT"
},
"signatures": [
"2CxNRsyRT7y88GBwvAB3hRg8wijMSZh3VNYXAdUesGSyvbRJbRR2q9G1KSEpQENmXHmmMLHiXumw4dp8CvzQMjrM"
]
},
"version": 0
},
"id": 1
}
Handling Withdrawals
When a user wishes to withdraw KOII, generate a Koii transfer transaction and send it to your RPC node for forwarding to the cluster.
Synchronous Withdrawals
You can easily ensure that a transfer is successful by sending a synchronous transfer to the Koii cluster.
The Koii CLI tool offers the command koii transfer
, which allows you manage and confirm transfer transactions. This method is synchronous, so it continue to track progress until the transfer has been finalized. It will also report any errors in the event the transfer fails.
koii transfer <USER_ADDRESS> <AMOUNT> --allow-unfunded-recipient --keypair <KEYPAIR> --url http://localhost:10899
You can also accomplish this with the Koii Javascript SDK. You can build a transaction with SystemProgram
and submit it with sendAndConfirmTransaction
.
Asynchronous Withdrawals
If you wish to submit withdrawals asynchronously, it is your responsibility to ensure the transactions succeeded and was finalized.
To ensure you do not double spend, it is vital that you do not retry a withdrawal until the transaction's recent blockhash has expired, even if it does not appear to be confirmed or finalized. Blockhash expiration is explained in more detail below.
To get the recent blockhash, send a request to getFees
.
koii fees --url http://localhost:10899
To send a transaction asynchronously, pass the --no-wait
flag in the Koii CLI tool and include the recent blockhash with the --blockhash
argument.
koii transfer <USER_ADDRESS> <AMOUNT> --no-wait --allow-unfunded-recipient --blockhash <RECENT_BLOCKHASH> --keypair <KEYPAIR> --url http://localhost:10899
Alternatively, you can build, sign, and serialize the transfer manually, then sent it to the cluster using the sendTransaction
endpoint.
Checking Transaction Status
You can use the getSignatureStatuses
JSON-RPC endpoint to get the status of a batch of transactions. You can check the number of confirmed blocks that have elapsed since the transaction was processed by checking the confirmations
field. If confirmations
is null
, the transaction has been finalized.
Request
curl https://testnet.koii.network -X POST -H "Content-Type: application/json" -d '{
"jsonrpc":"2.0",
"id":1,
"method":"getSignatureStatuses",
"params":[
[
"5VERv8NMvzbJMEkV8xnrLkEaWRtSz9CosKDYjCJjBRnbJLgp8uirBgmQpjKhoR4tjF3ZpRzrFmBV6UjKdiSZkQUW",
"5j7s6NiJS3JAkvgkoc18WVAsiSaci2pxB2A6ueCJP4tprA2TFg9wSyTLeYouxPBJEMzJinENTkpA52YStRW5Dia7"
]
]
}'
Result
{
"jsonrpc": "2.0",
"result": {
"context": {
"slot": 82
},
"value": [
{
"slot": 72,
"confirmations": 10,
"err": null,
"status": {
"Ok": null
}
},
{
"slot": 48,
"confirmations": null,
"err": null,
"status": {
"Ok": null
}
}
]
},
"id": 1
}
Checking Blockhash Expiration
By passing a blockhash to the getFeeCalculatorForBlockhash
endpoint with the blockhash
parameter, you can check whether a given blockhash has expired. The blockhash is expired when the response is null
, and you can safely retry a failed withdrawal transaction.
Withdrawals are irreversible. To avoid accidental loss of a user's funds, we recommend validating all user-supplied account addresses.
Validating User-Supplied Account Addresses
Address Verification
There is no checksum on Koii addresses. It is highly recommended to decode the string and confirm that the length of the byte array is 32. However, it is possible for a mistyped address to decode to 32 bytes, so we also recommend checking the balance of each withdrawal address and asking the user to confirm their intentions when the balance is greater than zero.
Koii addresses are generated as 32-byte arrays, encoded with the bitcoin base58 alphabet. The following regex describes the ASCII text string that is generated:
[1-9A-HJ-NP-Za-km-z]{32,44}
Validating Public Keys
A normal Koii account address is a 256-bit ed25519 public key, encoded as a base-58 string. It is recommended to confirm that user-supplied account addresses are valid ed25519 public keys.
Here is a Java example of validating a user-supplied address as a valid ed25519 public key:
The following code sample assumes you're using the Maven.
pom.xml
:
<repositories>
...
<repository>
<id>spring</id>
<url>https://repo.spring.io/libs-release/</url>
</repository>
</repositories>
...
<dependencies>
...
<dependency>
<groupId>io.github.novacrypto</groupId>
<artifactId>Base58</artifactId>
<version>0.1.3</version>
</dependency>
<dependency>
<groupId>cafe.cryptography</groupId>
<artifactId>curve25519-elisabeth</artifactId>
<version>0.1.0</version>
</dependency>
<dependencies>
import io.github.novacrypto.base58.Base58;
import cafe.cryptography.curve25519.CompressedEdwardsY;
public class PubkeyValidator
{
public static boolean verifyPubkey(String userProvidedPubkey)
{
try {
return _verifyPubkeyInternal(userProvidedPubkey);
} catch (Exception e) {
return false;
}
}
public static boolean _verifyPubkeyInternal(String maybePubkey) throws Exception
{
byte[] bytes = Base58.base58Decode(maybePubkey);
return !(new CompressedEdwardsY(bytes)).decompress().isSmallOrder();
}
}
Minimum Transaction Amounts
Deposit and withdrawal transactions must be at least equal to the minimum rent-exempt balance for a KOII account holding no data. You can check this via the CLI using koii rent 0
.
Similarly, every deposit account must contain at least this balance.
Request
curl https://testnet.koii.network -X POST -H "Content-Type: application/json" -d '{
"jsonrpc": "2.0",
"id": 1,
"method": "getMinimumBalanceForRentExemption",
"params": [0]
}'
Response
{ "jsonrpc": "2.0", "result": 890880, "id": 1 }