Add Solana to Your Exchange | Solana (2024)

This guide describes how to add Solana's native token SOL to your cryptocurrencyexchange.

Node Setup #

We highly recommend setting up at least two nodes on high-grade computers/cloudinstances, upgrading to newer versions promptly, and keeping an eye on serviceoperations with a bundled monitoring tool.

This setup enables you:

  • to have a self-administered gateway to the Solana mainnet-beta cluster to getdata and submit withdrawal transactions
  • to have full control over how much historical block data is retained
  • to maintain your service availability even if one node fails

Solana nodes demand relatively high computing power to handle our fast blocksand high TPS. For specific requirements, please seehardware recommendations.

To run an api node:

  1. Install the Solana command-line tool suite
  2. Start the validator with at least the following parameters:
solana-validator \ --ledger <LEDGER_PATH> \ --identity <VALIDATOR_IDENTITY_KEYPAIR> \ --entrypoint <CLUSTER_ENTRYPOINT> \ --expected-genesis-hash <EXPECTED_GENESIS_HASH> \ --rpc-port 8899 \ --no-voting \ --enable-rpc-transaction-history \ --limit-ledger-size \ --known-validator <VALIDATOR_ADDRESS> \ --only-known-rpc

Customize --ledger to your desired ledger storage location, and --rpc-portto the port you want to expose.

The --entrypoint and --expected-genesis-hash parameters are all specific tothe cluster you are joining.Current parameters for Mainnet Beta

The --limit-ledger-size parameter allows you to specify how many ledgershreds your node retains on disk. If you do notinclude this parameter, the validator will keep the entire ledger until it runsout of disk space. The default value attempts to keep the ledger disk usageunder 500GB. More or less disk usage may be requested by adding an argument to--limit-ledger-size if desired. Check solana-validator --help for thedefault limit value used by --limit-ledger-size. More information aboutselecting a custom limit value isavailable here.

Specifying one or more --known-validator parameters can protect you frombooting from a malicious snapshot.More on the value of booting with known validators

Optional parameters to consider:

  • --private-rpc prevents your RPC port from being published for use by othernodes
  • --rpc-bind-address allows you to specify a different IP address to bind theRPC port

Automatic Restarts and Monitoring #

We recommend configuring each of your nodes to restart automatically on exit, toensure you miss as little data as possible. Running the solana software as asystemd service is one great option.

For monitoring, we providesolana-watchtower,which can monitor your validator and detect with the solana-validator processis unhealthy. It can directly be configured to alert you via Slack, Telegram,Discord, or Twillio. For details, run solana-watchtower --help.

solana-watchtower --validator-identity <YOUR VALIDATOR IDENTITY>

Info

You can find more information about thebest practices for Solana Watchtowerhere in the docs.

New Software Release Announcements #

We release new software frequently (around 1 release / week). Sometimes newerversions include incompatible protocol changes, which necessitate timelysoftware update to avoid errors in processing blocks.

Our official release announcements for all kinds of releases (normal andsecurity) are communicated via a discord channelcalled #mb-announcement (mb stands for mainnet-beta).

Like staked validators, we expect any exchange-operated validators to be updatedat your earliest convenience within a business day or two after a normal releaseannouncement. For security-related releases, more urgent action may be needed.

Ledger Continuity #

By default, each of your nodes will boot from a snapshot provided by one of yourknown validators. This snapshot reflects the current state of the chain, butdoes not contain the complete historical ledger. If one of your node exits andboots from a new snapshot, there may be a gap in the ledger on that node. Inorder to prevent this issue, add the --no-snapshot-fetch parameter to yoursolana-validator command to receive historical ledger data instead of asnapshot.

Do not pass the --no-snapshot-fetch parameter on your initial boot as it's notpossible to boot the node all the way from the genesis block. Instead boot froma snapshot first and then add the --no-snapshot-fetch parameter for reboots.

It is important to note that the amount of historical ledger available to yournodes from the rest of the network is limited at any point in time. Onceoperational if your validators experience significant downtime they may not beable to catch up to the network and will need to download a new snapshot from aknown validator. In doing so your validators will now have a gap in itshistorical ledger data that cannot be filled.

Minimizing Validator Port Exposure #

The validator requires that various UDP and TCP ports be open for inboundtraffic from all other Solana validators. While this is the most efficient modeof operation, and is strongly recommended, it is possible to restrict thevalidator to only require inbound traffic from one other Solana validator.

