import { buildSponsoredContractCall, RelayerError } from '@velumx/sdk';
import { uintCV, principalCV, contractPrincipalCV } from '@stacks/transactions';
// 1. Estimate fee — also tells you the active policy and the relayer address
const estimate = await velumx.estimateFee({
feeToken: 'SP2C2YFP12AJZB4MABJBAJ55XECVS7E4PMMZ89YZR.aeusdc',
estimatedGas: 200_000,
});
// Guard: relayerAddress is required for USER_PAYS — always comes from estimateFee, never hardcoded
if (!estimate.relayerAddress) throw new Error('Relayer address missing from fee estimate');
// 2. Build call to your paymaster contract
// The paymaster atomically: collects fee → executes action
// relayer = estimate.relayerAddress — the paymaster transfers the fee token to this address on-chain
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,
});
// 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 on-chain
try {
const { txid } = await velumx.sponsor(signResult.transaction, {
feeToken: estimate.feeToken,
feeAmount: estimate.maxFee,
});
console.log('Transaction ID:', txid);
} catch (err) {
if (err instanceof RelayerError) {
console.error('Relayer rejected:', err.reason);
} else {
throw err;
}
}