Add SPL Token wallet integration guide

This commit is contained in:
Michael Vines 2020-10-14 20:07:44 -07:00
parent 8ae0f927db
commit 414544042e
1 changed files with 191 additions and 4 deletions

View File

@ -117,8 +117,7 @@ $ spl-token balance 7UX2i7SucgLMQcfZ75s3VXmZZY4YRUyJN9X1RgfMoDUi
Mint 100 tokens into the account:
```sh
$ spl-token mint AQoKYV7tYpTrFZN6P5oUufbQKAUr9mNYGe1TTJC9wajM 100 \
7UX2i7SucgLMQcfZ75s3VXmZZY4YRUyJN9X1RgfMoDUi
$ spl-token mint AQoKYV7tYpTrFZN6P5oUufbQKAUr9mNYGe1TTJC9wajM 100
Minting 100 tokens
Token: AQoKYV7tYpTrFZN6P5oUufbQKAUr9mNYGe1TTJC9wajM
Recipient: 7UX2i7SucgLMQcfZ75s3VXmZZY4YRUyJN9X1RgfMoDUi
@ -162,7 +161,44 @@ Unwrapping GJTxcnA5Sydy8YRhqvHxbQ5QNsPyRKvzguodQEaShJje
Signature: f7opZ86ZHKGvkJBQsJ8Pk81v8F3v1VUfyd4kFs4CABmfTnSZK5BffETznUU3tEWvzibgKJASCf7TUpDmwGi8Rmh
```
### Example: Transferring tokens
### Example: Transferring tokens to another user, with sender-funding
First the receiver uses `spl-token create-account` to create their associated
token account for the Token type. Then the receiver obtains their wallet
address by running `solana address` and provides it to the sender.
The sender then runs:
```
$ spl-token transfer 7UX2i7SucgLMQcfZ75s3VXmZZY4YRUyJN9X1RgfMoDUi 50 vines1vzrYbzLMRdu58ou5XTby4qAqVRLmqo36NKPTg
Transfer 50 tokens
Sender: 7UX2i7SucgLMQcfZ75s3VXmZZY4YRUyJN9X1RgfMoDUi
Recipient: vines1vzrYbzLMRdu58ou5XTby4qAqVRLmqo36NKPTg
Recipient associated token account: F59618aQB8r6asXeMcB9jWuY6NEx1VduT9yFo1GTi1ks
Signature: 5a3qbvoJQnTAxGPHCugibZTbSu7xuTgkxvF4EJupRjRXGgZZrnWFmKzfEzcqKF2ogCaF4QKVbAtuFx7xGwrDUcGd
```
### Example: Transferring tokens to another user, with sender-funding
If the receiver does not yet have an associated token account, the sender may
choose to fund the receiver's account.
The receiver obtains their wallet address by running `solana address` and provides it to the sender.
The sender then runs to fund the receiver's associated token account, at the
sender's expense, and then transfers 50 tokens into it:
```
$ spl-token transfer --fund-recipient 7UX2i7SucgLMQcfZ75s3VXmZZY4YRUyJN9X1RgfMoDUi 50 vines1vzrYbzLMRdu58ou5XTby4qAqVRLmqo36NKPTg
Transfer 50 tokens
Sender: 7UX2i7SucgLMQcfZ75s3VXmZZY4YRUyJN9X1RgfMoDUi
Recipient: vines1vzrYbzLMRdu58ou5XTby4qAqVRLmqo36NKPTg
Recipient associated token account: F59618aQB8r6asXeMcB9jWuY6NEx1VduT9yFo1GTi1ks
Funding recipient: F59618aQB8r6asXeMcB9jWuY6NEx1VduT9yFo1GTi1ks (0.00203928 SOL)
Signature: 5a3qbvoJQnTAxGPHCugibZTbSu7xuTgkxvF4EJupRjRXGgZZrnWFmKzfEzcqKF2ogCaF4QKVbAtuFx7xGwrDUcGd
```
### Example: Transferring tokens to an explicit recipient token account
Tokens may be transferred to a specific recipient token account. The recipient
token account must already exist and be of the same Token type.
```
$ spl-token create-account AQoKYV7tYpTrFZN6P5oUufbQKAUr9mNYGe1TTJC9wajM
@ -176,12 +212,12 @@ Account Token
7UX2i7SucgLMQcfZ75s3VXmZZY4YRUyJN9X1RgfMoDUi AQoKYV7tYpTrFZN6P5oUufbQKAUr9mNYGe1TTJC9wajM 100
CqAxDdBRnawzx9q4PYM3wrybLHBhDZ4P6BTV13WsRJYJ AQoKYV7tYpTrFZN6P5oUufbQKAUr9mNYGe1TTJC9wajM 0
```
```
$ spl-token transfer 7UX2i7SucgLMQcfZ75s3VXmZZY4YRUyJN9X1RgfMoDUi 50 CqAxDdBRnawzx9q4PYM3wrybLHBhDZ4P6BTV13WsRJYJ
Transfer 50 tokens
Sender: 7UX2i7SucgLMQcfZ75s3VXmZZY4YRUyJN9X1RgfMoDUi
Recipient: CqAxDdBRnawzx9q4PYM3wrybLHBhDZ4P6BTV13WsRJYJ
Signature: 5a3qbvoJQnTAxGPHCugibZTbSu7xuTgkxvF4EJupRjRXGgZZrnWFmKzfEzcqKF2ogCaF4QKVbAtuFx7xGwrDUcGd
```
```
@ -192,6 +228,9 @@ Account Token
CqAxDdBRnawzx9q4PYM3wrybLHBhDZ4P6BTV13WsRJYJ AQoKYV7tYpTrFZN6P5oUufbQKAUr9mNYGe1TTJC9wajM 50
```
### Example: Transferring tokens with sender funding
If the recipient a
### Example: Create a non-fungible token
Create the token type,
@ -476,3 +515,151 @@ have a balance of zero to be closed.
### Non-Fungible tokens
An NTF is simply a token type where only a single token has been minted.
## Wallet Integration Guide
This section describes how to integrate SPL Token support into an existing
wallet supporting native SOL. It assumes a model whereby the user has a single
system account as their **main wallet address** that they send and receive SOL
from.
Although all SPL Token accounts do have their own address on-chain, there's no
need to surface these additional addresses to the user.
There are two programs that are used by the wallet:
* SPL Token program - generic program that is used by all SPL Tokens
* SPL Associated Token Account program - this program defines the convention and the
provides the mechanism for mapping the user's wallet address to the associated
token accounts they hold.
### Associated Token Account
The associated token account convention allows all tokens to have the same
destination address, allowing the user share their main wallet address to
receive any SPL token.
The following Rust function can be used to derive the address of a user's
associated token account for a given SPL Token mint:
```rust
/// Finds the associated token address for a given wallet
/// address and SPL Token mint
pub fn get_associated_token_address(
wallet_address: &Pubkey,
spl_token_mint_address: &Pubkey,
) -> Pubkey {
Pubkey::find_program_address(
&[
&primary_account_address.to_bytes(),
&spl_token::id().to_bytes(),
&spl_token_mint_address.to_bytes(),
],
&spl_associated_token_account::id()
).0
}
```
Javascript equivalent:
```
import {PublicKey, PublicKeyNonce} from '@solana/web3.js';
async function findAssociatedTokenAddress(
walletAddress: Pubkey,
tokenMintAddress: Pubkey
): Promise<PublicKey> {
return PublicKey.findProgramAddress(
[
walletAddress.toBuffer(),
TOKEN_PROGRAM_ID.toBuffer(),
tokenMintAddress.toBuffer(),
],
SPL_ASSOCIATED_TOKEN_ACCOUNT_PROGRAM_ID
)[0];
}
```
### How to fetch and display token holdings
The [getTokenAccountsByOwner](https://docs.solana.com/apps/jsonrpc-api#gettokenaccountsbyowner)
JSON RPC method can be used to fetch all token accounts for a wallet address.
For each token mint, the wallet could have be multiple token accounts: the
associated token account and/or other ancillary token accounts
By convention it is suggested that wallets roll up the balances from all token
accounts of the same token mint into a single balance for the user to shield the
user from this complexity. See the
[Garbage Collecting Ancillary Token Accounts](#garbage-collecting-ancillary-token-accounts)
section for suggestions on how the wallet should clean up ancillary token accounts on the user's
### Associated Token Account
Before the user can receive tokens their associated token account must be created
on-chain, requiring a small amount of SOL to mark the account as rent-exempt.
There's no restriction on who can create a user's associated token account. It
could either be created by the wallet on behalf of the user or funded by a 3rd
party through an airdrop campaign.
Ultimately the
`spl_associated_token_account::create_associated_token_account()` instruction
just needs to be executed by some party.
#### Sample "Add Token" workflow
When the user wants to receive tokens, they should fund their associated token
account.
To do so, the wallet should provide a UI that allow the users to "add a token".
The user selects the kind of token, and is presented with information about how
much SOL it will cost to add the token. Upon confirmation, the wallet sends a
transaction with the
`spl_associated_token_account::create_associated_token_account()` instruction.
#### Sample "Airdrop campaign" workflow
For each recipient wallet addresses, send a transaction containing:
1. `spl_associated_token_account::create_associated_token_account()` to create
the recipient's associated token account if necessary
2. `TokenInstruction::Transfer` to complete the airdrop
#### Associated Token Account Ownership
The wallet should never use `TokenInstruction::SetAuthority` to set the
`AccountOwner` authority of the associated token account to another address.
### Ancillary Token Accounts
At any time ownership of an existing SPL Token account may be assigned to the
user. One way to accomplish this is with the
`spl-token authorize <TOKEN_ADDRESS> owner <USER_ADDRESS>` command.
### Transferring Tokens Between Wallets
The preferred method of transferring tokens between wallets is to transfer into
associated token account of the recipient.
The recipient must provide their main wallet address to the sender. The sender
then:
1. Derives the associated token account for the recipient using `spl_associated_token_account::get_associated_token_address`
1. Fetches the recipient's associated token account over RPC and checks that it exists.
1. If the recipient's associated token accountdoes not exist, the sender wallet may choose to first fund the recipient's wallet at their expense
1. Use `TokenInstruction::Transfer` to complete the transfer.
### Registry for token details
At the moment Token Mint addresses need to be hard coded by each wallet. **Improving this situation is a work in progress.**
### Garbage Collecting Ancillary Token Accounts
Wallets should empty ancillary token accounts as quickly as practical by
transferring into the user's associated token account. This effort serves two
purposes:
* If the user is the close authority for the ancillary account, the wallet can
reclaim SOL for the user by closing the account.
* If the ancillary account was funded by a 3rd party, once the account is
emptied that 3rd party may close the account and reclaim the SOL.
One natural time to garbage collect ancillary token accounts is when the user
next sends tokens. The additional instructions to do so can be added to the
existing transaction, and will not require an additional fee.
Cleanup Pseudo Steps:
1. For all non-empty ancillary token accounts, add a
`TokenInstruction::Transfer` instruction to the transfer the full token
amount to the user's associated token account.
2. For all empty ancillary token accounts where the user is the close authority,
add a `TokenInstruction::CloseAccount` instruction
If adding one or more of clean up instructions cause the transaction to exceed
the maximum allowed transaction size, remove those extra clean up instructions.
They can be cleaned up during the next send operation.