2020-07-16 09:13:45 -07:00
|
|
|
---
|
|
|
|
title: Token Program
|
|
|
|
---
|
|
|
|
|
2020-10-14 08:42:15 -07:00
|
|
|
A Token program on the Solana blockchain.
|
2020-07-16 09:13:45 -07:00
|
|
|
|
2020-10-14 08:42:15 -07:00
|
|
|
This program defines a common implementation for Fungible and Non Fungible tokens.
|
2020-07-16 09:13:45 -07:00
|
|
|
|
2020-07-28 11:00:37 -07:00
|
|
|
## Background
|
|
|
|
|
|
|
|
Solana's programming model and the definitions of the Solana terms used in this
|
|
|
|
document are available at:
|
2020-08-03 07:31:23 -07:00
|
|
|
|
2020-07-28 11:00:37 -07:00
|
|
|
- https://docs.solana.com/apps
|
|
|
|
- https://docs.solana.com/terminology
|
|
|
|
|
|
|
|
## Source
|
|
|
|
|
|
|
|
The Token Program's source is available on
|
|
|
|
[github](https://github.com/solana-labs/solana-program-library)
|
|
|
|
|
|
|
|
## Interface
|
|
|
|
|
2020-11-04 10:13:40 -08:00
|
|
|
The Token Program is written in Rust and available on [crates.io](https://crates.io/crates/spl-token) and [docs.rs](https://docs.rs/spl-token).
|
2020-07-28 11:00:37 -07:00
|
|
|
|
2020-11-04 10:13:40 -08:00
|
|
|
Auto-generated C bindings are also available
|
2020-09-01 18:41:05 -07:00
|
|
|
[here](https://github.com/solana-labs/solana-program-library/blob/master/token/program/inc/token.h)
|
2020-07-28 11:00:37 -07:00
|
|
|
|
2020-09-09 16:57:30 -07:00
|
|
|
[JavaScript
|
2020-07-28 11:00:37 -07:00
|
|
|
bindings](https://github.com/solana-labs/solana-program-library/blob/master/token/js/client/token.js)
|
2020-08-28 17:16:55 -07:00
|
|
|
are available that support loading the Token Program on to a chain and issue
|
2020-07-28 11:00:37 -07:00
|
|
|
instructions.
|
|
|
|
|
2020-11-04 10:13:40 -08:00
|
|
|
See the [SPL Associated Token Account](associated-token-account.md) program for
|
|
|
|
convention around wallet address to token account mapping and funding.
|
|
|
|
|
2020-08-10 09:16:56 -07:00
|
|
|
## Command-line Utility
|
|
|
|
|
|
|
|
The `spl-token` command-line utility can be used to experiment with SPL
|
|
|
|
tokens. Once you have [Rust installed](https://rustup.rs/), run:
|
|
|
|
```sh
|
|
|
|
$ cargo install spl-token-cli
|
|
|
|
```
|
|
|
|
|
2020-08-10 19:57:22 -07:00
|
|
|
Run `spl-token --help` for a full description of available commands.
|
2020-08-10 09:16:56 -07:00
|
|
|
|
2020-09-17 17:43:55 -07:00
|
|
|
### Configuration
|
2020-09-22 02:03:25 -07:00
|
|
|
|
2020-09-17 17:43:55 -07:00
|
|
|
The `spl-token` configuration is shared with the `solana` command-line tool.
|
|
|
|
|
|
|
|
#### Current Configuration
|
|
|
|
|
|
|
|
```
|
|
|
|
solana config get
|
|
|
|
```
|
|
|
|
|
|
|
|
```
|
|
|
|
Config File: ${HOME}/.config/solana/cli/config.yml
|
|
|
|
RPC URL: https://api.mainnet-beta.solana.com
|
|
|
|
WebSocket URL: wss://api.mainnet-beta.solana.com/ (computed)
|
|
|
|
Keypair Path: ${HOME}/.config/solana/id.json
|
|
|
|
```
|
|
|
|
|
|
|
|
#### Cluster RPC URL
|
|
|
|
|
2020-09-22 02:03:25 -07:00
|
|
|
See [Solana clusters](https://docs.solana.com/clusters) for cluster-specific RPC URLs
|
2020-09-17 17:43:55 -07:00
|
|
|
```
|
|
|
|
solana config set --url https://devnet.solana.com
|
|
|
|
```
|
|
|
|
|
|
|
|
#### Default Keypair
|
|
|
|
|
2020-11-04 10:13:40 -08:00
|
|
|
See [Keypair conventions](https://docs.solana.com/cli/conventions#keypair-conventions)
|
|
|
|
for information on how to setup a keypair if you don't already have one.
|
2020-09-22 02:03:25 -07:00
|
|
|
|
2020-09-17 17:43:55 -07:00
|
|
|
Keypair File
|
|
|
|
```
|
|
|
|
solana config set --keypair ${HOME}/new-keypair.json
|
|
|
|
```
|
|
|
|
|
|
|
|
Hardware Wallet URL (See [URL spec](https://docs.solana.com/wallet-guide/hardware-wallets#specify-a-keypair-url))
|
|
|
|
```
|
|
|
|
solana config set --keypair usb://ledger/
|
|
|
|
```
|
|
|
|
|
2020-10-14 08:42:15 -07:00
|
|
|
### Example: Creating your own fungible token
|
2020-08-10 09:16:56 -07:00
|
|
|
|
|
|
|
```sh
|
|
|
|
$ spl-token create-token
|
|
|
|
Creating token AQoKYV7tYpTrFZN6P5oUufbQKAUr9mNYGe1TTJC9wajM
|
|
|
|
Signature: 47hsLFxWRCg8azaZZPSnQR8DNTRsGyPNfUK7jqyzgt7wf9eag3nSnewqoZrVZHKm8zt3B6gzxhr91gdQ5qYrsRG4
|
|
|
|
```
|
|
|
|
|
|
|
|
The unique identifier of the token is `AQoKYV7tYpTrFZN6P5oUufbQKAUr9mNYGe1TTJC9wajM`.
|
|
|
|
|
|
|
|
Tokens when initially created by `spl-token` have no supply:
|
|
|
|
```sh
|
|
|
|
spl-token supply AQoKYV7tYpTrFZN6P5oUufbQKAUr9mNYGe1TTJC9wajM
|
|
|
|
0
|
|
|
|
```
|
|
|
|
|
|
|
|
Let's mint some. First create an account to hold a balance of the new
|
|
|
|
`AQoKYV7tYpTrFZN6P5oUufbQKAUr9mNYGe1TTJC9wajM` token:
|
|
|
|
```sh
|
|
|
|
$ spl-token create-account AQoKYV7tYpTrFZN6P5oUufbQKAUr9mNYGe1TTJC9wajM
|
|
|
|
Creating account 7UX2i7SucgLMQcfZ75s3VXmZZY4YRUyJN9X1RgfMoDUi
|
|
|
|
Signature: 42Sa5eK9dMEQyvD9GMHuKxXf55WLZ7tfjabUKDhNoZRAxj9MsnN7omriWMEHXLea3aYpjZ862qocRLVikvkHkyfy
|
|
|
|
```
|
|
|
|
|
|
|
|
`7UX2i7SucgLMQcfZ75s3VXmZZY4YRUyJN9X1RgfMoDUi` is now an empty account:
|
|
|
|
```sh
|
|
|
|
$ spl-token balance 7UX2i7SucgLMQcfZ75s3VXmZZY4YRUyJN9X1RgfMoDUi
|
|
|
|
0
|
|
|
|
```
|
|
|
|
|
|
|
|
Mint 100 tokens into the account:
|
|
|
|
```sh
|
2020-10-14 20:07:44 -07:00
|
|
|
$ spl-token mint AQoKYV7tYpTrFZN6P5oUufbQKAUr9mNYGe1TTJC9wajM 100
|
2020-08-10 09:16:56 -07:00
|
|
|
Minting 100 tokens
|
|
|
|
Token: AQoKYV7tYpTrFZN6P5oUufbQKAUr9mNYGe1TTJC9wajM
|
|
|
|
Recipient: 7UX2i7SucgLMQcfZ75s3VXmZZY4YRUyJN9X1RgfMoDUi
|
|
|
|
Signature: 41mARH42fPkbYn1mvQ6hYLjmJtjW98NXwd6pHqEYg9p8RnuoUsMxVd16RkStDHEzcS2sfpSEpFscrJQn3HkHzLaa
|
|
|
|
```
|
|
|
|
|
|
|
|
The token `supply` and account `balance` now reflect the result of minting:
|
|
|
|
```sh
|
|
|
|
$ spl-token supply AQoKYV7tYpTrFZN6P5oUufbQKAUr9mNYGe1TTJC9wajM
|
|
|
|
100
|
|
|
|
$ spl-token balance 7UX2i7SucgLMQcfZ75s3VXmZZY4YRUyJN9X1RgfMoDUi
|
|
|
|
100
|
|
|
|
```
|
|
|
|
|
|
|
|
### Example: View all Tokens that you own
|
|
|
|
|
|
|
|
```sh
|
|
|
|
$ spl-token accounts
|
|
|
|
Account Token Balance
|
|
|
|
-------------------------------------------------------------------------------------------------
|
|
|
|
2ryb53FGVLVYFXmAemN7avawevuNFXwTVetMpH9ag3XZ 7e2X5oeAAJyUTi4PfSGXFLGhyPw2H8oELm1mx87ZCgwF 84
|
|
|
|
7UX2i7SucgLMQcfZ75s3VXmZZY4YRUyJN9X1RgfMoDUi AQoKYV7tYpTrFZN6P5oUufbQKAUr9mNYGe1TTJC9wajM 100
|
|
|
|
CqAxDdBRnawzx9q4PYM3wrybLHBhDZ4P6BTV13WsRJYJ AQoKYV7tYpTrFZN6P5oUufbQKAUr9mNYGe1TTJC9wajM 0
|
|
|
|
JAopo117aj6HMwCRjXSyNpZfGDJRi7ukqHgs2inXD8Rc AQoKYV7tYpTrFZN6P5oUufbQKAUr9mNYGe1TTJC9wajM 0
|
|
|
|
```
|
|
|
|
|
|
|
|
### Example: Wrapping SOL in a Token
|
|
|
|
|
|
|
|
```sh
|
|
|
|
$ spl-token wrap 1
|
|
|
|
Wrapping 1 SOL into GJTxcnA5Sydy8YRhqvHxbQ5QNsPyRKvzguodQEaShJje
|
|
|
|
Signature: 4f4s5QVMKisLS6ihZcXXPbiBAzjnvkBcp2A7KKER7k9DwJ4qjbVsQBKv2rAyBumXC1gLn8EJQhwWkybE4yJGnw2Y
|
|
|
|
```
|
|
|
|
|
|
|
|
To unwrap the Token back to SOL:
|
|
|
|
```
|
|
|
|
$ spl-token unwrap GJTxcnA5Sydy8YRhqvHxbQ5QNsPyRKvzguodQEaShJje
|
|
|
|
Unwrapping GJTxcnA5Sydy8YRhqvHxbQ5QNsPyRKvzguodQEaShJje
|
|
|
|
Amount: 1 SOL
|
|
|
|
Recipient: vines1vzrYbzLMRdu58ou5XTby4qAqVRLmqo36NKPTg
|
|
|
|
Signature: f7opZ86ZHKGvkJBQsJ8Pk81v8F3v1VUfyd4kFs4CABmfTnSZK5BffETznUU3tEWvzibgKJASCf7TUpDmwGi8Rmh
|
|
|
|
```
|
|
|
|
|
2020-11-04 10:13:40 -08:00
|
|
|
### Example: Transferring tokens to another user
|
2020-10-14 20:07:44 -07:00
|
|
|
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.
|
2020-08-10 12:55:56 -07:00
|
|
|
|
|
|
|
```
|
|
|
|
$ spl-token create-account AQoKYV7tYpTrFZN6P5oUufbQKAUr9mNYGe1TTJC9wajM
|
|
|
|
Creating account CqAxDdBRnawzx9q4PYM3wrybLHBhDZ4P6BTV13WsRJYJ
|
|
|
|
Signature: 4yPWj22mbyLu5mhfZ5WATNfYzTt5EQ7LGzryxM7Ufu7QCVjTE7czZdEBqdKR7vjKsfAqsBdjU58NJvXrTqCXvfWW
|
|
|
|
```
|
|
|
|
```
|
|
|
|
$ spl-token accounts AQoKYV7tYpTrFZN6P5oUufbQKAUr9mNYGe1TTJC9wajM
|
|
|
|
Account Token Balance
|
|
|
|
-------------------------------------------------------------------------------------------------
|
|
|
|
7UX2i7SucgLMQcfZ75s3VXmZZY4YRUyJN9X1RgfMoDUi AQoKYV7tYpTrFZN6P5oUufbQKAUr9mNYGe1TTJC9wajM 100
|
|
|
|
CqAxDdBRnawzx9q4PYM3wrybLHBhDZ4P6BTV13WsRJYJ AQoKYV7tYpTrFZN6P5oUufbQKAUr9mNYGe1TTJC9wajM 0
|
|
|
|
```
|
|
|
|
```
|
|
|
|
$ spl-token transfer 7UX2i7SucgLMQcfZ75s3VXmZZY4YRUyJN9X1RgfMoDUi 50 CqAxDdBRnawzx9q4PYM3wrybLHBhDZ4P6BTV13WsRJYJ
|
|
|
|
Transfer 50 tokens
|
|
|
|
Sender: 7UX2i7SucgLMQcfZ75s3VXmZZY4YRUyJN9X1RgfMoDUi
|
|
|
|
Recipient: CqAxDdBRnawzx9q4PYM3wrybLHBhDZ4P6BTV13WsRJYJ
|
2020-10-14 20:07:44 -07:00
|
|
|
|
2020-08-10 12:55:56 -07:00
|
|
|
Signature: 5a3qbvoJQnTAxGPHCugibZTbSu7xuTgkxvF4EJupRjRXGgZZrnWFmKzfEzcqKF2ogCaF4QKVbAtuFx7xGwrDUcGd
|
|
|
|
```
|
|
|
|
```
|
|
|
|
$ spl-token accounts AQoKYV7tYpTrFZN6P5oUufbQKAUr9mNYGe1TTJC9wajM
|
|
|
|
Account Token Balance
|
|
|
|
-------------------------------------------------------------------------------------------------
|
|
|
|
7UX2i7SucgLMQcfZ75s3VXmZZY4YRUyJN9X1RgfMoDUi AQoKYV7tYpTrFZN6P5oUufbQKAUr9mNYGe1TTJC9wajM 50
|
|
|
|
CqAxDdBRnawzx9q4PYM3wrybLHBhDZ4P6BTV13WsRJYJ AQoKYV7tYpTrFZN6P5oUufbQKAUr9mNYGe1TTJC9wajM 50
|
|
|
|
```
|
|
|
|
|
2020-10-14 08:42:15 -07:00
|
|
|
### Example: Create a non-fungible token
|
|
|
|
|
|
|
|
Create the token type,
|
|
|
|
```
|
|
|
|
$ spl-token create-token
|
|
|
|
Creating token 559u4Tdr9umKwft3yHMsnAxohhzkFnUBPAFtibwuZD9z
|
|
|
|
Signature: 4kz82JUey1B9ki1McPW7NYv1NqPKCod6WNptSkYqtuiEsQb9exHaktSAHJJsm4YxuGNW4NugPJMFX9ee6WA2dXts
|
|
|
|
```
|
|
|
|
|
|
|
|
then create an account to hold tokens of this new type:
|
|
|
|
```
|
|
|
|
$ spl-token create-account 559u4Tdr9umKwft3yHMsnAxohhzkFnUBPAFtibwuZD9z
|
|
|
|
Creating account 7KqpRwzkkeweW5jQoETyLzhvs9rcCj9dVQ1MnzudirsM
|
|
|
|
Signature: sjChze6ecaRtvuQVZuwURyg6teYeiH8ZwT6UTuFNKjrdayQQ3KNdPB7d2DtUZ6McafBfEefejHkJ6MWQEfVHLtC
|
|
|
|
```
|
|
|
|
|
|
|
|
Now mint only one token into the account,
|
|
|
|
```
|
|
|
|
$ spl-token mint 559u4Tdr9umKwft3yHMsnAxohhzkFnUBPAFtibwuZD9z 1 7KqpRwzkkeweW5jQoETyLzhvs9rcCj9dVQ1MnzudirsM
|
|
|
|
Minting 1 tokens
|
|
|
|
Token: 559u4Tdr9umKwft3yHMsnAxohhzkFnUBPAFtibwuZD9z
|
|
|
|
Recipient: 7KqpRwzkkeweW5jQoETyLzhvs9rcCj9dVQ1MnzudirsM
|
|
|
|
Signature: 2Kzg6ZArQRCRvcoKSiievYy3sfPqGV91Whnz6SeimhJQXKBTYQf3E54tWg3zPpYLbcDexxyTxnj4QF69ucswfdY
|
|
|
|
```
|
|
|
|
|
|
|
|
and disable future minting:
|
|
|
|
```
|
|
|
|
$ spl-token authorize 559u4Tdr9umKwft3yHMsnAxohhzkFnUBPAFtibwuZD9z mint --disable
|
|
|
|
Updating 559u4Tdr9umKwft3yHMsnAxohhzkFnUBPAFtibwuZD9z
|
|
|
|
Current mint authority: vines1vzrYbzLMRdu58ou5XTby4qAqVRLmqo36NKPTg
|
|
|
|
New mint authority: disabled
|
|
|
|
Signature: 5QpykLzZsceoKcVRRFow9QCdae4Dp2zQAcjebyEWoezPFg2Np73gHKWQicHG1mqRdXu3yiZbrft3Q8JmqNRNqhwU
|
|
|
|
```
|
|
|
|
|
|
|
|
Now the `7KqpRwzkkeweW5jQoETyLzhvs9rcCj9dVQ1MnzudirsM` account holds the
|
|
|
|
one and only `559u4Tdr9umKwft3yHMsnAxohhzkFnUBPAFtibwuZD9z` token:
|
|
|
|
|
|
|
|
```
|
|
|
|
$ spl-token account-info 7KqpRwzkkeweW5jQoETyLzhvs9rcCj9dVQ1MnzudirsM
|
|
|
|
|
|
|
|
Address: 7KqpRwzkkeweW5jQoETyLzhvs9rcCj9dVQ1MnzudirsM
|
|
|
|
Balance: 1
|
|
|
|
Mint: 559u4Tdr9umKwft3yHMsnAxohhzkFnUBPAFtibwuZD9z
|
|
|
|
Owner: vines1vzrYbzLMRdu58ou5XTby4qAqVRLmqo36NKPTg
|
|
|
|
State: Initialized
|
|
|
|
Delegation: (not set)
|
|
|
|
Close authority: (not set)
|
|
|
|
```
|
|
|
|
|
|
|
|
```
|
|
|
|
$ spl-token supply 559u4Tdr9umKwft3yHMsnAxohhzkFnUBPAFtibwuZD9z
|
|
|
|
1
|
|
|
|
```
|
|
|
|
|
2020-11-09 17:59:25 -08:00
|
|
|
### Mutlisig usage
|
|
|
|
|
|
|
|
The main difference in `spl-token` command line usage when referencing multisig
|
|
|
|
accounts is in specifying the `--owner` argument. Typically the signer specified
|
|
|
|
by this argument directly provides a signature granting its authority, but in
|
|
|
|
the multisig case it just points to the address of the multisig account.
|
2020-11-09 19:47:55 -08:00
|
|
|
Signatures are then provided by the multisig signer-set members specified by the
|
2020-11-09 17:59:25 -08:00
|
|
|
`--multisig-signer` argument.
|
|
|
|
|
|
|
|
Multisig accounts can be used for any authority on an SPL Token mint or token
|
|
|
|
account.
|
|
|
|
- Mint account mint authority: `spl-token mint ...`, `spl-token authorize ... mint ...`
|
|
|
|
- Mint account freeze authority: `spl-token freeze ...`, `spl-token thaw ...`,
|
|
|
|
`spl-token authorize ... freeze ...`
|
|
|
|
- Token account owner authority: `spl-token transfer ...`, `spl-token approve ...`,
|
|
|
|
`spl-token revoke ...`, `spl-token burn ...`, `spl-token wrap ...`,
|
|
|
|
`spl-token unwrap ...`, `spl-token authorize ... owner ...`
|
|
|
|
- Token account close authority: `spl-token close ...`, `spl-token authorize ... close ...`
|
|
|
|
|
|
|
|
### Example: Mint with multisig authority
|
|
|
|
|
2020-11-09 19:47:55 -08:00
|
|
|
First create keypairs to act as the multisig signer-set. In reality, these can
|
2020-11-09 19:23:47 -08:00
|
|
|
be any supported signer, like: a Ledger hardware wallet, a keypair file, or
|
|
|
|
a paper wallet. For convenience, keypair files will be used in this example.
|
2020-11-09 17:59:25 -08:00
|
|
|
```
|
|
|
|
$ for i in $(seq 3); do solana-keygen new --no-passphrase -so "signer-${i}.json"; done
|
|
|
|
Wrote new keypair to signer-1.json
|
|
|
|
Wrote new keypair to signer-2.json
|
|
|
|
Wrote new keypair to signer-3.json
|
|
|
|
```
|
|
|
|
|
2020-11-09 19:47:55 -08:00
|
|
|
In order to create the multisig account, the public keys of the signer-set must
|
2020-11-09 17:59:25 -08:00
|
|
|
be collected.
|
|
|
|
```
|
|
|
|
$ for i in $(seq 3); do SIGNER="signer-${i}.json"; echo "$SIGNER: $(solana-keygen pubkey "$SIGNER")"; done
|
|
|
|
signer-1.json: BzWpkuRrwXHq4SSSFHa8FJf6DRQy4TaeoXnkA89vTgHZ
|
|
|
|
signer-2.json: DhkUfKgfZ8CF6PAGKwdABRL1VqkeNrTSRx8LZfpPFVNY
|
|
|
|
signer-3.json: D7ssXHrZJjfpZXsmDf8RwfPxe1BMMMmP1CtmX3WojPmG
|
|
|
|
```
|
|
|
|
|
|
|
|
Now the multisig account can be created with the `spl-token create-multisig`
|
|
|
|
subcommand. Its first positional argument is the minimum number of signers (`M`)
|
|
|
|
that must sign a transaction affecting a token/mint account that is controlled
|
|
|
|
by this multisig account. The remaining positional arguments are the public keys
|
|
|
|
of all keypairs allowed (`N`) to sign for the multisig account. This example
|
|
|
|
will use a "2 of 3" multisig account. That is, two of the three allowed keypairs
|
|
|
|
must sign all transactions.
|
|
|
|
|
2020-11-09 20:18:58 -08:00
|
|
|
NOTE: SPL Token Multisig accounts are limited to a signer-set of eleven signers
|
2020-11-09 17:59:25 -08:00
|
|
|
(1 <= `N` <= 11) and minimum signers must be no more than `N` (1 <= `M` <= `N`)
|
|
|
|
```
|
|
|
|
$ spl-token create-multisig 2 BzWpkuRrwXHq4SSSFHa8FJf6DRQy4TaeoXnkA89vTgHZ \
|
|
|
|
DhkUfKgfZ8CF6PAGKwdABRL1VqkeNrTSRx8LZfpPFVNY D7ssXHrZJjfpZXsmDf8RwfPxe1BMMMmP1CtmX3WojPmG
|
|
|
|
Creating 2/3 multisig 46ed77fd4WTN144q62BwjU2B3ogX3Xmmc8PT5Z3Xc2re
|
|
|
|
Signature: 2FN4KXnczAz33SAxwsuevqrD1BvikP6LUhLie5Lz4ETt594X8R7yvMZzZW2zjmFLPsLQNHsRuhQeumExHbnUGC9A
|
|
|
|
```
|
|
|
|
|
2021-01-26 23:36:09 -08:00
|
|
|
Next create the token mint and receiving accounts
|
2020-11-09 17:59:25 -08:00
|
|
|
[as previously described](#example-creating-your-own-fungible-token)
|
|
|
|
```
|
|
|
|
$ spl-token create-token
|
|
|
|
Creating token 4VNVRJetwapjwYU8jf4qPgaCeD76wyz8DuNj8yMCQ62o
|
|
|
|
Signature: 3n6zmw3hS5Hyo5duuhnNvwjAbjzC42uzCA3TTsrgr9htUonzDUXdK1d8b8J77XoeSherqWQM8mD8E1TMYCpksS2r
|
|
|
|
|
|
|
|
$ spl-token create-account 4VNVRJetwapjwYU8jf4qPgaCeD76wyz8DuNj8yMCQ62o
|
|
|
|
Creating account EX8zyi2ZQUuoYtXd4MKmyHYLTjqFdWeuoTHcsTdJcKHC
|
|
|
|
Signature: 5mVes7wjE7avuFqzrmSCWneKBQyPAjasCLYZPNSkmqmk2YFosYWAP9hYSiZ7b7NKpV866x5gwyKbbppX3d8PcE9s
|
|
|
|
```
|
|
|
|
|
|
|
|
Then set the mint account's minting authority to the multisig account
|
|
|
|
```
|
|
|
|
$ spl-token authorize 4VNVRJetwapjwYU8jf4qPgaCeD76wyz8DuNj8yMCQ62o mint 46ed77fd4WTN144q62BwjU2B3ogX3Xmmc8PT5Z3Xc2re
|
|
|
|
Updating 4VNVRJetwapjwYU8jf4qPgaCeD76wyz8DuNj8yMCQ62o
|
|
|
|
Current mint authority: 5hbZyJ3KRuFvdy5QBxvE9KwK17hzkAUkQHZTxPbiWffE
|
|
|
|
New mint authority: 46ed77fd4WTN144q62BwjU2B3ogX3Xmmc8PT5Z3Xc2re
|
|
|
|
Signature: yy7dJiTx1t7jvLPCRX5RQWxNRNtFwvARSfbMJG94QKEiNS4uZcp3GhhjnMgZ1CaWMWe4jVEMy9zQBoUhzomMaxC
|
|
|
|
```
|
|
|
|
|
|
|
|
To demonstrate that the mint account is now under control of the multisig
|
2020-11-09 20:18:58 -08:00
|
|
|
account, attempting to mint with one multisig signer fails
|
2020-11-09 17:59:25 -08:00
|
|
|
```
|
|
|
|
$ spl-token mint 4VNVRJetwapjwYU8jf4qPgaCeD76wyz8DuNj8yMCQ62o 1 EX8zyi2ZQUuoYtXd4MKmyHYLTjqFdWeuoTHcsTdJcKHC \
|
|
|
|
--owner 46ed77fd4WTN144q62BwjU2B3ogX3Xmmc8PT5Z3Xc2re \
|
|
|
|
--multisig-signer signer-1.json
|
|
|
|
Minting 1 tokens
|
|
|
|
Token: 4VNVRJetwapjwYU8jf4qPgaCeD76wyz8DuNj8yMCQ62o
|
|
|
|
Recipient: EX8zyi2ZQUuoYtXd4MKmyHYLTjqFdWeuoTHcsTdJcKHC
|
|
|
|
RPC response error -32002: Transaction simulation failed: Error processing Instruction 0: missing required signature for instruction
|
|
|
|
```
|
|
|
|
|
2020-11-09 20:18:58 -08:00
|
|
|
But repeating with a second multisig signer, succeeds
|
2020-11-09 17:59:25 -08:00
|
|
|
```
|
|
|
|
spl-token mint 4VNVRJetwapjwYU8jf4qPgaCeD76wyz8DuNj8yMCQ62o 1 EX8zyi2ZQUuoYtXd4MKmyHYLTjqFdWeuoTHcsTdJcKHC \
|
|
|
|
--owner 46ed77fd4WTN144q62BwjU2B3ogX3Xmmc8PT5Z3Xc2re \
|
|
|
|
--multisig-signer signer-1.json \
|
|
|
|
--multisig-signer signer-2.json
|
|
|
|
Minting 1 tokens
|
|
|
|
Token: 4VNVRJetwapjwYU8jf4qPgaCeD76wyz8DuNj8yMCQ62o
|
|
|
|
Recipient: EX8zyi2ZQUuoYtXd4MKmyHYLTjqFdWeuoTHcsTdJcKHC
|
|
|
|
Signature: 2ubqWqZb3ooDuc8FLaBkqZwzguhtMgQpgMAHhKsWcUzjy61qtJ7cZ1bfmYktKUfnbMYWTC1S8zdKgU6m4THsgspT
|
|
|
|
```
|
|
|
|
|
|
|
|
### Example: Mint with multisig authority and offline signers
|
|
|
|
|
|
|
|
This example builds off of the [online mint with multisig](#example-mint-with-multisig-authority)
|
2020-11-09 19:23:47 -08:00
|
|
|
example. Be sure to familiarize yourself with that process, [offline signing](https://docs.solana.com/offline-signing),
|
2020-11-09 17:59:25 -08:00
|
|
|
and the [durable nonce](https://docs.solana.com/offline-signing/durable-nonce)
|
|
|
|
feature before continuing.
|
|
|
|
|
2020-11-09 19:47:55 -08:00
|
|
|
This example will use the same mint account, token account, multisig account,
|
|
|
|
and multisig signer-set keypair filenames as the online example.
|
2020-11-09 17:59:25 -08:00
|
|
|
|
|
|
|
A nonce account at `Fjyud2VXixk2vCs4DkBpfpsq48d81rbEzh6deKt7WvPj` will be used
|
|
|
|
```
|
|
|
|
$ solana nonce-account Fjyud2VXixk2vCs4DkBpfpsq48d81rbEzh6deKt7WvPj
|
|
|
|
Balance: 0.01 SOL
|
|
|
|
Minimum Balance Required: 0.00144768 SOL
|
2020-11-09 19:23:47 -08:00
|
|
|
Nonce blockhash: 6DPt2TfFBG7sR4Hqu16fbMXPj8ddHKkbU4Y3EEEWrC2E
|
2020-11-09 17:59:25 -08:00
|
|
|
Fee: 5000 lamports per signature
|
|
|
|
Authority: 5hbZyJ3KRuFvdy5QBxvE9KwK17hzkAUkQHZTxPbiWffE
|
|
|
|
```
|
|
|
|
|
2020-11-09 19:23:47 -08:00
|
|
|
For the fee-payer and nonce-authority roles, a local hot wallet at
|
|
|
|
`5hbZyJ3KRuFvdy5QBxvE9KwK17hzkAUkQHZTxPbiWffE` will be used.
|
2020-11-09 17:59:25 -08:00
|
|
|
|
|
|
|
First a template command is built by specifying all signers by their public
|
|
|
|
key. Upon running this command, all signers will be listed as "Absent Signers"
|
|
|
|
in the output. This command will be run by each offline signer to generate the
|
|
|
|
corresponding signature.
|
|
|
|
|
2020-11-09 19:23:47 -08:00
|
|
|
NOTE: The argument to the `--blockhash` parameter is the "Nonce blockhash:" field from
|
2020-11-09 17:59:25 -08:00
|
|
|
the designated durable nonce account.
|
|
|
|
|
|
|
|
```
|
|
|
|
$ spl-token mint 4VNVRJetwapjwYU8jf4qPgaCeD76wyz8DuNj8yMCQ62o 1 EX8zyi2ZQUuoYtXd4MKmyHYLTjqFdWeuoTHcsTdJcKHC \
|
|
|
|
--owner 46ed77fd4WTN144q62BwjU2B3ogX3Xmmc8PT5Z3Xc2re \
|
|
|
|
--multisig-signer BzWpkuRrwXHq4SSSFHa8FJf6DRQy4TaeoXnkA89vTgHZ \
|
|
|
|
--multisig-signer DhkUfKgfZ8CF6PAGKwdABRL1VqkeNrTSRx8LZfpPFVNY \
|
|
|
|
--blockhash 6DPt2TfFBG7sR4Hqu16fbMXPj8ddHKkbU4Y3EEEWrC2E \
|
|
|
|
--fee-payer 5hbZyJ3KRuFvdy5QBxvE9KwK17hzkAUkQHZTxPbiWffE \
|
|
|
|
--nonce Fjyud2VXixk2vCs4DkBpfpsq48d81rbEzh6deKt7WvPj \
|
|
|
|
--nonce-authority 5hbZyJ3KRuFvdy5QBxvE9KwK17hzkAUkQHZTxPbiWffE \
|
|
|
|
--sign-only \
|
|
|
|
--mint-decimals 9
|
|
|
|
Minting 1 tokens
|
|
|
|
Token: 4VNVRJetwapjwYU8jf4qPgaCeD76wyz8DuNj8yMCQ62o
|
|
|
|
Recipient: EX8zyi2ZQUuoYtXd4MKmyHYLTjqFdWeuoTHcsTdJcKHC
|
|
|
|
|
|
|
|
Blockhash: 6DPt2TfFBG7sR4Hqu16fbMXPj8ddHKkbU4Y3EEEWrC2E
|
|
|
|
Absent Signers (Pubkey):
|
|
|
|
5hbZyJ3KRuFvdy5QBxvE9KwK17hzkAUkQHZTxPbiWffE
|
|
|
|
BzWpkuRrwXHq4SSSFHa8FJf6DRQy4TaeoXnkA89vTgHZ
|
|
|
|
DhkUfKgfZ8CF6PAGKwdABRL1VqkeNrTSRx8LZfpPFVNY
|
|
|
|
```
|
|
|
|
|
|
|
|
Next each offline signer executes the template command, replacing each instance
|
|
|
|
of their public key with the corresponding keypair.
|
|
|
|
```
|
|
|
|
spl-token mint 4VNVRJetwapjwYU8jf4qPgaCeD76wyz8DuNj8yMCQ62o 1 EX8zyi2ZQUuoYtXd4MKmyHYLTjqFdWeuoTHcsTdJcKHC \
|
|
|
|
--owner 46ed77fd4WTN144q62BwjU2B3ogX3Xmmc8PT5Z3Xc2re \
|
|
|
|
--multisig-signer signer-1.json \
|
|
|
|
--multisig-signer DhkUfKgfZ8CF6PAGKwdABRL1VqkeNrTSRx8LZfpPFVNY \
|
|
|
|
--blockhash 6DPt2TfFBG7sR4Hqu16fbMXPj8ddHKkbU4Y3EEEWrC2E \
|
|
|
|
--fee-payer 5hbZyJ3KRuFvdy5QBxvE9KwK17hzkAUkQHZTxPbiWffE \
|
|
|
|
--nonce Fjyud2VXixk2vCs4DkBpfpsq48d81rbEzh6deKt7WvPj \
|
|
|
|
--nonce-authority 5hbZyJ3KRuFvdy5QBxvE9KwK17hzkAUkQHZTxPbiWffE \
|
|
|
|
--sign-only \
|
|
|
|
--mint-decimals 9
|
|
|
|
Minting 1 tokens
|
|
|
|
Token: 4VNVRJetwapjwYU8jf4qPgaCeD76wyz8DuNj8yMCQ62o
|
|
|
|
Recipient: EX8zyi2ZQUuoYtXd4MKmyHYLTjqFdWeuoTHcsTdJcKHC
|
|
|
|
|
|
|
|
Blockhash: 6DPt2TfFBG7sR4Hqu16fbMXPj8ddHKkbU4Y3EEEWrC2E
|
|
|
|
Signers (Pubkey=Signature):
|
|
|
|
BzWpkuRrwXHq4SSSFHa8FJf6DRQy4TaeoXnkA89vTgHZ=2QVah9XtvPAuhDB2QwE7gNaY962DhrGP6uy9zeN4sTWvY2xDUUzce6zkQeuT3xg44wsgtUw2H5Rf8pEArPSzJvHX
|
|
|
|
Absent Signers (Pubkey):
|
|
|
|
5hbZyJ3KRuFvdy5QBxvE9KwK17hzkAUkQHZTxPbiWffE
|
|
|
|
DhkUfKgfZ8CF6PAGKwdABRL1VqkeNrTSRx8LZfpPFVNY
|
|
|
|
```
|
|
|
|
|
|
|
|
```
|
|
|
|
$ spl-token mint 4VNVRJetwapjwYU8jf4qPgaCeD76wyz8DuNj8yMCQ62o 1 EX8zyi2ZQUuoYtXd4MKmyHYLTjqFdWeuoTHcsTdJcKHC \
|
|
|
|
--owner 46ed77fd4WTN144q62BwjU2B3ogX3Xmmc8PT5Z3Xc2re \
|
|
|
|
--multisig-signer BzWpkuRrwXHq4SSSFHa8FJf6DRQy4TaeoXnkA89vTgHZ \
|
|
|
|
--multisig-signer signer-2.json \
|
|
|
|
--blockhash 6DPt2TfFBG7sR4Hqu16fbMXPj8ddHKkbU4Y3EEEWrC2E \
|
|
|
|
--fee-payer 5hbZyJ3KRuFvdy5QBxvE9KwK17hzkAUkQHZTxPbiWffE \
|
|
|
|
--nonce Fjyud2VXixk2vCs4DkBpfpsq48d81rbEzh6deKt7WvPj \
|
|
|
|
--nonce-authority 5hbZyJ3KRuFvdy5QBxvE9KwK17hzkAUkQHZTxPbiWffE \
|
|
|
|
--sign-only \
|
|
|
|
--mint-decimals 9
|
|
|
|
Minting 1 tokens
|
|
|
|
Token: 4VNVRJetwapjwYU8jf4qPgaCeD76wyz8DuNj8yMCQ62o
|
|
|
|
Recipient: EX8zyi2ZQUuoYtXd4MKmyHYLTjqFdWeuoTHcsTdJcKHC
|
|
|
|
|
|
|
|
Blockhash: 6DPt2TfFBG7sR4Hqu16fbMXPj8ddHKkbU4Y3EEEWrC2E
|
|
|
|
Signers (Pubkey=Signature):
|
|
|
|
DhkUfKgfZ8CF6PAGKwdABRL1VqkeNrTSRx8LZfpPFVNY=2brZbTiCfyVYSCp6vZE3p7qCDeFf3z1JFmJHPBrz8SnWSDZPjbpjsW2kxFHkktTNkhES3y6UULqS4eaWztLW7FrU
|
|
|
|
Absent Signers (Pubkey):
|
|
|
|
5hbZyJ3KRuFvdy5QBxvE9KwK17hzkAUkQHZTxPbiWffE
|
|
|
|
BzWpkuRrwXHq4SSSFHa8FJf6DRQy4TaeoXnkA89vTgHZ
|
|
|
|
```
|
|
|
|
|
|
|
|
Finally, the offline signers communicate the `Pubkey=Signature` pair from the
|
|
|
|
output of their command to the party who will broadcast the transaction to the
|
|
|
|
cluster. The broadcasting party then runs the template command after modifying
|
|
|
|
it as follows:
|
|
|
|
1. Replaces any corresponding public keys with their keypair (`--fee-payer ...`
|
|
|
|
and `--nonce-authority ...` in this example)
|
|
|
|
1. Removes the `--sign-only` argument, and in the case of the `mint` subcommand,
|
|
|
|
the `--mint-decimals ...` argument as it will be queried from the cluster
|
|
|
|
1. Adds the offline signatures to the template command via the `--signer` argument
|
|
|
|
```
|
|
|
|
$ spl-token mint 4VNVRJetwapjwYU8jf4qPgaCeD76wyz8DuNj8yMCQ62o 1 EX8zyi2ZQUuoYtXd4MKmyHYLTjqFdWeuoTHcsTdJcKHC \
|
|
|
|
--owner 46ed77fd4WTN144q62BwjU2B3ogX3Xmmc8PT5Z3Xc2re \
|
|
|
|
--multisig-signer BzWpkuRrwXHq4SSSFHa8FJf6DRQy4TaeoXnkA89vTgHZ \
|
|
|
|
--multisig-signer DhkUfKgfZ8CF6PAGKwdABRL1VqkeNrTSRx8LZfpPFVNY \
|
|
|
|
--blockhash 6DPt2TfFBG7sR4Hqu16fbMXPj8ddHKkbU4Y3EEEWrC2E \
|
|
|
|
--fee-payer hot-wallet.json \
|
|
|
|
--nonce Fjyud2VXixk2vCs4DkBpfpsq48d81rbEzh6deKt7WvPj \
|
|
|
|
--nonce-authority hot-wallet.json \
|
|
|
|
--signer BzWpkuRrwXHq4SSSFHa8FJf6DRQy4TaeoXnkA89vTgHZ=2QVah9XtvPAuhDB2QwE7gNaY962DhrGP6uy9zeN4sTWvY2xDUUzce6zkQeuT3xg44wsgtUw2H5Rf8pEArPSzJvHX \
|
|
|
|
--signer DhkUfKgfZ8CF6PAGKwdABRL1VqkeNrTSRx8LZfpPFVNY=2brZbTiCfyVYSCp6vZE3p7qCDeFf3z1JFmJHPBrz8SnWSDZPjbpjsW2kxFHkktTNkhES3y6UULqS4eaWztLW7FrU
|
|
|
|
Minting 1 tokens
|
|
|
|
Token: 4VNVRJetwapjwYU8jf4qPgaCeD76wyz8DuNj8yMCQ62o
|
|
|
|
Recipient: EX8zyi2ZQUuoYtXd4MKmyHYLTjqFdWeuoTHcsTdJcKHC
|
|
|
|
Signature: 2AhZXVPDBVBxTQLJohyH1wAhkkSuxRiYKomSSXtwhPL9AdF3wmhrrJGD7WgvZjBPLZUFqWrockzPp9S3fvzbgicy
|
|
|
|
```
|
|
|
|
|
2020-10-14 23:25:53 -07:00
|
|
|
## JSON RPC methods
|
|
|
|
|
|
|
|
There is a rich set of JSON RPC methods available for use with SPL Token:
|
|
|
|
* `getTokenAccountBalance`
|
|
|
|
* `getTokenAccountsByDelegate`
|
|
|
|
* `getTokenAccountsByOwner`
|
|
|
|
* `getTokenLargestAccounts`
|
|
|
|
* `getTokenSupply`
|
|
|
|
|
|
|
|
See https://docs.solana.com/apps/jsonrpc-api for more details.
|
|
|
|
|
|
|
|
Additionally the versatile `getProgramAcccounts` JSON RPC method can be employed in various ways to fetch SPL Token accounts of interest.
|
|
|
|
|
|
|
|
### Finding all token accounts for a specific mint
|
|
|
|
|
|
|
|
To find all token accounts for the `TESTpKgj42ya3st2SQTKiANjTBmncQSCqLAZGcSPLGM` mint:
|
|
|
|
```
|
|
|
|
curl http://api.mainnet-beta.solana.com -X POST -H "Content-Type: application/json" -d '
|
|
|
|
{
|
|
|
|
"jsonrpc": "2.0",
|
|
|
|
"id": 1,
|
|
|
|
"method": "getProgramAccounts",
|
|
|
|
"params": [
|
|
|
|
"TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA",
|
|
|
|
{
|
|
|
|
"encoding": "jsonParsed",
|
|
|
|
"filters": [
|
|
|
|
{
|
|
|
|
"dataSize": 165
|
|
|
|
},
|
|
|
|
{
|
|
|
|
"memcmp": {
|
|
|
|
"offset": 0,
|
|
|
|
"bytes": "TESTpKgj42ya3st2SQTKiANjTBmncQSCqLAZGcSPLGM"
|
|
|
|
}
|
|
|
|
}
|
|
|
|
]
|
|
|
|
}
|
|
|
|
]
|
|
|
|
}
|
|
|
|
'
|
|
|
|
```
|
|
|
|
|
|
|
|
The `"dataSize": 165` filter selects all [Token
|
|
|
|
Acccount](https://github.com/solana-labs/solana-program-library/blob/08d9999f997a8bf38719679be9d572f119d0d960/token/program/src/state.rs#L86-L106)s,
|
|
|
|
and then the `"memcmp": ...` filter selects based on the
|
|
|
|
[mint](https://github.com/solana-labs/solana-program-library/blob/08d9999f997a8bf38719679be9d572f119d0d960/token/program/src/state.rs#L88)
|
|
|
|
address within each token account.
|
|
|
|
|
|
|
|
### Finding all token accounts for a wallet
|
|
|
|
|
|
|
|
Find all token accounts owned by the `vines1vzrYbzLMRdu58ou5XTby4qAqVRLmqo36NKPTg` user:
|
|
|
|
```
|
|
|
|
curl http://api.mainnet-beta.solana.com -X POST -H "Content-Type: application/json" -d '
|
|
|
|
{
|
|
|
|
"jsonrpc": "2.0",
|
|
|
|
"id": 1,
|
|
|
|
"method": "getProgramAccounts",
|
|
|
|
"params": [
|
|
|
|
"TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA",
|
|
|
|
{
|
|
|
|
"encoding": "jsonParsed",
|
|
|
|
"filters": [
|
|
|
|
{
|
|
|
|
"dataSize": 165
|
|
|
|
},
|
|
|
|
{
|
|
|
|
"memcmp": {
|
|
|
|
"offset": 32,
|
|
|
|
"bytes": "vines1vzrYbzLMRdu58ou5XTby4qAqVRLmqo36NKPTg"
|
|
|
|
}
|
|
|
|
}
|
|
|
|
]
|
|
|
|
}
|
|
|
|
]
|
|
|
|
}
|
|
|
|
```
|
|
|
|
|
|
|
|
The `"dataSize": 165` filter selects all [Token
|
|
|
|
Acccount](https://github.com/solana-labs/solana-program-library/blob/08d9999f997a8bf38719679be9d572f119d0d960/token/program/src/state.rs#L86-L106)s,
|
|
|
|
and then the `"memcmp": ...` filter selects based on the
|
|
|
|
[owner](https://github.com/solana-labs/solana-program-library/blob/08d9999f997a8bf38719679be9d572f119d0d960/token/program/src/state.rs#L90)
|
|
|
|
address within each token account.
|
|
|
|
|
2020-07-28 11:00:37 -07:00
|
|
|
## Operational overview
|
|
|
|
|
|
|
|
### Creating a new token type
|
|
|
|
|
|
|
|
A new token type can be created by initializing a new Mint with the
|
2020-08-28 17:16:55 -07:00
|
|
|
`InitializeMint` instruction. The Mint is used to create or "mint" new tokens,
|
2020-08-03 07:31:23 -07:00
|
|
|
and these tokens are stored in Accounts. A Mint is associated with each
|
2020-07-28 11:00:37 -07:00
|
|
|
Account, which means that the total supply of a particular token type is equal
|
|
|
|
to the balances of all the associated Accounts.
|
|
|
|
|
|
|
|
It's important to note that the `InitializeMint` instruction does not require
|
2020-08-03 07:31:23 -07:00
|
|
|
the Solana account being initialized also be a signer. The `InitializeMint`
|
2020-07-28 11:00:37 -07:00
|
|
|
instruction should be atomically processed with the system instruction that
|
|
|
|
creates the Solana account by including both instructions in the same
|
|
|
|
transaction.
|
|
|
|
|
2020-08-28 17:16:55 -07:00
|
|
|
Once a Mint is initialized, the `mint_authority` can create new tokens using the
|
|
|
|
`MintTo` instruction. As long as a Mint contains a valid `mint_authority`, the
|
|
|
|
Mint is considered to have a non-fixed supply, and the `mint_authority` can
|
|
|
|
create new tokens with the `MintTo` instruction at any time. The `SetAuthority`
|
|
|
|
instruction can be used to irreversibly set the Mint's authority to `None`,
|
|
|
|
rendering the Mint's supply fixed. No further tokens can ever be Minted.
|
|
|
|
|
|
|
|
Token supply can be reduced at any time by issuing a `Burn` instruction which
|
|
|
|
removes and discards tokens from an Account.
|
|
|
|
|
2020-07-28 11:00:37 -07:00
|
|
|
### Creating accounts
|
|
|
|
|
|
|
|
Accounts hold token balances and are created using the `InitializeAccount`
|
2020-08-03 07:31:23 -07:00
|
|
|
instruction. Each Account has an owner who must be present as a signer in some
|
2020-07-28 11:00:37 -07:00
|
|
|
instructions.
|
|
|
|
|
|
|
|
An Account's owner may transfer ownership of an account to another using the
|
2020-08-28 10:04:37 -07:00
|
|
|
`SetAuthority` instruction.
|
2020-07-28 11:00:37 -07:00
|
|
|
|
|
|
|
It's important to note that the `InitializeAccount` instruction does not require
|
2020-08-03 07:31:23 -07:00
|
|
|
the Solana account being initialized also be a signer. The `InitializeAccount`
|
2020-07-28 11:00:37 -07:00
|
|
|
instruction should be atomically processed with the system instruction that
|
|
|
|
creates the Solana account by including both instructions in the same
|
|
|
|
transaction.
|
|
|
|
|
2020-10-27 19:48:23 -07:00
|
|
|
### Transferring tokens
|
|
|
|
Balances can be transferred between Accounts using the `Transfer` instruction.
|
|
|
|
The owner of the source Account must be present as a signer in the `Transfer`
|
|
|
|
instruction when the source and destination accounts are different.
|
|
|
|
|
|
|
|
It's important to note that when the source and destination of a `Transfer` are
|
|
|
|
the **same**, the `Transfer` will _always_ succeed. Therefore, a successful `Transfer`
|
|
|
|
does not necessarily imply that the involved Accounts were valid SPL Token
|
|
|
|
accounts, that any tokens were moved, or that the source Account was present as
|
|
|
|
a signer. We strongly recommend that developers are careful about checking that
|
|
|
|
the source and destination are **different** before invoking a `Transfer`
|
|
|
|
instruction from within their program.
|
|
|
|
|
2020-07-28 11:00:37 -07:00
|
|
|
### Burning
|
|
|
|
|
|
|
|
The `Burn` instruction decreases an Account's token balance without transferring
|
|
|
|
to another Account, effectively removing the token from circulation permanently.
|
|
|
|
|
2020-10-29 23:17:41 -07:00
|
|
|
There is no other way to reduce supply on chain. This is similar to transferring
|
|
|
|
to an account with unknown private key or destroying a private key. But the act
|
|
|
|
of burning by using `Burn` instructions is more explicit and can be confirmed on
|
|
|
|
chain by any parties.
|
|
|
|
|
|
|
|
Note: there is a method by which a malicious and determined account owner
|
|
|
|
can silently burn their tokens without updating supply on chain by making an
|
|
|
|
account that is removed by rent collection because of [this known issue](#rent-exemption-loophole).
|
|
|
|
|
2020-07-28 11:00:37 -07:00
|
|
|
### Authority delegation
|
|
|
|
|
|
|
|
Account owners may delegate authority over some or all of their token balance
|
2020-08-03 07:31:23 -07:00
|
|
|
using the `Approve` instruction. Delegated authorities may transfer or burn up
|
2020-08-28 17:16:55 -07:00
|
|
|
to the amount they've been delegated. Authority delegation may be revoked by the
|
|
|
|
Account's owner via the `Revoke` instruction.
|
2020-07-28 11:00:37 -07:00
|
|
|
|
|
|
|
### Multisignatures
|
|
|
|
|
2020-08-28 17:16:55 -07:00
|
|
|
M of N multisignatures are supported and can be used in place of Mint
|
|
|
|
authorities or Account owners or delegates. Multisignature authorities must be
|
2020-07-28 11:00:37 -07:00
|
|
|
initialized with the `InitializeMultisig` instruction. Initialization specifies
|
|
|
|
the set of N public keys that are valid and the number M of those N that must be
|
|
|
|
present as instruction signers for the authority to be legitimate.
|
|
|
|
|
|
|
|
It's important to note that the `InitializeMultisig` instruction does not
|
2020-08-03 07:31:23 -07:00
|
|
|
require the Solana account being initialized also be a signer. The
|
2020-07-28 11:00:37 -07:00
|
|
|
`InitializeMultisig` instruction should be atomically processed with the system
|
|
|
|
instruction that creates the Solana account by including both instructions in
|
|
|
|
the same transaction.
|
|
|
|
|
2020-08-28 17:16:55 -07:00
|
|
|
### Freezing accounts
|
|
|
|
|
2020-09-16 16:20:06 -07:00
|
|
|
The Mint may also contain a `freeze_authority` which can be used to issue
|
2020-08-28 17:16:55 -07:00
|
|
|
`FreezeAccount` instructions that will render an Account unusable. Token
|
|
|
|
instructions that include a frozen account will fail until the Account is thawed
|
|
|
|
using the `ThawAccount` instruction. The `SetAuthority` instruction can be used
|
|
|
|
to change a Mint's `freeze_authority`. If a Mint's `freeze_authority` is set to
|
2020-09-16 16:20:06 -07:00
|
|
|
`None` then account freezing and thawing is permanently disabled and all
|
|
|
|
currently frozen accounts will also stay frozen permanently.
|
2020-08-28 17:16:55 -07:00
|
|
|
|
2020-07-28 11:00:37 -07:00
|
|
|
### Wrapping SOL
|
|
|
|
|
2020-08-03 07:31:23 -07:00
|
|
|
The Token Program can be used to wrap native SOL. Doing so allows native SOL to
|
2020-07-28 11:00:37 -07:00
|
|
|
be treated like any other Token program token type and can be useful when being
|
|
|
|
called from other programs that interact with the Token Program's interface.
|
|
|
|
|
|
|
|
Accounts containing wrapped SOL are associated with a specific Mint called the
|
2020-08-03 07:31:23 -07:00
|
|
|
"Native Mint" using the public key
|
2020-08-28 09:12:05 -07:00
|
|
|
`So11111111111111111111111111111111111111112`.
|
2020-07-28 11:00:37 -07:00
|
|
|
|
|
|
|
These accounts have a few unique behaviors
|
2020-08-03 07:31:23 -07:00
|
|
|
|
2020-07-28 11:00:37 -07:00
|
|
|
- `InitializeAccount` sets the balance of the initialized Account to the SOL
|
|
|
|
balance of the Solana account being initialized, resulting in a token balance
|
|
|
|
equal to the SOL balance.
|
|
|
|
- Transfers to and from not only modify the token balance but also transfer an
|
|
|
|
equal amount of SOL from the source account to the destination account.
|
|
|
|
- Burning is not supported
|
|
|
|
- When closing an Account the balance may be non-zero.
|
|
|
|
|
2020-10-14 08:42:15 -07:00
|
|
|
The Native Mint supply will always report 0, regardless of how much SOL is currently wrapped.
|
2020-09-01 18:41:05 -07:00
|
|
|
|
2020-08-28 17:16:55 -07:00
|
|
|
### Rent-exemption
|
|
|
|
|
|
|
|
To ensure a reliable calculation of supply, a consistency valid Mint, and
|
|
|
|
consistently valid Multisig accounts all Solana accounts holding a Account,
|
2020-09-17 17:43:55 -07:00
|
|
|
Mint, or Multisig must contain enough SOL to be considered [rent
|
|
|
|
exempt](https://docs.solana.com/implemented-proposals/rent)
|
2020-08-28 17:16:55 -07:00
|
|
|
|
2020-10-29 23:17:41 -07:00
|
|
|
#### Rent-exemption loophole
|
|
|
|
|
|
|
|
However note that there is currently a loophole to escape from the rent-exemption
|
|
|
|
rule. It is possible to create SPL Token accounts that are not rent exempt by
|
|
|
|
spoofing the Rent sysvar, since
|
|
|
|
[there are insufficient sysvar checks](https://github.com/solana-labs/solana/pull/13175)
|
|
|
|
in the program. This could be abused to burn tokens by transferring tokens to
|
|
|
|
a non-exempt Account that is subsequently rent-collected out of existence.
|
|
|
|
|
2020-07-28 11:00:37 -07:00
|
|
|
### Closing accounts
|
|
|
|
|
2020-08-03 07:31:23 -07:00
|
|
|
An account may be closed using the `CloseAccount` instruction. When closing an
|
2020-07-28 11:00:37 -07:00
|
|
|
Account, all remaining SOL will be transferred to another Solana account
|
2020-08-28 17:16:55 -07:00
|
|
|
(doesn't have to be associated with the Token Program). Non-native Accounts must
|
|
|
|
have a balance of zero to be closed.
|
2020-10-14 08:42:15 -07:00
|
|
|
|
|
|
|
### Non-Fungible tokens
|
|
|
|
An NTF is simply a token type where only a single token has been minted.
|
2020-10-14 20:07:44 -07:00
|
|
|
|
|
|
|
## 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:
|
2020-11-04 10:13:40 -08:00
|
|
|
* SPL Token program: generic program that is used by all SPL Tokens
|
|
|
|
* [SPL Associated Token Account](associated-token-account.md) program: defines
|
|
|
|
the convention and provides the mechanism for mapping the user's wallet
|
|
|
|
address to the associated token accounts they hold.
|
2020-10-14 20:07:44 -07:00
|
|
|
|
|
|
|
### 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.
|
|
|
|
|
2020-11-04 10:13:40 -08:00
|
|
|
For each token mint, the wallet could have multiple token accounts: the
|
2020-10-14 20:07:44 -07:00
|
|
|
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
|
2020-11-04 10:13:40 -08:00
|
|
|
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 behalf.
|
2020-10-14 20:07:44 -07:00
|
|
|
|
|
|
|
### Associated Token Account
|
2020-11-04 10:13:40 -08:00
|
|
|
Before the user can receive tokens, their associated token account must be created
|
2020-10-14 20:07:44 -07:00
|
|
|
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.
|
|
|
|
|
2020-11-04 10:13:40 -08:00
|
|
|
The creation process is described [here](associated-token-account.md#creating-an-associated-token-account).
|
2020-10-14 20:07:44 -07:00
|
|
|
|
2021-01-11 18:04:30 -08:00
|
|
|
It's highly recommended that the wallet create the associated token account for
|
|
|
|
a given SPL Token itself before indicating to the user that they are able to
|
|
|
|
receive that SPL Tokens type (typically done by showing the user their receiving
|
|
|
|
address). A wallet that chooses to not perform this step may limit its user's ability
|
|
|
|
to receive SPL Tokens from other wallets.
|
|
|
|
|
2020-10-14 20:07:44 -07:00
|
|
|
#### Sample "Add Token" workflow
|
2021-01-11 18:04:30 -08:00
|
|
|
The user should first fund their associated token account when they want to
|
|
|
|
receive SPL Tokens of a certain type to:
|
|
|
|
1. Maximize interoperability with other wallet implementations
|
|
|
|
2. Avoid pushing the cost of creating their associated token account on the first sender
|
2020-10-14 20:07:44 -07:00
|
|
|
|
2020-11-04 10:13:40 -08:00
|
|
|
The wallet should provide a UI that allow the users to "add a token".
|
2020-10-14 20:07:44 -07:00
|
|
|
The user selects the kind of token, and is presented with information about how
|
2020-11-04 10:13:40 -08:00
|
|
|
much SOL it will cost to add the token.
|
|
|
|
|
|
|
|
Upon confirmation, the wallet creates the associated token type as the described
|
|
|
|
[here](associated-token-account.md#creating-an-associated-token-account).
|
2020-10-14 20:07:44 -07:00
|
|
|
|
|
|
|
#### Sample "Airdrop campaign" workflow
|
|
|
|
For each recipient wallet addresses, send a transaction containing:
|
2020-11-04 10:13:40 -08:00
|
|
|
1. Create the associated token account on the recipient's behalf.
|
|
|
|
2. Use `TokenInstruction::Transfer` to complete the transfer
|
2020-10-14 20:07:44 -07:00
|
|
|
|
|
|
|
#### Associated Token Account Ownership
|
2020-11-04 10:13:40 -08:00
|
|
|
⚠️ The wallet should never use `TokenInstruction::SetAuthority` to set the
|
2020-10-14 20:07:44 -07:00
|
|
|
`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
|
2020-11-04 10:13:40 -08:00
|
|
|
`spl-token authorize <TOKEN_ADDRESS> owner <USER_ADDRESS>` command. Wallets
|
|
|
|
should be prepared to gracefully manage token accounts that they themselves did
|
|
|
|
not create for the user.
|
2020-10-14 20:07:44 -07:00
|
|
|
|
|
|
|
### 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:
|
2020-11-04 10:13:40 -08:00
|
|
|
1. Derives the associated token account for the recipient
|
2021-01-11 18:04:30 -08:00
|
|
|
1. Fetches the recipient's associated token account over RPC and checks that it exists
|
|
|
|
1. If the recipient's associated token account does not yet exist, the sender
|
|
|
|
wallet should create the recipient's associated token account as described
|
|
|
|
[here](associated-token-account.md#creating-an-associated-token-account).
|
|
|
|
The sender's wallet may choose to inform the user that as a result of account
|
|
|
|
creation the transfer will require more SOL than normal.
|
|
|
|
However a wallet that chooses to not support creating the recipient's
|
|
|
|
associated token account at this time should present a message to the user with enough
|
|
|
|
information to permit them to find a workaround (such as transferring the
|
|
|
|
token through a fully compliant intermediary wallet such as https://sollet.io)
|
|
|
|
to allow the users to accomplish their goal
|
|
|
|
1. Use `TokenInstruction::Transfer` to complete the transfer
|
|
|
|
|
|
|
|
The sender's wallet must not require that the recipient's main wallet address
|
|
|
|
hold a balance before allowing the transfer.
|
2020-10-14 20:07:44 -07:00
|
|
|
|
|
|
|
### 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.
|
2020-11-04 10:13:40 -08:00
|
|
|
|
|
|
|
The `spl-token gc` command provides an example implementation of this cleanup process.
|