Cardano Transaction Sponsorship

Sponsor Cardano transactions to pay network fees on behalf of your users. This guide walks you through setup, implementation, and common patterns.

Prerequisites

  • A UTXOS account with a project created
  • Node.js 18+ and npm/bun installed
  • A Cardano provider (e.g., Blockfrost)

Quick Start

Create a sponsorship

  1. Log in to the UTXOS Dashboard
  2. Navigate to Sponsorships and create a new sponsorship
  3. Configure your sponsorship settings
  4. Copy the Sponsorship ID for use in your code

Fund the sponsorship wallet

  1. Find your sponsorship wallet address in the dashboard
  2. Send ADA to this address from your funding wallet
  3. Wait for confirmation before proceeding

Start with a small amount for testing. You can add more funds at any time.

Install the SDK

npm install @utxos/sdk @meshsdk/provider

Initialize the SDK

import { Web3Sdk } from "@utxos/sdk";
import { BlockfrostProvider } from "@meshsdk/provider";
 
const provider = new BlockfrostProvider(
  process.env.BLOCKFROST_API_KEY_PREPROD
);
 
const sdk = new Web3Sdk({
  projectId: process.env.NEXT_PUBLIC_UTXOS_PROJECT_ID,
  apiKey: process.env.UTXOS_API_KEY,
  network: "testnet", // or "mainnet"
  privateKey: process.env.UTXOS_PRIVATE_KEY,
  fetcher: provider,
  submitter: provider,
});
🚫

Never expose your private key in client-side code. Run all sponsorship logic on your server.

Build the transaction

Use the static sponsorship info as placeholders:

import { MeshTxBuilder } from "@meshsdk/core";
 
// Get placeholder inputs
const staticInfo = sdk.sponsorship.getStaticInfo();
 
// Build your transaction
const txBuilder = new MeshTxBuilder({ fetcher: provider });
 
txBuilder
  // Your business logic here
  .txOut(recipientAddress, [{ unit: "lovelace", quantity: "2000000" }])
 
  // Required: Set change address from static info
  .changeAddress(staticInfo.changeAddress)
 
  // Required: Add static UTXO as input
  .txIn(
    staticInfo.utxo.input.txHash,
    staticInfo.utxo.input.outputIndex,
    staticInfo.utxo.output.amount,
    staticInfo.utxo.output.address,
    0
  )
 
  // Optional: Add collateral for script transactions
  .txInCollateral(
    staticInfo.collateral.input.txHash,
    staticInfo.collateral.input.outputIndex,
    staticInfo.collateral.output.amount,
    staticInfo.collateral.output.address
  );
 
const unsignedTx = await txBuilder.complete();
// Request sponsorship
const sponsorResult = await sdk.sponsorship.sponsorTx({
  sponsorshipId: "your_sponsorship_id",
  tx: unsignedTx,
});
 
if (!sponsorResult.success) {
  throw new Error(sponsorResult.error);
}
 
// User signs the sponsored transaction
const signedTx = await userWallet.cardano.signTx(sponsorResult.data, true);
 
// Submit to the network
const txHash = await provider.submitTx(signedTx);
console.log(`Transaction submitted: ${txHash}`);

Complete Example: Sponsored NFT Mint

This example demonstrates minting an NFT where the developer pays all fees:

import { Web3Sdk } from "@utxos/sdk";
import { BlockfrostProvider, MeshTxBuilder, ForgeScript, resolveScriptHash, stringToHex } from "@meshsdk/core";
 
// Initialize provider and SDK
const provider = new BlockfrostProvider(process.env.BLOCKFROST_API_KEY_PREPROD);
 
const sdk = new Web3Sdk({
  projectId: process.env.NEXT_PUBLIC_UTXOS_PROJECT_ID,
  apiKey: process.env.UTXOS_API_KEY,
  network: "testnet",
  privateKey: process.env.UTXOS_PRIVATE_KEY,
  fetcher: provider,
  submitter: provider,
});
 
