Skip to content
This repository was archived by the owner on Mar 11, 2025. It is now read-only.

Commit 4145440

Browse files
committed
Add SPL Token wallet integration guide
1 parent 8ae0f92 commit 4145440

File tree

1 file changed

+191
-4
lines changed

1 file changed

+191
-4
lines changed

docs/src/token.md

Lines changed: 191 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -117,8 +117,7 @@ $ spl-token balance 7UX2i7SucgLMQcfZ75s3VXmZZY4YRUyJN9X1RgfMoDUi
117117

118118
Mint 100 tokens into the account:
119119
```sh
120-
$ spl-token mint AQoKYV7tYpTrFZN6P5oUufbQKAUr9mNYGe1TTJC9wajM 100 \
121-
7UX2i7SucgLMQcfZ75s3VXmZZY4YRUyJN9X1RgfMoDUi
120+
$ spl-token mint AQoKYV7tYpTrFZN6P5oUufbQKAUr9mNYGe1TTJC9wajM 100
122121
Minting 100 tokens
123122
Token: AQoKYV7tYpTrFZN6P5oUufbQKAUr9mNYGe1TTJC9wajM
124123
Recipient: 7UX2i7SucgLMQcfZ75s3VXmZZY4YRUyJN9X1RgfMoDUi
@@ -162,7 +161,44 @@ Unwrapping GJTxcnA5Sydy8YRhqvHxbQ5QNsPyRKvzguodQEaShJje
162161
Signature: f7opZ86ZHKGvkJBQsJ8Pk81v8F3v1VUfyd4kFs4CABmfTnSZK5BffETznUU3tEWvzibgKJASCf7TUpDmwGi8Rmh
163162
```
164163

165-
### Example: Transferring tokens
164+
### Example: Transferring tokens to another user, with sender-funding
165+
First the receiver uses `spl-token create-account` to create their associated
166+
token account for the Token type. Then the receiver obtains their wallet
167+
address by running `solana address` and provides it to the sender.
168+
169+
The sender then runs:
170+
```
171+
$ spl-token transfer 7UX2i7SucgLMQcfZ75s3VXmZZY4YRUyJN9X1RgfMoDUi 50 vines1vzrYbzLMRdu58ou5XTby4qAqVRLmqo36NKPTg
172+
Transfer 50 tokens
173+
Sender: 7UX2i7SucgLMQcfZ75s3VXmZZY4YRUyJN9X1RgfMoDUi
174+
Recipient: vines1vzrYbzLMRdu58ou5XTby4qAqVRLmqo36NKPTg
175+
Recipient associated token account: F59618aQB8r6asXeMcB9jWuY6NEx1VduT9yFo1GTi1ks
176+
177+
Signature: 5a3qbvoJQnTAxGPHCugibZTbSu7xuTgkxvF4EJupRjRXGgZZrnWFmKzfEzcqKF2ogCaF4QKVbAtuFx7xGwrDUcGd
178+
```
179+
180+
### Example: Transferring tokens to another user, with sender-funding
181+
If the receiver does not yet have an associated token account, the sender may
182+
choose to fund the receiver's account.
183+
184+
The receiver obtains their wallet address by running `solana address` and provides it to the sender.
185+
186+
The sender then runs to fund the receiver's associated token account, at the
187+
sender's expense, and then transfers 50 tokens into it:
188+
```
189+
$ spl-token transfer --fund-recipient 7UX2i7SucgLMQcfZ75s3VXmZZY4YRUyJN9X1RgfMoDUi 50 vines1vzrYbzLMRdu58ou5XTby4qAqVRLmqo36NKPTg
190+
Transfer 50 tokens
191+
Sender: 7UX2i7SucgLMQcfZ75s3VXmZZY4YRUyJN9X1RgfMoDUi
192+
Recipient: vines1vzrYbzLMRdu58ou5XTby4qAqVRLmqo36NKPTg
193+
Recipient associated token account: F59618aQB8r6asXeMcB9jWuY6NEx1VduT9yFo1GTi1ks
194+
Funding recipient: F59618aQB8r6asXeMcB9jWuY6NEx1VduT9yFo1GTi1ks (0.00203928 SOL)
195+
196+
Signature: 5a3qbvoJQnTAxGPHCugibZTbSu7xuTgkxvF4EJupRjRXGgZZrnWFmKzfEzcqKF2ogCaF4QKVbAtuFx7xGwrDUcGd
197+
```
198+
199+
### Example: Transferring tokens to an explicit recipient token account
200+
Tokens may be transferred to a specific recipient token account. The recipient
201+
token account must already exist and be of the same Token type.
166202

