Requirements
- Target platform
- OpenClaw
- Install method
- Manual import
- Extraction
- Extract archive
- Prerequisites
- OpenClaw
- Primary doc
- SKILL.md
Create and manage private stablecoin wallets using Senddy's zero-knowledge protocol on Base. Use when building payment agents, bots, server-side apps, or any...
Create and manage private stablecoin wallets using Senddy's zero-knowledge protocol on Base. Use when building payment agents, bots, server-side apps, or any...
Hand the extracted package to your coding agent with a concrete install brief instead of figuring it out manually.
I downloaded a skill package from Yavira. Read SKILL.md from the extracted folder and install it by following the included instructions. Tell me what you changed and call out any manual steps you could not complete.
I downloaded an updated skill package from Yavira. Read SKILL.md from the extracted folder, compare it with my current installation, and upgrade it while preserving any custom configuration unless the package docs explicitly say otherwise. Summarize what changed and any follow-up checks I should run.
Build private stablecoin wallets with zero-knowledge proofs on Base. Senddy lets agents and apps hold, transfer, and withdraw USDC privately β no public on-chain linkage between deposits, transfers, and withdrawals.
5 steps to a working private wallet: npm install @senddy/node import { createSenddyAgent, toUSDC } from '@senddy/node'; import { randomBytes } from 'node:crypto'; // 1. Generate a seed (store this securely β it controls the wallet) const seed = randomBytes(32); // 2. Create the agent (only seed + apiKey required) const agent = createSenddyAgent({ seed, apiKey: process.env.SENDDY_API_KEY!, }); // 3. Initialize (derives keys, loads WASM prover, first sync) await agent.init(); // 4. Get your receive address console.log('Address:', agent.getReceiveAddress()); // senddy1... // 5. Check balance, transfer, withdraw const balance = await agent.getBalance(); await agent.transfer('senddy1...recipient', toUSDC('5.00')); await agent.withdraw('0xPublicAddress...', toUSDC('10.00')); Set SENDDY_API_KEY in your environment. Get one at https://senddy.com.
Only seed and apiKey are required. Everything else defaults to the canonical Base mainnet deployment: createSenddyAgent({ seed: Uint8Array, // 32-byte secret (REQUIRED) apiKey: string, // 'sk_live_...' (REQUIRED) })
createSenddyAgent({ seed: Uint8Array, apiKey: string, apiUrl: string, // default: 'https://senddy.com/api/v1' chainId: number, // default: 8453 (Base) rpcUrl: string, // default: 'https://mainnet.base.org' pool: '0x...', // default: canonical pool usdc: '0x...', // default: canonical USDC permit2: '0x...', // default: canonical Permit2 subgraphUrl: string, // default: canonical subgraph attestorUrl: string, // override: bypass API gateway for attestor relayerUrl: string, // override: bypass API gateway for relayer context: string, // default: 'main' (for multi-agent from one seed) debug: boolean, // default: false })
The apiKey authenticates all requests through the Senddy API gateway: Attestor β ZK proof verification (TEE-based, off-chain) Relayer β Gas-sponsored transaction submission (you don't pay gas) Usernames β Resolve senddy1... addresses to human-readable names Merkle tree β Proof generation helper endpoints
const balance = await agent.getBalance(); // { shares: bigint, estimatedUSDC: bigint, noteCount: number } estimatedUSDC is in 6-decimal USDC units. shares are 18-decimal internal units.
// Simple transfer const result = await agent.transfer('senddy1...', toUSDC('25.00')); // { txHash, shares, nullifierCount, circuit: 'spend' | 'spend9' } // With memo (max 31 ASCII chars) await agent.transfer('senddy1...', toUSDC('5.00'), { memo: 'Payment' }); // Anonymous (hide sender identity) await agent.transfer('senddy1...', toUSDC('5.00'), { anonymous: true }); Auto-escalation: tries spend circuit (3 inputs), escalates to spend9 (9 inputs), and auto-consolidates if neither suffices.
Withdraw to a public Ethereum address (USDC leaves the privacy pool): const result = await agent.withdraw('0x...', toUSDC('50.00')); // { txHash, shares, to, circuit }
State is synced automatically on init(). For long-running agents: // Manual sync const result = await agent.sync(); // { newNotes, newSpent, unspentCount, durationMs }
When notes fragment (many small UTXOs), consolidate them: const result = await agent.consolidate({ noteThreshold: 16 }); // { txHash, notesConsolidated, totalShares, needsMore }
const address = agent.getReceiveAddress(); // 'senddy1qw508d6q...' Share this address to receive private transfers. It's derived from your viewing public key and is deterministic for a given seed + context.
const txs = await agent.getTransactions({ limit: 50 }); // Array<{ id, type, shares, estimatedUSDC, counterparty, memo, timestamp, status }>
agent.on('balanceChange', (balance) => { /* ... */ }); agent.on('sync', (result) => { /* ... */ }); agent.on('noteStrategy', (event) => { /* escalation/consolidation info */ }); agent.on('error', (err) => { /* ... */ });
Use the context parameter to derive different wallets from the same seed: const treasury = createSenddyAgent({ seed, apiKey, context: 'treasury' }); const payroll = createSenddyAgent({ seed, apiKey, context: 'payroll' }); const tips = createSenddyAgent({ seed, apiKey, context: 'tips' }); Each context produces different keys and a different receive address.
Always use toUSDC() to convert human-readable amounts: import { toUSDC } from '@senddy/node'; toUSDC('1.00') // 1_000_000n toUSDC('100') // 100_000_000n toUSDC('0.01') // 10_000n toUSDC(50) // 50_000_000n Raw amounts are in USDC's 6-decimal format (bigint).
import { isValidSenddyAddress } from '@senddy/node'; isValidSenddyAddress('senddy1qw508d6q...'); // true isValidSenddyAddress('0x...'); // false
import { SHARED_CONTRACTS, V3_CONTRACTS } from '@senddy/node'; SHARED_CONTRACTS.USDC // '0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913' SHARED_CONTRACTS.Permit2 // '0x000000000022D473030F116dDEE9F6B43aC78BA3' V3_CONTRACTS.Pool // '0x0b4e0C18e4005363A10a93cb30e0a11A88bee648'
agent.destroy(); // Zeros key material and cleans up resources Always call destroy() when done (especially in short-lived processes).
Do NOT create a new agent and call init() on every request. The init() call takes 5-15 seconds because it compiles WASM, loads the SRS into memory, and syncs the full state from the subgraph. Re-initializing on every request will make the agent unusably slow. Instead, run the agent as a long-lived background process that initializes once and handles requests over a local HTTP API or Unix socket: // senddy-daemon.ts β run once, stays alive forever import { createSenddyAgent, toUSDC, isValidSenddyAddress } from '@senddy/node'; import { createServer } from 'node:http'; const agent = createSenddyAgent({ seed: Buffer.from(process.env.AGENT_SEED_HEX!, 'hex'), apiKey: process.env.SENDDY_API_KEY!, }); await agent.init(); console.log(`Agent ready: ${agent.getReceiveAddress()}`); // Periodic sync to stay up-to-date setInterval(() => agent.sync().catch(console.error), 30_000); // Simple JSON-RPC over HTTP const server = createServer(async (req, res) => { if (req.method !== 'POST') { res.writeHead(405); res.end(); return; } const chunks: Buffer[] = []; for await (const chunk of req) chunks.push(chunk as Buffer); const { method, params } = JSON.parse(Buffer.concat(chunks).toString()); try { let result: any; switch (method) { case 'getBalance': result = await agent.getBalance(); result = { ...result, shares: result.shares.toString(), estimatedUSDC: result.estimatedUSDC.toString() }; break; case 'getAddress': result = { address: agent.getReceiveAddress() }; break; case 'transfer': result = await agent.transfer(params.to, toUSDC(params.amount), params.opts); result = { ...result, shares: result.shares.toString() }; break; case 'withdraw': result = await agent.withdraw(params.to, toUSDC(params.amount)); result = { ...result, shares: result.shares.toString() }; break; case 'sync': result = await agent.sync(); break; case 'getTransactions': result = await agent.getTransactions(params); break; default: res.writeHead(400); res.end(JSON.stringify({ error: `Unknown method: ${method}` })); return; } res.writeHead(200, { 'Content-Type': 'application/json' }); res.end(JSON.stringify({ ok: true, result })); } catch (err: any) { res.writeHead(500, { 'Content-Type': 'application/json' }); res.end(JSON.stringify({ ok: false, error: err.message })); } }); const PORT = parseInt(process.env.SENDDY_DAEMON_PORT ?? '0'); server.listen(PORT, '127.0.0.1', () => { const addr = server.address() as import('node:net').AddressInfo; console.log(`Senddy daemon on :${addr.port}`); }); Start the daemon once. It picks a free port automatically (or set SENDDY_DAEMON_PORT to pin one). Read the port from stdout and use it for all subsequent requests: AGENT_SEED_HEX="your64charhex..." SENDDY_API_KEY="sk_live_..." \ npx tsx senddy-daemon.ts # Output: "Senddy daemon on :18790" (port varies) Then query it instantly from any client: # Check balance (use the port printed at startup) curl -s -X POST http://127.0.0.1:18790 \ -d '{"method":"getBalance"}' | jq # Transfer curl -s -X POST http://127.0.0.1:18790 \ -d '{"method":"transfer","params":{"to":"senddy1...","amount":"5.00"}}' | jq For a complete daemon example with process management, see examples.md.
No deposits: Agents can't deposit directly. Fund them by sending a private transfer from a funded wallet (browser app or another agent). In-memory storage: Notes are lost on process restart. The agent re-syncs from the subgraph on init(), so this is safe β just costs a few seconds. First init downloads SRS: The first init() downloads a ~16 MB structured reference string (cached to ~/.bb-crs/ for subsequent runs). WASM compilation: Even with cached SRS, init() takes 5-15s to compile the WASM prover. Always run the agent persistently, not per-request. Shares vs USDC: Internal values are in 18-decimal shares. Use balance.estimatedUSDC and toUSDC() for human-readable amounts.
For full type signatures and advanced composition, see reference.md For copy-paste usage examples, see examples.md
Agent frameworks, memory systems, reasoning layers, and model-native orchestration.
Largest current source with strong distribution and engagement signals.