Guides
Spending Limits

Spending Limits

Spending limits add an extra layer of security to your vault by capping how much can be transferred within a time period.

Overview

Spending limits protect against:

  • Compromised session keys
  • Stolen devices (before recovery)
  • Accidental large transfers

Three-Layer Spending Safety

Veridex enforces spending limits at three levels:

LayerEnforcementDescription
SDKOff-chainSpendingLimitsManager / SpendingTracker checks before signing
ContractOn-chainSmart contract enforces daily/per-tx limits
Post-ConditionsProtocol (Stacks)Stacks native guarantee on max transfer amounts

Core SDK: Check Spending Limits

import { createSDK } from '@veridex/sdk';
import { ethers } from 'ethers';
 
const sdk = createSDK('base', {
  network: 'testnet',
  relayerUrl: 'https://relayer.veridex.network',
});
 
const { credential } = await sdk.passkey.authenticate();
 
// Get current on-chain spending limits
const limits = await sdk.getSpendingLimits();
console.log('Daily limit:', limits.dailyLimit);
console.log('Daily used:', limits.dailyUsed);
console.log('Daily remaining:', limits.dailyRemaining);
console.log('Per-tx limit:', limits.perTransactionLimit);
console.log('Resets at:', new Date(limits.resetsAt));

Check Before Transfer

// Check if a specific amount is within limits
const check = await sdk.checkSpendingLimit(ethers.parseUnits('500', 6));
 
if (check.allowed) {
  console.log('Transfer allowed!');
  // Proceed with prepareTransfer + transfer
} else {
  console.log('Blocked:', check.message);
  console.log('Suggestions:', check.suggestions);
}

Formatted Limits for UI

const formatted = await sdk.getFormattedSpendingLimits();
 
console.log(`${formatted.dailyUsedPercentage}% of daily limit used`);
console.log(`Resets in: ${formatted.timeUntilReset}`);
console.log(`Daily: ${formatted.dailyUsed} / ${formatted.dailyLimit}`);

Update Spending Limits

// Prepare a transaction to update the daily limit
const prepared = await sdk.prepareSetDailyLimit(ethers.parseEther('5.0'));
 
// Execute (requires passkey signature)
const result = await sdk.executeTransfer(prepared, signer);
console.log('Limit updated:', result.transactionHash);

Emergency Pause

// Prepare an emergency pause transaction
const prepared = await sdk.preparePauseVault();
const result = await sdk.executeTransfer(prepared, signer);
console.log('Vault paused:', result.transactionHash);

Agent SDK: USD-Denominated Limits

The Agent SDK provides USD-aware spending limits via SpendingTracker:

import { createAgentWallet } from '@veridex/agentic-payments';
 
const agent = await createAgentWallet({
  masterCredential: { /* ... */ },
  session: {
    dailyLimitUSD: 100,          // $100/day
    perTransactionLimitUSD: 25,  // $25 max per tx
    expiryHours: 24,
    allowedChains: [10004],      // Base Sepolia
  },
});
 
// Check session spending status
const status = agent.getSessionStatus();
console.log('Daily limit:', `$${status.limits!.dailyLimitUSD}`);
console.log('Spent today:', `$${status.totalSpentUSD.toFixed(2)}`);
console.log('Remaining:', `$${status.remainingDailyLimitUSD.toFixed(2)}`);
 
// Set up spending alerts
agent.onSpendingAlert((alert) => {
  if (alert.type === 'approaching_limit') {
    console.warn('Approaching daily limit!');
  } else if (alert.type === 'limit_exceeded') {
    console.error('Transaction blocked by limit');
  }
});

Stacks: StacksSpendingTracker

For Stacks, the Agent SDK provides a specialized tracker with Pyth pricing:

import { StacksSpendingTracker } from '@veridex/agentic-payments';
 
const tracker = new StacksSpendingTracker({
  dailyLimitUSD: 100,
  network: 'testnet',
});
 
// Convert microSTX to USD using real-time Pyth price
const usdValue = await tracker.convertToUSD(1000000n, 'STX');
console.log('1 STX =', usdValue, 'USD');
 
