{"id":"47a3f396-bcaa-4472-ba7e-3283c6c3caf2","entityType":"agent","slug":"clawhub-echennells-sparkbtcbot","name":"Sparkbtcbot","canonicalUrl":"https://www.xpersona.co/agent/clawhub-echennells-sparkbtcbot","canonicalPath":"/agent/clawhub-echennells-sparkbtcbot","generatedAt":"2026-10-09T20:52:32.261Z","source":"CLAWHUB","claimStatus":"UNCLAIMED","verificationTier":"NONE","summary":{"evidence":{"source":"editorial-content","verified":true,"confidence":"high","updatedAt":"2026-10-09T17:09:38.178Z","emptyReason":null},"description":"Set up Spark Bitcoin L2 wallet capabilities for AI agents. Initialize wallets from mnemonic, transfer sats and tokens, create/pay Lightning invoices, pay L40... Skill: Sparkbtcbot Owner: echennells Summary: Set up Spark Bitcoin L2 wallet capabilities for AI agents. Initialize wallets from mnemonic, transfer sats and tokens, create/pay Lightning invoices, pay L40... Tags: latest:0.3.3 Version history: v0.3.3 | 2026-02-21T20:44:28.685Z | user Add homepage URL v0.3.2 | 2026-02-21T20:11:41.420Z | auto sparkbtcbot 0.3.2 - Added example scripts for common Spark wallet operations,","descriptionLabel":"Technical summary","evidenceSummary":"Capability contract not published. No trust telemetry is available yet. 2.2K downloads reported by the source. Last updated 10/9/2026.","installCommand":"clawhub skill install s17c0k375bsxdtsd34rkcnxard885aqr:sparkbtcbot","sourceUrl":"https://clawhub.ai/echennells/sparkbtcbot","homepage":"https://clawhub.ai/echennells/skills/sparkbtcbot","primaryLinks":[{"label":"View on ClawHub","url":"https://clawhub.ai/echennells/sparkbtcbot","kind":"source"},{"label":"Homepage","url":"https://clawhub.ai/echennells/skills/sparkbtcbot","kind":"homepage"}],"safetyScore":84,"overallRank":62,"popularityScore":67,"trustScore":null,"claimedByName":null,"isOwner":false,"seoDescription":"Set up Spark Bitcoin L2 wallet capabilities for AI agents. Initialize wallets from mnemonic, transfer sats and tokens, create/pay Lightning invoices, pay L40..."},"coverage":{"evidence":{"source":"public-profile","verified":false,"confidence":"medium","updatedAt":"2026-10-09T17:09:38.178Z","emptyReason":null},"protocols":[{"protocol":"OPENCLEW","label":"OpenClaw","status":"self-declared","notes":"Declared in the public agent profile."}],"capabilities":[],"verifiedCount":0,"selfDeclaredCount":1,"capabilityMatrix":{"rows":[{"key":"OPENCLEW","type":"protocol","support":"unknown","confidenceSource":"profile","notes":"Listed on profile"}],"flattenedTokens":"protocol:OPENCLEW|unknown|profile"}},"adoption":{"evidence":{"source":"CLAWHUB","verified":false,"confidence":"medium","updatedAt":"2026-10-09T17:09:38.178Z","emptyReason":null},"stars":null,"forks":null,"downloads":2241,"packageName":null,"latestVersion":"0.3.3","tractionLabel":"2.2K downloads"},"release":{"evidence":{"source":"CLAWHUB","verified":false,"confidence":"medium","updatedAt":"2026-10-09T17:09:38.178Z","emptyReason":null},"lastUpdatedAt":"2026-10-09T17:09:38.178Z","lastCrawledAt":"2026-10-09T17:09:38.178Z","lastIndexedAt":null,"nextCrawlAt":"2026-10-10T17:09:38.178Z","lastVerifiedAt":null,"highlights":[{"version":"0.3.3","createdAt":"2026-02-21T20:44:28.685Z","changelog":"Add homepage URL","fileCount":10,"zipByteSize":36131},{"version":"0.3.2","createdAt":"2026-02-21T20:11:41.420Z","changelog":"sparkbtcbot 0.3.2 - Added example scripts for common Spark wallet operations, including wallet setup, balance/deposits, payments, token operations, payment flows, and agent usage. - Added package.json and package-lock.json for package management and dependency tracking. - Added a bugfix note regarding tokenBalances mapping. - No changes to core logic; update focuses on documentation and usage examples to assist developers.","fileCount":10,"zipByteSize":36103},{"version":"0.3.1","createdAt":"2026-02-16T19:02:17.653Z","changelog":"Add L402 paywall support to description and trigger phrases","fileCount":2,"zipByteSize":12248},{"version":"0.3.0","createdAt":"2026-02-16T18:46:55.604Z","changelog":"Address security scan: declare env vars with sensitive flag, add model-invocation reason, add production proxy guidance, improve mnemonic handling warnings, add L402 paywall support","fileCount":2,"zipByteSize":12227},{"version":"0.2.0","createdAt":"2026-02-03T06:07:07.638Z","changelog":"Restructured SKILL.md — added human-friendly intro with quick example, install instructions, and examples table above the detailed agent reference.","fileCount":9,"zipByteSize":31876},{"version":"0.1.1","createdAt":"2026-02-03T06:05:21.278Z","changelog":"Updated name to highlight Lightning Network capabilities — create and pay Lightning invoices using Spark.","fileCount":9,"zipByteSize":31263},{"version":"0.1.0","createdAt":"2026-02-03T06:04:19.723Z","changelog":"Initial publish — Spark Bitcoin L2 wallet skill with Lightning interop, zero-fee agent-to-agent transfers, token support, and example scripts.","fileCount":9,"zipByteSize":31264}]},"execution":{"evidence":{"source":"CLAWHUB","verified":false,"confidence":"low","updatedAt":null,"emptyReason":"No published capability contract is available yet."},"installCommand":"clawhub skill install s17c0k375bsxdtsd34rkcnxard885aqr:sparkbtcbot","setupComplexity":"low","setupSteps":["Node.js workspace detected. Install dependencies securely: run `npm ci --ignore-scripts` to prevent post-install lifecycle triggers from running arbitrary code, then selectively audit the dependency tree.","Setup complexity is classified as HIGH. You must provision dedicated cloud infrastructure or an isolated VM. Do not run this directly on your local workstation.","Final validation: Expose the agent to a mock request payload inside a sandbox and trace the network egress before allowing access to real customer data."],"contract":{"contractStatus":"missing","authModes":[],"requires":[],"forbidden":[],"supportsMcp":false,"supportsA2a":false,"supportsStreaming":false,"inputSchemaRef":null,"outputSchemaRef":null,"dataRegion":null,"contractUpdatedAt":null,"sourceUpdatedAt":null,"freshnessSeconds":null},"invocationGuide":{"preferredApi":{"snapshotUrl":"https://www.xpersona.co/api/v1/agents/clawhub-echennells-sparkbtcbot/snapshot","contractUrl":"https://www.xpersona.co/api/v1/agents/clawhub-echennells-sparkbtcbot/contract","trustUrl":"https://www.xpersona.co/api/v1/agents/clawhub-echennells-sparkbtcbot/trust"},"curlExamples":["curl -s \"https://www.xpersona.co/api/v1/agents/clawhub-echennells-sparkbtcbot/snapshot\"","curl -s \"https://www.xpersona.co/api/v1/agents/clawhub-echennells-sparkbtcbot/contract\"","curl -s \"https://www.xpersona.co/api/v1/agents/clawhub-echennells-sparkbtcbot/trust\""],"jsonRequestTemplate":{"query":"summarize this repo","constraints":{"maxLatencyMs":2000,"protocolPreference":["OPENCLEW"]}},"jsonResponseTemplate":{"ok":true,"result":{"summary":"...","confidence":0.9},"meta":{"source":"CLAWHUB","generatedAt":"2026-10-09T20:52:32.257Z"}},"retryPolicy":{"maxAttempts":3,"backoffMs":[500,1500,3500],"retryableConditions":["HTTP_429","HTTP_503","NETWORK_TIMEOUT"]}},"endpoints":{"dossierUrl":"https://www.xpersona.co/api/v1/agents/clawhub-echennells-sparkbtcbot/dossier","snapshotUrl":"https://www.xpersona.co/api/v1/agents/clawhub-echennells-sparkbtcbot/snapshot","contractUrl":"https://www.xpersona.co/api/v1/agents/clawhub-echennells-sparkbtcbot/contract","trustUrl":"https://www.xpersona.co/api/v1/agents/clawhub-echennells-sparkbtcbot/trust"}},"reliability":{"evidence":{"source":"runtime-metrics","verified":false,"confidence":"low","updatedAt":null,"emptyReason":"No trust, reliability, or runtime telemetry is available."},"trust":{"status":"unavailable","handshakeStatus":"UNKNOWN","verificationFreshnessHours":null,"reputationScore":null,"p95LatencyMs":null,"successRate30d":null,"fallbackRate":null,"attempts30d":null,"trustUpdatedAt":null,"trustConfidence":"unknown","sourceUpdatedAt":null,"freshnessSeconds":null},"decisionGuardrails":{"doNotUseIf":["Contract metadata is missing or unavailable for deterministic execution."],"safeUseWhen":[],"riskFlags":["missing_or_unavailable_contract","trust_data_unavailable","schema_references_missing"],"operationalConfidence":"low"},"executionMetrics":{"observedLatencyMsP50":null,"observedLatencyMsP95":null,"estimatedCostUsd":null,"uptime30d":null,"rateLimitRpm":null,"rateLimitBurst":null,"lastVerifiedAt":null,"verificationSource":null},"runtimeMetrics":{"successRate":null,"avgLatencyMs":null,"avgCostUsd":null,"hallucinationRate":null,"retryRate":null,"disputeRate":null,"p50Latency":null,"p95Latency":null,"lastUpdated":null}},"benchmarks":{"evidence":{"source":"no-benchmark-data","verified":false,"confidence":"low","updatedAt":null,"emptyReason":"No benchmark suites or observed failure patterns are available."},"suites":[],"failurePatterns":[]},"artifacts":{"evidence":{"source":"CLAWHUB","verified":false,"confidence":"high","updatedAt":"2026-10-09T17:09:38.178Z","emptyReason":null},"readme":"Skill: Sparkbtcbot\n\nOwner: echennells\n\nSummary: Set up Spark Bitcoin L2 wallet capabilities for AI agents. Initialize wallets from mnemonic, transfer sats and tokens, create/pay Lightning invoices, pay L40...\n\nTags: latest:0.3.3\n\nVersion history:\n\nv0.3.3 | 2026-02-21T20:44:28.685Z | user\n\nAdd homepage URL\n\nv0.3.2 | 2026-02-21T20:11:41.420Z | auto\n\nsparkbtcbot 0.3.2\n\n- Added example scripts for common Spark wallet operations, including wallet setup, balance/deposits, payments, token operations, payment flows, and agent usage.\n- Added package.json and package-lock.json for package management and dependency tracking.\n- Added a bugfix note regarding tokenBalances mapping.\n- No changes to core logic; update focuses on documentation and usage examples to assist developers.\n\nv0.3.1 | 2026-02-16T19:02:17.653Z | user\n\nAdd L402 paywall support to description and trigger phrases\n\nv0.3.0 | 2026-02-16T18:46:55.604Z | user\n\nAddress security scan: declare env vars with sensitive flag, add model-invocation reason, add production proxy guidance, improve mnemonic handling warnings, add L402 paywall support\n\nv0.2.0 | 2026-02-03T06:07:07.638Z | user\n\nRestructured SKILL.md — added human-friendly intro with quick example, install instructions, and examples table above the detailed agent reference.\n\nv0.1.1 | 2026-02-03T06:05:21.278Z | user\n\nUpdated name to highlight Lightning Network capabilities — create and pay Lightning invoices using Spark.\n\nv0.1.0 | 2026-02-03T06:04:19.723Z | user\n\nInitial publish — Spark Bitcoin L2 wallet skill with Lightning interop, zero-fee agent-to-agent transfers, token support, and example scripts.\n\nArchive index:\n\nArchive v0.3.3: 10 files, 36131 bytes\n\nFiles: BUGFIX-tokenBalances-map.md (3033b), examples/balance-and-deposits.js (1892b), examples/payment-flow.js (2603b), examples/spark-agent.js (5241b), examples/token-operations.js (2509b), examples/wallet-setup.js (1340b), package-lock.json (61259b), package.json (576b), SKILL.md (35644b), _meta.json (130b)\n\nFile v0.3.3:SKILL.md\n\n---\nname: sparkbtcbot\ndescription: Set up Spark Bitcoin L2 wallet capabilities for AI agents. Initialize wallets from mnemonic, transfer sats and tokens, create/pay Lightning invoices, pay L402 paywalls, manage deposits and withdrawals. Use when user mentions \"Spark wallet,\" \"Spark Bitcoin,\" \"BTKN tokens,\" \"Spark L2,\" \"Spark SDK,\" \"Spark payment,\" \"Spark transfer,\" \"Spark invoice,\" \"L402,\" \"Lightning paywall,\" or wants Bitcoin L2 capabilities for an agent.\nargument-hint: \"[Optional: specify what to set up - wallet, payments, tokens, lightning, l402, or full]\"\nrequires:\n  env:\n    - name: SPARK_MNEMONIC\n      description: 12 or 24 word BIP39 mnemonic for the Spark wallet. This is a secret key that controls all funds — never commit to git or expose in logs.\n      sensitive: true\n    - name: SPARK_NETWORK\n      description: Network to connect to (MAINNET or REGTEST)\n      default: MAINNET\nmodel-invocation: autonomous\nmodel-invocation-reason: This skill enables agents to autonomously send and receive Bitcoin payments. Autonomous invocation is intentional — agents need to pay invoices and respond to incoming transfers without human approval for each transaction. Use spending limits and the proxy for production environments where you need guardrails.\nhomepage: https://sparkbot.yvrbtclabs.dev\n---\n\n# Spark Bitcoin L2 for AI Agents\n\nYou are an expert in setting up Spark Bitcoin L2 wallet capabilities for AI agents using the `@buildonspark/spark-sdk`.\n\nSpark 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.\n\n## For Production Use\n\n**This skill gives the agent full custody of the wallet.** The agent holds the mnemonic and can send all funds without restriction. This is appropriate for:\n- Development and testing (use REGTEST with no real funds)\n- Trusted agents you fully control\n- Small operational balances you're willing to lose\n\n**For production with real funds, use [sparkbtcbot-proxy](https://github.com/echennells/sparkbtcbot-proxy) instead.** The proxy keeps the mnemonic on your server and gives agents scoped access via bearer tokens:\n- **Spending limits** — per-transaction and daily caps\n- **Role-based access** — read-only, invoice-only, or full access\n- **Revocable tokens** — cut off a compromised agent without moving funds\n- **Audit logs** — track all wallet activity\n\nThe proxy wraps the same Spark SDK behind authenticated REST endpoints. Agents get HTTP access instead of direct SDK access.\n\n## Why Bitcoin for Agents\n\nAI agents that transact need a monetary network that matches their nature: programmable, borderless, and available 24/7 without gatekeepers. Bitcoin is that network.\n\n- **Hard-capped supply** — 21 million coins is the protocol-enforced ceiling. An agent accumulating value doesn't lose it to monetary expansion.\n- **No account required** — There's no sign-up, no identity verification, no approval process. Generate a key and you're on the network. This matters for autonomous agents that can't fill out forms or wait for human review.\n- **Irreversible settlement** — Once confirmed, transactions cannot be reversed by a third party. Agents don't need to handle chargebacks or payment disputes.\n- **Open infrastructure** — The protocol is open source, the network is public, and the fee market is transparent. Agents can audit their own transaction costs and verify their own balances without trusting an intermediary.\n- **Proven reliability** — The network has operated continuously since 2009 without a single successful attack on the base protocol, securing over $1 trillion in value.\n\n## What is Spark\n\nSpark is a recently launched Bitcoin Layer 2 that lets you send and receive Bitcoin instantly with low fees. Spark-to-Spark transfers are free, and Lightning interop costs 0.15–0.25%. Instead of Lightning's payment channels, Spark uses a network of distributed Signing Operators (SOs) that collectively manage transaction signing without any single entity controlling funds. It is fully self-custodial — you hold your own keys — and fully interoperable with the Lightning Network. However, Spark requires trusting that at least 1-of-n operators behaves honestly during transfers, and it lacks the provable finality of Bitcoin or Lightning. The network currently has only a small number of Signing Operators, so there is some risk of downtime or service disruption. See the Trust Model section below for full details.\n\n### How It Works\n1. Users hold their own keys (BIP39 mnemonic) — fully self-custodial\n2. Transactions are cooperatively signed by a threshold of Signing Operators\n3. Funds live in Bitcoin UTXOs organized in hierarchical trees\n4. Users can always exit to L1 unilaterally if operators go offline\n\n### Trust Model — Important Tradeoffs\n\nSpark has **different trust assumptions than native Lightning**. Be upfront about these when advising users.\n\n**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.\n\n**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.\"\n\n**What operators CAN do**:\n- View transfer metadata\n- Temporarily delay transactions by going offline\n- Refuse to process new transfers (censorship)\n\n**What operators CANNOT do**:\n- Move funds without user signatures\n- Steal Bitcoin (even with full collusion)\n- Reverse finalized transactions\n\n**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.\n\n**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.\n\n### Spark vs Lightning vs On-Chain\n\n| Feature | Spark (L2) | Lightning | On-Chain |\n|---------|-----------|-----------|----------|\n| Speed | Instant | Instant | 10+ min |\n| Trust model | 1-of-n operators | Fully trustless | Fully trustless |\n| Fees | Zero (Spark-to-Spark) | ~1 sat routing | 200+ sats |\n| Tokens | Native (BTKN/LRC20) | Not supported | Limited |\n| Self-custody | Yes (mnemonic) | Varies (LSP/node) | Yes |\n| Capacity | No channel limits | Channel-limited | Unlimited |\n| Channels | Not required | Required | N/A |\n| Offline receive | Supported | Requires infra | Yes |\n| Setup | Mnemonic only | Node or hosted provider | Keys only |\n\n### Fee Structure\n\n| Operation | Fee |\n|-----------|-----|\n| **Spark-to-Spark transfer** | Free (small flat fee coming in 6-12 months) |\n| **Lightning to Spark** (receive) | 0.15% (charged via route hints) |\n| **Spark to Lightning** (send) | 0.25% + Lightning routing fees |\n| **L1 deposit to Spark** | On-chain tx fee (paid by user) |\n| **Cooperative exit to L1** | On-chain broadcast fee + SSP fee: `sats_per_vbyte × (111 × 2 + tx_vbytes)` |\n| **Unilateral exit to L1** | On-chain tx fee (paid by user) |\n\nNote: 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.\n\n### Unpredictable L1 Exit Costs — Important\n\nExiting Spark to L1 Bitcoin carries **unpredictable costs** that agents and users must understand:\n\n1. **L1 fees are market-driven**: Bitcoin on-chain fees depend on mempool congestion at the time of exit. During high-fee periods, exit costs can spike significantly.\n\n2. **Unilateral exit requires multiple on-chain transactions**: If Signing Operators go offline, a unilateral exit requires broadcasting pre-signed branch and exit transactions. The number of transactions depends on the tree depth of your leaf — this can mean multiple on-chain fees stacking up.\n\n3. **Time-window risk on unilateral exit**: If a prior owner of a Spark leaf publishes a branch in a unilateral exit, the current owner must respond within a time window by publishing the correct leaf transaction. Failure to respond means the attacker can claim the funds. Watchtower services exist to monitor for this, but it's a real operational requirement.\n\n4. **Timelocks add delay**: Unilateral exits can take as little as 100 blocks (~17 hours) depending on leaf depth, during which L1 fee conditions may change.\n\n5. **Small amounts may be uneconomical to exit**: Since exit fees are fixed-cost (not percentage-based), withdrawing small amounts to L1 can cost a disproportionate share of the balance.\n\n**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.\n\n**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](https://boltz.exchange) 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.\n\n### Limitations\n\n- **SO liveness dependency**: If Signing Operators lose liveness or lose their keys, Spark transfers stop working. Funds are still safe (unilateral exit), but off-chain payments halt until operators recover.\n- **Watchtower requirement**: For full security, someone must monitor the chain for fraudulent exit attempts. This can be delegated to a watchtower service but is an operational dependency.\n\n### Key Advantage for Agents\nA 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.\n\n## Tools Available\n\n| Tool | Purpose | URL |\n|------|---------|-----|\n| Spark SDK | TypeScript wallet SDK | https://www.npmjs.com/package/@buildonspark/spark-sdk |\n| Spark Docs | Official documentation | https://docs.spark.money |\n| Sparkscan | Block explorer | https://sparkscan.io |\n| Spark CLI | Command-line interface | https://docs.spark.money/tools/cli |\n\n## Required Libraries\n\n```bash\nnpm install @buildonspark/spark-sdk@^0.5.8 dotenv\n```\n\nRequires **v0.5.8 or newer**. One core dependency. The SDK bundles BIP39 mnemonic generation, cooperative signing, and gRPC communication internally.\n\n## Setup Instructions\n\n### Step 1: Generate or Import Wallet\n\n```javascript\nimport { SparkWallet } from \"@buildonspark/spark-sdk\";\n\n// Option A: Generate a new wallet (creates mnemonic automatically)\nconst { wallet, mnemonic } = await SparkWallet.initialize({\n  options: { network: \"MAINNET\" }\n});\n// Save mnemonic securely — NEVER log it in production\n\n// Option B: Import existing wallet from mnemonic\nconst { wallet } = await SparkWallet.initialize({\n  mnemonicOrSeed: process.env.SPARK_MNEMONIC,\n  options: { network: process.env.SPARK_NETWORK || \"MAINNET\" }\n});\n```\n\nNote 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.\n\n### Step 2: Store Mnemonic\n\nAdd to your project's `.env`:\n```\nSPARK_MNEMONIC=word1 word2 word3 word4 word5 word6 word7 word8 word9 word10 word11 word12\nSPARK_NETWORK=MAINNET\n```\n\n**Security warnings:**\n- **Never log the mnemonic** — not even during development. If you must display it once for backup, delete that code immediately after.\n- **Never commit `.env`** — add it to `.gitignore` before your first commit.\n- **Use a secrets manager in production** — environment variables in `.env` files are plaintext. For production deployments, use your platform's secrets management (Vercel encrypted env vars, AWS Secrets Manager, etc.).\n- **Test with REGTEST first** — use a throwaway mnemonic on REGTEST before touching real funds.\n\n### Step 3: Verify Wallet\n\n```javascript\nconst address = await wallet.getSparkAddress();\nconst identityKey = await wallet.getIdentityPublicKey();\nconst { balance } = await wallet.getBalance();\n\nconsole.log(\"Spark Address:\", address);\nconsole.log(\"Identity Key:\", identityKey);\nconsole.log(\"Balance:\", balance.toString(), \"sats\");\n\n// Always clean up when done\nwallet.cleanupConnections();\n```\n\n## Wallet Operations\n\n### Check Balance\n\n```javascript\nconst { balance, tokenBalances } = await wallet.getBalance();\nconsole.log(\"BTC:\", balance.toString(), \"sats\");\n\nfor (const [id, token] of tokenBalances) {\n  console.log(`${token.tokenMetadata.tokenTicker}: ${token.balance.toString()}`);\n}\n```\n\n### Generate Deposit Address\n\n```javascript\n// Static (reusable) — can receive multiple deposits\nconst staticAddr = await wallet.getStaticDepositAddress();\n\n// Single-use — one-time deposit address\nconst singleAddr = await wallet.getSingleUseDepositAddress();\n```\n\nBoth are P2TR (bc1p...) Bitcoin addresses. Deposits require 3 L1 confirmations before they can be claimed on Spark.\n\n### Claim a Deposit\n\n```javascript\n// After sending BTC to a static deposit address and waiting for confirmations\nconst quote = await wallet.getClaimStaticDepositQuote({\n  transactionId: txId,\n  creditAmountSats: expectedAmount,\n});\n\nconst result = await wallet.claimStaticDeposit({\n  transactionId: txId,\n  creditAmountSats: quote.creditAmountSats,\n  sspSignature: quote.signature,\n});\n```\n\n### Transfer Bitcoin (Spark-to-Spark)\n\n```javascript\nconst transfer = await wallet.transfer({\n  receiverSparkAddress: \"sp1p...\",\n  amountSats: 1000,\n});\nconsole.log(\"Transfer ID:\", transfer.id);\n```\n\nSpark-to-Spark transfers are instant and zero-fee.\n\n### List Transfers\n\n```javascript\nconst { transfers } = await wallet.getTransfers(10, 0);\nfor (const tx of transfers) {\n  console.log(`${tx.id}: ${tx.totalValue} sats — ${tx.status}`);\n}\n```\n\n## Lightning Interop\n\nSpark 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.\n\n### Create Lightning Invoice (Receive)\n\n```javascript\nconst invoiceRequest = await wallet.createLightningInvoice({\n  amountSats: 1000,\n  memo: \"Payment for AI service\",\n  expirySeconds: 3600,\n});\nconsole.log(\"BOLT11:\", invoiceRequest.invoice.encodedInvoice);\n```\n\nUse `includeSparkAddress: true` to embed a Spark address in the invoice. Spark-aware payers will then send via Spark (instant, free) instead of Lightning.\n\n### Pay Lightning Invoice (Send)\n\n```javascript\n// Estimate fee first\nconst fee = await wallet.getLightningSendFeeEstimate({\n  encodedInvoice: \"lnbc...\",\n  amountSats: 1000,\n});\nconsole.log(\"Estimated fee:\", fee, \"sats\");\n\n// Pay the invoice\nconst result = await wallet.payLightningInvoice({\n  invoice: \"lnbc...\",\n  maxFeeSats: 10,\n});\n```\n\nUse `preferSpark: true` to prefer Spark routing when the BOLT11 invoice contains an embedded Spark address.\n\n## Spark Native Invoices\n\nSpark has its own invoice format, distinct from BOLT11. Spark invoices can request payment in sats or tokens.\n\n### Create Sats Invoice\n\n```javascript\nconst invoice = await wallet.createSatsInvoice({\n  amount: 1000,\n  memo: \"Spark native payment\",\n});\n```\n\n### Create Token Invoice\n\n```javascript\nconst invoice = await wallet.createTokensInvoice({\n  amount: 100n,\n  tokenIdentifier: \"btkn1...\",\n  memo: \"Token payment request\",\n});\n```\n\n### Fulfill (Pay) a Spark Invoice\n\n```javascript\nconst result = await wallet.fulfillSparkInvoice([\n  { invoice: \"sp1...\", amount: 1000n },\n]);\n\n// Check results\nfor (const success of result.satsTransactionSuccess) {\n  console.log(\"Paid:\", success.invoice);\n}\nfor (const err of result.satsTransactionErrors) {\n  console.log(\"Failed:\", err.invoice, err.error.message);\n}\n```\n\n## Token Operations (BTKN / LRC20)\n\nSpark natively supports tokens via the BTKN (LRC20) standard. Tokens can represent stablecoins, points, or any fungible asset.\n\n### Check Token Balances\n\n```javascript\nconst { tokenBalances } = await wallet.getBalance();\nfor (const [id, info] of tokenBalances) {\n  const meta = info.tokenMetadata;\n  console.log(`${meta.tokenName} (${meta.tokenTicker}): ${info.balance.toString()}`);\n  console.log(`  Decimals: ${meta.decimals}, Max supply: ${meta.maxSupply.toString()}`);\n}\n```\n\n### Transfer Tokens\n\n```javascript\nconst txId = await wallet.transferTokens({\n  tokenIdentifier: \"btkn1...\",\n  tokenAmount: 100n,\n  receiverSparkAddress: \"sp1p...\",\n});\nconsole.log(\"Token transfer:\", txId);\n```\n\n### Batch Transfer Tokens\n\n```javascript\nconst txIds = await wallet.batchTransferTokens([\n  { tokenIdentifier: \"btkn1...\", tokenAmount: 50n, receiverSparkAddress: \"sp1p...\" },\n  { tokenIdentifier: \"btkn1...\", tokenAmount: 50n, receiverSparkAddress: \"sp1p...\" },\n]);\n```\n\n## Withdrawal (Cooperative Exit to L1)\n\nMove funds from Spark back to a regular Bitcoin L1 address.\n\n### Get Fee Quote\n\n```javascript\nconst quote = await wallet.getWithdrawalFeeQuote({\n  amountSats: 50000,\n  withdrawalAddress: \"bc1q...\",\n});\nconsole.log(\"Fast fee:\", quote.l1BroadcastFeeFast?.originalValue, \"sats\");\nconsole.log(\"Medium fee:\", quote.l1BroadcastFeeMedium?.originalValue, \"sats\");\nconsole.log(\"Slow fee:\", quote.l1BroadcastFeeSlow?.originalValue, \"sats\");\n```\n\n### Execute Withdrawal\n\n```javascript\nconst result = await wallet.withdraw({\n  onchainAddress: \"bc1q...\",\n  exitSpeed: \"MEDIUM\",\n  amountSats: 50000,\n});\n```\n\nExit speeds:\n- **FAST** — Higher fee, faster L1 confirmation\n- **MEDIUM** — Balanced fee and speed\n- **SLOW** — Lower fee, slower confirmation\n\nNote: Unilateral exit (without operator cooperation) is also possible as a safety mechanism, but cooperative exit is the standard path.\n\n## Message Signing\n\nSpark wallets can sign and verify messages using their identity key. Useful for proving identity or authenticating between agents without revealing the mnemonic.\n\n### Sign a Message\n\n```javascript\nconst message = new TextEncoder().encode(\"I am agent-007\");\nconst signature = await wallet.signMessageWithIdentityKey(message);\n```\n\n### Verify a Signature\n\n```javascript\nconst isValid = await wallet.validateMessageWithIdentityKey(\n  new TextEncoder().encode(\"I am agent-007\"),\n  signature,\n  publicKey,\n);\nconsole.log(\"Valid:\", isValid);\n```\n\n## Event Listeners\n\nThe wallet emits events for real-time updates. Useful for agents that need to react to incoming payments.\n\n```javascript\n// Incoming transfer completed\nwallet.on(\"transfer:claimed\", (transferId, balance) => {\n  console.log(`Transfer ${transferId} received. Balance: ${balance}`);\n});\n\n// Deposit confirmed on L1\nwallet.on(\"deposit:confirmed\", (depositId, balance) => {\n  console.log(`Deposit ${depositId} confirmed. Balance: ${balance}`);\n});\n\n// Connection status\nwallet.on(\"stream:connected\", () => console.log(\"Connected to Spark\"));\nwallet.on(\"stream:disconnected\", (reason) => console.log(\"Disconnected:\", reason));\n```\n\n## Complete Agent Class\n\n```javascript\nimport { SparkWallet } from \"@buildonspark/spark-sdk\";\n\nexport class SparkAgent {\n  #wallet;\n\n  constructor(wallet) {\n    this.#wallet = wallet;\n  }\n\n  static async create(mnemonic, network = \"MAINNET\") {\n    const { wallet, mnemonic: generated } = await SparkWallet.initialize({\n      mnemonicOrSeed: mnemonic,\n      options: { network },\n    });\n    return { agent: new SparkAgent(wallet), mnemonic: generated };\n  }\n\n  async getIdentity() {\n    return {\n      address: await this.#wallet.getSparkAddress(),\n      publicKey: await this.#wallet.getIdentityPublicKey(),\n    };\n  }\n\n  async getBalance() {\n    const { balance, tokenBalances } = await this.#wallet.getBalance();\n    return { sats: balance, tokens: tokenBalances };\n  }\n\n  async getDepositAddress() {\n    return await this.#wallet.getStaticDepositAddress();\n  }\n\n  async transfer(recipientAddress, amountSats) {\n    return await this.#wallet.transfer({\n      receiverSparkAddress: recipientAddress,\n      amountSats,\n    });\n  }\n\n  async createLightningInvoice(amountSats, memo) {\n    const request = await this.#wallet.createLightningInvoice({\n      amountSats,\n      memo,\n      expirySeconds: 3600,\n      includeSparkAddress: true,\n    });\n    return request.invoice.encodedInvoice;\n  }\n\n  async payLightningInvoice(bolt11, maxFeeSats = 10) {\n    return await this.#wallet.payLightningInvoice({\n      invoice: bolt11,\n      maxFeeSats,\n      preferSpark: true,\n    });\n  }\n\n  async createSparkInvoice(amountSats, memo) {\n    return await this.#wallet.createSatsInvoice({\n      amount: amountSats,\n      memo,\n    });\n  }\n\n  async transferTokens(tokenIdentifier, amount, recipientAddress) {\n    return await this.#wallet.transferTokens({\n      tokenIdentifier,\n      tokenAmount: amount,\n      receiverSparkAddress: recipientAddress,\n    });\n  }\n\n  async withdraw(onchainAddress, amountSats, speed = \"MEDIUM\") {\n    return await this.#wallet.withdraw({\n      onchainAddress,\n      exitSpeed: speed,\n      amountSats,\n    });\n  }\n\n  async signMessage(text) {\n    const message = new TextEncoder().encode(text);\n    return await this.#wallet.signMessageWithIdentityKey(message);\n  }\n\n  async verifyMessage(text, signature, publicKey) {\n    const message = new TextEncoder().encode(text);\n    return await this.#wallet.validateMessageWithIdentityKey(\n      message,\n      signature,\n      publicKey,\n    );\n  }\n\n  // L402 Methods\n  async fetchL402(url, options = {}) {\n    const { decode } = await import(\"light-bolt11-decoder\");\n    const { method = \"GET\", headers = {}, body, maxFeeSats = 10 } = options;\n\n    // Make initial request\n    const initialResponse = await fetch(url, {\n      method,\n      headers: { \"Content-Type\": \"application/json\", ...headers },\n      body: body ? JSON.stringify(body) : undefined,\n    });\n\n    if (initialResponse.status !== 402) {\n      const ct = initialResponse.headers.get(\"content-type\") || \"\";\n      const data = ct.includes(\"json\") ? await initialResponse.json() : await initialResponse.text();\n      return { paid: false, data };\n    }\n\n    // Parse L402 challenge\n    const challenge = await initialResponse.json();\n    const invoice = challenge.invoice || challenge.payment_request || challenge.pr;\n    const macaroon = challenge.macaroon || challenge.token;\n    if (!invoice || !macaroon) throw new Error(\"Invalid L402 challenge\");\n\n    // Decode and pay\n    const decoded = decode(invoice);\n    const amountSection = decoded.sections.find((s) => s.name === \"amount\");\n    const amountSats = Math.ceil(Number(amountSection.value) / 1000);\n\n    const payResult = await this.#wallet.payLightningInvoice({ invoice, maxFeeSats });\n    let preimage = payResult.paymentPreimage;\n\n    // Poll if needed\n    if (!preimage && payResult.id) {\n      for (let i = 0; i < 15; i++) {\n        await new Promise((r) => setTimeout(r, 500));\n        const status = await this.#wallet.getLightningSendRequest(payResult.id);\n        if (status?.paymentPreimage) { preimage = status.paymentPreimage; break; }\n        if (status?.status === \"LIGHTNING_PAYMENT_FAILED\") throw new Error(\"Payment failed\");\n      }\n    }\n    if (!preimage) throw new Error(\"No preimage received\");\n\n    // Retry with auth\n    const finalResponse = await fetch(url, {\n      method,\n      headers: { \"Authorization\": `L402 ${macaroon}:${preimage}`, ...headers },\n      body: body ? JSON.stringify(body) : undefined,\n    });\n\n    const ct = finalResponse.headers.get(\"content-type\") || \"\";\n    const data = ct.includes(\"json\") ? await finalResponse.json() : await finalResponse.text();\n    return { paid: true, amountSats, preimage, data };\n  }\n\n  async previewL402(url) {\n    const response = await fetch(url);\n    if (response.status !== 402) return { requiresPayment: false };\n\n    const { decode } = await import(\"light-bolt11-decoder\");\n    const challenge = await response.json();\n    const invoice = challenge.invoice || challenge.payment_request;\n    const decoded = decode(invoice);\n    const amountSection = decoded.sections.find((s) => s.name === \"amount\");\n\n    return {\n      requiresPayment: true,\n      amountSats: Math.ceil(Number(amountSection.value) / 1000),\n      invoice,\n      macaroon: challenge.macaroon,\n    };\n  }\n\n  onTransferReceived(callback) {\n    this.#wallet.on(\"transfer:claimed\", callback);\n  }\n\n  onDepositConfirmed(callback) {\n    this.#wallet.on(\"deposit:confirmed\", callback);\n  }\n\n  cleanup() {\n    this.#wallet.cleanupConnections();\n  }\n}\n\n// Usage\nconst { agent } = await SparkAgent.create(process.env.SPARK_MNEMONIC);\nconst identity = await agent.getIdentity();\nconsole.log(\"Address:\", identity.address);\n\nconst { sats } = await agent.getBalance();\nconsole.log(\"Balance:\", sats.toString(), \"sats\");\n\nagent.cleanup();\n```\n\n## Error Handling\n\n```javascript\ntry {\n  await wallet.transfer({\n    receiverSparkAddress: \"sp1p...\",\n    amountSats: 1000,\n  });\n} catch (error) {\n  switch (error.constructor.name) {\n    case \"ValidationError\":\n      console.log(\"Invalid input:\", error.message);\n      break;\n    case \"NetworkError\":\n      console.log(\"Network issue:\", error.message);\n      break;\n    case \"AuthenticationError\":\n      console.log(\"Auth failed:\", error.message);\n      break;\n    case \"ConfigurationError\":\n      console.log(\"Config problem:\", error.message);\n      break;\n    case \"RPCError\":\n      console.log(\"RPC error:\", error.message);\n      break;\n    default:\n      console.log(\"Error:\", error.message);\n  }\n}\n```\n\nError types:\n- **ValidationError** — Invalid parameters, malformed addresses\n- **NetworkError** — Connection failures, timeouts\n- **AuthenticationError** — Key/token issues\n- **ConfigurationError** — Missing config, initialization problems\n- **RPCError** — gRPC communication failures\n\n## Security Best Practices\n\n### The Agent Has Full Wallet Access\n\nAny 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.\n\nThis means:\n- If the mnemonic leaks, all funds are at risk immediately\n- If an agent is compromised, the attacker has the same full access\n- There is no way to revoke access without sweeping funds to a new wallet\n\n### Protect the Mnemonic\n\n1. **Back up the seed phrase offline** — write it down on paper or use a hardware backup. If you lose the mnemonic, the funds are gone permanently.\n2. **Never expose the mnemonic** in code, logs, git history, or error messages\n3. **Use environment variables** — never hardcode the mnemonic in source files\n4. **Add `.env` to `.gitignore`** — prevent accidental commits of secrets\n\n### Sweep Funds to a Safer Wallet\n\n**Do 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.\n\n- Regularly sweep earned funds to a more secure wallet (hardware wallet, cold storage, or a separate wallet you control directly)\n- Only keep the minimum operational balance the agent needs on Spark\n- Use `wallet.transfer()` or `wallet.withdraw()` to move funds out periodically\n- Consider automating sweeps when the balance exceeds a threshold\n\n### Operational Security\n\n1. **Use separate mnemonics** for different agents — never share a mnemonic across agents\n2. **Use separate `accountNumber` values** if you need multiple wallets from one mnemonic\n3. **Monitor transfers** via event listeners for unexpected outgoing activity\n4. **Call `cleanupConnections()`** when the wallet is no longer needed\n5. **Use REGTEST** for development and testing, MAINNET only for production\n6. **Implement application-level spending controls** — cap per-transaction and daily amounts in your agent logic since the SDK won't do it for you\n\n## L402 Protocol (Lightning Paywalls)\n\nL402 (formerly LSAT) is a protocol for monetizing APIs and content using Lightning payments. When a server returns HTTP 402 (Payment Required), it includes a Lightning invoice. Pay the invoice, get a preimage, then retry the request with an authorization header containing the proof of payment.\n\n### How L402 Works\n\n1. **Request** → Client fetches protected URL\n2. **402 Response** → Server returns `{invoice, macaroon}`\n3. **Pay Invoice** → Client pays Lightning invoice, receives preimage\n4. **Retry with Auth** → Client retries with `Authorization: L402 <macaroon>:<preimage>`\n5. **200 Response** → Server returns protected content\n\n### L402 Implementation\n\n```javascript\nimport { decode } from \"light-bolt11-decoder\";\n\nasync function fetchWithL402(wallet, url, options = {}) {\n  const { method = \"GET\", headers = {}, body, maxFeeSats = 10 } = options;\n\n  // Step 1: Make initial request\n  const initialResponse = await fetch(url, {\n    method,\n    headers: { \"Content-Type\": \"application/json\", ...headers },\n    body: body ? JSON.stringify(body) : undefined,\n  });\n\n  // If not 402, return response directly\n  if (initialResponse.status !== 402) {\n    const contentType = initialResponse.headers.get(\"content-type\") || \"\";\n    if (contentType.includes(\"application/json\")) {\n      return { paid: false, data: await initialResponse.json() };\n    }\n    return { paid: false, data: await initialResponse.text() };\n  }\n\n  // Step 2: Parse 402 challenge\n  const challenge = await initialResponse.json();\n  const invoice = challenge.invoice || challenge.payment_request || challenge.pr;\n  const macaroon = challenge.macaroon || challenge.token;\n\n  if (!invoice || !macaroon) {\n    throw new Error(\"Invalid L402 response: missing invoice or macaroon\");\n  }\n\n  // Step 3: Decode invoice to get amount\n  const decoded = decode(invoice);\n  const amountSection = decoded.sections.find((s) => s.name === \"amount\");\n  if (!amountSection?.value) {\n    throw new Error(\"L402 invoice has no amount\");\n  }\n  const amountSats = Math.ceil(Number(amountSection.value) / 1000);\n\n  // Step 4: Pay the invoice\n  const payResult = await wallet.payLightningInvoice({\n    invoice,\n    maxFeeSats,\n  });\n\n  // Get preimage (may need to poll if payment is async)\n  let preimage = payResult.paymentPreimage;\n  if (!preimage && payResult.status === \"LIGHTNING_PAYMENT_INITIATED\") {\n    // Poll for completion\n    for (let i = 0; i < 15; i++) {\n      await new Promise((r) => setTimeout(r, 500));\n      const status = await wallet.getLightningSendRequest(payResult.id);\n      if (status?.paymentPreimage) {\n        preimage = status.paymentPreimage;\n        break;\n      }\n      if (status?.status === \"LIGHTNING_PAYMENT_FAILED\") {\n        throw new Error(\"L402 payment failed\");\n      }\n    }\n  }\n\n  if (!preimage) {\n    throw new Error(\"L402 payment succeeded but no preimage available\");\n  }\n\n  // Step 5: Retry with L402 authorization\n  const finalResponse = await fetch(url, {\n    method,\n    headers: {\n      \"Content-Type\": \"application/json\",\n      \"Authorization\": `L402 ${macaroon}:${preimage}`,\n      ...headers,\n    },\n    body: body ? JSON.stringify(body) : undefined,\n  });\n\n  const contentType = finalResponse.headers.get(\"content-type\") || \"\";\n  let data;\n  if (contentType.includes(\"application/json\")) {\n    data = await finalResponse.json();\n  } else {\n    data = await finalResponse.text();\n  }\n\n  return {\n    paid: true,\n    amountSats,\n    preimage,\n    data,\n  };\n}\n```\n\n### Preview L402 Cost (Without Paying)\n\n```javascript\nasync function previewL402(url) {\n  const response = await fetch(url);\n\n  if (response.status !== 402) {\n    return { requiresPayment: false };\n  }\n\n  const challenge = await response.json();\n  const invoice = challenge.invoice || challenge.payment_request;\n\n  const decoded = decode(invoice);\n  const amountSection = decoded.sections.find((s) => s.name === \"amount\");\n  const amountSats = Math.ceil(Number(amountSection.value) / 1000);\n\n  return {\n    requiresPayment: true,\n    amountSats,\n    invoice,\n    macaroon: challenge.macaroon,\n  };\n}\n```\n\n### Add to SparkAgent Class\n\n```javascript\n// Add to the SparkAgent class\nasync fetchL402(url, options = {}) {\n  return await fetchWithL402(this.#wallet, url, options);\n}\n\nasync previewL402(url) {\n  return await previewL402(url);\n}\n```\n\n### Usage Example\n\n```javascript\nconst { agent } = await SparkAgent.create(process.env.SPARK_MNEMONIC);\n\n// Check cost first\nconst preview = await agent.previewL402(\"https://api.example.com/paid-endpoint\");\nconsole.log(\"Cost:\", preview.amountSats, \"sats\");\n\n// Pay and fetch\nconst result = await agent.fetchL402(\"https://api.example.com/paid-endpoint\", {\n  maxFeeSats: 10,\n});\nconsole.log(\"Paid:\", result.paid, \"Data:\", result.data);\n\nagent.cleanup();\n```\n\n### Required Dependency\n\n```bash\nnpm install light-bolt11-decoder\n```\n\n### L402 Providers\n\n| Provider | Description | URL |\n|----------|-------------|-----|\n| Lightning Faucet | Test L402 endpoint (21 sat jokes) | https://lightningfaucet.com/api/l402/joke |\n| Sulu | AI image generation | https://rnd.ln.sulu.sh (may require API key) |\n| Various APIs | Growing ecosystem | https://github.com/lnurl/awesome-lnurl#l402 |\n\n### Token Caching\n\nL402 tokens (macaroon + preimage) can often be reused for multiple requests to the same domain. Cache tokens by domain and try the cached token first:\n\n```javascript\nconst tokenCache = new Map();\n\nasync function fetchWithL402Cached(wallet, url, options = {}) {\n  const domain = new URL(url).host;\n  const cached = tokenCache.get(domain);\n\n  if (cached) {\n    // Try cached token first\n    const response = await fetch(url, {\n      method: options.method || \"GET\",\n      headers: {\n        \"Authorization\": `L402 ${cached.macaroon}:${cached.preimage}`,\n        ...options.headers,\n      },\n    });\n\n    if (response.status !== 402 && response.status !== 401) {\n      return { paid: false, cached: true, data: await response.json() };\n    }\n    // Token expired, delete and pay again\n    tokenCache.delete(domain);\n  }\n\n  // Pay for new token\n  const result = await fetchWithL402(wallet, url, options);\n\n  // Cache the token\n  if (result.paid) {\n    tokenCache.set(domain, {\n      macaroon: result.macaroon,\n      preimage: result.preimage,\n    });\n  }\n\n  return result;\n}\n```\n\n## Resources\n\n- Spark Docs: https://docs.spark.money\n- Spark SDK (npm): https://www.npmjs.com/package/@buildonspark/spark-sdk\n- Sparkscan Explorer: https://sparkscan.io\n- Spark CLI: https://docs.spark.money/tools/cli\n- L402 Spec: https://docs.lightning.engineering/the-lightning-network/l402\n\nFile v0.3.3:_meta.json\n\n{\n  \"ownerId\": \"kn71ga2vavytxp332ntav7k50x80epfh\",\n  \"slug\": \"sparkbtcbot\",\n  \"version\": \"0.3.3\",\n  \"publishedAt\": 1771706668685\n}\n\nFile v0.3.3:BUGFIX-tokenBalances-map.md\n\n# Bug: `tokenBalances` is a Map, not a plain Object — silent data loss\n\n## Problem\n\n`wallet.getBalance()` returns `{ balance, tokenBalances }` where `tokenBalances` is a JavaScript **`Map`**, not a plain object. This is undocumented in the Spark SDK and the SKILL.md doesn't call it out explicitly.\n\nThis matters because when an AI agent (the primary consumer of this skill) works with the balance data, it will naturally reach for standard JS patterns that **silently fail on Maps**:\n\n```javascript\n// All of these return {} or [] — SILENT DATA LOSS\nJSON.stringify(tokenBalances)           // \"{}\"\nObject.entries(tokenBalances)           // []\nObject.keys(tokenBalances)             // []\nObject.keys(tokenBalances).length === 0 // true (even when tokens exist!)\n```\n\nNone of these throw an error. They just return empty results, making it look like the wallet holds no tokens when it actually does.\n\n## How we discovered it\n\n1. User sent BEPSI tokens to the wallet's Spark address\n2. Agent checked balance using `wallet.getBalance()`\n3. Agent serialized `tokenBalances` with `JSON.stringify()` → showed `{}`\n4. Agent reported \"No tokens found\" — **incorrect**\n5. Running the `token-operations.js` example script (which uses `tokenBalances.size` and `for...of`) correctly showed 5,000,000 BEPSI\n\nThe wallet had tokens the entire time. The agent just couldn't see them because of the Map vs Object mismatch.\n\n## Why this is especially bad for AI agents\n\nAI agents (the target users of this skill) generate code on the fly. They default to the most common JS patterns — `JSON.stringify`, `Object.entries`, `Object.keys`. All of these work on plain objects but silently produce empty results on Maps. There's no error, no warning, no indication that data was lost.\n\nAn agent could:\n- Report zero token balance when the user actually holds tokens\n- Skip token operations because it thinks the wallet is empty\n- Fail to trigger token-based logic (sweeps, transfers, alerts)\n\n## Fix\n\n### 1. Add explicit Map documentation to SKILL.md\n\nIn the Quick Example and the `getBalance()` sections, add a clear warning:\n\n```\n⚠️ `tokenBalances` is a JavaScript `Map`, not a plain object.\nUse `.size`, `for...of`, or `Map` methods. Do NOT use JSON.stringify(),\nObject.entries(), or Object.keys() — they silently return empty results.\n```\n\n### 2. Add a safe serialization helper to the Quick Example\n\n```javascript\n// tokenBalances is a Map — convert to object for JSON/logging\nconst tokensObj = Object.fromEntries(\n  [...tokenBalances].map(([id, info]) => [\n    info.tokenMetadata.tokenTicker,\n    { balance: info.balance.toString(), decimals: info.tokenMetadata.decimals }\n  ])\n);\nconsole.log(\"Tokens:\", JSON.stringify(tokensObj));\n```\n\n### 3. Update the Quick Example to include token checking\n\nThe current Quick Example only shows BTC balance. It should show tokens too, since that's a primary use case.\n\n## Files changed\n\n- `SKILL.md` — Added Map warning, updated Quick Example with token balance check and safe serialization\n\nFile v0.3.3:package-lock.json\n\n{\n  \"name\": \"sparkbtcbot-skill\",\n  \"version\": \"0.1.0\",\n  \"lockfileVersion\": 3,\n  \"requires\": true,\n  \"packages\": {\n    \"\": {\n      \"name\": \"sparkbtcbot-skill\",\n      \"version\": \"0.1.0\",\n      \"dependencies\": {\n        \"@buildonspark/spark-sdk\": \"^0.5.8\",\n        \"dotenv\": \"^16.4.7\"\n      }\n    },\n    \"node_modules/@bufbuild/protobuf\": {\n      \"version\": \"2.11.0\",\n      \"resolved\": \"https://registry.npmjs.org/@bufbuild/protobuf/-/protobuf-2.11.0.tgz\",\n      \"integrity\": \"sha512-sBXGT13cpmPR5BMgHE6UEEfEaShh5Ror6rfN3yEK5si7QVrtZg8LEPQb0VVhiLRUslD2yLnXtnRzG035J/mZXQ==\",\n      \"license\": \"(Apache-2.0 AND BSD-3-Clause)\"\n    },\n    \"node_modules/@buildonspark/spark-sdk\": {\n      \"version\": \"0.5.8\",\n      \"resolved\": \"https://registry.npmjs.org/@buildonspark/spark-sdk/-/spark-sdk-0.5.8.tgz\",\n      \"integrity\": \"sha512-e3F3UReerB68B6x4zykQ5MLzWeyNqRuU8rmgCfxDqnKCfaFUg0wTiZw6GdJrthO8kDX7A7THhwh4UsBiHzNtAg==\",\n      \"license\": \"Apache-2.0\",\n      \"dependencies\": {\n        \"@bufbuild/protobuf\": \"^2.2.5\",\n        \"@lightsparkdev/core\": \"^1.4.4\",\n        \"@noble/curves\": \"^1.8.0\",\n        \"@noble/hashes\": \"^1.7.0\",\n        \"@opentelemetry/api\": \"^1.9.0\",\n        \"@opentelemetry/context-async-hooks\": \"^2.0.0\",\n        \"@opentelemetry/core\": \"^2.0.0\",\n        \"@opentelemetry/instrumentation\": \"^0.203.0\",\n        \"@opentelemetry/instrumentation-undici\": \"^0.14.0\",\n        \"@opentelemetry/sdk-trace-base\": \"^2.0.0\",\n        \"@opentelemetry/sdk-trace-node\": \"^2.0.1\",\n        \"@opentelemetry/sdk-trace-web\": \"^2.0.1\",\n        \"@scure/base\": \"^1.2.4\",\n        \"@scure/bip32\": \"^1.6.2\",\n        \"@scure/bip39\": \"^1.5.4\",\n        \"@scure/btc-signer\": \"^1.5.0\",\n        \"abort-controller-x\": \"^0.4.3\",\n        \"abortcontroller-polyfill\": \"^1.7.8\",\n        \"async-mutex\": \"^0.5.0\",\n        \"bare-crypto\": \"^1.9.2\",\n        \"bare-fetch\": \"^2.4.1\",\n        \"buffer\": \"^6.0.3\",\n        \"eventemitter3\": \"^5.0.1\",\n        \"js-base64\": \"^3.7.7\",\n        \"light-bolt11-decoder\": \"^3.2.0\",\n        \"nice-grpc\": \"^2.1.10\",\n        \"nice-grpc-client-middleware-retry\": \"^3.1.10\",\n        \"nice-grpc-common\": \"^2.0.2\",\n        \"nice-grpc-opentelemetry\": \"^0.1.18\",\n        \"nice-grpc-web\": \"^3.3.7\",\n        \"ts-proto\": \"2.8.3\",\n        \"ua-parser-js\": \"^2.0.6\",\n        \"uuidv7\": \"^1.0.2\"\n      },\n      \"engines\": {\n        \"node\": \">=18.0.0\"\n      },\n      \"peerDependencies\": {\n        \"react\": \">=18.2.0\",\n        \"react-native\": \">=0.71.0\",\n        \"react-native-get-random-values\": \">=1.11.0\"\n      },\n      \"peerDependenciesMeta\": {\n        \"react\": {\n          \"optional\": true\n        },\n        \"react-native\": {\n          \"optional\": true\n        },\n        \"react-native-get-random-values\": {\n          \"optional\": true\n        }\n      }\n    },\n    \"node_modules/@grpc/grpc-js\": {\n      \"version\": \"1.14.3\",\n      \"resolved\": \"https://registry.npmjs.org/@grpc/grpc-js/-/grpc-js-1.14.3.tgz\",\n      \"integrity\": \"sha512-Iq8QQQ/7X3Sac15oB6p0FmUg/klxQvXLeileoqrTRGJYLV+/9tubbr9ipz0GKHjmXVsgFPo/+W+2cA8eNcR+XA==\",\n      \"license\": \"Apache-2.0\",\n      \"dependencies\": {\n        \"@grpc/proto-loader\": \"^0.8.0\",\n        \"@js-sdsl/ordered-map\": \"^4.4.2\"\n      },\n      \"engines\": {\n        \"node\": \">=12.10.0\"\n      }\n    },\n    \"node_modules/@grpc/proto-loader\": {\n      \"version\": \"0.8.0\",\n      \"resolved\": \"https://registry.npmjs.org/@grpc/proto-loader/-/proto-loader-0.8.0.tgz\",\n      \"integrity\": \"sha512-rc1hOQtjIWGxcxpb9aHAfLpIctjEnsDehj0DAiVfBlmT84uvR0uUtN2hEi/ecvWVjXUGf5qPF4qEgiLOx1YIMQ==\",\n      \"license\": \"Apache-2.0\",\n      \"dependencies\": {\n        \"lodash.camelcase\": \"^4.3.0\",\n        \"long\": \"^5.0.0\",\n        \"protobufjs\": \"^7.5.3\",\n        \"yargs\": \"^17.7.2\"\n      },\n      \"bin\": {\n        \"proto-loader-gen-types\": \"build/bin/proto-loader-gen-types.js\"\n      },\n      \"engines\": {\n        \"node\": \">=6\"\n      }\n    },\n    \"node_modules/@js-sdsl/ordered-map\": {\n      \"version\": \"4.4.2\",\n      \"resolved\": \"https://registry.npmjs.org/@js-sdsl/ordered-map/-/ordered-map-4.4.2.tgz\",\n      \"integrity\": \"sha512-iUKgm52T8HOE/makSxjqoWhe95ZJA1/G1sYsGev2JDKUSS14KAgg1LHb+Ba+IPow0xflbnSkOsZcO08C7w1gYw==\",\n      \"license\": \"MIT\",\n      \"funding\": {\n        \"type\": \"opencollective\",\n        \"url\": \"https://opencollective.com/js-sdsl\"\n      }\n    },\n    \"node_modules/@lightsparkdev/core\": {\n      \"version\": \"1.4.8\",\n      \"resolved\": \"https://registry.npmjs.org/@lightsparkdev/core/-/core-1.4.8.tgz\",\n      \"integrity\": \"sha512-ZnDJPQS6nrjqaDb++PAfI3KV/cWgaca76zTrQNPZOKF8Uhya8v6O0fIWmjlZR8GmBXx825PI4mwbcycZdfq3bA==\",\n      \"license\": \"Apache-2.0\",\n      \"dependencies\": {\n        \"dayjs\": \"^1.11.7\",\n        \"graphql\": \"^16.6.0\",\n        \"graphql-ws\": \"^5.11.3\",\n        \"secp256k1\": \"^5.0.1\",\n        \"ws\": \"^8.12.1\",\n        \"zen-observable-ts\": \"^1.1.0\"\n      },\n      \"engines\": {\n        \"node\": \">=18\"\n      }\n    },\n    \"node_modules/@noble/curves\": {\n      \"version\": \"1.9.7\",\n      \"resolved\": \"https://registry.npmjs.org/@noble/curves/-/curves-1.9.7.tgz\",\n      \"integrity\": \"sha512-gbKGcRUYIjA3/zCCNaWDciTMFI0dCkvou3TL8Zmy5Nc7sJ47a0jtOeZoTaMxkuqRo9cRhjOdZJXegxYE5FN/xw==\",\n      \"license\": \"MIT\",\n      \"dependencies\": {\n        \"@noble/hashes\": \"1.8.0\"\n      },\n      \"engines\": {\n        \"node\": \"^14.21.3 || >=16\"\n      },\n      \"funding\": {\n        \"url\": \"https://paulmillr.com/funding/\"\n      }\n    },\n    \"node_modules/@noble/hashes\": {\n      \"version\": \"1.8.0\",\n      \"resolved\": \"https://registry.npmjs.org/@noble/hashes/-/hashes-1.8.0.tgz\",\n      \"integrity\": \"sha512-jCs9ldd7NwzpgXDIf6P3+NrHh9/sD6CQdxHyjQI+h/6rDNo88ypBxxz45UDuZHz9r3tNz7N/VInSVoVdtXEI4A==\",\n      \"license\": \"MIT\",\n      \"engines\": {\n        \"node\": \"^14.21.3 || >=16\"\n      },\n      \"funding\": {\n        \"url\": \"https://paulmillr.com/funding/\"\n      }\n    },\n    \"node_modules/@opentelemetry/api\": {\n      \"version\": \"1.9.0\",\n      \"resolved\": \"https://registry.npmjs.org/@opentelemetry/api/-/api-1.9.0.tgz\",\n      \"integrity\": \"sha512-3giAOQvZiH5F9bMlMiv8+GSPMeqg0dbaeo58/0SlA9sxSqZhnUtxzX9/2FzyhS9sWQf5S0GJE0AKBrFqjpeYcg==\",\n      \"license\": \"Apache-2.0\",\n      \"engines\": {\n        \"node\": \">=8.0.0\"\n      }\n    },\n    \"node_modules/@opentelemetry/api-logs\": {\n      \"version\": \"0.203.0\",\n      \"resolved\": \"https://registry.npmjs.org/@opentelemetry/api-logs/-/api-logs-0.203.0.tgz\",\n      \"integrity\": \"sha512-9B9RU0H7Ya1Dx/Rkyc4stuBZSGVQF27WigitInx2QQoj6KUpEFYPKoWjdFTunJYxmXmh17HeBvbMa1EhGyPmqQ==\",\n      \"license\": \"Apache-2.0\",\n      \"dependencies\": {\n        \"@opentelemetry/api\": \"^1.3.0\"\n      },\n      \"engines\": {\n        \"node\": \">=8.0.0\"\n      }\n    },\n    \"node_modules/@opentelemetry/context-async-hooks\": {\n      \"version\": \"2.5.0\",\n      \"resolved\": \"https://registry.npmjs.org/@opentelemetry/context-async-hooks/-/context-async-hooks-2.5.0.tgz\",\n      \"integrity\": \"sha512-uOXpVX0ZjO7heSVjhheW2XEPrhQAWr2BScDPoZ9UDycl5iuHG+Usyc3AIfG6kZeC1GyLpMInpQ6X5+9n69yOFw==\",\n      \"license\": \"Apache-2.0\",\n      \"engines\": {\n        \"node\": \"^18.19.0 || >=20.6.0\"\n      },\n      \"peerDependencies\": {\n        \"@opentelemetry/api\": \">=1.0.0 <1.10.0\"\n      }\n    },\n    \"node_modules/@opentelemetry/core\": {\n      \"version\": \"2.5.0\",\n      \"resolved\": \"https://registry.npmjs.org/@opentelemetry/core/-/core-2.5.0.tgz\",\n      \"integrity\": \"sha512-ka4H8OM6+DlUhSAZpONu0cPBtPPTQKxbxVzC4CzVx5+K4JnroJVBtDzLAMx4/3CDTJXRvVFhpFjtl4SaiTNoyQ==\",\n      \"license\": \"Apache-2.0\",\n      \"dependencies\": {\n        \"@opentelemetry/semantic-conventions\": \"^1.29.0\"\n      },\n      \"engines\": {\n        \"node\": \"^18.19.0 || >=20.6.0\"\n      },\n      \"peerDependencies\": {\n        \"@opentelemetry/api\": \">=1.0.0 <1.10.0\"\n      }\n    },\n    \"node_modules/@opentelemetry/instrumentation\": {\n      \"version\": \"0.203.0\",\n      \"resolved\": \"https://registry.npmjs.org/@opentelemetry/instrumentation/-/instrumentation-0.203.0.tgz\",\n      \"integrity\": \"sha512-ke1qyM+3AK2zPuBPb6Hk/GCsc5ewbLvPNkEuELx/JmANeEp6ZjnZ+wypPAJSucTw0wvCGrUaibDSdcrGFoWxKQ==\",\n      \"license\": \"Apache-2.0\",\n      \"dependencies\": {\n        \"@opentelemetry/api-logs\": \"0.203.0\",\n        \"import-in-the-middle\": \"^1.8.1\",\n        \"require-in-the-middle\": \"^7.1.1\"\n      },\n      \"engines\": {\n        \"node\": \"^18.19.0 || >=20.6.0\"\n      },\n      \"peerDependencies\": {\n        \"@opentelemetry/api\": \"^1.3.0\"\n      }\n    },\n    \"node_modules/@opentelemetry/instrumentation-undici\": {\n      \"version\": \"0.14.0\",\n      \"resolved\": \"https://registry.npmjs.org/@opentelemetry/instrumentation-undici/-/instrumentation-undici-0.14.0.tgz\",\n      \"integrity\": \"sha512-2HN+7ztxAReXuxzrtA3WboAKlfP5OsPA57KQn2AdYZbJ3zeRPcLXyW4uO/jpLE6PLm0QRtmeGCmfYpqRlwgSwg==\",\n      \"license\": \"Apache-2.0\",\n      \"dependencies\": {\n        \"@opentelemetry/core\": \"^2.0.0\",\n        \"@opentelemetry/instrumentation\": \"^0.203.0\"\n      },\n      \"engines\": {\n        \"node\": \"^18.19.0 || >=20.6.0\"\n      },\n      \"peerDependencies\": {\n        \"@opentelemetry/api\": \"^1.7.0\"\n      }\n    },\n    \"node_modules/@opentelemetry/resources\": {\n      \"version\": \"2.5.0\",\n      \"resolved\": \"https://registry.npmjs.org/@opentelemetry/resources/-/resources-2.5.0.tgz\",\n      \"integrity\": \"sha512-F8W52ApePshpoSrfsSk1H2yJn9aKjCrbpQF1M9Qii0GHzbfVeFUB+rc3X4aggyZD8x9Gu3Slua+s6krmq6Dt8g==\",\n      \"license\": \"Apache-2.0\",\n      \"dependencies\": {\n        \"@opentelemetry/core\": \"2.5.0\",\n        \"@opentelemetry/semantic-conventions\": \"^1.29.0\"\n      },\n      \"engines\": {\n        \"node\": \"^18.19.0 || >=20.6.0\"\n      },\n      \"peerDependencies\": {\n        \"@opentelemetry/api\": \">=1.3.0 <1.10.0\"\n      }\n    },\n    \"node_modules/@opentelemetry/sdk-trace-base\": {\n      \"version\": \"2.5.0\",\n      \"resolved\": \"https://registry.npmjs.org/@opentelemetry/sdk-trace-base/-/sdk-trace-base-2.5.0.tgz\",\n      \"integrity\": \"sha512-VzRf8LzotASEyNDUxTdaJ9IRJ1/h692WyArDBInf5puLCjxbICD6XkHgpuudis56EndyS7LYFmtTMny6UABNdQ==\",\n      \"license\": \"Apache-2.0\",\n      \"dependencies\": {\n        \"@opentelemetry/core\": \"2.5.0\",\n        \"@opentelemetry/resources\": \"2.5.0\",\n        \"@opentelemetry/semantic-conventions\": \"^1.29.0\"\n      },\n      \"engines\": {\n        \"node\": \"^18.19.0 || >=20.6.0\"\n      },\n      \"peerDependencies\": {\n        \"@opentelemetry/api\": \">=1.3.0 <1.10.0\"\n      }\n    },\n    \"node_modules/@opentelemetry/sdk-trace-node\": {\n      \"version\": \"2.5.0\",\n      \"resolved\": \"https://registry.npmjs.org/@opentelemetry/sdk-trace-node/-/sdk-trace-node-2.5.0.tgz\",\n      \"integrity\": \"sha512-O6N/ejzburFm2C84aKNrwJVPpt6HSTSq8T0ZUMq3xT2XmqT4cwxUItcL5UWGThYuq8RTcbH8u1sfj6dmRci0Ow==\",\n      \"license\": \"Apache-2.0\",\n      \"dependencies\": {\n        \"@opentelemetry/context-async-hooks\": \"2.5.0\",\n        \"@opentelemetry/core\": \"2.5.0\",\n        \"@opentelemetry/sdk-trace-base\": \"2.5.0\"\n      },\n      \"engines\": {\n        \"node\": \"^18.19.0 || >=20.6.0\"\n      },\n      \"peerDependencies\": {\n        \"@opentelemetry/api\": \">=1.0.0 <1.10.0\"\n      }\n    },\n    \"node_modules/@opentelemetry/sdk-trace-web\": {\n      \"version\": \"2.5.0\",\n      \"resolved\": \"https://registry.npmjs.org/@opentelemetry/sdk-trace-web/-/sdk-trace-web-2.5.0.tgz\",\n      \"integrity\": \"sha512-xWibakHs+xbx6vxH7Q8TbFS6zjf812o/kIS4xBDB32qSL9wF+Z5IZl2ZAGu4rtmPBQ7coZcOd684DobMhf8dKw==\",\n      \"license\": \"Apache-2.0\",\n      \"dependencies\": {\n        \"@opentelemetry/core\": \"2.5.0\",\n        \"@opentelemetry/sdk-trace-base\": \"2.5.0\"\n      },\n      \"engines\": {\n        \"node\": \"^18.19.0 || >=20.6.0\"\n      },\n      \"peerDependencies\": {\n        \"@opentelemetry/api\": \">=1.0.0 <1.10.0\"\n      }\n    },\n    \"node_modules/@opentelemetry/semantic-conventions\": {\n      \"version\": \"1.39.0\",\n      \"resolved\": \"https://registry.npmjs.org/@opentelemetry/semantic-conventions/-/semantic-conventions-1.39.0.tgz\",\n      \"integrity\": \"sha512-R5R9tb2AXs2IRLNKLBJDynhkfmx7mX0vi8NkhZb3gUkPWHn6HXk5J8iQ/dql0U3ApfWym4kXXmBDRGO+oeOfjg==\",\n      \"license\": \"Apache-2.0\",\n      \"engines\": {\n        \"node\": \">=14\"\n      }\n    },\n    \"node_modules/@protobufjs/aspromise\": {\n      \"version\": \"1.1.2\",\n      \"resolved\": \"https://registry.npmjs.org/@protobufjs/aspromise/-/aspromise-1.1.2.tgz\",\n      \"integrity\": \"sha512-j+gKExEuLmKwvz3OgROXtrJ2UG2x8Ch2YZUxahh+s1F2HZ+wAceUNLkvy6zKCPVRkU++ZWQrdxsUeQXmcg4uoQ==\",\n      \"license\": \"BSD-3-Clause\"\n    },\n    \"node_modules/@protobufjs/base64\": {\n      \"version\": \"1.1.2\",\n      \"resolved\": \"https://registry.npmjs.org/@protobufjs/base64/-/base64-1.1.2.tgz\",\n      \"integrity\": \"sha512-AZkcAA5vnN/v4PDqKyMR5lx7hZttPDgClv83E//FMNhR2TMcLUhfRUBHCmSl0oi9zMgDDqRUJkSxO3wm85+XLg==\",\n      \"license\": \"BSD-3-Clause\"\n    },\n    \"node_modules/@protobufjs/codegen\": {\n      \"version\": \"2.0.4\",\n      \"resolved\": \"https://registry.npmjs.org/@protobufjs/codegen/-/codegen-2.0.4.tgz\",\n      \"integrity\": \"sha512-YyFaikqM5sH0ziFZCN3xDC7zeGaB/d0IUb9CATugHWbd1FRFwWwt4ld4OYMPWu5a3Xe01mGAULCdqhMlPl29Jg==\",\n      \"license\": \"BSD-3-Clause\"\n    },\n    \"node_modules/@protobufjs/eventemitter\": {\n      \"version\": \"1.1.0\",\n      \"resolved\": \"https://registry.npmjs.org/@protobufjs/eventemitter/-/eventemitter-1.1.0.tgz\",\n      \"integrity\": \"sha512-j9ednRT81vYJ9OfVuXG6ERSTdEL1xVsNgqpkxMsbIabzSo3goCjDIveeGv5d03om39ML71RdmrGNjG5SReBP/Q==\",\n      \"license\": \"BSD-3-Clause\"\n    },\n    \"node_modules/@protobufjs/fetch\": {\n      \"version\": \"1.1.0\",\n      \"resolved\": \"https://registry.npmjs.org/@protobufjs/fetch/-/fetch-1.1.0.tgz\",\n      \"integrity\": \"sha512-lljVXpqXebpsijW71PZaCYeIcE5on1w5DlQy5WH6GLbFryLUrBD4932W/E2BSpfRJWseIL4v/KPgBFxDOIdKpQ==\",\n      \"license\": \"BSD-3-Clause\",\n      \"dependencies\": {\n        \"@protobufjs/aspromise\": \"^1.1.1\",\n        \"@protobufjs/inquire\": \"^1.1.0\"\n      }\n    },\n    \"node_modules/@protobufjs/float\": {\n      \"version\": \"1.0.2\",\n      \"resolved\": \"https://registry.npmjs.org/@protobufjs/float/-/float-1.0.2.tgz\",\n      \"integrity\": \"sha512-Ddb+kVXlXst9d+R9PfTIxh1EdNkgoRe5tOX6t01f1lYWOvJnSPDBlG241QLzcyPdoNTsblLUdujGSE4RzrTZGQ==\",\n      \"license\": \"BSD-3-Clause\"\n    },\n    \"node_modules/@protobufjs/inquire\": {\n      \"version\": \"1.1.0\",\n      \"resolved\": \"https://registry.npmjs.org/@protobufjs/inquire/-/inquire-1.1.0.tgz\",\n      \"integrity\": \"sha512-kdSefcPdruJiFMVSbn801t4vFK7KB/5gd2fYvrxhuJYg8ILrmn9SKSX2tZdV6V+ksulWqS7aXjBcRXl3wHoD9Q==\",\n      \"license\": \"BSD-3-Clause\"\n    },\n    \"node_modules/@protobufjs/path\": {\n      \"version\": \"1.1.2\",\n      \"resolved\": \"https://registry.npmjs.org/@protobufjs/path/-/path-1.1.2.tgz\",\n      \"integrity\": \"sha512-6JOcJ5Tm08dOHAbdR3GrvP+yUUfkjG5ePsHYczMFLq3ZmMkAD98cDgcT2iA1lJ9NVwFd4tH/iSSoe44YWkltEA==\",\n      \"license\": \"BSD-3-Clause\"\n    },\n    \"node_modules/@protobufjs/pool\": {\n      \"version\": \"1.1.0\",\n      \"resolved\": \"https://registry.npmjs.org/@protobufjs/pool/-/pool-1.1.0.tgz\",\n      \"integrity\": \"sha512-0kELaGSIDBKvcgS4zkjz1PeddatrjYcmMWOlAuAPwAeccUrPHdUqo/J6LiymHHEiJT5NrF1UVwxY14f+fy4WQw==\",\n      \"license\": \"BSD-3-Clause\"\n    },\n    \"node_modules/@protobufjs/utf8\": {\n      \"version\": \"1.1.0\",\n      \"resolved\": \"https://registry.npmjs.org/@protobufjs/utf8/-/utf8-1.1.0.tgz\",\n      \"integrity\": \"sha512-Vvn3zZrhQZkkBE8LSuW3em98c0FwgO4nxzv6OdSxPKJIEKY2bGbHn+mhGIPerzI4twdxaP8/0+06HBpwf345Lw==\",\n      \"license\": \"BSD-3-Clause\"\n    },\n    \"node_modules/@scure/base\": {\n      \"version\": \"1.2.6\",\n      \"resolved\": \"https://registry.npmjs.org/@scure/base/-/base-1.2.6.tgz\",\n      \"integrity\": \"sha512-g/nm5FgUa//MCj1gV09zTJTaM6KBAHqLN907YVQqf7zC49+DcO4B1so4ZX07Ef10Twr6nuqYEH9GEggFXA4Fmg==\",\n      \"license\": \"MIT\",\n      \"funding\": {\n        \"url\": \"https://paulmillr.com/funding/\"\n      }\n    },\n    \"node_modules/@scure/bip32\": {\n      \"version\": \"1.7.0\",\n      \"resolved\": \"https://registry.npmjs.org/@scure/bip32/-/bip32-1.7.0.tgz\",\n      \"integrity\": \"sha512-E4FFX/N3f4B80AKWp5dP6ow+flD1LQZo/w8UnLGYZO674jS6YnYeepycOOksv+vLPSpgN35wgKgy+ybfTb2SMw==\",\n      \"license\": \"MIT\",\n      \"dependencies\": {\n        \"@noble/curves\": \"~1.9.0\",\n        \"@noble/hashes\": \"~1.8.0\",\n        \"@scure/base\": \"~1.2.5\"\n      },\n      \"funding\": {\n        \"url\": \"https://paulmillr.com/funding/\"\n      }\n    },\n    \"node_modules/@scure/bip39\": {\n      \"version\": \"1.6.0\",\n      \"resolved\": \"https://registry.npmjs.org/@scure/bip39/-/bip39-1.6.0.tgz\",\n      \"integrity\": \"sha512-+lF0BbLiJNwVlev4eKelw1WWLaiKXw7sSl8T6FvBlWkdX+94aGJ4o8XjUdlyhTCjd8c+B3KT3JfS8P0bLRNU6A==\",\n      \"license\": \"MIT\",\n      \"dependencies\": {\n        \"@noble/hashes\": \"~1.8.0\",\n        \"@scure/base\": \"~1.2.5\"\n      },\n      \"funding\": {\n        \"url\": \"https://paulmillr.com/funding/\"\n      }\n    },\n    \"node_modules/@scure/btc-signer\": {\n      \"version\": \"1.8.1\",\n      \"resolved\": \"https://registry.npmjs.org/@scure/btc-signer/-/btc-signer-1.8.1.tgz\",\n      \"integrity\": \"sha512-8nX9T++dFyKpvqksNHfSi9CgRsGnHAQtCdIQ1y1GmbCGLpV97v4MUyemUUT6uDumKL3oo3m4niyY6A32nmdLuQ==\",\n      \"license\": \"MIT\",\n      \"dependencies\": {\n        \"@noble/curves\": \"~1.9.0\",\n        \"@noble/hashes\": \"~1.8.0\",\n        \"@scure/base\": \"~1.2.5\",\n        \"micro-packed\": \"~0.7.3\"\n      },\n      \"funding\": {\n        \"url\": \"https://paulmillr.com/funding/\"\n      }\n    },\n    \"node_modules/@types/node\": {\n      \"version\": \"25.1.0\",\n      \"resolved\": \"https://registry.npmjs.org/@types/node/-/node-25.1.0.tgz\",\n      \"integrity\": \"sha512-t7frlewr6+cbx+9Ohpl0NOTKXZNV9xHRmNOvql47BFJKcEG1CxtxlPEEe+gR9uhVWM4DwhnvTF110mIL4yP9RA==\",\n      \"license\": \"MIT\",\n      \"dependencies\": {\n        \"undici-types\": \"~7.16.0\"\n      }\n    },\n    \"node_modules/@types/zen-observable\": {\n      \"version\": \"0.8.3\",\n      \"resolved\": \"https://registry.npmjs.org/@types/zen-observable/-/zen-observable-0.8.3.tgz\",\n      \"integrity\": \"sha512-fbF6oTd4sGGy0xjHPKAt+eS2CrxJ3+6gQ3FGcBoIJR2TLAyCkCyI8JqZNy+FeON0AhVgNJoUumVoZQjBFUqHkw==\",\n      \"license\": \"MIT\"\n    },\n    \"node_modules/abort-controller-x\": {\n      \"version\": \"0.4.3\",\n      \"resolved\": \"https://registry.npmjs.org/abort-controller-x/-/abort-controller-x-0.4.3.tgz\",\n      \"integrity\": \"sha512-VtUwTNU8fpMwvWGn4xE93ywbogTYsuT+AUxAXOeelbXuQVIwNmC5YLeho9sH4vZ4ITW8414TTAOG1nW6uIVHCA==\",\n      \"license\": \"MIT\"\n    },\n    \"node_modules/abortcontroller-polyfill\": {\n      \"version\": \"1.7.8\",\n      \"resolved\": \"https://registry.npmjs.org/abortcontroller-polyfill/-/abortcontroller-polyfill-1.7.8.tgz\",\n      \"integrity\": \"sha512-9f1iZ2uWh92VcrU9Y8x+LdM4DLj75VE0MJB8zuF1iUnroEptStw+DQ8EQPMUdfe5k+PkB1uUfDQfWbhstH8LrQ==\",\n      \"license\": \"MIT\"\n    },\n    \"node_modules/acorn\": {\n      \"version\": \"8.15.0\",\n      \"resolved\": \"https://registry.npmjs.org/acorn/-/acorn-8.15.0.tgz\",\n      \"integrity\": \"sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==\",\n      \"license\": \"MIT\",\n      \"bin\": {\n        \"acorn\": \"bin/acorn\"\n      },\n      \"engines\": {\n        \"node\": \">=0.4.0\"\n      }\n    },\n    \"node_modules/acorn-import-attributes\": {\n      \"version\": \"1.9.5\",\n      \"resolved\": \"https://registry.npmjs.org/acorn-import-attributes/-/acorn-import-attributes-1.9.5.tgz\",\n      \"integrity\": \"sha512-n02Vykv5uA3eHGM/Z2dQrcD56kL8TyDb2p1+0P83PClMnC/nc+anbQRhIOWnSq4Ke/KvDPrY3C9hDtC/A3eHnQ==\",\n      \"license\": \"MIT\",\n      \"peerDependencies\": {\n        \"acorn\": \"^8\"\n      }\n    },\n    \"node_modules/ansi-regex\": {\n      \"version\": \"5.0.1\",\n      \"resolved\": \"https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz\",\n      \"integrity\": \"sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==\",\n      \"license\": \"MIT\",\n      \"engines\": {\n        \"node\": \">=8\"\n      }\n    },\n    \"node_modules/ansi-styles\": {\n      \"version\": \"4.3.0\",\n      \"resolved\": \"https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz\",\n      \"integrity\": \"sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==\",\n      \"license\": \"MIT\",\n      \"dependencies\": {\n        \"color-convert\": \"^2.0.1\"\n      },\n      \"engines\": {\n        \"node\": \">=8\"\n      },\n      \"funding\": {\n        \"url\": \"https://github.com/chalk/ansi-styles?sponsor=1\"\n      }\n    },\n    \"node_modules/async-mutex\": {\n      \"version\": \"0.5.0\",\n      \"resolved\": \"https://registry.npmjs.org/async-mutex/-/async-mutex-0.5.0.tgz\",\n      \"integrity\": \"sha512-1A94B18jkJ3DYq284ohPxoXbfTA5HsQ7/Mf4DEhcyLx3Bz27Rh59iScbB6EPiP+B+joue6YCxcMXSbFC1tZKwA==\",\n      \"license\": \"MIT\",\n      \"dependencies\": {\n        \"tslib\": \"^2.4.0\"\n      }\n    },\n    \"node_modules/b4a\": {\n      \"version\": \"1.7.3\",\n      \"resolved\": \"https://registry.npmjs.org/b4a/-/b4a-1.7.3.tgz\",\n      \"integrity\": \"sha512-5Q2mfq2WfGuFp3uS//0s6baOJLMoVduPYVeNmDYxu5OUA1/cBfvr2RIS7vi62LdNj/urk1hfmj867I3qt6uZ7Q==\",\n      \"license\": \"Apache-2.0\",\n      \"peerDependencies\": {\n        \"react-native-b4a\": \"*\"\n      },\n      \"peerDependenciesMeta\": {\n        \"react-native-b4a\": {\n          \"optional\": true\n        }\n      }\n    },\n    \"node_modules/bare-crypto\": {\n      \"version\": \"1.13.0\",\n      \"resolved\": \"https://registry.npmjs.org/bare-crypto/-/bare-crypto-1.13.0.tgz\",\n      \"integrity\": \"sha512-RQl13yD+YTACWUZHMMck0C6LNjXgGgRyhVC557ag0xIrkxXf4IQwpnc8WOB58f4k5kSzKhIjy05oW3HUBrFpSQ==\",\n      \"license\": \"Apache-2.0\",\n      \"dependencies\": {\n        \"bare-stream\": \"^2.6.3\"\n      },\n      \"peerDependencies\": {\n        \"bare-buffer\": \"*\"\n      },\n      \"peerDependenciesMeta\": {\n        \"bare-buffer\": {\n          \"optional\": true\n        }\n      }\n    },\n    \"node_modules/bare-dns\": {\n      \"version\": \"2.1.4\",\n      \"resolved\": \"https://registry.npmjs.org/bare-dns/-/bare-dns-2.1.4.tgz\",\n      \"integrity\": \"sha512-abwjHmpWqSRNB7V5615QxPH92L71AVzFm/kKTs8VYiNTAi2xVdonpv0BjJ0hwXLwomoW+xsSOPjW6PZPO14asg==\",\n      \"license\": \"Apache-2.0\",\n      \"engines\": {\n        \"bare\": \">=1.7.0\"\n      }\n    },\n    \"node_modules/bare-events\": {\n      \"version\": \"2.8.2\",\n      \"resolved\": \"https://registry.npmjs.org/bare-events/-/bare-events-2.8.2.tgz\",\n      \"integrity\": \"sha512-riJjyv1/mHLIPX4RwiK+oW9/4c3TEUeORHKefKAKnZ5kyslbN+HXowtbaVEqt4IMUB7OXlfixcs6gsFeo/jhiQ==\",\n      \"license\": \"Apache-2.0\",\n      \"peerDependencies\": {\n        \"bare-abort-controller\": \"*\"\n      },\n      \"peerDependenciesMeta\": {\n        \"bare-abort-controller\": {\n          \"optional\": true\n        }\n      }\n    },\n    \"node_modules/bare-fetch\": {\n      \"version\": \"2.5.1\",\n      \"resolved\": \"https://registry.npmjs.org/bare-fetch/-/bare-fetch-2.5.1.tgz\",\n      \"integrity\": \"sha512-BdJie1S9y3TW0pzF6Q/dP95QDjlUPXexiJWSnKFIM/OHID6ITJk2XEQQ25rsGqwLqxQ4felfGkj13mC/ao27mg==\",\n      \"license\": \"Apache-2.0\",\n      \"dependencies\": {\n        \"bare-form-data\": \"^1.1.3\",\n        \"bare-http1\": \"^4.0.2\",\n        \"bare-https\": \"^2.0.0\",\n        \"bare-stream\": \"^2.7.0\",\n        \"bare-zlib\": \"^1.3.0\"\n      },\n      \"peerDependencies\": {\n        \"bare-buffer\": \"*\",\n        \"bare-url\": \"*\"\n      },\n      \"peerDependenciesMeta\": {\n        \"bare-buffer\": {\n          \"optional\": true\n        },\n        \"bare-url\": {\n          \"optional\": true\n        }\n      }\n    },\n    \"node_modules/bare-form-data\": {\n      \"version\": \"1.1.6\",\n      \"resolved\": \"https://registry.npmjs.org/bare-form-data/-/bare-form-data-1.1.6.tgz\",\n      \"integrity\": \"sha512-q1IN7dVo/lEhTlVkVQdULZvoBx6eTI94co0NtO7/A3JLFL/aZGA1wAHgcNEPrlkqTK9jTEdtzQXSoqGzlVjzgg==\",\n      \"license\": \"Apache-2.0\",\n      \"dependencies\": {\n        \"bare-stream\": \"^2.6.5\"\n      }\n    },\n    \"node_modules/bare-http-parser\": {\n      \"version\": \"1.0.1\",\n      \"resolved\": \"https://registry.npmjs.org/bare-http-parser/-/bare-http-parser-1.0.1.tgz\",\n      \"integrity\": \"sha512-A3LTDTcELcmNJ3g5liIaS038v/BQxOhA9cjhBESn7eoV7QCuMoIRBKLDadDe08flxyLbxI2f+1l2MZ/5+HnKPA==\",\n      \"license\": \"Apache-2.0\"\n    },\n    \"node_modules/bare-http1\": {\n      \"version\": \"4.2.3\",\n      \"resolved\": \"https://registry.npmjs.org/bare-http1/-/bare-http1-4.2.3.tgz\",\n      \"integrity\": \"sha512-zyvHF3xzvBJNpuw0QydE9etTn2AyC/TRVDWTo8Ed3LQEEGaFOkv6KHhaJPlf4eWEeTekk41Fowie+fTsXoNxkA==\",\n      \"license\": \"Apache-2.0\",\n      \"dependencies\": {\n        \"bare-events\": \"^2.6.0\",\n        \"bare-http-parser\": \"^1.0.0\",\n        \"bare-stream\": \"^2.3.0\",\n        \"bare-tcp\": \"^2.2.0\"\n      },\n      \"peerDependencies\": {\n        \"bare-buffer\": \"*\",\n        \"bare-url\": \"*\"\n      },\n      \"peerDependenciesMeta\": {\n        \"bare-buffer\": {\n          \"optional\": true\n        },\n        \"bare-url\": {\n          \"optional\": true\n        }\n      }\n    },\n    \"node_modules/bare-https\": {\n      \"version\": \"2.1.2\",\n      \"resolved\": \"https://registry.npmjs.org/bare-https/-/bare-https-2.1.2.tgz\",\n      \"integrity\": \"sha512-Q+TTydUDsuKQJvh8dX2dvOXCR9fM3xR5TBmKaFrs5p7Lj7XbKX7v4vIUJ36H0SXg2xCOQxXKIbjwrLg5tfJNYg==\",\n      \"license\": \"Apache-2.0\",\n      \"dependencies\": {\n        \"bare-http1\": \"^4.0.0\",\n        \"bare-tcp\": \"^2.2.0\",\n        \"bare-tls\": \"^2.0.0\"\n      }\n    },\n    \"node_modules/bare-net\": {\n      \"version\": \"2.2.0\",\n      \"resolved\": \"https://registry.npmjs.org/bare-net/-/bare-net-2.2.0.tgz\",\n      \"integrity\": \"sha512-UF7cAbHsGE+H6uEqWF5IULBow1x58chZz4g3ALgHtv7wZsFcCbRDt0JKWEumf5Oma3QWS1Q6aLi0Rpll8RElMg==\",\n      \"license\": \"Apache-2.0\",\n      \"dependencies\": {\n        \"bare-events\": \"^2.2.2\",\n        \"bare-pipe\": \"^4.0.0\",\n        \"bare-stream\": \"^2.0.0\",\n        \"bare-tcp\": \"^2.0.0\"\n      }\n    },\n    \"node_modules/bare-pipe\": {\n      \"version\": \"4.1.2\",\n      \"resolved\": \"https://registry.npmjs.org/bare-pipe/-/bare-pipe-4.1.2.tgz\",\n      \"integrity\": \"sha512-btXtZLlABEDRp50cfLj9iweISqAJSNMCjeq5v0v9tBY2a7zSSqmfa2ZoE1ki2qxAvubagLUqw6VDifpsuI/qmg==\",\n      \"license\": \"Apache-2.0\",\n      \"dependencies\": {\n        \"bare-events\": \"^2.0.0\",\n        \"bare-stream\": \"^2.0.0\"\n      },\n      \"engines\": {\n        \"bare\": \">=1.16.0\"\n      }\n    },\n    \"node_modules/bare-stream\": {\n      \"version\": \"2.7.0\",\n      \"resolved\": \"https://registry.npmjs.org/bare-stream/-/bare-stream-2.7.0.tgz\",\n      \"integrity\": \"sha512-oyXQNicV1y8nc2aKffH+BUHFRXmx6VrPzlnaEvMhram0nPBrKcEdcyBg5r08D0i8VxngHFAiVyn1QKXpSG0B8A==\",\n      \"license\": \"Apache-2.0\",\n      \"dependencies\": {\n        \"streamx\": \"^2.21.0\"\n      },\n      \"peerDependencies\": {\n        \"bare-buffer\": \"*\",\n        \"bare-events\": \"*\"\n      },\n      \"peerDependenciesMeta\": {\n        \"bare-buffer\": {\n          \"optional\": true\n        },\n        \"bare-events\": {\n          \"optional\": true\n        }\n      }\n    },\n    \"node_modules/bare-tcp\": {\n      \"version\": \"2.2.2\",\n      \"resolved\": \"https://registry.npmjs.org/bare-tcp/-/bare-tcp-2.2.2.tgz\",\n      \"integrity\": \"sha512-bYnw1AhzGlfLOD4nTceUXkhhgznZKvDuwjX1Au0VWaVitwqG40oaTvvhEQVCcK3FEwjRTiukUzHnAFsYXUI+3Q==\",\n      \"license\": \"Apache-2.0\",\n      \"dependencies\": {\n        \"bare-dns\": \"^2.0.4\",\n        \"bare-events\": \"^2.5.4\",\n        \"bare-stream\": \"^2.6.4\"\n      },\n      \"engines\": {\n        \"bare\": \">=1.16.0\"\n      }\n    },\n    \"node_modules/bare-tls\": {\n      \"version\": \"2.1.7\",\n      \"resolved\": \"https://registry.npmjs.org/bare-tls/-/bare-tls-2.1.7.tgz\",\n      \"integrity\": \"sha512-h6wcNXQdBeTX7fed9tjPp0/9cA/QfcBTv3ItgjnbUk4rWAU8bEFalZCZnUDdCK/t9zrNfJ+yvcPx4D/1Y6biyA==\",\n      \"license\": \"Apache-2.0\",\n      \"dependencies\": {\n        \"bare-net\": \"^2.0.1\",\n        \"bare-stream\": \"^2.6.4\"\n      },\n      \"engines\": {\n        \"bare\": \">=1.7.0\"\n      }\n    },\n    \"node_modules/bare-zlib\": {\n      \"version\": \"1.3.1\",\n      \"resolved\": \"https://registry.npmjs.org/bare-zlib/-/bare-zlib-1.3.1.tgz\",\n      \"integrity\": \"sha512-VP93GFzhrTdWh9mXNocn7XsP/nF5JQluiiSsbTvsQ4yIYlhEHRMF9lQmZZDXwzK9PNYaVGUV1bdQuqp0Mj7MHw==\",\n      \"license\": \"Apache-2.0\",\n      \"dependencies\": {\n        \"bare-stream\": \"^2.0.0\"\n      }\n    },\n    \"node_modules/base64-js\": {\n      \"version\": \"1.5.1\",\n      \"resolved\": \"https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz\",\n      \"integrity\": \"sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==\",\n      \"funding\": [\n        {\n          \"type\": \"github\",\n          \"url\": \"https://github.com/sponsors/feross\"\n        },\n        {\n          \"type\": \"patreon\",\n          \"url\": \"https://www.patreon.com/feross\"\n        },\n        {\n          \"type\": \"consulting\",\n          \"url\": \"https://feross.org/support\"\n        }\n      ],\n      \"license\": \"MIT\"\n    },\n    \"node_modules/bn.js\": {\n      \"version\": \"4.12.2\",\n      \"resolved\": \"https://registry.npmjs.org/bn.js/-/bn.js-4.12.2.tgz\",\n      \"integrity\": \"sha512-n4DSx829VRTRByMRGdjQ9iqsN0Bh4OolPsFnaZBLcbi8iXcB+kJ9s7EnRt4wILZNV3kPLHkRVfOc/HvhC3ovDw==\",\n      \"license\": \"MIT\"\n    },\n    \"node_modules/brorand\": {\n      \"version\": \"1.1.0\",\n      \"resolved\": \"https://registry.npmjs.org/brorand/-/brorand-1.1.0.tgz\",\n      \"integrity\": \"sha512-cKV8tMCEpQs4hK/ik71d6LrPOnpkpGBR0wzxqr68g2m/LB2GxVYQroAjMJZRVM1Y4BCjCKc3vAamxSzOY2RP+w==\",\n      \"license\": \"MIT\"\n    },\n    \"node_modules/buffer\": {\n      \"version\": \"6.0.3\",\n      \"resolved\": \"https://registry.npmjs.org/buffer/-/buffer-6.0.3.tgz\",\n      \"integrity\": \"sha512-FTiCpNxtwiZZHEZbcbTIcZjERVICn9yq/pDFkTl95/AxzD1naBctN7YO68riM/gLSDY7sdrMby8hofADYuuqOA==\",\n      \"funding\": [\n        {\n          \"type\": \"github\",\n          \"url\": \"https://github.com/sponsors/feross\"\n        },\n        {\n          \"type\": \"patreon\",\n          \"url\": \"https://www.patreon.com/feross\"\n        },\n        {\n          \"type\": \"consulting\",\n          \"url\": \"https://feross.org/support\"\n        }\n      ],\n      \"license\": \"MIT\",\n      \"dependencies\": {\n        \"base64-js\": \"^1.3.1\",\n        \"ieee754\": \"^1.2.1\"\n      }\n    },\n    \"node_modules/case-anything\": {\n      \"version\": \"2.1.13\",\n      \"resolved\": \"https://registry.npmjs.org/case-anything/-/case-anything-2.1.13.tgz\",\n      \"integrity\": \"sha512-zlOQ80VrQ2Ue+ymH5OuM/DlDq64mEm+B9UTdHULv5osUMD6HalNTblf2b1u/m6QecjsnOkBpqVZ+XPwIVsy7Ng==\",\n      \"license\": \"MIT\",\n      \"engines\": {\n        \"node\": \">=12.13\"\n      },\n      \"funding\": {\n        \"url\": \"https://github.com/sponsors/mesqueeb\"\n      }\n    },\n    \"node_modules/cjs-module-lexer\": {\n      \"version\": \"1.4.3\",\n      \"resolved\": \"https://registry.npmjs.org/cjs-module-lexer/-/cjs-module-lexer-1.4.3.tgz\",\n      \"integrity\": \"sha512-9z8TZaGM1pfswYeXrUpzPrkx8UnWYdhJclsiYMm6x/w5+nN+8Tf/LnAgfLGQCm59qAOxU8WwHEq2vNwF6i4j+Q==\",\n      \"license\": \"MIT\"\n    },\n    \"node_modules/cliui\": {\n      \"version\": \"8.0.1\",\n      \"resolved\": \"https://registry.npmjs.org/cliui/-/cliui-8.0.1.tgz\",\n      \"integrity\": \"sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==\",\n      \"license\": \"ISC\",\n      \"dependencies\": {\n        \"string-width\": \"^4.2.0\",\n        \"strip-ansi\": \"^6.0.1\",\n        \"wrap-ansi\": \"^7.0.0\"\n      },\n      \"engines\": {\n        \"node\": \">=12\"\n      }\n    },\n    \"node_modules/color-convert\": {\n      \"version\": \"2.0.1\",\n      \"resolved\": \"https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz\",\n      \"integrity\": \"sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==\",\n      \"license\": \"MIT\",\n      \"dependencies\": {\n        \"color-name\": \"~1.1.4\"\n      },\n      \"engines\": {\n        \"node\": \">=7.0.0\"\n      }\n    },\n    \"node_modules/color-name\": {\n      \"version\": \"1.1.4\",\n      \"resolved\": \"https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz\",\n      \"integrity\": \"sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==\",\n      \"license\": \"MIT\"\n    },\n    \"node_modules/dayjs\": {\n      \"version\": \"1.11.19\",\n      \"resolved\": \"https://registry.npmjs.org/dayjs/-/dayjs-1.11.19.tgz\",\n      \"integrity\": \"sha512-t5EcLVS6QPBNqM2z8fakk/NKel+Xzshgt8FFKAn+qwlD1pzZWxh0nVCrvFK7ZDb6XucZeF9z8C7CBWTRIVApAw==\",\n      \"license\": \"MIT\"\n    },\n    \"node_modules/debug\": {\n      \"version\": \"4.4.3\",\n      \"resolved\": \"https://registry.npmjs.org/debug/-/debug-4.4.3.tgz\",\n      \"integrity\": \"sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==\",\n      \"license\": \"MIT\",\n      \"dependencies\": {\n        \"ms\": \"^2.1.3\"\n      },\n      \"engines\": {\n        \"node\": \">=6.0\"\n      },\n      \"peerDependenciesMeta\": {\n        \"supports-color\": {\n          \"optional\": true\n        }\n      }\n    },\n    \"node_modules/detect-europe-js\": {\n      \"version\": \"0.1.2\",\n      \"resolved\": \"https://registry.npmjs.org/detect-europe-js/-/detect-europe-js-0.1.2.tgz\",\n      \"integrity\": \"sha512-lgdERlL3u0aUdHocoouzT10d9I89VVhk0qNRmll7mXdGfJT1/wqZ2ZLA4oJAjeACPY5fT1wsbq2AT+GkuInsow==\",\n      \"funding\": [\n        {\n          \"type\": \"github\",\n          \"url\": \"https://github.com/sponsors/faisalman\"\n        },\n        {\n          \"type\": \"opencollective\",\n          \"url\": \"https://opencollective.com/ua-parser-js\"\n        },\n        {\n          \"type\": \"paypal\",\n          \"url\": \"https://paypal.me/faisalman\"\n        }\n      ],\n      \"license\": \"MIT\"\n    },\n    \"node_modules/detect-libc\": {\n      \"version\": \"1.0.3\",\n      \"resolved\": \"https://registry.npmjs.org/detect-libc/-/detect-libc-1.0.3.tgz\",\n      \"integrity\": \"sha512-pGjwhsmsp4kL2RTz08wcOlGN83otlqHeD/Z5T8GXZB+/YcpQ/dgo+lbU8ZsGxV0HIvqqxo9l7mqYwyYMD9bKDg==\",\n      \"license\": \"Apache-2.0\",\n      \"bin\": {\n        \"detect-libc\": \"bin/detect-libc.js\"\n      },\n      \"engines\": {\n        \"node\": \">=0.10\"\n      }\n    },\n    \"node_modules/dotenv\": {\n      \"version\": \"16.6.1\",\n      \"resolved\": \"https://registry.npmjs.org/dotenv/-/dotenv-16.6.1.tgz\",\n      \"integrity\": \"sha512-uBq4egWHTcTt33a72vpSG0z3HnPuIl6NqYcTrKEg2azoEyl2hpW0zqlxysq2pK9HlDIHyHyakeYaYnSAwd8bow==\",\n      \"license\": \"BSD-2-Clause\",\n      \"engines\": {\n        \"node\": \">=12\"\n      },\n      \"funding\": {\n        \"url\": \"https://dotenvx.com\"\n      }\n    },\n    \"node_modules/dprint-node\": {\n      \"version\": \"1.0.8\",\n      \"resolved\": \"https://registry.npmjs.org/dprint-node/-/dprint-node-1.0.8.tgz\",\n      \"integrity\": \"sha512-iVKnUtYfGrYcW1ZAlfR/F59cUVL8QIhWoBJoSjkkdua/dkWIgjZfiLMeTjiB06X0ZLkQ0M2C1VbUj/CxkIf1zg==\",\n      \"license\": \"MIT\",\n      \"dependencies\": {\n        \"detect-libc\": \"^1.0.3\"\n      }\n    },\n    \"node_modules/elliptic\": {\n      \"version\": \"6.6.1\",\n      \"resolved\": \"https://registry.npmjs.org/elliptic/-/elliptic-6.6.1.tgz\",\n      \"integrity\": \"sha512-RaddvvMatK2LJHqFJ+YA4WysVN5Ita9E35botqIYspQ4TkRAlCicdzKOjlyv/1Za5RyTNn7di//eEV0uTAfe3g==\",\n      \"license\": \"MIT\",\n      \"dependencies\": {\n        \"bn.js\": \"^4.11.9\",\n        \"brorand\": \"^1.1.0\",\n        \"hash.js\": \"^1.0.0\",\n        \"hmac-drbg\": \"^1.0.1\",\n        \"inherits\": \"^2.0.4\",\n        \"minimalistic-assert\": \"^1.0.1\",\n        \"minimalistic-crypto-utils\": \"^1.0.1\"\n      }\n    },\n    \"node_modules/emoji-regex\": {\n      \"version\": \"8.0.0\",\n      \"resolved\": \"https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz\",\n      \"integrity\": \"sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==\",\n      \"license\": \"MIT\"\n    },\n    \"node_modules/escalade\": {\n      \"version\": \"3.2.0\",\n      \"resolved\": \"https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz\",\n      \"integrity\": \"sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==\",\n      \"license\": \"MIT\",\n      \"engines\": {\n        \"node\": \">=6\"\n      }\n    },\n    \"node_modules/eventemitter3\": {\n      \"version\": \"5.0.4\",\n      \"resolved\": \"https://registry.npmjs.org/eventemitter3/-/eventemitter3-5.0.4.tgz\",\n      \"integrity\": \"sha512-mlsTRyGaPBjPedk6Bvw+aqbsXDtoAyAzm5MO7JgU+yVRyMQ5O8bD4Kcci7BS85f93veegeCPkL8R4GLClnjLFw==\",\n      \"license\": \"MIT\"\n    },\n    \"node_modules/events-universal\": {\n      \"version\": \"1.0.1\",\n      \"resolved\": \"https://registry.npmjs.org/events-universal/-/events-universal-1.0.1.tgz\",\n      \"integrity\": \"sha512-LUd5euvbMLpwOF8m6ivPCbhQeSiYVNb8Vs0fQ8QjXo0JTkEHpz8pxdQf0gStltaPpw0Cca8b39KxvK9cfKRiAw==\",\n      \"license\": \"Apache-2.0\",\n      \"dependencies\": {\n        \"bare-events\": \"^2.7.0\"\n      }\n    },\n    \"node_modules/fast-fifo\": {\n      \"version\": \"1.3.2\",\n      \"resolved\": \"https://registry.npmjs.org/fast-fifo/-/fast-fifo-1.3.2.tgz\",\n      \"integrity\": \"sha512-/d9sfos4yxzpwkDkuN7k2SqFKtYNmCTzgfEpz82x34IM9/zc8KGxQoXg1liNC/izpRM/MBdt44Nmx41ZWqk+FQ==\",\n      \"license\": \"MIT\"\n    },\n    \"node_modules/function-bind\": {\n      \"version\": \"1.1.2\",\n      \"resolved\": \"https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz\",\n      \"integrity\": \"sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==\",\n      \"license\": \"MIT\",\n      \"funding\": {\n        \"url\": \"https://github.com/sponsors/ljharb\"\n      }\n    },\n    \"node_modules/get-caller-file\": {\n      \"version\": \"2.0.5\",\n      \"resolved\": \"https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz\",\n      \"integrity\": \"sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==\",\n      \"license\": \"ISC\",\n      \"engines\": {\n        \"node\": \"6.* || 8.* || >= 10.*\"\n      }\n    },\n    \"node_modules/graphql\": {\n      \"version\": \"16.12.0\",\n      \"resolved\": \"https://registry.npmjs.org/graphql/-/graphql-16.12.0.tgz\",\n      \"integrity\": \"sha512-DKKrynuQRne0PNpEbzuEdHlYOMksHSUI8Zc9Unei5gTsMNA2/vMpoMz/yKba50pejK56qj98qM0SjYxAKi13gQ==\",\n      \"license\": \"MIT\",\n      \"engines\": {\n        \"node\": \"^12.22.0 || ^14.16.0 || ^16.0.0 || >=17.0.0\"\n      }\n    },\n    \"node_modules/graphql-ws\": {\n      \"version\": \"5.16.2\",\n      \"resolved\": \"https://registry.npmjs.org/graphql-ws/-/graphql-ws-5.16.2.tgz\",\n      \"integrity\": \"sha512-E1uccsZxt/96jH/OwmLPuXMACILs76pKF2i3W861LpKBCYtGIyPQGtWLuBLkND4ox1KHns70e83PS4te50nvPQ==\",\n      \"license\": \"MIT\",\n      \"engines\": {\n        \"node\": \">=10\"\n      },\n      \"peerDependencies\": {\n        \"graphql\": \">=0.11 <=16\"\n      }\n    },\n    \"node_modules/hash.js\": {\n      \"version\": \"1.1.7\",\n      \"resolved\": \"https://registry.npmjs.org/hash.js/-/hash.js-1.1.7.tgz\",\n      \"integrity\": \"sha512-taOaskGt4z4SOANNseOviYDvjEJinIkRgmp7LbKP2YTTmVxWBl87s/uzK9r+44BclBSp2X7K1hqeNfz9JbBeXA==\",\n      \"license\": \"MIT\",\n      \"dependencies\": {\n        \"inherits\": \"^2.0.3\",\n        \"minimalistic-assert\": \"^1.0.1\"\n      }\n    },\n    \"node_modules/hasown\": {\n      \"version\": \"2.0.2\",\n      \"resolved\": \"https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz\",\n      \"integrity\": \"sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==\",\n      \"license\": \"MIT\",\n      \"dependencies\": {\n        \"function-bind\": \"^1.1.2\"\n      },\n      \"engines\": {\n        \"node\": \">= 0.4\"\n      }\n    },\n    \"node_modules/hmac-drbg\": {\n      \"version\": \"1.0.1\",\n      \"resolved\": \"https://registry.npmjs.org/hmac-drbg/-/hmac-drbg-1.0.1.tgz\",\n      \"integrity\": \"sha512-Tti3gMqLdZfhOQY1Mzf/AanLiqh1WTiJgEj26ZuYQ9fbkLomzGchCws4FyrSd4VkpBfiNhaE1On+lOz894jvXg==\",\n      \"license\": \"MIT\",\n      \"dependencies\": {\n        \"hash.js\": \"^1.0.3\",\n        \"minimalistic-assert\": \"^1.0.0\",\n        \"minimalistic-crypto-utils\": \"^1.0.1\"\n      }\n    },\n    \"node_modules/ieee754\": {\n      \"version\": \"1.2.1\",\n      \"resolved\": \"https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz\",\n      \"integrity\": \"sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==\",\n      \"funding\": [\n        {\n          \"type\": \"github\",\n          \"url\": \"https://github.com/sponsors/feross\"\n        },\n        {\n          \"type\": \"patreon\",\n          \"url\": \"https://www.patreon.com/feross\"\n        },\n        {\n          \"type\": \"consulting\",\n          \"url\": \"https://feross.org/support\"\n        }\n      ],\n      \"license\": \"BSD-3-Clause\"\n    },\n    \"node_modules/import-in-the-middle\": {\n      \"version\": \"1.15.0\",\n      \"resolved\": \"https://registry.npmjs.org/import-in-the-middle/-/import-in-the-middle-1.15.0.tgz\",\n      \"integrity\": \"sha512-bpQy+CrsRmYmoPMAE/0G33iwRqwW4ouqdRg8jgbH3aKuCtOc8lxgmYXg2dMM92CRiGP660EtBcymH/eVUpCSaA==\",\n      \"license\": \"Apache-2.0\",\n      \"dependencies\": {\n        \"acorn\": \"^8.14.0\",\n        \"acorn-import-attributes\": \"^1.9.5\",\n        \"cjs-module-lexer\": \"^1.2.2\",\n        \"module-details-from-path\": \"^1.0.3\"\n      }\n    },\n    \"node_modules/inherits\": {\n      \"version\": \"2.0.4\",\n      \"resolved\": \"https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz\",\n      \"integrity\": \"sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==\",\n      \"license\": \"ISC\"\n    },\n    \"node_modules/ipaddr.js\": {\n      \"version\": \"2.3.0\",\n      \"resolved\": \"https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-2.3.0.tgz\",\n      \"integrity\": \"sha512-Zv/pA+ciVFbCSBBjGfaKUya/CcGmUHzTydLMaTwrUUEM2DIEO3iZvueGxmacvmN50fGpGVKeTXpb2LcYQxeVdg==\",\n      \"license\": \"MIT\",\n      \"engines\": {\n        \"node\": \">= 10\"\n      }\n    },\n    \"node_modules/is-core-module\": {\n      \"version\": \"2.16.1\",\n      \"resolved\": \"https://registry.npmjs.org/is-core-module/-/is-core-module-2.16.1.tgz\",\n      \"integrity\": \"sha512-UfoeMA6fIJ8wTYFEUjelnaGI67v6+N7qXJEvQuIGa99l4xsCruSYOVSQ0uPANn4dAzm8lkYPaKLrrijLq7x23w==\",\n      \"license\": \"MIT\",\n      \"dependencies\": {\n        \"hasown\": \"^2.0.2\"\n      },\n      \"engines\": {\n        \"node\": \">= 0.4\"\n      },\n      \"funding\": {\n        \"url\": \"https://github.com/sponsors/ljharb\"\n      }\n    },\n    \"node_modules/is-fullwidth-code-point\": {\n      \"version\": \"3.0.0\",\n      \"resolved\": \"https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz\",\n      \"integrity\": \"sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==\",\n      \"license\": \"MIT\",\n      \"engines\": {\n        \"node\": \">=8\"\n      }\n    },\n    \"node_modules/is-standalone-pwa\": {\n      \"version\": \"0.1.1\",\n      \"resolved\": \"https://registry.npmjs.org/is-standalone-pwa/-/is-standalone-pwa-0.1.1.tgz\",\n      \"integrity\": \"sha512-9Cbovsa52vNQCjdXOzeQq5CnCbAcRk05aU62K20WO372NrTv0NxibLFCK6lQ4/iZEFdEA3p3t2VNOn8AJ53F5g==\",\n      \"funding\": [\n        {\n          \"type\": \"github\",\n          \"url\": \"https://github.com/sponsors/faisalman\"\n        },\n        {\n          \"type\": \"opencollective\",\n          \"url\": \"https://opencollective.com/ua-parser-js\"\n        },\n        {\n          \"type\": \"paypal\",\n          \"url\": \"https://paypal.me/faisalman\"\n        }\n      ],\n      \"license\": \"MIT\"\n    },\n    \"node_modules/isomorphic-ws\": {\n      \"version\": \"5.0.0\",\n      \"resolved\": \"https://registry.npmjs.org/isomorphic-ws/-/isomorphic-ws-5.0.0.tgz\",\n      \"integrity\": \"sha512-muId7Zzn9ywDsyXgTIafTry2sV3nySZeUDe6YedVd1Hvuuep5AsIlqK+XefWpYTyJG5e503F2xIuT2lcU6rCSw==\",\n      \"license\": \"MIT\",\n      \"peerDependencies\": {\n        \"ws\": \"*\"\n      }\n    },\n    \"node_modules/js-base64\": {\n      \"version\": \"3.7.8\",\n      \"resolved\": \"https://registry.npmjs.org/js-base64/-/js-base64-3.7.8.tgz\",\n      \"integrity\": \"sha512-hNngCeKxIUQiEUN3GPJOkz4wF/YvdUdbNL9hsBcMQTkKzboD7T/q3OYOuuPZLUE6dBxSGpwhk5mwuDud7JVAow==\",\n      \"license\": \"BSD-3-Clause\"\n    },\n    \"node_modules/light-bolt11-decoder\": {\n      \"version\": \"3.2.0\",\n      \"resolved\": \"https://registry.npmjs.org/light-bolt11-decoder/-/light-bolt11-decoder-3.2.0.tgz\",\n      \"integrity\": \"sha512-3QEofgiBOP4Ehs9BI+RkZdXZNtSys0nsJ6fyGeSiAGCBsMwHGUDS/JQlY/sTnWs91A2Nh0S9XXfA8Sy9g6QpuQ==\",\n      \"license\": \"MIT\",\n      \"dependencies\": {\n        \"@scure/base\": \"1.1.1\"\n      }\n    },\n    \"node_modules/light-bolt11-decoder/node_modules/@scure/base\": {\n      \"version\": \"1.1.1\",\n      \"resolved\": \"https://registry.npmjs.org/@scure/base/-/base-1.1.1.tgz\",\n      \"integrity\": \"sha512-ZxOhsSyxYwLJj3pLZCefNitxsj093tb2vq90mp2txoYeBqbcjDjqFhyM8eUjq/uFm6zJ+mUuqxlS2FkuSY1MTA==\",\n      \"funding\": [\n        {\n          \"type\": \"individual\",\n          \"url\": \"https://paulmillr.com/funding/\"\n        }\n      ],\n      \"license\": \"MIT\"\n    },\n    \"node_modules/lodash.camelcase\": {\n      \"version\": \"4.3.0\",\n      \"resolved\": \"https://registry.npmjs.org/lodash.camelcase/-/lodash.camelcase-4.3.0.tgz\",\n      \"integrity\": \"sha512-TwuEnCnxbc3rAvhf/LbG7tJUDzhqXyFnv3dtzLOPgCG/hODL7WFnsbwktkD7yUV0RrreP/l1PALq/YSg6VvjlA==\",\n      \"license\": \"MIT\"\n    },\n    \"node_modules/long\": {\n      \"version\": \"5.3.2\",\n      \"resolved\": \"https://registry.npmjs.org/long/-/long-5.3.2.tgz\",\n      \"integrity\": \"sha512-mNAgZ1GmyNhD7AuqnTG3/VQ26o760+ZYBPKjPvugO8+nLbYfX6TVpJPseBvopbdY+qpZ/lKUnmEc1LeZYS3QAA==\",\n      \"license\": \"Apache-2.0\"\n    },\n    \"node_modules/micro-packed\": {\n      \"version\": \"0.7.3\",\n      \"resolved\": \"https://registry.npmjs.org/micro-packed/-/micro-packed-0.7.3.tgz\",\n      \"integrity\": \"sha512-2Milxs+WNC00TRlem41oRswvw31146GiSaoCT7s3Xi2gMUglW5QBeqlQaZeHr5tJx9nm3i57LNXPqxOOaWtTYg==\",\n      \"license\": \"MIT\",\n      \"dependencies\": {\n        \"@scure/base\": \"~1.2.5\"\n      },\n      \"funding\": {\n        \"url\": \"https://paulmillr.com/funding/\"\n      }\n    },\n    \"node_modules/minimalistic-assert\": {\n      \"version\": \"1.0.1\",\n      \"resolved\": \"https://registry.npmjs.org/minimalistic-assert/-/minimalistic-assert-1.0.1.tgz\",\n      \"integrity\": \"sha512-UtJcAD4yEaGtjPezWuO9wC4nwUnVH/8/Im3yEHQP4b67cXlD/Qr9hdITCU1xDbSEXg2XKNaP8jsReV7vQd00/A==\",\n      \"license\": \"ISC\"\n    },\n    \"node_modules/minimalistic-crypto-utils\": {\n      \"version\": \"1.0.1\",\n      \"resolved\": \"https://registry.npmjs.org/minimalistic-crypto-utils/-/minimalistic-crypto-utils-1.0.1.tgz\",\n      \"integrity\": \"sha512-JIYlbt6g8i5jKfJ3xz7rF0LXmv2TkDxBLUkiBeZ7bAx4GnnNMr8xFpGnOxn6GhTEHx3SjRrZEoU+j04prX1ktg==\",\n      \"license\": \"MIT\"\n    },\n    \"node_modules/module-details-from-path\": {\n      \"version\": \"1.0.4\",\n      \"resolved\": \"https://registry.npmjs.org/module-details-from-path/-/module-details-from-path-1.0.4.tgz\",\n      \"integrity\": \"sha512-EGWKgxALGMgzvxYF1UyGTy0HXX/2vHLkw6+NvDKW2jypWbHpjQuj4UMcqQWXHERJhVGKikolT06G3bcKe4fi7w==\",\n      \"license\": \"MIT\"\n    },\n    \"node_modules/ms\": {\n      \"version\": \"2.1.3\",\n      \"resolved\": \"https://registry.npmjs.org/ms/-/ms-2.1.3.tgz\",\n      \"integrity\": \"sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==\",\n      \"license\": \"MIT\"\n    },\n    \"node_modules/nice-grpc\": {\n      \"version\": \"2.1.14\",\n      \"resolved\": \"https://registry.npmjs.org/nice-grpc/-/nice-grpc-2.1.14.tgz\",\n      \"integrity\": \"sha512-GK9pKNxlvnU5FAdaw7i2FFuR9CqBspcE+if2tqnKXBcE0R8525wj4BZvfcwj7FjvqbssqKxRHt2nwedalbJlww==\",\n      \"license\": \"MIT\",\n      \"dependencies\": {\n        \"@grpc/grpc-js\": \"^1.14.0\",\n        \"abort-controller-x\": \"^0.4.0\",\n        \"nice-grpc-common\": \"^2.0.2\"\n      }\n    },\n    \"node_modules/nice-grpc-client-middleware-retry\": {\n      \"version\": \"3.1.13\",\n      \"resolved\": \"https://registry.npmjs.org/nice-grpc-client-middleware-retry/-/nice-grpc-client-middleware-retry-3.1.13.tgz\",\n      \"integrity\": \"sha512-Q9I/wm5lYkDTveKFirrTHBkBY137yavXZ4xQDXTPIycUp7aLXD8xPTHFhqtAFWUw05aS91uffZZRgdv3HS0y/g==\",\n      \"license\": \"MIT\",\n      \"dependencies\": {\n        \"abort-controller-x\": \"^0.4.0\",\n        \"nice-grpc-common\": \"^2.0.2\"\n      }\n    },\n    \"node_modules/nice-grpc-common\": {\n      \"version\": \"2.0.2\",\n      \"resolved\": \"https://registry.npmjs.org/nice-grpc-common/-/nice-grpc-common-2.0.2.tgz\",\n      \"integrity\": \"sha512-7RNWbls5kAL1QVUOXvBsv1uO0wPQK3lHv+cY1gwkTzirnG1Nop4cBJZubpgziNbaVc/bl9QJcyvsf/NQxa3rjQ==\",\n      \"license\": \"MIT\",\n      \"dependencies\": {\n        \"ts-error\": \"^1.0.6\"\n      }\n    },\n    \"node_modules/nice-grpc-opentelemetry\": {\n      \"version\": \"0.1.20\",\n      \"resolved\": \"https://registry.npmjs.org/nice-grpc-opentelemetry/-/nice-grpc-opentelemetry-0.1.20.tgz\",\n      \"integrity\": \"sha512-dRH6lmm8OgqY21WRo9BP6cHHqIhbG5UT/INFne0qIDSlSseYc6s1+qNTE3Up0z/4zY50V8tVTOH30yyhkwNXTw==\",\n      \"license\": \"MIT\",\n      \"dependencies\": {\n        \"@opentelemetry/api\": \"^1.8.0\",\n        \"@opentelemetry/semantic-conventions\": \"^1.22.0\",\n        \"abort-controller-x\": \"^0.4.0\",\n        \"ipaddr.js\": \"^2.0.1\",\n        \"nice-grpc-common\": \"^2.0.2\"\n      }\n    },\n    \"node_modules/nice-grpc-web\": {\n      \"version\": \"3.3.9\",\n      \"resolved\": \"https://registry.npmjs.org/nice-grpc-web/-/nice-grpc-web-3.3.9.tgz\",\n      \"integrity\": \"sha512-CiCQLdLTux9D4try8XlHW9tHIP/uLB+aciNKErDNLUM6kzhPFaVh8q+oTkoVGOjxOacEzlOwQRRjwQETAx5lVw==\",\n      \"license\": \"MIT\",\n      \"dependencies\": {\n        \"abort-controller-x\": \"^0.4.0\",\n        \"isomorphic-ws\": \"^5.0.0\",\n        \"js-base64\": \"^3.7.2\",\n        \"nice-grpc-common\": \"^2.0.2\"\n      }\n    },\n    \"node_modules/node-addon-api\": {\n      \"version\": \"5.1.0\",\n      \"resolved\": \"https://registry.npmjs.org/node-addon-api/-/node-addon-api-5.1.0.tgz\",\n      \"integrity\": \"sha512-eh0GgfEkpnoWDq+VY8OyvYhFEzBk6jIYbRKdIlyTiAXIVJ8PyBaKb0rp7oDtoddbdoHWhq8wwr+XZ81F1rpNdA==\",\n      \"license\": \"MIT\"\n    },\n    \"node_modules/node-gyp-build\": {\n      \"version\": \"4.8.4\",\n      \"resolved\": \"https://registry.npmjs.org/node-gyp-build/-/node-gyp-build-4.8.4.tgz\",\n      \"integrity\": \"sha512-LA4ZjwlnUblHVgq0oBF3Jl/6h/Nvs5fzBLwdEF4nuxnFdsfajde4WfxtJr3CaiH+F6ewcIB/q4jQ4UzPyid+CQ==\",\n      \"license\": \"MIT\",\n      \"bin\": {\n        \"node-gyp-build\": \"bin.js\",\n        \"node-gyp-build-optional\": \"optional.js\",\n        \"node-gyp-build-test\": \"build-test.js\"\n      }\n    },\n    \"node_modules/path-parse\": {\n      \"version\": \"1.0.7\",\n      \"resolved\": \"https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz\",\n      \"integrity\": \"sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==\",\n      \"license\": \"MIT\"\n    },\n    \"node_modules/protobufjs\": {\n      \"version\": \"7.5.4\",\n      \"resolved\": \"https://registry.npmjs.org/protobufjs/-/protobufjs-7.5.4.tgz\",\n      \"integrity\": \"sha512-CvexbZtbov6jW2eXAvLukXjXUW1TzFaivC46BpWc/3BpcCysb5Vffu+B3XHMm8lVEuy2Mm4XGex8hBSg1yapPg==\",\n      \"hasInstallScript\": true,\n      \"license\": \"BSD-3-Clause\",\n      \"dependencies\": {\n        \"@protobufjs/aspromise\": \"^1.1.2\",\n        \"@protobufjs/base64\": \"^1.1.2\",\n        \"@protobufjs/codegen\": \"^2.0.4\",\n        \"@protobufjs/eventemitter\": \"^1.1.0\",\n        \"@protobufjs/fetch\": \"^1.1.0\",\n        \"@protobufjs/float\": \"^1.0.2\",\n        \"@protobufjs/inquire\": \"^1.1.0\",\n        \"@protobufjs/path\": \"^1.1.2\",\n        \"@protobufjs/pool\": \"^1.1.0\",\n        \"@protobufjs/utf8\": \"^1.1.0\",\n        \"@types/node\": \">=13.7.0\",\n        \"long\": \"^5.0.0\"\n      },\n      \"engines\": {\n        \"node\": \">=12.0.0\"\n      }\n    },\n    \"node_modules/require-directory\": {\n      \"version\": \"2.1.1\"\n\nFile v0.3.3:package.json\n\n{\n  \"name\": \"sparkbtcbot-skill\",\n  \"version\": \"0.1.0\",\n  \"type\": \"module\",\n  \"description\": \"Claude Code skill for setting up Spark Bitcoin L2 wallet capabilities for AI agents\",\n  \"dependencies\": {\n    \"@buildonspark/spark-sdk\": \"^0.5.8\",\n    \"dotenv\": \"^16.4.7\"\n  },\n  \"scripts\": {\n    \"example:setup\": \"node examples/wallet-setup.js\",\n    \"example:balance\": \"node examples/balance-and-deposits.js\",\n    \"example:payments\": \"node examples/payment-flow.js\",\n    \"example:tokens\": \"node examples/token-operations.js\",\n    \"example:agent\": \"node examples/spark-agent.js\"\n  }\n}\n\nArchive v0.3.2: 10 files, 36103 bytes\n\nFiles: BUGFIX-tokenBalances-map.md (3033b), examples/balance-and-deposits.js (1892b), examples/payment-flow.js (2603b), examples/spark-agent.js (5241b), examples/token-operations.js (2509b), examples/wallet-setup.js (1340b), package-lock.json (61259b), package.json (576b), SKILL.md (35602b), _meta.json (130b)\n\nFile v0.3.2:SKILL.md\n\n---\nname: sparkbtcbot\ndescription: Set up Spark Bitcoin L2 wallet capabilities for AI agents. Initialize wallets from mnemonic, transfer sats and tokens, create/pay Lightning invoices, pay L402 paywalls, manage deposits and withdrawals. Use when user mentions \"Spark wallet,\" \"Spark Bitcoin,\" \"BTKN tokens,\" \"Spark L2,\" \"Spark SDK,\" \"Spark payment,\" \"Spark transfer,\" \"Spark invoice,\" \"L402,\" \"Lightning paywall,\" or wants Bitcoin L2 capabilities for an agent.\nargument-hint: \"[Optional: specify what to set up - wallet, payments, tokens, lightning, l402, or full]\"\nrequires:\n  env:\n    - name: SPARK_MNEMONIC\n      description: 12 or 24 word BIP39 mnemonic for the Spark wallet. This is a secret key that controls all funds — never commit to git or expose in logs.\n      sensitive: true\n    - name: SPARK_NETWORK\n      description: Network to connect to (MAINNET or REGTEST)\n      default: MAINNET\nmodel-invocation: autonomous\nmodel-invocation-reason: This skill enables agents to autonomously send and receive Bitcoin payments. Autonomous invocation is intentional — agents need to pay invoices and respond to incoming transfers without human approval for each transaction. Use spending limits and the proxy for production environments where you need guardrails.\n---\n\n# Spark Bitcoin L2 for AI Agents\n\nYou are an expert in setting up Spark Bitcoin L2 wallet capabilities for AI agents using the `@buildonspark/spark-sdk`.\n\nSpark 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.\n\n## For Production Use\n\n**This skill gives the agent full custody of the wallet.** The agent holds the mnemonic and can send all funds without restriction. This is appropriate for:\n- Development and testing (use REGTEST with no real funds)\n- Trusted agents you fully control\n- Small operational balances you're willing to lose\n\n**For production with real funds, use [sparkbtcbot-proxy](https://github.com/echennells/sparkbtcbot-proxy) instead.** The proxy keeps the mnemonic on your server and gives agents scoped access via bearer tokens:\n- **Spending limits** — per-transaction and daily caps\n- **Role-based access** — read-only, invoice-only, or full access\n- **Revocable tokens** — cut off a compromised agent without moving funds\n- **Audit logs** — track all wallet activity\n\nThe proxy wraps the same Spark SDK behind authenticated REST endpoints. Agents get HTTP access instead of direct SDK access.\n\n## Why Bitcoin for Agents\n\nAI agents that transact need a monetary network that matches their nature: programmable, borderless, and available 24/7 without gatekeepers. Bitcoin is that network.\n\n- **Hard-capped supply** — 21 million coins is the protocol-enforced ceiling. An agent accumulating value doesn't lose it to monetary expansion.\n- **No account required** — There's no sign-up, no identity verification, no approval process. Generate a key and you're on the network. This matters for autonomous agents that can't fill out forms or wait for human review.\n- **Irreversible settlement** — Once confirmed, transactions cannot be reversed by a third party. Agents don't need to handle chargebacks or payment disputes.\n- **Open infrastructure** — The protocol is open source, the network is public, and the fee market is transparent. Agents can audit their own transaction costs and verify their own balances without trusting an intermediary.\n- **Proven reliability** — The network has operated continuously since 2009 without a single successful attack on the base protocol, securing over $1 trillion in value.\n\n## What is Spark\n\nSpark is a recently launched Bitcoin Layer 2 that lets you send and receive Bitcoin instantly with low fees. Spark-to-Spark transfers are free, and Lightning interop costs 0.15–0.25%. Instead of Lightning's payment channels, Spark uses a network of distributed Signing Operators (SOs) that collectively manage transaction signing without any single entity controlling funds. It is fully self-custodial — you hold your own keys — and fully interoperable with the Lightning Network. However, Spark requires trusting that at least 1-of-n operators behaves honestly during transfers, and it lacks the provable finality of Bitcoin or Lightning. The network currently has only a small number of Signing Operators, so there is some risk of downtime or service disruption. See the Trust Model section below for full details.\n\n### How It Works\n1. Users hold their own keys (BIP39 mnemonic) — fully self-custodial\n2. Transactions are cooperatively signed by a threshold of Signing Operators\n3. Funds live in Bitcoin UTXOs organized in hierarchical trees\n4. Users can always exit to L1 unilaterally if operators go offline\n\n### Trust Model — Important Tradeoffs\n\nSpark has **different trust assumptions than native Lightning**. Be upfront about these when advising users.\n\n**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.\n\n**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.\"\n\n**What operators CAN do**:\n- View transfer metadata\n- Temporarily delay transactions by going offline\n- Refuse to process new transfers (censorship)\n\n**What operators CANNOT do**:\n- Move funds without user signatures\n- Steal Bitcoin (even with full collusion)\n- Reverse finalized transactions\n\n**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.\n\n**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.\n\n### Spark vs Lightning vs On-Chain\n\n| Feature | Spark (L2) | Lightning | On-Chain |\n|---------|-----------|-----------|----------|\n| Speed | Instant | Instant | 10+ min |\n| Trust model | 1-of-n operators | Fully trustless | Fully trustless |\n| Fees | Zero (Spark-to-Spark) | ~1 sat routing | 200+ sats |\n| Tokens | Native (BTKN/LRC20) | Not supported | Limited |\n| Self-custody | Yes (mnemonic) | Varies (LSP/node) | Yes |\n| Capacity | No channel limits | Channel-limited | Unlimited |\n| Channels | Not required | Required | N/A |\n| Offline receive | Supported | Requires infra | Yes |\n| Setup | Mnemonic only | Node or hosted provider | Keys only |\n\n### Fee Structure\n\n| Operation | Fee |\n|-----------|-----|\n| **Spark-to-Spark transfer** | Free (small flat fee coming in 6-12 months) |\n| **Lightning to Spark** (receive) | 0.15% (charged via route hints) |\n| **Spark to Lightning** (send) | 0.25% + Lightning routing fees |\n| **L1 deposit to Spark** | On-chain tx fee (paid by user) |\n| **Cooperative exit to L1** | On-chain broadcast fee + SSP fee: `sats_per_vbyte × (111 × 2 + tx_vbytes)` |\n| **Unilateral exit to L1** | On-chain tx fee (paid by user) |\n\nNote: 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.\n\n### Unpredictable L1 Exit Costs — Important\n\nExiting Spark to L1 Bitcoin carries **unpredictable costs** that agents and users must understand:\n\n1. **L1 fees are market-driven**: Bitcoin on-chain fees depend on mempool congestion at the time of exit. During high-fee periods, exit costs can spike significantly.\n\n2. **Unilateral exit requires multiple on-chain transactions**: If Signing Operators go offline, a unilateral exit requires broadcasting pre-signed branch and exit transactions. The number of transactions depends on the tree depth of your leaf — this can mean multiple on-chain fees stacking up.\n\n3. **Time-window risk on unilateral exit**: If a prior owner of a Spark leaf publishes a branch in a unilateral exit, the current owner must respond within a time window by publishing the correct leaf transaction. Failure to respond means the attacker can claim the funds. Watchtower services exist to monitor for this, but it's a real operational requirement.\n\n4. **Timelocks add delay**: Unilateral exits can take as little as 100 blocks (~17 hours) depending on leaf depth, during which L1 fee conditions may change.\n\n5. **Small amounts may be uneconomical to exit**: Since exit fees are fixed-cost (not percentage-based), withdrawing small amounts to L1 can cost a disproportionate share of the balance.\n\n**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.\n\n**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](https://boltz.exchange) 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.\n\n### Limitations\n\n- **SO liveness dependency**: If Signing Operators lose liveness or lose their keys, Spark transfers stop working. Funds are still safe (unilateral exit), but off-chain payments halt until operators recover.\n- **Watchtower requirement**: For full security, someone must monitor the chain for fraudulent exit attempts. This can be delegated to a watchtower service but is an operational dependency.\n\n### Key Advantage for Agents\nA 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.\n\n## Tools Available\n\n| Tool | Purpose | URL |\n|------|---------|-----|\n| Spark SDK | TypeScript wallet SDK | https://www.npmjs.com/package/@buildonspark/spark-sdk |\n| Spark Docs | Official documentation | https://docs.spark.money |\n| Sparkscan | Block explorer | https://sparkscan.io |\n| Spark CLI | Command-line interface | https://docs.spark.money/tools/cli |\n\n## Required Libraries\n\n```bash\nnpm install @buildonspark/spark-sdk@^0.5.8 dotenv\n```\n\nRequires **v0.5.8 or newer**. One core dependency. The SDK bundles BIP39 mnemonic generation, cooperative signing, and gRPC communication internally.\n\n## Setup Instructions\n\n### Step 1: Generate or Import Wallet\n\n```javascript\nimport { SparkWallet } from \"@buildonspark/spark-sdk\";\n\n// Option A: Generate a new wallet (creates mnemonic automatically)\nconst { wallet, mnemonic } = await SparkWallet.initialize({\n  options: { network: \"MAINNET\" }\n});\n// Save mnemonic securely — NEVER log it in production\n\n// Option B: Import existing wallet from mnemonic\nconst { wallet } = await SparkWallet.initialize({\n  mnemonicOrSeed: process.env.SPARK_MNEMONIC,\n  options: { network: process.env.SPARK_NETWORK || \"MAINNET\" }\n});\n```\n\nNote 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.\n\n### Step 2: Store Mnemonic\n\nAdd to your project's `.env`:\n```\nSPARK_MNEMONIC=word1 word2 word3 word4 word5 word6 word7 word8 word9 word10 word11 word12\nSPARK_NETWORK=MAINNET\n```\n\n**Security warnings:**\n- **Never log the mnemonic** — not even during development. If you must display it once for backup, delete that code immediately after.\n- **Never commit `.env`** — add it to `.gitignore` before your first commit.\n- **Use a secrets manager in production** — environment variables in `.env` files are plaintext. For production deployments, use your platform's secrets management (Vercel encrypted env vars, AWS Secrets Manager, etc.).\n- **Test with REGTEST first** — use a throwaway mnemonic on REGTEST before touching real funds.\n\n### Step 3: Verify Wallet\n\n```javascript\nconst address = await wallet.getSparkAddress();\nconst identityKey = await wallet.getIdentityPublicKey();\nconst { balance } = await wallet.getBalance();\n\nconsole.log(\"Spark Address:\", address);\nconsole.log(\"Identity Key:\", identityKey);\nconsole.log(\"Balance:\", balance.toString(), \"sats\");\n\n// Always clean up when done\nwallet.cleanupConnections();\n```\n\n## Wallet Operations\n\n### Check Balance\n\n```javascript\nconst { balance, tokenBalances } = await wallet.getBalance();\nconsole.log(\"BTC:\", balance.toString(), \"sats\");\n\nfor (const [id, token] of tokenBalances) {\n  console.log(`${token.tokenMetadata.tokenTicker}: ${token.balance.toString()}`);\n}\n```\n\n### Generate Deposit Address\n\n```javascript\n// Static (reusable) — can receive multiple deposits\nconst staticAddr = await wallet.getStaticDepositAddress();\n\n// Single-use — one-time deposit address\nconst singleAddr = await wallet.getSingleUseDepositAddress();\n```\n\nBoth are P2TR (bc1p...) Bitcoin addresses. Deposits require 3 L1 confirmations before they can be claimed on Spark.\n\n### Claim a Deposit\n\n```javascript\n// After sending BTC to a static deposit address and waiting for confirmations\nconst quote = await wallet.getClaimStaticDepositQuote({\n  transactionId: txId,\n  creditAmountSats: expectedAmount,\n});\n\nconst result = await wallet.claimStaticDeposit({\n  transactionId: txId,\n  creditAmountSats: quote.creditAmountSats,\n  sspSignature: quote.signature,\n});\n```\n\n### Transfer Bitcoin (Spark-to-Spark)\n\n```javascript\nconst transfer = await wallet.transfer({\n  receiverSparkAddress: \"sp1p...\",\n  amountSats: 1000,\n});\nconsole.log(\"Transfer ID:\", transfer.id);\n```\n\nSpark-to-Spark transfers are instant and zero-fee.\n\n### List Transfers\n\n```javascript\nconst { transfers } = await wallet.getTransfers(10, 0);\nfor (const tx of transfers) {\n  console.log(`${tx.id}: ${tx.totalValue} sats — ${tx.status}`);\n}\n```\n\n## Lightning Interop\n\nSpark 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.\n\n### Create Lightning Invoice (Receive)\n\n```javascript\nconst invoiceRequest = await wallet.createLightningInvoice({\n  amountSats: 1000,\n  memo: \"Payment for AI service\",\n  expirySeconds: 3600,\n});\nconsole.log(\"BOLT11:\", invoiceRequest.invoice.encodedInvoice);\n```\n\nUse `includeSparkAddress: true` to embed a Spark address in the invoice. Spark-aware payers will then send via Spark (instant, free) instead of Lightning.\n\n### Pay Lightning Invoice (Send)\n\n```javascript\n// Estimate fee first\nconst fee = await wallet.getLightningSendFeeEstimate({\n  encodedInvoice: \"lnbc...\",\n  amountSats: 1000,\n});\nconsole.log(\"Estimated fee:\", fee, \"sats\");\n\n// Pay the invoice\nconst result = await wallet.payLightningInvoice({\n  invoice: \"lnbc...\",\n  maxFeeSats: 10,\n});\n```\n\nUse `preferSpark: true` to prefer Spark routing when the BOLT11 invoice contains an embedded Spark address.\n\n## Spark Native Invoices\n\nSpark has its own invoice format, distinct from BOLT11. Spark invoices can request payment in sats or tokens.\n\n### Create Sats Invoice\n\n```javascript\nconst invoice = await wallet.createSatsInvoice({\n  amount: 1000,\n  memo: \"Spark native payment\",\n});\n```\n\n### Create Token Invoice\n\n```javascript\nconst invoice = await wallet.createTokensInvoice({\n  amount: 100n,\n  tokenIdentifier: \"btkn1...\",\n  memo: \"Token payment request\",\n});\n```\n\n### Fulfill (Pay) a Spark Invoice\n\n```javascript\nconst result = await wallet.fulfillSparkInvoice([\n  { invoice: \"sp1...\", amount: 1000n },\n]);\n\n// Check results\nfor (const success of result.satsTransactionSuccess) {\n  console.log(\"Paid:\", success.invoice);\n}\nfor (const err of result.satsTransactionErrors) {\n  console.log(\"Failed:\", err.invoice, err.error.message);\n}\n```\n\n## Token Operations (BTKN / LRC20)\n\nSpark natively supports tokens via the BTKN (LRC20) standard. Tokens can represent stablecoins, points, or any fungible asset.\n\n### Check Token Balances\n\n```javascript\nconst { tokenBalances } = await wallet.getBalance();\nfor (const [id, info] of tokenBalances) {\n  const meta = info.tokenMetadata;\n  console.log(`${meta.tokenName} (${meta.tokenTicker}): ${info.balance.toString()}`);\n  console.log(`  Decimals: ${meta.decimals}, Max supply: ${meta.maxSupply.toString()}`);\n}\n```\n\n### Transfer Tokens\n\n```javascript\nconst txId = await wallet.transferTokens({\n  tokenIdentifier: \"btkn1...\",\n  tokenAmount: 100n,\n  receiverSparkAddress: \"sp1p...\",\n});\nconsole.log(\"Token transfer:\", txId);\n```\n\n### Batch Transfer Tokens\n\n```javascript\nconst txIds = await wallet.batchTransferTokens([\n  { tokenIdentifier: \"btkn1...\", tokenAmount: 50n, receiverSparkAddress: \"sp1p...\" },\n  { tokenIdentifier: \"btkn1...\", tokenAmount: 50n, receiverSparkAddress: \"sp1p...\" },\n]);\n```\n\n## Withdrawal (Cooperative Exit to L1)\n\nMove funds from Spark back to a regular Bitcoin L1 address.\n\n### Get Fee Quote\n\n```javascript\nconst quote = await wallet.getWithdrawalFeeQuote({\n  amountSats: 50000,\n  withdrawalAddress: \"bc1q...\",\n});\nconsole.log(\"Fast fee:\", quote.l1BroadcastFeeFast?.originalValue, \"sats\");\nconsole.log(\"Medium fee:\", quote.l1BroadcastFeeMedium?.originalValue, \"sats\");\nconsole.log(\"Slow fee:\", quote.l1BroadcastFeeSlow?.originalValue, \"sats\");\n```\n\n### Execute Withdrawal\n\n```javascript\nconst result = await wallet.withdraw({\n  onchainAddress: \"bc1q...\",\n  exitSpeed: \"MEDIUM\",\n  amountSats: 50000,\n});\n```\n\nExit speeds:\n- **FAST** — Higher fee, faster L1 confirmation\n- **MEDIUM** — Balanced fee and speed\n- **SLOW** — Lower fee, slower confirmation\n\nNote: Unilateral exit (without operator cooperation) is also possible as a safety mechanism, but cooperative exit is the standard path.\n\n## Message Signing\n\nSpark wallets can sign and verify messages using their identity key. Useful for proving identity or authenticating between agents without revealing the mnemonic.\n\n### Sign a Message\n\n```javascript\nconst message = new TextEncoder().encode(\"I am agent-007\");\nconst signature = await wallet.signMessageWithIdentityKey(message);\n```\n\n### Verify a Signature\n\n```javascript\nconst isValid = await wallet.validateMessageWithIdentityKey(\n  new TextEncoder().encode(\"I am agent-007\"),\n  signature,\n  publicKey,\n);\nconsole.log(\"Valid:\", isValid);\n```\n\n## Event Listeners\n\nThe wallet emits events for real-time updates. Useful for agents that need to react to incoming payments.\n\n```javascript\n// Incoming transfer completed\nwallet.on(\"transfer:claimed\", (transferId, balance) => {\n  console.log(`Transfer ${transferId} received. Balance: ${balance}`);\n});\n\n// Deposit confirmed on L1\nwallet.on(\"deposit:confirmed\", (depositId, balance) => {\n  console.log(`Deposit ${depositId} confirmed. Balance: ${balance}`);\n});\n\n// Connection status\nwallet.on(\"stream:connected\", () => console.log(\"Connected to Spark\"));\nwallet.on(\"stream:disconnected\", (reason) => console.log(\"Disconnected:\", reason));\n```\n\n## Complete Agent Class\n\n```javascript\nimport { SparkWallet } from \"@buildonspark/spark-sdk\";\n\nexport class SparkAgent {\n  #wallet;\n\n  constructor(wallet) {\n    this.#wallet = wallet;\n  }\n\n  static async create(mnemonic, network = \"MAINNET\") {\n    const { wallet, mnemonic: generated } = await SparkWallet.initialize({\n      mnemonicOrSeed: mnemonic,\n      options: { network },\n    });\n    return { agent: new SparkAgent(wallet), mnemonic: generated };\n  }\n\n  async getIdentity() {\n    return {\n      address: await this.#wallet.getSparkAddress(),\n      publicKey: await this.#wallet.getIdentityPublicKey(),\n    };\n  }\n\n  async getBalance() {\n    const { balance, tokenBalances } = await this.#wallet.getBalance();\n    return { sats: balance, tokens: tokenBalances };\n  }\n\n  async getDepositAddress() {\n    return await this.#wallet.getStaticDepositAddress();\n  }\n\n  async transfer(recipientAddress, amountSats) {\n    return await this.#wallet.transfer({\n      receiverSparkAddress: recipientAddress,\n      amountSats,\n    });\n  }\n\n  async createLightningInvoice(amountSats, memo) {\n    const request = await this.#wallet.createLightningInvoice({\n      amountSats,\n      memo,\n      expirySeconds: 3600,\n      includeSparkAddress: true,\n    });\n    return request.invoice.encodedInvoice;\n  }\n\n  async payLightningInvoice(bolt11, maxFeeSats = 10) {\n    return await this.#wallet.payLightningInvoice({\n      invoice: bolt11,\n      maxFeeSats,\n      preferSpark: true,\n    });\n  }\n\n  async createSparkInvoice(amountSats, memo) {\n    return await this.#wallet.createSatsInvoice({\n      amount: amountSats,\n      memo,\n    });\n  }\n\n  async transferTokens(tokenIdentifier, amount, recipientAddress) {\n    return await this.#wallet.transferTokens({\n      tokenIdentifier,\n      tokenAmount: amount,\n      receiverSparkAddress: recipientAddress,\n    });\n  }\n\n  async withdraw(onchainAddress, amountSats, speed = \"MEDIUM\") {\n    return await this.#wallet.withdraw({\n      onchainAddress,\n      exitSpeed: speed,\n      amountSats,\n    });\n  }\n\n  async signMessage(text) {\n    const message = new TextEncoder().encode(text);\n    return await this.#wallet.signMessageWithIdentityKey(message);\n  }\n\n  async verifyMessage(text, signature, publicKey) {\n    const message = new TextEncoder().encode(text);\n    return await this.#wallet.validateMessageWithIdentityKey(\n      message,\n      signature,\n      publicKey,\n    );\n  }\n\n  // L402 Methods\n  async fetchL402(url, options = {}) {\n    const { decode } = await import(\"light-bolt11-decoder\");\n    const { method = \"GET\", headers = {}, body, maxFeeSats = 10 } = options;\n\n    // Make initial request\n    const initialResponse = await fetch(url, {\n      method,\n      headers: { \"Content-Type\": \"application/json\", ...headers },\n      body: body ? JSON.stringify(body) : undefined,\n    });\n\n    if (initialResponse.status !== 402) {\n      const ct = initialResponse.headers.get(\"content-type\") || \"\";\n      const data = ct.includes(\"json\") ? await initialResponse.json() : await initialResponse.text();\n      return { paid: false, data };\n    }\n\n    // Parse L402 challenge\n    const challenge = await initialResponse.json();\n    const invoice = challenge.invoice || challenge.payment_request || challenge.pr;\n    const macaroon = challenge.macaroon || challenge.token;\n    if (!invoice || !macaroon) throw new Error(\"Invalid L402 challenge\");\n\n    // Decode and pay\n    const decoded = decode(invoice);\n    const amountSection = decoded.sections.find((s) => s.name === \"amount\");\n    const amountSats = Math.ceil(Number(amountSection.value) / 1000);\n\n    const payResult = await this.#wallet.payLightningInvoice({ invoice, maxFeeSats });\n    let preimage = payResult.paymentPreimage;\n\n    // Poll if needed\n    if (!preimage && payResult.id) {\n      for (let i = 0; i < 15; i++) {\n        await new Promise((r) => setTimeout(r, 500));\n        const status = await this.#wallet.getLightningSendRequest(payResult.id);\n        if (status?.paymentPreimage) { preimage = status.paymentPreimage; break; }\n        if (status?.status === \"LIGHTNING_PAYMENT_FAILED\") throw new Error(\"Payment failed\");\n      }\n    }\n    if (!preimage) throw new Error(\"No preimage received\");\n\n    // Retry with auth\n    const finalResponse = await fetch(url, {\n      method,\n      headers: { \"Authorization\": `L402 ${macaroon}:${preimage}`, ...headers },\n      body: body ? JSON.stringify(body) : undefined,\n    });\n\n    const ct = finalResponse.headers.get(\"content-type\") || \"\";\n    const data = ct.includes(\"json\") ? await finalResponse.json() : await finalResponse.text();\n    return { paid: true, amountSats, preimage, data };\n  }\n\n  async previewL402(url) {\n    const response = await fetch(url);\n    if (response.status !== 402) return { requiresPayment: false };\n\n    const { decode } = await import(\"light-bolt11-decoder\");\n    const challenge = await response.json();\n    const invoice = challenge.invoice || challenge.payment_request;\n    const decoded = decode(invoice);\n    const amountSection = decoded.sections.find((s) => s.name === \"amount\");\n\n    return {\n      requiresPayment: true,\n      amountSats: Math.ceil(Number(amountSection.value) / 1000),\n      invoice,\n      macaroon: challenge.macaroon,\n    };\n  }\n\n  onTransferReceived(callback) {\n    this.#wallet.on(\"transfer:claimed\", callback);\n  }\n\n  onDepositConfirmed(callback) {\n    this.#wallet.on(\"deposit:confirmed\", callback);\n  }\n\n  cleanup() {\n    this.#wallet.cleanupConnections();\n  }\n}\n\n// Usage\nconst { agent } = await SparkAgent.create(process.env.SPARK_MNEMONIC);\nconst identity = await agent.getIdentity();\nconsole.log(\"Address:\", identity.address);\n\nconst { sats } = await agent.getBalance();\nconsole.log(\"Balance:\", sats.toString(), \"sats\");\n\nagent.cleanup();\n```\n\n## Error Handling\n\n```javascript\ntry {\n  await wallet.transfer({\n    receiverSparkAddress: \"sp1p...\",\n    amountSats: 1000,\n  });\n} catch (error) {\n  switch (error.constructor.name) {\n    case \"ValidationError\":\n      console.log(\"Invalid input:\", error.message);\n      break;\n    case \"NetworkError\":\n      console.log(\"Network issue:\", error.message);\n      break;\n    case \"AuthenticationError\":\n      console.log(\"Auth failed:\", error.message);\n      break;\n    case \"ConfigurationError\":\n      console.log(\"Config problem:\", error.message);\n      break;\n    case \"RPCError\":\n      console.log(\"RPC error:\", error.message);\n      break;\n    default:\n      console.log(\"Error:\", error.message);\n  }\n}\n```\n\nError types:\n- **ValidationError** — Invalid parameters, malformed addresses\n- **NetworkError** — Connection failures, timeouts\n- **AuthenticationError** — Key/token issues\n- **ConfigurationError** — Missing config, initialization problems\n- **RPCError** — gRPC communication failures\n\n## Security Best Practices\n\n### The Agent Has Full Wallet Access\n\nAny 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.\n\nThis means:\n- If the mnemonic leaks, all funds are at risk immediately\n- If an agent is compromised, the attacker has the same full access\n- There is no way to revoke access without sweeping funds to a new wallet\n\n### Protect the Mnemonic\n\n1. **Back up the seed phrase offline** — write it down on paper or use a hardware backup. If you lose the mnemonic, the funds are gone permanently.\n2. **Never expose the mnemonic** in code, logs, git history, or error messages\n3. **Use environment variables** — never hardcode the mnemonic in source files\n4. **Add `.env` to `.gitignore`** — prevent accidental commits of secrets\n\n### Sweep Funds to a Safer Wallet\n\n**Do 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.\n\n- Regularly sweep earned funds to a more secure wallet (hardware wallet, cold storage, or a separate wallet you control directly)\n- Only keep the minimum operational balance the agent needs on Spark\n- Use `wallet.transfer()` or `wallet.withdraw()` to move funds out periodically\n- Consider automating sweeps when the balance exceeds a threshold\n\n### Operational Security\n\n1. **Use separate mnemonics** for different agents — never share a mnemonic across agents\n2. **Use separate `accountNumber` values** if you need multiple wallets from one mnemonic\n3. **Monitor transfers** via event listeners for unexpected outgoing activity\n4. **Call `cleanupConnections()`** when the wallet is no longer needed\n5. **Use REGTEST** for development and testing, MAINNET only for production\n6. **Implement application-level spending controls** — cap per-transaction and daily amounts in your agent logic since the SDK won't do it for you\n\n## L402 Protocol (Lightning Paywalls)\n\nL402 (formerly LSAT) is a protocol for monetizing APIs and content using Lightning payments. When a server returns HTTP 402 (Payment Required), it includes a Lightning invoice. Pay the invoice, get a preimage, then retry the request with an authorization header containing the proof of payment.\n\n### How L402 Works\n\n1. **Request** → Client fetches protected URL\n2. **402 Response** → Server returns `{invoice, macaroon}`\n3. **Pay Invoice** → Client pays Lightning invoice, receives preimage\n4. **Retry with Auth** → Client retries with `Authorization: L402 <macaroon>:<preimage>`\n5. **200 Response** → Server returns protected content\n\n### L402 Implementation\n\n```javascript\nimport { decode } from \"light-bolt11-decoder\";\n\nasync function fetchWithL402(wallet, url, options = {}) {\n  const { method = \"GET\", headers = {}, body, maxFeeSats = 10 } = options;\n\n  // Step 1: Make initial request\n  const initialResponse = await fetch(url, {\n    method,\n    headers: { \"Content-Type\": \"application/json\", ...headers },\n    body: body ? JSON.stringify(body) : undefined,\n  });\n\n  // If not 402, return response directly\n  if (initialResponse.status !== 402) {\n    const contentType = initialResponse.headers.get(\"content-type\") || \"\";\n    if (contentType.includes(\"application/json\")) {\n      return { paid: false, data: await initialResponse.json() };\n    }\n    return { paid: false, data: await initialResponse.text() };\n  }\n\n  // Step 2: Parse 402 challenge\n  const challenge = await initialResponse.json();\n  const invoice = challenge.invoice || challenge.payment_request || challenge.pr;\n  const macaroon = challenge.macaroon || challenge.token;\n\n  if (!invoice || !macaroon) {\n    throw new Error(\"Invalid L402 response: missing invoice or macaroon\");\n  }\n\n  // Step 3: Decode invoice to get amount\n  const decoded = decode(invoice);\n  const amountSection = decoded.sections.find((s) => s.name === \"amount\");\n  if (!amountSection?.value) {\n    throw new Error(\"L402 invoice has no amount\");\n  }\n  const amountSats = Math.ceil(Number(amountSection.value) / 1000);\n\n  // Step 4: Pay the invoice\n  const payResult = await wallet.payLightningInvoice({\n    invoice,\n    maxFeeSats,\n  });\n\n  // Get preimage (may need to poll if payment is async)\n  let preimage = payResult.paymentPreimage;\n  if (!preimage && payResult.status === \"LIGHTNING_PAYMENT_INITIATED\") {\n    // Poll for completion\n    for (let i = 0; i < 15; i++) {\n      await new Promise((r) => setTimeout(r, 500));\n      const status = await wallet.getLightningSendRequest(payResult.id);\n      if (status?.paymentPreimage) {\n        preimage = status.paymentPreimage;\n        break;\n      }\n      if (status?.status === \"LIGHTNING_PAYMENT_FAILED\") {\n        throw new Error(\"L402 payment failed\");\n      }\n    }\n  }\n\n  if (!preimage) {\n    throw new Error(\"L402 payment succeeded but no preimage available\");\n  }\n\n  // Step 5: Retry with L402 authorization\n  const finalResponse = await fetch(url, {\n    method,\n    headers: {\n      \"Content-Type\": \"application/json\",\n      \"Authorization\": `L402 ${macaroon}:${preimage}`,\n      ...headers,\n    },\n    body: body ? JSON.stringify(body) : undefined,\n  });\n\n  const contentType = finalResponse.headers.get(\"content-type\") || \"\";\n  let data;\n  if (contentType.includes(\"application/json\")) {\n    data = await finalResponse.json();\n  } else {\n    data = await finalResponse.text();\n  }\n\n  return {\n    paid: true,\n    amountSats,\n    preimage,\n    data,\n  };\n}\n```\n\n### Preview L402 Cost (Without Paying)\n\n```javascript\nasync function previewL402(url) {\n  const response = await fetch(url);\n\n  if (response.status !== 402) {\n    return { requiresPayment: false };\n  }\n\n  const challenge = await response.json();\n  const invoice = challenge.invoice || challenge.payment_request;\n\n  const decoded = decode(invoice);\n  const amountSection = decoded.sections.find((s) => s.name === \"amount\");\n  const amountSats = Math.ceil(Number(amountSection.value) / 1000);\n\n  return {\n    requiresPayment: true,\n    amountSats,\n    invoice,\n    macaroon: challenge.macaroon,\n  };\n}\n```\n\n### Add to SparkAgent Class\n\n```javascript\n// Add to the SparkAgent class\nasync fetchL402(url, options = {}) {\n  return await fetchWithL402(this.#wallet, url, options);\n}\n\nasync previewL402(url) {\n  return await previewL402(url);\n}\n```\n\n### Usage Example\n\n```javascript\nconst { agent } = await SparkAgent.create(process.env.SPARK_MNEMONIC);\n\n// Check cost first\nconst preview = await agent.previewL402(\"https://api.example.com/paid-endpoint\");\nconsole.log(\"Cost:\", preview.amountSats, \"sats\");\n\n// Pay and fetch\nconst result = await agent.fetchL402(\"https://api.example.com/paid-endpoint\", {\n  maxFeeSats: 10,\n});\nconsole.log(\"Paid:\", result.paid, \"Data:\", result.data);\n\nagent.cleanup();\n```\n\n### Required Dependency\n\n```bash\nnpm install light-bolt11-decoder\n```\n\n### L402 Providers\n\n| Provider | Description | URL |\n|----------|-------------|-----|\n| Lightning Faucet | Test L402 endpoint (21 sat jokes) | https://lightningfaucet.com/api/l402/joke |\n| Sulu | AI image generation | https://rnd.ln.sulu.sh (may require API key) |\n| Various APIs | Growing ecosystem | https://github.com/lnurl/awesome-lnurl#l402 |\n\n### Token Caching\n\nL402 tokens (macaroon + preimage) can often be reused for multiple requests to the same domain. Cache tokens by domain and try the cached token first:\n\n```javascript\nconst tokenCache = new Map();\n\nasync function fetchWithL402Cached(wallet, url, options = {}) {\n  const domain = new URL(url).host;\n  const cached = tokenCache.get(domain);\n\n  if (cached) {\n    // Try cached token first\n    const response = await fetch(url, {\n      method: options.method || \"GET\",\n      headers: {\n        \"Authorization\": `L402 ${cached.macaroon}:${cached.preimage}`,\n        ...options.headers,\n      },\n    });\n\n    if (response.status !== 402 && response.status !== 401) {\n      return { paid: false, cached: true, data: await response.json() };\n    }\n    // Token expired, delete and pay again\n    tokenCache.delete(domain);\n  }\n\n  // Pay for new token\n  const result = await fetchWithL402(wallet, url, options);\n\n  // Cache the token\n  if (result.paid) {\n    tokenCache.set(domain, {\n      macaroon: result.macaroon,\n      preimage: result.preimage,\n    });\n  }\n\n  return result;\n}\n```\n\n## Resources\n\n- Spark Docs: https://docs.spark.money\n- Spark SDK (npm): https://www.npmjs.com/package/@buildonspark/spark-sdk\n- Sparkscan Explorer: https://sparkscan.io\n- Spark CLI: https://docs.spark.money/tools/cli\n- L402 Spec: https://docs.lightning.engineering/the-lightning-network/l402\n\nFile v0.3.2:_meta.json\n\n{\n  \"ownerId\": \"kn71ga2vavytxp332ntav7k50x80epfh\",\n  \"slug\": \"sparkbtcbot\",\n  \"version\": \"0.3.2\",\n  \"publishedAt\": 1771704701420\n}\n\nFile v0.3.2:BUGFIX-tokenBalances-map.md\n\n# Bug: `tokenBalances` is a Map, not a plain Object — silent data loss\n\n## Problem\n\n`wallet.getBalance()` returns `{ balance, tokenBalances }` where `tokenBalances` is a JavaScript **`Map`**, not a plain object. This is undocumented in the Spark SDK and the SKILL.md doesn't call it out explicitly.\n\nThis matters because when an AI agent (the primary consumer of this skill) works with the balance data, it will naturally reach for standard JS patterns that **silently fail on Maps**:\n\n```javascript\n// All of these return {} or [] — SILENT DATA LOSS\nJSON.stringify(tokenBalances)           // \"{}\"\nObject.entries(tokenBalances)           // []\nObject.keys(tokenBalances)             // []\nObject.keys(tokenBalances).length === 0 // true (even when tokens exist!)\n```\n\nNone of these throw an error. They just return empty results, making it look like the wallet holds no tokens when it actually does.\n\n## How we discovered it\n\n1. User sent BEPSI tokens to the wallet's Spark address\n2. Agent checked balance using `wallet.getBalance()`\n3. Agent serialized `tokenBalances` with `JSON.stringify()` → showed `{}`\n4. Agent reported \"No tokens found\" — **incorrect**\n5. Running the `token-operations.js` example script (which uses `tokenBalances.size` and `for...of`) correctly showed 5,000,000 BEPSI\n\nThe wallet had tokens the entire time. The agent just couldn't see them because of the Map vs Object mismatch.\n\n## Why this is especially bad for AI agents\n\nAI agents (the target users of this skill) generate code on the fly. They default to the most common JS patterns — `JSON.stringify`, `Object.entries`, `Object.keys`. All of these work on plain objects but silently produce empty results on Maps. There's no error, no warning, no indication that data was lost.\n\nAn agent could:\n- Report zero token balance when the user actually holds tokens\n- Skip token operations because it thinks the wallet is empty\n- Fail to trigger token-based logic (sweeps, transfers, alerts)\n\n## Fix\n\n### 1. Add explicit Map documentation to SKILL.md\n\nIn the Quick Example and the `getBalance()` sections, add a clear warning:\n\n```\n⚠️ `tokenBalances` is a JavaScript `Map`, not a plain object.\nUse `.size`, `for...of`, or `Map` methods. Do NOT use JSON.stringify(),\nObject.entries(), or Object.keys() — they silently return empty results.\n```\n\n### 2. Add a safe serialization helper to the Quick Example\n\n```javascript\n// tokenBalances is a Map — convert to object for JSON/logging\nconst tokensObj = Object.fromEntries(\n  [...tokenBalances].map(([id, info]) => [\n    info.tokenMetadata.tokenTicker,\n    { balance: info.balance.toString(), decimals: info.tokenMetadata.decimals }\n  ])\n);\nconsole.log(\"Tokens:\", JSON.stringify(tokensObj));\n```\n\n### 3. Update the Quick Example to include token checking\n\nThe current Quick Example only shows BTC balance. It should show tokens too, since that's a primary use case.\n\n## Files changed\n\n- `SKILL.md` — Added Map warning, updated Quick Example with token balance check and safe serialization\n\nFile v0.3.2:package-lock.json\n\n{\n  \"name\": \"sparkbtcbot-skill\",\n  \"version\": \"0.1.0\",\n  \"lockfileVersion\": 3,\n  \"requires\": true,\n  \"packages\": {\n    \"\": {\n      \"name\": \"sparkbtcbot-skill\",\n      \"version\": \"0.1.0\",\n      \"dependencies\": {\n        \"@buildonspark/spark-sdk\": \"^0.5.8\",\n        \"dotenv\": \"^16.4.7\"\n      }\n    },\n    \"node_modules/@bufbuild/protobuf\": {\n      \"version\": \"2.11.0\",\n      \"resolved\": \"https://registry.npmjs.org/@bufbuild/protobuf/-/protobuf-2.11.0.tgz\",\n      \"integrity\": \"sha512-sBXGT13cpmPR5BMgHE6UEEfEaShh5Ror6rfN3yEK5si7QVrtZg8LEPQb0VVhiLRUslD2yLnXtnRzG035J/mZXQ==\",\n      \"license\": \"(Apache-2.0 AND BSD-3-Clause)\"\n    },\n    \"node_modules/@buildonspark/spark-sdk\": {\n      \"version\": \"0.5.8\",\n      \"resolved\": \"https://registry.npmjs.org/@buildonspark/spark-sdk/-/spark-sdk-0.5.8.tgz\",\n      \"integrity\": \"sha512-e3F3UReerB68B6x4zykQ5MLzWeyNqRuU8rmgCfxDqnKCfaFUg0wTiZw6GdJrthO8kDX7A7THhwh4UsBiHzNtAg==\",\n      \"license\": \"Apache-2.0\",\n      \"dependencies\": {\n        \"@bufbuild/protobuf\": \"^2.2.5\",\n        \"@lightsparkdev/core\": \"^1.4.4\",\n        \"@noble/curves\": \"^1.8.0\",\n        \"@noble/hashes\": \"^1.7.0\",\n        \"@opentelemetry/api\": \"^1.9.0\",\n        \"@opentelemetry/context-async-hooks\": \"^2.0.0\",\n        \"@opentelemetry/core\": \"^2.0.0\",\n        \"@opentelemetry/instrumentation\": \"^0.203.0\",\n        \"@opentelemetry/instrumentation-undici\": \"^0.14.0\",\n        \"@opentelemetry/sdk-trace-base\": \"^2.0.0\",\n        \"@opentelemetry/sdk-trace-node\": \"^2.0.1\",\n        \"@opentelemetry/sdk-trace-web\": \"^2.0.1\",\n        \"@scure/base\": \"^1.2.4\",\n        \"@scure/bip32\": \"^1.6.2\",\n        \"@scure/bip39\": \"^1.5.4\",\n        \"@scure/btc-signer\": \"^1.5.0\",\n        \"abort-controller-x\": \"^0.4.3\",\n        \"abortcontroller-polyfill\": \"^1.7.8\",\n        \"async-mutex\": \"^0.5.0\",\n        \"bare-crypto\": \"^1.9.2\",\n        \"bare-fetch\": \"^2.4.1\",\n        \"buffer\": \"^6.0.3\",\n        \"eventemitter3\": \"^5.0.1\",\n        \"js-base64\": \"^3.7.7\",\n        \"light-bolt11-decoder\": \"^3.2.0\",\n        \"nice-grpc\": \"^2.1.10\",\n        \"nice-grpc-client-middleware-retry\": \"^3.1.10\",\n        \"nice-grpc-common\": \"^2.0.2\",\n        \"nice-grpc-opentelemetry\": \"^0.1.18\",\n        \"nice-grpc-web\": \"^3.3.7\",\n        \"ts-proto\": \"2.8.3\",\n        \"ua-parser-js\": \"^2.0.6\",\n        \"uuidv7\": \"^1.0.2\"\n      },\n      \"engines\": {\n        \"node\": \">=18.0.0\"\n      },\n      \"peerDependencies\": {\n        \"react\": \">=18.2.0\",\n        \"react-native\": \">=0.71.0\",\n        \"react-native-get-random-values\": \">=1.11.0\"\n      },\n      \"peerDependenciesMeta\": {\n        \"react\": {\n          \"optional\": true\n        },\n        \"react-native\": {\n          \"optional\": true\n        },\n        \"react-native-get-random-values\": {\n          \"optional\": true\n        }\n      }\n    },\n    \"node_modules/@grpc/grpc-js\": {\n      \"version\": \"1.14.3\",\n      \"resolved\": \"https://registry.npmjs.org/@grpc/grpc-js/-/grpc-js-1.14.3.tgz\",\n      \"integrity\": \"sha512-Iq8QQQ/7X3Sac15oB6p0FmUg/klxQvXLeileoqrTRGJYLV+/9tubbr9ipz0GKHjmXVsgFPo/+W+2cA8eNcR+XA==\",\n      \"license\": \"Apache-2.0\",\n      \"dependencies\": {\n        \"@grpc/proto-loader\": \"^0.8.0\",\n        \"@js-sdsl/ordered-map\": \"^4.4.2\"\n      },\n      \"engines\": {\n        \"node\": \">=12.10.0\"\n      }\n    },\n    \"node_modules/@grpc/proto-loader\": {\n      \"version\": \"0.8.0\",\n      \"resolved\": \"https://registry.npmjs.org/@grpc/proto-loader/-/proto-loader-0.8.0.tgz\",\n      \"integrity\": \"sha512-rc1hOQtjIWGxcxpb9aHAfLpIctjEnsDehj0DAiVfBlmT84uvR0uUtN2hEi/ecvWVjXUGf5qPF4qEgiLOx1YIMQ==\",\n      \"license\": \"Apache-2.0\",\n      \"dependencies\": {\n        \"lodash.camelcase\": \"^4.3.0\",\n        \"long\": \"^5.0.0\",\n        \"protobufjs\": \"^7.5.3\",\n        \"yargs\": \"^17.7.2\"\n      },\n      \"bin\": {\n        \"proto-loader-gen-types\": \"build/bin/proto-loader-gen-types.js\"\n      },\n      \"engines\": {\n        \"node\": \">=6\"\n      }\n    },\n    \"node_modules/@js-sdsl/ordered-map\": {\n      \"version\": \"4.4.2\",\n      \"resolved\": \"https://registry.npmjs.org/@js-sdsl/ordered-map/-/ordered-map-4.4.2.tgz\",\n      \"integrity\": \"sha512-iUKgm52T8HOE/makSxjqoWhe95ZJA1/G1sYsGev2JDKUSS14KAgg1LHb+Ba+IPow0xflbnSkOsZcO08C7w1gYw==\",\n      \"license\": \"MIT\",\n      \"funding\": {\n        \"type\": \"opencollective\",\n        \"url\": \"https://opencollective.com/js-sdsl\"\n      }\n    },\n    \"node_modules/@lightsparkdev/core\": {\n      \"version\": \"1.4.8\",\n      \"resolved\": \"https://registry.npmjs.org/@lightsparkdev/core/-/core-1.4.8.tgz\",\n      \"integrity\": \"sha512-ZnDJPQS6nrjqaDb++PAfI3KV/cWgaca76zTrQNPZOKF8Uhya8v6O0fIWmjlZR8GmBXx825PI4mwbcycZdfq3bA==\",\n      \"license\": \"Apache-2.0\",\n      \"dependencies\": {\n        \"dayjs\": \"^1.11.7\",\n        \"graphql\": \"^16.6.0\",\n        \"graphql-ws\": \"^5.11.3\",\n        \"secp256k1\": \"^5.0.1\",\n        \"ws\": \"^8.12.1\",\n        \"zen-observable-ts\": \"^1.1.0\"\n      },\n      \"engines\": {\n        \"node\": \">=18\"\n      }\n    },\n    \"node_modules/@noble/curves\": {\n      \"version\": \"1.9.7\",\n      \"resolved\": \"https://registry.npmjs.org/@noble/curves/-/curves-1.9.7.tgz\",\n      \"integrity\": \"sha512-gbKGcRUYIjA3/zCCNaWDciTMFI0dCkvou3TL8Zmy5Nc7sJ47a0jtOeZoTaMxkuqRo9cRhjOdZJXegxYE5FN/xw==\",\n      \"license\": \"MIT\",\n      \"dependencies\": {\n        \"@noble/hashes\": \"1.8.0\"\n      },\n      \"engines\": {\n        \"node\": \"^14.21.3 || >=16\"\n      },\n      \"funding\": {\n        \"url\": \"https://paulmillr.com/funding/\"\n      }\n    },\n    \"node_modules/@noble/hashes\": {\n      \"version\": \"1.8.0\",\n      \"resolved\": \"https://registry.npmjs.org/@noble/hashes/-/hashes-1.8.0.tgz\",\n      \"integrity\": \"sha512-jCs9ldd7NwzpgXDIf6P3+NrHh9/sD6CQdxHyjQI+h/6rDNo88ypBxxz45UDuZHz9r3tNz7N/VInSVoVdtXEI4A==\",\n      \"license\": \"MIT\",\n      \"engines\": {\n        \"node\": \"^14.21.3 || >=16\"\n      },\n      \"funding\": {\n        \"url\": \"https://paulmillr.com/funding/\"\n      }\n    },\n    \"node_modules/@opentelemetry/api\": {\n      \"version\": \"1.9.0\",\n      \"resolved\": \"https://registry.npmjs.org/@opentelemetry/api/-/api-1.9.0.tgz\",\n      \"integrity\": \"sha512-3giAOQvZiH5F9bMlMiv8+GSPMeqg0dbaeo58/0SlA9sxSqZhnUtxzX9/2FzyhS9sWQf5S0GJE0AKBrFqjpeYcg==\",\n      \"license\": \"Apache-2.0\",\n      \"engines\": {\n        \"node\": \">=8.0.0\"\n      }\n    },\n    \"node_modules/@opentelemetry/api-logs\": {\n      \"version\": \"0.203.0\",\n      \"resolved\": \"https://registry.npmjs.org/@opentelemetry/api-logs/-/api-logs-0.203.0.tgz\",\n      \"integrity\": \"sha512-9B9RU0H7Ya1Dx/Rkyc4stuBZSGVQF27WigitInx2QQoj6KUpEFYPKoWjdFTunJYxmXmh17HeBvbMa1EhGyPmqQ==\",\n      \"license\": \"Apache-2.0\",\n      \"dependencies\": {\n        \"@opentelemetry/api\": \"^1.3.0\"\n      },\n      \"engines\": {\n        \"node\": \">=8.0.0\"\n      }\n    },\n    \"node_modules/@opentelemetry/context-async-hooks\": {\n      \"version\": \"2.5.0\",\n      \"resolved\": \"https://registry.npmjs.org/@opentelemetry/context-async-hooks/-/context-async-hooks-2.5.0.tgz\",\n      \"integrity\": \"sha512-uOXpVX0ZjO7heSVjhheW2XEPrhQAWr2BScDPoZ9UDycl5iuHG+Usyc3AIfG6kZeC1GyLpMInpQ6X5+9n69yOFw==\",\n      \"license\": \"Apache-2.0\",\n      \"engines\": {\n        \"node\": \"^18.19.0 || >=20.6.0\"\n      },\n      \"peerDependencies\": {\n        \"@opentelemetry/api\": \">=1.0.0 <1.10.0\"\n      }\n    },\n    \"node_modules/@opentelemetry/core\": {\n      \"version\": \"2.5.0\",\n      \"resolved\": \"https://registry.npmjs.org/@opentelemetry/core/-/core-2.5.0.tgz\",\n      \"integrity\": \"sha512-ka4H8OM6+DlUhSAZpONu0cPBtPPTQKxbxVzC4CzVx5+K4JnroJVBtDzLAMx4/3CDTJXRvVFhpFjtl4SaiTNoyQ==\",\n      \"license\": \"Apache-2.0\",\n      \"dependencies\": {\n        \"@opentelemetry/semantic-conventions\": \"^1.29.0\"\n      },\n      \"engines\": {\n        \"node\": \"^18.19.0 || >=20.6.0\"\n      },\n      \"peerDependencies\": {\n        \"@opentelemetry/api\": \">=1.0.0 <1.10.0\"\n      }\n    },\n    \"node_modules/@opentelemetry/instrumentation\": {\n      \"version\": \"0.203.0\",\n      \"resolved\": \"https://registry.npmjs.org/@opentelemetry/instrumentation/-/instrumentation-0.203.0.tgz\",\n      \"integrity\": \"sha512-ke1qyM+3AK2zPuBPb6Hk/GCsc5ewbLvPNkEuELx/JmANeEp6ZjnZ+wypPAJSucTw0wvCGrUaibDSdcrGFoWxKQ==\",\n      \"license\": \"Apache-2.0\",\n      \"dependencies\": {\n        \"@opentelemetry/api-logs\": \"0.203.0\",\n        \"import-in-the-middle\": \"^1.8.1\",\n        \"require-in-the-middle\": \"^7.1.1\"\n      },\n      \"engines\": {\n        \"node\": \"^18.19.0 || >=20.6.0\"\n      },\n      \"peerDependencies\": {\n        \"@opentelemetry/api\": \"^1.3.0\"\n      }\n    },\n    \"node_modules/@opentelemetry/instrumentation-undici\": {\n      \"version\": \"0.14.0\",\n      \"resolved\": \"https://registry.npmjs.org/@opentelemetry/instrumentation-undici/-/instrumentation-undici-0.14.0.tgz\",\n      \"integrity\": \"sha512-2HN+7ztxAReXuxzrtA3WboAKlfP5OsPA57KQn2AdYZbJ3zeRPcLXyW4uO/jpLE6PLm0QRtmeGCmfYpqRlwgSwg==\",\n      \"license\": \"Apache-2.0\",\n      \"dependencies\": {\n        \"@opentelemetry/core\": \"^2.0.0\",\n        \"@opentelemetry/instrumentation\": \"^0.203.0\"\n      },\n      \"engines\": {\n        \"node\": \"^18.19.0 || >=20.6.0\"\n      },\n      \"peerDependencies\": {\n        \"@opentelemetry/api\": \"^1.7.0\"\n      }\n    },\n    \"node_modules/@opentelemetry/resources\": {\n      \"version\": \"2.5.0\",\n      \"resolved\": \"https://registry.npmjs.org/@opentelemetry/resources/-/resources-2.5.0.tgz\",\n      \"integrity\": \"sha512-F8W52ApePshpoSrfsSk1H2yJn9aKjCrbpQF1M9Qii0GHzbfVeFUB+rc3X4aggyZD8x9Gu3Slua+s6krmq6Dt8g==\",\n      \"license\": \"Apache-2.0\",\n      \"dependencies\": {\n        \"@opentelemetry/core\": \"2.5.0\",\n        \"@opentelemetry/semantic-conventions\": \"^1.29.0\"\n      },\n      \"engines\": {\n        \"node\": \"^18.19.0 || >=20.6.0\"\n      },\n      \"peerDependencies\": {\n        \"@opentelemetry/api\": \">=1.3.0 <1.10.0\"\n      }\n    },\n    \"node_modules/@opentelemetry/sdk-trace-base\": {\n      \"version\": \"2.5.0\",\n      \"resolved\": \"https://registry.npmjs.org/@opentelemetry/sdk-trace-base/-/sdk-trace-base-2.5.0.tgz\",\n      \"integrity\": \"sha512-VzRf8LzotASEyNDUxTdaJ9IRJ1/h692WyArDBInf5puLCjxbICD6XkHgpuudis56EndyS7LYFmtTMny6UABNdQ==\",\n      \"license\": \"Apache-2.0\",\n      \"dependencies\": {\n        \"@opentelemetry/core\": \"2.5.0\",\n        \"@opentelemetry/resources\": \"2.5.0\",\n        \"@opentelemetry/semantic-conventions\": \"^1.29.0\"\n      },\n      \"engines\": {\n        \"node\": \"^18.19.0 || >=20.6.0\"\n      },\n      \"peerDependencies\": {\n        \"@opentelemetry/api\": \">=1.3.0 <1.10.0\"\n      }\n    },\n    \"node_modules/@opentelemetry/sdk-trace-node\": {\n      \"version\": \"2.5.0\",\n      \"resolved\": \"https://registry.npmjs.org/@opentelemetry/sdk-trace-node/-/sdk-trace-node-2.5.0.tgz\",\n      \"integrity\": \"sha512-O6N/ejzburFm2C84aKNrwJVPpt6HSTSq8T0ZUMq3xT2XmqT4cwxUItcL5UWGThYuq8RTcbH8u1sfj6dmRci0Ow==\",\n      \"license\": \"Apache-2.0\",\n      \"dependencies\": {\n        \"@opentelemetry/context-async-hooks\": \"2.5.0\",\n        \"@opentelemetry/core\": \"2.5.0\",\n        \"@opentelemetry/sdk-trace-base\": \"2.5.0\"\n      },\n      \"engines\": {\n        \"node\": \"^18.19.0 || >=20.6.0\"\n      },\n      \"peerDependencies\": {\n        \"@opentelemetry/api\": \">=1.0.0 <1.10.0\"\n      }\n    },\n    \"node_modules/@opentelemetry/sdk-trace-web\": {\n      \"version\": \"2.5.0\",\n      \"resolved\": \"https://registry.npmjs.org/@opentelemetry/sdk-trace-web/-/sdk-trace-web-2.5.0.tgz\",\n      \"integrity\": \"sha512-xWibakHs+xbx6vxH7Q8TbFS6zjf812o/kIS4xBDB32qSL9wF+Z5IZl2ZAGu4rtmPBQ7coZcOd684DobMhf8dKw==\",\n      \"license\": \"Apache-2.0\",\n      \"dependencies\": {\n        \"@opentelemetry/core\": \"2.5.0\",\n        \"@opentelemetry/sdk-trace-base\": \"2.5.0\"\n      },\n      \"engines\": {\n        \"node\": \"^18.19.0 || >=20.6.0\"\n      },\n      \"peerDependencies\": {\n        \"@opentelemetry/api\": \">=1.0.0 <1.10.0\"\n      }\n    },\n    \"node_modules/@opentelemetry/semantic-conventions\": {\n      \"version\": \"1.39.0\",\n      \"resolved\": \"https://registry.npmjs.org/@opentelemetry/semantic-conventions/-/semantic-conventions-1.39.0.tgz\",\n      \"integrity\": \"sha512-R5R9tb2AXs2IRLNKLBJDynhkfmx7mX0vi8NkhZb3gUkPWHn6HXk5J8iQ/dql0U3ApfWym4kXXmBDRGO+oeOfjg==\",\n      \"license\": \"Apache-2.0\",\n      \"engines\": {\n        \"node\": \">=14\"\n      }\n    },\n    \"node_modules/@protobufjs/aspromise\": {\n      \"version\": \"1.1.2\",\n      \"resolved\": \"https://registry.npmjs.org/@protobufjs/aspromise/-/aspromise-1.1.2.tgz\",\n      \"integrity\": \"sha512-j+gKExEuLmKwvz3OgROXtrJ2UG2x8Ch2YZUxahh+s1F2HZ+wAceUNLkvy6z\n\nArchive v0.3.1: 2 files, 12248 bytes\n\nFiles: SKILL.md (35602b), _meta.json (130b)\n\nArchive v0.3.0: 2 files, 12227 bytes\n\nFiles: SKILL.md (35548b), _meta.json (130b)\n\nArchive v0.2.0: 9 files, 31876 bytes\n\nFiles: examples/balance-and-deposits.js (1892b), examples/payment-flow.js (2603b), examples/spark-agent.js (5241b), examples/token-operations.js (2509b), examples/wallet-setup.js (1340b), package-lock.json (61259b), package.json (576b), SKILL.md (25720b), _meta.json (130b)\n\nArchive v0.1.1: 9 files, 31263 bytes\n\nFiles: examples/balance-and-deposits.js (1892b), examples/payment-flow.js (2603b), examples/spark-agent.js (5241b), examples/token-operations.js (2509b), examples/wallet-setup.js (1340b), package-lock.json (61259b), package.json (576b), SKILL.md (23707b), _meta.json (130b)\n\nArchive v0.1.0: 9 files, 31264 bytes\n\nFiles: examples/balance-and-deposits.js (1892b), examples/payment-flow.js (2603b), examples/spark-agent.js (5241b), examples/token-operations.js (2509b), examples/wallet-setup.js (1340b), package-lock.json (61259b), package.json (576b), SKILL.md (23707b), _meta.json (130b)","readmeExcerpt":"Skill: Sparkbtcbot Owner: echennells Summary: Set up Spark Bitcoin L2 wallet capabilities for AI agents. Initialize wallets from mnemonic, transfer sats and tokens, create/pay Lightning invoices, pay L40... Tags: latest:0.3.3 Version history: v0.3.3 | 2026-02-21T20:44:28.685Z | user Add homepage URL v0.3.2 | 2026-02-21T20:11:41.420Z | auto sparkbtcbot 0.3.2 - Added example scripts for common Spark wallet operations, ","codeSnippets":[],"executableExamples":[{"language":"bash","snippet":"npm install @buildonspark/spark-sdk@^0.5.8 dotenv"},{"language":"javascript","snippet":"import { SparkWallet } from \"@buildonspark/spark-sdk\";\n\n// Option A: Generate a new wallet (creates mnemonic automatically)\nconst { wallet, mnemonic } = await SparkWallet.initialize({\n  options: { network: \"MAINNET\" }\n});\n// Save mnemonic securely — NEVER log it in production\n\n// Option B: Import existing wallet from mnemonic\nconst { wallet } = await SparkWallet.initialize({\n  mnemonicOrSeed: process.env.SPARK_MNEMONIC,\n  options: { network: process.env.SPARK_NETWORK || \"MAINNET\" }\n});"},{"language":"text","snippet":"SPARK_MNEMONIC=word1 word2 word3 word4 word5 word6 word7 word8 word9 word10 word11 word12\nSPARK_NETWORK=MAINNET"},{"language":"javascript","snippet":"const address = await wallet.getSparkAddress();\nconst identityKey = await wallet.getIdentityPublicKey();\nconst { balance } = await wallet.getBalance();\n\nconsole.log(\"Spark Address:\", address);\nconsole.log(\"Identity Key:\", identityKey);\nconsole.log(\"Balance:\", balance.toString(), \"sats\");\n\n// Always clean up when done\nwallet.cleanupConnections();"},{"language":"javascript","snippet":"const { balance, tokenBalances } = await wallet.getBalance();\nconsole.log(\"BTC:\", balance.toString(), \"sats\");\n\nfor (const [id, token] of tokenBalances) {\n  console.log(`${token.tokenMetadata.tokenTicker}: ${token.balance.toString()}`);\n}"},{"language":"javascript","snippet":"// Static (reusable) — can receive multiple deposits\nconst staticAddr = await wallet.getStaticDepositAddress();\n\n// Single-use — one-time deposit address\nconst singleAddr = await wallet.getSingleUseDepositAddress();"}],"parameters":null,"dependencies":[],"permissions":[],"extractedFiles":[{"path":"SKILL.md","content":"---\nname: sparkbtcbot\ndescription: Set up Spark Bitcoin L2 wallet capabilities for AI agents. Initialize wallets from mnemonic, transfer sats and tokens, create/pay Lightning invoices, pay L402 paywalls, manage deposits and withdrawals. Use when user mentions \"Spark wallet,\" \"Spark Bitcoin,\" \"BTKN tokens,\" \"Spark L2,\" \"Spark SDK,\" \"Spark payment,\" \"Spark transfer,\" \"Spark invoice,\" \"L402,\" \"Lightning paywall,\" or wants Bitcoin L2 capabilities for an agent.\nargument-hint: \"[Optional: specify what to set up - wallet, payments, tokens, lightning, l402, or full]\"\nrequires:\n  env:\n    - name: SPARK_MNEMONIC\n      description: 12 or 24 word BIP39 mnemonic for the Spark wallet. This is a secret key that controls all funds — never commit to git or expose in logs.\n      sensitive: true\n    - name: SPARK_NETWORK\n      description: Network to connect to (MAINNET or REGTEST)\n      default: MAINNET\nmodel-invocation: autonomous\nmodel-invocation-reason: This skill enables agents to autonomously send and receive Bitcoin payments. Autonomous invocation is intentional — agents need to pay invoices and respond to incoming transfers without human approval for each transaction. Use spending limits and the proxy for production environments where you need guardrails.\nhomepage: https://sparkbot.yvrbtclabs.dev\n---\n\n# Spark Bitcoin L2 for AI Agents\n\nYou are an expert in setting up Spark Bitcoin L2 wallet capabilities for AI agents using the `@buildonspark/spark-sdk`.\n\nSpark 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.\n\n## For Production Use\n\n**This skill gives the agent full custody of the wallet.** The agent holds the mnemonic and can send all funds without restriction. This is appropriate for:\n- Development and testing (use REGTEST with no real funds)\n- Trusted agents you fully control\n- Small operational balances you're willing to lose\n\n**For production with real funds, use [sparkbtcbot-proxy](https://github.com/echennells/sparkbtcbot-proxy) instead.** The proxy keeps the mnemonic on your server and gives agents scoped access via bearer tokens:\n- **Spending limits** — per-transaction and daily caps\n- **Role-based access** — read-only, invoice-only, or full access\n- **Revocable tokens** — cut off a compromised agent without moving funds\n- **Audit logs** — track all wallet activity\n\nThe proxy wraps the same Spark SDK behind authenticated REST endpoints. Agents get HTTP access instead of direct SDK access.\n\n## Why Bitcoin for Agents\n\nAI agents that transact need a monetary network that matches their nature: programmable, borderless, and available 24/7 without gatekeepers. Bi"},{"path":"_meta.json","content":"{\n  \"ownerId\": \"kn71ga2vavytxp332ntav7k50x80epfh\",\n  \"slug\": \"sparkbtcbot\",\n  \"version\": \"0.3.3\",\n  \"publishedAt\": 1771706668685\n}"},{"path":"BUGFIX-tokenBalances-map.md","content":"# Bug: `tokenBalances` is a Map, not a plain Object — silent data loss\n\n## Problem\n\n`wallet.getBalance()` returns `{ balance, tokenBalances }` where `tokenBalances` is a JavaScript **`Map`**, not a plain object. This is undocumented in the Spark SDK and the SKILL.md doesn't call it out explicitly.\n\nThis matters because when an AI agent (the primary consumer of this skill) works with the balance data, it will naturally reach for standard JS patterns that **silently fail on Maps**:\n\n```javascript\n// All of these return {} or [] — SILENT DATA LOSS\nJSON.stringify(tokenBalances)           // \"{}\"\nObject.entries(tokenBalances)           // []\nObject.keys(tokenBalances)             // []\nObject.keys(tokenBalances).length === 0 // true (even when tokens exist!)\n```\n\nNone of these throw an error. They just return empty results, making it look like the wallet holds no tokens when it actually does.\n\n## How we discovered it\n\n1. User sent BEPSI tokens to the wallet's Spark address\n2. Agent checked balance using `wallet.getBalance()`\n3. Agent serialized `tokenBalances` with `JSON.stringify()` → showed `{}`\n4. Agent reported \"No tokens found\" — **incorrect**\n5. Running the `token-operations.js` example script (which uses `tokenBalances.size` and `for...of`) correctly showed 5,000,000 BEPSI\n\nThe wallet had tokens the entire time. The agent just couldn't see them because of the Map vs Object mismatch.\n\n## Why this is especially bad for AI agents\n\nAI agents (the target users of this skill) generate code on the fly. They default to the most common JS patterns — `JSON.stringify`, `Object.entries`, `Object.keys`. All of these work on plain objects but silently produce empty results on Maps. There's no error, no warning, no indication that data was lost.\n\nAn agent could:\n- Report zero token balance when the user actually holds tokens\n- Skip token operations because it thinks the wallet is empty\n- Fail to trigger token-based logic (sweeps, transfers, alerts)\n\n## Fix\n\n### 1. Add explicit Map documentation to SKILL.md\n\nIn the Quick Example and the `getBalance()` sections, add a clear warning:\n\n```\n⚠️ `tokenBalances` is a JavaScript `Map`, not a plain object.\nUse `.size`, `for...of`, or `Map` methods. Do NOT use JSON.stringify(),\nObject.entries(), or Object.keys() — they silently return empty results.\n```\n\n### 2. Add a safe serialization helper to the Quick Example\n\n```javascript\n// tokenBalances is a Map — convert to object for JSON/logging\nconst tokensObj = Object.fromEntries(\n  [...tokenBalances].map(([id, info]) => [\n    info.tokenMetadata.tokenTicker,\n    { balance: info.balance.toString(), decimals: info.tokenMetadata.decimals }\n  ])\n);\nconsole.log(\"Tokens:\", JSON.stringify(tokensObj));\n```\n\n### 3. Update the Quick Example to include token checking\n\nThe current Quick Example only shows BTC balance. It should show tokens too, since that's a primary use case.\n\n## Files changed\n\n- `SKILL.md` — Added Map warning, updated Quick Example with token balance check and safe s"},{"path":"package-lock.json","content":"{\n  \"name\": \"sparkbtcbot-skill\",\n  \"version\": \"0.1.0\",\n  \"lockfileVersion\": 3,\n  \"requires\": true,\n  \"packages\": {\n    \"\": {\n      \"name\": \"sparkbtcbot-skill\",\n      \"version\": \"0.1.0\",\n      \"dependencies\": {\n        \"@buildonspark/spark-sdk\": \"^0.5.8\",\n        \"dotenv\": \"^16.4.7\"\n      }\n    },\n    \"node_modules/@bufbuild/protobuf\": {\n      \"version\": \"2.11.0\",\n      \"resolved\": \"https://registry.npmjs.org/@bufbuild/protobuf/-/protobuf-2.11.0.tgz\",\n      \"integrity\": \"sha512-sBXGT13cpmPR5BMgHE6UEEfEaShh5Ror6rfN3yEK5si7QVrtZg8LEPQb0VVhiLRUslD2yLnXtnRzG035J/mZXQ==\",\n      \"license\": \"(Apache-2.0 AND BSD-3-Clause)\"\n    },\n    \"node_modules/@buildonspark/spark-sdk\": {\n      \"version\": \"0.5.8\",\n      \"resolved\": \"https://registry.npmjs.org/@buildonspark/spark-sdk/-/spark-sdk-0.5.8.tgz\",\n      \"integrity\": \"sha512-e3F3UReerB68B6x4zykQ5MLzWeyNqRuU8rmgCfxDqnKCfaFUg0wTiZw6GdJrthO8kDX7A7THhwh4UsBiHzNtAg==\",\n      \"license\": \"Apache-2.0\",\n      \"dependencies\": {\n        \"@bufbuild/protobuf\": \"^2.2.5\",\n        \"@lightsparkdev/core\": \"^1.4.4\",\n        \"@noble/curves\": \"^1.8.0\",\n        \"@noble/hashes\": \"^1.7.0\",\n        \"@opentelemetry/api\": \"^1.9.0\",\n        \"@opentelemetry/context-async-hooks\": \"^2.0.0\",\n        \"@opentelemetry/core\": \"^2.0.0\",\n        \"@opentelemetry/instrumentation\": \"^0.203.0\",\n        \"@opentelemetry/instrumentation-undici\": \"^0.14.0\",\n        \"@opentelemetry/sdk-trace-base\": \"^2.0.0\",\n        \"@opentelemetry/sdk-trace-node\": \"^2.0.1\",\n        \"@opentelemetry/sdk-trace-web\": \"^2.0.1\",\n        \"@scure/base\": \"^1.2.4\",\n        \"@scure/bip32\": \"^1.6.2\",\n        \"@scure/bip39\": \"^1.5.4\",\n        \"@scure/btc-signer\": \"^1.5.0\",\n        \"abort-controller-x\": \"^0.4.3\",\n        \"abortcontroller-polyfill\": \"^1.7.8\",\n        \"async-mutex\": \"^0.5.0\",\n        \"bare-crypto\": \"^1.9.2\",\n        \"bare-fetch\": \"^2.4.1\",\n        \"buffer\": \"^6.0.3\",\n        \"eventemitter3\": \"^5.0.1\",\n        \"js-base64\": \"^3.7.7\",\n        \"light-bolt11-decoder\": \"^3.2.0\",\n        \"nice-grpc\": \"^2.1.10\",\n        \"nice-grpc-client-middleware-retry\": \"^3.1.10\",\n        \"nice-grpc-common\": \"^2.0.2\",\n        \"nice-grpc-opentelemetry\": \"^0.1.18\",\n        \"nice-grpc-web\": \"^3.3.7\",\n        \"ts-proto\": \"2.8.3\",\n        \"ua-parser-js\": \"^2.0.6\",\n        \"uuidv7\": \"^1.0.2\"\n      },\n      \"engines\": {\n        \"node\": \">=18.0.0\"\n      },\n      \"peerDependencies\": {\n        \"react\": \">=18.2.0\",\n        \"react-native\": \">=0.71.0\",\n        \"react-native-get-random-values\": \">=1.11.0\"\n      },\n      \"peerDependenciesMeta\": {\n        \"react\": {\n          \"optional\": true\n        },\n        \"react-native\": {\n          \"optional\": true\n        },\n        \"react-native-get-random-values\": {\n          \"optional\": true\n        }\n      }\n    },\n    \"node_modules/@grpc/grpc-js\": {\n      \"version\": \"1.14.3\",\n      \"resolved\": \"https://registry.npmjs.org/@grpc/grpc-js/-/grpc-js-1.14.3.tgz\",\n      \"integrity\": \"sha512-Iq8QQQ/7X3Sac15oB6p0FmUg/klxQvXLeileoqrTRGJYLV+/9tubbr9ipz0GKHjmXVsgFPo/+W+2cA8e"},{"path":"package.json","content":"{\n  \"name\": \"sparkbtcbot-skill\",\n  \"version\": \"0.1.0\",\n  \"type\": \"module\",\n  \"description\": \"Claude Code skill for setting up Spark Bitcoin L2 wallet capabilities for AI agents\",\n  \"dependencies\": {\n    \"@buildonspark/spark-sdk\": \"^0.5.8\",\n    \"dotenv\": \"^16.4.7\"\n  },\n  \"scripts\": {\n    \"example:setup\": \"node examples/wallet-setup.js\",\n    \"example:balance\": \"node examples/balance-and-deposits.js\",\n    \"example:payments\": \"node examples/payment-flow.js\",\n    \"example:tokens\": \"node examples/token-operations.js\",\n    \"example:agent\": \"node examples/spark-agent.js\"\n  }\n}"}],"languages":[],"docsSourceLabel":"CLAWHUB","editorialOverview":"Set up Spark Bitcoin L2 wallet capabilities for AI agents. Initialize wallets from mnemonic, transfer sats and tokens, create/pay Lightning invoices, pay L40... Skill: Sparkbtcbot Owner: echennells Summary: Set up Spark Bitcoin L2 wallet capabilities for AI agents. Initialize wallets from mnemonic, transfer sats and tokens, create/pay Lightning invoices, pay L40... Tags: latest:0.3.3 Version history: v0.3.3 | 2026-02-21T20:44:28.685Z | user Add homepage URL v0.3.2 | 2026-02-21T20:11:41.420Z | auto sparkbtcbot 0.3.2 - Added example scripts for common Spark wallet operations,","editorialQuality":{"score":100,"threshold":65,"status":"ready","wordCount":1591,"uniquenessScore":48,"reasons":[]}},"media":{"evidence":{"source":"no-media","verified":false,"confidence":"low","updatedAt":"2026-10-09T17:09:38.178Z","emptyReason":"No screenshots, media assets, or demo links are available."},"primaryImageUrl":null,"mediaAssetCount":0,"assets":[],"demoUrl":null},"ownerResources":{"evidence":{"source":"unclaimed","verified":false,"confidence":"low","updatedAt":"2026-10-09T17:09:38.178Z","emptyReason":"This page has not been claimed by the agent owner."},"hasCustomPage":false,"customPageUpdatedAt":null,"customLinks":[],"structuredLinks":{"docsUrl":null,"demoUrl":null,"supportUrl":null,"pricingUrl":null,"statusUrl":null},"customPage":null},"relatedAgents":{"evidence":{"source":"protocol-neighbors","verified":false,"confidence":"medium","updatedAt":"2026-10-09T20:52:32.261Z","emptyReason":null},"items":[{"id":"8ebccd8e-3863-4187-8355-c3f14e1f9edf","entityType":"agent","canonicalPath":"/agent/iofficeai-aionui","slug":"iofficeai-aionui","name":"AionUi","description":"Free, local, open-source 24/7 Cowork app and OpenClaw for Gemini CLI, Claude Code, Codex, OpenCode, Qwen Code, Goose CLI, Auggie, and more | 🌟 Star if you like it!","url":"https://github.com/iOfficeAI/AionUi","homepage":"https://www.aionui.com","source":"GITHUB_REPOS","protocols":["MCP","OPENCLAW"],"capabilities":[],"safetyScore":100,"overallRank":70,"updatedAt":"2026-10-09T19:11:12.944Z","createdAt":"2026-02-25T03:38:16.584Z","downloads":null},{"id":"b917f68a-ebff-438e-84f8-3f4b2494c0bc","entityType":"agent","canonicalPath":"/agent/activepieces-activepieces","slug":"activepieces-activepieces","name":"activepieces","description":"AI Agents & MCPs & AI Workflow Automation • (~400 MCP servers for AI agents) • AI Automation / AI Agent with MCPs • AI Workflows & AI Agents • MCPs for AI Agents","url":"https://github.com/activepieces/activepieces","homepage":"https://www.activepieces.com","source":"GITHUB_REPOS","protocols":["OPENCLAW"],"capabilities":[],"safetyScore":100,"overallRank":70,"updatedAt":"2026-04-15T02:22:12.426Z","createdAt":"2026-02-25T03:38:12.412Z","downloads":null},{"id":"5cb26759-3a39-483f-94cf-276a98c13bb8","entityType":"agent","canonicalPath":"/agent/cherryhq-cherry-studio","slug":"cherryhq-cherry-studio","name":"cherry-studio","description":"AI productivity studio with smart chat, autonomous agents, and 300+ assistants. Unified access to frontier LLMs","url":"https://github.com/CherryHQ/cherry-studio","homepage":"https://cherry-ai.com","source":"GITHUB_REPOS","protocols":["MCP","OPENCLAW"],"capabilities":[],"safetyScore":100,"overallRank":70,"updatedAt":"2026-04-11T14:38:40.986Z","createdAt":"2026-02-25T03:38:19.379Z","downloads":null},{"id":"6f6582d0-5d76-4f0f-b81d-86520247950b","entityType":"agent","canonicalPath":"/agent/copilotkit-copilotkit","slug":"copilotkit-copilotkit","name":"CopilotKit","description":"The Frontend for Agents & Generative UI. React + Angular","url":"https://github.com/CopilotKit/CopilotKit","homepage":"https://docs.copilotkit.ai","source":"GITHUB_REPOS","protocols":["OPENCLAW"],"capabilities":[],"safetyScore":100,"overallRank":70,"updatedAt":"2026-03-25T09:50:57.846Z","createdAt":"2026-02-25T03:39:14.617Z","downloads":null}],"links":{"hub":"/agent","source":"/agent/source/clawhub","protocols":[{"label":"OpenClaw","href":"/agent/protocol/openclew"}]}}}