> ## Documentation Index
> Fetch the complete documentation index at: https://docs.velumx.xyz/llms.txt
> Use this file to discover all available pages before exploring further.

# Reference

# SDK Reference

The `@velumx/sdk` package is the primary integration point for VelumX gasless transactions.

## Installation

```bash theme={null}
npm install @velumx/sdk
```

## Initialization

```typescript theme={null}
import { VelumXClient } from '@velumx/sdk';

const velumx = new VelumXClient({
  paymasterUrl: '/api/velumx/proxy', // Your secure backend proxy
  network: 'mainnet',                // 'mainnet' | 'testnet'
});
```

***

## Core Methods

### `estimateFee(params)`

Estimates the required fee in a SIP-010 token based on estimated gas units. **Always call this before building a USER\_PAYS transaction** — the response tells you the active policy, the fee amount, and the relayer address to pass into the paymaster.

```typescript theme={null}
const estimate = await velumx.estimateFee({
  feeToken: 'SP2C2YFP12AJZB4MABJBAJ55XECVS7E4PMMZ89YZR.aeusdc', // optional
  estimatedGas: 150000, // optional, defaults to 150000
});

// Returns:
// {
//   maxFee: string,           // fee in token micro-units → pass as fee-amount to paymaster
//   feeToken: string,         // resolved fee token principal
//   policy: 'DEVELOPER_SPONSORS' | 'USER_PAYS',
//   relayerAddress: string,   // relayer's Stacks address → pass as relayer to paymaster
// }
```

**`relayerAddress` is the source of truth for the relayer arg in USER\_PAYS transactions.** Do not hardcode it or read it from an env var — always use `estimate.relayerAddress`. The relayer address can change when VelumX rotates keys or when your project is reassigned to a different relayer node.

```typescript theme={null}
const estimate = await velumx.estimateFee({ feeToken: '...aeusdc', estimatedGas: 200_000 });

// DEVELOPER_SPONSORS: relayerAddress is not used in the transaction at all.
// USER_PAYS: pass it directly to the paymaster as principalCV.
if (estimate.policy === 'USER_PAYS') {
  if (!estimate.relayerAddress) throw new Error('Relayer address missing from fee estimate');
  // use estimate.relayerAddress in functionArgs below
}
```

***

### `sponsorBatch(transactions, options?)`

Sponsors up to **25 transactions in a single API call**. Each transaction is processed independently — one failure does not abort the rest. Always returns HTTP 200; check each item's `error` field for per-item failures.

**When to use batch vs. single `sponsor()`:**

* **Airdrop distributions** — submit tokens to many recipients without looping over `sponsor()`.
* **Multi-step DeFi setup** — approve + stake + claim in one round-trip instead of three sequential calls.
* **Queue draining** — flush a backlog of pending user actions (e.g. queued bridge withdrawals) in one shot.

```typescript theme={null}
const result = await velumx.sponsorBatch([
  { txHex: signedTx1 },
  { txHex: signedTx2 },
  { txHex: signedTx3 },
  // For USER_PAYS, add feeAmount per item:
  // { txHex: signedTx4, feeAmount: estimate.maxFee },
]);

result.results.forEach(item => {
  if ('error' in item) {
    console.error(`TX[${item.index}] failed:`, item.error);
  } else {
    console.log(`TX[${item.index}] sponsored:`, item.txid);
  }
});
console.log(result.summary);
// { total: 3, succeeded: 2, failed: 1 }
```

**Rate limits:** 5 batch calls/min per API key, 10/min per IP.

***

### `sponsor(signedTxHex, options?)`

The primary method. Submits a signed sponsored transaction to the VelumX relayer for co-signing and broadcast.

```typescript theme={null}
const result = await velumx.sponsor(signedTxHex, {
  feeToken: 'SP...aeusdc',  // required for USER_PAYS, omit for DEVELOPER_SPONSORS
  feeAmount: '250000',       // required for USER_PAYS
  network: 'mainnet',        // optional, defaults to client network
});

// Returns: { txid: string }
// Throws: RelayerError if rejected
```

**For DEVELOPER\_SPONSORS** — omit `feeToken` and `feeAmount`:

```typescript theme={null}
const { txid } = await velumx.sponsor(signedTxHex);
```

**For USER\_PAYS** — include fee params:

```typescript theme={null}
const { txid } = await velumx.sponsor(signedTxHex, {
  feeToken: estimate.feeToken,
  feeAmount: estimate.maxFee,
});
```

***

### `buildSponsoredContractCall(params)`

Helper that builds an unsigned sponsored `ContractCall` transaction ready for wallet signing.

