From 4c3b1061c18091d7e4553e69ca45ffe1ac8c31d5 Mon Sep 17 00:00:00 2001 From: Tyera Eulberg Date: Wed, 27 May 2020 10:47:03 -0600 Subject: [PATCH] Add exchange integration docs (#10054) * Add exchange integration doc * Round 1 review comments * Add rent stub doc * Pretty-print some things * Rework blockhash info, move offline signing * Add something to test section * Update blockhash/last-valid-slot info --- src/SUMMARY.md | 3 + src/apps/rent.md | 1 + src/integrations/README.md | 0 src/integrations/exchange.md | 389 +++++++++++++++++++++++++++++++++++ 4 files changed, 393 insertions(+) create mode 100644 src/apps/rent.md create mode 100644 src/integrations/README.md create mode 100644 src/integrations/exchange.md diff --git a/src/SUMMARY.md b/src/SUMMARY.md index ff42336f1c05aa..491baa93affba2 100644 --- a/src/SUMMARY.md +++ b/src/SUMMARY.md @@ -24,12 +24,15 @@ * [Command-line Reference](cli/usage.md) * [Solana Clusters](clusters.md) * [Develop Applications](apps/README.md) + * [Accounts and Rent](apps/rent.md) * [Example: Web Wallet](apps/webwallet.md) * [Example: Tic-Tac-Toe](apps/tictactoe.md) * [Drones](apps/drones.md) * [Anatomy of a Transaction](transaction.md) * [JSON RPC API](apps/jsonrpc-api.md) * [JavaScript API](apps/javascript-api.md) +* [Integration Guides](integrations/README.md) + * [Exchange](integrations/exchange.md) * [Run a Validator](running-validator/README.md) * [Validator Requirements](running-validator/validator-reqs.md) * [Start a Validator](running-validator/validator-start.md) diff --git a/src/apps/rent.md b/src/apps/rent.md new file mode 100644 index 00000000000000..aea797350fd474 --- /dev/null +++ b/src/apps/rent.md @@ -0,0 +1 @@ +# Accounts and Rent diff --git a/src/integrations/README.md b/src/integrations/README.md new file mode 100644 index 00000000000000..e69de29bb2d1d6 diff --git a/src/integrations/exchange.md b/src/integrations/exchange.md new file mode 100644 index 00000000000000..aa399860478869 --- /dev/null +++ b/src/integrations/exchange.md @@ -0,0 +1,389 @@ +# Add Solana to Your Exchange + +This guide describes how to add Solana's native token SOL to your cryptocurrency +exchange. + +## Node Setup + +We highly recommend setting up at least two of your own Solana api nodes to +give you a trusted entrypoint to the network, allow you full control over how +much data is retained, and ensure you do not miss any data if one node fails. + +To run an api node: +1. [Install the Solana command-line tool suite](../cli/install-solana-cli-tools.md) +2. Boot the node with at least the following parameters: +```bash +solana-validator \ + --ledger \ + --entrypoint \ + --expected-genesis-hash \ + --expected-shred-version \ + --rpc-port 8899 \ + --no-voting \ + --enable-rpc-transaction-history \ + --limit-ledger-size \ + --trusted-validator \ + --no-untrusted-rpc +``` + + Customize `--ledger` to your desired ledger storage location, and `--rpc-port` to the port you want to expose. + + The `--entrypoint`, `--expected-genesis-hash`, and `--expected-shred-version` parameters are all specific to the cluster you are joining. The shred version will change on any hard forks in the cluster, so including `--expected-shred-version` ensures you are receiving current data from the cluster you expect. + [Current parameters for Mainnet Beta](../clusters.md#example-solana-validator-command-line-2) + + The `--limit-ledger-size` parameter allows you to specify how many ledger [shreds](../terminology.md#shred) your node retains on disk. If you do not include this parameter, the ledger will keep the entire ledger until it runs out of disk space. A larger value like `--limit-ledger-size 250000000000` is good for a couple days + + Specifying one or more `--trusted-validator` parameters can protect you from booting from a malicious snapshot. [More on the value of booting with trusted validators](../running-validator/validator-start.md#trusted-validators) + + Optional parameters to consider: + - `--private-rpc` prevents your RPC port from being published for use by other nodes + - `--rpc-bind-address` allows you to specify a different IP address to bind the RPC port + +### Automatic Restarts + +We recommend configuring each of your nodes to restart automatically on exit, to +ensure you miss as little data as possible. Running the solana software as a +systemd service is one great option. + +### Ledger Continuity + +By default, each of your nodes will boot from a snapshot provided by one of your +trusted validators. This snapshot reflects the current state of the chain, but +does not contain the complete historical ledger. If one of your node exits and +boots from a new snapshot, there may be a gap in the ledger on that node. In +order to prevent this issue, add the `--no-snapshot-fetch` parameter to your +`solana-validator` command to receive historical ledger data instead of a +snapshot. + +If you pass the `--no-snapshot-fetch` parameter on your initial boot, it will +take your node a very long time to catch up. We recommend booting from a +snapshot first, and then using the `--no-snapshot-fetch` parameter for reboots. + +It is important to note that the amount of historical ledger available to your +nodes is limited to what your trusted validators retain. You will need to ensure +your nodes do not experience downtimes longer than this span, if ledger +continuity is crucial for you. + +## Setting up Deposit Accounts + +Solana accounts do not require any on-chain initialization; once they contain +some SOL, they exist. To set up a deposit account for your exchange, simply +generate a Solana keypair using any of our [wallet tools](../wallet-guide/cli.md). + +We recommend using a unique deposit account for each of your users. + +Solana accounts are charged [rent](../apps/rent.md) on creation and once per +epoch, but they can be made rent-exempt if they contain 2-years worth of rent in +SOL. In order to find the minimum rent-exempt balance for your deposit accounts, +query the +[`getMinimumBalanceForRentExemption` endpoint](../apps/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 + +{"jsonrpc":"2.0","result":890880,"id":1} +``` + +### Offline Accounts + +You may wish to keep the keys for one or more collection accounts offline for +greater security. If so, you will need to move SOL to hot accounts using our +[offline methods](../offline-signing/README.md). + +## Listening for Deposits + +When a user wants to deposit SOL into your exchange, instruct them to send a +transfer to the appropriate deposit address. + +### Poll for Blocks + +The easiest way to track all the deposit accounts for your exchange is to poll +for each confirmed block and inspect for addresses of interest, using the +JSON-RPC service of your Solana api node. + +1. To identify which blocks are available, send a [`getConfirmedBlocks` request](../apps/jsonrpc-api.md#getconfirmedblocks), +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":"getConfirmedBlocks","params":[5]}' localhost:8899 + +{"jsonrpc":"2.0","result":[5,6,8,9,11],"id":1} +``` +Not every slot produces a block, so there may be gaps in the sequence of integers. + +2. For each block, request its contents with a [`getConfirmedBlock` request](../apps/jsonrpc-api.md#getconfirmedblock): + +```bash +curl -X POST -H "Content-Type: application/json" -d '{"jsonrpc": "2.0","id":1,"method":"getConfirmedBlock","params":[5, "json"]}' localhost:8899 + +{ + "jsonrpc": "2.0", + "result": { + "blockhash": "2WcrsKSVANoe6xQHKtCcqNdUpCQPQ3vb6QTgi1dcE2oL", + "parentSlot": 4, + "previousBlockhash": "7ZDoGW83nXgP14vnn9XhGSaGjbuLdLWkQAoUQ7pg6qDZ", + "rewards": [], + "transactions": [ + { + "meta": { + "err": null, + "fee": 5000, + "postBalances": [ + 2033973061360, + 218099990000, + 42000000003 + ], + "preBalances": [ + 2044973066360, + 207099990000, + 42000000003 + ], + "status": { + "Ok": null + } + }, + "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" + ] + } + } + ] + }, + "id": 1 +} +``` + +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#lamports), 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 + +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 +[Rust SDK](https://github.com/solana-labs/solana) or +[Javascript SDK](https://github.com/solana-labs/solana-web3.js). + +### Address History + +You can also query the transaction history of a specific address. + +1. Send a [`getConfirmedSignaturesForAddress`](../apps/jsonrpc-api.md#getconfirmedsignaturesforaddress) +request to the api node, specifying a range of recent slots: + +```bash +curl -X POST -H "Content-Type: application/json" -d '{"jsonrpc": "2.0","id":1,"method":"getConfirmedSignaturesForAddress","params":["6H94zdiaYfRfPfKjYLjyr2VFBg6JHXygy84r3qhc3NsC", 0, 10]}' localhost:8899 + +{ + "jsonrpc": "2.0", + "result": [ + "35YGay1Lwjwgxe9zaH6APSHbt9gYQUCtBWTNL3aVwVGn9xTFw2fgds7qK5AL29mP63A9j3rh8KpN1TgSR62XCaby", + "4bJdGN8Tt2kLWZ3Fa1dpwPSEkXWWTSszPSf1rRVsCwNjxbbUdwTeiWtmi8soA26YmwnKD4aAxNp8ci1Gjpdv4gsr", + "dhjhJp2V2ybQGVfELWM1aZy98guVVsxRCB5KhNiXFjCBMK5KEyzV8smhkVvs3xwkAug31KnpzJpiNPtcD5bG1t6" + ], + "id": 1 +} +``` + +2. For each signature returned, get the transaction details by sending a +[`getConfirmedTransaction`](../apps/jsonrpc-api.md#getconfirmedtransaction) request: + +```bash +curl -X POST -H "Content-Type: application/json" -d '{"jsonrpc": "2.0","id":1,"method":"getConfirmedTransaction","params":["dhjhJp2V2ybQGVfELWM1aZy98guVVsxRCB5KhNiXFjCBMK5KEyzV8smhkVvs3xwkAug31KnpzJpiNPtcD5bG1t6", "json"]}' localhost:8899 + +// 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" + ] + }, + "meta": { + "err": null, + "fee": 5000, + "postBalances": [ + 2033973061360, + 218099990000, + 42000000003 + ], + "preBalances": [ + 2044973066360, + 207099990000, + 42000000003 + ], + "status": { + "Ok": null + } + } + }, + "id": 1 +} +``` + +## Sending Withdrawals + +To accommodate a user's request to withdraw SOL, you must generate a Solana +transfer transaction, and send it to the api node to be forwarded to your +cluster. + +### Synchronous + +Sending a synchronous transfer to the Solana cluster allows you to easily ensure +that a transfer is successful and finalized by the cluster. + +Solana's command-line tool offers a simple command, `solana transfer`, to +generate, submit, and confirm transfer transactions. By default, this method +will wait and track progress on stderr until the transaction has been finalized +by the cluster. If the transaction fails, it will report any transaction errors. + +```bash +solana transfer --keypair --url http://localhost:8899 +``` + +The [Solana Javascript SDK](https://github.com/solana-labs/solana-web3.js) +offers a similar approach for the JS ecosystem. Use the `SystemProgram` to build +a transfer transaction, and submit it using the `sendAndConfirmTransaction` +method. + +### Asynchronous + +For greater flexibility, you can submit withdrawal transfers asynchronously. In +these cases, it is your responsibility to verify that the transaction succeeded +and was finalized by the cluster. + +**Note:** Each transaction contains a [recent blockhash](../transaction.md#blockhash-format) +to indicate its liveness. It is **critical** to wait until this blockhash +expires before retrying a withdrawal transfer that does not appear to have been +confirmed or finalized by the cluster. Otherwise, you risk a double spend. See +more on [blockhash expiration](#blockhash-expiration) below. + +First, get a recent blockhash using the [`getFees` endpoint](../apps/jsonrpc-api.md#getfees) +or the CLI command: +```bash +solana fees --url http://localhost:8899 +``` + +In the command-line tool, pass the `--no-wait` argument to send a transfer +asynchronously, and include your recent blockhash with the `--blockhash` argument: + +```bash +solana transfer --no-wait --blockhash --keypair --url http://localhost:8899 +``` + +You can also build, sign, and serialize the transaction manually, and fire it off to +the cluster using the JSON-RPC [`sendTransaction` endpoint](../apps/jsonrpc-api.md#sendtransaction). + +#### Transaction Confirmations & Finality + +Get the status of a batch of transactions using the +[`getSignatureStatuses` JSON-RPC endpoint](../apps/jsonrpc-api.md#getsignaturestatuses). +The `confirmations` field reports how many +[confirmed blocks](../terminology.md#confirmed-block) have elapsed since the +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 + +{ + "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 + +When you request a recent blockhash for your withdrawal transaction using the +[`getFees` endpoint](../apps/jsonrpc-api.md#getfees) or `solana fees`, the +response will include the `lastValidSlot`, the last slot in which the blockhash +will be valid. You can check the cluster slot with a +[`getSlot` query](../apps/jsonrpc-api.md#getslot); once the cluster slot is +greater than `lastValidSlot`, the withdrawal transaction using that blockhash +should never succeed. + +You can also doublecheck whether a particular blockhash is still valid by sending a +[`getFeeCalculatorForBlockhash`](../apps/jsonrpc-api.md#getfeecalculatorforblockhash) +request with the blockhash as a parameter. If the response value is null, the +blockhash is expired, and the withdrawal transaction should never succeed. + +## Testing the Integration + +Be sure to test your complete workflow on Solana devnet and testnet +[clusters](../clusters.md) before moving to production on mainnet-beta. Devnet +is the most open and flexible, and ideal for initial development, while testnet +offers more realistic cluster configuration. Devnet features a token faucet, but +you will need to request some testnet SOL to get going on testnet.