Transaction Sponsorship Usage

Transaction sponsorship allows you to pay for users’ transaction fees, improving user experience by removing the need for users to hold native tokens for gas fees.

Prerequisites

Before you begin, ensure you have:

  • A UTXOS project with API access
  • Your project ID and API key from the dashboard
  • A Developer Controlled Wallet with sufficient funds for sponsoring transactions

Create A Sponsorship

  1. Navigate to the UTXOS dashboard and log into your account
  2. Create a new sponsorship campaign
  3. Configure your sponsorship settings and policies
  4. Copy the Sponsorship ID from the sponsorship settings for use in your code

Initialize The SDK

Initialize the Web3Sdk with your project credentials and network configuration:

import { Web3Sdk } from "@utxos/web3-sdk";
 
const sdk = new Web3Sdk({
  projectId: process.env.NEXT_PUBLIC_UTXOS_PROJECT_ID, // https://utxos.dev/dashboard
  apiKey: process.env.UTXOS_API_KEY,
  network: "preprod",
  privateKey: process.env.UTXOS_PRIVATE_KEY,
  fetcher: provider,
  submitter: provider,
});
⚠️

Never expose your private key in client-side code. Use environment variables or secure key management in production.

Build The Transaction

The SDK provides static UTXO information that must be included in your transaction:

// Get the required static information
const staticInfo = sdk.sponsorship.getStaticInfo();
 
// Build your transaction with the required fields
const txBuilder = your_preferred_transaction_builder
  .
  .
  // Required: Set the change address from static info
  .changeAddress(staticInfo.changeAddress)
  // Required: Add the static UTXO as an input
  .txIn(
    staticInfo.utxo.input.txHash,
    staticInfo.utxo.input.outputIndex,
    staticInfo.utxo.output.amount,
    staticInfo.utxo.output.address,
    0,
  )
  // If required: Add the static UTXO as collateral
  .txInCollateral(
    staticInfo.collateral.input.txHash,
    staticInfo.collateral.input.outputIndex,
    staticInfo.collateral.output.amount,
    staticInfo.collateral.output.address,
  );
 
const transaction = txBuilder.complete();

If you prefer to configure manually, use these static values for the preprod network:

const universalStaticUtxo = {
  input: {
    outputIndex: 0,
    txHash: "5a1edf7da58eff2059030abd456947a96cb2d16b9d8c3822ffff58d167ed8bfc",
  },
  output: {
    address:
      "addr_test1qrsj3xj6q99m4g9tu9mm2lzzdafy04035eya7hjhpus55r204nlu6dmhgpruq7df228h9gpujt0mtnfcnkcaj3wj457q5zv6kz",
    amount: [{ unit: "lovelace", quantity: "5000000" }],
  },
};
 
const universalStaticChangeAddress =
  "addr_test1qrsj3xj6q99m4g9tu9mm2lzzdafy04035eya7hjhpus55r204nlu6dmhgpruq7df228h9gpujt0mtnfcnkcaj3wj457q5zv6kz";

These are the static UTXOs available for the preprod network, it 5 ADA and 99 ADA respectively:

{
  "5": {
    input: {
      outputIndex: 0,
      txHash:
        "5a1edf7da58eff2059030abd456947a96cb2d16b9d8c3822ffff58d167ed8bfc",
    },
    output: {
      address:
        "addr_test1qrsj3xj6q99m4g9tu9mm2lzzdafy04035eya7hjhpus55r204nlu6dmhgpruq7df228h9gpujt0mtnfcnkcaj3wj457q5zv6kz",
      amount: [
        {
          unit: "lovelace",
          quantity: "5000000",
        },
      ],
    },
  },
  "99": {
    input: {
      outputIndex: 0,
      txHash:
        "8222b0327a95e8c357016a5df64d93d7cf8a585a07c55327ae618a7e00d58d9e",
    },
    output: {
      address:
        "addr_test1qrsj3xj6q99m4g9tu9mm2lzzdafy04035eya7hjhpus55r204nlu6dmhgpruq7df228h9gpujt0mtnfcnkcaj3wj457q5zv6kz",
      amount: [
        {
          unit: "lovelace",
          quantity: "99000000",
        },
      ],
    },
  },
}

Once your transaction is built, sponsor it and submit to the network:

try {
  // Step 1: Request sponsorship for your transaction
  const sponsoredTx = await sdk.sponsorship.sponsorTx({
    sponsorshipId: "your_sponsorship_id", // Replace with your actual sponsorship ID
    tx: transaction, // Your built transaction
  });
 
  // Step 2: User signs the sponsored transaction
  const signedTx = await userWallet.signTx(sponsoredTx, true);
 
  // Step 3: Submit the signed transaction to the network
  const txHash = await provider.submitTx(signedTx);
 
  console.log(`Transaction submitted successfully! Hash: ${txHash}`);
} catch (error) {
  console.error("Sponsorship failed:", error);
  // Handle sponsorship errors (insufficient funds, policy violations, etc.)
}

Complete Example

Here’s a complete working example:

const provider = BlockfrostProvider("...");
 
const sponsorshipId = "your_sponsorship_id"; // Replace with your actual sponsorship ID
 
const sdk = new Web3Sdk({
  projectId: "11111111-2222-3333-YOUR-PROJECTID", // Your project ID from dashboard
  apiKey: "YOUR_API_KEY", // Your API key from dashboard
  network: "testnet", // Use "testnet" for testing, "mainnet" for production
  privateKey: "YOUR_PRIVATE_KEY", // Developer's wallet private key (keep secure!)
  fetcher: provider, // Your Cardano provider for fetching data
  submitter: provider, // Your Cardano provider for submitting transactions
});
 
async function developerCreateTx() {
  /**
   * get wallet info
   */
  const userWallet = await getUserWallet();
  const userAddress = await userWallet.getChangeAddress();
 
  /**
   * build transaction
   */
  const txBuilder = new MeshTxBuilder({
    fetcher: provider,
  });
 
  const forgingScript = ForgeScript.withOneSignature(userAddress);
 
  const demoAssetMetadata = {
    name: "Mesh Token",
    image: "ipfs://QmRzicpReutwCkM6aotuKjErFCUD213DpwPq6ByuzMJaua",
    mediaType: "image/jpg",
    description: "This NFT was minted by Mesh (https://meshjs.dev/).",
  };
  const policyId = resolveScriptHash(forgingScript);
  const tokenName = "MeshToken";
  const tokenNameHex = stringToHex(tokenName);
  const metadata = { [policyId]: { [tokenName]: { ...demoAssetMetadata } } };
 
  const staticInfo = sdk.sponsorship.getStaticInfo();
 
  txBuilder
    .mint("1", policyId, tokenNameHex)
    .mintingScript(forgingScript)
    .metadataValue(721, 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();
  return unsignedTx;
}
 
async function runFullDemo() {
  const staticInfo = sdk.sponsorship.getStaticInfo();
 
  const tx = await developerCreateTx();
 
  const tx2 = await sdk.sponsorship.sponsorTx({
    sponsorshipId: sponsorshipId,
    tx: tx,
  });
 
  // 3. client sign
  const userWallet = await main.getUserWallet();
  const signedTx = await userWallet.signTx(tx2, true);
 
  const txHash = await provider.submitTx(signedTx);
  console.log("txHash", txHash);
}