Skip to main content

Getting Started

Integrate gasless transactions into your Stacks dApp in minutes.
No Smart Wallets Required: VelumX uses Stacks’ native sponsored transaction capability. Users keep their existing Leather, Xverse, or OKX wallets.

1. Get Your API Key

Log in to the VelumX Dashboard and create a new project. You’ll receive:
  • A VELUMX_API_KEY — your server-side credential
  • A Relayer Address — the unique wallet that co-signs and pays STX gas for your users
Fund your relayer: Send STX to your Relayer Address from the Dashboard. This is your “gas tank” — the STX used to sponsor your users’ transactions.

2. Install the SDK

npm install @velumx/sdk

3. Set Up a Secure Proxy

Your API key must never be exposed in client-side code. Create a server-side proxy that injects it on every request to the VelumX Relayer.
// app/api/velumx/[...path]/route.ts
export async function POST(req: Request, { params }: { params: { path: string[] } }) {
  const targetUrl = `https://api.velumx.xyz/api/v1/${params.path.join('/')}`;
  const response = await fetch(targetUrl, {
    method: 'POST',
    headers: {
      'x-api-key': process.env.VELUMX_API_KEY!,
      'Content-Type': 'application/json',
    },
    body: await req.text(),
  });
  return Response.json(await response.json());
}
Point VelumXClient.paymasterUrl at whichever route you create — the SDK doesn’t care about the framework.

4. Initialize the SDK

import { VelumXClient } from '@velumx/sdk';

const velumx = new VelumXClient({
  paymasterUrl: '/api/velumx',  // your secure proxy base path
  network: 'mainnet',           // 'mainnet' | 'testnet'
});

5. Get the User’s Public Key

buildSponsoredContractCall requires the user’s Stacks public key to construct the transaction. Fetch it once after wallet connection and cache it for the session.
import { request } from '@stacks/connect';

async function getUserPublicKey(userAddress: string): Promise<string> {
  const result = await request('stx_getAddresses') as {
    addresses: Array<{ address: string; publicKey: string }>;
  };
  const entry = result.addresses.find(a => a.address === userAddress)
    ?? result.addresses[0];
  if (!entry?.publicKey) throw new Error('Wallet did not return a public key');
  return entry.publicKey;
}

6. DEVELOPER_SPONSORS — Simplest Integration

The developer’s relayer pays STX gas. Users pay nothing. No paymaster contract needed. This example calls the Bitflow STX/stSTX stableswap pool directly — a real, runnable contract call.
import { VelumXClient, buildSponsoredContractCall, RelayerError } from '@velumx/sdk';
import { uintCV, contractPrincipalCV } from '@stacks/transactions';
import { request } from '@stacks/connect';

const velumx = new VelumXClient({ paymasterUrl: '/api/velumx', network: 'mainnet' });

// Token contracts for the STX/stSTX pool
const STX_TOKEN  = contractPrincipalCV('SP4SZE494VC2YC5JYG7AYFQ44F5Q4PYV7DVMDPBG', 'wstx');
const STST_TOKEN = contractPrincipalCV('SP4SZE494VC2YC5JYG7AYFQ44F5Q4PYV7DVMDPBG', 'ststx');

const publicKey = await getUserPublicKey(userAddress);

// 1. Build the sponsored contract call
const unsignedTx = await buildSponsoredContractCall({
  contractAddress: 'SPQC38PW542EQJ5M11CR25P7BS1CA6QT4TBXGB3M',
  contractName: 'stableswap-stx-ststx-v-1-2',
  functionName: 'swap-x-for-y',
  functionArgs: [
    uintCV(1n),           // pool id
    STX_TOKEN,            // token-x (STX)
    STST_TOKEN,           // token-y (stSTX)
    uintCV(1_000_000n),   // amount-in: 1 STX in micro-units
    uintCV(990_000n),     // min-amount-out: 1% slippage
  ],
  publicKey,
});

// 2. User signs — no broadcast
// buildSponsoredContractCall returns a Uint8Array — convert to hex for the wallet
const txForSigning = unsignedTx instanceof Uint8Array
  ? Buffer.from(unsignedTx).toString('hex')
  : unsignedTx;

const signResult = await request('stx_signTransaction', {
  transaction: txForSigning,
  broadcast: false,
});

// 3. Relayer co-signs and broadcasts
try {
  const { txid } = await velumx.sponsor(signResult.transaction);
  console.log('Transaction ID:', txid);
} catch (err) {
  if (err instanceof RelayerError) {
    console.error('Relayer rejected:', err.reason);
  } else {
    throw err;
  }
}

7. USER_PAYS — Fee Collected in SIP-010 Token

The user pays a token fee. You need a paymaster contract that atomically collects the fee and executes the action. Option A: Use the VelumX DeFi reference paymaster (for Bitflow swaps and USDCx bridge). Option B: Deploy your own paymaster contract. Copy velumx-defi-paymaster-v1-1.clar as a template and replace the protocol calls with your own logic. See The Paymaster Pattern for a full walkthrough.
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;
  }
}

8. Testing on Testnet

To test the full flow before going to mainnet:
  1. Switch network to 'testnet' in VelumXClient and buildSponsoredContractCall.
  2. Get testnet STX for your relayer from the Stacks Testnet Faucet.
  3. Get testnet STX for your test wallet from the same faucet (needed to hold tokens for USER_PAYS testing).
  4. Use testnet contract addresses — the Bitflow testnet deployer is ST1PQHQKV0RJXZFY1DGX8MNSNYVE3VGZJSRTPGZGM.
  5. Your VELUMX_API_KEY works on both networks — the relayer routes based on the network field in each request.
// Testnet client
const velumx = new VelumXClient({
  paymasterUrl: '/api/velumx',
  network: 'testnet',
});

9. Export Your Relayer Key

You can export your relayer’s private key from the Dashboard at any time. This gives you full custody of your sponsorship funds and any fee revenue collected via USER_PAYS.