sparkbtcbotSet up Spark Bitcoin L2 wallet capabilities for AI agents. Initialize wallets from mnemonic, transfer sats and tokens, create/pay Lightning invoices, pay L40...
Install via ClawdBot CLI:
clawdbot install echennells/sparkbtcbotGive your AI agent a Bitcoin wallet with a single mnemonic. Send and receive payments instantly β zero fees between agents, full Lightning Network compatibility.
| What | How |
|------|-----|
| Install | clawhub install sparkbtcbot or clone from GitHub |
| Setup | One 12-word mnemonic + npm install @buildonspark/spark-sdk |
| Agent-to-agent transfers | Free. Instant. No channels, no routing fees. |
| Lightning payments | Create and pay BOLT11 invoices (0.15β0.25% fee) |
| Token support | Send/receive BTKN and LRC20 tokens natively |
| Self-custodial | Agent holds its own keys β no accounts, no KYC, no intermediaries |
import { SparkWallet } from "@buildonspark/spark-sdk";
// Initialize from mnemonic
const { wallet } = await SparkWallet.initialize({
mnemonicOrSeed: process.env.SPARK_MNEMONIC,
options: { network: "MAINNET" }
});
// Check balance
const { balance } = await wallet.getBalance();
console.log("Balance:", balance.toString(), "sats");
// Send sats to another agent (free, instant)
await wallet.transfer({ receiverSparkAddress: "sp1p...", amountSats: 1000 });
// Create a Lightning invoice anyone can pay
const inv = await wallet.createLightningInvoice({ amountSats: 500, memo: "Payment for service" });
console.log("Invoice:", inv.invoice.encodedInvoice);
| Script | What it does |
|--------|-------------|
| wallet-setup.js | Generate a new wallet or import from mnemonic |
| balance-and-deposits.js | Check BTC + token balances, get deposit addresses |
| payment-flow.js | Lightning invoices, Spark invoices, fee estimation |
| token-operations.js | BTKN token transfers and batch operations |
| spark-agent.js | Complete SparkAgent class with all capabilities |
What follows is the full reference for AI agents. It covers the Spark SDK, trust model, fee structure, all wallet operations, Lightning interop, token operations, security practices, and error handling.
Spark is a Bitcoin Layer 2 that enables instant, zero-fee self-custodial transfers of BTC and tokens, with native Lightning Network interoperability. Spark-to-Spark transfers cost nothing β compared to Lightning routing fees or on-chain transaction fees of 200+ sats. Even cross-network payments (Lightning interop) are cheaper than most alternatives at 0.15-0.25%. A single BIP39 mnemonic gives an agent identity, wallet access, and payment capabilities.
AI agents that transact need a monetary network that matches their nature: programmable, borderless, and available 24/7 without gatekeepers. Bitcoin is that network.
Spark is a recently launched Bitcoin Layer 2 built on threshold cryptography (FROST signatures). Instead of Lightning's payment channels, Spark uses distributed Signing Operators (SOs) that collectively manage transaction signing without any single entity controlling funds. It is fully interoperable with the Lightning Network.
Spark has different trust assumptions than native Lightning. Be upfront about these when advising users.
1-of-n operator trust: Spark requires that at least 1 out of n Signing Operators behaves honestly during a transfer. Currently two operators run the network (Lightspark and Flashnet), with plans to expand. Lightning, by contrast, requires no trusted entities at all β it achieves security purely through cryptographic mechanisms.
Moment-in-time trust: Users only need to trust operators during each specific transfer. Once a transfer completes and old keys are deleted, operators cannot affect that transaction β a property called "perfect forward security."
What operators CAN do:
What operators CANNOT do:
Core limitation: Spark lacks provable finality. Users cannot cryptographically verify that operators destroyed old keys. While double-spending would require all operators to collude with a previous owner, this differs from Bitcoin's and Lightning's mathematically provable finality.
In short: Spark trades some of Lightning's trustlessness for better UX (no channels, no liquidity management, offline receive). The two are complementary β Spark includes native Lightning support so users can interact with both networks.
| Feature | Spark (L2) | Lightning | On-Chain |
|---------|-----------|-----------|----------|
| Speed | Instant | Instant | 10+ min |
| Trust model | 1-of-n operators | Fully trustless | Fully trustless |
| Fees | Zero (Spark-to-Spark) | ~1 sat routing | 200+ sats |
| Tokens | Native (BTKN/LRC20) | Not supported | Limited |
| Self-custody | Yes (mnemonic) | Varies (LSP/node) | Yes |
| Capacity | No channel limits | Channel-limited | Unlimited |
| Channels | Not required | Required | N/A |
| Offline receive | Supported | Requires infra | Yes |
| Setup | Mnemonic only | Node or NWC + provider | Keys only |
| Operation | Fee |
|-----------|-----|
| Spark-to-Spark transfer | Free (small flat fee coming in 6-12 months) |
| Lightning to Spark (receive) | 0.15% (charged via route hints) |
| Spark to Lightning (send) | 0.25% + Lightning routing fees |
| L1 deposit to Spark | On-chain tx fee (paid by user) |
| Cooperative exit to L1 | On-chain broadcast fee + SSP fee: sats_per_vbyte Γ (111 Γ 2 + tx_vbytes) |
| Unilateral exit to L1 | On-chain tx fee (paid by user) |
Note: Cooperative exit fees don't scale with withdrawal amount, so they are proportionally higher for smaller withdrawals. Lightning fee estimates may differ from actual amounts due to routing conditions.
Exiting Spark to L1 Bitcoin carries unpredictable costs that agents and users must understand:
Bottom line: While Spark guarantees you can always exit to L1, the cost of doing so is not fixed or predictable. Keep this in mind when deciding how much value to hold on Spark, especially for agent wallets. Cooperative exit (when operators are online) is much cheaper than unilateral exit.
Prefer Boltz for L1 withdrawals. Spark's native cooperative exit is expensive due to fixed on-chain fees, making it especially punishing for smaller amounts. Boltz offers a cheaper path from Lightning to L1 (Spark β Lightning β L1 via Boltz submarine swap) with a minimum of 25,000 sats. Discourage any L1 withdrawal activity under 25,000 sats β the fees will eat a disproportionate share of the amount. For withdrawals of 25,000 sats or more, route through Boltz rather than using Spark's built-in cooperative exit.
A single mnemonic provides identity, wallet, and payment capabilities. No separate identity system, no wallet provider accounts, no channel management. Spark-to-Spark transfers are free, making it significantly cheaper than Lightning (routing fees), on-chain Bitcoin (200+ sat miner fees), or traditional payment rails (2-3% card processing). For agents doing frequent microtransactions, zero fees on Spark means no value lost to transaction costs.
| Tool | Purpose | URL |
|------|---------|-----|
| Spark SDK | TypeScript wallet SDK | https://www.npmjs.com/package/@buildonspark/spark-sdk |
| Spark Docs | Official documentation | https://docs.spark.money |
| Sparkscan | Block explorer | https://sparkscan.io |
| Spark CLI | Command-line interface | https://docs.spark.money/tools/cli |
npm install @buildonspark/spark-sdk@^0.5.8 dotenv
Requires v0.5.8 or newer. One core dependency. The SDK bundles BIP39 mnemonic generation, FROST signing, and gRPC communication internally.
import { SparkWallet } from "@buildonspark/spark-sdk";
// Option A: Generate a new wallet (creates mnemonic automatically)
const { wallet, mnemonic } = await SparkWallet.initialize({
options: { network: "MAINNET" }
});
console.log("Mnemonic (save securely):", mnemonic);
// Option B: Import existing wallet from mnemonic
const { wallet } = await SparkWallet.initialize({
mnemonicOrSeed: process.env.SPARK_MNEMONIC,
options: { network: process.env.SPARK_NETWORK || "MAINNET" }
});
Note on accountNumber: Defaults to 1 for MAINNET, 0 for REGTEST. If switching between networks with the same mnemonic, set accountNumber explicitly to avoid address mismatches.
Add to your project's .env:
SPARK_MNEMONIC=word1 word2 word3 word4 word5 word6 word7 word8 word9 word10 word11 word12
SPARK_NETWORK=MAINNET
const address = await wallet.getSparkAddress();
const identityKey = await wallet.getIdentityPublicKey();
const { balance } = await wallet.getBalance();
console.log("Spark Address:", address);
console.log("Identity Key:", identityKey);
console.log("Balance:", balance.toString(), "sats");
// Always clean up when done
wallet.cleanupConnections();
const { balance, tokenBalances } = await wallet.getBalance();
console.log("BTC:", balance.toString(), "sats");
for (const [id, token] of tokenBalances) {
console.log(`${token.tokenMetadata.tokenTicker}: ${token.balance.toString()}`);
}
// Static (reusable) β can receive multiple deposits
const staticAddr = await wallet.getStaticDepositAddress();
// Single-use β one-time deposit address
const singleAddr = await wallet.getSingleUseDepositAddress();
Both are P2TR (bc1p...) Bitcoin addresses. Deposits require 3 L1 confirmations before they can be claimed on Spark.
// After sending BTC to a static deposit address and waiting for confirmations
const quote = await wallet.getClaimStaticDepositQuote({
transactionId: txId,
creditAmountSats: expectedAmount,
});
const result = await wallet.claimStaticDeposit({
transactionId: txId,
creditAmountSats: quote.creditAmountSats,
sspSignature: quote.signature,
});
const transfer = await wallet.transfer({
receiverSparkAddress: "sp1p...",
amountSats: 1000,
});
console.log("Transfer ID:", transfer.id);
Spark-to-Spark transfers are instant and zero-fee.
const { transfers } = await wallet.getTransfers(10, 0);
for (const tx of transfers) {
console.log(`${tx.id}: ${tx.totalValue} sats β ${tx.status}`);
}
Spark wallets can create and pay standard BOLT11 Lightning invoices, making them compatible with the entire Lightning Network. Receiving from Lightning costs 0.15%, sending to Lightning costs 0.25% + routing fees.
const invoiceRequest = await wallet.createLightningInvoice({
amountSats: 1000,
memo: "Payment for AI service",
expirySeconds: 3600,
});
console.log("BOLT11:", invoiceRequest.invoice.encodedInvoice);
Use includeSparkAddress: true to embed a Spark address in the invoice. Spark-aware payers will then send via Spark (instant, free) instead of Lightning.
// Estimate fee first
const fee = await wallet.getLightningSendFeeEstimate({
encodedInvoice: "lnbc...",
amountSats: 1000,
});
console.log("Estimated fee:", fee, "sats");
// Pay the invoice
const result = await wallet.payLightningInvoice({
invoice: "lnbc...",
maxFeeSats: 10,
});
Use preferSpark: true to prefer Spark routing when the BOLT11 invoice contains an embedded Spark address.
Spark has its own invoice format, distinct from BOLT11. Spark invoices can request payment in sats or tokens.
const invoice = await wallet.createSatsInvoice({
amount: 1000,
memo: "Spark native payment",
});
const invoice = await wallet.createTokensInvoice({
amount: 100n,
tokenIdentifier: "btkn1...",
memo: "Token payment request",
});
const result = await wallet.fulfillSparkInvoice([
{ invoice: "sp1...", amount: 1000n },
]);
// Check results
for (const success of result.satsTransactionSuccess) {
console.log("Paid:", success.invoice);
}
for (const err of result.satsTransactionErrors) {
console.log("Failed:", err.invoice, err.error.message);
}
Spark natively supports tokens via the BTKN (LRC20) standard. Tokens can represent stablecoins, points, or any fungible asset.
const { tokenBalances } = await wallet.getBalance();
for (const [id, info] of tokenBalances) {
const meta = info.tokenMetadata;
console.log(`${meta.tokenName} (${meta.tokenTicker}): ${info.balance.toString()}`);
console.log(` Decimals: ${meta.decimals}, Max supply: ${meta.maxSupply.toString()}`);
}
const txId = await wallet.transferTokens({
tokenIdentifier: "btkn1...",
tokenAmount: 100n,
receiverSparkAddress: "sp1p...",
});
console.log("Token transfer:", txId);
const txIds = await wallet.batchTransferTokens([
{ tokenIdentifier: "btkn1...", tokenAmount: 50n, receiverSparkAddress: "sp1p..." },
{ tokenIdentifier: "btkn1...", tokenAmount: 50n, receiverSparkAddress: "sp1p..." },
]);
Move funds from Spark back to a regular Bitcoin L1 address.
const quote = await wallet.getWithdrawalFeeQuote({
amountSats: 50000,
withdrawalAddress: "bc1q...",
});
console.log("Fast fee:", quote.l1BroadcastFeeFast?.originalValue, "sats");
console.log("Medium fee:", quote.l1BroadcastFeeMedium?.originalValue, "sats");
console.log("Slow fee:", quote.l1BroadcastFeeSlow?.originalValue, "sats");
const result = await wallet.withdraw({
onchainAddress: "bc1q...",
exitSpeed: "MEDIUM",
amountSats: 50000,
});
Exit speeds:
Note: Unilateral exit (without operator cooperation) is also possible as a safety mechanism, but cooperative exit is the standard path.
Spark wallets can sign and verify messages using their identity key. Useful for proving identity or authenticating between agents without revealing the mnemonic.
const message = new TextEncoder().encode("I am agent-007");
const signature = await wallet.signMessageWithIdentityKey(message);
const isValid = await wallet.validateMessageWithIdentityKey(
new TextEncoder().encode("I am agent-007"),
signature,
publicKey,
);
console.log("Valid:", isValid);
The wallet emits events for real-time updates. Useful for agents that need to react to incoming payments.
// Incoming transfer completed
wallet.on("transfer:claimed", (transferId, balance) => {
console.log(`Transfer ${transferId} received. Balance: ${balance}`);
});
// Deposit confirmed on L1
wallet.on("deposit:confirmed", (depositId, balance) => {
console.log(`Deposit ${depositId} confirmed. Balance: ${balance}`);
});
// Connection status
wallet.on("stream:connected", () => console.log("Connected to Spark"));
wallet.on("stream:disconnected", (reason) => console.log("Disconnected:", reason));
import { SparkWallet } from "@buildonspark/spark-sdk";
export class SparkAgent {
#wallet;
constructor(wallet) {
this.#wallet = wallet;
}
static async create(mnemonic, network = "MAINNET") {
const { wallet, mnemonic: generated } = await SparkWallet.initialize({
mnemonicOrSeed: mnemonic,
options: { network },
});
return { agent: new SparkAgent(wallet), mnemonic: generated };
}
async getIdentity() {
return {
address: await this.#wallet.getSparkAddress(),
publicKey: await this.#wallet.getIdentityPublicKey(),
};
}
async getBalance() {
const { balance, tokenBalances } = await this.#wallet.getBalance();
return { sats: balance, tokens: tokenBalances };
}
async getDepositAddress() {
return await this.#wallet.getStaticDepositAddress();
}
async transfer(recipientAddress, amountSats) {
return await this.#wallet.transfer({
receiverSparkAddress: recipientAddress,
amountSats,
});
}
async createLightningInvoice(amountSats, memo) {
const request = await this.#wallet.createLightningInvoice({
amountSats,
memo,
expirySeconds: 3600,
includeSparkAddress: true,
});
return request.invoice.encodedInvoice;
}
async payLightningInvoice(bolt11, maxFeeSats = 10) {
return await this.#wallet.payLightningInvoice({
invoice: bolt11,
maxFeeSats,
preferSpark: true,
});
}
async createSparkInvoice(amountSats, memo) {
return await this.#wallet.createSatsInvoice({
amount: amountSats,
memo,
});
}
async transferTokens(tokenIdentifier, amount, recipientAddress) {
return await this.#wallet.transferTokens({
tokenIdentifier,
tokenAmount: amount,
receiverSparkAddress: recipientAddress,
});
}
async withdraw(onchainAddress, amountSats, speed = "MEDIUM") {
return await this.#wallet.withdraw({
onchainAddress,
exitSpeed: speed,
amountSats,
});
}
async signMessage(text) {
const message = new TextEncoder().encode(text);
return await this.#wallet.signMessageWithIdentityKey(message);
}
async verifyMessage(text, signature, publicKey) {
const message = new TextEncoder().encode(text);
return await this.#wallet.validateMessageWithIdentityKey(
message,
signature,
publicKey,
);
}
onTransferReceived(callback) {
this.#wallet.on("transfer:claimed", callback);
}
onDepositConfirmed(callback) {
this.#wallet.on("deposit:confirmed", callback);
}
cleanup() {
this.#wallet.cleanupConnections();
}
}
// Usage
const { agent } = await SparkAgent.create(process.env.SPARK_MNEMONIC);
const identity = await agent.getIdentity();
console.log("Address:", identity.address);
const { sats } = await agent.getBalance();
console.log("Balance:", sats.toString(), "sats");
agent.cleanup();
try {
await wallet.transfer({
receiverSparkAddress: "sp1p...",
amountSats: 1000,
});
} catch (error) {
switch (error.constructor.name) {
case "ValidationError":
console.log("Invalid input:", error.message);
break;
case "NetworkError":
console.log("Network issue:", error.message);
break;
case "AuthenticationError":
console.log("Auth failed:", error.message);
break;
case "ConfigurationError":
console.log("Config problem:", error.message);
break;
case "RPCError":
console.log("RPC error:", error.message);
break;
default:
console.log("Error:", error.message);
}
}
Error types:
Any agent or process with the mnemonic has unrestricted control over the wallet β it can check balance, create invoices, and send every sat to any address. There is no permission scoping, no spending limits, no read-only mode. Unlike NWC (Nostr Wallet Connect), you cannot grant partial access.
This means:
.env to .gitignore β prevent accidental commits of secretsDo not accumulate large balances in an agent wallet. The agent wallet is a hot wallet with the mnemonic sitting in an environment variable β treat it as high-risk.
wallet.transfer() or wallet.withdraw() to move funds out periodicallyaccountNumber values if you need multiple wallets from one mnemoniccleanupConnections() when the wallet is no longer neededAI Usage Analysis
Analysis is being generated⦠refresh in a few seconds.
Connect Claude to Clawdbot instantly and keep it connected 24/7. Run after setup to link your subscription, then auto-refreshes tokens forever.
ERC-8004 Trustless Agents - Register, discover, and build reputation for AI agents on Ethereum. Use when registering agents on-chain, querying agent registries, giving/receiving reputation feedback, or interacting with the AI agent trust layer.
Autonomous crypto trading on Base via Bankr. Use for trading tokens, monitoring launches, executing strategies, or managing a trading portfolio. Triggers on "trade", "buy", "sell", "launch", "snipe", "profit", "PnL", "portfolio balance", or any crypto trading task on Base.
Deploy ERC20 tokens on Base using Clanker SDK. Create tokens with built-in Uniswap V4 liquidity pools. Supports Base mainnet and Sepolia testnet. Requires PRIVATE_KEY in config.
Query DeFi portfolio data across 50+ chains via Zapper's GraphQL API. Use when the user wants to check wallet balances, DeFi positions, NFT holdings, token prices, or transaction history. Supports Base, Ethereum, Polygon, Arbitrum, Optimism, and more. Requires ZAPPER_API_KEY.
Interact with Solana blockchain via Helius APIs. Create/manage wallets, check balances (SOL + tokens), send transactions, swap tokens via Jupiter, and monitor addresses. Use for any Solana blockchain operation, crypto wallet management, token transfers, DeFi swaps, or portfolio tracking.