First add the --restricted-repair-only-mode argument. This will cause thevalidator to operate in a restricted mode where it will not receive pushes fromthe rest of the validators, and instead will need to continually poll othervalidators for blocks. The validator will only transmit UDP packets to othervalidators using the Gossip and ServeR ("serve repair") ports, and onlyreceive UDP packets on its Gossip and Repair ports.

The Gossip port is bi-directional and allows your validator to remain incontact with the rest of the cluster. Your validator transmits on the ServeRto make repair requests to obtaining new blocks from the rest of the network,since Turbine is now disabled. Your validator will then receive repair responseson the Repair port from other validators.

To further restrict the validator to only requesting blocks from one or morevalidators, first determine the identity pubkey for that validator and add the--gossip-pull-validator PUBKEY --repair-validator PUBKEY arguments for eachPUBKEY. This will cause your validator to be a resource drain on each validatorthat you add, so please do this sparingly and only after consulting with thetarget validator.

Your validator should now only be communicating with the explicitly listedvalidators and only on the Gossip, Repair and ServeR ports.

Setting up Deposit Accounts #

Solana accounts do not require any on-chain initialization; once they containsome SOL, they exist. To set up a deposit account for your exchange, simplygenerate a Solana keypair using any of ourwallet tools.

We recommend using a unique deposit account for each of your users.

Solana accounts must be made rent-exempt by containing 2-years worth ofrent in SOL. In order to find the minimum rent-exemptbalance for your deposit accounts, query thegetMinimumBalanceForRentExemption endpoint:

curl https://api.devnet.solana.com -X POST -H "Content-Type: application/json" -d '{ "jsonrpc": "2.0", "id": 1, "method": "getMinimumBalanceForRentExemption", "params": [0]}'
Result
{ "jsonrpc": "2.0", "result": 890880, "id": 1 }

Offline Accounts #

You may wish to keep the keys for one or more collection accounts offline forgreater security. If so, you will need to move SOL to hot accounts using ouroffline methods.

Listening for Deposits #

When a user wants to deposit SOL into your exchange, instruct them to send atransfer to the appropriate deposit address.

Versioned Transaction Migration #

When the Mainnet Beta network starts processing versioned transactions,exchanges MUST make changes. If no changes are made, deposit detection willno longer work properly because fetching a versioned transaction or a blockcontaining versioned transactions will return an error.

  • {"maxSupportedTransactionVersion": 0}

    The maxSupportedTransactionVersion parameter must be added to getBlock andgetTransaction requests to avoid disruption to deposit detection. The latesttransaction version is 0 and should be specified as the max supportedtransaction version value.

It's important to understand that versioned transactions allow users to createtransactions that use another set of account keys loaded from on-chain addresslookup tables.

  • {"encoding": "jsonParsed"}

    When fetching blocks and transactions, it's now recommended to use the"jsonParsed" encoding because it includes all transaction account keys(including those from lookup tables) in the message "accountKeys" list. Thismakes it straightforward to resolve balance changes detailed in preBalances/ postBalances and preTokenBalances / postTokenBalances.

    If the "json" encoding is used instead, entries in preBalances /postBalances and preTokenBalances / postTokenBalances may refer toaccount keys that are NOT in the "accountKeys" list and need to beresolved using "loadedAddresses" entries in the transaction metadata.

Poll for Blocks #

To track all the deposit accounts for your exchange, poll for each confirmedblock and inspect for addresses of interest, using the JSON-RPC service of yourSolana API node.

  • To identify which blocks are available, send agetBlocks request, passing the last blockyou have already processed as the start-slot parameter:
curl https://api.devnet.solana.com -X POST -H "Content-Type: application/json" -d '{ "jsonrpc": "2.0", "id": 1, "method": "getBlocks", "params": [160017005, 160017015]}'
Result
{ "jsonrpc": "2.0", "result": [ 160017005, 160017006, 160017007, 160017012, 160017013, 160017014, 160017015 ], "id": 1}

Not every slot produces a block, so there may be gaps in the sequence ofintegers.

  • For each block, request its contents with agetBlock request:

Block Fetching Tips #

  • {"rewards": false}

