Skip to main content

Design a Revocable Credential Token with Non-Transferable and Permanent Delegate Extensions

Create a Solana credential that works more like a real certificate: it belongs to one wallet, cannot be passed around, and can still be revoked by the issuer.
Design a Revocable Credential Token with Non-Transferable and Permanent Delegate Extensions background
Challenge

Design a Revocable Credential Token with Non-Transferable and Permanent Delegate Extensions

The Scenario

In Web2, credentials and certifications follow a specific lifecycle. A coding bootcamp issues you a certificate of completion. You cannot sell it or give it to someone else; it belongs to you. But the issuing institution retains the right to revoke it if they discover fraud or if the credential expires. Think of professional licenses, employee badges, or verified status markers on social platforms. They are yours to hold, but someone else controls whether they remain valid.

Over the past few days, you have built tokens with transfer fees, metadata, and frozen account states. Today you are going to experiment with two extensions you have not used yet: the non-transferable extension and the permanent delegate extension. Combined, these let you create a token that cannot be moved between wallets (soulbound) but can still be burned by a designated authority (revocable). You will also layer on metadata so the credential carries human-readable information. This is an experiment day, so you have room to try things, observe what works, and learn from any errors you encounter along the way.

The Challenge

What you’ll need

  • A terminal with the Solana CLI installed
  • The spl-token CLI (with Token-2022 support)
  • Your Solana keypair configured for devnet (solana config set --url devnet)
  • Devnet SOL in your wallet (use solana airdrop 2 if needed)
  • A second keypair to simulate a “recipient” wallet

Steps

Step 1: Create a non-transferable token with a permanent delegate and metadata

You are going to combine three extensions in a single mint. The non-transferable extension locks the token in place once minted. The permanent delegate extension gives your authority keypair the ability to burn tokens from any account. The metadata extension attaches human-readable information directly on-chain.

Run it

spl-token --program-id TokenzQdBNbLqP5VEhdkAS6EPFLC1PHnBqCXEpPxuEb create-token \
  --decimals 0 \
  --enable-non-transferable \
  --enable-permanent-delegate \
  --enable-metadata

Save the mint address from the output. You will need it throughout this experiment. Note that you use --decimals 0 because credentials are whole units; you either have one or you do not.

Step 2: Initialize the token metadata

Now give your credential a name, symbol, and URI. The URI would typically point to a JSON file with additional details about the credential, but for this experiment a placeholder works fine.

Run it

spl-token initialize-metadata [MINT_ADDRESS] \
  "Solana Dev Credential" \
  "CRED" \
  "https://example.com/credential.json"

Step 3: Create a token account for the recipient and mint one credential

Generate a second keypair to act as your recipient. In a real scenario, this would be the wallet of a developer who earned the credential.

Run it

solana-keygen new --outfile ~/recipient-wallet.json --no-bip39-passphrase --force
RECIPIENT=$(solana-keygen pubkey ~/recipient-wallet.json)
spl-token create-account [MINT_ADDRESS] --owner $RECIPIENT \
  --fee-payer ~/.config/solana/id.json \
  --program-id TokenzQdBNbLqP5VEhdkAS6EPFLC1PHnBqCXEpPxuEb
spl-token mint [MINT_ADDRESS] 1 --recipient-owner $RECIPIENT \
  --program-id TokenzQdBNbLqP5VEhdkAS6EPFLC1PHnBqCXEpPxuEb

Step 4: Verify the token cannot be transferred

Now experiment. Try to transfer the credential from the recipient to another address. This should fail because the non-transferable extension blocks all transfers after minting.

Run it

solana-keygen new --outfile ~/third-party.json --no-bip39-passphrase --force
THIRD_PARTY=$(solana-keygen pubkey ~/third-party.json)
spl-token transfer [MINT_ADDRESS] 1 $THIRD_PARTY \
  --owner ~/recipient-wallet.json \
  --fee-payer ~/.config/solana/id.json \
  --program-id TokenzQdBNbLqP5VEhdkAS6EPFLC1PHnBqCXEpPxuEb \
  --fund-recipient --allow-unfunded-recipient

You should see an error indicating the transfer is not allowed. Read the error message carefully. It confirms that the non-transferable extension is working as intended.

Step 5: Revoke the credential using the permanent delegate

Now simulate the issuing authority revoking the credential. Because your default keypair is the permanent delegate, you can burn the token from the recipient’s account without their signature.

Run it

spl-token burn [RECIPIENT_TOKEN_ACCOUNT_ADDRESS] 1 --owner ~/.config/solana/id.json \
  --program-id TokenzQdBNbLqP5VEhdkAS6EPFLC1PHnBqCXEpPxuEb

If the burn succeeds, the credential has been revoked. The recipient no longer holds any tokens. Confirm this by checking their balance:

spl-token balance [MINT_ADDRESS] --owner $RECIPIENT \
  --program-id TokenzQdBNbLqP5VEhdkAS6EPFLC1PHnBqCXEpPxuEb

Step 6: Inspect the mint to confirm all extensions are present

Use the display command to review the full extension configuration of your mint, just like you practiced on the previous Reinforce day.

Run it

spl-token display [MINT_ADDRESS] \
  --program-id TokenzQdBNbLqP5VEhdkAS6EPFLC1PHnBqCXEpPxuEb

In the output, look for the non-transferable flag, the permanent delegate address, and the metadata fields you set earlier. All three extensions should be visible in the account data.

Bonus experiment

Try these variations to deepen your understanding:

  • Mint a second credential to the same recipient and then burn only one. Does the balance update correctly?
  • Attempt to use spl-token authorize to change the permanent delegate. What happens? Can it be reassigned?
  • Add a custom metadata field using spl-token update-metadata to store an “issued_date” or “expiry_date” value on the mint.

What Just Happened

You built a token that behaves like a revocable credential. The non-transferable extension ensures that once a token is minted to a wallet, only that wallet can hold it. Nobody can sell it, trade it, or move it to a secondary market. In Web2 terms, this is like a professional certification that is tied to your identity and cannot be forwarded to someone else.

The permanent delegate extension gives the issuing authority ongoing power over the token supply in every holder’s account. Unlike the default frozen approach from Day 38 (where you had to thaw accounts before they could interact with tokens), the permanent delegate works silently in the background. The holder uses their credential normally, and the authority can revoke it at any time without the holder’s cooperation. This maps directly to how Web2 credential systems work: your employer can revoke your building access badge, and a certification body can rescind your professional license.

By layering metadata on top, you made the credential self-describing. Anyone inspecting the mint on-chain can see what it represents without needing an external database or API call. This combination of three extensions creates a pattern that is immediately useful for DAOs issuing membership badges, protocols granting verified contributor status, or education platforms distributing completion certificates.

Resources

Submission

Take a screenshot showing the output of your spl-token display command with all three extensions visible (non-transferable, permanent delegate, and metadata) along with the failed transfer attempt error message. Submit it here.

Submit your project