Summary
- The permanent delegate holds global ownership over all token accounts associated with the mint
- The permanent delegate has unrestricted permissions to transfer and burn tokens from any token account of that mint
- This delegate role designates a trusted entity with comprehensive control. Common use cases include sanction compliance and revocable access tokens.
- With this level of access, the permanent delegate can carry out high-level administrative functions, such as reassigning tokens, managing token supplies, and directly implementing specific policies or rules on the token accounts.
Overview
The permanent delegate
extension allows a permanent delegate
for all tokens
of the mint. This means one address is capable of transferring or burning any
token of that mint, from any token account. This makes the extension very
powerful but can also be very risky. It gives a single address complete control
over the token supply. This can be good for things like automatic payments,
recovering drained wallets, and refunds. However, it's a double-edged sword, the
permanent delegate
could be stolen or abused. In the words of Uncle Ben, "With
great power, comes great responsibility."
Imagine a Solana based AirBnb, where NFTs that use permanent delegate are used
as the keys to unlock the door. When you check-in, the NFT key will be
transferred to you and you'll be able to enjoy your stay. At the end of your
stay, the owner will transfer it back from you to them - since they are the
permanent delegate
. What happens if your wallet gets drained, or you lose
access to the key? No worries, the owner can transfer the key from any account
back to you! But on the other end, say the owner doesn't want you staying there
anymore, they can revoke it at any time, and you'd be locked out. In this way,
the permanent delegate extension is a double-edged sword.
This all being said - the permanent delegate
is a very exciting extension that
adds a world of possibilities to Solana tokens.
Initializing a permanent delegate to mint
Initializing a permanent delegate token involves three instructions:
SystemProgram.createAccount
createInitializePermanentDelegateInstruction
createInitializeMintInstruction
The first instruction SystemProgram.createAccount
allocates space on the
blockchain for the mint account. This instruction accomplishes three things:
- Allocates space
- Transfers lamports for rent
- Assigns to its owning program
The second instruction createInitializePermanentDelegateInstruction
initializes the permanent delegate extension. The defining argument that
dictates the permanent delegate will be a variable we create named
permanentDelegate
.
The third instruction createInitializeMintInstruction
initializes the mint.
When the transaction with these three instructions is sent, a new permanent delegate token is created with the specified configuration.
Transferring tokens as delegate
The transferChecked
function enables the permanent delegate to securely
transfer tokens between accounts. This function makes sure that the token
transfer adheres to the mint's configured rules and requires the delegate to
sign the transaction.
Burning tokens as delegate
The burnChecked
function allows the permanent delegate to burn tokens from any
token account of the mint. This function makes sure that the burn operation
complies with the mint's rules and requires the delegate to sign the
transaction.
Assign permissions to a new delegate
The approveChecked
function approves a delegate to transfer or burn up to a
maximum number of tokens from an account. This allows the designated delegate to
perform token transfers on behalf of the account owner up to the specified
limit.
Lab
In this lab, we'll explore the functionality of the permanent delegate
extension by creating a mint account with a permanent delegate and testing
various interactions with token accounts associated with that mint.
1. Setup Environment
To get started, create an empty directory named permanent-delegate
and
navigate to it. We'll be initializing a brand new project. Run npm init
and
follow through the prompts.
Next, we'll need to add our dependencies. Run the following to install the required packages:
Create a directory named src
. In this directory, create a file named
index.ts
. This is where we will run checks against the rules of this
extension. Paste the following code in index.ts
:
index.ts
creates a connection to the specified validator node and calls
initializeKeypair
. It also has a few variables we will be using in the rest of
this lab. The index.ts
is where we'll end up calling the rest of our script
once we've written it.
If you run into an error in initializeKeypair
with airdropping, follow the
next step.
2. Run validator node
For the sake of this guide, we'll be running our own validator node.
In a separate terminal, run the following command: solana-test-validator
. This
will run the node and also log out some keys and values. The value we need to
retrieve and use in our connection is the JSON RPC URL, which in this case is
http://127.0.0.1:8899
. We then use that in the connection to specify to use
the local RPC URL.
Alternatively, if you'd like to use testnet or devnet, import the
clusterApiUrl
from @solana/web3.js
and pass it to the connection as such:
3. Helpers
When we pasted the index.ts
code from earlier, we added the following helpers:
initializeKeypair
: This function creates the keypair for thepayer
and also airdrops some SOL to itmakeKeypairs
: This function creates keypairs without airdropping any SOL
Additionally we have some initial accounts and variables that will be used to
test the permanent delegate
extension!
4. Create Mint with permanent delegate
When creating a mint token with default state, we must create the account instruction, initialize the default account state for the mint account and initialize the mint itself.
Create an asynchronous function named
createTokenExtensionMintWithPermanentDelegate
in src/mint-helper.ts
. This
function will create the mint such that all new mints will be created with a
permanent delegate. The function will take the following arguments:
connection
: The connection objectpayer
: Payer for the transactionmintKeypair
: Keypair for the new mintdecimals
: Mint decimalspermanentDelegate
: Assigned delegate keypair
The first step in creating a mint is reserving space on Solana with the
SystemProgram.createAccount
method. This requires specifying the payer's
keypair, (the account that will fund the creation and provide SOL for rent
exemption), the new mint account's public key (mintKeypair.publicKey
), the
space required to store the mint information on the blockchain, the amount of
SOL (lamports) necessary to exempt the account from rent and the ID of the token
program that will manage this mint account (TOKEN_2022_PROGRAM_ID
).
After the mint account creation, the next step involves initializing it with a
permanent delegate. The createInitializePermanentDelegateInstruction
function
is used to generate an instruction that enables the mint to set the permanent
delegate of any new mint accounts.
Next, let's add the mint instruction by calling
createInitializeMintInstruction
and passing in the required arguments. This
function is provided by the SPL Token package and it constructs a transaction
instruction that initializes a new mint.
Lastly, let's add all of the instructions to a transaction and send it to the blockchain:
Putting it all together, the final src/mint-helper.ts
file will look like
this:
6. Create printBalances function
We're going to be creating multiple tests that modify a token account's balance. To make it easier to follow along we should probably create a utility function that prints all token account balances.
At the bottom of the src/index.ts
file add the following printBalances
function:
7. Test Setup
Now that we have the ability to create a mint with a permanent delegate for all of its new mint accounts, let's write some tests to see how it functions.
7.1 Create Mint with Permanent Delegate
Let's first create a mint with payer
as the permanent delegate. To do this we
call the createTokenExtensionMintWithPermanentDelegate
function we just
created in our index.ts
file:
7.2 Create Test Token Accounts
Now, let's create three new Token accounts to test with. We can accomplish this
by calling the createAccount
helper provided by the SPL Token library. We will
use the keypairs we generated at the beginning: alice
, bob
, and carol
.
In this lab, alice
will be the permanent delegate.
7.3 Mint tokens to accounts
In the previous step, we created the 3 accounts we need to test the
permanent delegate
extension. Next, we need to mint tokens to those accounts
before we write the tests.
Add the tokenAccounts
and names
variables and then create a for loop that
iterates over each account and mints 100 tokens to each account. Call the
printBalances
function so we can display the token balance of each account:
Start your local validator and run npx esrun src/index.ts
. You should see the
following in your terminal, indicating that our token accounts have had tokens
minted to them:
8. Tests
Now let's write some tests to show the interactions that can be had with the
permanent delegate
extension.
We'll write the following tests:
-
Attempt to Transfer with Correct Delegate:
- Have Alice transfer tokens from Bob's account to herself successfully since she is the permanent delegate.
- Print balances to verify the transfer.
-
Attempt to Transfer without Correct Delegate:
- Have Bob attempt to transfer tokens from Alice's account to himself (expect this to fail since Bob isn't authorized).
- Print balances to verify the failure.
-
Attempt to Transfer from One Account to Another with Correct Delegate:
- Have Alice transfer tokens from Bob's account to Carol's account.
- Print balances to verify the transfer.
-
Attempt to Burn with Correct Delegate:
- Have Alice burn tokens from Bob's account successfully since she is the permanent delegate.
- Print balances to verify the burning.
-
Attempt to Burn without Correct Delegate:
- Have Bob attempt to burn tokens from Carol's account (expect this to fail since Bob isn't authorized).
- Print balances to verify the failure.
-
Grant Permission to an Account to Transfer Tokens from a Different Token Account:
- Approve Carol to transfer tokens from Bob's account to herself.
- Transfer tokens from Bob's account to Carol's account.
- Print balances to verify the transfer.
-
Try to Transfer Tokens Again with Carol as the Delegate, Overdrawing Her Allotted Control:
- Attempt to transfer tokens from Bob's account to Carol's account with Carol again, but overdraw her allotted control (expect this to fail).
8.1 Transfer tokens with the correct delegate
In this test, alice
attempts to transfer tokens from bob
to herself. This
test is expected to pass as alice
is the permanent delegate and has control
over the token accounts of that mint.
To do this, let's wrap a transferChecked
function in a try catch
and print
out the balances of our accounts:
Test this by running the script:
We should see the following error logged out in the terminal, meaning the
extension is working as intended.
✅ Since Alice is the permanent delegate, she has control over all token accounts of this mint
8.2 Transfer tokens with incorrect delegate
In this test, bob
is going to try to transfer tokens from alice
to himself.
Given that bob
is not the permanent delegate, the attempt won't be successful.
Similar to the previous test we can create this test by calling
transferChecked
and then printing the balances:
Go ahead and run the script, the transaction should fail.
8.3 Transfer from one account to another with the correct delegate
Lets use the power of the permanent delegate extension to have alice
transfer
some tokens from bob
to carol
.
We expect this test to succeed. Remember, the permanent delegate has control over all token accounts of the mint.
To test this, let's wrap a transferChecked
function in a try catch
and print
the balances:
In our first test we wrote, bob
had 10 of his tokens transferred to carol
.
Up until this point bob
has 90 tokens remaining. Run the test and see the
results. You will notice that bob
now has 80 tokens:
8.4 Burn with correct delegate
Now let's try and burn some of the tokens from bob
. This test is expected to
pass.
We'll do this by calling burnChecked
and then printing out the balances:
Run the tests again:
Bob had 5 tokens burned and now only has 75 tokens. Poor Bob!
8.5 Burn with incorrect delegate
Let's try and burn tokens from an account using the incorrect delegate. This is
expected to fail as bob
doesn't have any control over the token accounts.
Run npm start
. You will see the following message, indicating that the
extension is working as intended:
✅ We expect this to fail since Bob is not the permanent delegate and has no control over the tokens
8.6. Assign delegate permissions to Carol and transfer
With the permanent delegate
extension, the initial delegate can grant a token
account permission to hold a certain level of control over the mint tokens. In
this case, alice
will allow carol
to transfer some of the tokens from bob
account to herself.
For this to work we will need to set some boundaries for carol
. Using the
approveChecked
function provided by the SPL Library, we can set the maximum
number of tokens that can be transferred or burned by carol
. This ensures that
she can only transfer a specified amount, protecting the overall balance from
excessive or unauthorized transfers.
Add the following test:
Run the tests again. You will notice that bob
now only has 65 tokens as
carol
has just transferred 10 of his tokens to herself:
npx esrun src/index.ts
8.7. Attempt to transfer again
In the previous test, we approved carol
to be able to transfer 10 tokens to
herself. This means that she has reached the maximum amount of tokens to send
from another account. Let's write a test and attempt to transfer another 10
tokens to herself. This is expected to fail.
Run the tests one last time and you will see this message, meaning that the
✅ We expect this to fail since Carol already transferred 10 tokens and has no more allotted
Thats it! You've just created a mint account with a permanent delegate and tested that the functionality all works!
Challenge
Create your own mint account with a permanent delegate.