167203
```
168204
$ spl-token create-account AQoKYV7tYpTrFZN6P5oUufbQKAUr9mNYGe1TTJC9wajM
@@ -176,12 +212,12 @@ Account Token
176212
7UX2i7SucgLMQcfZ75s3VXmZZY4YRUyJN9X1RgfMoDUi AQoKYV7tYpTrFZN6P5oUufbQKAUr9mNYGe1TTJC9wajM 100
177213
CqAxDdBRnawzx9q4PYM3wrybLHBhDZ4P6BTV13WsRJYJ AQoKYV7tYpTrFZN6P5oUufbQKAUr9mNYGe1TTJC9wajM 0
178214
```
179-
180215
```
181216
$ spl-token transfer 7UX2i7SucgLMQcfZ75s3VXmZZY4YRUyJN9X1RgfMoDUi 50 CqAxDdBRnawzx9q4PYM3wrybLHBhDZ4P6BTV13WsRJYJ
182217
Transfer 50 tokens
183218
Sender: 7UX2i7SucgLMQcfZ75s3VXmZZY4YRUyJN9X1RgfMoDUi
184219
Recipient: CqAxDdBRnawzx9q4PYM3wrybLHBhDZ4P6BTV13WsRJYJ
220+
185221
Signature: 5a3qbvoJQnTAxGPHCugibZTbSu7xuTgkxvF4EJupRjRXGgZZrnWFmKzfEzcqKF2ogCaF4QKVbAtuFx7xGwrDUcGd
186222
```
187223
```
@@ -192,6 +228,9 @@ Account Token
192228
CqAxDdBRnawzx9q4PYM3wrybLHBhDZ4P6BTV13WsRJYJ AQoKYV7tYpTrFZN6P5oUufbQKAUr9mNYGe1TTJC9wajM 50
193229
```
194230

231+
### Example: Transferring tokens with sender funding
232+
If the recipient a
233+
195234
### Example: Create a non-fungible token
196235

197236
Create the token type,
@@ -476,3 +515,151 @@ have a balance of zero to be closed.
476515

