A common use case: your app builds a transaction and needs the user to sign it.
The flow
- User generates code at actioncode.app
- User shares code with your app
- Your app builds a transaction and attaches it
- User approves in their wallet
- Your app gets the signed transaction
Full example
import { ActionCodesClient } from '@actioncodes/sdk'
import {
Transaction,
SystemProgram,
PublicKey,
Connection,
LAMPORTS_PER_SOL
} from '@solana/web3.js'
const client = new ActionCodesClient({
authToken: process.env.ACTION_CODES_TOKEN
})
const connection = new Connection('https://api.mainnet-beta.solana.com')
async function requestTransactionSignature(
userCode: string,
recipient: string,
amountSol: number
) {
// 1. Verify the code and get wallet address
const actionCode = await client.resolve(userCode)
const userWallet = new PublicKey(actionCode.pubkey)
console.log(`Code valid for wallet: ${userWallet.toBase58()}`)
// 2. Build the transaction
const { blockhash, lastValidBlockHeight } = await connection.getLatestBlockhash()
const transaction = new Transaction({
recentBlockhash: blockhash,
feePayer: userWallet,
lastValidBlockHeight
}).add(
SystemProgram.transfer({
fromPubkey: userWallet,
toPubkey: new PublicKey(recipient),
lamports: amountSol * LAMPORTS_PER_SOL
})
)
// 3. Serialize and attach to the code
const serialized = transaction
.serialize({ requireAllSignatures: false })
.toString('base64')
await client.attachTransaction(userCode, serialized, {
description: `Transfer ${amountSol} SOL to ${recipient.slice(0, 8)}...`
})
console.log('Transaction attached — waiting for user approval...')
// 4. Wait for user to approve
for await (const status of client.observeStatus(userCode)) {
if (status.finalizedSignature) {
console.log('Transaction signed!')
return status.finalizedSignature
}
if (status.status === 'expired') {
throw new Error('Code expired before user approved')
}
}
}
// Usage
const signature = await requestTransactionSignature(
'48291037', // User's code from actioncode.app
'RecipientAddress', // Where to send
0.1 // Amount in SOL
)
console.log('Transaction signature:', signature)
Submitting the transaction
After getting the signature, you can submit the transaction to the network:
// The signature confirms the user signed — now submit to network
const txid = await connection.sendRawTransaction(signedTransaction)
// Wait for confirmation
await connection.confirmTransaction({
signature: txid,
blockhash,
lastValidBlockHeight
})
console.log('Transaction confirmed:', txid)
The finalizedSignature returned by observeStatus is the signature from the user’s wallet. Depending on your setup, you may need to also add your own signature (if you’re a co-signer) before submitting.
Add helpful context for the user:
await client.attachTransaction(code, serialized, {
description: 'Purchase NFT: Cool Cat #1234',
label: 'NFT Purchase',
memo: 'Order ID: abc123'
})
The user will see this description in actioncode.app when reviewing the request.
Error handling
import {
CodeNotFoundError,
ExpiredCodeError
} from '@actioncodes/sdk'
try {
const actionCode = await client.resolve(userCode)
// ... rest of flow
} catch (error) {
if (error instanceof CodeNotFoundError) {
return { error: 'Invalid code. Please get a new one from actioncode.app' }
}
if (error instanceof ExpiredCodeError) {
return { error: 'Code expired. Please get a new one from actioncode.app' }
}
throw error
}