@@ -117,8 +117,7 @@ $ spl-token balance 7UX2i7SucgLMQcfZ75s3VXmZZY4YRUyJN9X1RgfMoDUi
117
117
118
118
Mint 100 tokens into the account:
119
119
``` sh
120
- $ spl-token mint AQoKYV7tYpTrFZN6P5oUufbQKAUr9mNYGe1TTJC9wajM 100 \
121
- 7UX2i7SucgLMQcfZ75s3VXmZZY4YRUyJN9X1RgfMoDUi
120
+ $ spl-token mint AQoKYV7tYpTrFZN6P5oUufbQKAUr9mNYGe1TTJC9wajM 100
122
121
Minting 100 tokens
123
122
Token: AQoKYV7tYpTrFZN6P5oUufbQKAUr9mNYGe1TTJC9wajM
124
123
Recipient: 7UX2i7SucgLMQcfZ75s3VXmZZY4YRUyJN9X1RgfMoDUi
@@ -162,7 +161,44 @@ Unwrapping GJTxcnA5Sydy8YRhqvHxbQ5QNsPyRKvzguodQEaShJje
162
161
Signature: f7opZ86ZHKGvkJBQsJ8Pk81v8F3v1VUfyd4kFs4CABmfTnSZK5BffETznUU3tEWvzibgKJASCf7TUpDmwGi8Rmh
163
162
```
164
163
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.
166
202
167
203
```
168
204
$ spl-token create-account AQoKYV7tYpTrFZN6P5oUufbQKAUr9mNYGe1TTJC9wajM
@@ -176,12 +212,12 @@ Account Token
176
212
7UX2i7SucgLMQcfZ75s3VXmZZY4YRUyJN9X1RgfMoDUi AQoKYV7tYpTrFZN6P5oUufbQKAUr9mNYGe1TTJC9wajM 100
177
213
CqAxDdBRnawzx9q4PYM3wrybLHBhDZ4P6BTV13WsRJYJ AQoKYV7tYpTrFZN6P5oUufbQKAUr9mNYGe1TTJC9wajM 0
178
214
```
179
-
180
215
```
181
216
$ spl-token transfer 7UX2i7SucgLMQcfZ75s3VXmZZY4YRUyJN9X1RgfMoDUi 50 CqAxDdBRnawzx9q4PYM3wrybLHBhDZ4P6BTV13WsRJYJ
182
217
Transfer 50 tokens
183
218
Sender: 7UX2i7SucgLMQcfZ75s3VXmZZY4YRUyJN9X1RgfMoDUi
184
219
Recipient: CqAxDdBRnawzx9q4PYM3wrybLHBhDZ4P6BTV13WsRJYJ
220
+
185
221
Signature: 5a3qbvoJQnTAxGPHCugibZTbSu7xuTgkxvF4EJupRjRXGgZZrnWFmKzfEzcqKF2ogCaF4QKVbAtuFx7xGwrDUcGd
186
222
```
187
223
```
@@ -192,6 +228,9 @@ Account Token
192
228
CqAxDdBRnawzx9q4PYM3wrybLHBhDZ4P6BTV13WsRJYJ AQoKYV7tYpTrFZN6P5oUufbQKAUr9mNYGe1TTJC9wajM 50
193
229
```
194
230
231
+ ### Example: Transferring tokens with sender funding
232
+ If the recipient a
233
+
195
234
### Example: Create a non-fungible token
196
235
197
236
Create the token type,
@@ -476,3 +515,151 @@ have a balance of zero to be closed.
476
515
477
516
### Non-Fungible tokens
478
517
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