-
Notifications
You must be signed in to change notification settings - Fork 283
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
docs(token 2022): guide for permanent delegate extension (#21)
* docs(token 2022): guide for permanent delegate extension * docs(token 2022): add missing section * Add Solana Playground * Revision and print SolanaFM links * proofread * refactor: changed dir * refactor: editorial changes --------- Co-authored-by: John <[email protected]> Co-authored-by: nickfrosty <[email protected]>
- Loading branch information
1 parent
7adc252
commit 22b41ee
Showing
1 changed file
with
349 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,349 @@ | ||
--- | ||
date: Dec 06, 2023 | ||
seoTitle: "Token Extensions: Permanent Delegate" | ||
title: How to use the Permanent Delegate extension | ||
description: | ||
"Token Extensions allow you to specify a permanent account delegate for a mint | ||
with unlimited delegate privileges over any account associated with that mint, | ||
including burning or transferring any amount of tokens." | ||
keywords: | ||
- token 2022 | ||
- token extensions | ||
- token program | ||
difficulty: beginner | ||
tags: | ||
- token 2022 | ||
- token extensions | ||
--- | ||
|
||
The `PermanentDelegate` extension allows for a designated Permanent Delegate for | ||
a Mint Account. This permanent delegate has unrestricted delegate privileges | ||
over all Token Accounts for that mint, enabling them to burn or transfer tokens | ||
without limitation. | ||
|
||
In this guide, we'll walk through an example of creating a token with the | ||
Permanent Delegate using Solana Playground. Here is the | ||
[final script](https://beta.solpg.io/6570a56bfb53fa325bfd0c4b) that this guide | ||
will walkthrough. | ||
|
||
## Understanding the Implications | ||
|
||
This is a very powerful feature, and its implications have to be clearly stated | ||
for both users and app developers. | ||
|
||
<Callout type="warning"> | ||
The Permanent Delegate is effectively a global owner of all Token Accounts for | ||
the mint. Due to the unlimited powers of the Permanent Delegate, if the | ||
delegate's keys are compromised, an attacker will have complete control over all | ||
Token Accounts for that mint. | ||
</Callout> | ||
|
||
## Getting Started | ||
|
||
Start by opening this Solana Playground | ||
[link](https://beta.solpg.io/656e19acfb53fa325bfd0c46) with the following | ||
starter code. | ||
|
||
```javascript | ||
// Client | ||
console.log("My address:", pg.wallet.publicKey.toString()); | ||
const balance = await pg.connection.getBalance(pg.wallet.publicKey); | ||
console.log(`My balance: ${balance / web3.LAMPORTS_PER_SOL} SOL`); | ||
``` | ||
|
||
If it is your first time using Solana Playground, you'll first need to create a | ||
Playground Wallet and fund the wallet with devnet SOL. | ||
|
||
To get devnet SOL, run the `solana airdrop` command in the Playground's | ||
terminal, or visit this [devnet faucet](https://faucet.solana.com/). | ||
|
||
``` | ||
solana airdrop 5 | ||
``` | ||
|
||
Once you've created and funded the Playground wallet, click the "Run" button to | ||
run the starter code. | ||
|
||
## Add Dependencies | ||
|
||
Lets start by setting up our script. We'll be using the `@solana/web3.js` and | ||
`@solana/spl-token` libraries. | ||
|
||
Replace the starter code with the following: | ||
|
||
```javascript | ||
import { | ||
Connection, | ||
Keypair, | ||
SystemProgram, | ||
Transaction, | ||
clusterApiUrl, | ||
sendAndConfirmTransaction, | ||
} from "@solana/web3.js"; | ||
import { | ||
ExtensionType, | ||
TOKEN_2022_PROGRAM_ID, | ||
createInitializePermanentDelegateInstruction, | ||
createInitializeMintInstruction, | ||
getMintLen, | ||
createAccount, | ||
mintTo, | ||
transferChecked, | ||
burnChecked, | ||
} from "@solana/spl-token"; | ||
|
||
// Playground wallet | ||
const payer = pg.wallet.keypair; | ||
|
||
// Connection to devnet cluster | ||
const connection = new Connection(clusterApiUrl("devnet"), "confirmed"); | ||
|
||
// Transaction signature returned from sent transaction | ||
let transactionSignature: string; | ||
``` | ||
|
||
## Mint Setup | ||
|
||
First, lets define the properties of the Mint Account we'll be creating in the | ||
following step. | ||
|
||
```javascript | ||
// Generate new keypair for Mint Account | ||
const mintKeypair = Keypair.generate(); | ||
// Address for Mint Account | ||
const mint = mintKeypair.publicKey; | ||
// Decimals for Mint Account | ||
const decimals = 2; | ||
// Authority that can mint new tokens | ||
const mintAuthority = pg.wallet.publicKey; | ||
// Authority that can transfer or burn from any token account | ||
const permanentDelegate = pg.wallet.publicKey; | ||
``` | ||
|
||
Next, lets determine the size of the new Mint Account and calculate the minimum | ||
lamports needed for rent exemption. | ||
|
||
```javascript | ||
// Size of Mint Account with extension | ||
const mintLen = getMintLen([ExtensionType.PermanentDelegate]); | ||
// Minimum lamports required for Mint Account | ||
const lamports = await connection.getMinimumBalanceForRentExemption(mintLen); | ||
``` | ||
|
||
With Token Extensions, the size of the Mint Account will vary based on the | ||
extensions enabled. | ||
|
||
## Build Instructions | ||
|
||
We will need to build a set of instructions to: | ||
|
||
- Create a new account | ||
- Initialize the `PermanentDelegate` extension | ||
- Initialize the remaining Mint Account data | ||
|
||
First, build the instruction to invoke the System Program to create an account | ||
and assign ownership to the Token Extensions Program. | ||
|
||
```javascript | ||
// Instruction to invoke System Program to create new account | ||
const createAccountInstruction = SystemProgram.createAccount({ | ||
fromPubkey: payer.publicKey, // Account that will transfer lamports to created account | ||
newAccountPubkey: mint, // Address of the account to create | ||
space: mintLen, // Amount of bytes to allocate to the created account | ||
lamports, // Amount of lamports transferred to created account | ||
programId: TOKEN_2022_PROGRAM_ID, // Program assigned as owner of created account | ||
}); | ||
``` | ||
|
||
Next, build the instruction to initialize the `PermanentDelegate` extension for | ||
the Mint Account. | ||
|
||
```javascript | ||
// Instruction to initialize the MintCloseAuthority Extension | ||
const initializeMintCloseAuthorityInstruction = | ||
createInitializePermanentDelegateInstruction( | ||
mint, // Mint Account address | ||
permanentDelegate, // Designated Permanent Delegate | ||
TOKEN_2022_PROGRAM_ID, // Token Extension Program ID | ||
); | ||
``` | ||
|
||
Lastly, build the instruction to initialize the rest of the Mint Account data. | ||
This is the same as with the original Token Program. | ||
|
||
```javascript | ||
// Instruction to initialize Mint Account data | ||
const initializeMintInstruction = createInitializeMintInstruction( | ||
mint, // Mint Account Address | ||
decimals, // Decimals of Mint | ||
mintAuthority, // Designated Mint Authority | ||
null, // Optional Freeze Authority | ||
TOKEN_2022_PROGRAM_ID, // Token Extension Program ID | ||
); | ||
``` | ||
|
||
## Send Transaction | ||
|
||
Now add the instructions to a new transaction and send it to the network. This | ||
will create a Mint Account with the `PermanentDelegate` extension enabled. | ||
|
||
```javascript | ||
// Add instructions to new transaction | ||
const transaction = new Transaction().add( | ||
createAccountInstruction, | ||
initializeMintCloseAuthorityInstruction, | ||
initializeMintInstruction, | ||
); | ||
|
||
// Send transaction | ||
transactionSignature = await sendAndConfirmTransaction( | ||
connection, | ||
transaction, | ||
[payer, mintKeypair], // Signers | ||
); | ||
|
||
console.log( | ||
"\nCreate Mint Account:", | ||
`https://solana.fm/tx/${transactionSignature}?cluster=devnet-solana`, | ||
); | ||
``` | ||
|
||
Run the script by clicking the `Run` button. You can then inspect the | ||
transaction on the SolanaFM. | ||
|
||
## Create Token Accounts | ||
|
||
Next, we will set up two Token Accounts to demonstrate the functionality of the | ||
Permanent Delegate. | ||
|
||
First, generate a random keypair and use it as the owner of a | ||
`sourceTokenAccount`. | ||
|
||
```javascript | ||
// Random keypair to use as owner of Token Account | ||
const randomKeypair = new Keypair(); | ||
// Create Token Account for random keypair | ||
const sourceTokenAccount = await createAccount( | ||
connection, | ||
payer, // Payer to create Token Account | ||
mint, // Mint Account address | ||
randomKeypair.publicKey, // Token Account owner | ||
undefined, // Optional keypair, default to Associated Token Account | ||
undefined, // Confirmation options | ||
TOKEN_2022_PROGRAM_ID, // Token Extension Program ID | ||
); | ||
``` | ||
|
||
Next, create a `destinationTokenAccount` owned by the Playground wallet. | ||
|
||
```javascript | ||
// Create Token Account for Playground wallet | ||
const destinationTokenAccount = await createAccount( | ||
connection, | ||
payer, // Payer to create Token Account | ||
mint, // Mint Account address | ||
payer.publicKey, // Token Account owner | ||
undefined, // Optional keypair, default to Associated Token Account | ||
undefined, // Confirmation options | ||
TOKEN_2022_PROGRAM_ID, // Token Extension Program ID | ||
); | ||
``` | ||
|
||
Lastly, mint 2 tokens to the `sourceTokenAccount` to fund it. | ||
|
||
```javascript | ||
// Mint tokens to sourceTokenAccount | ||
transactionSignature = await mintTo( | ||
connection, | ||
payer, // Transaction fee payer | ||
mint, // Mint Account address | ||
sourceTokenAccount, // Mint to | ||
mintAuthority, // Mint Authority address | ||
200, // Amount | ||
undefined, // Additional signers | ||
undefined, // Confirmation options | ||
TOKEN_2022_PROGRAM_ID, // Token Extension Program ID | ||
); | ||
|
||
console.log( | ||
"\nMint Tokens:", | ||
`https://solana.fm/tx/${transactionSignature}?cluster=devnet-solana`, | ||
); | ||
``` | ||
|
||
## Transfer with Permanent Delegate | ||
|
||
Next, lets send a transaction to transfer 1 token from the `sourceTokenAccount` | ||
to the `destinationTokenAccount`. Remember, the `sourceTokenAccount` is owned by | ||
a randomly generated keypair. | ||
|
||
To transfer tokens using the Permanent Delegate, use the `transferChecked` | ||
instruction and specify the Permanent Delegate as the owner of the | ||
`sourceTokenAccount`. | ||
|
||
```javascript | ||
// Transfer tokens from source to destination | ||
transactionSignature = await transferChecked( | ||
connection, | ||
payer, // Transaction fee payer | ||
sourceTokenAccount, // Transfer from | ||
mint, // Mint Account address | ||
destinationTokenAccount, // Transfer to | ||
permanentDelegate, // Use Permanent Delegate as owner | ||
100, // Amount | ||
decimals, // Mint Account decimals | ||
undefined, // Additional signers | ||
undefined, // Confirmation options | ||
TOKEN_2022_PROGRAM_ID, // Token Extension Program ID | ||
); | ||
|
||
console.log( | ||
"\nTransfer Tokens:", | ||
`https://solana.fm/tx/${transactionSignature}?cluster=devnet-solana`, | ||
); | ||
``` | ||
|
||
## Burn with Permanent Delegate | ||
|
||
Lets also send a transaction to burn 1 token from the `sourceTokenAccount`. | ||
|
||
To burn tokens using the `Permanent Delegate`, use the `burnChecked` instruction | ||
and specify the Permanent Delegate as the owner of the `sourceTokenAccount`. | ||
|
||
```javascript | ||
// Burn tokens from token account | ||
transactionSignature = await burnChecked( | ||
connection, | ||
payer, // Transaction fee payer | ||
sourceTokenAccount, // Burn from | ||
mint, // Mint Account address | ||
permanentDelegate, // Use Permanent Delegate as owner | ||
100, // Amount | ||
decimals, // Mint Account decimals | ||
undefined, // Additional signers | ||
undefined, // Confirmation options | ||
TOKEN_2022_PROGRAM_ID, // Token Extension Program ID | ||
); | ||
|
||
console.log( | ||
"\nBurn Tokens:", | ||
`https://solana.fm/tx/${transactionSignature}?cluster=devnet-solana`, | ||
); | ||
``` | ||
|
||
Run the script by clicking the `Run` button. You can then inspect the | ||
transactions on the SolanaFM. | ||
|
||
<Callout type="success"> | ||
Note that both the transfer and burn transactions complete successfully, | ||
even though the transactions are not signed by the owner of the Token Account. | ||
</Callout> | ||
|
||
## Conclusion | ||
|
||
The `PermanentDelegate` extension is a powerful extension that enables | ||
developers to have much greater control over tokens they create, such as the | ||
ability to retrieve tokens that have been mistakenly transferred. While this | ||
extension offers greater flexibility, it's essential for users to be aware of | ||
the implications of holding tokens with this extension enabled, particularly the | ||
risks associated with compromised delegate keys. |