This document explains how to create and use a smart-wallet with Secp256r1 signature verification to execute arbitrary instructions (like Raydium swaps) on Solana. The process is explained in two main flows: smart-wallet creation and instruction execution.
- Generate ECDSA keypair using
ecdsa-secp256r1:
const privateKey = ECDSA.generateKey();
const publicKeyBase64 = privateKey.toCompressedPublicKey();
const pubkey = Array.from(Buffer.from(publicKeyBase64, 'base64'));- Prepare initialization transaction by finding smart-wallet PDA and required accounts:
const txn = await createInitSmartWalletTransaction({
secp256k1PubkeyBytes: pubkey,
connection: anchorProvider.connection,
payer: wallet.publicKey,
});- Smart contract validates initialization parameters
- Creates smart-wallet account (PDA) with:
- Owner field set to ECDSA compressed public key [u8; 33]
- Authority field set to program ID
- State initialized as active
- Prepare the arbitrary instruction (e.g., Raydium swap):
const swapInstruction = await prepareSwapInstruction({
fromTokenAccount,
toTokenAccount,
amount,
// ...other swap parameters
});- Sign message with ECDSA private key:
const message = Buffer.from(swapInstruction.data);
const signatureBase64 = privateKey.sign(message);
const signature = Buffer.from(signatureBase64, 'base64');- Prepare verification and execution transaction:
const transaction = await createVerifyAndExecuteTransaction({
arbitraryInstruction: swapInstruction,
pubkey: pubkey,
signature: signature,
message: message,
connection: anchorProvider.connection,
payer: wallet.publicKey,
smartWalletPda: smartWalletPubkey,
});-
Smart contract receives:
- Arbitrary instruction data
- ECDSA signature
- Message (instruction data)
- ECDSA public key
-
Native Secp256r1 program verifies signature:
- Validates signature against provided message
- Confirms signer matches smart-wallet owner's public key
-
Smart contract processes instruction:
- Finds smart-wallet PDA in instruction's account_metas
- Verifies PDA matches derived address
- Sets
is_signerto true for smart-wallet PDA
-
Smart contract executes instruction:
- Uses CPI (Cross-Program Invocation) to execute modified instruction
- Smart-wallet PDA acts as signer
- Original instruction executes with proper authorization
Send transaction to network:
const signature = await anchorProvider.connection.sendTransaction(transaction);
await anchorProvider.connection.confirmTransaction(signature);#[account]
pub struct SmartWallet {
pub owner: [u8; 33], // ECDSA compressed public key
#[max_len(5)]
pub authority: Vec<[u8; 33]>, // Vec of another ECDSA public key
pub id: u64, // Unique identifier
pub bump: u8, // PDA bump seed
}const [smartWalletPda] = PublicKey.findProgramAddressSync(
[Buffer.from('smart-wallet'), &id.to_le_bytes()],
programId
);- Smart contract calls Secp256r1 program with:
- Message (instruction data)
- Signature
- Public key
- Native program returns verification result
- Smart contract validates verification before proceeding
- Smart-wallet ownership is proven through ECDSA signatures
- Each instruction must be signed fresh - no replay protection needed
- Smart-wallet PDA can only sign when signature is verified
- Original instruction's integrity is maintained while adding authorization
Common error cases:
- Invalid signature
- Incorrect public key format
- Smart-wallet not initialized
- Unauthorized instruction attempt
- Invalid PDA derivation
Each error returns a specific error code for proper client-side handling.