Skip to content

Conversation

grod220
Copy link
Member

@grod220 grod220 commented Apr 7, 2025

Final CLI work for the token wrap program. This enables the use of the Unwrap command when the token account is owned by a multisig.

Walkthrough for testing locally:

  1. Build program (generates .so file and builds binary)
cargo build-sbf && cargo build
  1. Start solana test validator
solana-test-validator \
  --bpf-program TwRapQCDhWkZRrDaHfZGuHxkZ91gHDRkyuzNqeU5MgR target/deploy/spl_token_wrap.so \
  --reset

Keep this running in a separate terminal.

  1. Generate Multisig Member Keypairs
for i in $(seq 3); do solana-keygen new --no-passphrase --outfile "signer-${i}.json"; done

SIGNER_1_KEYPAIR_PATH="signer-1.json"
SIGNER_2_KEYPAIR_PATH="signer-2.json"
SIGNER_3_KEYPAIR_PATH="signer-3.json"
SIGNER_1_PUBKEY=$(solana-keygen pubkey $SIGNER_1_KEYPAIR_PATH)
SIGNER_2_PUBKEY=$(solana-keygen pubkey $SIGNER_2_KEYPAIR_PATH)
SIGNER_3_PUBKEY=$(solana-keygen pubkey $SIGNER_3_KEYPAIR_PATH)

echo "Signer 1 Pubkey: $SIGNER_1_PUBKEY"
echo "Signer 2 Pubkey: $SIGNER_2_PUBKEY"
echo "Signer 3 Pubkey: $SIGNER_3_PUBKEY"
  1. Generate multisig account. Make sure the owner is the token program you are wrapping into.
spl-token create-multisig 2 $SIGNER_1_PUBKEY $SIGNER_2_PUBKEY $SIGNER_3_PUBKEY --program-id TokenzQdBNbLqP5VEhdkAS6EPFLC1PHnBqCXEpPxuEb

Save the result in a local var

MULTISIG_ADDRESS=FyEWySnccActUkNwTqgxXZPaDqeVs5y9FwK31VhskAVH
  1. Save fee payer
FEE_PAYER_KEYPAIR_PATH="$HOME/.config/solana/id.json"
FEE_PAYER_PUBKEY=$(solana-keygen pubkey $FEE_PAYER_KEYPAIR_PATH)
echo "Fee Payer Pubkey: $FEE_PAYER_PUBKEY"
  1. Create Unwrapped Mint & Token Account
UNWRAPPED_MINT=$(spl-token create-token --fee-payer "$FEE_PAYER_KEYPAIR_PATH" --output json | jq -r '.commandOutput.address')
echo "Unwrapped Mint Address: $UNWRAPPED_MINT"

spl-token create-account "$UNWRAPPED_MINT" --fee-payer "$FEE_PAYER_KEYPAIR_PATH"

Save the account result in a local var

UNWRAPPED_TOKEN_ACCOUNT=Hasm4hbwmtryn8bJbpRakkpT9GGpX4FrubuVxNXEB9ps
echo "Using Token Account: $UNWRAPPED_TOKEN_ACCOUNT"

Mint tokens to the multisig-owned account

