Skip to content

Commit 45acd73

Browse files
authored
docs: token guides (#351)
* docs: token guides * chore: docs nextjs * refactor: docs order * fix: code selector
1 parent c4430e5 commit 45acd73

11 files changed

Lines changed: 1773 additions & 582 deletions

File tree

docs/content/docs/guides/index.mdx

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,26 @@ working with SPL tokens on Solana.
2929
Token Extensions program.
3030
</Card>
3131

32+
<Card href="/docs/guides/tokens/mint-tokens" title="Mint Tokens">
33+
Learn how to mint new token supply to a wallet using the mint authority, including automatically
34+
creating the destination's Associated Token Account.
35+
</Card>
36+
37+
<Card href="/docs/guides/tokens/transfer-tokens" title="Transfer Tokens">
38+
Learn how to transfer tokens between wallets using the token authority, including automatically
39+
creating the destination's Associated Token Account.
40+
</Card>
41+
42+
<Card href="/docs/guides/tokens/burn-tokens" title="Burn Tokens">
43+
Learn how to permanently remove tokens from circulation by burning them from a wallet's Associated
44+
Token Account.
45+
</Card>
46+
47+
<Card href="/docs/guides/tokens/get-token-metadata" title="Get Token Metadata">
48+
Learn how to read and parse token metadata from the Solana blockchain, including both legacy Token
49+
Metadata Program accounts and Token Extensions (Token22) inline metadata.
50+
</Card>
51+
3252
</Cards>
3353

3454
## Transactions
Lines changed: 189 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,189 @@
1+
---
2+
title: Burn Tokens
3+
description: Learn how to burn tokens from a wallet using the gill JavaScript library.
4+
---
5+
6+
Burning tokens permanently removes them from circulation by destroying them from a wallet's
7+
Associated Token Account (ATA). The token account `authority` (owner) must sign the transaction to
8+
authorize the burn.
9+
10+
This guide demonstrates how to burn tokens using the
11+
[`gill` package](https://www.npmjs.com/package/gill) with the `getBurnCheckedInstruction` from
12+
`gill/programs`.
13+
14+
## Install gill
15+
16+
Install gill using the core `gill` library:
17+
18+
```package-install
19+
gill
20+
```
21+
22+
import { PackageBadges } from "@/components/package-badges";
23+
24+
<PackageBadges packageName="gill" />
25+
26+
## Create an RPC connection
27+
28+
In order to send transactions and/or fetch data from the Solana blockchain, you will need a client
29+
connection. You can easily create a Solana client connection using the `createSolanaClient()`
30+
function.
31+
32+
The `urlOrMoniker` can be either a Solana network moniker (e.g. `devnet`, `mainnet`, `localnet`) or
33+
a full URL of your RPC provider.
34+
35+
```ts twoslash
36+
import { createSolanaClient } from "gill";
37+
38+
const { rpc, sendAndConfirmTransaction } = createSolanaClient({
39+
urlOrMoniker: "devnet", // `mainnet`, `localnet`, etc
40+
});
41+
```
42+
43+
<Callout title="Public RPC endpoints are subject to rate limits">
44+
Using a Solana moniker will connect to the public RPC endpoints. These are subject to rate limits
45+
and should not be used in production applications. Applications should find their own RPC provider
46+
and the URL provided from them.
47+
</Callout>
48+
49+
## Prepare a Signer
50+
51+
Every Solana transaction requires at least one "signer" to be the fee payer for the transaction.
52+
When burning tokens, the `authority` (the token account owner) must also be a signer to authorize
53+
the burn.
54+
55+
### Load a signer from a local keypair file
56+
57+
For backend scripts and some server environments, you can load a signer from your local filesystem:
58+
59+
```ts twoslash
60+
import { type KeyPairSigner } from "gill";
61+
import { loadKeypairSignerFromFile } from "gill/node";
62+
63+
// This defaults to the file path used by the Solana CLI: `~/.config/solana/id.json`
64+
const signer: KeyPairSigner = await loadKeypairSignerFromFile();
65+
console.log("signer:", signer.address);
66+
```
67+
68+
## Understanding token amounts
69+
70+
When burning tokens, the `amount` you provide is in raw base units, not human-readable units. The
71+
conversion depends on the `decimals` value of your token mint.
72+
73+
For example, if your token has `decimals = 9` (the most common for fungible tokens):
74+
75+
- `1_000_000_000` (1e9) = **1 token**
76+
- `5_000_000_000` (5e9) = **5 tokens**
77+
- `1_000_000` (1e6) = **0.001 tokens**
78+
79+
<Callout title="Decimals matter">
80+
With `decimals = 9`, to burn **100 tokens** you would set `amount` to `100_000_000_000` (100e9).
81+
With `decimals = 6`, to burn **100 tokens** you would set `amount` to `100_000_000` (100e6).
82+
Always check your token's decimals to calculate the correct raw amount.
83+
</Callout>
84+
85+
## Build the burn transaction
86+
87+
To burn tokens, use the `getBurnCheckedInstruction()` from `gill/programs`. This is the recommended
88+
burn instruction because it validates the token's decimals for safety.
89+
90+
First, derive the ATA for the wallet that holds the tokens to burn:
91+
92+
```ts
93+
import { getAssociatedTokenAccountAddress } from "gill/programs";
94+
95+
const ata = await getAssociatedTokenAccountAddress(mint, signer.address);
96+
```
97+
98+
Then create the burn instruction and build the transaction:
99+
100+
```ts
101+
import { createTransaction } from "gill";
102+
import { getBurnCheckedInstruction } from "gill/programs";
103+
104+
const { value: latestBlockhash } = await rpc.getLatestBlockhash().send();
105+
106+
const burnIx = getBurnCheckedInstruction({
107+
account: ata,
108+
mint,
109+
authority: signer,
110+
amount: 1_000_000_000, // 1 token (with decimals=9)
111+
decimals: 9,
112+
});
113+
114+
const transaction = createTransaction({
115+
feePayer: signer,
116+
version: "legacy",
117+
instructions: [burnIx],
118+
latestBlockhash,
119+
});
120+
```
121+
122+
Where `mint` is the address of the token mint and `signer` is the owner of the token account.
123+
124+
## Sign and send the transaction
125+
126+
With your transaction fully created, you can now sign and send it:
127+
128+
```ts
129+
import {
130+
signTransactionMessageWithSigners,
131+
getSignatureFromTransaction,
132+
getExplorerLink,
133+
} from "gill";
134+
135+
const signedTransaction = await signTransactionMessageWithSigners(transaction);
136+
137+
console.log(
138+
"Explorer:",
139+
getExplorerLink({
140+
cluster: "devnet",
141+
transaction: getSignatureFromTransaction(signedTransaction),
142+
}),
143+
);
144+
```
145+
146+
If your transaction is already fully signed or has all signers available, you can send and confirm
147+
it on the blockchain:
148+
149+
```ts
150+
await sendAndConfirmTransaction(signedTransaction);
151+
```
152+
153+
<Callout title="Pro Tip">
154+
If you do not need to know the transaction signature prior to sending the transaction AND all
155+
signers are attached to the transaction, you can pass a fully signable transaction to the
156+
`sendAndConfirmTransaction()` function initialized from `createSolanaClient()`. It will then
157+
perform the signing operations prior to sending and confirming.
158+
</Callout>
159+
160+
## Using Token Extensions (Token22)
161+
162+
If your token was created with the Token Extensions program (Token22), pass the `programAddress` in
163+
the config to `getBurnCheckedInstruction()` and the `tokenProgram` to
164+
`getAssociatedTokenAccountAddress()`:
165+
166+
```ts
167+
import {
168+
TOKEN_2022_PROGRAM_ADDRESS,
169+
getAssociatedTokenAccountAddress,
170+
getBurnCheckedInstruction,
171+
} from "gill/programs";
172+
173+
const ata = await getAssociatedTokenAccountAddress(
174+
mint,
175+
signer.address,
176+
TOKEN_2022_PROGRAM_ADDRESS,
177+
);
178+
179+
const burnIx = getBurnCheckedInstruction(
180+
{
181+
account: ata,
182+
mint,
183+
authority: signer,
184+
amount: 1_000_000_000,
185+
decimals: 9,
186+
},
187+
{ programAddress: TOKEN_2022_PROGRAM_ADDRESS },
188+
);
189+
```
Lines changed: 176 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,176 @@
1+
---
2+
title: Get Token Metadata
3+
description:
4+
Learn how to read and parse token metadata from the Solana blockchain using the gill JavaScript
5+
library.
6+
---
7+
8+
Every Solana token can have metadata associated with it, including a name, symbol, URI pointing to
9+
off-chain data, and more. The approach to reading this metadata depends on which token program was
10+
used to create the token:
11+
12+
- **Token Metadata Program (legacy):** Metadata lives in a separate PDA account managed by the
13+
Metaplex Token Metadata program.
14+
- **Token Extensions (Token22):** Metadata is stored inline on the mint account as an extension.
15+
16+
This guide demonstrates how to fetch token metadata using the
17+
[`gill` package](https://www.npmjs.com/package/gill), covering both approaches.
18+
19+
## Install gill
20+
21+
Install gill using the core `gill` library:
22+
23+
```package-install
24+
gill
25+
```
26+
27+
import { PackageBadges } from "@/components/package-badges";
28+
29+
<PackageBadges packageName="gill" />
30+
31+
## Create an RPC connection
32+
33+
In order to fetch data from the Solana blockchain, you will need a client connection. You can easily
34+
create a Solana client connection using the `createSolanaClient()` function.
35+
36+
The `urlOrMoniker` can be either a Solana network moniker (e.g. `devnet`, `mainnet`, `localnet`) or
37+
a full URL of your RPC provider.
38+
39+
```ts twoslash
40+
import { createSolanaClient } from "gill";
41+
42+
const { rpc } = createSolanaClient({
43+
urlOrMoniker: "mainnet", // `devnet`, `localnet`, etc
44+
});
45+
```
46+
47+
<Callout title="Public RPC endpoints are subject to rate limits">
48+
Using a Solana moniker will connect to the public RPC endpoints. These are subject to rate limits
49+
and should not be used in production applications. Applications should find their own RPC provider
50+
and the URL provided from them.
51+
</Callout>
52+
53+
## Token Metadata Program (legacy tokens)
54+
55+
For tokens created with the original Token Program and the Metaplex Token Metadata program, metadata
56+
is stored in a separate PDA account derived from the token's mint address.
57+
58+
### Derive the metadata address
59+
60+
Use `getTokenMetadataAddress()` from `gill/programs` to derive the metadata PDA from the mint
61+
address:
62+
63+
```ts
64+
import { address } from "gill";
65+
import { getTokenMetadataAddress } from "gill/programs";
66+
67+
const mint = address("So11111111111111111111111111111111111111112");
68+
69+
const metadataAddress = await getTokenMetadataAddress(mint);
70+
```
71+
72+
### Fetch the metadata account
73+
74+
Use `fetchMetadata()` from `gill/programs` to fetch and decode the metadata account:
75+
76+
```ts
77+
import { fetchMetadata } from "gill/programs";
78+
79+
const metadata = await fetchMetadata(rpc, metadataAddress);
80+
81+
console.log("Name:", metadata.data.name);
82+
console.log("Symbol:", metadata.data.symbol);
83+
console.log("URI:", metadata.data.uri);
84+
console.log("Seller fee:", metadata.data.sellerFeeBasisPoints);
85+
console.log("Creators:", metadata.data.creators);
86+
```
87+
88+
The metadata account also contains additional fields:
89+
90+
```ts
91+
console.log("Update authority:", metadata.updateAuthority);
92+
console.log("Is mutable:", metadata.isMutable);
93+
console.log("Collection:", metadata.collection);
94+
console.log("Token standard:", metadata.tokenStandard);
95+
```
96+
97+
<Callout title="Use fetchMaybeMetadata for optional lookups">
98+
If you are unsure whether a metadata account exists for a given mint, use `fetchMaybeMetadata()`
99+
instead. It returns `null` if the account does not exist, rather than throwing an error.
100+
</Callout>
101+
102+
```ts
103+
import { fetchMaybeMetadata } from "gill/programs";
104+
105+
const maybeMeta = await fetchMaybeMetadata(rpc, metadataAddress);
106+
107+
if (maybeMeta.exists) {
108+
console.log("Name:", maybeMeta.data.name);
109+
} else {
110+
console.log("No metadata account found");
111+
}
112+
```
113+
114+
## Token Extensions (Token22)
115+
116+
For tokens created with the Token Extensions program (Token22), metadata can be stored directly on
117+
the mint account as an extension. No separate PDA is needed.
118+
119+
### Fetch the mint account
120+
121+
Use `fetchMint()` from `gill/programs` to fetch the mint account, which includes any extensions:
122+
123+
```ts
124+
import { address } from "gill";
125+
import { fetchMint } from "gill/programs";
126+
127+
const mint = address("your-token22-mint-address");
128+
129+
const mintAccount = await fetchMint(rpc, mint);
130+
```
131+
132+
### Find the TokenMetadata extension
133+
134+
The mint account's `extensions` array contains all enabled extensions. Use the `isExtension()` type
135+
guard from `gill/programs` to find the `TokenMetadata` extension:
136+
137+
```ts
138+
import { fetchMint, isExtension } from "gill/programs";
139+
140+
const mintAccount = await fetchMint(rpc, mint);
141+
const tokenMetadata = mintAccount.data.extensions?.find((ext) => isExtension("TokenMetadata", ext));
142+
143+
if (tokenMetadata && tokenMetadata.__kind === "TokenMetadata") {
144+
console.log("Name:", tokenMetadata.name);
145+
console.log("Symbol:", tokenMetadata.symbol);
146+
console.log("URI:", tokenMetadata.uri);
147+
console.log("Update authority:", tokenMetadata.updateAuthority);
148+
console.log("Additional metadata:", tokenMetadata.additionalMetadata);
149+
}
150+
```
151+
152+
<Callout title="Token22 metadata differences">
153+
Token Extensions metadata includes an `additionalMetadata` field, which is a `Map` of arbitrary
154+
key-value string pairs. This is different from the legacy Token Metadata Program, which uses
155+
structured fields like `creators` and `collection`.
156+
</Callout>
157+
158+
## Fetching the off-chain JSON metadata
159+
160+
Both the legacy Token Metadata Program and Token Extensions store a `uri` field that points to an
161+
off-chain JSON file. This JSON typically contains the token's image, description, attributes, and
162+
other rich metadata.
163+
164+
Fetching this data is a standard HTTP request, not a Solana RPC call:
165+
166+
```ts
167+
const response = await fetch(uri);
168+
const offChainMetadata = await response.json();
169+
170+
console.log("Image:", offChainMetadata.image);
171+
console.log("Description:", offChainMetadata.description);
172+
```
173+
174+
The off-chain JSON format generally follows the
175+
[Metaplex Token Metadata Standard](https://developers.metaplex.com/token-metadata/token-standard),
176+
which includes fields like `image`, `description`, `attributes`, `external_url`, and more.

0 commit comments

Comments
 (0)