```typescript theme={null}
import { buildSponsoredContractCall } from '@velumx/sdk';
import { uintCV, contractPrincipalCV } from '@stacks/transactions';

// Token CVs for the Bitflow STX/stSTX pool (real example)
const tokenX = contractPrincipalCV('SP4SZE494VC2YC5JYG7AYFQ44F5Q4PYV7DVMDPBG', 'wstx');
const tokenY = contractPrincipalCV('SP4SZE494VC2YC5JYG7AYFQ44F5Q4PYV7DVMDPBG', 'ststx');

const unsignedTx = await buildSponsoredContractCall({
  contractAddress: 'SPQC38PW542EQJ5M11CR25P7BS1CA6QT4TBXGB3M',
  contractName: 'stableswap-stx-ststx-v-1-2',
  functionName: 'swap-x-for-y',
  functionArgs: [
    uintCV(1n),           // pool id
    tokenX,               // token-x (STX)
    tokenY,               // token-y (stSTX)
    uintCV(1_000_000n),   // amount-in: 1 STX
    uintCV(990_000n),     // min-amount-out: 1% slippage
  ],
  publicKey: userPublicKey,   // user's Stacks public key — see note below
  nonce: 5n,                  // optional — see nonce note below
  network: 'mainnet',         // optional, defaults to 'mainnet'
});

// Returns: Uint8Array (serialized unsigned sponsored tx)
```

**Getting `publicKey`**: Use `stx_getAddresses` from `@stacks/connect` after wallet connection:

```typescript theme={null}
const result = await request('stx_getAddresses') as {
  addresses: Array<{ address: string; publicKey: string }>;
};
const publicKey = result.addresses.find(a => a.address === userAddress)?.publicKey
  ?? result.addresses[0].publicKey;
```

**Passing to `stx_signTransaction`**: `buildSponsoredContractCall` returns a `Uint8Array`. Some wallet versions expect a hex string. Convert if needed:

```typescript theme={null}
const txForSigning = unsignedTx instanceof Uint8Array
  ? Buffer.from(unsignedTx).toString('hex')
  : unsignedTx;

const signResult = await request('stx_signTransaction', {
  transaction: txForSigning,
  broadcast: false,
});
// signResult.transaction is always a hex string — pass directly to velumx.sponsor()
```

**Nonce handling**: If `nonce` is omitted, the SDK fetches it from the Stacks API. This is fine for single transactions, but for rapid sequential calls (e.g. batch setup flows where you build multiple txs before any confirms), fetch the nonce once and increment manually:

```typescript theme={null}
const accountRes = await fetch(`https://api.mainnet.hiro.so/v2/accounts/${userAddress}?proof=0`);
const { nonce: currentNonce } = await accountRes.json();

const tx1 = await buildSponsoredContractCall({ ..., nonce: BigInt(currentNonce) });
const tx2 = await buildSponsoredContractCall({ ..., nonce: BigInt(currentNonce) + 1n });
const tx3 = await buildSponsoredContractCall({ ..., nonce: BigInt(currentNonce) + 2n });
```

***

## Types

```typescript theme={null}
interface SponsorOptions {
  feeToken?: string;   // SIP-010 contract principal
  feeAmount?: string;  // fee in token micro-units
  network?: 'mainnet' | 'testnet';
}

interface FeeEstimateResult {
  maxFee: string;
  feeToken: string;
  policy: 'DEVELOPER_SPONSORS' | 'USER_PAYS';
  relayerAddress: string;
}

interface SponsorResult {
  txid: string;
  status: string;
}

interface BatchSponsorItem {
  /** Signed sponsored transaction hex */
  txHex: string;
  /** Fee in token micro-units — omit for DEVELOPER_SPONSORS */
  feeAmount?: string;
}

type BatchSponsorItemResult =
  | ({ index: number } & SponsorResult)
  | { index: number; error: string };

interface BatchSponsorResult {
  results: BatchSponsorItemResult[];
  summary: {
    total: number;
    succeeded: number;
    failed: number;
  };
}

interface ContractCallParams {
  contractAddress: string;
  contractName: string;
  functionName: string;
  functionArgs: ClarityValue[];
  publicKey: string;
  nonce?: bigint;
  network?: 'mainnet' | 'testnet';
}

class RelayerError extends Error {
  reason: string;
}
```

***

## Error Handling

`velumx.sponsor()` and `velumx.sponsorBatch()` throw `RelayerError` when the relayer rejects a request. Always catch it separately from generic errors so you can surface a useful message to the user.

```typescript theme={null}
import { VelumXClient, RelayerError } from '@velumx/sdk';

