Skip to main content
A common use case: your app builds a transaction and needs the user to sign it.

The flow

  1. User generates code at actioncode.app
  2. User shares code with your app
  3. Your app builds a transaction and attaches it
  4. User approves in their wallet
  5. 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.

With metadata

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
}