spl-token mint $UNWRAPPED_MINT 1000 $UNWRAPPED_TOKEN_ACCOUNT
  1. Run CreateMint token-wrap command (saving results to vars
CREATE_MINT_JSON_OUTPUT=$(cargo run --bin spl-token-wrap create-mint "$UNWRAPPED_MINT" TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA TokenzQdBNbLqP5VEhdkAS6EPFLC1PHnBqCXEpPxuEb --fee-payer "$FEE_PAYER_KEYPAIR_PATH" --output json)
WRAPPED_MINT=$(echo "$CREATE_MINT_JSON_OUTPUT" | jq -r '.wrappedMintAddress')
WRAPPED_BACKPOINTER=$(echo "$CREATE_MINT_JSON_OUTPUT" | jq -r '.wrappedBackpointerAddress')
echo "WRAPPED_MINT=$WRAPPED_MINT"
echo "WRAPPED_BACKPOINTER=$WRAPPED_BACKPOINTER"
  1. Find PDAs and save to local vars
PDAS_JSON_OUTPUT=$(cargo run --bin spl-token-wrap find-pdas "$UNWRAPPED_MINT" TokenzQdBNbLqP5VEhdkAS6EPFLC1PHnBqCXEpPxuEb --output json)
WRAPPED_MINT_AUTHORITY=$(echo "$PDAS_JSON_OUTPUT" | jq -r '.wrappedMintAuthority')
echo "WRAPPED_MINT_AUTHORITY=$WRAPPED_MINT_AUTHORITY"
  1. Create an escrow token account and set its owner to the mint_authority
spl-token create-account $UNWRAPPED_MINT --owner $WRAPPED_MINT_AUTHORITY --fee-payer $FEE_PAYER_KEYPAIR_PATH

Save result to vars

ESCROW_ACCOUNT=8V3Cn5rsvu9K9aCz6cjvW1m7jEJLSuqXc8yb6m7kkZhE
  1. Create a recipient token account of that new wrapped mint. Should be the multisig previously created.
spl-token create-account $WRAPPED_MINT --owner $MULTISIG_ADDRESS --fee-payer $FEE_PAYER_KEYPAIR_PATH

Save result to vars

RECIPIENT_ACCOUNT=CkfK13CgcZJ2b7eorEA2WzJyGexJ3fLqsw2JscvoEFR8
  1. Issue wrap command (from fee-payers account to multisig wrapped account).
cargo run --bin spl-token-wrap wrap <UNWRAPPED_TOKEN_ACCOUNT> <ESCROW_ACCOUNT> <WRAPPED_TOKEN_PROGRAM> <AMOUNT>

cargo run --bin spl-token-wrap wrap $UNWRAPPED_TOKEN_ACCOUNT $ESCROW_ACCOUNT TokenzQdBNbLqP5VEhdkAS6EPFLC1PHnBqCXEpPxuEb  55 --recipient-token-account $RECIPIENT_ACCOUNT
  1. Execute Multisig Unwrap Command. Combined here in multiple steps to avoid block hash expiry.
UNWRAP_AMOUNT=50
WRAPPED_TOKEN_PROGRAM=TokenzQdBNbLqP5VEhdkAS6EPFLC1PHnBqCXEpPxuEb
UNWRAPPED_TOKEN_PROGRAM=TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA
BLOCKHASH=$(solana block --output json | jq -r '.blockhash')

SIGNER_1_OUTPUT=$(cargo run --bin spl-token-wrap unwrap \
  "$RECIPIENT_ACCOUNT" \
  "$ESCROW_ACCOUNT" \
  "$UNWRAPPED_TOKEN_ACCOUNT" \
  "$UNWRAP_AMOUNT" \
  --transfer-authority "$MULTISIG_ADDRESS" \
  --fee-payer "$FEE_PAYER_PUBKEY" \
  --unwrapped-mint "$UNWRAPPED_MINT" \
  --wrapped-token-program "$WRAPPED_TOKEN_PROGRAM" \
  --unwrapped-token-program "$UNWRAPPED_TOKEN_PROGRAM" \
  --multisig-signer "$SIGNER_1_KEYPAIR_PATH" \
  --multisig-signer "$SIGNER_2_PUBKEY" \
  --blockhash "$BLOCKHASH" \
  --sign-only \
  --output json)

SIGNER_1_SIGNATURE=$(echo "$SIGNER_1_OUTPUT" | jq -r '.signOnlyData.signers[0]')
echo "Signer 1 Signature generated: $SIGNER_1_SIGNATURE"

SIGNER_2_OUTPUT=$(cargo run --bin spl-token-wrap unwrap \
  "$RECIPIENT_ACCOUNT" \
  "$ESCROW_ACCOUNT" \
  "$UNWRAPPED_TOKEN_ACCOUNT" \
  "$UNWRAP_AMOUNT" \
  --transfer-authority "$MULTISIG_ADDRESS" \
  --fee-payer "$FEE_PAYER_PUBKEY" \
  --unwrapped-mint "$UNWRAPPED_MINT" \
  --wrapped-token-program "$WRAPPED_TOKEN_PROGRAM" \
  --unwrapped-token-program "$UNWRAPPED_TOKEN_PROGRAM" \
  --multisig-signer "$SIGNER_1_PUBKEY" \
  --multisig-signer "$SIGNER_2_KEYPAIR_PATH" \
  --blockhash "$BLOCKHASH" \
  --sign-only \
  --output json)

SIGNER_2_SIGNATURE=$(echo "$SIGNER_2_OUTPUT" | jq -r '.signOnlyData.signers[0]')
echo "Signer 2 Signature generated: $SIGNER_2_SIGNATURE"

cargo run --bin spl-token-wrap unwrap \
  "$RECIPIENT_ACCOUNT" \
  "$ESCROW_ACCOUNT" \
  "$UNWRAPPED_TOKEN_ACCOUNT" \
  "$UNWRAP_AMOUNT" \
  --transfer-authority "$MULTISIG_ADDRESS" \
  --fee-payer "$FEE_PAYER_KEYPAIR_PATH" \
  --unwrapped-mint "$UNWRAPPED_MINT" \
  --wrapped-token-program "$WRAPPED_TOKEN_PROGRAM" \
  --unwrapped-token-program "$UNWRAPPED_TOKEN_PROGRAM" \
  --multisig-signer "$SIGNER_1_PUBKEY" \
  --multisig-signer "$SIGNER_2_PUBKEY" \
  --blockhash "$BLOCKHASH" \
  --signer "$SIGNER_1_SIGNATURE" \
  --signer "$SIGNER_2_SIGNATURE"
...

Base automatically changed from cli-unwrap to main April 9, 2025 14:15
@grod220 grod220 force-pushed the cli-unwrap-multisig branch 2 times, most recently from fe2727f to 8666fdc Compare April 10, 2025 14:08
@grod220 grod220 marked this pull request as ready for review April 10, 2025 15:08
Copy link

@buffalojoec buffalojoec left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Lgtm, just one question about the helper setup!

@grod220 grod220 force-pushed the cli-unwrap-multisig branch from 8666fdc to bdda27c Compare April 15, 2025 08:33
Copy link

@buffalojoec buffalojoec left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nice work!!

@grod220
Copy link
Member Author

grod220 commented Apr 15, 2025

Given Jon is out this week, going to merge to move things forward.

@joncinque when you're back, if there something more, happy to have a follow up PR!

@grod220 grod220 merged commit fb0b3c1 into main Apr 15, 2025
10 checks passed
@grod220 grod220 deleted the cli-unwrap-multisig branch April 15, 2025 09:52
Copy link
Contributor

@joncinque joncinque left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks great!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants