import { buildSponsoredContractCall, RelayerError } from '@velumx/sdk';
import { uintCV, principalCV, contractPrincipalCV } from '@stacks/transactions';
// 1. Estimate fee — this gives you the policy, fee amount, AND relayer address
const estimate = await velumx.estimateFee({
feeToken: 'SP2C2YFP12AJZB4MABJBAJ55XECVS7E4PMMZ89YZR.aeusdc',
estimatedGas: 200_000,
});
// Guard: relayerAddress is required for USER_PAYS — always comes from estimateFee
if (!estimate.relayerAddress) throw new Error('Relayer address missing from fee estimate');
// 2. Build call to your paymaster contract
// The paymaster atomically collects the fee then executes the action.
// fee-amount, relayer, and fee-token are always the last three args.
// relayer = estimate.relayerAddress — never hardcode this value.
const unsignedTx = await buildSponsoredContractCall({
contractAddress: 'SP...your-paymaster',
contractName: 'my-paymaster-v1',
functionName: 'my-action',
functionArgs: [
uintCV(1_000_000n), // your action param
uintCV(BigInt(estimate.maxFee)), // fee-amount
principalCV(estimate.relayerAddress), // relayer — from estimateFee
contractPrincipalCV('SP2C2YFP12AJZB4MABJBAJ55XECVS7E4PMMZ89YZR', 'aeusdc'), // fee-token
],
publicKey: userPublicKey,
});
// 3. User signs (no broadcast)
const txForSigning = unsignedTx instanceof Uint8Array
? Buffer.from(unsignedTx).toString('hex')
: unsignedTx;
const signResult = await request('stx_signTransaction', {
transaction: txForSigning,
broadcast: false,
});
// 4. Sponsor — pass fee params so the relayer can validate the on-chain fee transfer
try {
const { txid } = await velumx.sponsor(signResult.transaction, {
feeToken: estimate.feeToken,
feeAmount: estimate.maxFee,
});
console.log('txid:', txid);
} catch (err) {
if (err instanceof RelayerError) console.error('Rejected:', err.reason);
else throw err;
}