// Check if a payment is within limits
const allowed = await tracker.checkPayment(5000000n); // 5 STX
console.log('Payment allowed:', allowed);

React Example

import { useState, useEffect } from 'react';
import { createSDK } from '@veridex/sdk';
 
const sdk = createSDK('base', { 
  network: 'testnet',
  relayerUrl: 'https://relayer.veridex.network',
});
 
export function SpendingLimitsPanel() {
  const [formatted, setFormatted] = useState<any>(null);
  const [loading, setLoading] = useState(true);
 
  useEffect(() => {
    loadLimits();
  }, []);
 
  const loadLimits = async () => {
    const data = await sdk.getFormattedSpendingLimits();
    setFormatted(data);
    setLoading(false);
  };
 
  if (loading) return <div>Loading limits...</div>;
  if (!formatted) return <div>No limits configured</div>;
 
  const percentage = formatted.dailyUsedPercentage;
 
  return (
    <div className="p-4 border rounded-lg space-y-4">
      <h3 className="font-bold">Spending Limits</h3>
 
      <div className="bg-gray-50 p-3 rounded">
        <p>Daily: {formatted.dailyUsed} / {formatted.dailyLimit}</p>
        <p>Per-tx max: {formatted.perTransactionLimit}</p>
        <p className="text-sm text-gray-500">
          Resets in: {formatted.timeUntilReset}
        </p>
      </div>
 
      <div className="h-3 bg-gray-200 rounded-full overflow-hidden">
        <div
          className={`h-full transition-all ${
            percentage > 90 ? 'bg-red-500' : 
            percentage > 70 ? 'bg-yellow-500' : 'bg-green-500'
          }`}
          style={{ width: `${Math.min(percentage, 100)}%` }}
        />
      </div>
      <p className="text-sm text-gray-500">
        {percentage.toFixed(1)}% of daily limit used
      </p>
    </div>
  );
}

Limit Exceeded Behavior

Always check limits before attempting a transfer:

import { ethers } from 'ethers';
 
const amount = ethers.parseUnits('10000', 6);
const check = await sdk.checkSpendingLimit(amount);
 
if (!check.allowed) {
  console.log('Transfer blocked:', check.message);
  console.log('Suggestions:', check.suggestions);
  // Suggestions might include: "Wait for daily reset" or "Reduce amount to X"
}

For Agent SDK:

import { AgentPaymentError, AgentPaymentErrorCode } from '@veridex/agentic-payments';
 
try {
  await agent.pay({ chain: 10004, token: 'USDC', amount: '10000000000', recipient: '0x...' });
} catch (error) {
  if (error instanceof AgentPaymentError && error.code === AgentPaymentErrorCode.LIMIT_EXCEEDED) {
    console.log('Agent spending limit exceeded:', error.message);
  }
}

Security Considerations

⚠️

Spending limits are enforced on-chain and cannot be bypassed by the SDK or relayer. Changing limits requires passkey authentication.

Best practices:

  1. Set conservative defaults: Start low, increase as needed
  2. Use token-specific limits: Stablecoins may need different limits
  3. Review periodically: Adjust limits based on usage patterns
  4. Combine with session keys: Sessions have their own limits too

Stacks Post-Conditions

On Stacks, spending limits have an additional protocol-level layer via Post-Conditions:

import { buildStxWithdrawalPostConditions, validateStacksPostConditions } from '@veridex/sdk';
 
// Build post-conditions that guarantee max transfer amount
const postConditions = buildStxWithdrawalPostConditions({
  vaultAddress: 'ST1CKNN84MDPCJRQDHDCY34GMRKQ3TASNNDJQDRTN.veridex-vault',
  amount: 1000000n, // 1 STX max
});
 
// Validate before submitting
const validation = validateStacksPostConditions(postConditions);
if (!validation.valid) {
  console.warn('Post-condition issues:', validation.warnings);
}

Post-Conditions are enforced at the Stacks protocol level and cannot be bypassed by any contract or SDK code.

Next Steps