By default, fetched blocks will return information about validator fees on eachblock and staking rewards on epoch boundaries. If you don't need thisinformation, disable it with the "rewards" parameter.

  • {"transactionDetails": "accounts"}

By default, fetched blocks will return a lot of transaction info and metadatathat isn't necessary for tracking account balances. Set the "transactionDetails"parameter to speed up block fetching.

curl https://api.devnet.solana.com -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 } ]}'
Result
{ "jsonrpc": "2.0", "result": { "blockHeight": 157201607, "blockTime": 1665070281, "blockhash": "HKhao674uvFc4wMK1Cm3UyuuGbKExdgPFjXQ5xtvsG3o", "parentSlot": 166974441, "previousBlockhash": "98CNLU4rsYa2HDUyp7PubU4DhwYJJhSX9v6pvE7SWsAo", "transactions": [ ... (omit) { "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 }, ... (omit) ] }, "id": 1}

The preBalances and postBalances fields allow you to track the balancechanges in every account without having to parse the entire transaction. Theylist the starting and ending balances of each account inlamports, indexed to the accountKeys list. Forexample, if the deposit address of interest isG1wZ113tiUHdSpQEBcid8n1x8BAvcWZoZgxPKxgE5B7o, this transaction represents atransfer of 1040000000 - 1030000000 = 10,000,000 lamports = 0.01 SOL

If you need more information about the transaction type or other specifics, youcan request the block from RPC in binary format, and parse it using either ourRust SDK orJavascript SDK.

Address History #

You can also query the transaction history of a specific address. This isgenerally not a viable method for tracking all your deposit addresses over allslots, but may be useful for examining a few accounts for a specific period oftime.

  • Send a getSignaturesForAddressrequest to the api node:
curl https://api.devnet.solana.com -X POST -H "Content-Type: application/json" -d '{ "jsonrpc": "2.0", "id": 1, "method": "getSignaturesForAddress", "params": [ "3M2b3tLji7rvscqrLAHMukYxDK2nB96Q9hwfV6QkdzBN", { "limit": 3 } ]}'
Result
{ "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 agetTransaction request:
curl https://api.devnet.solana.com -X POST -H 'Content-Type: application/json' -d '{ "jsonrpc":"2.0", "id":1, "method":"getTransaction", "params":[ "2CxNRsyRT7y88GBwvAB3hRg8wijMSZh3VNYXAdUesGSyvbRJbRR2q9G1KSEpQENmXHmmMLHiXumw4dp8CvzQMjrM", { "encoding":"jsonParsed", "maxSupportedTransactionVersion":0 } ]}'
Result
{ "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}

Sending Withdrawals #

To accommodate a user's request to withdraw SOL, you must generate a Solanatransfer transaction, and send it to the api node to be forwarded to yourcluster.

Synchronous #

Sending a synchronous transfer to the Solana cluster allows you to easily ensurethat a transfer is successful and finalized by the cluster.

Solana's command-line tool offers a simple command, solana transfer, togenerate, submit, and confirm transfer transactions. By default, this methodwill wait and track progress on stderr until the transaction has been finalizedby the cluster. If the transaction fails, it will report any transaction errors.

solana transfer <USER_ADDRESS> <AMOUNT> --allow-unfunded-recipient --keypair <KEYPAIR> --url http://localhost:8899

The Solana Javascript SDKoffers a similar approach for the JS ecosystem. Use the SystemProgram to builda transfer transaction, and submit it using the sendAndConfirmTransactionmethod.

Asynchronous #

For greater flexibility, you can submit withdrawal transfers asynchronously. Inthese cases, it is your responsibility to verify that the transaction succeededand was finalized by the cluster.

Note: Each transaction contains arecent blockhash to indicate itsliveness. It is critical to wait until this blockhash expires beforeretrying a withdrawal transfer that does not appear to have been confirmed orfinalized by the cluster. Otherwise, you risk a double spend. See more onblockhash expiration below.

First, get a recent blockhash using thegetFees endpoint or the CLI command:

solana fees --url http://localhost:8899

In the command-line tool, pass the --no-wait argument to send a transferasynchronously, and include your recent blockhash with the --blockhashargument:

solana transfer <USER_ADDRESS> <AMOUNT> --no-wait --allow-unfunded-recipient --blockhash <RECENT_BLOCKHASH> --keypair <KEYPAIR> --url http://localhost:8899

You can also build, sign, and serialize the transaction manually, and fire itoff to the cluster using the JSON-RPCsendTransaction endpoint.

Transaction Confirmations & Finality #

Get the status of a batch of transactions using thegetSignatureStatuses JSON-RPCendpoint. The confirmations field reports how manyconfirmed blocks have elapsed since thetransaction was processed. If confirmations: null, it isfinalized.

curl https://api.devnet.solana.com -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}

Blockhash Expiration #

You can check whether a particular blockhash is still valid by sending agetFeeCalculatorForBlockhashrequest with the blockhash as a parameter. If the response value is null, theblockhash is expired, and the withdrawal transaction using that blockhash shouldnever succeed.

Validating User-supplied Account Addresses for Withdrawals #

As withdrawals are irreversible, it may be a good practice to validate auser-supplied account address before authorizing a withdrawal in order toprevent accidental loss of user funds.

Basic verification #

Solana addresses a 32-byte array, encoded with the bitcoin base58 alphabet. Thisresults in an ASCII text string matching the following regular expression:

[1-9A-HJ-NP-Za-km-z]{32,44}

This check is insufficient on its own as Solana addresses are not checksummed,so typos cannot be detected. To further validate the user's input, the stringcan be decoded and the resulting byte array's length confirmed to be 32.However, there are some addresses that can decode to 32 bytes despite a typosuch as a single missing character, reversed characters and ignored case

Advanced verification #

Due to the vulnerability to typos described above, it is recommended that thebalance be queried for candidate withdraw addresses and the user prompted toconfirm their intentions if a non-zero balance is discovered.

Valid ed25519 pubkey check #

The address of a normal account in Solana is a Base58-encoded string of a256-bit ed25519 public key. Not all bit patterns are valid public keys for theed25519 curve, so it is possible to ensure user-supplied account addresses areat least correct ed25519 public keys.

Java #

Here is a Java example of validating a user-supplied address as a valid ed25519public 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 Deposit & Withdrawal Amounts #

Every deposit and withdrawal of SOL must be greater or equal to the minimumrent-exempt balance for the account at the wallet address (a basic SOL accountholding no data), currently: 0.000890880 SOL

Similarly, every deposit account must contain at least this balance.

curl https://api.devnet.solana.com -X POST -H "Content-Type: application/json" -d '{ "jsonrpc": "2.0", "id": 1, "method": "getMinimumBalanceForRentExemption", "params": [0]}'
Result
{ "jsonrpc": "2.0", "result": 890880, "id": 1 }

Prioritization Fees and Compute Units #

In periods of high demand, it’s possible for a transaction to expire before avalidator has included such transactions in their block because they chose othertransactions with higher economic value. Valid Transactions on Solana may bedelayed or dropped if Prioritization Fees are not implemented properly.

Prioritization Fees are additionalfees that can be added on top of thebase Transaction Fee to ensuretransaction inclusion within blocks and in these situations and help ensuredeliverability.

These priority fees are added to transaction by adding a special Compute Budgetinstruction that sets the desired priority fee to be paid.

Important Note

Failure to implement these instructions may result in network disruptions anddropped transactions. It is strongly recommended that every exchange supportingSolana make use of priority fees to avoid disruption.

What is a Prioritization Fee? #

Prioritization Fees are priced in micro-lamports per Compute Unit (e.g. smallamounts of SOL) prepended to transactions to make them economically compellingfor validator nodes to include within blocks on the network.

How much should the Prioritization Fee be? #

The method for setting your prioritization fee should involve querying recentprioritization fees to set a fee which is likely to be compelling for thenetwork. Using thegetRecentPrioritizationFees RPCmethod, you can query for the prioritization fees required to land a transactionin a recent block.

Pricing strategy for these priority fees will vary based on your use case. Thereis no canonical way to do so. One strategy for setting your Prioritization Feesmight be to calculate your transaction success rate and then increase yourPrioritization Fee against a query to the recent transaction fees API and adjustaccordingly. Pricing for Prioritization Fees will be dynamic based on theactivity on the network and bids placed by other participants, only knowableafter the fact.

One challenge with using the getRecentPrioritizationFees API call is that itmay only return the lowest fee for each block. This will often be zero, which isnot a fully useful approximation of what Prioritization Fee to use in order toavoid being rejected by validator nodes.

The getRecentPrioritizationFees API takes accounts’ pubkeys as parameters, andthen returns the highest of the minimum prioritization fees for these accounts.When no account is specified, the API will return the lowest fee to land toblock, which is usually zero (unless the block is full).

Exchanges and applications should query the RPC endpoint with the accounts thata transaction is going to write-lock. The RPC endpoint will return themax(account_1_min_fee, account_2_min_fee, ... account_n_min_fee), which shouldbe the base point for the user to set the prioritization fee for thattransaction.

There are different approaches to setting Prioritization Fees and somethird-party APIsare available to determine the best fee to apply. Given the dynamic nature ofthe network, there will not be a “perfect” way to go about pricing yourPrioritization fees and careful analysis should be applied before choosing apath forward.

How to Implement Prioritization Fees #

Adding priority fees on a transaction consists of prepending two Compute Budgetinstructions on a given transaction:

  • one to set the compute unit price, and
  • another to set the compute unit limit

Info

Here, you can also find a more detailed developerguide on how to use priority feeswhich includes more information about implementing priority fees.

Create a setComputeUnitPrice instruction to add a Prioritization Fee above theBase Transaction Fee (5,000 Lamports).

// import { ComputeBudgetProgram } from "@solana/web3.js"ComputeBudgetProgram.setComputeUnitPrice({ microLamports: number });

The value provided in micro-lamports will be multiplied by the Compute Unit (CU)budget to determine the Prioritization Fee in Lamports. For example, if your CUbudget is 1M CU, and you add 1 microLamport/CU, the Prioritization Fee will be1 lamport (1M * 0. 000001). The total fee will then be 5001 lamports.

To set a new compute unit budget for the transaction, create asetComputeUnitLimit instruction

// import { ComputeBudgetProgram } from "@solana/web3.js"ComputeBudgetProgram.setComputeUnitLimit({ units: number });

The units value provided will replace the Solana runtime's default computebudget value.

Set the lowest CU required for the transaction

Transactions should request the minimum amount of compute units (CU) requiredfor execution to maximize throughput and minimize overall fees.

You can get the CU consumed by a transaction by sending the transaction on adifferent Solana cluster, like devnet. For example, asimple token transfertakes 300 CU.

// import { ... } from "@solana/web3.js" const modifyComputeUnits = ComputeBudgetProgram.setComputeUnitLimit({ // note: set this to be the lowest actual CU consumed by the transaction units: 300,}); const addPriorityFee = ComputeBudgetProgram.setComputeUnitPrice({ microLamports: 1,}); const transaction = new Transaction() .add(modifyComputeUnits) .add(addPriorityFee) .add( SystemProgram.transfer({ fromPubkey: payer.publicKey, toPubkey: toAccount, lamports: 10000000, }), );

Prioritization Fees And Durable Nonces #

If your setup uses Durable Nonce Transactions, it is important to properlyimplement Prioritization Fees in combination with Durable Transaction Nonces toensure successful transactions. Failure to do so will cause intended DurableNonce transactions not to be detected as such.

If you ARE using Durable Transaction Nonces, the AdvanceNonceAccountinstruction MUST be specified FIRST in the instructions list, even when thecompute budget instructions are used to specify priority fees.

You can find a specific code exampleusing durable nonces and priority fees togetherin this developer guide.

Supporting the SPL Token Standard #

SPL Token is the standard for wrapped/synthetictoken creation and exchange on the Solana blockchain.

The SPL Token workflow is similar to that of native SOL tokens, but there are afew differences which will be discussed in this section.

Token Mints #

Each type of SPL Token is declared by creating a mint account. This accountstores metadata describing token features like the supply, number of decimals,and various authorities with control over the mint. Each SPL Token accountreferences its associated mint and may only interact with SPL Tokens of thattype.

Installing the spl-token CLI Tool #

SPL Token accounts are queried and modified using the spl-token command lineutility. The examples provided in this section depend upon having it installedon the local system.

spl-token is distributed from Rustcrates.io via the Rust cargo commandline utility. The latest version of cargo can be installed using a handyone-liner for your platform at rustup.rs. Once cargo isinstalled, spl-token can be obtained with the following command:

cargo install spl-token-cli

You can then check the installed version to verify

spl-token --version

Which should result in something like

spl-token-cli 2.0.1

Account Creation #

SPL Token accounts carry additional requirements that native System Programaccounts do not:

  1. SPL Token accounts must be created before an amount of tokens can bedeposited. Token accounts can be created explicitly with thespl-token create-account command, or implicitly by thespl-token transfer --fund-recipient ... command.
  2. SPL Token accounts must remain rent-exemptfor the duration of their existence and therefore require a small amount ofnative SOL tokens be deposited at account creation. For SPL Token accounts,this amount is 0.00203928 SOL (2,039,280 lamports).

Command Line #

To create an SPL Token account with the following properties:

  1. Associated with the given mint
  2. Owned by the funding account's keypair
spl-token create-account <TOKEN_MINT_ADDRESS>

Example #

spl-token create-account AkUFCWTXb3w9nY2n6SFJvBV6VwvFUCe4KBMCcgLsa2ir

Giving an output similar to:

Creating account 6VzWGL51jLebvnDifvcuEDec17sK6Wupi4gYhm5RzfkVSignature: 4JsqZEPra2eDTHtHpB4FMWSfk3UgcCVmkKkP7zESZeMrKmFFkDkNd91pKP3vPVVZZPiu5XxyJwS73Vi5WsZL88D7

Or to create an SPL Token account with a specific keypair:

solana-keygen new -o token-account.json spl-token create-account AkUFCWTXb3w9nY2n6SFJvBV6VwvFUCe4KBMCcgLsa2ir token-account.json

Giving an output similar to:

Creating account 6VzWGL51jLebvnDifvcuEDec17sK6Wupi4gYhm5RzfkVSignature: 4JsqZEPra2eDTHtHpB4FMWSfk3UgcCVmkKkP7zESZeMrKmFFkDkNd91pKP3vPVVZZPiu5XxyJwS73Vi5WsZL88D7

Checking an Account's Balance #

Command Line #

spl-token balance <TOKEN_ACCOUNT_ADDRESS>

Example #

solana balance 6VzWGL51jLebvnDifvcuEDec17sK6Wupi4gYhm5RzfkV

Giving an output similar to:

0

Token Transfers #

The source account for a transfer is the actual token account that contains theamount.

The recipient address however can be a normal wallet account. If an associatedtoken account for the given mint does not yet exist for that wallet, thetransfer will create it provided that the --fund-recipient argument asprovided.

Command Line #

spl-token transfer <SENDER_ACCOUNT_ADDRESS> <AMOUNT> <RECIPIENT_WALLET_ADDRESS> --fund-recipient

Example #

spl-token transfer 6B199xxzw3PkAm25hGJpjj3Wj3WNYNHzDAnt1tEqg5BN 1

Giving an output similar to:

6VzWGL51jLebvnDifvcuEDec17sK6Wupi4gYhm5RzfkVTransfer 1 tokens Sender: 6B199xxzw3PkAm25hGJpjj3Wj3WNYNHzDAnt1tEqg5BN Recipient: 6VzWGL51jLebvnDifvcuEDec17sK6Wupi4gYhm5RzfkVSignature: 3R6tsog17QM8KfzbcbdP4aoMfwgo6hBggJDVy7dZPVmH2xbCWjEj31JKD53NzMrf25ChFjY7Uv2dfCDq4mGFFyAj

Depositing #

Since each (wallet, mint) pair requires a separate account onchain. It isrecommended that the addresses for these accounts be derived from SOL depositwallets using theAssociated Token Account(ATA) scheme and that only deposits from ATA addresses be accepted.

Monitoring for deposit transactions should follow theblock polling method described above. Each new block shouldbe scanned for successful transactions referencing user token-account derivedaddresses. The preTokenBalance and postTokenBalance fields from thetransaction's metadata must then be used to determine the effective balancechange. These fields will identify the token mint and account owner (main walletaddress) of the affected account.

Note that if a receiving account is created during the transaction, it will haveno preTokenBalance entry as there is no existing account state. In this case,the initial balance can be assumed to be zero.

Withdrawing #

The withdrawal address a user provides must be that of their SOL wallet.

Before executing a withdrawal transfer, the exchange shouldcheck the address asdescribed above.Additionally this address must be owned by the System Program and have noaccount data. If the address has no SOL balance, user confirmation should beobtained before proceeding with the withdrawal. All other withdrawal addressesmust be rejected.

From the withdrawal address, theAssociated Token Account(ATA) for the correct mint is derived and the transfer issued to that accountvia aTransferCheckedinstruction. Note that it is possible that the ATA address does not yet exist,at which point the exchange should fund the account on behalf of the user. ForSPL Token accounts, funding the withdrawal account will require 0.00203928 SOL(2,039,280 lamports).

Template spl-token transfer command for a withdrawal:

spl-token transfer --fund-recipient <exchange token account> <withdrawal amount> <withdrawal address>

Other Considerations #

Freeze Authority #

For regulatory compliance reasons, an SPL Token issuing entity may optionallychoose to hold "Freeze Authority" over all accounts created in association withits mint. This allows them tofreeze the assets in a givenaccount at will, rendering the account unusable until thawed. If this feature isin use, the freeze authority's pubkey will be registered in the SPL Token's mintaccount.

Basic Support for the SPL Token-2022 (Token-Extensions) Standard #

SPL Token-2022 is the newest standard forwrapped/synthetic token creation and exchange on the Solana blockchain.

Also known as "Token Extensions", the standard contains many new features thattoken creators and account holders may optionally enable. These features includeconfidential transfers, fees on transfer, closing mints, metadata, permanentdelegates, immutable ownership, and much more. Please see theextension guide for moreinformation.

If your exchange supports SPL Token, there isn't a lot more work required tosupport SPL Token-2022:

  • the CLI tool works seamlessly with both programs starting with version 3.0.0.
  • preTokenBalances and postTokenBalances include SPL Token-2022 balances
  • RPC indexes SPL Token-2022 accounts, but they must be queried separately withprogram id TokenzQdBNbLqP5VEhdkAS6EPFLC1PHnBqCXEpPxuEb

The Associated Token Account works the same way, and properly calculates therequired deposit amount of SOL for the new account.

Because of extensions, however, accounts may be larger than 165 bytes, so theymay require more than 0.00203928 SOL to fund.

For example, the Associated Token Account program always includes the "immutableowner" extension, so accounts take a minimum of 170 bytes, which requires0.00207408 SOL.

Extension-Specific Considerations #

The previous section outlines the most basic support for SPL Token-2022. Sincethe extensions modify the behavior of tokens, exchanges may need to change howthey handle tokens.

It is possible to see all extensions on a mint or token account:

spl-token display <account address>

Transfer Fee #

A token may be configured with a transfer fee, where a portion of transferredtokens are withheld at the destination for future collection.

If your exchange transfers these tokens, beware that they may not all arrive atthe destination due to the withheld amount.

It is possible to specify the expected fee during a transfer to avoid anysurprises:

spl-token transfer --expected-fee <fee amount> --fund-recipient <exchange token account> <withdrawal amount> <withdrawal address>

Mint Close Authority #

With this extension, a token creator may close a mint, provided the supply oftokens is zero.

When a mint is closed, there may still be empty token accounts in existence, andthey will no longer be associated to a valid mint.

It is safe to simply close these token accounts:

spl-token close --address <account address>

Confidential Transfer #

Mints may be configured for confidential transfers, so that token amounts areencrypted, but the account owners are still public.

Exchanges may configure token accounts to send and receive confidentialtransfers, to hide user amounts. It is not required to enable confidentialtransfers on token accounts, so exchanges can force users to send tokensnon-confidentially.

To enable confidential transfers, the account must be configured for it:

spl-token configure-confidential-transfer-account --address <account address>

And to transfer:

spl-token transfer --confidential <exchange token account> <withdrawal amount> <withdrawal address>

During a confidential transfer, the preTokenBalance and postTokenBalancefields will show no change. In order to sweep deposit accounts, you must decryptthe new balance to withdraw the tokens:

spl-token apply-pending-balance --address <account address>spl-token withdraw-confidential-tokens --address <account address> <amount or ALL>

Default Account State #

Mints may be configured with a default account state, such that all new tokenaccounts are frozen by default. These token creators may require users to gothrough a separate process to thaw the account.

Non-Transferable #

Some tokens are non-transferable, but they may still be burned and the accountcan be closed.

Permanent Delegate #

Token creators may designate a permanent delegate for all of their tokens. Thepermanent delegate may transfer or burn tokens from any account, potentiallystealing funds.

This is a legal requirement for stablecoins in certain jurisdictions, or couldbe used for token repossession schemes.

Beware that these tokens may be transferred without your exchange's knowledge.

Transfer Hook #

Tokens may be configured with an additional program that must be called duringtransfers, in order to validate the transfer or perform any other logic.

Since the Solana runtime requires all accounts to be explicitly passed to aprogram, and transfer hooks require additional accounts, the exchange needs tocreate transfer instructions differently for these tokens.

The CLI and instruction creators such ascreateTransferCheckedWithTransferHookInstruction add the extra accountsautomatically, but the additional accounts may also be specified explicitly:

spl-token transfer --transfer-hook-account <pubkey:role> --transfer-hook-account <pubkey:role> ...

Required Memo on Transfer #

Users may configure their token accounts to require a memo on transfer.

Exchanges may need to prepend a memo instruction before transferring tokens backto users, or they may require users to prepend a memo instruction before sendingto the exchange:

spl-token transfer --with-memo <memo text> <exchange token account> <withdrawal amount> <withdrawal address>

Testing the Integration #

Be sure to test your complete workflow on Solana devnet and testnetclusters before moving to production on mainnet-beta.Devnet is the most open and flexible, and ideal for initial development, whiletestnet offers more realistic cluster configuration. Both devnet and testnetsupport a faucet, run solana airdrop 1 to obtain some devnet or testnet SOLfor development and testing.

Add Solana to Your Exchange | Solana (2024)
Top Articles
Mystical Flowers
What is Tokenization?
Po Box 7250 Sioux Falls Sd
Kreme Delite Menu
Noaa Charleston Wv
Lamb Funeral Home Obituaries Columbus Ga
Southside Grill Schuylkill Haven Pa
Meer klaarheid bij toewijzing rechter
Robinhood Turbotax Discount 2023
Aiken County government, school officials promote penny tax in North Augusta
Tlc Africa Deaths 2021
Corporate Homepage | Publix Super Markets
Camstreams Download
Craigslist Estate Sales Tucson
Detroit Lions 50 50
Sport Clip Hours
Nier Automata Chapter Select Unlock
Valentina Gonzalez Leak
Classic Lotto Payout Calculator
Guidewheel lands $9M Series A-1 for SaaS that boosts manufacturing and trims carbon emissions | TechCrunch
Moonshiner Tyler Wood Net Worth
SXSW Film & TV Alumni Releases – July & August 2024
Lazarillo De Tormes Summary and Study Guide | SuperSummary
Kp Nurse Scholars
Where to Find Scavs in Customs in Escape from Tarkov
Sprinkler Lv2
Xsensual Portland
How to Grow and Care for Four O'Clock Plants
Wkow Weather Radar
Craigslistodessa
Troy Gamefarm Prices
Prot Pally Wrath Pre Patch
Divina Rapsing
3569 Vineyard Ave NE, Grand Rapids, MI 49525 - MLS 24048144 - Coldwell Banker
Die 8 Rollen einer Führungskraft
Divide Fusion Stretch Hoodie Daunenjacke für Herren | oliv
Jurassic World Exhibition Discount Code
10-Day Weather Forecast for Santa Cruz, CA - The Weather Channel | weather.com
Guinness World Record For Longest Imessage
Free Tiktok Likes Compara Smm
B.k. Miller Chitterlings
How Much Is Mink V3
Henry County Illuminate
Cherry Spa Madison
Has any non-Muslim here who read the Quran and unironically ENJOYED it?
Mugshots Journal Star
فیلم گارد ساحلی زیرنویس فارسی بدون سانسور تاینی موویز
BCLJ July 19 2019 HTML Shawn Day Andrea Day Butler Pa Divorce
UNC Charlotte Admission Requirements
Www Ventusky
2487872771
Latest Posts
Article information

Author: Jeremiah Abshire

Last Updated:

Views: 6017

Rating: 4.3 / 5 (74 voted)

Reviews: 81% of readers found this page helpful

Author information

Name: Jeremiah Abshire

Birthday: 1993-09-14

Address: Apt. 425 92748 Jannie Centers, Port Nikitaville, VT 82110

Phone: +8096210939894

Job: Lead Healthcare Manager

Hobby: Watching movies, Watching movies, Knapping, LARPing, Coffee roasting, Lacemaking, Gaming

Introduction: My name is Jeremiah Abshire, I am a outstanding, kind, clever, hilarious, curious, hilarious, outstanding person who loves writing and wants to share my knowledge and understanding with you.