async function mintSponsoredNft(userWallet: any, metadata: any) {
  // Get user's address for the NFT
  const userAddress = await userWallet.cardano.getChangeAddress();
 
  // Create minting policy
  const forgingScript = ForgeScript.withOneSignature(userAddress);
  const policyId = resolveScriptHash(forgingScript);
  const tokenName = "MyNFT";
  const tokenNameHex = stringToHex(tokenName);
 
  // Get static sponsorship info
  const staticInfo = sdk.sponsorship.getStaticInfo();
 
  // Build the minting transaction
  const txBuilder = new MeshTxBuilder({ fetcher: provider });
 
  txBuilder
    .mint("1", policyId, tokenNameHex)
    .mintingScript(forgingScript)
    .metadataValue(721, { [policyId]: { [tokenName]: metadata } })
    .txOut(userAddress, [{ unit: policyId + tokenNameHex, quantity: "1" }])
    .changeAddress(staticInfo.changeAddress)
    .txIn(
      staticInfo.utxo.input.txHash,
      staticInfo.utxo.input.outputIndex,
      staticInfo.utxo.output.amount,
      staticInfo.utxo.output.address,
      0
    )
    .txInCollateral(
      staticInfo.collateral.input.txHash,
      staticInfo.collateral.input.outputIndex,
      staticInfo.collateral.output.amount,
      staticInfo.collateral.output.address
    );
 
  const unsignedTx = await txBuilder.complete();
 
  // Sponsor the transaction
  const sponsorResult = await sdk.sponsorship.sponsorTx({
    sponsorshipId: process.env.SPONSORSHIP_ID,
    tx: unsignedTx,
  });
 
  if (!sponsorResult.success) {
    throw new Error(`Sponsorship failed: ${sponsorResult.error}`);
  }
 
  // User signs and submits
  const signedTx = await userWallet.cardano.signTx(sponsorResult.data, true);
  const txHash = await provider.submitTx(signedTx);
 
  return { txHash, policyId, tokenName };
}

What configuration options are available?

Configure these settings in the UTXOS dashboard for each sponsorship:

OptionDescriptionRecommended Value
numUtxosTriggerPrepareUTXO count that triggers automatic pool replenishment5
numUtxosPrepareNumber of UTXOs to create during preparation10
utxoAmountADA amount for each sponsorship UTXO5 ADA (standard) or 99 ADA (batch)

Choosing UTXO Amount

// Standard transactions (most use cases)
const staticInfo = sdk.sponsorship.getStaticInfo("5");
 
// High-fee transactions or batch operations
const staticInfo = sdk.sponsorship.getStaticInfo("99");

Environment Variables

Add these to your .env file:

NEXT_PUBLIC_UTXOS_PROJECT_ID=your_project_id
UTXOS_API_KEY=your_api_key
UTXOS_PRIVATE_KEY=your_entity_secret_private_key
BLOCKFROST_API_KEY_PREPROD=your_blockfrost_key
SPONSORSHIP_ID=your_sponsorship_id

Troubleshooting

”Insufficient UTXOs” error

Cause: The sponsorship wallet UTXO pool is depleted.

Solutions:

  1. Wait a few seconds and retry. The SDK auto-chains a preparation transaction.
  2. Add more funds to the sponsorship wallet.
  3. Increase numUtxosPrepare in your configuration.

”Invalid CBOR” error

Cause: The transaction format is incorrect or missing required fields.

Solutions:

  1. Verify you included staticInfo.changeAddress as the change address
  2. Verify you added staticInfo.utxo as a transaction input
  3. Check that your transaction builder produces valid CBOR

”Sponsorship not found” error

Cause: The sponsorship ID is incorrect or the sponsorship was deleted.

Solutions:

  1. Copy the sponsorship ID directly from the dashboard
  2. Verify the sponsorship exists and is active

Transaction stuck in mempool

Cause: Network congestion or fee too low.

Solutions:

  1. Wait for the transaction to confirm or expire
  2. If using custom fee calculation, increase the fee estimate
  3. Check the Cardano network status for congestion

User wallet signature fails

Cause: The user wallet cannot sign the sponsored transaction.

Solutions:

  1. Ensure you pass true as the second argument to signTx() for partial signing
  2. Verify the user has the required keys for their inputs
  3. Check that the wallet is connected and unlocked

Best Practices

⚠️

Always implement server-side rate limiting to prevent sponsorship abuse.

  1. Validate user requests before sponsoring to prevent spam
  2. Set per-user limits to control costs (e.g., 10 sponsored transactions per day)
  3. Monitor your sponsorship wallet balance and set up alerts for low funds
  4. Log all sponsored transactions for auditing and debugging
  5. Handle errors gracefully and provide clear feedback to users

Next Steps