try {
  const { txid } = await velumx.sponsor(signedTxHex);
} catch (err) {
  if (err instanceof RelayerError) {
    // err.reason is a machine-readable string from the relayer
    // e.g. 'insufficient_relayer_balance', 'invalid_sponsor_sig', 'fee_too_low'
    console.error('Relayer rejected the transaction:', err.reason);
    showToast(`Transaction failed: ${err.reason}`);
  } else {
    // Network error, proxy misconfiguration, etc.
    throw err;
  }
}
```

For batch calls, per-item failures are returned inline — only a top-level network/auth failure throws:

```typescript theme={null}
try {
  const result = await velumx.sponsorBatch([
    { txHex: signedTx1 },
    { txHex: signedTx2 },
  ]);

  for (const item of result.results) {
    if ('error' in item) {
      console.warn(`TX ${item.index} failed: ${item.error}`);
    } else {
      console.log(`TX ${item.index} confirmed: ${item.txid}`);
    }
  }
} catch (err) {
  if (err instanceof RelayerError) {
    // Entire batch request was rejected (auth failure, rate limit, etc.)
    console.error('Batch rejected:', err.reason);
  }
}
```

***

## Complete Example — DEVELOPER\_SPONSORS

```typescript theme={null}
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' });

// Get the user's public key after wallet connection
const addrResult = await request('stx_getAddresses') as {
  addresses: Array<{ address: string; publicKey: string }>;
};
const userPublicKey = addrResult.addresses[0].publicKey;

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

// 1. Build unsigned sponsored tx
//    DEVELOPER_SPONSORS: call the protocol contract directly — no fee args needed
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
    STST_TOKEN,          // token-y
    uintCV(1_000_000n),  // amount-in (1 STX)
    uintCV(990_000n),    // min-amount-out (1% slippage)
  ],
  publicKey: userPublicKey,
});

// 2. User signs (no broadcast)
// buildSponsoredContractCall returns 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 — no fee params needed for DEVELOPER_SPONSORS
try {
  const { txid } = await velumx.sponsor(signResult.transaction);
  console.log('txid:', txid);
} catch (err) {
  if (err instanceof RelayerError) console.error('Rejected:', err.reason);
  else throw err;
}
```

## Complete Example — USER\_PAYS

```typescript theme={null}
import { buildSponsoredContractCall, RelayerError } from '@velumx/sdk';
import { uintCV, principalCV, contractPrincipalCV } from '@stacks/transactions';

// 1. Estimate fee — this gives you the policy, fee amount, AND relayer address
const estimate = await velumx.estimateFee({
  feeToken: 'SP2C2YFP12AJZB4MABJBAJ55XECVS7E4PMMZ89YZR.aeusdc',
  estimatedGas: 200_000,
});

// Guard: relayerAddress is required for USER_PAYS — always comes from estimateFee
if (!estimate.relayerAddress) throw new Error('Relayer address missing from fee estimate');

// 2. Build call to your paymaster contract
//    The paymaster atomically collects the fee then executes the action.
//    fee-amount, relayer, and fee-token are always the last three args.
//    relayer = estimate.relayerAddress — never hardcode this value.
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: userPublicKey,
});

// 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 the on-chain fee transfer
try {
  const { txid } = await velumx.sponsor(signResult.transaction, {
    feeToken: estimate.feeToken,
    feeAmount: estimate.maxFee,
  });
  console.log('txid:', txid);
} catch (err) {
  if (err instanceof RelayerError) console.error('Rejected:', err.reason);
  else throw err;
}
```

## Complete Example — Batch Sponsorship

`sponsorBatch` is useful any time you need to submit multiple independent transactions in one shot — without waiting for each to confirm before sending the next.

**Common use cases:**

* **Airdrop distributions** — send tokens to hundreds of recipients in a single API call instead of looping over `sponsor()`.
* **Multi-step DeFi setup** — approve + stake + claim in one batch rather than three sequential round-trips.
* **Processing a queue** — drain a backlog of pending user actions (e.g. queued bridge withdrawals) atomically.

Each transaction is processed independently. One failure does not block the rest.

```typescript theme={null}
import { RelayerError } from '@velumx/sdk';

// Build and sign each tx independently (same flow as single sponsor)
const [signedTx1, signedTx2, signedTx3] = await Promise.all([
  buildAndSign(params1, userPublicKey),
  buildAndSign(params2, userPublicKey),
  buildAndSign(params3, userPublicKey),
]);

try {
  const result = await velumx.sponsorBatch([
    { txHex: signedTx1 },
    { txHex: signedTx2 },
    { txHex: signedTx3 },
    // For USER_PAYS, add feeAmount per item:
    // { txHex: signedTx4, feeAmount: estimate.maxFee },
  ]);

  for (const item of result.results) {
    if ('error' in item) {
      console.warn(`TX[${item.index}] failed: ${item.error}`);
    } else {
      console.log(`TX[${item.index}] sponsored: ${item.txid}`);
    }
  }

  console.log(result.summary);
  // { total: 3, succeeded: 3, failed: 0 }
} catch (err) {
  if (err instanceof RelayerError) {
    // Top-level rejection — auth failure, rate limit, malformed request
    console.error('Batch rejected:', err.reason);
  }
}
```

**Rate limits:** 5 batch calls/min per API key, 10/min per IP.
