Create a sponsored transaction
Build transactions where a sponsor pays the fees on behalf of users
import { STACKS_TESTNET } from "@stacks/network";import { bytesToHex } from "@stacks/common";import {broadcastTransaction,deserializeTransaction,makeContractCall,sponsorTransaction,BufferReader,AnchorMode,Cl,} from "@stacks/transactions";// Step 1: User creates the transaction with sponsored flagconst userTxOptions = {contractAddress: "ST1PQHQKV0RJXZFY1DGX8MNSNYVE3VGZJSRTPGZGM",contractName: "my-contract",functionName: "my-function",functionArgs: [Cl.uint(123)],fee: 0, // User doesn't pay feessenderKey: "b244296d5907de9864c0b0d51f98a13c52890be0404e83f273144cd5b9960eed01",network: STACKS_TESTNET,sponsored: true, // Mark as sponsoredanchorMode: AnchorMode.Any,};const transaction = await makeContractCall(userTxOptions);const serializedTx = bytesToHex(transaction.serialize());// Step 2: Send serialized transaction to sponsor// (In practice, this would be sent to a sponsorship service)// Step 3: Sponsor signs and pays feesconst sponsorKey = "753b7cc01a1a2e86221266a154af739463fce51219d97e4f856cd7200c3bd2a601";const deserializedTx = deserializeTransaction(serializedTx);const sponsoredTx = await sponsorTransaction({transaction: deserializedTx,sponsorPrivateKey: sponsorKey,fee: 1000, // Sponsor pays the feesponsorNonce: 0,});// Step 4: Broadcast the sponsored transactionconst broadcastResponse = await broadcastTransaction({transaction: sponsoredTx,network: STACKS_TESTNET,});console.log("Sponsored transaction ID:", broadcastResponse.txid);
Use cases
- Onboarding new users without STX for fees
- Subsidizing transaction costs for dApp users
- Enterprise applications paying for user transactions
- Gaming applications with seamless user experience
Key concepts
Sponsored transactions have two parties:
- User: Creates and signs the transaction with
sponsored: true
- Sponsor: Pays the fees and broadcasts the transaction
Building a sponsorship service
// Sponsorship service endpointasync function sponsorUserTransaction(serializedTx: string) {// Deserialize user transactionconst userTx = deserializeTransaction(serializedTx);// Validate transaction (check whitelist, limits, etc.)if (!isValidForSponsorship(userTx)) {throw new Error("Transaction not eligible for sponsorship");}// Get sponsor nonceconst sponsorAddress = "ST1PQHQKV0RJXZFY1DGX8MNSNYVE3VGZJSRTPGZGM";const sponsorNonce = await getNonce(sponsorAddress);// Sponsor the transactionconst sponsoredTx = await sponsorTransaction({transaction: userTx,sponsorPrivateKey: process.env.SPONSOR_KEY,fee: calculateDynamicFee(userTx),sponsorNonce,});// Broadcastreturn broadcastTransaction({transaction: sponsoredTx,network: STACKS_TESTNET,});}
STX transfer with sponsorship
import { makeUnsignedSTXTokenTransfer } from "@stacks/transactions";// User creates unsigned STX transferconst unsignedTransfer = await makeUnsignedSTXTokenTransfer({recipient: "ST1SJ3DTE5DN7X54YDH5D64R3BCB6A2AG2ZQ8YPD5",amount: 1000000n,fee: 0,nonce: 0n,network: STACKS_TESTNET,sponsored: true,publicKey: userPublicKey,});// User signsconst userSignedTx = await signTransaction(unsignedTransfer, userPrivateKey);// Sponsor signs and paysconst sponsoredTx = await sponsorTransaction({transaction: userSignedTx,sponsorPrivateKey: sponsorKey,fee: 500,sponsorNonce: 10,});
Sponsorship patterns
Conditional sponsorship
function shouldSponsor(tx: StacksTransaction): boolean {// Only sponsor specific contractsconst allowedContracts = ["my-dapp", "my-token"];if (!allowedContracts.includes(tx.contractName)) {return false;}// Limit sponsorship per userconst userTxCount = getUserTransactionCount(tx.senderAddress);if (userTxCount > 10) {return false;}// Check transaction typeif (tx.functionName === "expensive-function") {return false;}return true;}
Security consideration
Always validate sponsored transactions to prevent abuse. Implement rate limiting, whitelisting, and spending caps in production sponsorship services.
Package installation
Terminal
$npm install @stacks/network @stacks/common @stacks/transactions