477516
### Non-Fungible tokens
478517
An NTF is simply a token type where only a single token has been minted.
518+
519+
## Wallet Integration Guide
520+
This section describes how to integrate SPL Token support into an existing
521+
wallet supporting native SOL. It assumes a model whereby the user has a single
522+
system account as their **main wallet address** that they send and receive SOL
523+
from.
524+
525+
Although all SPL Token accounts do have their own address on-chain, there's no
526+
need to surface these additional addresses to the user.
527+
528+
There are two programs that are used by the wallet:
529+
* SPL Token program - generic program that is used by all SPL Tokens
530+
* SPL Associated Token Account program - this program defines the convention and the
531+
provides the mechanism for mapping the user's wallet address to the associated
532+
token accounts they hold.
533+
534+
### Associated Token Account
535+
The associated token account convention allows all tokens to have the same
536+
destination address, allowing the user share their main wallet address to
537+
receive any SPL token.
538+
539+
The following Rust function can be used to derive the address of a user's
540+
associated token account for a given SPL Token mint:
541+
```rust
542+
/// Finds the associated token address for a given wallet
543+
/// address and SPL Token mint
544+
pub fn get_associated_token_address(
545+
wallet_address: &Pubkey,
546+
spl_token_mint_address: &Pubkey,
547+
) -> Pubkey {
548+
Pubkey::find_program_address(
549+
&[
550+
&primary_account_address.to_bytes(),
551+
&spl_token::id().to_bytes(),
552+
&spl_token_mint_address.to_bytes(),
553+
],
554+
&spl_associated_token_account::id()
555+
).0
556+
}
557+
```
558+
559+
560+
Javascript equivalent:
561+
```
562+
import {PublicKey, PublicKeyNonce} from '@solana/web3.js';
563+
564+
async function findAssociatedTokenAddress(
565+
walletAddress: Pubkey,
566+
tokenMintAddress: Pubkey
567+
): Promise<PublicKey> {
568+
return PublicKey.findProgramAddress(
569+
[
570+
walletAddress.toBuffer(),
571+
TOKEN_PROGRAM_ID.toBuffer(),
572+
tokenMintAddress.toBuffer(),
573+
],
574+
SPL_ASSOCIATED_TOKEN_ACCOUNT_PROGRAM_ID
575+
)[0];
576+
}
577+
```
578+
579+
### How to fetch and display token holdings
580+
The [getTokenAccountsByOwner](https://docs.solana.com/apps/jsonrpc-api#gettokenaccountsbyowner)
581+
JSON RPC method can be used to fetch all token accounts for a wallet address.
582+
583+
For each token mint, the wallet could have be multiple token accounts: the
584+
associated token account and/or other ancillary token accounts
585+
586+
By convention it is suggested that wallets roll up the balances from all token
587+
accounts of the same token mint into a single balance for the user to shield the
588+
user from this complexity. See the
589+
[Garbage Collecting Ancillary Token Accounts](#garbage-collecting-ancillary-token-accounts)
590+
section for suggestions on how the wallet should clean up ancillary token accounts on the user's
591+
592+
### Associated Token Account
593+
Before the user can receive tokens their associated token account must be created
594+
on-chain, requiring a small amount of SOL to mark the account as rent-exempt.
595+
596+
There's no restriction on who can create a user's associated token account. It
597+
could either be created by the wallet on behalf of the user or funded by a 3rd
598+
party through an airdrop campaign.
599+
600+
Ultimately the
601+
`spl_associated_token_account::create_associated_token_account()` instruction
602+
just needs to be executed by some party.
603+
604+
#### Sample "Add Token" workflow
605+
When the user wants to receive tokens, they should fund their associated token
606+
account.
607+
608+
To do so, the wallet should provide a UI that allow the users to "add a token".
609+
The user selects the kind of token, and is presented with information about how
610+
much SOL it will cost to add the token. Upon confirmation, the wallet sends a
611+
transaction with the
612+
`spl_associated_token_account::create_associated_token_account()` instruction.
613+
614+
#### Sample "Airdrop campaign" workflow
615+
For each recipient wallet addresses, send a transaction containing:
616+
1. `spl_associated_token_account::create_associated_token_account()` to create
617+
the recipient's associated token account if necessary
618+
2. `TokenInstruction::Transfer` to complete the airdrop
619+
620+
#### Associated Token Account Ownership
621+
The wallet should never use `TokenInstruction::SetAuthority` to set the
622+
`AccountOwner` authority of the associated token account to another address.
623+
624+
### Ancillary Token Accounts
625+
At any time ownership of an existing SPL Token account may be assigned to the
626+
user. One way to accomplish this is with the
627+
`spl-token authorize <TOKEN_ADDRESS> owner <USER_ADDRESS>` command.
628+
629+
### Transferring Tokens Between Wallets
630+
The preferred method of transferring tokens between wallets is to transfer into
631+
associated token account of the recipient.
632+
633+
The recipient must provide their main wallet address to the sender. The sender
634+
then:
635+
1. Derives the associated token account for the recipient using `spl_associated_token_account::get_associated_token_address`
636+
1. Fetches the recipient's associated token account over RPC and checks that it exists.
637+
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
638+
1. Use `TokenInstruction::Transfer` to complete the transfer.
639+
640+
### Registry for token details
641+
At the moment Token Mint addresses need to be hard coded by each wallet. **Improving this situation is a work in progress.**
642+
643+
### Garbage Collecting Ancillary Token Accounts
644+
Wallets should empty ancillary token accounts as quickly as practical by
645+
transferring into the user's associated token account. This effort serves two
646+
purposes:
647+
* If the user is the close authority for the ancillary account, the wallet can
648+
reclaim SOL for the user by closing the account.
649+
* If the ancillary account was funded by a 3rd party, once the account is
650+
emptied that 3rd party may close the account and reclaim the SOL.
651+
652+
One natural time to garbage collect ancillary token accounts is when the user
653+
next sends tokens. The additional instructions to do so can be added to the
654+
existing transaction, and will not require an additional fee.
655+
656+
Cleanup Pseudo Steps:
657+
1. For all non-empty ancillary token accounts, add a
658+
`TokenInstruction::Transfer` instruction to the transfer the full token
659+
amount to the user's associated token account.
660+
2. For all empty ancillary token accounts where the user is the close authority,
661+
add a `TokenInstruction::CloseAccount` instruction
662+
663+
If adding one or more of clean up instructions cause the transaction to exceed
664+
the maximum allowed transaction size, remove those extra clean up instructions.
665+
They can be cleaned up during the next send operation.

0 commit comments

Comments
 (0)