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:
| Layer | Enforcement | Description |
|---|---|---|
| SDK | Off-chain | SpendingLimitsManager / SpendingTracker checks before signing |
| Contract | On-chain | Smart contract enforces daily/per-tx limits |
| Post-Conditions | Protocol (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:
- Set conservative defaults: Start low, increase as needed
- Use token-specific limits: Stablecoins may need different limits
- Review periodically: Adjust limits based on usage patterns
- 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.