diff --git a/docs/src/integrations/exchange.md b/docs/src/integrations/exchange.md index c308d001fb..5dd7150838 100644 --- a/docs/src/integrations/exchange.md +++ b/docs/src/integrations/exchange.md @@ -159,8 +159,14 @@ the minimum rent-exempt balance for your deposit accounts, query the [`getMinimumBalanceForRentExemption` endpoint](developing/clients/jsonrpc-api.md#getminimumbalanceforrentexemption): ```bash -curl -X POST -H "Content-Type: application/json" -d '{"jsonrpc": "2.0","id":1,"method":"getMinimumBalanceForRentExemption","params":[0]}' localhost:8899 +curl localhost:8899 -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} ``` @@ -175,6 +181,37 @@ greater security. If so, you will need to move SOL to hot accounts using our When a user wants to deposit SOL into your exchange, instruct them to send a transfer 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 will no longer +work properly because fetching a versioned transaction or a block containing +versioned transactions will return an error. + +- `{"maxSupportedTransactionVersion": 0}` + + The `maxSupportedTransactionVersion` parameter must be added to `getBlock` and + `getTransaction` requests to avoid disruption to deposit detection. The latest + transaction version is `0` and should be specified as the max supported + transaction version value. + +It's important to understand that versioned transactions allow users to create +transactions that use another set of account keys loaded from on-chain address +lookup 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. + This makes 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 to + account keys that are **NOT** in the `"accountKeys"` list and need to be + resolved using `"loadedAddresses"` entries in the transaction metadata. + ### Poll for Blocks To track all the deposit accounts for your exchange, poll for each confirmed @@ -185,8 +222,14 @@ Solana API node. passing the last block you have already processed as the start-slot parameter: ```bash -curl -X POST -H "Content-Type: application/json" -d '{"jsonrpc": "2.0","id":1,"method":"getBlocks","params":[5]}' localhost:8899 +curl localhost:8899 -X POST -H "Content-Type: application/json" -d '{ + "jsonrpc": "2.0", + "id": 1, + "method": "getBlocks", + "params": [5] +}' +# Result {"jsonrpc":"2.0","result":[5,6,8,9,11],"id":1} ``` @@ -194,63 +237,92 @@ Not every slot produces a block, so there may be gaps in the sequence of integer - For each block, request its contents with a [`getBlock` request](developing/clients/jsonrpc-api.md#getblock): -```bash -curl -X POST -H "Content-Type: application/json" -d '{"jsonrpc": "2.0","id":1,"method":"getBlock","params":[5, "json"]}' localhost:8899 +### Block Fetching Tips +- `{"rewards": false}` + +By default, fetched blocks will return information about validator fees on each +block and staking rewards on epoch boundaries. If you don't need this +information, disable it with the "rewards" parameter. + +- `{"transactionDetails": "accounts"}` + +By default, fetched blocks will return a lot of transaction info and metadata +that isn't necessary for tracking account balances. Set the "transactionDetails" +parameter to speed up block fetching. + +```bash +curl localhost:8899 -X POST -H 'Content-Type: application/json' -d '{ + "jsonrpc": "2.0", + "id": 1, + "method": "getBlock", + "params": [ + 148696677, + { + "encoding": "jsonParsed", + "maxSupportedTransactionVersion": 0, + "transactionDetails": "accounts", + "rewards": false + } + ] +}' + +# Result { "jsonrpc": "2.0", "result": { - "blockhash": "2WcrsKSVANoe6xQHKtCcqNdUpCQPQ3vb6QTgi1dcE2oL", - "parentSlot": 4, - "previousBlockhash": "7ZDoGW83nXgP14vnn9XhGSaGjbuLdLWkQAoUQ7pg6qDZ", - "rewards": [], + "blockHeight": 134239354, + "blockTime": 1662064341, + "blockhash": "AuPLyvFX2yA1aVFUqvFfyiB2Sxwu2McL8ALhwbU6w7er", + "parentSlot": 148696675, + "previousBlockhash": "AKu155zCvrgrPvcVBFyboAfY2GF33S3ZDkj2Pa8x19XM", "transactions": [ { "meta": { "err": null, "fee": 5000, "postBalances": [ - 2033973061360, - 218099990000, - 42000000003 + 7161091286, + 2769675090, + 1 ], + "postTokenBalances": [], "preBalances": [ - 2044973066360, - 207099990000, - 42000000003 + 8130576328, + 1800195048, + 1 ], + "preTokenBalances": [], "status": { "Ok": null } }, "transaction": { - "message": { - "accountKeys": [ - "Bbqg1M4YVVfbhEzwA9SpC9FhsaG83YMTYoR4a8oTDLX", - "47Sbuv6jL7CViK9F2NMW51aQGhfdpUu7WNvKyH645Rfi", - "11111111111111111111111111111111" - ], - "header": { - "numReadonlySignedAccounts": 0, - "numReadonlyUnsignedAccounts": 1, - "numRequiredSignatures": 1 + "accountKeys": [ + { + "pubkey": "ogDsdvMKRRRMmsrT2hTPdkQBu1qY2z1jBDzgpi8HZri", + "signer": true, + "source": "transaction", + "writable": true }, - "instructions": [ - { - "accounts": [ - 0, - 1 - ], - "data": "3Bxs3zyH82bhpB8j", - "programIdIndex": 2 - } - ], - "recentBlockhash": "7GytRgrWXncJWKhzovVoP9kjfLwoiuDb3cWjpXGnmxWh" - }, + { + "pubkey": "3M2b3tLji7rvscqrLAHMukYxDK2nB96Q9hwfV6QkdzBN", + "signer": false, + "source": "transaction", + "writable": true + }, + { + "pubkey": "11111111111111111111111111111111", + "signer": false, + "source": "transaction", + "writable": false + } + ], "signatures": [ - "dhjhJp2V2ybQGVfELWM1aZy98guVVsxRCB5KhNiXFjCBMK5KEyzV8smhkVvs3xwkAug31KnpzJpiNPtcD5bG1t6" + "36Q383JMiqiobuPV9qBqy41xjMsVnQBm9rdZSdpbrLTGhSQDTGZJnocM4TQTVfUGfV2vEX9ZB3sex6wUBUWzjEvs" ] - } + }, + "version": "legacy" } ] }, @@ -262,9 +334,9 @@ The `preBalances` and `postBalances` fields allow you to track the balance changes in every account without having to parse the entire transaction. They list the starting and ending balances of each account in [lamports](../terminology.md#lamport), indexed to the `accountKeys` list. For -example, if the deposit address if interest is -`47Sbuv6jL7CViK9F2NMW51aQGhfdpUu7WNvKyH645Rfi`, this transaction represents a -transfer of 218099990000 - 207099990000 = 11000000000 lamports = 11 SOL +example, if the deposit address of interest is +`3M2b3tLji7rvscqrLAHMukYxDK2nB96Q9hwfV6QkdzBN`, this transaction represents a +transfer of 2769675090 - 1800195048 = 969,480,042 lamports = 0.969485042 SOL If you need more information about the transaction type or other specifics, you can request the block from RPC in binary format, and parse it using either our @@ -282,28 +354,45 @@ time. request to the api node: ```bash -curl -X POST -H "Content-Type: application/json" -d '{"jsonrpc": "2.0","id":1,"method":"getSignaturesForAddress","params":["6H94zdiaYfRfPfKjYLjyr2VFBg6JHXygy84r3qhc3NsC", {"limit": 3}]}' localhost:8899 +curl localhost:8899 -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": "35YGay1Lwjwgxe9zaH6APSHbt9gYQUCtBWTNL3aVwVGn9xTFw2fgds7qK5AL29mP63A9j3rh8KpN1TgSR62XCaby", - "slot": 114 + "signature": "3EDRvnD5TbbMS2mCusop6oyHLD8CgnjncaYQd5RXpgnjYUXRCYwiNPmXb6ZG5KdTK4zAaygEhfdLoP7TDzwKBVQp", + "slot": 148697216 }, { + "blockTime": 1662064434, + "confirmationStatus": "finalized", "err": null, "memo": null, - "signature": "4bJdGN8Tt2kLWZ3Fa1dpwPSEkXWWTSszPSf1rRVsCwNjxbbUdwTeiWtmi8soA26YmwnKD4aAxNp8ci1Gjpdv4gsr", - "slot": 112 + "signature": "4rPQ5wthgSP1kLdLqcRgQnkYkPAZqjv5vm59LijrQDSKuL2HLmZHoHjdSLDXXWFwWdaKXUuryRBGwEvSxn3TQckY", + "slot": 148696843 }, { + "blockTime": 1662064341, + "confirmationStatus": "finalized", "err": null, "memo": null, - "signature": "dhjhJp2V2ybQGVfELWM1aZy98guVVsxRCB5KhNiXFjCBMK5KEyzV8smhkVvs3xwkAug31KnpzJpiNPtcD5bG1t6", - "slot": 108 + "signature": "36Q383JMiqiobuPV9qBqy41xjMsVnQBm9rdZSdpbrLTGhSQDTGZJnocM4TQTVfUGfV2vEX9ZB3sex6wUBUWzjEvs", + "slot": 148696677 } ], "id": 1 @@ -314,58 +403,102 @@ curl -X POST -H "Content-Type: application/json" -d '{"jsonrpc": "2.0","id":1,"m [`getTransaction`](developing/clients/jsonrpc-api.md#gettransaction) request: ```bash -curl -X POST -H "Content-Type: application/json" -d '{"jsonrpc": "2.0","id":1,"method":"getTransaction","params":["dhjhJp2V2ybQGVfELWM1aZy98guVVsxRCB5KhNiXFjCBMK5KEyzV8smhkVvs3xwkAug31KnpzJpiNPtcD5bG1t6", "json"]}' localhost:8899 +curl localhost:8899 -X POST -H 'Content-Type: application/json' -d '{ + "jsonrpc":"2.0", + "id":1, + "method":"getTransaction", + "params":[ + "4Cswku8E9sm8TVZ4kP4iHbwCQygMDx78SXSURBkJuJAaXCbL9eYM8RPS2BDooLd5ftML4JjQrohe4deJrFkVzPBa", + { + "encoding":"jsonParsed", + "maxSupportedTransactionVersion":0 + } + ] +}' -// Result +# Result { "jsonrpc": "2.0", "result": { - "slot": 5, - "transaction": { - "message": { - "accountKeys": [ - "Bbqg1M4YVVfbhEzwA9SpC9FhsaG83YMTYoR4a8oTDLX", - "47Sbuv6jL7CViK9F2NMW51aQGhfdpUu7WNvKyH645Rfi", - "11111111111111111111111111111111" - ], - "header": { - "numReadonlySignedAccounts": 0, - "numReadonlyUnsignedAccounts": 1, - "numRequiredSignatures": 1 - }, - "instructions": [ - { - "accounts": [ - 0, - 1 - ], - "data": "3Bxs3zyH82bhpB8j", - "programIdIndex": 2 - } - ], - "recentBlockhash": "7GytRgrWXncJWKhzovVoP9kjfLwoiuDb3cWjpXGnmxWh" - }, - "signatures": [ - "dhjhJp2V2ybQGVfELWM1aZy98guVVsxRCB5KhNiXFjCBMK5KEyzV8smhkVvs3xwkAug31KnpzJpiNPtcD5bG1t6" - ] - }, + "blockTime": 1660763773, "meta": { "err": null, "fee": 5000, + "innerInstructions": [], + "logMessages": [ + "Program 11111111111111111111111111111111 invoke [1]", + "Program 11111111111111111111111111111111 success" + ], "postBalances": [ - 2033973061360, - 218099990000, - 42000000003 + 2078778739, + 1, + 26396753106 ], + "postTokenBalances": [], "preBalances": [ - 2044973066360, - 207099990000, - 42000000003 + 2078783740, + 1, + 26396753105 ], + "preTokenBalances": [], + "rewards": [], "status": { "Ok": null } - } + }, + "slot": 155713260, + "transaction": { + "message": { + "accountKeys": [ + { + "pubkey": "9aE476sH92Vz7DMPyq5WLPkrKWivxeuTKEFKd2sZZcde", + "signer": true, + "source": "transaction", + "writable": true + }, + { + "pubkey": "11111111111111111111111111111111", + "signer": false, + "source": "transaction", + "writable": false + }, + { + "pubkey": "2xNweLHLqrbx4zo1waDvgWJHgsUpPj8Y8icbAFeR4a8i", + "signer": false, + "source": "lookupTable", + "writable": true + } + ], + "addressTableLookups": [ + { + "accountKey": "3LZbwptsCkv5R5uu1GNZKiX9SoC6egNG8NXg9zH5ZVM9", + "readonlyIndexes": [], + "writableIndexes": [ + 1 + ] + } + ], + "instructions": [ + { + "parsed": { + "info": { + "destination": "2xNweLHLqrbx4zo1waDvgWJHgsUpPj8Y8icbAFeR4a8i", + "lamports": 1, + "source": "9aE476sH92Vz7DMPyq5WLPkrKWivxeuTKEFKd2sZZcde" + }, + "type": "transfer" + }, + "program": "system", + "programId": "11111111111111111111111111111111" + } + ], + "recentBlockhash": "9nLh3gmVhyjrh68UeV1rafyo8BFNyZtHSRUUjZYikveh" + }, + "signatures": [ + "4Cswku8E9sm8TVZ4kP4iHbwCQygMDx78SXSURBkJuJAaXCbL9eYM8RPS2BDooLd5ftML4JjQrohe4deJrFkVzPBa" + ] + }, + "version": 0 }, "id": 1 } @@ -435,8 +568,19 @@ The `confirmations` field reports how many transaction was processed. If `confirmations: null`, it is [finalized](../terminology.md#finality). ```bash -curl -X POST -H "Content-Type: application/json" -d '{"jsonrpc":"2.0", "id":1, "method":"getSignatureStatuses", "params":[["5VERv8NMvzbJMEkV8xnrLkEaWRtSz9CosKDYjCJjBRnbJLgp8uirBgmQpjKhoR4tjF3ZpRzrFmBV6UjKdiSZkQUW", "5j7s6NiJS3JAkvgkoc18WVAsiSaci2pxB2A6ueCJP4tprA2TFg9wSyTLeYouxPBJEMzJinENTkpA52YStRW5Dia7"]]}' http://localhost:8899 +curl localhost:8899 -X POST -H "Content-Type: application/json" -d '{ + "jsonrpc":"2.0", + "id":1, + "method":"getSignatureStatuses", + "params":[ + [ + "5VERv8NMvzbJMEkV8xnrLkEaWRtSz9CosKDYjCJjBRnbJLgp8uirBgmQpjKhoR4tjF3ZpRzrFmBV6UjKdiSZkQUW", + "5j7s6NiJS3JAkvgkoc18WVAsiSaci2pxB2A6ueCJP4tprA2TFg9wSyTLeYouxPBJEMzJinENTkpA52YStRW5Dia7" + ] + ] +}' +# Result { "jsonrpc": "2.0", "result": { @@ -480,7 +624,7 @@ As withdrawals are irreversible, it may be a good practice to validate a user-supplied account address before authorizing a withdrawal in order to prevent accidental loss of user funds. -#### Basic verfication +#### Basic verification Solana addresses a 32-byte array, encoded with the bitcoin base58 alphabet. This results in an ASCII text string matching the following regular expression: @@ -575,8 +719,14 @@ holding no data), currently: 0.000890880 SOL Similarly, every deposit account must contain at least this balance. ```bash -curl -X POST -H "Content-Type: application/json" -d '{"jsonrpc": "2.0","id":1,"method":"getMinimumBalanceForRentExemption","params":[0]}' localhost:8899 +curl localhost:8899 -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} ``` @@ -722,7 +872,7 @@ identify the token mint and account owner (main wallet address) of the affected account. Note that if a receiving account is created during the transaction, it will have no -`preTokenBalance` entry as there is no existing account state. In this +`preTokenBalance` entry as there is no existing account state. In this case, the initial balance can be assumed to be zero. ### Withdrawing @@ -734,7 +884,7 @@ the exchange should check the address as [described above](#validating-user-supplied-account-addresses-for-withdrawals). Additionally this address must be owned by the System Program and have no account data. If the address has no SOL balance, user confirmation should be -obtained before proceeding with the withdrawal. All other withdrawal addresses +obtained before proceeding with the withdrawal. All other withdrawal addresses must be rejected. From the withdrawal address, the [Associated Token Account](https://spl.solana.com/associated-token-account)