# Page Not Found Source: https://docs.spark.money/404 The page you are looking for doesn't exist or has been moved. Sorry, we couldn't find the page you're looking for. Here are some helpful links: * [Home](/start/overview) * [Wallet Documentation](/wallets/overview) * [Issuance Documentation](/issuance/overview) * [API Reference](/api-reference/overview) If you believe this is a bug, please [let us know](https://www.spark.money/contact). # CLAUDE Source: https://docs.spark.money/CLAUDE # Doc Sync Agent Instructions This repo contains documentation for Spark ([https://spark.money](https://spark.money)). ## Repo Structure ``` issuance/ # Token issuance tutorials (IssuerSparkWallet) wallets/ # Wallet tutorials (SparkWallet) api-reference/ wallet/ # SparkWallet method reference issuer/ # IssuerSparkWallet method reference learn/ # Conceptual docs (architecture, trust model) quickstart/ # Getting started guides ``` ## SDK Source of Truth The Spark SDKs are the source of truth: * **SparkWallet**: `sdks/js/packages/spark-sdk/src/spark-wallet/spark-wallet.ts` * **IssuerSparkWallet**: `sdks/js/packages/issuer-sdk/src/issuer-wallet/issuer-spark-wallet.ts` ## Sync Rules ### Method Signatures * Parameter names must match SDK EXACTLY * Types must match EXACTLY (bigint vs number matters) * Optional parameters must be marked correctly * Return types must match ### Deprecations * Methods marked `@deprecated` in SDK need `**Deprecated**...` in docs * Link to the replacement method ### Code Examples * Must use correct parameter syntax * Object-style params: `method({ param1, param2 })` * Positional params: `method(param1, param2)` ## Common Issues to Check 1. **Multi-token support** - IssuerSparkWallet methods now take `tokenIdentifier` parameter 2. **Object vs positional params** - Many methods changed from positional to object params 3. **Async/await** - Ensure examples have `await` for async methods 4. **Type accuracy** - `bigint` for token amounts, `number` for sats ## Files to Compare When syncing, always compare: | Doc File | SDK Source | | ---------------------------- | ------------------------ | | `api-reference/wallet/*.mdx` | `spark-wallet.ts` | | `api-reference/issuer/*.mdx` | `issuer-spark-wallet.ts` | | `issuance/*.mdx` | `issuer-spark-wallet.ts` | | `wallets/*.mdx` | `spark-wallet.ts` | # Issuer API Source: https://docs.spark.money/api-reference/issuer-overview IssuerSparkWallet class methods for token creation, minting, burning, and freezing. The `IssuerSparkWallet` class extends `SparkWallet` with token issuance and management capabilities on the Spark network. All functions from `SparkWallet` are also available, including Bitcoin transfers, Lightning payments, and wallet management. *** ## Installation Install the Issuer SDK packages using your package manager of choice. ```bash npm theme={null} npm install @buildonspark/issuer-sdk ``` ```bash yarn theme={null} yarn add @buildonspark/issuer-sdk ``` ```bash pnpm theme={null} pnpm add @buildonspark/issuer-sdk ``` *** ## Method Categories # batchTransferTokens Source: https://docs.spark.money/api-reference/issuer/batch-transfer-tokens Transfer tokens to multiple recipients in one transaction. Transfers tokens to multiple recipients in a single transaction via the `IssuerSparkWallet`. ## Method Signature ```typescript theme={null} async batchTransferTokens( receiverOutputs: { tokenIdentifier: Bech32mTokenIdentifier; tokenAmount: bigint; receiverSparkAddress: string; }[], outputSelectionStrategy?: "SMALL_FIRST" | "LARGE_FIRST", selectedOutputs?: OutputWithPreviousTransactionData[], executeBefore?: Date ): Promise ``` ## Parameters Array of transfer outputs. Outputs may include multiple token identifiers: * `tokenIdentifier`: Bech32m token identifier (e.g., `btkn1...`) * `tokenAmount`: Amount of tokens to transfer (`bigint`) * `receiverSparkAddress`: Recipient's Spark address Strategy for selecting outputs: `"SMALL_FIRST"` or `"LARGE_FIRST"` (default: `"SMALL_FIRST"`) Specific outputs to use for transfer (overrides selection strategy) Optional deadline for the transaction. If the transaction is not executed before this time, it will not be processed. ## Returns Transaction ID ## Example ```typescript theme={null} const txId = await issuerWallet.batchTransferTokens([ { tokenIdentifier: "btkn1...", tokenAmount: 1000n, receiverSparkAddress: "spark1abc..." }, { tokenIdentifier: "btkn1...", tokenAmount: 500n, receiverSparkAddress: "spark1def..." }, { tokenIdentifier: "btkn1...", tokenAmount: 250n, receiverSparkAddress: "spark1ghi..." } ]); console.log("Batch transfer completed:", txId); ``` # burnTokens Source: https://docs.spark.money/api-reference/issuer/burn-tokens Burn tokens to reduce circulating supply. Burns existing tokens to reduce the circulating supply for the `IssuerSparkWallet`. ## Method Signature ```typescript theme={null} async burnTokens({ tokenAmount, tokenIdentifier, selectedOutputs, }: { tokenAmount: bigint; tokenIdentifier: Bech32mTokenIdentifier; selectedOutputs?: OutputWithPreviousTransactionData[]; }): Promise ``` ## Parameters The amount to burn (e.g., `1000n`) The token identifier to burn. Optional specific outputs to use for the burn operation ## Returns Transaction ID ## Example ```typescript theme={null} const tokenIdentifiers = await issuerWallet.getIssuerTokenIdentifiers(); const tokenId = tokenIdentifiers[0]; if (!tokenId) { throw new Error("No issuer token found"); } const txId = await issuerWallet.burnTokens({ tokenAmount: 500n, tokenIdentifier: tokenId, }); console.log("Tokens burned:", txId); ``` # createToken Source: https://docs.spark.money/api-reference/issuer/create-token Create a new token with name, ticker, decimals, and supply settings. Creates a new token on Spark using Spark Native Tokens for the `IssuerSparkWallet`. ## Method Signature ```typescript theme={null} // Returns transaction hash only async createToken({ tokenName, tokenTicker, decimals, maxSupply, isFreezable, extraMetadata, returnIdentifierForCreate, }: { tokenName: string; tokenTicker: string; decimals: number; maxSupply?: bigint; // defaults to 0n (unlimited) isFreezable: boolean; extraMetadata?: Uint8Array; returnIdentifierForCreate?: false; }): Promise // Returns both transaction hash and token identifier async createToken({ tokenName, tokenTicker, decimals, maxSupply, isFreezable, extraMetadata, returnIdentifierForCreate, }: { tokenName: string; tokenTicker: string; decimals: number; maxSupply?: bigint; // defaults to 0n (unlimited) isFreezable: boolean; extraMetadata?: Uint8Array; returnIdentifierForCreate: true; }): Promise ``` ## Parameters Name of the token (eg: SparkCoin) Token ticker (eg: SPARKC) The precision the token supports (eg: 8 for BTC) The maximum supply for this token (defaults to `0n` for unlimited supply) Whether or not the Issuer can freeze this token Optional extra metadata bytes to associate with the token (e.g., image data, JSON metadata) When `true`, returns both the transaction hash and token identifier as `TokenCreationDetails`. When `false` or omitted, returns only the transaction hash as a string (default: `false`) ## Returns When `returnIdentifierForCreate` is `false` or omitted: Spark Transaction ID When `returnIdentifierForCreate` is `true`: Object containing: * `tokenIdentifier`: Bech32m token identifier (e.g., `btkn1...`) * `transactionHash`: Spark Transaction ID ## Example ```typescript theme={null} // Basic usage - returns only transaction hash const txId = await issuerWallet.createToken({ tokenName: "SparkCoin", tokenTicker: "SPARKC", decimals: 8, maxSupply: 1000000n, isFreezable: true, // Optional: add extra metadata extraMetadata: new TextEncoder().encode(JSON.stringify({ icon: "..." })) }); console.log("Token created:", txId); // Get both transaction hash and token identifier const result = await issuerWallet.createToken({ tokenName: "SparkCoin", tokenTicker: "SPARKC", decimals: 8, maxSupply: 1000000n, isFreezable: true, returnIdentifierForCreate: true }); console.log("Token created:", result.transactionHash); console.log("Token identifier:", result.tokenIdentifier); ``` # freezeTokens Source: https://docs.spark.money/api-reference/issuer/freeze-tokens Freeze tokens held by a specific Spark address. Freezes issuer tokens for a specific wallet via the `IssuerSparkWallet`. ## Method Signature ```typescript theme={null} async freezeTokens({ tokenIdentifier, sparkAddress, }: { tokenIdentifier: Bech32mTokenIdentifier; sparkAddress: string; }): Promise<{ impactedTokenOutputs: TokenOutputRef[]; impactedTokenAmount: bigint; }> ``` ## Parameters The token identifier to freeze. The Spark Address to freeze ## Returns Array of token output references that were frozen. Each `TokenOutputRef` contains: * `transactionHash`: `Uint8Array` - The transaction hash * `vout`: `number` - The output index Total amount of tokens frozen ## Example ```typescript theme={null} const tokenIdentifiers = await issuerWallet.getIssuerTokenIdentifiers(); const tokenId = tokenIdentifiers[0]; if (!tokenId) { throw new Error("No issuer token found"); } const result = await issuerWallet.freezeTokens({ tokenIdentifier: tokenId, sparkAddress: "spark1qw508d6qejxtdg4y5r3zarvary0c5xw7kxpjzsx", }); console.log("Frozen outputs:", result.impactedTokenOutputs); console.log("Frozen amount:", result.impactedTokenAmount); ``` # getIdentityPublicKey Source: https://docs.spark.money/api-reference/issuer/get-identity-public-key Get the issuer wallet's identity public key as hex. Gets the identity public key of the `SparkWallet`. ## Method Signature ```typescript theme={null} async getIdentityPublicKey(): Promise ``` ## Returns The identity public key as a hex string ## Example ```typescript theme={null} const identityPubKey = await wallet.getIdentityPublicKey(); console.log("Identity Public Key:", identityPubKey); ``` # getIssuerTokenBalances Source: https://docs.spark.money/api-reference/issuer/get-issuer-token-balances Get all token balances for a multi-token issuer. Gets all token balances for tokens issued by this `IssuerSparkWallet`. Supports issuers with multiple tokens. ## Method Signature ```typescript theme={null} async getIssuerTokenBalances(): Promise<{ tokenIdentifier: Bech32mTokenIdentifier | undefined; balance: bigint; }[]> ``` ## Returns Array of objects containing token identifier and balance for each token issued by this wallet. Bech32m token identifier (e.g., `btkn1...`), or `undefined` if no token exists Token balance held by the issuer ## Example ```typescript theme={null} const balances = await issuerWallet.getIssuerTokenBalances(); for (const { tokenIdentifier, balance } of balances) { if (tokenIdentifier) { console.log(`Token ${tokenIdentifier}: ${balance}`); } } ``` # getIssuerTokenDistribution Source: https://docs.spark.money/api-reference/issuer/get-issuer-token-distribution Get token distribution stats: circulating supply, holders, and transactions. Gets the token distribution information for the token associated with this `IssuerSparkWallet`. This feature is currently under development and will be available in a future release of Spark. ## Method Signature ```typescript theme={null} interface TokenDistribution { totalCirculatingSupply: bigint; totalIssued: bigint; totalBurned: bigint; numHoldingAddress: number; numConfirmedTransactions: bigint; } async function getIssuerTokenDistribution(): Promise; ``` ## Returns Total circulating supply of the token Total issued tokens Total tokens burned Number of addresses holding the token Number of confirmed transactions ## Example ```typescript theme={null} const distribution = await issuerWallet.getIssuerTokenDistribution(); console.log("Circulating supply:", distribution.totalCirculatingSupply); console.log("Total issued:", distribution.totalIssued); console.log("Total burned:", distribution.totalBurned); console.log("Holding addresses:", distribution.numHoldingAddress); console.log("Confirmed transactions:", distribution.numConfirmedTransactions); ``` # getIssuerTokenIdentifiers Source: https://docs.spark.money/api-reference/issuer/get-issuer-token-identifiers Get all token identifiers (btkn1...) for a multi-token issuer. Gets all Bech32m token identifiers for tokens issued by this `IssuerSparkWallet`. Supports issuers with multiple tokens. ## Method Signature ```typescript theme={null} async getIssuerTokenIdentifiers(): Promise ``` ## Returns Array of Bech32m token identifiers (e.g., `["btkn1...", "btkn1..."]`) ## Example ```typescript theme={null} const tokenIdentifiers = await issuerWallet.getIssuerTokenIdentifiers(); console.log("Tokens issued by this wallet:"); for (const tokenId of tokenIdentifiers) { console.log(" -", tokenId); } // Use in operations for (const tokenId of tokenIdentifiers) { const balance = await issuerWallet.getBalance(); const tokenBalance = balance.tokenBalances.get(tokenId); console.log(`${tokenId}: ${tokenBalance?.balance ?? 0n}`); } ``` # getIssuerTokensMetadata Source: https://docs.spark.money/api-reference/issuer/get-issuer-tokens-metadata Get metadata for all tokens issued by this wallet. Gets metadata for all tokens issued by this `IssuerSparkWallet`. Supports issuers with multiple tokens. ## Method Signature ```typescript theme={null} interface IssuerTokenMetadata { tokenPublicKey: string; // Issuer's public key (same as identity public key) rawTokenIdentifier: Uint8Array; // Binary token identifier tokenName: string; tokenTicker: string; // Token ticker symbol (e.g., "USDT") decimals: number; // Number of decimal places maxSupply: bigint; isFreezable: boolean; extraMetadata?: Uint8Array; // Arbitrary bytes set during token creation bech32mTokenIdentifier: string; // Bech32m encoded token identifier } async getIssuerTokensMetadata( tokenIdentifiers?: Bech32mTokenIdentifier[] ): Promise ``` ## Parameters Optional array of token identifiers to filter by. If not provided, returns metadata for all tokens issued by this wallet. ## Returns Array of metadata objects for each token issued by this wallet (or filtered tokens if `tokenIdentifiers` is provided) ## Examples ```typescript theme={null} // Get metadata for all tokens issued by this wallet const tokensMetadata = await issuerWallet.getIssuerTokensMetadata(); for (const metadata of tokensMetadata) { console.log("Token name:", metadata.tokenName); console.log("Token ticker:", metadata.tokenTicker); console.log("Token ID:", metadata.bech32mTokenIdentifier); console.log("Decimals:", metadata.decimals); console.log("Max supply:", metadata.maxSupply); console.log("---"); } // Get metadata for specific tokens only const specificTokensMetadata = await issuerWallet.getIssuerTokensMetadata([ "btkn1...", "btkn1..." ]); ``` # getSparkAddress Source: https://docs.spark.money/api-reference/issuer/get-spark-address Get the issuer wallet's Spark address for receiving payments. Gets the Spark Address of the `SparkWallet`. ## Method Signature ```typescript theme={null} async getSparkAddress(): Promise ``` ## Returns The Spark Address ## Example ```typescript theme={null} const sparkAddress = await wallet.getSparkAddress(); console.log("Spark Address:", sparkAddress); ``` # initialize Source: https://docs.spark.money/api-reference/issuer/initialize Create or restore an IssuerSparkWallet instance from mnemonic or seed. Creates and initializes a new `IssuerSparkWallet` instance. ## Method Signature ```typescript theme={null} interface IssuerSparkWalletProps { mnemonicOrSeed?: Uint8Array | string; accountNumber?: number; signer?: SparkSigner; options?: ConfigOptions; } static async initialize(props: IssuerSparkWalletProps): Promise<{ wallet: IssuerSparkWallet; mnemonic?: string; }> ``` ## Parameters BIP-39 mnemonic phrase or raw seed Number used to generate multiple identity keys from the same mnemonic Custom signer implementation for advanced use cases Wallet configuration options including network selection ## Returns The initialized IssuerSparkWallet instance The mnemonic if one was generated (undefined for raw seed) ## Example ```typescript Create New Wallet theme={null} import { IssuerSparkWallet } from "@buildonspark/issuer-sdk"; // Create a new issuer wallet const { wallet, mnemonic } = await IssuerSparkWallet.initialize({ options: { network: "REGTEST" } // or "MAINNET" }); console.log("Issuer wallet initialized:", wallet); console.log("Generated mnemonic:", mnemonic); ``` ```typescript Import Existing Wallet theme={null} import { IssuerSparkWallet } from "@buildonspark/issuer-sdk"; // Import issuer wallet from existing mnemonic const { wallet } = await IssuerSparkWallet.initialize({ mnemonicOrSeed: "abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon about", accountNumber: 0, // Optional: specify account index options: { network: "REGTEST" } }); console.log("Issuer wallet restored from mnemonic"); ``` # mintTokens Source: https://docs.spark.money/api-reference/issuer/mint-tokens Mint new tokens to increase circulating supply. Mints new tokens to increase the circulating supply for the `IssuerSparkWallet`. ## Method Signature ```typescript theme={null} async mintTokens({ tokenAmount, tokenIdentifier, }: { tokenAmount: bigint; tokenIdentifier: Bech32mTokenIdentifier; }): Promise ``` ## Parameters The amount to mint (e.g., `1000n`) The token identifier to mint. ## Returns Transaction ID ## Example ```typescript theme={null} const tokenIdentifiers = await issuerWallet.getIssuerTokenIdentifiers(); const tokenId = tokenIdentifiers[0]; if (!tokenId) { throw new Error("No issuer token found"); } const txId = await issuerWallet.mintTokens({ tokenAmount: 5000n, tokenIdentifier: tokenId, }); console.log("Tokens minted:", txId); ``` # transferTokens Source: https://docs.spark.money/api-reference/issuer/transfer-tokens Transfer tokens from issuer wallet to a Spark address. Transfers tokens to another Spark Address via the `IssuerSparkWallet`. ## Method Signature ```typescript theme={null} async transferTokens({ tokenIdentifier, tokenAmount, receiverSparkAddress, outputSelectionStrategy, selectedOutputs, executeBefore, }: { tokenIdentifier: Bech32mTokenIdentifier; tokenAmount: bigint; receiverSparkAddress: string; outputSelectionStrategy?: "SMALL_FIRST" | "LARGE_FIRST"; selectedOutputs?: OutputWithPreviousTransactionData[]; executeBefore?: Date; }): Promise ``` ## Parameters Bech32m token identifier (eg: btkn1…) of the token to transfer Amount of tokens to transfer Recipient's Spark Address Strategy for selecting outputs: `"SMALL_FIRST"` or `"LARGE_FIRST"` (defaults to `"SMALL_FIRST"`) Specific outputs to use for transfer (overrides selection strategy) Optional deadline for the transaction. If the transaction is not executed before this time, it will not be processed. ## Returns Transaction ID ## Example ```typescript theme={null} const txId = await issuerWallet.transferTokens({ tokenIdentifier: "btkn1...", tokenAmount: 1000n, receiverSparkAddress: "spark1qw508d6qejxtdg4y5r3zarvary0c5xw7kxpjzsx" }); console.log("Tokens transferred:", txId); ``` # unfreezeTokens Source: https://docs.spark.money/api-reference/issuer/unfreeze-tokens Unfreeze previously frozen tokens for a Spark address. Unfreezes issuer tokens for a specific wallet via the `IssuerSparkWallet`. ## Method Signature ```typescript theme={null} async unfreezeTokens({ tokenIdentifier, sparkAddress, }: { tokenIdentifier: Bech32mTokenIdentifier; sparkAddress: string; }): Promise<{ impactedTokenOutputs: TokenOutputRef[]; impactedTokenAmount: bigint; }> ``` ## Parameters The token identifier to unfreeze. The Spark Address to unfreeze ## Returns Array of token output references that were unfrozen. Each `TokenOutputRef` contains: * `transactionHash`: `Uint8Array` - The transaction hash * `vout`: `number` - The output index Total amount of tokens unfrozen ## Example ```typescript theme={null} const tokenIdentifiers = await issuerWallet.getIssuerTokenIdentifiers(); const tokenId = tokenIdentifiers[0]; if (!tokenId) { throw new Error("No issuer token found"); } const result = await issuerWallet.unfreezeTokens({ tokenIdentifier: tokenId, sparkAddress: "spark1qw508d6qejxtdg4y5r3zarvary0c5xw7kxpjzsx", }); console.log("Unfrozen outputs:", result.impactedTokenOutputs); console.log("Unfrozen amount:", result.impactedTokenAmount); ``` # Overview Source: https://docs.spark.money/api-reference/overview Spark SDK methods for wallets, Lightning, and token issuance. Welcome to the Spark API Reference documentation. This comprehensive guide covers all the methods available in the Spark SDKs for building Bitcoin-native applications, including wallet management, Bitcoin operations, Lightning Network integration, and token issuance. *** ## Available SDKs *** ## Getting Started To get started with the Spark API, follow the steps below. Install the SDK package using your package manager of choice. For the **Wallet SDK**: ```bash npm theme={null} npm install @buildonspark/spark-sdk ``` ```bash yarn theme={null} yarn add @buildonspark/spark-sdk ``` ```bash pnpm theme={null} pnpm add @buildonspark/spark-sdk ``` For the **Issuer SDK**: ```bash npm theme={null} npm install @buildonspark/issuer-sdk ``` ```bash yarn theme={null} yarn add @buildonspark/issuer-sdk ``` ```bash pnpm theme={null} pnpm add @buildonspark/issuer-sdk ``` Create a wallet instance to start interacting with the Spark network. For the **Wallet SDK**: ```ts theme={null} import { SparkWallet } from "@buildonspark/spark-sdk"; async function main() { const { wallet, mnemonic } = await SparkWallet.initialize({ options: { network: "MAINNET" }, }); const address = await wallet.getSparkAddress(); console.log({ address }); } main(); ``` For the **Issuer SDK**: ```ts theme={null} import { IssuerSparkWallet } from "@buildonspark/issuer-sdk"; async function main() { const { wallet, mnemonic } = await IssuerSparkWallet.initialize({ options: { network: "MAINNET" }, }); const address = await wallet.getSparkAddress(); console.log({ address }); } main(); ``` Browse the method documentation using the sidebar navigation. Each method includes detailed parameters, return values, and code examples. *** ## Error Handling The SDK throws typed errors that you can catch and handle appropriately: ```typescript theme={null} import { SparkError, SparkValidationError, SparkRequestError } from "@buildonspark/spark-sdk"; try { await wallet.transfer({ receiverSparkAddress: "...", amountSats: 1000 }); } catch (error) { if (error instanceof SparkValidationError) { // Invalid parameters (e.g., negative amount, invalid address format) console.error("Validation error:", error.message); const { field } = error.getContext() as { field?: string }; console.error("Field:", field); } else if (error instanceof SparkRequestError) { // Network/API errors console.error("Request failed:", error.message); } else if (error instanceof SparkError) { // General SDK errors console.error("SDK error:", error.message); } } ``` | Error Type | When Thrown | | ---------------------- | ------------------------------------------------------ | | `SparkValidationError` | Invalid parameters, out-of-range values, format errors | | `SparkRequestError` | Network failures, API errors, timeout | | `SparkError` | General SDK errors, configuration issues | *** ## Use AI Tools with These Docs Our documentation is optimized for AI coding assistants like Cursor, Claude, and ChatGPT. | Resource | URL | | :------------------------ | :----------------------------------------------------------------------- | | Full docs (LLM-optimized) | [docs.spark.money/llms-full.txt](https://docs.spark.money/llms-full.txt) | | Docs index | [docs.spark.money/llms.txt](https://docs.spark.money/llms.txt) | | Any page as Markdown | Append `.md` to any URL | Paste the `llms-full.txt` URL into your AI assistant's context for complete knowledge of Spark's APIs and best practices. # Wallet Viewer API Source: https://docs.spark.money/api-reference/readonly-overview SparkReadonlyClient methods for querying balances, transfers, deposits, and token data. The `SparkReadonlyClient` provides read-only access to wallet data on the Spark network. Use it for dashboards, explorers, analytics, or any scenario where you need to query data without full wallet initialization. Three ways to create a client: | Factory | Auth | Use case | | ------------------------------------------------------------------------- | ------------- | ----------------------------- | | [`createPublic()`](/api-reference/readonly/create-public) | None | Query public wallet data | | [`createWithMasterKey()`](/api-reference/readonly/create-with-master-key) | Identity key | Query private wallets you own | | [`createWithSigner()`](/api-reference/readonly/create-with-signer) | Custom signer | Partner integrations | *** ## Installation ```bash npm theme={null} npm install @buildonspark/spark-sdk ``` ```bash yarn theme={null} yarn add @buildonspark/spark-sdk ``` ```bash pnpm theme={null} pnpm add @buildonspark/spark-sdk ``` *** ## Method Categories # createPublic Source: https://docs.spark.money/api-reference/readonly/create-public Create an unauthenticated wallet viewer for public wallet queries. Creates a `SparkReadonlyClient` with no authentication. Can query any wallet that hasn't enabled [privacy mode](/wallets/privacy). ## Method Signature ```typescript theme={null} static createPublic(config?: ConfigOptions): SparkReadonlyClient ``` ## Parameters Configuration options including network selection. ```typescript theme={null} interface ConfigOptions { network?: "MAINNET" | "TESTNET" | "SIGNET" | "REGTEST" | "LOCAL"; // Additional advanced options rarely needed for readonly use } ``` ## Returns A wallet viewer instance with no authentication. Requests are made without an identity key. ## Example ```typescript theme={null} import { SparkReadonlyClient } from "@buildonspark/spark-sdk"; const client = SparkReadonlyClient.createPublic({ network: "MAINNET", }); // Query any public wallet const balance = await client.getAvailableBalance("sp1..."); console.log("Balance:", balance, "sats"); ``` Private wallets return empty results (zero balances, empty arrays) to unauthenticated clients, not errors. Use [`createWithMasterKey()`](/api-reference/readonly/create-with-master-key) to query private wallets you own. # createWithMasterKey Source: https://docs.spark.money/api-reference/readonly/create-with-master-key Create an authenticated wallet viewer using a mnemonic or seed. Creates a `SparkReadonlyClient` authenticated with an identity key derived from a mnemonic or seed. Can query private wallets the key owns. ## Method Signature ```typescript theme={null} static async createWithMasterKey( config: ConfigOptions | undefined, mnemonicOrSeed: Uint8Array | string, accountNumber?: number, ): Promise ``` ## Parameters Configuration options including network selection. Pass `undefined` for defaults. BIP-39 mnemonic phrase, hex-encoded seed string, or raw seed bytes. Account index for key derivation. Defaults to `0` for REGTEST, `1` otherwise. ## Returns An authenticated wallet viewer. Requests include an identity key signature for access to private wallet data. ## Example ```typescript From Mnemonic theme={null} import { SparkReadonlyClient } from "@buildonspark/spark-sdk"; const client = await SparkReadonlyClient.createWithMasterKey( { network: "MAINNET" }, "abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon about", ); // Can see private wallet data const balance = await client.getAvailableBalance("sp1..."); ``` ```typescript From Seed theme={null} import { SparkReadonlyClient } from "@buildonspark/spark-sdk"; const client = await SparkReadonlyClient.createWithMasterKey( { network: "MAINNET" }, seedBytes, // Uint8Array 1, // account number ); ``` # createWithSigner Source: https://docs.spark.money/api-reference/readonly/create-with-signer Create an authenticated wallet viewer with a custom SparkSigner. Creates a `SparkReadonlyClient` authenticated with a custom `SparkSigner`. Designed for partners (e.g., Privy) who provide their own signer implementation without exposing a mnemonic. ## Method Signature ```typescript theme={null} static createWithSigner( config: ConfigOptions | undefined, signer: SparkSigner, ): SparkReadonlyClient ``` ## Parameters Configuration options including network selection. Pass `undefined` for defaults. A pre-initialized `SparkSigner` implementation. The signer's identity key is used for authentication. ## Returns An authenticated wallet viewer using the provided signer for identity verification. ## Example ```typescript theme={null} import { SparkReadonlyClient } from "@buildonspark/spark-sdk"; const client = SparkReadonlyClient.createWithSigner( { network: "MAINNET" }, myCustomSigner, ); const balance = await client.getAvailableBalance("sp1..."); ``` See the [Spark Signer Interface](/wallets/spark-signer) docs for implementing a custom `SparkSigner`. # getAvailableBalance Source: https://docs.spark.money/api-reference/readonly/get-available-balance Get the available Bitcoin balance for a Spark address. Gets the available Bitcoin balance in satoshis for a Spark address. Automatically paginates through all wallet nodes. ## Method Signature ```typescript theme={null} async getAvailableBalance(sparkAddress: string): Promise ``` ## Parameters The Spark address to query (e.g., `"sp1..."`). ## Returns Available balance in satoshis. Returns `0n` for private wallets queried without authentication. ## Example ```typescript theme={null} const balance = await client.getAvailableBalance("sp1..."); console.log("Balance:", balance, "sats"); console.log("Balance (BTC):", Number(balance) / 1e8); ``` # getPendingTransfers Source: https://docs.spark.money/api-reference/readonly/get-pending-transfers Get pending inbound transfers for a Spark address. Gets pending inbound transfers for a Spark address. ## Method Signature ```typescript theme={null} async getPendingTransfers(sparkAddress: string): Promise ``` ## Parameters The Spark address to query pending transfers for. ## Returns Array of pending inbound transfer objects. ## Example ```typescript theme={null} const pending = await client.getPendingTransfers("sp1..."); console.log(`${pending.length} pending transfers`); for (const transfer of pending) { console.log(transfer.id, transfer.totalValue); } ``` # getSparkInvoices Source: https://docs.spark.money/api-reference/readonly/get-spark-invoices Query the status of Spark invoices. Queries the status of Spark invoices. ## Method Signature ```typescript theme={null} async getSparkInvoices(params: QuerySparkInvoicesParams): Promise<{ invoiceStatuses: InvoiceResponse[]; offset: number; }> interface QuerySparkInvoicesParams { invoices: string[]; limit?: number; // default: 100 offset?: number; // default: 0 } ``` ## Parameters Array of Spark invoice strings to query. Must be non-empty. Maximum number of results to return (default: `100`). Offset for pagination (default: `0`). ## Returns Array of invoice status objects. The offset used for this request. ## Example ```typescript theme={null} const { invoiceStatuses } = await client.getSparkInvoices({ invoices: ["spark1..."], }); for (const invoice of invoiceStatuses) { console.log(invoice); } ``` # getStaticDepositAddresses Source: https://docs.spark.money/api-reference/readonly/get-static-deposit-addresses Get all static deposit addresses for a Spark address. Gets all static deposit addresses for a Spark address. ## Method Signature ```typescript theme={null} async getStaticDepositAddresses( sparkAddress: string, ): Promise ``` ## Parameters The Spark address to query static deposit addresses for. ## Returns Array of static deposit address objects. ## Example ```typescript theme={null} const addresses = await client.getStaticDepositAddresses("sp1..."); for (const addr of addresses) { console.log(addr); } ``` # getTokenBalance Source: https://docs.spark.money/api-reference/readonly/get-token-balance Get token balances and metadata for a Spark address. Gets token balances with metadata for a Spark address. Automatically paginates through all token outputs. ## Method Signature ```typescript theme={null} async getTokenBalance( sparkAddress: string, tokenIdentifiers?: string[], ): Promise // TokenBalanceMap = Map interface UserTokenMetadata { rawTokenIdentifier: Uint8Array; tokenPublicKey: string; tokenName: string; tokenTicker: string; decimals: number; maxSupply: bigint; extraMetadata?: Uint8Array; } ``` ## Parameters The Spark address to query. Optional array of Bech32m token identifiers to filter by. If omitted, returns all tokens. ## Returns Map of token identifiers to balance and metadata objects. Each entry contains: * `ownedBalance`: Total tokens owned (including pending outbound) * `availableToSendBalance`: Tokens available to send * `tokenMetadata`: Name, ticker, decimals, issuer key, max supply ## Example ```typescript theme={null} // All tokens const balances = await client.getTokenBalance("sp1..."); for (const [tokenId, info] of balances) { console.log(`${info.tokenMetadata.tokenTicker}: ${info.availableToSendBalance}`); } // Specific tokens const filtered = await client.getTokenBalance("sp1...", ["btkn1..."]); ``` # getTokenTransactions Source: https://docs.spark.money/api-reference/readonly/get-token-transactions Query token transactions with cursor-based pagination. Queries token transactions with optional filters and cursor-based pagination. ## Method Signature ```typescript theme={null} async getTokenTransactions( params?: QueryTokenTransactionsParams, ): Promise<{ transactions: TokenTransactionWithStatus[]; pageResponse: { nextCursor?: string; prevCursor?: string; }; }> interface QueryTokenTransactionsParams { sparkAddresses?: string[]; issuerPublicKeys?: string[]; tokenIdentifiers?: string[]; outputIds?: string[]; pageSize?: number; // default: 50 cursor?: string; direction?: "NEXT" | "PREVIOUS"; } ``` ## Parameters Filters are combined using **AND** logic. Filter by Spark addresses. Filter by issuer public keys. Filter by Bech32m token identifiers. Filter by output IDs. Number of results per page (default: `50`). Cursor from a previous response for pagination. Pagination direction (default: `"NEXT"`). ## Returns Array of token transaction objects. Pagination metadata with `nextCursor` and `prevCursor`. ## Example ```typescript theme={null} // First page const page = await client.getTokenTransactions({ sparkAddresses: ["sp1..."], pageSize: 25, }); console.log(`${page.transactions.length} transactions`); // Next page if (page.pageResponse?.nextCursor) { const next = await client.getTokenTransactions({ sparkAddresses: ["sp1..."], cursor: page.pageResponse.nextCursor, direction: "NEXT", }); } ``` # getTransfers Source: https://docs.spark.money/api-reference/readonly/get-transfers Get paginated transfer history for a Spark address. Gets paginated transfer history for a Spark address with optional date filters. ## Method Signature ```typescript theme={null} async getTransfers(params: QueryTransfersParams): Promise<{ transfers: Transfer[]; offset: number; }> interface QueryTransfersParams { sparkAddress: string; limit?: number; // default: 20 offset?: number; // default: 0 types?: TransferType[]; createdAfter?: Date; createdBefore?: Date; } ``` ## Parameters The Spark address to query transfers for. Maximum number of transfers to return (default: `20`). Offset for pagination (default: `0`). Filter by transfer types. Defaults to `TRANSFER`, `PREIMAGE_SWAP`, `COOPERATIVE_EXIT`, `UTXO_SWAP`. Only return transfers created after this date. Only return transfers created before this date. `createdAfter` and `createdBefore` are mutually exclusive. Providing both will throw an error. ## Returns Array of transfer objects. The offset used for this request. ## Example ```typescript theme={null} // Basic pagination const { transfers, offset } = await client.getTransfers({ sparkAddress: "sp1...", limit: 25, offset: 0, }); // Transfers from the last 24 hours const recent = await client.getTransfers({ sparkAddress: "sp1...", createdAfter: new Date(Date.now() - 86_400_000), }); ``` # getTransfersByIds Source: https://docs.spark.money/api-reference/readonly/get-transfers-by-ids Look up specific transfers by their IDs. Looks up specific transfers by their IDs. ## Method Signature ```typescript theme={null} async getTransfersByIds(transferIds: string[]): Promise ``` ## Parameters Array of transfer IDs to look up. Must be non-empty. ## Returns Array of matching transfer objects. ## Example ```typescript theme={null} const transfers = await client.getTransfersByIds([ "transfer-id-1", "transfer-id-2", ]); for (const transfer of transfers) { console.log(transfer.id, transfer.status); } ``` # getUnusedDepositAddresses Source: https://docs.spark.money/api-reference/readonly/get-unused-deposit-addresses Get unused deposit addresses for a Spark address. Gets unused deposit addresses for a Spark address with pagination. ## Method Signature ```typescript theme={null} async getUnusedDepositAddresses( params: QueryDepositAddressesParams, ): Promise<{ depositAddresses: DepositAddressQueryResult[]; offset: number; }> interface QueryDepositAddressesParams { sparkAddress: string; limit?: number; // default: 100 offset?: number; // default: 0 } ``` ## Parameters The Spark address to query deposit addresses for. Maximum number of addresses to return (default: `100`). Offset for pagination (default: `0`). ## Returns Array of unused deposit address objects. The offset used for this request. ## Example ```typescript theme={null} const { depositAddresses } = await client.getUnusedDepositAddresses({ sparkAddress: "sp1...", limit: 50, }); for (const addr of depositAddresses) { console.log(addr); } ``` # getUtxosForDepositAddress Source: https://docs.spark.money/api-reference/readonly/get-utxos-for-deposit-address Get confirmed UTXOs for a specific deposit address. Gets confirmed UTXOs for a specific Bitcoin deposit address with pagination. ## Method Signature ```typescript theme={null} async getUtxosForDepositAddress(params: GetUtxosParams): Promise<{ utxos: { txid: string; vout: number }[]; offset: number; }> interface GetUtxosParams { depositAddress: string; limit?: number; // default: 100 offset?: number; // default: 0 excludeClaimed?: boolean; } ``` ## Parameters The Bitcoin deposit address to query UTXOs for (e.g., `"bc1..."`). Maximum number of UTXOs to return (default: `100`). Offset for pagination (default: `0`). If `true`, excludes already-claimed UTXOs from results. ## Returns Array of UTXO objects with transaction ID and output index. The offset used for this request. ## Example ```typescript theme={null} const { utxos } = await client.getUtxosForDepositAddress({ depositAddress: "bc1...", excludeClaimed: true, }); for (const utxo of utxos) { console.log(`${utxo.txid}:${utxo.vout}`); } ``` # Verify Signatures Without Wallet Source: https://docs.spark.money/api-reference/utilities/verify-signature-readonly Verify message signatures using only a Spark address, without initializing a wallet. Verify message signatures without creating a wallet instance. This is useful for readonly verification scenarios where you only have access to a Spark address. ## Use Case When building applications that need to verify signatures but don't have access to wallet credentials: * Readonly wallet views * Signature verification services * Cross-chain verification (verifying Spark signatures from other chains) * Server-side verification without sensitive key material ## How It Works Spark addresses encode the wallet's identity public key. You can extract this key using `decodeSparkAddress` and verify signatures directly with secp256k1. ## Implementation ```typescript theme={null} import { decodeSparkAddress } from "@buildonspark/spark-sdk"; import * as secp256k1 from "@noble/secp256k1"; // Extract identity public key from Spark address const { identityPublicKey } = decodeSparkAddress(address, network); // Verify the signature const isValid = secp256k1.verify(signature, message, identityPublicKey); ``` ## Parameters The Spark address to extract the identity public key from The network type (`MAINNET`, `TESTNET`, `SIGNET`, `REGTEST`, or `LOCAL`) The signature to verify The original message that was signed ## Full Example ```typescript theme={null} import { decodeSparkAddress } from "@buildonspark/spark-sdk"; import type { NetworkType } from "@buildonspark/spark-sdk"; import * as secp256k1 from "@noble/secp256k1"; import { sha256 } from "@noble/hashes/sha256"; async function verifySparkSignature( address: string, message: string, signature: string, network: NetworkType = "MAINNET" ): Promise { // Decode address to get identity public key const { identityPublicKey } = decodeSparkAddress(address, network); // Hash the message (signatures are typically over message hashes) const messageHash = sha256(new TextEncoder().encode(message)); // Convert signature from hex if needed const sigBytes = typeof signature === "string" ? Uint8Array.from(Buffer.from(signature, "hex")) : signature; // Verify using secp256k1 return secp256k1.verify(sigBytes, messageHash, identityPublicKey); } // Usage const isValid = await verifySparkSignature( "sp1qw508d6qejxtdg4y5r3zarvary0c5xw7k...", "Hello, Spark!", "304402..." ); console.log("Signature valid:", isValid); ``` ## Key Points `decodeSparkAddress` is exported directly from the SDK and doesn't require wallet initialization Only needs the SDK and a secp256k1 library—no sensitive data or network calls ## Related * [signMessageWithIdentityKey](/api-reference/wallet/sign-message-with-identity-key) - Sign messages with wallet * [validateMessageWithIdentityKey](/api-reference/wallet/validate-message-with-identity-key) - Validate with wallet instance * [getIdentityPublicKey](/api-reference/wallet/get-identity-public-key) - Get identity key from wallet # Wallet API Source: https://docs.spark.money/api-reference/wallet-overview SparkWallet class methods for deposits, transfers, Lightning, and tokens. The `SparkWallet` class is the primary interface for interacting with the Spark network, providing everything you need to build wallet applications on Bitcoin. It includes methods for creating and managing wallets, handling deposits and withdrawals, executing transfers, and interacting with the Lightning Network. *** ## Installation Install the Spark SDK packages using your package manager of choice. ```bash npm theme={null} npm install @buildonspark/spark-sdk ``` ```bash yarn theme={null} yarn add @buildonspark/spark-sdk ``` ```bash pnpm theme={null} pnpm add @buildonspark/spark-sdk ``` *** ## Method Categories # advancedDeposit Source: https://docs.spark.money/api-reference/wallet/advanced-deposit Deposit funds to Spark using a raw Bitcoin transaction. Non-trusty flow for depositing funds to the `SparkWallet`. Construct a Bitcoin transaction that pays to one of the wallet's unused deposit addresses, then call `advancedDeposit()` with the raw transaction hex. `advancedDeposit()` does not broadcast the transaction. You must sign and broadcast the transaction yourself. ## Method Signature ```typescript theme={null} async advancedDeposit(txHex: string): Promise ``` ## Parameters The hex string of the transaction to deposit ## Returns The nodes resulting from the deposit ## Example ```typescript theme={null} const nodes = await wallet.advancedDeposit("transaction-hex-string"); console.log("Deposit nodes:", nodes); ``` # batchTransferTokens Source: https://docs.spark.money/api-reference/wallet/batch-transfer-tokens Send tokens to multiple recipients in one transaction. Transfers tokens to multiple recipients in a single transaction. ## Method Signature ```typescript theme={null} async batchTransferTokens( receiverOutputs: { tokenIdentifier: Bech32mTokenIdentifier; tokenAmount: bigint; receiverSparkAddress: string; }[], outputSelectionStrategy: "SMALL_FIRST" | "LARGE_FIRST" = "SMALL_FIRST", selectedOutputs?: OutputWithPreviousTransactionData[] ): Promise ``` ## Parameters Array of transfer outputs, each containing: * `tokenIdentifier`: Bech32m token identifier for this output. Outputs may include multiple token identifiers. * `tokenAmount`: Amount of tokens to send * `receiverSparkAddress`: Recipient's Spark address Strategy for selecting outputs: `"SMALL_FIRST"` or `"LARGE_FIRST"` (default: `"SMALL_FIRST"`) Specific outputs to use (overrides selection strategy) ## Returns The transaction ID of the batch token transfer ## Example ```typescript theme={null} const txId = await wallet.batchTransferTokens([ { tokenIdentifier: "btkn1...", tokenAmount: 500n, receiverSparkAddress: "spark1abc..." }, { tokenIdentifier: "btkn1...", tokenAmount: 300n, receiverSparkAddress: "spark1def..." } ]); console.log("Batch transfer completed:", txId); ``` # checkTimelock Source: https://docs.spark.money/api-reference/wallet/check-timelock Check remaining timelock blocks for a node. Checks the remaining timelock on a given node. ## Method Signature ```typescript theme={null} async checkTimelock(nodeId: string): Promise<{ nodeTimelock: number; refundTimelock: number; }> ``` ## Parameters The ID of the node to check ## Returns Object containing: * `nodeTimelock`: Remaining blocks for the node transaction * `refundTimelock`: Remaining blocks for the refund transaction ## Example ```typescript theme={null} const timelocks = await wallet.checkTimelock("node-id-here"); console.log("Node timelock:", timelocks.nodeTimelock, "blocks"); console.log("Refund timelock:", timelocks.refundTimelock, "blocks"); ``` # claimDeposit Source: https://docs.spark.money/api-reference/wallet/claim-deposit Claim a Bitcoin deposit from a single-use deposit address. Claims a Bitcoin deposit made to a single-use deposit address for the `SparkWallet`. ## Method Signature ```typescript theme={null} async claimDeposit(txid: string): Promise ``` ## Parameters The transaction ID of the Bitcoin deposit ## Returns The wallet leaves resulting from the claim operation ## Example ```typescript theme={null} const leaves = await wallet.claimDeposit("transaction-hash-here"); console.log("Deposit claimed, leaves:", leaves); ``` # claimHTLC Source: https://docs.spark.money/api-reference/wallet/claim-htlc Claim an HTLC by providing the preimage. Claims an HTLC by providing the preimage. ## Method Signature ```typescript theme={null} async claimHTLC(preimage: string): Promise ``` ## Parameters The 32-byte preimage as a hex string ## Returns The claimed transfer details ## Example ```typescript theme={null} const transfer = await wallet.claimHTLC("abc123..."); // 32 bytes hex console.log("HTLC claimed:", transfer.id); ``` # claimStaticDeposit Source: https://docs.spark.money/api-reference/wallet/claim-static-deposit Claim a deposit from a static address using a quote. Claims a deposit made to a static deposit address using quote information from `getClaimStaticDepositQuote` for the `SparkWallet`. ## Method Signature ```typescript theme={null} async claimStaticDeposit({ transactionId, creditAmountSats, sspSignature, outputIndex, }: { transactionId: string; creditAmountSats: number; sspSignature: string; outputIndex?: number; }): Promise ``` ## Parameters The Bitcoin transaction ID from the quote The amount of sats from the quote The SSP signature from the quote The index of the output ## Returns The claim result or null if the operation fails ## Example ```typescript theme={null} const quote = await wallet.getClaimStaticDepositQuote(txId); const claimResult = await wallet.claimStaticDeposit({ transactionId: txId, creditAmountSats: quote.creditAmountSats, sspSignature: quote.signature, // Note: quote returns 'signature', pass as 'sspSignature' }); console.log("Claim result:", claimResult); ``` # claimStaticDepositWithMaxFee Source: https://docs.spark.money/api-reference/wallet/claim-static-deposit-with-max-fee Claim a static deposit only if fee is within your limit. Gets a quote for a static deposit claim and automatically claims it if the fee is within the specified maximum. ## Method Signature ```typescript theme={null} async claimStaticDepositWithMaxFee({ transactionId, maxFee, outputIndex, }: { transactionId: string; maxFee: number; outputIndex?: number; }): Promise ``` ## Parameters The Bitcoin transaction ID of the deposit Maximum fee in satoshis you're willing to pay The output index (auto-detected if not provided) ## Returns The claim result, or null if the fee exceeds maxFee ## Example ```typescript theme={null} const result = await wallet.claimStaticDepositWithMaxFee({ transactionId: "abc123...", maxFee: 500 // Will only claim if fee <= 500 sats }); if (result) { console.log("Deposit claimed:", result); } else { console.log("Fee too high, deposit not claimed"); } ``` # cleanupConnections Source: https://docs.spark.money/api-reference/wallet/cleanup-connections Close active connections and abort streams. Cleans up connections and aborts any active streams for the `SparkWallet`. ## Method Signature ```typescript theme={null} async cleanupConnections(): Promise ``` ## Returns This method returns nothing and resolves to `void`. ## Example ```typescript theme={null} await wallet.cleanupConnections(); console.log("Connections cleaned up"); ``` # createHTLC Source: https://docs.spark.money/api-reference/wallet/create-htlc Create a Hash Time-Locked Contract for atomic swaps. Creates a Hash Time-Locked Contract (HTLC) for atomic swaps and conditional payments. ## Method Signature ```typescript theme={null} async createHTLC({ receiverSparkAddress, amountSats, preimage, expiryTime, }: { receiverSparkAddress: string; amountSats: number; preimage?: string; // Optional - auto-generated if not provided expiryTime: Date; }): Promise ``` ## Parameters The Spark address of the receiver The amount in satoshis to lock The preimage (32 bytes hex) for the HTLC hash lock. If not provided, a deterministic preimage is generated using [`getHTLCPreimage()`](/api-reference/wallet/get-htlc-preimage). The expiry time for the HTLC (must be in the future) ## Returns The HTLC transfer details ## Example ```typescript theme={null} // With auto-generated preimage (recommended) const htlc = await wallet.createHTLC({ receiverSparkAddress: "spark1...", amountSats: 10000, expiryTime: new Date(Date.now() + 3600000) // 1 hour from now }); console.log("HTLC created:", htlc.id); // With custom preimage const htlcWithPreimage = await wallet.createHTLC({ receiverSparkAddress: "spark1...", amountSats: 10000, preimage: "abc123def456...", // 32 bytes hex expiryTime: new Date(Date.now() + 3600000) }); ``` # createHTLCReceiverSpendTx Source: https://docs.spark.money/api-reference/wallet/create-htlc-receiver-spend-tx Create a signed transaction to spend an HTLC as the receiver. Creates a receiver spend transaction for an HTLC using the preimage. ## Method Signature ```typescript theme={null} async createHTLCReceiverSpendTx({ htlcTx, hash, hashLockDestinationPubkey, sequenceLockDestinationPubkey, preimage, satsPerVbyteFee, }: { htlcTx: string; hash: string; hashLockDestinationPubkey: string; sequenceLockDestinationPubkey: string; preimage: string; satsPerVbyteFee: number; }): Promise ``` ## Parameters The HTLC transaction hex The hash used in the HTLC Public key for the hash lock destination Public key for the sequence lock destination The preimage to unlock the HTLC Fee rate in sats per vbyte ## Returns The signed receiver spend transaction hex ## Example ```typescript theme={null} const txHex = await wallet.createHTLCReceiverSpendTx({ htlcTx: "02000000...", hash: "abc123...", hashLockDestinationPubkey: "02...", sequenceLockDestinationPubkey: "03...", preimage: "secret123...", satsPerVbyteFee: 10 }); console.log("Receiver spend tx:", txHex); ``` # createHTLCSenderSpendTx Source: https://docs.spark.money/api-reference/wallet/create-htlc-sender-spend-tx Create a signed transaction to reclaim an expired HTLC as sender. Creates a sender spend transaction for an HTLC after the timelock expires. ## Method Signature ```typescript theme={null} async createHTLCSenderSpendTx({ htlcTx, hash, hashLockDestinationPubkey, sequenceLockDestinationPubkey, satsPerVbyteFee, }: { htlcTx: string; hash: string; hashLockDestinationPubkey: string; sequenceLockDestinationPubkey: string; satsPerVbyteFee: number; }): Promise ``` ## Parameters The HTLC transaction hex The hash used in the HTLC Public key for the hash lock destination Public key for the sequence lock destination Fee rate in sats per vbyte ## Returns The signed sender spend transaction hex ## Example ```typescript theme={null} const txHex = await wallet.createHTLCSenderSpendTx({ htlcTx: "02000000...", hash: "abc123...", hashLockDestinationPubkey: "02...", sequenceLockDestinationPubkey: "03...", satsPerVbyteFee: 10 }); console.log("Sender spend tx:", txHex); ``` # createLightningHodlInvoice Source: https://docs.spark.money/api-reference/wallet/create-lightning-hodl-invoice Create a BOLT11 Lightning hold invoice with a custom payment hash. Creates a Lightning hold invoice (HODL invoice) with a custom payment hash. Hold invoices allow you to accept payments that are held in a pending state until you explicitly settle or cancel them. ## Method Signature ```typescript theme={null} type CreateLightningHodlInvoiceParams = { amountSats: number; paymentHash: string; memo?: string; expirySeconds?: number; includeSparkAddress?: boolean; includeSparkInvoice?: boolean; receiverIdentityPubkey?: string; descriptionHash?: string; }; async createLightningHodlInvoice(params: CreateLightningHodlInvoiceParams): Promise; ``` ## Parameters Amount in satoshis to receive. Must be a safe integer (less than 2^53). 64-character hex string representing the payment hash. You must know the preimage for this hash to settle the payment. Optional memo/description for the invoice (max 639 characters). Cannot be used together with `descriptionHash`. Invoice expiry time in seconds (default: 2,592,000 = 30 days) Whether to embed Spark address in the invoice fallback field. Mutually exclusive with `includeSparkInvoice`. **Note:** If the payer uses the fallback address instead of Lightning, the payment cannot be correlated to this invoice—it appears as a separate Spark transfer. Whether to include a Spark invoice in the invoice routing hints. Mutually exclusive with `includeSparkAddress`. When enabled, allows payers to seamlessly pay over Spark if they support it. 33-byte compressed identity pubkey for generating invoices for other Spark users SHA256 hash of the description for BOLT11 description\_hash field. Cannot be used together with `memo`. ## Returns The Lightning receive request object containing: * `id`: Unique identifier for the request * `invoice`: Invoice object with `encodedInvoice`, `paymentHash`, `amount`, etc. * `status`: Request status * `createdAt`, `updatedAt`: Timestamps Access the BOLT11 invoice string via `request.invoice.encodedInvoice`. **Hold Invoice Requirements**: You must know the preimage that corresponds to the `paymentHash` parameter. When a payer pays this invoice, the payment will be held until you provide the preimage to settle it. If you don't know the preimage, you won't be able to claim the funds. ## Examples ```typescript theme={null} import { createHash, randomBytes } from 'crypto'; // Generate preimage and payment hash const preimage = randomBytes(32); const paymentHash = createHash('sha256').update(preimage).digest('hex'); // Create hold invoice with custom payment hash const hodlInvoice = await wallet.createLightningHodlInvoice({ amountSats: 1000, paymentHash: paymentHash, memo: "Hold invoice for conditional payment", expirySeconds: 3600 // 1 hour }); console.log("Hold invoice:", hodlInvoice.invoice.encodedInvoice); console.log("Request ID:", hodlInvoice.id); console.log("Payment hash:", hodlInvoice.invoice.paymentHash); // Store preimage securely - you'll need it to settle the payment console.log("Preimage (keep secret):", preimage.toString('hex')); // Hold invoice with embedded Spark invoice const sparkHodlInvoice = await wallet.createLightningHodlInvoice({ amountSats: 1000, paymentHash: paymentHash, memo: "Hold invoice with Spark support", includeSparkInvoice: true }); ``` ## Use Cases Hold invoices are useful for: * **Conditional payments**: Accept payment but don't settle until certain conditions are met * **Atomic swaps**: Coordinate payments across different systems using the same preimage * **Escrow services**: Hold funds until both parties confirm the transaction * **Cross-chain operations**: Lock funds that depend on events in other blockchains # createLightningInvoice Source: https://docs.spark.money/api-reference/wallet/create-lightning-invoice Create a BOLT11 Lightning invoice to receive payments. Creates a Lightning invoice for receiving payments via the `SparkWallet`. ## Method Signature ```typescript theme={null} type CreateLightningInvoiceParams = { amountSats: number; memo?: string; expirySeconds?: number; includeSparkAddress?: boolean; includeSparkInvoice?: boolean; receiverIdentityPubkey?: string; descriptionHash?: string; }; async createLightningInvoice(params: CreateLightningInvoiceParams): Promise; ``` ## Parameters Amount in satoshis to receive. Use `0` for zero-amount invoices. Must be a safe integer (less than 2^53). Optional memo/description for the invoice (max 639 characters). Cannot be used together with `descriptionHash`. Invoice expiry time in seconds (default: 2,592,000 = 30 days) Whether to embed Spark address in the invoice fallback field. Mutually exclusive with `includeSparkInvoice`. **Note:** If the payer uses the fallback address instead of Lightning, the payment cannot be correlated to this invoice—it appears as a separate Spark transfer. Whether to include a Spark invoice in the invoice routing hints. Mutually exclusive with `includeSparkAddress`. When enabled, allows payers to seamlessly pay over Spark if they support it. 33-byte compressed identity pubkey for generating invoices for other Spark users SHA256 hash of the description for BOLT11 description\_hash field. Cannot be used together with `memo`. ## Returns The Lightning receive request object containing: * `id`: Unique identifier for the request * `invoice`: Invoice object with `encodedInvoice`, `paymentHash`, `amount`, etc. * `status`: Request status * `createdAt`, `updatedAt`: Timestamps Access the BOLT11 invoice string via `request.invoice.encodedInvoice`. ## Examples ```typescript theme={null} // Basic Lightning invoice const request = await wallet.createLightningInvoice({ amountSats: 1000, memo: "Payment for services", expirySeconds: 3600 // 1 hour }); console.log("Lightning invoice:", request.invoice.encodedInvoice); console.log("Request ID:", request.id); console.log("Payment hash:", request.invoice.paymentHash); // Invoice with embedded Spark invoice (for seamless Spark payments) const sparkEnabledRequest = await wallet.createLightningInvoice({ amountSats: 1000, memo: "Payment with Spark fallback", includeSparkInvoice: true // Enables Spark-to-Spark payments }); console.log("Invoice with Spark support:", sparkEnabledRequest.invoice.encodedInvoice); ``` # createSatsInvoice Source: https://docs.spark.money/api-reference/wallet/create-sats-invoice Create a Spark invoice to receive sats from another Spark wallet. Creates a Spark invoice for receiving a sats payment on Spark. ## Method Signature ```typescript theme={null} async createSatsInvoice({ amount, memo, senderSparkAddress, expiryTime, receiverIdentityPubkey, }: { amount?: number; memo?: string; senderSparkAddress?: SparkAddressFormat; expiryTime?: Date; receiverIdentityPubkey?: string; }): Promise ``` ## Parameters The amount of sats to receive (optional for open invoices). Max: 2,100,000,000,000,000 sats (21M BTC). Optional memo/description for the payment Optional Spark address of the expected sender Optional expiry time for the invoice Optional public key of the wallet receiving the invoice. If provided and different from the creator's identity public key, the created invoice will be unsigned. ## Returns A Spark address/invoice that can be paid by another Spark wallet ## Example ```typescript theme={null} // Create an invoice for 1000 sats const invoice = await wallet.createSatsInvoice({ amount: 1000, memo: "Payment for coffee" }); console.log("Spark invoice:", invoice); // Create an open invoice (no amount specified) const openInvoice = await wallet.createSatsInvoice({ memo: "Tip jar" }); ``` # createTokensInvoice Source: https://docs.spark.money/api-reference/wallet/create-tokens-invoice Create a Spark invoice to receive tokens. Creates a Spark invoice for receiving a tokens payment on Spark. ## Method Signature ```typescript theme={null} async createTokensInvoice({ amount, tokenIdentifier, memo, senderSparkAddress, expiryTime, }: { tokenIdentifier?: Bech32mTokenIdentifier; amount?: bigint; memo?: string; senderSparkAddress?: SparkAddressFormat; expiryTime?: Date; }): Promise ``` ## Parameters The Bech32m token identifier (e.g., `btkn1...`) The amount of tokens to receive. Max: 2^128 - 1. Optional memo/description for the payment Optional Spark address of the expected sender Optional expiry time for the invoice ## Returns A Spark address/invoice that can be paid by another Spark wallet ## Example ```typescript theme={null} const invoice = await wallet.createTokensInvoice({ tokenIdentifier: "btkn1...", amount: 1000n, memo: "Token payment" }); console.log("Token invoice:", invoice); ``` # EventEmitter Source: https://docs.spark.money/api-reference/wallet/event-emitter Listen for wallet events like transfers and deposits. `SparkWallet` extends `EventEmitter`, so it inherits the following methods for handling events. ## Available Events | Event | Callback Signature | Description | | ----------------------- | -------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------- | | `"transfer:claimed"` | `(transferId: string, updatedBalance: bigint) => void` | Emitted when an **incoming** transfer is claimed. Does NOT fire for outgoing Lightning payments. | | `"deposit:confirmed"` | `(depositId: string, updatedBalance: bigint) => void` | Emitted when a claimed L1 deposit is marked available (after the required confirmations, 3 by default). | | `"stream:connected"` | `() => void` | Emitted when the event stream connects | | `"stream:disconnected"` | `(reason: string) => void` | Emitted when the stream disconnects after max retries | | `"stream:reconnecting"` | `(attempt: number, maxAttempts: number, delayMs: number, error: string) => void` | Emitted when attempting to reconnect | The `updatedBalance` parameter is a `bigint` representing the wallet's new total balance in satoshis after the event. Events are only emitted for **incoming** funds (transfers received, deposits confirmed). For outgoing operations (Lightning sends, withdrawals), poll the status using `getLightningSendRequest()` or `getCoopExitRequest()`.
***
## on(event: string, listener: Function) Adds a listener for the specified event. ```typescript theme={null} on(event: keyof SparkWalletEvents, listener: Function): this ``` ## Parameters The event name to listen for The callback function to execute when the event is emitted ## Returns The SparkWallet instance for chaining ## Example ```typescript theme={null} wallet.on("transfer:claimed", (transferId, updatedBalance) => { console.log(`Transfer ${transferId} claimed. New balance: ${updatedBalance}`); }); ```
***
## once(event: string, listener: Function) Adds a one-time listener for the specified event. ```typescript theme={null} once(event: keyof SparkWalletEvents, listener: Function): this ``` ## Parameters The event name to listen for The callback function to execute when the event is emitted ## Returns The SparkWallet instance for chaining ## Example ```typescript theme={null} wallet.once("deposit:confirmed", (depositId, updatedBalance) => { console.log(`Deposit ${depositId} confirmed! New balance: ${updatedBalance}`); }); ```
***
## off(event: string, listener: Function) Removes the specified listener from the specified event. ```typescript theme={null} off(event: keyof SparkWalletEvents, listener: Function): this ``` ## Parameters The event name The callback function to remove ## Returns The SparkWallet instance for chaining ## Example ```typescript theme={null} const listener = (transferId) => console.log(`Transfer: ${transferId}`); wallet.on("transfer:claimed", listener); // Later, remove the listener wallet.off("transfer:claimed", listener); ``` # fulfillSparkInvoice Source: https://docs.spark.money/api-reference/wallet/fulfill-spark-invoice Pay one or more Spark invoices. Fulfills one or more Spark invoices by paying them. ## Method Signature ```typescript theme={null} async fulfillSparkInvoice( sparkInvoices: { invoice: SparkAddressFormat; amount?: bigint; }[] ): Promise ``` ## Parameters Array of invoices to fulfill: * `invoice`: The Spark invoice to pay (must use `spark1...` prefix; legacy `sp1...` invoices are not supported) * `amount`: Amount to pay (required for invoices without an encoded amount; if both are provided, the encoded amount takes precedence) ## Returns Response containing results for all invoice payment attempts ```typescript theme={null} interface FulfillSparkInvoiceResponse { // Successfully paid sats invoices satsTransactionSuccess: { invoice: SparkAddressFormat; transferResponse: WalletTransfer; }[]; // Failed sats invoice payments satsTransactionErrors: { invoice: SparkAddressFormat; error: Error; }[]; // Successfully paid token invoices tokenTransactionSuccess: { tokenIdentifier: Bech32mTokenIdentifier; invoices: SparkAddressFormat[]; txid: string; }[]; // Failed token invoice payments tokenTransactionErrors: { tokenIdentifier: Bech32mTokenIdentifier; invoices: SparkAddressFormat[]; error: Error; }[]; // Invoices that failed validation before payment invalidInvoices: { invoice: SparkAddressFormat; error: Error; }[]; } ``` ## Example ```typescript theme={null} // Pay a single invoice const result = await wallet.fulfillSparkInvoice([ { invoice: "spark1..." } ]); // Pay multiple invoices with amounts const batchResult = await wallet.fulfillSparkInvoice([ { invoice: invoiceWithNoAmount, amount: 1000n }, { invoice: invoiceWithEncodedAmount } ]); // Check results console.log("Successful:", result.satsTransactionSuccess); console.log("Errors:", result.satsTransactionErrors); ``` # getBalance Source: https://docs.spark.money/api-reference/wallet/get-balance Get wallet balance in sats and token balances with metadata. Gets the current balance of the `SparkWallet`, including Bitcoin balance and token balances. ## Method Signature ```typescript theme={null} async getBalance(): Promise<{ /** @deprecated Use satsBalance.available instead */ balance: bigint; satsBalance: { available: bigint; owned: bigint; incoming: bigint; }; tokenBalances: TokenBalanceMap; }> // TokenBalanceMap = Map interface UserTokenMetadata { rawTokenIdentifier: Uint8Array; // Binary token identifier used to encode the bech32m identifier tokenPublicKey: string; // Issuer's public key tokenName: string; tokenTicker: string; decimals: number; // Number of decimal places maxSupply: bigint; extraMetadata?: Uint8Array; // Arbitrary bytes set by the issuer } ``` ## Returns **Deprecated** — Use `satsBalance.available` instead. The wallet's immediately spendable balance in satoshis. Kept for backwards compatibility. Breakdown of the sats balance by status: * `available`: Immediately spendable satoshis * `owned`: All satoshis owned (available + locked in outgoing transfers/swaps) * `incoming`: Pending inbound transfers not yet claimed Map of Bech32m token identifiers to token balance and metadata objects. Each token balance contains: * `ownedBalance`: Total tokens owned (including those pending in outbound transfers) * `availableToSendBalance`: Tokens available to send (excludes pending outbound transfers) * `tokenMetadata`: Token metadata including name, ticker, decimals, etc. The `ownedBalance` represents all tokens you own, while `availableToSendBalance` excludes tokens that are locked in pending outbound transfers. Use `availableToSendBalance` to determine how many tokens can be sent. ## Example ```typescript theme={null} const { satsBalance, tokenBalances } = await wallet.getBalance(); console.log("Available:", satsBalance.available); console.log("Owned:", satsBalance.owned); console.log("Incoming:", satsBalance.incoming); // Iterate over token balances for (const [tokenId, info] of tokenBalances) { console.log(`Token ${tokenId}:`); console.log(` Owned: ${info.ownedBalance}`); console.log(` Available to send: ${info.availableToSendBalance}`); console.log(` Name: ${info.tokenMetadata.tokenName}`); console.log(` Ticker: ${info.tokenMetadata.tokenTicker}`); console.log(` Decimals: ${info.tokenMetadata.decimals}`); // Check for extra metadata if (info.tokenMetadata.extraMetadata) { console.log(` Extra metadata: ${info.tokenMetadata.extraMetadata.length} bytes`); } } ``` # getCachedBalance Source: https://docs.spark.money/api-reference/wallet/get-cached-balance Get wallet balance from the in-memory cache without network calls. Returns the wallet balance from the in-memory cache without making network calls for sats. The cache is kept up-to-date by the event stream (deposits, transfers, swaps). For guaranteed-fresh data, use [`getBalance()`](/api-reference/wallet/get-balance) instead. ## Method Signature ```typescript theme={null} async getCachedBalance(): Promise<{ /** @deprecated Use satsBalance.available instead */ balance: bigint; satsBalance: { available: bigint; owned: bigint; incoming: bigint; }; tokenBalances: TokenBalanceMap; }> ``` ## Returns **Deprecated** — Use `satsBalance.available` instead. The wallet's immediately spendable balance in satoshis. Kept for backwards compatibility. Breakdown of the sats balance by status (read from cache, no network call): * `available`: Immediately spendable satoshis * `owned`: All satoshis owned (available + locked in outgoing transfers/swaps) * `incoming`: Pending inbound transfers not yet claimed Map of Bech32m token identifiers to token balance and metadata objects. Token metadata may require a network call if not yet cached. `getCachedBalance()` reads sats data from the event-driven in-memory cache — no network call is made for sats. Use this for instant balance reads (e.g., in a UI). For a guaranteed-fresh balance, call [`getBalance()`](/api-reference/wallet/get-balance). ## Example ```typescript theme={null} const { satsBalance, tokenBalances } = await wallet.getCachedBalance(); console.log("Available (cached):", satsBalance.available); console.log("Owned:", satsBalance.owned); console.log("Incoming:", satsBalance.incoming); ``` # getClaimStaticDepositQuote Source: https://docs.spark.money/api-reference/wallet/get-claim-static-deposit-quote Get a quote with fee and SSP signature for claiming a static deposit. Gets a quote for claiming a deposit made to a static deposit address for the `SparkWallet`. ## Method Signature ```typescript theme={null} async getClaimStaticDepositQuote( transactionId: string, outputIndex?: number ): Promise ``` ## Parameters The Bitcoin transaction ID of the deposit transaction The index of the output (auto-detected if not provided) ## Returns Quote object containing: * `creditAmountSats`: The amount in satoshis to claim * `signature`: The SSP signature required for claiming ## Example ```typescript theme={null} const quote = await wallet.getClaimStaticDepositQuote("abc123..."); console.log("Credit amount:", quote.creditAmountSats); console.log("Signature:", quote.signature); ``` # getCoopExitRequest Source: https://docs.spark.money/api-reference/wallet/get-coop-exit-request Get the status of a withdrawal request by ID. Gets a cooperative exit request by ID for the `SparkWallet`. ## Method Signature ```typescript theme={null} async getCoopExitRequest(id: string): Promise ``` ## Parameters The withdrawal request ID returned by `withdraw()`. **Not the on-chain transaction ID.** Format: `SparkCoopExitRequest:xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx` ## Returns The cooperative exit request details or null if not found The `transfer` field in the response will be empty until the SSP has claimed the inbound Spark transfer. Poll until populated if you need the on-chain transaction details. ## Example ```typescript theme={null} // Use the ID returned from withdraw(), not the on-chain txid const exitRequest = await wallet.getCoopExitRequest(withdrawal.id); if (exitRequest) { console.log("Status:", exitRequest.status); console.log("On-chain txid:", exitRequest.coopExitTxid); } ``` # getHTLCPreimage Source: https://docs.spark.money/api-reference/wallet/get-htlc-preimage Generate a deterministic HTLC preimage from a transfer ID. Generates a deterministic HTLC preimage from a transfer ID using the wallet's keys. ## Method Signature ```typescript theme={null} async getHTLCPreimage(transferID: string): Promise ``` ## Parameters The transfer ID to generate a preimage for ## Returns The 32-byte preimage ## Example ```typescript theme={null} const preimage = await wallet.getHTLCPreimage("transfer-id"); console.log("Preimage:", bytesToHex(preimage)); ``` # getIdentityPublicKey Source: https://docs.spark.money/api-reference/wallet/get-identity-public-key Get the wallet's identity public key as hex string. Gets the identity public key of the `SparkWallet`. ## Method Signature ```typescript theme={null} async getIdentityPublicKey(): Promise ``` ## Returns The identity public key as a hex string ## Example ```typescript theme={null} const identityPubKey = await wallet.getIdentityPublicKey(); console.log("Identity Public Key:", identityPubKey); ``` # getLeaves Source: https://docs.spark.money/api-reference/wallet/get-leaves Get all wallet leaves (UTXOs) as TreeNode objects. Gets all available leaves (UTXOs) owned by the wallet. ## Method Signature ```typescript theme={null} async getLeaves(isBalanceCheck: boolean = false): Promise ``` ## Parameters If `true`, only queries the coordinator for faster response (default: `false`) ## Returns Array of tree nodes representing wallet leaves ## Example ```typescript theme={null} const leaves = await wallet.getLeaves(); console.log("Leaves:", leaves.length); console.log("Total value:", leaves.reduce((sum, l) => sum + l.value, 0)); ``` # getLightningReceiveRequest Source: https://docs.spark.money/api-reference/wallet/get-lightning-receive-request Check status of a Lightning invoice. Gets the status of a Lightning receive request (invoice) for the `SparkWallet`. ## Method Signature ```typescript theme={null} async getLightningReceiveRequest(id: string): Promise ``` ## Parameters The ID of the invoice to check ## Returns The Lightning receive request details or null if not found ## Example ```typescript theme={null} const receiveRequest = await wallet.getLightningReceiveRequest("request-id"); if (receiveRequest) { console.log("Invoice status:", receiveRequest.status); } ``` # getLightningSendFeeEstimate Source: https://docs.spark.money/api-reference/wallet/get-lightning-send-fee-estimate Estimate the fee for paying a Lightning invoice. Estimates the fee for sending a Lightning payment via the `SparkWallet`. ## Method Signature ```typescript theme={null} interface LightningSendFeeEstimateInput { encodedInvoice: string; amountSats?: number; } async getLightningSendFeeEstimate(params: LightningSendFeeEstimateInput): Promise ``` ## Parameters The BOLT11-encoded Lightning invoice Amount in satoshis to send (required for zero-amount invoices) ## Returns The estimated fee in satoshis ## Example ```typescript theme={null} const feeEstimate = await wallet.getLightningSendFeeEstimate({ encodedInvoice: "lnbc..." }); console.log("Estimated fee:", feeEstimate, "sats"); ``` # getLightningSendRequest Source: https://docs.spark.money/api-reference/wallet/get-lightning-send-request Check status of a Lightning payment. Gets the status of a Lightning send request for the `SparkWallet`. ## Method Signature ```typescript theme={null} async getLightningSendRequest(id: string): Promise ``` ## Parameters The ID of the Lightning send request to check ## Returns The Lightning send request details or null if not found ## Example ```typescript theme={null} const sendRequest = await wallet.getLightningSendRequest("request-id"); if (sendRequest) { console.log("Payment status:", sendRequest.status); } ``` # getSingleUseDepositAddress Source: https://docs.spark.money/api-reference/wallet/get-single-use-deposit-address Generate a one-time Bitcoin deposit address (P2TR). Generates a new single-use deposit address for receiving Bitcoin funds. Returns a Bitcoin taproot (P2TR) address (not a Spark address). **Single-use addresses are consumed after the first deposit.** Subsequent deposits to the same address are not supported and may be unrecoverable. Prefer [`getStaticDepositAddress()`](/api-reference/wallet/get-static-deposit-address) for reusable deposits. ## Method Signature ```typescript theme={null} async getSingleUseDepositAddress(): Promise ``` ## Returns A Bitcoin taproot (P2TR) address for depositing funds (`bc1p...` on mainnet, `bcrt1p...` on regtest/local, `tb1p...` on testnet/signet) ## Example ```typescript theme={null} const depositAddress = await wallet.getSingleUseDepositAddress(); console.log("Deposit Address:", depositAddress); ``` # getSparkAddress Source: https://docs.spark.money/api-reference/wallet/get-spark-address Get the wallet's Spark address for receiving payments. Gets the Spark Address of the `SparkWallet`. ## Method Signature ```typescript theme={null} async getSparkAddress(): Promise ``` ## Returns The Spark Address ## Example ```typescript theme={null} const sparkAddress = await wallet.getSparkAddress(); console.log("Spark Address:", sparkAddress); ``` # getStaticDepositAddress Source: https://docs.spark.money/api-reference/wallet/get-static-deposit-address Get a reusable Bitcoin deposit address for the wallet. Returns a reusable Bitcoin address for the `SparkWallet` that can be used to receive deposits multiple times. Currently only one static deposit address is supported per wallet. If a static deposit address already exists for this wallet, calling this method again will return the existing address (not create a new one). **Do not call this method concurrently.** Concurrent calls before an address exists can create multiple addresses. Always await the first call before making another. ## Method Signature ```typescript theme={null} async getStaticDepositAddress(): Promise ``` ## Returns A reusable Bitcoin address for depositing funds ## Example ```typescript theme={null} const staticAddress = await wallet.getStaticDepositAddress(); console.log("Static deposit address:", staticAddress); // Calling again returns the same address const sameAddress = await wallet.getStaticDepositAddress(); console.log(staticAddress === sameAddress); // true ``` # getSwapFeeEstimate Source: https://docs.spark.money/api-reference/wallet/get-swap-fee-estimate Estimate fees for a leaf swap operation. Gets the estimated fee for a swap of leaves for the `SparkWallet`. ## Method Signature ```typescript theme={null} async getSwapFeeEstimate(amountSats: number): Promise ``` ## Parameters The amount of sats to swap ## Returns The estimated fee for the swap ## Example ```typescript theme={null} const feeEstimate = await wallet.getSwapFeeEstimate(10000); console.log("Swap fee estimate:", feeEstimate); ``` # getTokenL1Address Source: https://docs.spark.money/api-reference/wallet/get-token-l1-address Get the L1 Bitcoin address derived from identity key for tokens. Gets the Layer 1 Bitcoin address derived from the wallet's identity key for token operations. ## Method Signature ```typescript theme={null} async getTokenL1Address(): Promise ``` ## Returns A P2WPKH Bitcoin address derived from the wallet's identity public key ## Example ```typescript theme={null} const l1Address = await wallet.getTokenL1Address(); console.log("Token L1 address:", l1Address); ``` # getTokenOutputStats Source: https://docs.spark.money/api-reference/wallet/get-token-output-stats Get token output statistics including count and total amount. Gets statistics about token outputs in the wallet. ## Method Signature ```typescript theme={null} async getTokenOutputStats( tokenIdentifier: Bech32mTokenIdentifier ): Promise<{ outputCount: number; totalAmount: bigint; }> ``` ## Parameters Token identifier to get stats for. ## Returns Number of token outputs Total amount across all outputs ## Example ```typescript theme={null} // Get stats for a specific token const stats = await wallet.getTokenOutputStats("btkn1..."); console.log("Output count:", stats.outputCount); console.log("Total amount:", stats.totalAmount); ``` # getTransfer Source: https://docs.spark.money/api-reference/wallet/get-transfer Get a specific transfer by ID. Gets a specific transfer by ID that the wallet is a participant of. This returns Spark transfer data only. For Lightning-related transfer information, use [`getTransferFromSsp()`](/api-reference/wallet/get-transfer-from-ssp). ## Method Signature ```typescript theme={null} async getTransfer(id: string): Promise ``` ## Parameters The transfer ID to query ## Returns The transfer details, or undefined if not found ### WalletTransfer Fields | Field | Type | Description | | --------------------------- | --------------------- | ----------------------------------------------------------------------------------------- | | `id` | `string` | Unique transfer identifier | | `status` | `string` | Transfer status (`TRANSFER_STATUS_SENDER_KEY_TWEAKED`, `TRANSFER_STATUS_COMPLETED`, etc.) | | `totalValue` | `number` | Amount in satoshis | | `senderIdentityPublicKey` | `string` | Sender's identity public key | | `receiverIdentityPublicKey` | `string` | Receiver's identity public key | | `transferDirection` | `string` | `INCOMING` or `OUTGOING` relative to this wallet | | `createdTime` | `Date \| undefined` | When the transfer was created | | `updatedTime` | `Date \| undefined` | When the transfer was last updated | | `expiryTime` | `Date \| undefined` | When the transfer expires | | `sparkInvoice` | `string \| undefined` | The Spark invoice associated with this transfer, if any | ## Example ```typescript theme={null} const transfer = await wallet.getTransfer("transfer-id-here"); if (transfer) { console.log("Transfer status:", transfer.status); console.log("Amount:", transfer.totalValue); } ``` # getTransferFromSsp Source: https://docs.spark.money/api-reference/wallet/get-transfer-from-ssp Get transfer details from SSP, including Lightning info. Gets a transfer from the SSP (Spark Service Provider), including Lightning-related transfer information. ## Method Signature ```typescript theme={null} async getTransferFromSsp(id: string): Promise ``` ## Parameters The transfer ID to query ## Returns The transfer with user request details, or undefined if not found ## Example ```typescript theme={null} const transfer = await wallet.getTransferFromSsp("transfer-id"); if (transfer) { console.log("Transfer:", transfer); console.log("User request:", transfer.userRequest); } ``` # getTransfers Source: https://docs.spark.money/api-reference/wallet/get-transfers Get transfer history with pagination and date filters. Gets all transfers for the `SparkWallet`. `getTransfers()` includes Spark transfers, Lightning sends/receives, and cooperative exits (L1 withdrawals). For token transaction details (e.g., sender address), use [`queryTokenTransactionsWithFilters()`](/api-reference/wallet/query-token-transactions-with-filters). ## Method Signature ```typescript theme={null} async getTransfers( limit: number = 20, offset: number = 0, createdAfter?: Date, createdBefore?: Date ): Promise<{ transfers: WalletTransfer[]; offset: number; }> ``` ## Parameters Maximum number of transfers to return (default: 20) Offset for pagination (default: 0) Only return transfers created after this date (mutually exclusive with `createdBefore`) Only return transfers created before this date (mutually exclusive with `createdAfter`) `createdAfter` and `createdBefore` are mutually exclusive. Providing both will throw an error. ## Returns Array of transfer objects The offset used for this request ## Example ```typescript theme={null} // Basic pagination const transfers = await wallet.getTransfers(10, 0); console.log("Transfers:", transfers.transfers); console.log("Offset:", transfers.offset); // Get transfers from the last 24 hours const yesterday = new Date(Date.now() - 24 * 60 * 60 * 1000); const recentTransfers = await wallet.getTransfers(50, 0, yesterday); // Get transfers before a specific date const cutoffDate = new Date("2024-01-01"); const oldTransfers = await wallet.getTransfers(50, 0, undefined, cutoffDate); ``` # getUnusedDepositAddresses Source: https://docs.spark.money/api-reference/wallet/get-unused-deposit-addresses Get all unused single-use deposit addresses. Gets all unused single-use deposit addresses for the `SparkWallet`. ## Method Signature ```typescript theme={null} async getUnusedDepositAddresses(): Promise ``` ## Returns Array of unused Bitcoin deposit addresses ## Example ```typescript theme={null} const unusedAddresses = await wallet.getUnusedDepositAddresses(); console.log("Unused deposit addresses:", unusedAddresses); ``` # getUserRequests Source: https://docs.spark.money/api-reference/wallet/get-user-requests Get user requests from the SSP. Gets user requests from the SSP. ## Method Signature ```typescript theme={null} async getUserRequests( params?: GetUserRequestsParams ): Promise ``` ## Parameters Optional filtering parameters ## Returns User requests connection, or null if not available ## Example ```typescript theme={null} const requests = await wallet.getUserRequests(); if (requests) { console.log("User requests:", requests); } ``` # getUtxosForDepositAddress Source: https://docs.spark.money/api-reference/wallet/get-utxos-for-deposit-address Get confirmed UTXOs for a deposit address. Returns confirmed UTXOs for a Spark-generated Bitcoin deposit address. ## Method Signature ```typescript theme={null} async getUtxosForDepositAddress( depositAddress: string, limit?: number, offset?: number, excludeClaimed?: boolean ): Promise<{ txid: string; vout: number }[]> ``` ## Parameters The Bitcoin deposit address to query (from `getSingleUseDepositAddress()` or `getStaticDepositAddress()`) Maximum number of UTXOs to return (default: 100) Pagination offset (default: 0) Whether to exclude already claimed UTXOs (default: false) ## Returns Array of UTXO objects with `txid` and `vout` ## Example ```typescript theme={null} const utxos = await wallet.getUtxosForDepositAddress( "bcrt1p...", 100, 0, true ); console.log("UTXOs:", utxos); ``` # getWalletSettings Source: https://docs.spark.money/api-reference/wallet/get-wallet-settings Get current wallet settings including privacy mode. Gets the current wallet settings. ## Method Signature ```typescript theme={null} async getWalletSettings(): Promise ``` ## Returns Current wallet settings, or undefined if not available ## Example ```typescript theme={null} const settings = await wallet.getWalletSettings(); if (settings) { console.log("Privacy enabled:", settings.privateEnabled); } ``` # getWithdrawalFeeQuote Source: https://docs.spark.money/api-reference/wallet/get-withdrawal-fee-quote Get fee quote for on-chain withdrawal at fast, medium, or slow speeds. Gets a fee quote for a cooperative exit (on-chain withdrawal) for the `SparkWallet`. The quote includes options for different speeds and an expiry time. ## Method Signature ```typescript theme={null} async getWithdrawalFeeQuote({ amountSats, withdrawalAddress, }: { amountSats: number; withdrawalAddress: string; }): Promise ``` ## Parameters The amount in satoshis to withdraw The Bitcoin address where the funds should be sent ## Returns A fee quote for the withdrawal, or null if not available ### CoopExitFeeQuote Fields | Field | Type | Description | | ---------------------- | ---------------- | ---------------------------------------------- | | `id` | `string` | Quote ID (use as `feeQuoteId` in `withdraw()`) | | `expiresAt` | `string` | When this quote expires | | `userFeeFast` | `CurrencyAmount` | Service fee for fast exit | | `userFeeMedium` | `CurrencyAmount` | Service fee for medium exit | | `userFeeSlow` | `CurrencyAmount` | Service fee for slow exit | | `l1BroadcastFeeFast` | `CurrencyAmount` | L1 tx fee for fast exit | | `l1BroadcastFeeMedium` | `CurrencyAmount` | L1 tx fee for medium exit | | `l1BroadcastFeeSlow` | `CurrencyAmount` | L1 tx fee for slow exit | `CurrencyAmount` has `originalValue` (number in satoshis) and `originalUnit` fields. ## Example ```typescript theme={null} const feeQuote = await wallet.getWithdrawalFeeQuote({ amountSats: 17000, withdrawalAddress: "bcrt1p..." }); if (feeQuote) { console.log("Quote expires:", feeQuote.expiresAt); console.log("Fast fee:", feeQuote.userFeeFast.originalValue + feeQuote.l1BroadcastFeeFast.originalValue, "sats"); console.log("Medium fee:", feeQuote.userFeeMedium.originalValue + feeQuote.l1BroadcastFeeMedium.originalValue, "sats"); console.log("Slow fee:", feeQuote.userFeeSlow.originalValue + feeQuote.l1BroadcastFeeSlow.originalValue, "sats"); } ``` # initialize Source: https://docs.spark.money/api-reference/wallet/initialize Create or restore a SparkWallet instance from mnemonic or seed. Creates and initializes a new `SparkWallet` instance. ## Method Signature ```typescript theme={null} interface SparkWalletProps { mnemonicOrSeed?: Uint8Array | string; accountNumber?: number; signer?: SparkSigner; options?: ConfigOptions; } static async initialize(props: SparkWalletProps): Promise<{ wallet: SparkWallet; mnemonic?: string; }> ``` ## Parameters BIP-39 mnemonic phrase or raw seed Number used to generate multiple identity keys from the same mnemonic Custom signer implementation for advanced use cases Wallet configuration options including network selection ```typescript theme={null} interface ConfigOptions { // e.g. "MAINNET" | "TESTNET" | "SIGNET" | "REGTEST" | "LOCAL" network?: NetworkType; // Required for most use cases // Advanced options (rarely needed): signingOperators?: Readonly>; coordinatorIdentifier?: string; frostSignerAddress?: string; threshold?: number; tokenSignatures?: "ECDSA" | "SCHNORR"; tokenValidityDurationSeconds?: number; tokenOutputLockExpiryMs?: number; tokenTransactionVersion?: "V2" | "V3"; electrsUrl?: string; sspClientOptions?: SspClientOptions; expectedWithdrawBondSats?: number; expectedWithdrawRelativeBlockLocktime?: number; signerWithPreExistingKeys?: boolean; console?: { otel?: boolean }; optimizationOptions?: { auto?: boolean; // Auto-optimize leaves on sync (default: true) multiplicity?: number; // Optimization level 0-5 (default: 1) }; tokenOptimizationOptions?: { enabled?: boolean; // Enable token output consolidation (default: true) intervalMs?: number; // Optimization interval (default: 300000 = 5 min) minOutputsThreshold?: number; // Min outputs before optimizing (default: 50) }; events?: Partial; // Pre-register event handlers at init } type NetworkType = "MAINNET" | "TESTNET" | "SIGNET" | "REGTEST" | "LOCAL"; type SigningOperator = { id: number; identifier: string; address: string; identityPublicKey: string; }; type SspClientOptions = { baseUrl: string; identityPublicKey: string; schemaEndpoint?: string; }; ``` ## Leaf Optimization Leaf optimization pre-arranges your wallet's internal structure to enable faster transfers. Without optimization, transfers may require a swap with the SSP (Spark Service Provider), adding latency. With optimization, transfers complete in \~5 seconds. ### How It Works Spark creates **power-of-2 denomination leaves** (1, 2, 4, 8, 16... sats). With one leaf of each denomination, you can transfer any amount without needing an SSP swap. With multiple leaves of each denomination, you can make multiple transfers without swapping. ### Multiplicity Levels The `multiplicity` setting controls how aggressively to optimize: | Level | Behavior | Use Case | | ------- | -------------------------------- | -------------------------------------------- | | **0** | No optimization | Testing only | | **1** | 1 leaf per denomination | Likely 1 fast transfer before needing a swap | | **2-4** | Multiple leaves per denomination | Multiple fast transfers | | **5** | Maximum optimization | Likely 5+ fast transfers without any swaps | ### The Tradeoff Higher multiplicity = faster transfers, but smaller individual leaves. Leaves under **16,348 sats cannot be unilaterally exited** (fees would exceed value). If unilateral exit capability is critical for your users, use a lower multiplicity or larger balances. For most consumer wallets, fast transfer speeds for 100% of users outweighs unilateral exit costs for a small fraction of users. ### Auto vs Manual Mode **Auto mode** (`auto: true`, default): * Optimization runs automatically in the background after sync * Swaps with SSP when leaves are too far from optimal * Transfers wait for optimization to complete before sending * Best for most applications **Manual mode** (`auto: false`): * You control exactly when optimization runs via [`optimizeLeaves()`](/api-reference/wallet/optimize-leaves) * More aggressive optimization (skips the "is it needed?" check) * Use when you want maximum optimization regardless of current state ### Configuration Examples ```typescript theme={null} // Default behavior - auto optimization with multiplicity 1 const { wallet } = await SparkWallet.initialize({ options: { network: "MAINNET" } }); // Fast transfers for consumer wallet const { wallet } = await SparkWallet.initialize({ options: { network: "MAINNET", optimizationOptions: { auto: true, multiplicity: 5 } } }); // Manual control for maximum optimization const { wallet } = await SparkWallet.initialize({ options: { network: "MAINNET", optimizationOptions: { auto: false, multiplicity: 5 } } }); // Then call wallet.optimizeLeaves() explicitly when needed ``` **Safe to pass on every init.** Pass `optimizationOptions` every time you initialize (e.g., on app reopen). The SDK only runs optimization if needed and does nothing on wallets with no balance. ## Returns The initialized SparkWallet instance The mnemonic if one was generated (undefined for raw seed) ## Example ```typescript Create New Wallet theme={null} import { SparkWallet } from "@buildonspark/spark-sdk"; // Create a new wallet const { wallet, mnemonic } = await SparkWallet.initialize({ options: { network: "REGTEST" } // or "MAINNET" }); console.log("Wallet initialized:", wallet); console.log("Generated mnemonic:", mnemonic); ``` ```typescript Import Existing Wallet theme={null} import { SparkWallet } from "@buildonspark/spark-sdk"; // Import wallet from existing mnemonic const { wallet } = await SparkWallet.initialize({ mnemonicOrSeed: "abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon about", accountNumber: 0, // Optional: specify account index options: { network: "REGTEST" } }); console.log("Wallet restored from mnemonic"); ``` ## Multiple SDK Instances When running multiple wallet instances (e.g., service worker + popup in a browser extension): Multiple instances are **safe** but may cause temporary claim failures. The SDK handles this automatically—failed claims retry and succeed on subsequent attempts. **Best practices:** * Avoid calling `getStaticDepositAddress()` concurrently—this can create duplicate addresses * Let one instance handle background claiming if possible * Failed claims due to concurrent access are automatically recoverable ## System Time Requirements The SDK uses your device's system time for expiry calculations. **Your device clock must be within 2 minutes of actual time**, or operations will fail with "invalid expiry\_time" errors. If users report this error, ask them to sync their device clock. # isOptimizationInProgress Source: https://docs.spark.money/api-reference/wallet/is-optimization-in-progress Check if leaf optimization is currently running. Checks if a leaf optimization operation is currently in progress. ## Method Signature ```typescript theme={null} async isOptimizationInProgress(): Promise ``` ## Returns `true` if optimization is running, `false` otherwise ## Example ```typescript theme={null} if (await wallet.isOptimizationInProgress()) { console.log("Optimization is running..."); } else { console.log("No optimization in progress"); } ``` # isTokenOptimizationInProgress Source: https://docs.spark.money/api-reference/wallet/is-token-optimization-in-progress Check if token output optimization is currently running. Checks if a token output optimization operation is currently in progress. ## Method Signature ```typescript theme={null} async isTokenOptimizationInProgress(): Promise ``` ## Returns `true` if token optimization is running, `false` otherwise ## Example ```typescript theme={null} if (await wallet.isTokenOptimizationInProgress()) { console.log("Token optimization is running..."); } else { console.log("No token optimization in progress"); } ``` # optimizeLeaves Source: https://docs.spark.money/api-reference/wallet/optimize-leaves Manually optimize leaf structure for faster transfers. Manually optimizes wallet leaf structure by consolidating or splitting leaves into power-of-2 denominations. This is an async generator that yields progress updates and can be interrupted between steps. For background on leaf optimization concepts, multiplicity levels, and tradeoffs, see [Leaf Optimization](/api-reference/wallet/initialize#leaf-optimization) in the initialize docs. ## When to Use Use this method when you've configured **manual mode** (`auto: false`) in your initialization options. In manual mode, the SDK won't optimize automatically—you control exactly when optimization runs. ```typescript theme={null} // Initialize with manual mode const { wallet } = await SparkWallet.initialize({ options: { network: "MAINNET", optimizationOptions: { auto: false, multiplicity: 5 } } }); // Then call optimizeLeaves() explicitly for await (const progress of wallet.optimizeLeaves()) { console.log(`Optimizing: ${progress.step}/${progress.total}`); } ``` In **auto mode** (default), the SDK handles optimization automatically. You typically don't need to call this method directly. ## Method Signature ```typescript theme={null} async *optimizeLeaves( multiplicity?: number ): AsyncGenerator<{ step: number; total: number; controller: AbortController; }> ``` ## Parameters Optimization multiplicity (0-5). Defaults to the value set in `optimizationOptions` during initialization. ## Yields Progress object containing: * `step`: Current step number * `total`: Total number of steps * `controller`: AbortController to cancel optimization between steps ## Interrupting Optimization The abort controller lets you pause optimization between steps—useful when the user wants to send a payment mid-optimization. ```typescript theme={null} let optimizeGenerator = wallet.optimizeLeaves(); async function runOptimization() { for await (const progress of optimizeGenerator) { console.log(`Step ${progress.step} of ${progress.total}`); // User wants to send payment - abort optimization if (userWantsToSend) { progress.controller.abort(); break; } } } // Start optimization runOptimization(); // Later: user triggers a send userWantsToSend = true; await wallet.transfer({ ... }); // Resume optimization after the transfer runOptimization(); ``` Optimization runs multiple swap operations with the SSP. If you abort mid-optimization, the wallet remains in a valid state but may not be fully optimized. You can resume optimization later. ## Example: Basic Usage ```typescript theme={null} for await (const progress of wallet.optimizeLeaves()) { console.log(`Step ${progress.step} of ${progress.total}`); } console.log("Optimization complete"); ``` ## Example: With Progress UI ```typescript theme={null} async function optimizeWithProgress() { const progressBar = showProgressBar(); try { for await (const { step, total } of wallet.optimizeLeaves(5)) { progressBar.update(step / total * 100); } progressBar.complete(); } catch (error) { progressBar.error("Optimization failed"); throw error; } } ``` # optimizeTokenOutputs Source: https://docs.spark.money/api-reference/wallet/optimize-token-outputs Consolidate token outputs for improved performance. Optimizes token outputs by consolidating them when there are more than the configured threshold. ## Method Signature ```typescript theme={null} async optimizeTokenOutputs(): Promise ``` ## Returns This method returns nothing and resolves to `void`. ## Example ```typescript theme={null} await wallet.optimizeTokenOutputs(); console.log("Token outputs optimized"); ``` # payLightningInvoice Source: https://docs.spark.money/api-reference/wallet/pay-lightning-invoice Pay a BOLT11 Lightning invoice. Pays a Lightning invoice via the `SparkWallet`. ## Method Signature ```typescript theme={null} interface PayLightningInvoiceParams { invoice: string; maxFeeSats: number; preferSpark?: boolean; amountSatsToSend?: number; idempotencyKey?: string; } async payLightningInvoice(params: PayLightningInvoiceParams): Promise ``` ## Parameters The BOLT11-encoded Lightning invoice to pay Maximum fee in satoshis to pay for the invoice When `true`, initiate a Spark transfer if a valid Spark address is found in the invoice (default: `false`) Amount in satoshis to send for zero-amount invoices Optional client-provided idempotency key for deduplication. Multiple requests with the same key will return the same result instead of creating duplicate payments or returning errors. Use this to prevent duplicate transactions when retrying failed requests. ## Returns The Lightning payment request details, or a `WalletTransfer` if `preferSpark` is `true` and a valid Spark address was found in the invoice The payment preimage is not returned immediately. To retrieve the preimage after payment completes, call [`getLightningSendRequest(id)`](/api-reference/wallet/get-lightning-send-request) with the returned request ID. When `preferSpark: true` and the invoice contains a valid Spark fallback address, the method returns a `WalletTransfer` instead of `LightningSendRequest`. If no valid Spark address is found, it falls back to Lightning. ## Examples ```typescript theme={null} // Pay a regular invoice via Lightning const payment_response = await wallet.payLightningInvoice({ invoice: "lnbc100n...", // Regular Lightning invoice with amount maxFeeSats: 5, }); console.log("Payment Response:", payment_response); // Pay a zero-amount invoice const zeroAmountPayment = await wallet.payLightningInvoice({ invoice: "lnbc...", // Zero-amount Lightning invoice maxFeeSats: 5, amountSatsToSend: 1000, // Specify amount for zero-amount invoice }); console.log("Zero-amount Payment Response:", zeroAmountPayment); // Prefer Spark transfer if invoice has Spark fallback address const sparkPreferred = await wallet.payLightningInvoice({ invoice: "lnbc100n...", // Invoice with Spark fallback maxFeeSats: 5, preferSpark: true, // Will use Spark transfer if available }); // Returns WalletTransfer if Spark used, LightningSendRequest otherwise // Use idempotency key to prevent duplicate payments const idempotentPayment = await wallet.payLightningInvoice({ invoice: "lnbc100n...", maxFeeSats: 5, idempotencyKey: "unique-payment-id-123", // Prevents duplicate if retried }); // Safe to retry with same key - won't create duplicate payment ``` # queryHTLC Source: https://docs.spark.money/api-reference/wallet/query-htlc Query HTLCs by payment hash, status, or transfer ID. Queries HTLCs with flexible filtering options. ## Method Signature ```typescript theme={null} async queryHTLC({ paymentHashes, status, transferIds, matchRole, limit, offset, }: { paymentHashes?: string[]; status?: PreimageRequestStatus; transferIds?: string[]; matchRole?: PreimageRequestRole; limit?: number; offset?: number; }): Promise ``` ## Parameters Array of payment hashes to filter by Filter by HTLC status Array of transfer IDs to filter by Filter by role (default: `PREIMAGE_REQUEST_ROLE_RECEIVER`) Maximum results to return (1-100, default: 100). Values outside this range throw `SparkValidationError`. Pagination offset (default: 0). Must be non-negative. ## Returns HTLC query results ## Example ```typescript theme={null} const htlcs = await wallet.queryHTLC({ limit: 50, offset: 0 }); console.log("HTLCs:", htlcs); ``` # querySparkInvoices Source: https://docs.spark.money/api-reference/wallet/query-spark-invoices Check the status of Spark invoices. Queries the status of Spark invoices. ## Method Signature ```typescript theme={null} async querySparkInvoices( invoices: string[] ): Promise ``` ## Parameters Array of Spark invoice strings to query ## Returns Response containing invoice status information ## Example ```typescript theme={null} const status = await wallet.querySparkInvoices([ "spark1...", "spark1..." ]); console.log("Invoice statuses:", status); ``` # queryStaticDepositAddresses Source: https://docs.spark.money/api-reference/wallet/query-static-deposit-addresses List all static deposit addresses for this wallet. Queries all static deposit addresses associated with the `SparkWallet`. ## Method Signature ```typescript theme={null} async queryStaticDepositAddresses(): Promise ``` ## Returns Array of static deposit Bitcoin addresses ## Example ```typescript theme={null} const addresses = await wallet.queryStaticDepositAddresses(); console.log("Static deposit addresses:", addresses); ``` # queryTokenTransactionsByTxHashes Source: https://docs.spark.money/api-reference/wallet/query-token-transactions-by-tx-hashes Query specific token transactions by transaction hash. Retrieves specific token transactions by hash for the `SparkWallet`. ## Method Signature ```typescript theme={null} async queryTokenTransactionsByTxHashes( tokenTransactionHashes: string[] ): Promise ``` ## Parameters Array of token transaction hashes (must contain at least one hash) ## Returns Token transactions response object Transactions matching the requested hashes ## Example ```typescript theme={null} const response = await wallet.queryTokenTransactionsByTxHashes([ "6f0f...", "78ab...", ]); for (const tx of response.tokenTransactionsWithStatus) { console.log(tx.status, tx.tokenTransactionHash); } ``` # queryTokenTransactionsWithFilters Source: https://docs.spark.money/api-reference/wallet/query-token-transactions-with-filters Query token transaction history with filter-based, cursor pagination. Queries token transactions with filters and cursor-based pagination for the `SparkWallet`. ## Method Signature ```typescript theme={null} async queryTokenTransactionsWithFilters({ sparkAddresses, issuerPublicKeys, tokenIdentifiers, outputIds, pageSize, cursor, direction, }: { sparkAddresses?: string[]; issuerPublicKeys?: string[]; tokenIdentifiers?: string[]; outputIds?: string[]; pageSize?: number; cursor?: string; direction?: "NEXT" | "PREVIOUS"; }): Promise ``` ## Parameters Request constraints are combined using an **AND** relation by the underlying API. Array of Spark addresses to filter by Array of issuer public keys to filter by Array of Bech32m token identifiers to filter by Array of output IDs to filter by Page size (defaults to `50`) Cursor returned from a previous response (`pageResponse.nextCursor` or `pageResponse.prevCursor`) Pagination direction (defaults to `"NEXT"`) ## Returns Token transactions response object Matching token transactions for the requested page Pagination metadata including `nextCursor` / `prevCursor` ## Example ```typescript theme={null} const firstPage = await wallet.queryTokenTransactionsWithFilters({ tokenIdentifiers: ["btkn1..."], pageSize: 25, direction: "NEXT", }); const nextCursor = firstPage.pageResponse?.nextCursor; if (nextCursor) { const secondPage = await wallet.queryTokenTransactionsWithFilters({ tokenIdentifiers: ["btkn1..."], pageSize: 25, cursor: nextCursor, direction: "NEXT", }); console.log(secondPage.tokenTransactionsWithStatus.length); } ``` # refundAndBroadcastStaticDeposit Source: https://docs.spark.money/api-reference/wallet/refund-and-broadcast-static-deposit Refund a static deposit and broadcast to Bitcoin network. Refunds a static deposit and broadcasts the transaction to the Bitcoin network. ## Method Signature ```typescript theme={null} async refundAndBroadcastStaticDeposit({ depositTransactionId, outputIndex, destinationAddress, satsPerVbyteFee, }: { depositTransactionId: string; outputIndex?: number; destinationAddress: string; satsPerVbyteFee?: number; }): Promise ``` ## Parameters The transaction ID of the original deposit The output index (auto-detected if not provided) The Bitcoin address to send the refund to The fee rate in sats per vbyte ## Returns The transaction ID of the broadcast refund transaction ## Example ```typescript theme={null} const txid = await wallet.refundAndBroadcastStaticDeposit({ depositTransactionId: "abc123...", destinationAddress: "bcrt1p...", satsPerVbyteFee: 10 }); console.log("Refund broadcast, txid:", txid); ``` # refundStaticDeposit Source: https://docs.spark.money/api-reference/wallet/refund-static-deposit Create a refund transaction for a static deposit (returns tx hex). Refunds a deposit made to a static deposit address back to a specified Bitcoin address for the `SparkWallet`. ## Method Signature ```typescript theme={null} async refundStaticDeposit({ depositTransactionId, outputIndex, destinationAddress, fee, satsPerVbyteFee, }: { depositTransactionId: string; outputIndex?: number; destinationAddress: string; fee?: number; // Deprecated: use satsPerVbyteFee satsPerVbyteFee?: number; }): Promise ``` ## Parameters The transaction ID of the original deposit The index of the output (auto-detected if not provided) The Bitcoin address to send the refund to Deprecated. Total fee in sats for the refund transaction. Use `satsPerVbyteFee` instead. The fee rate in sats per vbyte (must be less than 150) ## Returns The refund transaction as a hex string that needs to be broadcast ## Example ```typescript theme={null} const txHex = await wallet.refundStaticDeposit({ depositTransactionId: "abc123...", destinationAddress: "bcrt1p...", satsPerVbyteFee: 10 }); console.log("Refund transaction:", txHex); ``` # setPrivacyEnabled Source: https://docs.spark.money/api-reference/wallet/set-privacy-enabled Enable or disable privacy mode for the wallet. Enables or disables privacy mode for the wallet. ## Method Signature ```typescript theme={null} async setPrivacyEnabled(privacyEnabled: boolean): Promise ``` ## Parameters Whether to enable privacy mode ## Returns Updated wallet settings, or undefined if update failed ## Example ```typescript theme={null} // Enable privacy mode const settings = await wallet.setPrivacyEnabled(true); console.log("Privacy enabled:", settings?.privateEnabled); // Disable privacy mode await wallet.setPrivacyEnabled(false); ``` # signMessageWithIdentityKey Source: https://docs.spark.money/api-reference/wallet/sign-message-with-identity-key Sign a message with the wallet's identity key. Signs a message with the wallet's identity key. ## Method Signature ```typescript theme={null} async signMessageWithIdentityKey( message: string, compact?: boolean ): Promise ``` ## Parameters The message to sign Whether to use compact encoding. If `false`, uses DER encoding. ## Returns The signature as a hex string ## Example ```typescript theme={null} const signature = await wallet.signMessageWithIdentityKey("Hello, Spark!"); console.log("Signature:", signature); // Compact encoding const compactSig = await wallet.signMessageWithIdentityKey("Hello", true); ``` # signTransaction Source: https://docs.spark.money/api-reference/wallet/sign-transaction Sign a Bitcoin transaction with wallet keys. Signs a Bitcoin transaction with wallet keys. ## Method Signature ```typescript theme={null} async signTransaction( txHex: string, keyType?: string ): Promise ``` ## Parameters The transaction hex to sign Key type to use: `"identity"`, `"deposit"`, or `"auto-detect"` (default: `"auto-detect"`) ## Returns The signed transaction hex ## Example ```typescript theme={null} // Auto-detect key type const signedTx = await wallet.signTransaction("02000000..."); // Use specific key type const signedWithIdentity = await wallet.signTransaction( "02000000...", "identity" ); console.log("Signed transaction:", signedWithIdentity); ``` # transfer Source: https://docs.spark.money/api-reference/wallet/transfer Send Bitcoin to another Spark address. Transfers Bitcoin to another `SparkWallet`. ## Method Signature ```typescript theme={null} async transfer({ receiverSparkAddress, amountSats, }: { receiverSparkAddress: string; amountSats: number; }): Promise ``` ## Parameters The recipient's Spark Address The amount in satoshis to transfer. Must be positive and less than 2^53 (JavaScript safe integer limit). ## Returns The completed transfer details ## Example ```typescript theme={null} const transfer = await wallet.transfer({ receiverSparkAddress: "spark1...", amountSats: 1000 }); console.log("Transfer completed:", transfer); ``` Do not pass a Spark invoice (address with encoded payment details) to this method. Use [`fulfillSparkInvoice()`](/api-reference/wallet/fulfill-spark-invoice) instead. # transferTokens Source: https://docs.spark.money/api-reference/wallet/transfer-tokens Send tokens to another Spark address. Transfers tokens to another `SparkWallet`. ## Method Signature ```typescript theme={null} async transferTokens({ tokenIdentifier, tokenAmount, receiverSparkAddress, outputSelectionStrategy, selectedOutputs, }: { tokenIdentifier: Bech32mTokenIdentifier; tokenAmount: bigint; receiverSparkAddress: string; outputSelectionStrategy?: "SMALL_FIRST" | "LARGE_FIRST"; selectedOutputs?: OutputWithPreviousTransactionData[]; }): Promise ``` ## Parameters The Bech32m token identifier (format: `btkn1...` for mainnet, `btknrt1...` for regtest) The amount of tokens to transfer The recipient's Spark Address Strategy for selecting outputs: `"SMALL_FIRST"` or `"LARGE_FIRST"` (defaults to `"SMALL_FIRST"`) Specific outputs to use for the transfer (overrides selection strategy) ## Returns The transaction ID of the token transfer ## Example ```typescript theme={null} const txId = await wallet.transferTokens({ tokenIdentifier: "btkn1...", tokenAmount: 1000n, receiverSparkAddress: "spark1..." }); console.log("Token transfer completed:", txId); ``` # validateMessageWithIdentityKey Source: https://docs.spark.money/api-reference/wallet/validate-message-with-identity-key Verify a message signature against the wallet's identity key. Validates a message signature against the wallet's identity key. ## Method Signature ```typescript theme={null} async validateMessageWithIdentityKey( message: string, signature: string | Uint8Array ): Promise ``` ## Parameters The original message that was signed The signature to validate (hex string or bytes) ## Returns `true` if the signature is valid, `false` otherwise ## Example ```typescript theme={null} const isValid = await wallet.validateMessageWithIdentityKey( "Hello, Spark!", "304402..." ); console.log("Signature valid:", isValid); ``` # withdraw Source: https://docs.spark.money/api-reference/wallet/withdraw Withdraw to an on-chain Bitcoin address via cooperative exit. Initiates a withdrawal to move funds from the Spark network to an on-chain Bitcoin address via the `SparkWallet`. ## Method Signature ```typescript theme={null} interface WithdrawParams { onchainAddress: string; exitSpeed: ExitSpeed; feeQuote?: CoopExitFeeQuote; // Deprecated: use feeQuoteId + feeAmountSats amountSats?: number; feeQuoteId?: string; // ID from getWithdrawalFeeQuote feeAmountSats?: number; // Fee amount based on exitSpeed deductFeeFromWithdrawalAmount?: boolean; // default: true } async withdraw(params: WithdrawParams): Promise ``` ## Parameters The Bitcoin address where the funds should be sent The desired speed of the exit (`FAST`, `MEDIUM`, `SLOW`) Deprecated. Use `feeQuoteId` and `feeAmountSats` instead. The amount in satoshis to withdraw (if not specified, withdraws all available funds) The ID from the fee quote returned by `getWithdrawalFeeQuote` The fee amount in satoshis based on the chosen `exitSpeed` When `true`, fees are deducted from withdrawal amount; when `false`, from remaining wallet balance (default: `true`) ## Returns The withdrawal request details, or null if the request cannot be completed ## Example ```typescript theme={null} // 1) Fetch a fee quote const feeQuote = await wallet.getWithdrawalFeeQuote({ amountSats: 17000, withdrawalAddress: "bcrt1pf8hed85p94emupfpfhq2g0p5c40cgzqs4agvvfmeuy32nxeh549syu2lwf", }); // 2) Calculate fee based on exit speed const exitSpeed = ExitSpeed.MEDIUM; let feeAmountSats: number; switch (exitSpeed) { case ExitSpeed.FAST: feeAmountSats = (feeQuote.l1BroadcastFeeFast?.originalValue || 0) + (feeQuote.userFeeFast?.originalValue || 0); break; case ExitSpeed.MEDIUM: feeAmountSats = (feeQuote.l1BroadcastFeeMedium?.originalValue || 0) + (feeQuote.userFeeMedium?.originalValue || 0); break; case ExitSpeed.SLOW: feeAmountSats = (feeQuote.l1BroadcastFeeSlow?.originalValue || 0) + (feeQuote.userFeeSlow?.originalValue || 0); break; } // 3) Use the quote before it expires to create the withdrawal const withdraw_result = await wallet.withdraw({ onchainAddress: "bcrt1pf8hed85p94emupfpfhq2g0p5c40cgzqs4agvvfmeuy32nxeh549syu2lwf", amountSats: 17000, exitSpeed, feeQuoteId: feeQuote.id, feeAmountSats, deductFeeFromWithdrawalAmount: true, }); console.log("Withdraw Result:", withdraw_result); ``` # Brale Source: https://docs.spark.money/integrations/brale Issue and manage compliant stablecoins on Spark with Brale. Brale Brale lets you issue and manage fiat-backed stablecoins on Spark with built‑in compliance and treasury controls. Use Brale to create a token, set rules (freeze, allow/block lists), mint/burn supply, and distribute to Spark wallet addresses via API or dashboard. Designed for regulated issuers and fintechs. See the official docs for details. **USDB** is Brale's flagship stablecoin on Spark, a dollar-pegged token backed 1:1 by U.S. Treasuries that earns 3.5-6% APY paid daily in Bitcoin. [Learn more about USDB →](https://www.spark.money/research/usdb-stablecoin-pays-bitcoin)
*** ## Features
*** ## Quick Integration Below is a minimal pattern that mirrors Brale’s Quick Start: create or select a token in the dashboard, use an API key, then mint to a Spark address. Adjust endpoints and fields per your Brale workspace and token configuration. Reference: [Quick Start](https://docs.brale.xyz/overview/quick-start) ```typescript theme={null} // 1) Create/select a token in the Brale dashboard and get an API key // 2) Server-side mint to a Spark address const BRALE_API = 'https://api.brale.xyz'; // example base URL const BRALE_API_KEY = process.env.BRALE_API_KEY!; const TOKEN_ID = process.env.BRALE_TOKEN_ID!; // your Brale-issued token id type MintRequest = { tokenId: string; amount: string; // integer string amount in smallest units recipientSparkAddress: string; // Spark address (bech32m) to receive tokens }; async function mintToSpark(req: MintRequest) { const res = await fetch(`${BRALE_API}/v1/tokens/${req.tokenId}/mint`, { method: 'POST', headers: { 'Content-Type': 'application/json', 'Authorization': `Bearer ${BRALE_API_KEY}`, }, body: JSON.stringify({ amount: req.amount, recipient: req.recipientSparkAddress, }), }); if (!res.ok) { const err = await res.text(); throw new Error(`Brale mint failed: ${res.status} ${err}`); } return await res.json(); } // Example usage await mintToSpark({ tokenId: TOKEN_ID, amount: '1000000', // e.g., 1.000000 unit if 6 decimals recipientSparkAddress: 'spark1xxxxx', }); ```
*** ## Documentation # Breez Source: https://docs.spark.money/integrations/breez Add self-custodial Lightning and Spark payments with Breez SDK. Partner Breez is a fully featured Lightning SDK that lets developers add self-custodial Lightning and Spark payments to their apps with almost zero lift. If you want to integrate LNURL, Lightning Addresses, or Nostr support, Breez gives you everything in one place. It also includes bindings for all major languages and frameworks, making it the easiest and most complete way to build on Lightning today. *** ## Features *** The SDK is written in Rust with bindings for all major languages (JS, Kotlin, Swift, Go, Python, RN, Flutter, C#). Here's how to integrate it in your app. ## Installation ```bash theme={null} npm install @breeztech/breez-sdk-spark ``` ## Quick Integration When developing a browser application, import from `@breeztech/breez-sdk-spark/web`. You must initialize the WebAssembly module with `await init()` before making any other calls. ```javascript theme={null} import init, { initLogging, defaultConfig, SdkBuilder, } from "@breeztech/breez-sdk-spark/web"; // Initialise the WebAssembly module await init(); ``` When developing a Node.js application (v22+), import from `@breeztech/breez-sdk-spark/nodejs`. ```javascript theme={null} const { initLogging, defaultConfig, SdkBuilder, } = require("@breeztech/breez-sdk-spark/nodejs"); const { Command } = require("commander"); require("dotenv").config(); class JsLogger { log = (logEntry) => { console.log( `[${new Date().toISOString()} ${logEntry.level}]: ${logEntry.line}` ); }; } const fileLogger = new JsLogger(); class JsEventListener { onEvent = (event) => { fileLogger.log({ level: "INFO", line: `Received event: ${JSON.stringify(event)}`, }); }; } const eventListener = new JsEventListener(); const program = new Command(); const initSdk = async () => { // Set the logger to trace await initLogging(fileLogger); // Get the mnemonic const mnemonic = process.env.MNEMONIC; // Connect using the config let config = defaultConfig("regtest"); config.apiKey = process.env.BREEZ_API_KEY; console.log(`defaultConfig: ${JSON.stringify(config)}`); let sdkBuilder = SdkBuilder.new(config, { type: "mnemonic", mnemonic: mnemonic, }); sdkBuilder = await sdkBuilder.withDefaultStorage("./.data"); const sdk = await sdkBuilder.build(); await sdk.addEventListener(eventListener); return sdk; }; program .name("breez-sdk-spark-wasm-cli") .description("CLI for Breez SDK Spark - Wasm"); program.command("get-info").action(async () => { const sdk = await initSdk(); const res = await sdk.getInfo({}); console.log(`getInfo: ${JSON.stringify(res)}`); }); program.parse(); ``` *** ## Documentation # Flashnet Source: https://docs.spark.money/integrations/flashnet Swap tokens on Spark using Flashnet's AMM and liquidity pools. Flashnet Flashnet is the leading AMM (automated market maker) on Spark. It enables instant token swaps, liquidity pools, and market creation without custody risk. Built on Spark's cryptographic messaging layer, Flashnet provides near-instant settlement and zero-fee transactions for Bitcoin and token trading in your applications.
*** ## Use Cases
*** ## Quick Integration ```bash npm theme={null} npm install @flashnet/sdk @buildonspark/spark-sdk ``` ```bash yarn theme={null} yarn add @flashnet/sdk @buildonspark/spark-sdk ``` ```bash bun theme={null} bun add @flashnet/sdk @buildonspark/spark-sdk ``` ### Initialize Flashnet Client ```typescript theme={null} import { FlashnetClient } from '@flashnet/sdk'; import { SparkWallet } from '@buildonspark/spark-sdk'; // Initialize your Spark wallet const { wallet } = await SparkWallet.initialize({ mnemonicOrSeed: process.env.MNEMONIC, options: { network: "REGTEST", // or "MAINNET" }, }); // Create the Flashnet client const client = new FlashnetClient(wallet); await client.initialize(); // The client will automatically: // - Authenticate with the AMM service // - Handle all signing and intent generation ``` ### Using the Spark Wallet The Flashnet client can also be used as a regular Spark wallet: ```typescript theme={null} // Create a Lightning invoice using the client wallet const invoice = await client.wallet.createLightningInvoice({ amountSats: 1000000, memo: "Test invoice", }); console.log(invoice); ```
*** ## Documentation # Garden Source: https://docs.spark.money/integrations/garden Swap BTC across chains using Garden's atomic swap protocol on Spark. # Lightspark Grid Source: https://docs.spark.money/integrations/grid Add fiat on-ramp and off-ramp to your Spark app with Grid. Grid Grid gives builders on Spark everything they need to create natively embedded on/off-ramp experiences directly within their wallet architecture. Whether you’re pulling funds from a U.S. bank account into a Spark wallet or off-ramping elsewhere, Grid provides the APIs to power the next generation of money apps natively on Bitcoin.
*** ### Use Cases
*** ## Documentation # Partner Source: https://docs.spark.money/integrations/integration-template Template for Spark integration pages. Partner Integrate \[Partner Name] for use case this partner enables and what functionality they provide. \[Brief description of what the partner does and their value proposition for developers building on Spark.] Integrate \[Partner Name] for use case this partner enables and what functionality they provide. \[Brief description of what the partner does and their value proposition for developers building on Spark.] *** ## Features *** ## Quick Integration (if applicable) ```typescript theme={null} // Example code showing basic integration import { PartnerClient } from '@partner/client' const client = new PartnerClient({ apiKey: 'your-partner-key', network: 'spark' }) // Basic usage example const result = await client.someMethod({ // configuration options }) ``` *** ## Use Cases ## Guides Coming soon...
***
## Documentation # Privy Source: https://docs.spark.money/integrations/privy Add embedded wallets and social login to your Spark app with Privy. Privy Integrate Privy for seamless authentication and wallet management in your Spark applications. Privy provides a complete authentication and wallet management solution for Web3 applications, making it easy to onboard users and manage their digital identities on Spark.
*** ## Use Cases *** ## Quick Integration ### Installation Install the Privy React SDK using your package manager of choice: ```bash npm theme={null} npm install @privy-io/react-auth@latest ``` ```bash pnpm theme={null} pnpm install @privy-io/react-auth@latest ``` ```bash yarn theme={null} yarn add @privy-io/react-auth@latest ``` ### Setup ```typescript App.tsx theme={null} 'use client'; import {PrivyProvider} from '@privy-io/react-auth'; export default function Providers({children}: {children: React.ReactNode}) { return ( {children} ); } ```
*** ## Documentation # Sparkscan Source: https://docs.spark.money/integrations/sparkscan Block explorer and API for querying Spark transactions and network data. Sparkscan Integrate Sparkscan for comprehensive blockchain data and network analytics on Spark. Sparkscan is the official block explorer for Spark, providing real-time transaction data, network analytics, and comprehensive blockchain information through both web interface and API.
*** ## Features
*** ## Quick Integration ```typescript theme={null} import { SparkscanClient } from '@sparkscan/client' const client = new SparkscanClient({ apiKey: 'your-sparkscan-key', network: 'spark' }) // Get transaction details const tx = await client.getTransaction('tx-hash') // Get address balance const balance = await client.getBalance('spark:address') // Get network stats const stats = await client.getNetworkStats() ```
*** ## Use Cases ## Guides Coming soon...
*** ## Documentation # Tether Wallet Development Kit Source: https://docs.spark.money/integrations/tether-wdk Build wallets with Tether's WDK using the Spark module for Bitcoin and stablecoin support. # API Reference Source: https://docs.spark.money/issuance/api-reference # Burn Tokens Source: https://docs.spark.money/issuance/burn-tokens Burning permanently destroys tokens from the issuer wallet. When called, tokens are automatically removed from circulation and the network state. ## Burn ```typescript theme={null} const [tokenIdentifier] = await wallet.getIssuerTokenIdentifiers(); if (!tokenIdentifier) throw new Error("No token identifiers found for this issuer"); await wallet.burnTokens({ tokenIdentifier, tokenAmount: 5_000_000000n // 5,000 tokens (6 decimals) }); ``` Burning is irreversible. Burned tokens are gone forever. ## When to Burn Common use cases: * Stablecoin redemption: user redeems tokens for fiat, you burn the equivalent * Deflationary mechanics: periodic burns to reduce supply * Token buyback: repurchase tokens from the market and burn them ## Verify the Burn ```typescript theme={null} const [tokenIdentifier] = await wallet.getIssuerTokenIdentifiers(); if (!tokenIdentifier) throw new Error("No token identifiers found for this issuer"); const before = await wallet.getBalance(); console.log("Before:", before.tokenBalances.get(tokenIdentifier)?.ownedBalance); await wallet.burnTokens({ tokenIdentifier, tokenAmount: 1000_000000n }); const after = await wallet.getBalance(); console.log("After:", after.tokenBalances.get(tokenIdentifier)?.ownedBalance); ``` ## Proof-of-Burn Address Anyone can also burn tokens by sending them to the network's proof-of-burn address: ``` spark1pgssyqszqgpqyqszqgpqyqszqgpqyqszqgpqyqszqgpqyqszqgpqyqszykl0d2 ``` Tokens sent to this address are permanently removed from circulation and cannot be recovered. # Create Token Source: https://docs.spark.money/issuance/create-token Creating a token registers it on the network and locks in its properties. Once created, the token exists immediately and you can start minting. All token properties (name, ticker, decimals, max supply, freezability) are permanent and cannot be changed after creation. Create Token Create Token ## Create Your Token ```typescript theme={null} import { IssuerSparkWallet } from "@buildonspark/issuer-sdk"; const { wallet } = await IssuerSparkWallet.initialize({ options: { network: "REGTEST" } }); const { tokenIdentifier, transactionHash } = await wallet.createToken({ tokenName: "Acme Dollar", tokenTicker: "ACME", decimals: 6, maxSupply: 0n, isFreezable: true, returnIdentifierForCreate: true }); console.log("Token created:", tokenIdentifier); // btkn1q... console.log("Announcement tx:", transactionHash); ``` ## Token Properties All properties are permanent. Choose carefully. ### Name and Ticker Display name and symbol shown in wallets and explorers. ```typescript theme={null} tokenName: "Acme Dollar" tokenTicker: "ACME" ``` ### Decimals Defines the smallest unit. If `decimals: 6`, then `1000000` base units = 1 token. | Decimals | Smallest Unit | Common Usage | | :------- | :------------ | :------------ | | 6 | 0.000001 | Stablecoins | | 8 | 0.00000001 | Bitcoin-style | ### Max Supply `0n` for unlimited. Any other value caps total supply forever. ```typescript theme={null} // Unlimited maxSupply: 0n // Capped at 21 million (8 decimals) maxSupply: 21_000_000_00000000n ``` ### Freezable If `true`, you can freeze addresses from transacting your token. If `false`, you lose this ability permanently. This cannot be changed. If you set `isFreezable: false`, you can never freeze tokens. ## Get Token Info After creation, retrieve your token's metadata and identifier: ```typescript theme={null} const [tokenIdentifier] = await wallet.getIssuerTokenIdentifiers(); if (!tokenIdentifier) throw new Error("No token identifiers found for this issuer"); const [metadata] = await wallet.getIssuerTokensMetadata([tokenIdentifier]); if (!metadata) throw new Error(`No metadata found for token ${tokenIdentifier}`); console.log(metadata.tokenName); // "Acme Dollar" console.log(metadata.tokenTicker); // "ACME" console.log(metadata.decimals); // 6 console.log(metadata.maxSupply); // 0n console.log(metadata.isFreezable); // true console.log(tokenIdentifier); // btkn1q... ``` ## Vanity Identifiers Token identifiers are derived from your wallet keys. Want a custom suffix like `btkn1...usdc`? Use the [vanity generator](https://github.com/buildonspark/spark/tree/main/tools/vanity-token-generator). # FAQ Source: https://docs.spark.money/issuance/faq ### How did you make tokens possible on Spark? Tokens are possible through the BTKN (Bitcoin Token) protocol. BTKN is our adaptation of the LRC-20 protocol, specifically optimized for Spark. It lets us represent tokens using regular Bitcoin transactions. On Spark, we made this protocol native so tokens can be issued, transferred, and managed directly, while staying fully connected to Bitcoin. ### Can anyone issue a BTKN token? Yes. Token issuance is permissionless. BTKN and Spark are open protocols. ### Is BTKN compatible with L1? Yes, BTKN will be compatible with L1. We expect most of the activity to happen on Spark, with L1 acting as the escape valve in case anything goes wrong. ### Are there any fees? No gas, no network fees. BTKN assets on Spark move freely. ### Does BTKN support stablecoins? Yes. BTKN was built for stablecoin issuers. We're agnostic to what kind of stablecoin you're building. Our goal is to give issuers the right primitives, and meet them where they are. If you're planning to issue one, reach out. ### Is BTKN compatible with the Lightning Network? Not planned. As of now, we're focused on Spark support where liquidity, UX, and interoperability matter most. Lightning support is possible in the future, but not a priority today. ### Can BTKN exit from Spark to Bitcoin? Soon. Spark is designed for unilateral exits. Every token on Spark will wap to a real UTXO on Bitcoin L1. Any users will be able to exit at any time, without trusting anyone. ### Is Spark planning to support other token standards? Not natively for now. We think liquidity will consolidate around a single standard, and BTKN has the highest chance of winning by onboarding stablecoin issuers. That said, we're open to supporting other standards if real demand emerges. ### How does BTKN work with Spark? On Spark, BTKN tokens are native. Minting, transfers, and burns are embedded into Spark leaves and validated by Spark Operators. ### I want to issue my token: how do I get started? Start with our [quickstart](/quickstart/launch-token) to issue your first token. For more details, check out our [API reference](/api-reference/issuer-overview). # Freeze Tokens Source: https://docs.spark.money/issuance/freeze-tokens Freezing is an optional capability that issuers can enable when creating a token. Spark does not require or enforce freezing. If you created your token with `isFreezable: false`, this feature is permanently disabled and your token operates without any freeze restrictions. Issuers who need compliance controls (regulated stablecoins, for example) can opt into freezing. Issuers who want fully permissionless tokens simply set `isFreezable: false` at creation. ## Freeze an Address If your token has freezing enabled, you can prevent an address from sending or receiving: ```typescript theme={null} const [tokenIdentifier] = await wallet.getIssuerTokenIdentifiers(); if (!tokenIdentifier) throw new Error("No token identifiers found for this issuer"); const result = await wallet.freezeTokens({ tokenIdentifier, sparkAddress: "spark1abc..." }); console.log("Frozen amount:", result.impactedTokenAmount); ``` The response includes: * `impactedTokenOutputs`: Array of token output references that were frozen (each containing `transactionHash` and `vout`) * `impactedTokenAmount`: Total amount of tokens frozen ## Unfreeze ```typescript theme={null} await wallet.unfreezeTokens({ tokenIdentifier, sparkAddress: "spark1abc..." }); ``` ## Check Freezability ```typescript theme={null} const [tokenIdentifier] = await wallet.getIssuerTokenIdentifiers(); if (!tokenIdentifier) throw new Error("No token identifiers found for this issuer"); const [metadata] = await wallet.getIssuerTokensMetadata([tokenIdentifier]); if (!metadata) throw new Error(`No metadata found for token ${tokenIdentifier}`); if (!metadata.isFreezable) { console.log("This token cannot freeze addresses"); } ``` ## Limitations * You cannot freeze your own issuer address * Freezing takes effect immediately * Only works for tokens created with `isFreezable: true` # Issuer Wallet Source: https://docs.spark.money/issuance/issuer-wallet Your issuer wallet holds the keys that control your token. Create one to get started, or restore an existing wallet with your mnemonic. Issuer Wallet Issuer Wallet ## Create a Wallet ```typescript theme={null} import { IssuerSparkWallet } from "@buildonspark/issuer-sdk"; const { wallet, mnemonic } = await IssuerSparkWallet.initialize({ options: { network: "REGTEST" } }); console.log("Backup this phrase:", mnemonic); console.log("Your address:", await wallet.getSparkAddress()); ``` Store the mnemonic securely. It's the only way to recover your wallet. ## Restore a Wallet ```typescript theme={null} const { wallet } = await IssuerSparkWallet.initialize({ mnemonicOrSeed: "abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon about", options: { network: "MAINNET" } }); ``` ## Networks | Network | Use | | :-------- | :---------------------- | | `REGTEST` | Development and testing | | `MAINNET` | Production | Start on `REGTEST`. When you're ready for production, generate a fresh wallet on `MAINNET`. ## Multiple Tokens An issuer identity can create multiple tokens. Use `getIssuerTokenIdentifiers()` and `getIssuerTokenBalances()` to work with all of them. If you want separate issuer identities (separate keys) from the same mnemonic, derive them with different account numbers: ```typescript theme={null} const { wallet: tokenA } = await IssuerSparkWallet.initialize({ mnemonicOrSeed: "your mnemonic...", accountNumber: 0, options: { network: "REGTEST" } }); const { wallet: tokenB } = await IssuerSparkWallet.initialize({ mnemonicOrSeed: "your mnemonic...", accountNumber: 1, options: { network: "REGTEST" } }); ``` ## Cleanup Close connections when your app shuts down: ```typescript theme={null} await wallet.cleanupConnections(); ``` # Mint Tokens Source: https://docs.spark.money/issuance/mint-tokens Minting creates new tokens and deposits them directly into your issuer wallet. From there, you can transfer them to users or hold them in reserve. Only the issuer wallet that created the token can mint. Mint Tokens Mint Tokens ## Mint ```typescript theme={null} const [tokenIdentifier] = await wallet.getIssuerTokenIdentifiers(); if (!tokenIdentifier) throw new Error("No token identifiers found for this issuer"); // Mint 10,000 tokens (6 decimals) await wallet.mintTokens({ tokenIdentifier, tokenAmount: 10_000_000000n }); ``` The amount is in base units. If your token has 6 decimals, multiply the human-readable amount by 1,000,000. ```typescript theme={null} function toBaseUnits(amount: string, decimals: number): bigint { const [whole, fraction = ""] = amount.split("."); const fractionPadded = (fraction + "0".repeat(decimals)).slice(0, decimals); return BigInt(whole || "0") * 10n ** BigInt(decimals) + BigInt(fractionPadded || "0"); } await wallet.mintTokens({ tokenIdentifier, tokenAmount: toBaseUnits("10000", 6) }); ``` ## Check Your Balance After minting, tokens appear in your wallet immediately: ```typescript theme={null} const { tokenBalances } = await wallet.getBalance(); for (const [tokenId, info] of tokenBalances) { console.log(tokenId, info.ownedBalance); } ``` ## Max Supply If you set a `maxSupply` when creating your token, minting fails once you reach it. With unlimited supply (`maxSupply: 0n`), you can mint indefinitely. ```typescript theme={null} const [tokenIdentifier] = await wallet.getIssuerTokenIdentifiers(); if (!tokenIdentifier) throw new Error("No token identifiers found for this issuer"); const [metadata] = await wallet.getIssuerTokensMetadata([tokenIdentifier]); if (!metadata) throw new Error(`No metadata found for token ${tokenIdentifier}`); if (metadata.maxSupply === 0n) { console.log("Unlimited supply"); } else { console.log("Max supply:", metadata.maxSupply); } ``` # Issuer SDK Source: https://docs.spark.money/issuance/overview Issuer SDK Issuer SDK The Spark Issuer SDK lets you create and manage tokens on the Spark network in the most scalable and developer-friendly way possible. Whether you're launching a new token, managing supply, or building token-based applications, the SDK provides everything you need to get started. See how [Brale built USDB](https://brale.xyz/blog/brale-spark-digital-dollars-native-to-bitcoin), the first Bitcoin-native stablecoin on Spark. *** ## Installation *** ## Guides *** ## Tools # Testing Guide Source: https://docs.spark.money/issuance/testing-guide ### Prerequisites * Node.js 20+ * [Spark CLI tool](https://github.com/buildonspark/spark) ### Using our Spark CLI tool We have a CLI tool that allows you to test your wallet operations on Spark. No coding is required! To install the CLI tool: ```bash theme={null} git clone https://github.com/buildonspark/spark.git cd spark/sdks/js yarn && yarn build cd examples/spark-cli yarn cli ``` This will start the CLI tool and you will be able to interact with the wallet. Run `help` to see the available commands. ## Command Reference
Command Usage
initwallet initwallet \ - Creates a new wallet instance. If no mnemonic provided, generates one
getbalance Gets the current wallet balance as well the token balance
getsparkaddress Gets a new Spark Address for receiving transfers
announcetoken announcetoken \ \ \ \ \ - Mint a certain amount of tokens
minttokens minttokens \ - Mint a certain amount of tokens
transfertokens transfertokens \ \ \ - Sends tokens to the given receiver Spark Address using Bech32m token identifier (btkn1...)
burntokens burntokens \ - Burns the specified amount of tokens
freezetokens freezetokens \ - Freezes tokens at the Spark Address
unfreezetokens unfreezetokens \ - Unfreezes tokens at the Spark Address
tokenactivity Gets the token activity for the issuer's token
tokeninfo Gets the token info for the issuer's token
help Shows the help menu
exit Exits the CLI tool
## Demo application The fully built Demo Application is available [Here](https://github.com/buildonspark/spark/tree/develop/sdks/js/examples/spark-demo) ## Sample Express server project ### Clone the SDK repo ```bash theme={null} git clone https://github.com/buildonspark/spark.git ``` ### Navigate to project directory ```bash theme={null} cd spark/sdks/js/examples/spark-node-express/ ``` Follow the instructions in the README to install dependencies and run the server. # Analytics Source: https://docs.spark.money/issuance/token-analytics Query the full transaction history for your token. Filter by address, status, issuer key, or output ID to understand how your token moves through the network. Analytics Analytics ## Query Transactions Get all transactions for your token: ```typescript theme={null} const [tokenIdentifier] = await wallet.getIssuerTokenIdentifiers(); if (!tokenIdentifier) throw new Error("No token identifiers found for this issuer"); const response = await wallet.queryTokenTransactionsWithFilters({ tokenIdentifiers: [tokenIdentifier], pageSize: 50, direction: "NEXT", }); console.log("Total transactions in page:", response.tokenTransactionsWithStatus.length); ``` ## Filter by Address Get transactions for a specific Spark address: ```typescript theme={null} const response = await wallet.queryTokenTransactionsWithFilters({ sparkAddresses: ["spark1abc..."], pageSize: 50, }); ``` Filter constraints can be combined (AND semantics). For example, you can filter by both `tokenIdentifiers` and `sparkAddresses` in a single request. ## Transaction Status Each transaction has a status: | Status | Meaning | | :------------------------------------ | :----------------------- | | `TOKEN_TRANSACTION_STARTED` | Created, not yet signed | | `TOKEN_TRANSACTION_SIGNED` | Signed by all parties | | `TOKEN_TRANSACTION_REVEALED` | Reveal stage completed | | `TOKEN_TRANSACTION_FINALIZED` | Confirmed | | `TOKEN_TRANSACTION_STARTED_CANCELLED` | Cancelled before signing | | `TOKEN_TRANSACTION_SIGNED_CANCELLED` | Cancelled after signing | Filter by status: ```typescript theme={null} const finalized = response.tokenTransactionsWithStatus.filter( tx => tx.status === "TOKEN_TRANSACTION_FINALIZED" ); console.log("Completed transactions:", finalized.length); ``` ## Pagination For tokens with many transactions, use cursor pagination: ```typescript theme={null} const [tokenIdentifier] = await wallet.getIssuerTokenIdentifiers(); if (!tokenIdentifier) throw new Error("No token identifiers found for this issuer"); const firstPage = await wallet.queryTokenTransactionsWithFilters({ tokenIdentifiers: [tokenIdentifier], pageSize: 50, direction: "NEXT", }); console.log("Transactions in first page:", firstPage.tokenTransactionsWithStatus.length); const nextCursor = firstPage.pageResponse?.nextCursor; if (nextCursor) { const secondPage = await wallet.queryTokenTransactionsWithFilters({ tokenIdentifiers: [tokenIdentifier], pageSize: 50, cursor: nextCursor, direction: "NEXT", }); console.log("Transactions in next page:", secondPage.tokenTransactionsWithStatus.length); } ``` # Holders Source: https://docs.spark.money/issuance/token-holders Query token balances by Spark address using the `SparkReadonlyClient`. Public queries respect [privacy mode](/wallets/privacy): if a wallet is private, unauthenticated requests return empty results. Holders Holders ## Your Balance Check how many tokens you hold: ```typescript theme={null} const balances = await wallet.getIssuerTokenBalances(); for (const { tokenIdentifier, balance } of balances) { if (!tokenIdentifier) continue; console.log(tokenIdentifier, balance); } ``` ## Check Any Address Pass an address to check someone else's balance: ```typescript theme={null} import { SparkReadonlyClient } from "@buildonspark/spark-sdk"; const client = SparkReadonlyClient.createPublic({ network: "MAINNET" }); const [tokenIdentifier] = await wallet.getIssuerTokenIdentifiers(); if (!tokenIdentifier) throw new Error("No token identifiers found for this issuer"); const holderBalances = await client.getTokenBalance("spark1abc...", [tokenIdentifier]); const info = holderBalances.get(tokenIdentifier); console.log("User balance:", info?.ownedBalance); ``` ## Display Amounts Convert base units to human-readable format: ```typescript theme={null} const holderBalances = await client.getTokenBalance("spark1abc...", [tokenIdentifier]); const info = holderBalances.get(tokenIdentifier); if (!info) throw new Error("No balance found (zero balance, unknown token, or privacy mode)"); function formatUnits(amount: bigint, decimals: number): string { const negative = amount < 0n; const value = negative ? -amount : amount; const base = 10n ** BigInt(decimals); const whole = value / base; const fraction = value % base; const fractionStr = fraction.toString().padStart(decimals, "0").replace(/0+$/, ""); const out = fractionStr ? `${whole.toString()}.${fractionStr}` : whole.toString(); return negative ? `-${out}` : out; } console.log(`${formatUnits(info.ownedBalance, info.tokenMetadata.decimals)} ${info.tokenMetadata.tokenTicker}`); ``` ## All Token Balances Get all token balances for an address: ```typescript theme={null} const tokenBalances = await client.getTokenBalance("spark1abc..."); function formatUnits(amount: bigint, decimals: number): string { const negative = amount < 0n; const value = negative ? -amount : amount; const base = 10n ** BigInt(decimals); const whole = value / base; const fraction = value % base; const fractionStr = fraction.toString().padStart(decimals, "0").replace(/0+$/, ""); const out = fractionStr ? `${whole.toString()}.${fractionStr}` : whole.toString(); return negative ? `-${out}` : out; } for (const [tokenId, info] of tokenBalances) { console.log(`${info.tokenMetadata.tokenTicker}: ${formatUnits(info.ownedBalance, info.tokenMetadata.decimals)}`); } ``` ## Listen for Changes Get notified when balances change: ```typescript theme={null} wallet.on("transfer:claimed", async (transferId, newBalance) => { console.log("Transfer received:", transferId); const updated = await wallet.getBalance(); console.log("New balance:", updated.balance); }); ``` # Spark CLI Source: https://docs.spark.money/issuance/tools/cli # Transfer Tokens Source: https://docs.spark.money/issuance/transfer-tokens Send tokens to any Spark address. Transfers are instant, free, and recipients receive them automatically. Transfer Tokens Transfer Tokens ## Send Tokens ```typescript theme={null} const [tokenIdentifier] = await wallet.getIssuerTokenIdentifiers(); if (!tokenIdentifier) throw new Error("No token identifiers found for this issuer"); await wallet.transferTokens({ tokenIdentifier, tokenAmount: 100_000000n, // 100 tokens (6 decimals) receiverSparkAddress: "spark1abc..." }); ``` ## Batch Transfer Send to multiple recipients in a single transaction: ```typescript theme={null} await wallet.batchTransferTokens([ { tokenIdentifier, tokenAmount: 1000_000000n, receiverSparkAddress: "spark1alice..." }, { tokenIdentifier, tokenAmount: 500_000000n, receiverSparkAddress: "spark1bob..." }, { tokenIdentifier, tokenAmount: 250_000000n, receiverSparkAddress: "spark1carol..." } ]); ``` Batch transfers are atomic. All succeed or none do. Each item in the array must include its own `tokenIdentifier`. ## Token Amounts All amounts are in base units. If your token has 6 decimals: | Human | Base Units | | :---- | :---------- | | 1 | 1,000,000 | | 100 | 100,000,000 | | 0.5 | 500,000 | ```typescript theme={null} function toBaseUnits(amount: string, decimals: number): bigint { const [whole, fraction = ""] = amount.split("."); const fractionPadded = (fraction + "0".repeat(decimals)).slice(0, decimals); return BigInt(whole || "0") * 10n ** BigInt(decimals) + BigInt(fractionPadded || "0"); } await wallet.transferTokens({ tokenIdentifier, tokenAmount: toBaseUnits("50.5", 6), receiverSparkAddress: "spark1..." }); ``` ## Receiving Tokens Recipients don't need to do anything. Tokens appear instantly: ```typescript theme={null} const { tokenBalances } = await wallet.getBalance(); tokenBalances.forEach((data, key) => { console.log(data.tokenMetadata.tokenName, ":", data.ownedBalance); }); ``` # TypeScript Source: https://docs.spark.money/issuance/typescript TypeScript SDK for issuing and managing tokens on Spark. Requires Node.js v18+. ## Getting Started To get started, follow the steps below. Want to skip the manual setup? Scaffold a new project instantly: ```bash theme={null} npx @buildonspark/create-spark-app my-app ``` Supports React (Vite), Next.js, React Native, Expo, Express, and [more](https://github.com/buildonspark/spark). Install the Spark Issuer SDK packages using your package manager of choice. ```bash npm theme={null} npm install @buildonspark/issuer-sdk ``` ```bash yarn theme={null} yarn add @buildonspark/issuer-sdk ``` ```bash pnpm theme={null} pnpm add @buildonspark/issuer-sdk ``` Create an issuer instance that will be used to interact with the Spark network. ```ts issuer.ts theme={null} import { IssuerSparkWallet } from "@buildonspark/issuer-sdk"; const { wallet, mnemonic } = await IssuerSparkWallet.initialize({ mnemonicOrSeed: "optional-mnemonic-or-seed", accountNumber: 0, // optional options: { network: "REGTEST", }, }); console.log("Wallet initialized successfully:", mnemonic); ``` You're ready to start building. ```ts app.ts theme={null} import { IssuerSparkWallet } from "@buildonspark/issuer-sdk"; async function main() { try { // Initialize issuer wallet const { wallet } = await IssuerSparkWallet.initialize({ options: { network: "REGTEST" } }); console.log("Issuer wallet created!"); console.log("Address:", await wallet.getSparkAddress()); // Create a token const { tokenIdentifier, transactionHash } = await wallet.createToken({ tokenName: "My Token", tokenTicker: "MTK", decimals: 8, maxSupply: BigInt(21000000_00000000), // 21M tokens (with 8 decimals) isFreezable: false, returnIdentifierForCreate: true, }); console.log("Token created:", tokenIdentifier); console.log("Announcement tx:", transactionHash); // Mint tokens to yourself const mintTxId = await wallet.mintTokens({ tokenIdentifier, tokenAmount: BigInt(1000_00000000), // 1000 tokens (with 8 decimals) }); console.log("Tokens minted:", mintTxId); } catch (error) { console.error("Error:", error); } } main(); ``` ## TypeScript Configuration ### tsconfig.json Create a `tsconfig.json` file in your project root: ```json tsconfig.json theme={null} { "compilerOptions": { "target": "ES2020", "module": "commonjs", "lib": ["ES2020"], "outDir": "./dist", "rootDir": "./src", "strict": true, "esModuleInterop": true, "skipLibCheck": true, "forceConsistentCasingInFileNames": true, "resolveJsonModule": true, "declaration": true, "declarationMap": true, "sourceMap": true }, "include": ["src/**/*"], "exclude": ["node_modules", "dist"] } ``` ### Package.json Scripts Add TypeScript build scripts to your `package.json`: ```json package.json theme={null} { "scripts": { "build": "tsc", "start": "node dist/app.js", "dev": "ts-node src/app.ts", "watch": "tsc --watch" } } ``` ## Core Issuer Operations ### Initialize an Issuer Wallet An issuer wallet requires either a mnemonic or raw seed for initialization. The `initialize()` function accepts both. If no input is given, it will auto-generate a mnemonic and return it. ```ts theme={null} import { IssuerSparkWallet } from "@buildonspark/issuer-sdk"; // Initialize a new issuer wallet const { wallet, mnemonic } = await IssuerSparkWallet.initialize({ mnemonicOrSeed: "optional-mnemonic-or-seed", accountNumber: 0, // optional options: { network: "REGTEST" // or "MAINNET" } }); console.log("Issuer wallet initialized:", mnemonic); ``` ### Mnemonic Phrases A mnemonic is a human-readable encoding of your wallet's seed. It's a 12- or 24-word phrase from the BIP-39 wordlist, used to derive the cryptographic keys that control your issuer wallet. ## TypeScript Features ### Type Safety The Spark Issuer TypeScript SDK provides full type safety for all issuer operations: ```ts theme={null} import { IssuerSparkWallet } from "@buildonspark/issuer-sdk"; // TypeScript will provide autocomplete and type checking const { wallet } = await IssuerSparkWallet.initialize({ options: { network: "REGTEST" } }); // All methods are fully typed const address: string = await wallet.getSparkAddress(); const tokensMetadata = await wallet.getIssuerTokensMetadata(); const tokenIdentifiers = await wallet.getIssuerTokenIdentifiers(); ``` ### Interface Definitions Key interfaces used by the Issuer SDK: ```ts theme={null} // Token creation parameters interface CreateTokenParams { tokenName: string; tokenTicker: string; decimals: number; isFreezable: boolean; maxSupply?: bigint; // defaults to 0n (unlimited) extraMetadata?: Uint8Array; returnIdentifierForCreate?: boolean; } // Token metadata returned by getIssuerTokensMetadata() interface IssuerTokenMetadata { rawTokenIdentifier: Uint8Array; tokenPublicKey: string; tokenName: string; tokenTicker: string; decimals: number; maxSupply: bigint; isFreezable: boolean; extraMetadata?: Uint8Array; bech32mTokenIdentifier: Bech32mTokenIdentifier; } // Token distribution stats interface TokenDistribution { totalCirculatingSupply: bigint; totalIssued: bigint; totalBurned: bigint; numHoldingAddress: number; numConfirmedTransactions: bigint; } ``` ### Error Handling The SDK provides typed error classes for better error handling: ```ts theme={null} import { IssuerSparkWallet } from "@buildonspark/issuer-sdk"; import { SparkError, SparkValidationError } from "@buildonspark/spark-sdk"; const { wallet } = await IssuerSparkWallet.initialize({ options: { network: "REGTEST" }, }); try { await wallet.createToken({ tokenName: "My Token", tokenTicker: "MTK", decimals: 8, maxSupply: BigInt(21000000_00000000), isFreezable: false }); } catch (error) { if (error instanceof SparkValidationError) { console.error('Validation error:', error.message); } else if (error instanceof SparkError) { console.error('SDK error:', error.message); } else { console.error('Unexpected error:', error); } } ``` # Core Concepts Source: https://docs.spark.money/learn/core-concepts
A statechain is a protocol that enables off-chain transfers of ownership for blockchain assets. It allows users to transfer control of a UTXO multiple times without creating on-chain transactions, using cryptographic signatures facilitated by a group of entities. This approach aims to improve transaction speed, privacy, and scalability while maintaining the security of the underlying blockchain.
## Spark Entity (SE)
The group of operators that run a Spark. They are responsible for performing the operations necessary for signing and forgetting past keys.
## Spark Operator (SO)
One of the operators within the SE. All or a threshold of operators are used to aid in the transfer of off-chain UTXOs.
## Spark Service Provider (SSP)
A service provider who facilitates efficient deposits/withdrawals to/from Spark. Any number of SSPs can exist within a single Spark. Either SSPs or another entity can additionally optionally serve as Lightning providers to enable Lightning transactions for users within Spark.
## Branches and Leaves
Leaves are terminal transactions of the tree that are owned by an individual user. Branches are all transactions of the tree that are not leaf transactions. These are nearly identical to leaf transactions, except they do not have timelocks and can only be spent by the sum of the keys of the leaves under them.
## Exit Transaction
A signed Bitcoin transaction that sends funds from Spark to the user. This serves as the unilateral exit mechanism, enabling any user to withdraw funds from Spark without cooperation by broadcasting the exit transaction and its parents.
## Atomic Swap
Exchanging two secrets A and B trustlessly for each other such that either both parties involved know both A and B or neither know both A and B.
# Deposits from L1 Source: https://docs.spark.money/learn/deposits Depositing L1 funds to Spark is straightforward. The SE (Spark Entity) and user collaborate to generate an aggregate public key and derive a pay-to-taproot address from it. They then work together to create and sign two transactions: an exit transaction, and an intermediate branch transaction before it that triggers the exit transaction's relative timelock. Once these transactions are both signed, the user can finally broadcast the deposit transaction to the pay-to-taproot address. Note that the inputs to the funding transaction should be segwit inputs. The user will now have a leaf in Spark. ### Steps-by-Step Process
1. **Key Generation:** * The user and SE work together to generate an aggregate public key, which is the sum of the user's public key and the SE's public key (which itself is derived from the individual SO public keys). They then derive a pay-to-taproot address for this key. * $\text{PubKey}_{Combined} = \text{PubKey}_{User} + \text{PubKey}_{SE}$ * where $\text{PubKey}_{SE} = \sum \lambda_i * \text{PubKey}_{SO_i}$ * and $\lambda_i$ is the Lagrange Coefficient on $x_0 = 0$ for $i$
2. **Setup and Signing:** * User constructs a deposit transaction sending their funds to the pay-to-taproot address, but doesn't broadcast it. * User and SE collaboratively create and sign two transactions: 1. An intermediate branch transaction (not broadcasted) with no timelock that spends from the deposit transaction. This transaction triggers the relative timelock of leaves under it. 2. An exit transaction that spends from the intermediate transaction. This transaction is broadcasted if the user wants to unilaterally exit Spark. * **Both transactions are signed by all parties involved in order to provide a unilateral exit path. Spark-compatible wallets should verify the transaction validity of all transactions involved.**
3. **Storage:** * User and SE securely store the signed transactions.
4. **User Deposit and Confirmation:** * User broadcasts the deposit transaction created in step 2. Once the L1 transaction has been confirmed, the funds are available to transfer within Spark.
# FAQ Source: https://docs.spark.money/learn/faq ### Is Spark live? Yes. Spark's beta mainnet is now live. Developers can start building and transacting on the network. While this is a beta release, core functionality like sending and receiving Bitcoin, supporting tokens (e.g. stablecoins), and Lightning interoperability is fully operational. That said, Spark is still highly experimental. Expect bugs, rapid iteration, and breaking changes as we continue to scale the network. ### How do I check if Spark is having issues? Check [spark.money/status](https://spark.money/status) for real-time network status. You can also report issues there. ### How is Spark native to Bitcoin? The Bitcoin you hold on Spark is the same Bitcoin you hold on Bitcoin. It's not wrapped or custodied in a multisig by a set of signers. On Spark, you can unilaterally exit your funds back to L1 at any time by broadcasting your pre-signed transaction to a Bitcoin node. ### How many Spark Operators (SOs) are there, and who are they? At launch, Spark is supported by a small set of two Spark Operators (SOs): [Lightspark](https://www.lightspark.com) and [Flashnet](https://flashnet.xyz). We've intentionally kept the set small to simplify debugging and testing during the early phase. More operators will be added soon as we scale. ### Should I expect some limits? Spark is still early. Please treat everything as a beta environment. With that in mind, limits may be applied. Our first priority is making sure everything stays secure. ### What happens if Spark operators go offline?
You can always exit to Bitcoin L1 using your pre-signed exit transaction. If all operators go offline, you won't be able to make new Spark transfers until operators are back online, but your funds remain in your custody and are redeemable to Bitcoin L1 at any time. Check [Spark Status](https://spark.money/status) for real-time network health.
### What are the SSPs present?
Lightspark is running the first SSP on Spark. Anyone can become an SSP. Our goal is to maximize the diversity of SSPs to make the network more competitive and redundant.
### Does Spark have a token or a planned airdrop?
Spark does not have a token. Spark has not announced plans for any airdrop or token generation event. Be wary of scams claiming airdrops, giveaways, etc. If in doubt, refer to official Spark communication channels: Web: [https://spark.money](https://spark.money) Twitter: [@spark](https://x.com/spark)
### Does Spark support smart contracts?
No, Spark does not support smart contracts.
### Is Spark open source? Yes, Spark is open source. You can read the code or contribute directly [here](https://github.com/buildonspark/spark). ### Does Spark require KYC / KYB?
No. Spark is an open protocol that provides infrastructure for moving Bitcoin and tokens (e.g. stablecoins). Like Bitcoin, it does not impose any KYC requirements at the protocol level.
### What are the fees on Spark? | Transaction Type | Fee | | ------------------ | --------------------------------------------------------------------------------------------------------------- | | L1 to Spark | On-chain fee paid by user | | Spark to Spark | Free (small flat fee coming in 6-12 months) | | Spark to Lightning | 0.25% + routing fee | | Lightning to Spark | Free for the receiver. 0.15% for the sender (charged on routing nodes) | | Exit to L1 | `250 × sats_per_vbyte + 750` where `sats_per_vbyte` is the fee rate based on exit speed (slow, medium, or fast) | The L1 exit fee is flat and doesn't scale with amount. For small withdrawals, it may be a higher percentage. The fee varies based on exit speed (slow, medium, or fast). # Spark FROST Source: https://docs.spark.money/learn/frost-signing Spark employs a customized version of the FROST (Flexible Round-Optimized Schnorr Threshold) signing scheme to enable transaction signing by a required participant alongside a threshold group of signers known as Signing Operators (SOs). ## Problem Statement
In Spark, transactions require co-signing by a user and a group of Signing Operators (see [SOs](/learn/technical-definitions#spark-operator-so)), together SOs make up that Spark Entity [(SE)](/learn/technical-definitions#spark-entity-se). The signature must meet these requirements: * **Threshold Signing for SOs:** Only $t$ out of $n$ total SOs are needed to complete the signing process. * **Mandatory User Signing:** The user's participation is essential for a valid signature. * **Single Schnorr Signature:** The final signature must be aggregated into a single Schnorr signature, ruling out multi-signature schemes like 2-of-2. The final signature is the aggregate of the user's signature and the SOs' signatures, where the user is the required participant. Additionally, the cryptographic keys must support: * **Tweakability:** Keys can be modified by adding or multiplying a tweak value. * **Additive Aggregatability:** Given all shards of two keys (e.g., key1 and key2), it must be possible to derive all shards for their sum (key1 + key2). This document presents a modified FROST signing scheme tailored to address these needs in Spark.
## Key Generation ### User Key
The user independently generates a single key pair, denoted as $(sk_{user}, pk_{user})$, where $sk_{user}$ is the secret key and $pk_{user}$ is the corresponding public key.
### SO Keys
The Signing Operators (SOs) collaboratively generate a threshold signing key pair, denoted as $(sk_{so}, pk_{so})$, using a Distributed Key Generation (DKG) protocol. Each SO receives a Shamir secret share $ss_i$ of $sk_{so}$, configured with a threshold $(t, n)$, meaning any $t$ of the $n$ SOs can reconstruct the key. **Note:** Details of the secure DKG process are beyond the scope of this document. You can find more information in the [FROST paper](https://eprint.iacr.org/2020/852.pdf).
## Key Aggregation
The user's key and the SOs' key are combined into an aggregated key using additive aggregation: * The aggregated secret key is computed as $sk_{agg} = sk_{user} + sk_{so}$. * The corresponding public key is $Y = pk_{user} + pk_{so}$. All participants must know the aggregated public key $Y$.
## Pre-processing
Mirroring FROST's pre-processing phase, all participants generate and commit to nonces: * **SO Nonces:** Each SO generates nonce pairs $(d_{ij}, e_{ij})$ and their commitments $(D_{ij}, E_{ij})$, where $D_{ij} = g^{d_{ij}}$ and $E_{ij} = g^{e_{ij}}$, using a fixed generator $g$. * **User Nonces:** The user generates nonce pairs $(d_{user_i}, e_{user_i})$ and commitments $(D_{user_i}, E_{user_i})$, sharing these commitments with all SOs. These nonces enhance security during signing by preventing replay attacks.
## Signing Flow
The signing process involves the user, a coordinator, and a subset of SOs, proceeding as follows: 1. **Initiation:** * The user submits a signing request for message $m$ to a signing operator coordinator. 2. **Participant and Nonce Selection:** * The coordinator selects a set $S$ of $t$ participating SOs. * It compiles an unused nonce commitment list $B = \{(D_{ij}, E_{ij}) \mid i \in S\} \cup \{(D_{user_j}, E_{user_j})\}$ and broadcasts $B$ to all participants. 3. **Signature Share Calculation by SOs:** * Each SO $i \in S$ computes: * $\rho_i = H_1(i, m, B)$, using a hash function $H_1$. * Nonce contribution: $R_i = D_{ij} \cdot E_{ij}^{\rho_i}$. * Challenge: $c = H_2(R, Y, m)$, where $R = \prod_{i \in S} R_i$. * Signature share: $z_i = d_{ij} + e_{ij} \rho_i + \lambda_i ss_i c$, where $\lambda_i$ is the Lagrange coefficient for reconstructing $sk_{so}$. 4. **SO Signature Aggregation:** * The coordinator aggregates the SO shares: $z_{so} = \sum_{i \in S} z_i$. * It computes $R_{so} = \prod_{i \in S} R_i$, validates the partial signature, and sends $(R_{so}, z_{so})$ along with $B$ to the user. 5. **User Signature Share Calculation:** * The user computes: * $\rho_{user} = H_1(0, m, B)$ (assuming user index 0). * Nonce contribution: $R_{user} = D_{user_j} \cdot E_{user_j}^{\rho_{user}}$. * Full nonce: $R = R_{so} \cdot R_{user}$. * Challenge: $c = H_2(R, Y, m)$. * Signature share: $z_{user} = d_{user_j} + e_{user_j} \rho_{user} + sk_{user} c$. 6. **Final Signature:** * The user aggregates the final signature as $(R, z)$, where $z = z_{so} + z_{user}$.
## Key Tweaks
The SO key $\mathit{sk_{so}}$ is shared via Shamir secret sharing with the polynomial: $f(x) = \mathit{sk_{so}} + a_1 x + a_2 x^2 + \cdots + a_{t-1} x^{t-1}$ Each SO $i$ holds the share $(i, f(i))$.
### Additive Tweak
To tweak $\mathit{sk_{so}}$ by adding $t$ (i.e., $\mathit{sk_{so}'} = \mathit{sk_{so}} + t$): * Define the new polynomial $f'(x) = f(x) + t$ * Update each share to $f'(i) = f(i) + t$
### Multiplicative Tweak
For a multiplicative tweak by $t$ (i.e., $\mathit{sk_{so}'} = t \cdot \mathit{sk_{so}}$): * Update each share to $f'(i) = t \cdot f(i)$
### Secure Tweak Distribution
Directly sharing $t$ with SOs risks key exposure if the sender is an SO or colludes with one. Instead: * Construct a polynomial $g(x)$ of degree $t-1$ where $g(0) = t$ * Distribute $g(i)$ to each SO $i$ * Each SO updates their share: $f'(i) = f(i) + g(i)$ This method applies the tweak securely without revealing $t$.
## Key Split
When splitting a key into $n$ child keys (e.g., for transaction splitting), the property holds: $\mathit{Alice_{old}} + \mathit{SO_{old}} = \sum_{i=1}^n (\mathit{Alice_i} + \mathit{SO_i})$ Here, $\mathit{Alice_{old}}$ and $\mathit{SO_{old}}$ are the original user and SO keys, and $\mathit{Alice_i}$ and $\mathit{SO_i}$ are the child keys.
### Process
1. **User Key Splitting:** * The user (Alice) generates $n$ key pairs $(\mathit{sk_{Alice_i}}, \mathit{pk_{Alice_i}})$ for $i = 1$ to $n$ * Compute $\mathit{sum_{Alice}} = \sum_{i=1}^n \mathit{sk_{Alice_i}}$ * Calculate $\mathit{Tweak} = \mathit{sk_{Alice_{old}}} - \mathit{sum_{Alice}}$ 2. **Tweak Communication:** * The user sends $\mathit{Tweak}$ to the SOs. 3. **SO Key Splitting:** * The SOs use DKG to generate $n-1$ key pairs $(\mathit{sk_{SO_i}}, \mathit{pk_{SO_i}})$ for $i = 1$ to $n-1$ * Set the $n$-th key as $\mathit{sk_{SO_n}} = \mathit{sk_{SO_{old}}} - \left( \sum_{i=1}^{n-1} \mathit{sk_{SO_i}} - \mathit{Tweak} \right)$ 4. **Verification:** * The sum of child keys is: $\sum_{i=1}^n \mathit{sk_{Alice_i}} + \sum_{i=1}^n \mathit{sk_{SO_i}} = \mathit{sum_{Alice}} + \sum_{i=1}^{n-1} \mathit{sk_{SO_i}} + \mathit{sk_{SO_{old}}} - \sum_{i=1}^{n-1} \mathit{sk_{SO_i}} + \mathit{Tweak}$ * Substituting $\mathit{Tweak} = \mathit{sk_{Alice_{old}}} - \mathit{sum_{Alice}}$: $= \mathit{sum_{Alice}} + \mathit{sk_{SO_{old}}} + (\mathit{sk_{Alice_{old}}} - \mathit{sum_{Alice}}) = \mathit{sk_{Alice_{old}}} + \mathit{sk_{SO_{old}}}$ 5. **Shard Adjustment:** * For SOs' Shamir shares, the $n$-th key's share for SO $j$ is adjusted as: $f_{SO_n}(j) = f_{SO_{old}}(j) - \left( \sum_{i=1}^{n-1} f_{SO_i}(j) - \mathit{Tweak} \right)$
# Issue a Stablecoin Source: https://docs.spark.money/learn/issue-stablecoin Stablecoins are the killer app for blockchain payments, but they've been largely absent from Bitcoin. Spark changes that with native stablecoin issuance that's fast, compliant, and truly Bitcoin-native. ## Why Issue on Bitcoin? ### The Bitcoin Advantage | Bitcoin + Spark | Other Chains | | ------------------------------------------------------ | ------------------------- | | **Security**: Bitcoin's 15+ year security track record | Newer, less battle-tested | | **Neutrality**: No single entity controls Bitcoin | Often VC-controlled | | **Global Trust**: Bitcoin is recognized worldwide | Chain-specific adoption | | **Real Settlement**: Exit to Bitcoin L1 anytime | Bridge dependencies | ### Real-World Example: USDB [Brale](https://brale.xyz) launched **USDB**, the first Bitcoin-native stablecoin, on Spark. It demonstrates that regulated, compliant stablecoins can exist on Bitcoin with instant settlements and full reserve backing. ## Stablecoin Features on Spark ### Instant Transfers Token transfers settle in **under 1 second**. No block confirmations, no waiting. Recipients can use funds immediately. ### Zero Transfer Fees Spark-to-Spark token transfers are **free**. This enables micropayments, frequent transfers, and new use cases impossible with per-transaction fees. ### Built-in Compliance Controls Spark's [BTKN token standard](/learn/tokens/hello-btkn) includes compliance features required by regulated issuers: ```typescript theme={null} // Freeze tokens for a specific address (compliance action) await issuerWallet.freezeTokens({ tokenIdentifier: "btkn1...", sparkAddress: "sp1..." }); // Unfreeze when compliance is satisfied await issuerWallet.unfreezeTokens({ tokenIdentifier: "btkn1...", sparkAddress: "sp1..." }); ``` ### Burn and Redemption Issuers can burn tokens when users redeem for fiat, maintaining accurate supply: ```typescript theme={null} await issuerWallet.burnTokens({ tokenIdentifier: "btkn1...", tokenAmount: 50000n }); ``` ## Architecture Overview ``` ┌─────────────────────────────────────────────────┐ │ Your Stablecoin │ ├─────────────────────────────────────────────────┤ │ Issuer Wallet (You) │ │ - Create token with properties │ │ - Mint new supply │ │ - Freeze/unfreeze for compliance │ │ - Burn on redemption │ ├─────────────────────────────────────────────────┤ │ Spark Network │ │ - Instant transfers between users │ │ - Zero fees │ │ - Lightning interoperability │ ├─────────────────────────────────────────────────┤ │ Bitcoin L1 │ │ - Ultimate settlement layer │ │ - Unilateral exit capability │ └─────────────────────────────────────────────────┘ ``` ## Step-by-Step: Launch Your Stablecoin ### 1. Set Up Your Issuer Wallet ```typescript theme={null} import { IssuerSparkWallet } from "@buildonspark/issuer-sdk"; const { wallet: issuerWallet } = await IssuerSparkWallet.initialize({ mnemonicOrSeed: "your secure mnemonic phrase...", options: { network: "MAINNET" } }); ``` ### 2. Create the Token Define your stablecoin's properties: ```typescript theme={null} const DECIMALS = 6n; const toBaseUnits = (whole: number) => BigInt(whole) * 10n ** DECIMALS; const { tokenIdentifier, transactionHash } = await issuerWallet.createToken({ tokenName: "Your Dollar", tokenTicker: "YUSD", decimals: 6, // Standard for USD stablecoins maxSupply: toBaseUnits(1_000_000_000), // 1B YUSD (with 6 decimals) isFreezable: true, // Enable compliance controls returnIdentifierForCreate: true }); console.log(`Token created: ${tokenIdentifier}`); // btkn1qv6cps0n0ttm3p4gx62rzty4rjhzqwe5... console.log(`Announcement tx: ${transactionHash}`); ``` ### 3. Mint Initial Supply Mint tokens backed by your reserves: ```typescript theme={null} await issuerWallet.mintTokens({ tokenIdentifier, tokenAmount: toBaseUnits(1_000_000) // 1,000,000 YUSD }); ``` ### 4. Distribute to Users Transfer tokens to users instantly and for free: ```typescript theme={null} await issuerWallet.transferTokens({ tokenIdentifier, tokenAmount: toBaseUnits(100), // 100 YUSD receiverSparkAddress: "spark1user..." }); ``` ### 5. Handle Compliance Freeze tokens when required by regulations: ```typescript theme={null} // Freeze for compliance review await issuerWallet.freezeTokens({ tokenIdentifier, sparkAddress: "sp1suspicious..." }); ``` ### 6. Process Redemptions When users redeem for fiat, burn the tokens: ```typescript theme={null} await issuerWallet.burnTokens({ tokenIdentifier, tokenAmount: toBaseUnits(100) // 100 YUSD }); ``` ## Compliance Considerations ### What Spark Provides * **Freeze/Unfreeze**: Block specific addresses or amounts * **Burn**: Remove tokens from circulation on redemption * **Analytics**: Track holders and transactions * **Audit Trail**: Full transaction history ### What You Handle * **Reserve Management**: Maintain 1:1 backing * **KYC/AML**: Know your customers * **Regulatory Compliance**: Meet jurisdiction requirements * **Redemption Processing**: Convert tokens back to fiat ## Integration Options ### Option 1: Build Your Own Use the [Issuer SDK](/issuance/overview) directly to build custom issuance infrastructure. ### Option 2: Stablecoin-as-a-Service Partner with [Brale](https://brale.xyz) for turnkey stablecoin issuance with: * Reserve management * Banking relationships * Compliance infrastructure * Regulatory guidance ## Comparison: Bitcoin vs Other Chains | Feature | Spark (Bitcoin) | Ethereum | Solana | | ----------------------- | ------------------- | ------------------ | ---------------- | | **Settlement** | Instant | 12 sec blocks | 400ms blocks | | **Fees** | Zero | \$1-50+ gas | \~\$0.001 | | **Compliance Controls** | Native | Smart contract | Smart contract | | **Base Layer Security** | Bitcoin (15+ years) | Ethereum (8 years) | Solana (4 years) | | **Unilateral Exit** | Yes (to BTC L1) | No | No | ## Get Started Ready to issue a stablecoin on Bitcoin? 1. **Technical Setup**: [Issuer SDK Documentation](/issuance/overview) 2. **Quick Tutorial**: [Launch a Token in 5 Minutes](/quickstart/launch-token) 3. **Partner Option**: [Brale Stablecoin-as-a-Service](/integrations/brale) 4. **Talk to Us**: [Get in Touch](https://www.spark.money/contact) *** ## Learn More * [BTKN Token Standard](/learn/tokens/hello-btkn) * [Token Compliance Controls](/issuance/freeze-tokens) * [Token Analytics](/issuance/token-analytics) * [Brale Integration](/integrations/brale) # Lightning Interoperability Source: https://docs.spark.money/learn/lightning Lightning Payment
This provides Alice with true offline receive - she doesn't need to be online in order to receive the full Lightning payment. Additionally, Alice does not need to run a Lightning node and does not need to open any channels or perform any force closures. All operations here are atomic under the same 1/n or minority/n trust assumptions, as the operators must work together to recreate the preimage.
### Sending a Lightning Payment
1. Alice receives a Lightning invoice from Bob. 2. Alice makes an API call to the SSP, agreeing to conditionally transfer leaves upon payment completion. 3. SE locks transfers on Alice's specified leaves until a specified time. 4. SSP makes a Lightning payment to pay Bob's invoice. 5. SSP provides proof of Lightning payment to the SE. 6. SE finalizes the transfer of Alice's leaves to the SSP atomically. **Note:** If the specified time expires, the SE unlocks usage of Alice's leaves and control returns to her.
*** ### Routing Nodes If you're a Lightning node operator or LSP looking to establish channels with Spark, connect to our routing nodes: ``` 02a98e8c590a1b5602049d6b21d8f4c8861970aa310762f42eae1b2be88372e924@172.30.102.161:9735 039174f846626c6053ba80f5443d0db33da384f1dde135bf7080ba1eec465019c3@172.30.202.34:9735 ``` Opening channels to these nodes improves routing reliability for Lightning payments to and from Spark wallets. # Limitations / Attacks Source: https://docs.spark.money/learn/limitations
If a prior owner of a Spark leaf publishes the branch in a unilateral exit, there is a time limitation during which the correct owner needs to publish the correct leaf transaction. If the current owner does not do so during the window, then the attacker can claim the leaf UTXO. The SOs all have a copy of the signed transaction and can act as watchtowers on behalf of the current leaf owner. Further, the user can delegate watchtower functionality to any third party as the watchtower holds no toxic data. Additionally, depending on how the Spark is configured, this attack can be fairly costly for the attacker - they need to publish the entire branch (unless someone else has unilaterally closed much of the same branch) and CPFP each tree node.
### Loss of SE liveness
If any (or a minority of the Spark is configured for threshold) of the SOs lose liveness or lose their keys, the Spark will not be able to continue. Users will still be able to withdraw their funds through unilateral exits, but they will be unable to continue to send off-chain payments with Spark. This means that the entities comprising the SE should be carefully chosen to be highly available and low-latency since they are in the flow of UTXO transfers. The number of entities and the threshold of trust are configurable - for example, could require trusting ⅓ of the n entities in the SE, which would grant higher liveliness. This can be further mitigated by a single SE holding multiple public keys with the same threshold aggregate public key. That way if one host server is lost, the other cold key can be used, without the whole state-chain being closed on-chain.
# Manifesto Source: https://docs.spark.money/learn/manifesto Sixteen years ago, Bitcoin introduced something radical: a peer-to-peer electronic cash system. At the time it sounded like science fiction, but today it's a trillion-dollar asset. It's held by institutions, written about in central bank reports, and debated on Senate floors. Everyone knows what Bitcoin is; whether they use it or not, they know it's real. That alone makes it one of the most successful pieces of software ever written. The most well-known effort, Lightning, proved you can move Bitcoin quickly and cheaply without breaking its core trust guarantees. However, Lightning alone cannot scale to billions of users: its cumbersome UX, fragmented liquidity and high wallet-creation costs pose significant barriers. Meanwhile, stablecoins took off by solving a real problem rather than adhering to original visions or philosophical purity. Most people want money that doesn't fluctuate daily, that they can send, store and build with, and that settles instantly anywhere in the world. For developers, stablecoins became a superpower. A few lines of code enable wallets in any country and power new applications \[marketplaces, savings tools, creator payouts] without permission or bank interactions. Stablecoins transformed crypto from a speculative asset into a functional financial rail, moving more value today than PayPal. Yet none of this stablecoin activity occurs on Bitcoin, since it was not designed for such expressiveness and lacks the smart-contract capabilities of other networks. Still, Bitcoin's network remains the most resilient primitive: over 200 million users, more than 60 percent of crypto liquidity, and a transcendent brand. Developers will continue building on Bitcoin long after other networks fade. Today we introduce Spark, a Bitcoin-native Layer 2 for payments and settlement. No bridges, no custodians, only a lightweight signing protocol that makes digital cash, whether BTC or stablecoin, truly usable. Spark returns to first principles by enabling native applications on Bitcoin. First, it delivers the best UX ever seen on Bitcoin: whether for wallets, games or marketplaces, it offers the fastest, simplest and cheapest rails in crypto. Second, it unlocks new primitives such as stablecoins directly on Bitcoin as native, scalable, self-custodial assets rather than through wrappers or bridges. # Privacy Source: https://docs.spark.money/learn/privacy # Scalability Source: https://docs.spark.money/learn/scalability Spark's design enables it to be nearly infinitely scalable with minimal computational costs. This is unlocked through the lack of global consensus or propagation. Validator nodes (SOs) only need to gather for signing at moment of transfer. Transactions can be processed and settled independently of other transactions, making Spark fully parallelizable. SOs themselves can be scaled horizontally allowing them to process and sign more transactions as they scale. The goal of Spark is to be able to handle billions of concurrent users, and to do so with minimal fees and instant finality. # Self-Custody Lightning Source: https://docs.spark.money/learn/self-custody-lightning Lightning Network enables instant Bitcoin payments, but traditional Lightning wallets force a choice: run complex infrastructure or give up custody. Spark eliminates this tradeoff. ## The Lightning Custody Problem Traditional Lightning has a UX problem: ### Option 1: Run Your Own Node * Manage channels and liquidity * Keep node online 24/7 * Handle routing and rebalancing * Technical complexity deters most users ### Option 2: Custodial Wallet * Easy to use * **Someone else holds your Bitcoin** * Counterparty risk * Not your keys, not your coins Most users choose custodial wallets because self-custody Lightning is too hard. This undermines Bitcoin's core value proposition. ## Spark: Self-Custody Lightning Without the Complexity Spark gives you **true self-custody** with **native Lightning support**: ```typescript theme={null} // Send a Lightning payment - you keep custody const payment = await wallet.payLightningInvoice({ invoice: "lnbc100n1p3..." }); // Receive via Lightning - funds go to YOUR wallet const invoice = await wallet.createLightningInvoice({ amountSats: 10000, memo: "Payment for coffee" }); ``` No channels. No liquidity management. No node. Just Lightning payments where you control the keys. ## How It Works ### Sending Lightning Payments When you pay a Lightning invoice from Spark: 1. Your Spark wallet signs the payment authorization 2. Spark Service Providers (SSPs) route the payment over Lightning 3. The recipient gets paid instantly 4. **Your keys never leave your device** You pay a small routing fee (0.25% + network fees), but you maintain custody throughout. ### Receiving Lightning Payments When someone pays your Lightning invoice: 1. SSPs receive the Lightning payment 2. Funds are credited to your Spark wallet 3. **You can unilaterally exit to L1 anytime** The Bitcoin that arrives is real Bitcoin you control, not an IOU. ## Custody Comparison | Aspect | Custodial Lightning | Self-Custody (Node) | Spark | | --------------------------- | ------------------- | ------------------- | ----- | | **Who holds keys?** | Service provider | You | You | | **Can they steal funds?** | Yes | No | No | | **Channel management?** | N/A | Required | None | | **Liquidity requirements?** | N/A | Yes | None | | **Node uptime required?** | N/A | Yes | No | | **Unilateral exit?** | No | Yes | Yes | ## Real Self-Custody Guarantees What makes Spark Lightning truly self-custodial: ### 1. Your Keys, Your Bitcoin Private keys are generated and stored on your device. Spark operators never have access to move your funds without your signature. ### 2. Unilateral Exit If Spark operators disappear, go rogue, or refuse to cooperate, you can exit to Bitcoin L1 by broadcasting a pre-signed transaction. No one can stop you. ### 3. No Counterparty Risk Unlike custodial Lightning, there's no single entity that can lose, steal, or freeze your funds. The system is designed to be trustless. ## Use Cases ### For Users * **Daily payments**: Pay Lightning invoices without custody tradeoffs * **Receive tips/payments**: Get paid in Lightning, keep self-custody * **Store of value**: Hold Bitcoin with Lightning accessibility ### For Developers * **Wallets**: Build self-custody wallets with Lightning out of the box * **Payment apps**: Accept Lightning without custodial risk * **Exchanges**: Offer Lightning deposits/withdrawals with better security ## Getting Started ### 1. Create a Wallet ```typescript theme={null} import { SparkWallet } from "@buildonspark/spark-sdk"; const wallet = await SparkWallet.initialize({ mnemonicOrSeed: "your twelve word mnemonic phrase here..." }); ``` ### 2. Pay a Lightning Invoice ```typescript theme={null} const result = await wallet.payLightningInvoice({ invoice: "lnbc100n1p3..." }); console.log(`Paid ${result.amountSats} sats, fee: ${result.feeSats}`); ``` ### 3. Receive via Lightning ```typescript theme={null} const invoice = await wallet.createLightningInvoice({ amountSats: 50000, memo: "Self-custody Lightning payment" }); console.log(`Share this invoice: ${invoice.bolt11}`); ``` ## The Bottom Line Lightning Network is powerful but historically required either technical complexity or custody compromise. Spark removes this tradeoff: * **Keep your keys**: True self-custody, not custodial accounts * **No infrastructure**: No node, no channels, no liquidity * **Unilateral exit**: Always able to exit to L1 if needed * **Simple SDK**: Lightning payments in a few lines of code Self-custody Lightning is finally accessible to everyone. *** ## Learn More * [Lightning Integration Guide](/wallets/deposit-from-lightning) * [How Spark Works](/learn/tldr) * [Self-Custody Guarantees](/learn/sovereignty) * [Wallet SDK Reference](/api-reference/wallet-overview) # Sovereignty Source: https://docs.spark.money/learn/sovereignty Self-sovereignty is a core principle of Spark's design. It is an unparalleled quality that very few L2s can afford to its users. Spark, like Lightning, is narrowly designed for transfers of value, not general computation like other L2s. This focus makes achieving non-custodiality and true trustless exits much more straightforward.
How it works: * **Pre-signed Transactions:** Before you deposit funds into Spark, you work with the operators to create a pre-signed transaction that exits funds to the Bitcoin L1. Meaning you can always exit your funds if the operators go offline or become malicious after a transfer * **Timelocked Transactions:** When ownership of the leaf (in Spark, leaves work like UTXOs and represent the ownership of a UTXO on L1) is transferred (e.g., from Alice to Bob), a transaction is signed that gives Bob ownership of a leaf on L1. This exit transaction is encumbered by a timelock that is relative to its parent transaction. By the timelock being relative, it means that there is no limit to how long you can hold onto an unpublished leaf before it must go on-chain. * **Decrementing Timelocks**: If Bob then transfers the leaf to Charlie, a new transaction is signed for Charlie with a shorter timelock (e.g., 300 blocks instead of Bob's 400). Each subsequent transfer reduces the timelock, so the most recent owner's transaction becomes valid first. This ensures that the current owner always has a sooner exit than the previous owner. This can be supported by watchtowers to ensure that the current owner always exits before the previous owner. * **Fallback to L1:** If the Spark operators disappear, get compromised, attempt to censor, or refuse to cooperate, you (the current owner) can unilaterally broadcast to Bitcoin L1 the pre-signed transactions up to, and including, your leaf and claim the funds once the relative timelocks expire.
This mechanism ensures that you don't rely on any outside entity indefinitely. Even if it's a centralized component, you have an unshakeable and unconditional escape hatch to reclaim your funds on the Bitcoin L1, making your funds non-custodial in practice. The relativity of the timelock also makes it so the user does not have to refresh or exit funds to L1 on a set schedule. Exiting Spark can take as little as 100 blocks depending on the depth of their leaf. This differs substantially from the standard L2 exit mechanisms, in which users must rely on a centralized sequencer or bridge to exit, without absolute guarantees.
# Spark vs Liquid Source: https://docs.spark.money/learn/spark-vs-liquid Spark and Liquid Network are both Bitcoin Layer 2 solutions, but they take fundamentally different approaches to scaling. This comparison helps developers and users understand the tradeoffs. ## Quick Comparison | Feature | Spark | Liquid Network | | --------------------- | ---------------------------- | ----------------------------------- | | **Custody Model** | Self-custody (you hold keys) | Federated (11-of-15 multisig) | | **Exit Mechanism** | Unilateral (trustless) | Requires federation cooperation | | **Settlement Speed** | Instant (under 1 second) | \~2 minutes (2 block confirmations) | | **Transaction Fees** | Zero (Spark-to-Spark) | \~0.1 sat/vbyte | | **Lightning Support** | Native integration | Via submarine swaps | | **Token Standard** | BTKN (native) | Issued Assets | | **Trust Assumption** | 1-of-n honest operators | 11-of-15 federation honesty | ## Custody and Trust ### Spark: True Self-Custody On Spark, you always control your Bitcoin. The system uses a [1-of-n trust model](/learn/trust-model) where only one operator needs to behave honestly for your funds to remain secure. Most importantly, you can **unilaterally exit** to Bitcoin L1 at any time without anyone's permission. ``` Your Bitcoin on Spark = Real Bitcoin you can exit anytime ``` Spark achieves this through pre-signed exit transactions created before you deposit. If operators disappear or become malicious, you broadcast your exit transaction to Bitcoin and reclaim your funds after a timelock expires. ### Liquid: Federated Custody Liquid uses a **federated sidechain** model. Your Bitcoin is held in a 11-of-15 multisig controlled by federation members (exchanges, financial institutions). To move Bitcoin back to L1, you need cooperation from at least 11 federation members. ``` Your L-BTC on Liquid = IOU from federation members ``` If the federation fails or refuses to process your withdrawal, you cannot unilaterally exit. This is a fundamentally different trust model than Spark. ## Speed and Finality ### Spark: Instant Finality Spark transfers settle in **under 1 second** with immediate finality. There's no block time, no confirmations to wait for. When you send Bitcoin on Spark, the recipient can spend it immediately. ### Liquid: 2-Minute Blocks Liquid has \~1 minute block times and requires 2 confirmations for finality, meaning **\~2 minutes** for settlement. While faster than Bitcoin L1, this is still orders of magnitude slower than Spark. ## Fees ### Spark: Zero Fees Spark-to-Spark transfers are **completely free**. There are no gas fees, no per-transaction costs. You pay fees only when entering or exiting to L1 or Lightning. ### Liquid: Transaction Fees Liquid charges transaction fees (typically \~0.1 sat/vbyte). While low, these add up for high-frequency use cases like payments or micropayments. ## Lightning Integration ### Spark: Native Lightning Spark has **native Lightning Network integration**. You can send and receive Lightning payments without running a node, managing channels, or worrying about liquidity. Lightning is a first-class citizen. ### Liquid: Submarine Swaps Liquid connects to Lightning through submarine swaps, which adds complexity, fees, and trust requirements. It's not a native integration. ## Token Issuance Both platforms support token issuance, but with different approaches: ### Spark: BTKN Standard Spark's [BTKN token standard](/learn/tokens/hello-btkn) enables: * Instant, zero-fee token transfers * Built-in compliance controls (freeze/unfreeze) * No smart contracts required * Same self-custody guarantees as Bitcoin ### Liquid: Issued Assets Liquid Issued Assets allow token creation but inherit Liquid's federated trust model and 2-minute settlement times. ## When to Use Each ### Choose Spark When You Need: * **True self-custody** with guaranteed exit rights * **Instant settlements** for payments or trading * **Zero fees** for high-frequency transactions * **Lightning integration** without running infrastructure * **Regulated stablecoins** with compliance controls ### Choose Liquid When You Need: * Confidential transactions (amount hiding) * Established exchange integrations * Compatibility with existing Liquid infrastructure ## The Bottom Line Spark and Liquid solve Bitcoin scaling differently: * **Spark** prioritizes self-custody, instant finality, and zero fees. You never give up control of your Bitcoin. * **Liquid** prioritizes confidential transactions and exchange integrations, but requires trusting a federation. For developers building payments, wallets, or stablecoin applications, Spark's self-custody model and instant settlements typically provide a better foundation. *** ## Learn More * [How Spark Works](/learn/tldr) * [Self-Custody on Spark](/learn/sovereignty) * [Trust Model Explained](/learn/trust-model) * [Issue Stablecoins on Bitcoin](/learn/issue-stablecoin) # Understanding Spark's Operator Architecture Source: https://docs.spark.money/learn/spark-vs-others ## The Mental Model Shift If you're coming from Aptos, Sui, or EVM-based chains, you're used to a specific pattern: run a node, connect to it, and trust that the consensus mechanism guarantees validity. Spark works the same way—just with a different topology. | Traditional Chain | Spark | | :--------------------------------------- | :------------------------------------------ | | Your node validates transactions locally | SDK validates operator signatures locally | | Your node gossips with validators | Coordinator gossips with other operators | | Validators run BFT/PoS consensus | Operators run threshold signature consensus | | Finality requires quorum of validators | Finality requires threshold of operators | The security guarantee is equivalent: **no single party can forge transactions or lie about state**. *** ## How the Client Connects to Operators ### Operator Registry Every Spark SDK is configured with the **complete set of operators** upfront—not just a single API endpoint. This is analogous to how your Aptos/Sui node knows about the validator set. The SDK ships with hardcoded knowledge of: * Each operator's gRPC address * Each operator's identity public key * The threshold required for finality (e.g., 2-of-3) ``` Operator 0: https://0.spark.lightspark.com Identity: 03dfbdff4b6332c220f8fa2ba8ed496c698ceada563fa01b67d9983bfc5c95e763 Operator 1: https://spark-operator.breez.technology Identity: 03e625e9768651c9be268e287245cc33f96a68ce9141b0b4769205db027ee8ed77 Operator 2: https://2.spark.flashnet.xyz Identity: 022eda13465a59205413086130a65dc0ed1b8f8e51437043161f8be0c369b1a410 ``` This is **not** "just talking to an API." The SDK knows every participant in the consensus set and can verify their cryptographic signatures. *** ### Direct Verification When the SDK performs a sensitive operation (like generating a deposit address), it doesn't blindly trust the coordinator's response. It verifies cryptographic proofs from **all operators**. Here's what actually happens when you request a deposit address: 1. SDK sends request to coordinator 2. Coordinator forwards to all operators 3. Each operator signs the address with their identity key 4. Coordinator returns the address + all operator signatures 5. **SDK verifies every operator's signature locally** If even one operator's signature is missing or invalid, the SDK rejects the response. This is identical to how your Aptos node would reject a block without sufficient validator signatures. *** ## How Operators Achieve Consensus ### Coordinator-Based Fanout Spark uses a coordinator pattern where one operator acts as the entry point. This is purely an optimization—it reduces network round-trips. The coordinator has no special trust privileges. When a transaction is submitted: ``` User SDK → Coordinator → [All Operators in parallel] ↓ Collects signatures ↓ Returns to SDK ``` The coordinator calls `ExecuteTaskWithAllOperators()`, which spawns parallel gRPC requests to every operator. Each operator independently: * Validates the transaction * Checks its local state * Signs if valid, rejects if not *** ### Threshold Signatures (FROST) For Bitcoin-layer operations, Spark uses FROST (Flexible Round-Optimized Schnorr Threshold signatures). This is the same threshold signature scheme used by cutting-edge wallet infrastructure. The flow: 1. **Round 1**: Each operator generates and shares signing commitments 2. **Round 2**: Operators exchange partial signatures 3. **Aggregation**: Partial signatures combine into a single valid Schnorr signature No single operator can produce a valid signature alone. Exactly like a 2-of-3 multisig, but with a single on-chain signature. *** ### State Replication Every operator maintains its own complete database. There's no "primary" database that others read from. When a transaction is finalized: 1. Each operator writes to its local DB 2. All operators have identical state (eventually consistent, with strong guarantees on finalized txs) 3. The SDK can query **any** operator to verify state This is how Aptos validators work—each has a full copy of state, and you can query any of them. *** ## Token Transaction Consensus ### The Three-Phase Protocol Token transactions follow a strict protocol that requires participation from all (or threshold) operators: **Phase 1: Start** * User builds a partial transaction (inputs, outputs, amounts) * Sends to coordinator with their signature * Coordinator forwards to all operators * Each operator validates and reserves resources * Transaction enters `STARTED` state across all operators **Phase 2: Sign** * User confirms by requesting signatures * Coordinator collects signatures from all operators * Each operator signs the transaction hash with their identity key * Transaction enters `SIGNED` state **Phase 3: Reveal & Finalize** * Operators exchange revocation secret shares * Once threshold shares are collected, full secrets are recovered * Transaction enters `FINALIZED` state * State is now committed across all operators At every phase, the transaction must pass validation on every operator independently. A malicious coordinator cannot forge consensus—it would need to compromise the threshold of operators. *** ## Why You Can't Run Your Own Operator (Yet) This is a fair concern. Here's the honest answer: **Current state**: The operator set is permissioned, run by Lightspark and partners. This is similar to how Aptos launched with a permissioned validator set before opening up. **Why this exists**: 1. **Coordination complexity**: Operators must participate in DKG (Distributed Key Generation) ceremonies 2. **Uptime requirements**: Unlike validators that can be slashed, missing operators can halt transactions 3. **Network effects**: More operators means more latency (consensus fanout) **What you get instead**: 1. **Cryptographic verifiability**: Your SDK verifies all operator signatures 2. **Query any operator**: You can hit any operator's endpoint to verify state 3. **Unilateral exit**: You can always withdraw to L1 using the revocation secrets (no operator cooperation required) 4. **Transparency**: Operator identity keys are public, their signatures are verifiable *** ## Comparison to Other Chains ### vs. Aptos/Sui | Aspect | Aptos/Sui | Spark | | :------------------------ | :---------------- | :---------------------- | | Consensus | HotStuff BFT | Threshold signatures | | Validator/Operator count | \~100+ | 3 (currently) | | Can run your own node | Yes | Not yet (planned) | | Client verifies consensus | Via Merkle proofs | Via operator signatures | | Finality | \~1 second | Sub-second | Spark's operator model is intentionally smaller for latency. The tradeoff is fewer independent parties, but with cryptographic guarantees that are mathematically equivalent. ### vs. Lightning Network | Aspect | Lightning | Spark | | :----------- | :--------------------- | :--------------------- | | Counterparty | Single channel partner | Threshold of operators | | Fraud proofs | On-chain dispute | Revocation secrets | | Trust model | 1-of-1 | 2-of-3 (or n-of-m) | | Exit path | Force-close on L1 | Unilateral exit on L1 | Spark is strictly better than Lightning for counterparty risk—you're not trusting a single entity. ### vs. Rollups (Optimistic/ZK) | Aspect | Rollups | Spark | | :----------------------- | :--------------------------------- | :------------------------------------------------- | | Data availability | On L1/DA layer | Operators + exit path | | Verification | Fraud proofs / ZK proofs | Threshold signatures | | Withdrawal time | 7 days (optimistic) / instant (ZK) | Instant (cooperative) / \~1000 blocks (unilateral) | | Sequencer centralization | Often single sequencer | Multiple operators | Spark's trust model is similar to a rollup with a decentralized sequencer set. *** ## The Security Guarantees Let's be precise about what you're trusting: ### What a single malicious operator CAN'T do: * Forge your signature * Spend your tokens without your authorization * Create tokens out of thin air * Prevent your unilateral exit to L1 * Lie about the state (your SDK verifies signatures) ### What would require threshold collusion: * Censoring your transactions (but you can exit to L1) * Halting the network (liveness, not safety) ### What's cryptographically guaranteed: * All operator signatures are verified client-side * Transaction hashes are deterministic and verifiable * Revocation secrets enable unilateral exit *** ## Practical Implications for Your Integration 1. **Your SDK is not blindly trusting an API**. It's verifying cryptographic proofs from a known set of participants. 2. **You can independently verify state** by querying any operator directly. The SDK does this automatically for balance checks. 3. **Your users can always exit to L1**. Even if all operators collude against a user, the revocation secret mechanism ensures funds are recoverable. 4. **The operator set is transparent**. You know exactly who the operators are, and you can verify their signatures. 5. **This is the same trust model as early-stage L1s**. Aptos, Sui, and even Ethereum started with small, permissioned validator sets. *** ## Summary Spark's architecture is not fundamentally different from other chains. It's a **threshold signature consensus system** where: * Multiple independent operators must agree on state changes * Clients verify consensus via cryptographic signatures * No single point of failure for safety (only for liveness) * Unilateral exit path to Bitcoin L1 guarantees fund safety The main difference is **topology, not security model**. Instead of running your own validator that participates in block production, you run a client that verifies validator (operator) signatures. This is analogous to running a light client on Ethereum—you don't produce blocks, but you verify that blocks are correctly signed. For an issuer, this means: * Your integration is verifying real cryptographic consensus, not just trusting an API * The security model is threshold-based, similar to multisig but more sophisticated * The path to further decentralization exists (more operators, possibly including you) The architecture is sound. The operator set is small but growing. And the cryptographic guarantees are equivalent to what you'd expect from any other threshold-based system. # Technical Definitions Source: https://docs.spark.money/learn/technical-definitions > **Disclaimer** *This section can be bypassed if the reader understands statechains, which are used as a building block for what follows. This section provides a high-level overview of how statechains work. Please note that the description here is intentionally simplified and may not be entirely accurate; its purpose is to convey the core functionality of statechains in an accessible manner.* Statechains are a way to transfer ownership of a UTXO off-chain. In this example, all key signing operations are performed with [Schnorr](https://en.bitcoin.it/wiki/Schnorr). First, begin with the assumption that keys can be linearly added together to form a single key. We will start with two parties holding keys - one is the user Alice (A), and the other will be called the statechain entity (SE), which can be composed of any number of members but will be shown as a single entity below. $(SE+A=Key1)$ Each key is represented as a large random number. In this example, SE's key is represented by the value 100, and A's key is represented by the value 50. $(100+50=150=Key1)$ Now assume that a signature is simply multiplying the key by the hash of some object. The hash will also be represented by a number. In this example, our hash will be represented by the value 2. Thus, a signature of this hash will be: $((100+50)*2=300)$ To deposit funds into the statechain, Alice first creates a transaction to send money to a joint address controlled by the SE and herself (using their combined key of 150). However, she doesn't broadcast this transaction immediately. Instead, Alice and the SE collaborate to create and sign an exit transaction. This exit transaction is designed to transfer the coins directly to Alice, but it is locked with an absolute timelock set to an arbitrary amount of blocks in the future. Once Alice has the signed exit transaction in her possession, she then broadcasts the original deposit transaction. This creates a UTXO with a spending condition that requires the combined signature of SE and Alice. This careful sequencing ensures that Alice always has the ability to exit the statechain at any moment, even before the deposit is confirmed. This setup forms the foundation of the statechain mechanism. Alice now wishes to transfer ownership of this UTXO to Bob (B), but she wants to do so without putting the transaction on-chain. Bob's randomly generated key is represented by the number 40. He then calculates that the difference between his key and Alice's key is 10 (50 - 40 = 10). For Bob plus the SE to sign using the same value as Alice+SE (150), the SE needs to tweak their key by 10. So, the SE discards the old key (100) and only keeps a copy of the newly tweaked key 110 (100 + 10 = 110). Now, SE+Bob still equals 150 (110 + 40 = 150), so the UTXO can be spent by signing using the combined 150. This meets the original spending condition of the UTXO. Alice can't sign a new transaction anymore because the SE discarded the 100 key. If Alice tried to sign, Alice+SE would now equal 160 (50 + 110 = 160), which isn't a valid key to sign the transaction. This process effectively transfers control of the UTXO from Alice to Bob without requiring an on-chain transaction. During the key handoff process, the new owner collaborates with the SE to create and sign a new exit transaction. This new exit transaction features a lower timelock compared to the previous owner's exit transaction. For instance, if Alice's original exit transaction had a timelock of 100 blocks, Bob (the new owner) might set his timelock to 90 blocks. This process of decreasing timelocks continues with each subsequent transfer. When Bob transfers to Carol, her exit transaction might have a timelock of 80 blocks, and when Carol transfers to Dave, his could be set to 70 blocks. This decreasing timelock pattern ensures that the most recent owner always has the earliest opportunity to claim the UTXO on-chain. It's important to note that these exit transactions can only be executed on-chain once their respective timelocks expire. This mechanism provides a safeguard, allowing the current owner to claim the funds before any previous owners, while still maintaining the off-chain nature of the transfers until necessary. It's important to understand that this mechanism imposes a time limit on how long a UTXO can remain within a statechain. The current owner must claim the funds on-chain before the absolute timelock expires. If they fail to do so, previous owners will have the opportunity to claim the UTXO on-chain. This time constraint prevents indefinite off-chain existence of UTXOs in vanilla Statechains. ## Timelocks
Timelocks on Bitcoin can either be absolute or relative. An absolute timelock specifies a block height after which a transaction can be broadcasted. A relative timelock specifies a number of blocks after the parent transaction is included in a block before the child transaction can be broadcasted. The diagram below shows how statechains typically work:
In the above diagram, transactions 1 through 3 are all held off-chain. They all spend the same output from Txn0 and are replacements for each other. They use decreasing timelocks such that Txn4 can be put on-chain prior to Txn3, which could be put on-chain prior to Txn2. In this way, when ownership of the key controlling the funds is transferred to a new user, they can check the prior transactions and be assured that they can publish their new transaction first. These timelocks create a timebomb where the off-chain transaction with the lowest timelock will need to be put on-chain when its timelock expires, otherwise there is a risk that other transactions can claim the funds. This can, however, be eliminated by the following flow:
Txn1 spends the output of Txn0. Txn1 has no timelock. When users transfer keys, they transfer ownership of the key that encumbers the output of Txn1. The transactions that spend from Txn1's output look like the normal statechain transactions that include decreasing timelocks relative to their parent transaction. But because Txn1 is held off-chain, there is no absolute timebomb. The obvious problem with this is that if someone double-spends the output consumed by Txn1, then all of the leaves (Txn2..4) become invalid. To avoid this, the "SE" key is deleted by the SE - resulting in only one transaction ever generated for Txn0's output. The exact mechanics of this will be discussed later.
## Splitting a Leaf
We split leaves to spend smaller denominations. We do so by constructing a Bitcoin transaction that takes the parent UTXO as an input and produces multiple outputs, each controlled by a new key, which is split off from the original key. The sum of the new keys in all branches equals the original key, this allows for re-aggregation of leaves and more flexible leaf management without on-chain transactions. To split a leaf in Spark, we need to split its existing key into multiple keys such that the sum of the new keys equals the original key. This can be represented as follows: Original key: $(a_0=150)$ Split into two keys: $(a_1=100)$ $(a_2=50)$ Ensuring $(a_0= a_1+a_2)$ If the parent leaf is encumbered by the spending condition of $(a_0)$ it can then alternatively be spent using $(a_1+a_2)$ as they sum up to $(a_0)$ We want to ensure the following: $(\text{PrivKey}_{\text{User\_Old}} + \text{PrivKey}_{\text{SE\_Old}} = \sum_{i=1}^{n} \left( \text{PrivKey}_{\text{User}_i} + \text{PrivKey}_{\text{SE}_i} \right))$ 1. **User Key Splitting** * Generate $(n)$ new user private keys * Calculate the difference $(t)$ between old and new keys: $(t = \text{PrivKey}_{\text{User\_Old}} - \sum_{i=1}^{n} \text{PrivKey}_{\text{User}_i})$ 2. **SE Key Splitting** * Calculate the new SE key sum: $(P_{\text{SE\_New}} = P_{\text{SE\_Old}} + t)$ * Generate $(n-1)$ randomly generated SE private keys. * Calculate the final SE key to satisfy the key sum equality: $(P_{\text{SE\_New\_n}} = P_{\text{SE\_Old}} + t - \sum_{i=1}^{n-1} P_{\text{SE\_New\_i}})$ 3. **Branch Transaction Creation** * Create transaction with $(i)$ outputs, each locked by a new combined public key $(\text{SE}_n+\text{U}_n)$ 4. **Intermediate Branch and Exit Transactions for Each Leaf** * Create and sign intermediate branch and exit transactions for each new leaf using the new keys 5. **Key Deletion** * Securely delete original private keys 6. **Finalization** * Store the branch transaction off-chain. * Record the new leaves for use within Spark.
## Spark Tree
A Spark tree is made up of both Leaf-Transactions as well as Branch-Transactions.
We extend a UTXO within Spark into branches and leaves. Txn0 is spent by Txn1, which is held off-chain. Txn1 is the first branch transaction. As mentioned above, the absolute timelock timebombs are removed from the tree.
## Aggregation
When a transaction branches, the key that controls the parent input is divided into multiple child keys. These child keys are created in such a way that their sum equals the parent key. For example, Txn1 could be replaced by a new transaction, which would be valid if it's signed using the combined (aggregated) signatures from all the child keys at the bottom of the tree. In other words, the following keys, when aggregated, can spend Txn1: $( (B_{0.1.1.1.1} + SE_{0.1.1.1.1}) + (B_{0.1.2.1.1} + SE_{0.1.2.1.1}) + (B_{0.2.1.1.1} + SE_{0.2.1.1.1}) + (B_{0.2.2.1.1} + SE_{0.2.2.1.1}) )$ Which is equivalent to all the following: $(B_0 + SE_0)$ $(B_{0.1} + SE_{0.1} + B_{0.2} + SE_{0.2})$ $(B_{0.1.1} + SE_{0.1.1} + B_{0.1.2} + SE_{0.1.2} + B_{0.2.1} + SE_{0.2.1} + B_{0.2.2} + SE_{0.2.2})$ $((B_{0.1.1.1} + SE_{0.1.1.1}) + (B_{0.1.2.1} + SE_{0.1.2.1}) + (B_{0.2.1.1} + SE_{0.2.1.1}) + (B_{0.2.2.1} + SE_{0.2.2.1}))$
# TLDR Source: https://docs.spark.money/learn/tldr Spark is an off-chain scaling solution that builds on top of [Statechains](https://bitcoinops.org/en/topics/statechains/) to enable instant, extremely low fee, and unlimited self-custodial transactions of Bitcoin and tokens while also natively enabling sending and receiving via Lightning. Spark is not a rollup, nor a blockchain. There are no smart contracts nor VM. Spark is native to Bitcoin and its payments-focused architecture, enabling on-chain funds to be transacted at any scale, with near-instant speeds, and at virtually zero cost. At its core, Spark is a simple [shared signing protocol](/learn/frost-signing) on top of Bitcoin. It operates as a distributed ledger. There are no bridges, external consensus, or sequencers. Users can enter and exit the network freely, with funds always non-custodial and recoverable on L1 via a unilateral exit which depends on Bitcoin and no one else. Transactions on Spark happen by delegating on-chain funds via the shared signing protocol. Like the Lightning Network, Spark transactions work by delegating ownership of on-chain UTXOs between parties. The key difference: Spark introduces a set of signers, called Spark Operators (SOs), responsible for helping sign transactions as they happen. SOs cannot move funds without the users, who are required participants in any transfer. On L1, Spark state appears as a chain of multi-sigs, secured by users and SOs. On L2, it operates as a tree structure, mapping transaction history and balances in real time. Simple, fully native to Bitcoin, and open-sourced. For a system that scales Bitcoin as effectively as Spark does, it achieves the maximum possible level of trustlessness. Specifically, it maintains 1/n trust assumptions or minority/n depending on the setup. To learn more about the trust assumptions, read our [trust assumptions](/learn/trust-model) page. For a deeper technical explainer of how Spark works, including statechains, FROST signatures, and the leaf architecture, read [What is Spark? A Bitcoin Layer 2 Built on Statechains](https://www.spark.money/research/what-is-spark-bitcoin-layer-2). # Transaction Lifecycle Source: https://docs.spark.money/learn/tokens/broadcast-lifecycle How token transactions actually work under the hood. ## The basics A token transaction moves tokens from inputs to outputs. Three parties involved: 1. **You** (the wallet): construct and sign the transaction 2. **Spark Operators**: validate, co-sign, and commit 3. **Watchtowers**: watch for cheaters trying to double-spend on L1 The key thing: operators never have unilateral control. Every transaction needs your signature. They witness and enforce, but they can't move your tokens without you. ## The flow ``` 1. You build the transaction - Which outputs to spend - Who gets what - Sign it 2. Send to operators 3. Operators validate - Check your signatures - Check you own the inputs - Reserve revocation keys 4. Operators fill in the final details - Revocation commitments (for double-spend protection) - Withdrawal parameters (for L1 exit) 5. Operators commit - Threshold signs off - Watchtowers updated 6. Done - You get the final transaction - Recipient can spend immediately ``` One call handles the whole thing. It's atomic. Either everything succeeds or nothing does. ## Transaction types **Mint**: Issuer creates new tokens. No inputs spent. Just issuer signature authorizing creation. **Transfer**: Move tokens between owners. Inputs must equal outputs. **Create**: Register a new token type. Define name, ticker, decimals, max supply. ## Revocation: how double-spend protection works This is the clever part. When you receive tokens, each output has a "revocation commitment". This is a public key where the private key is split among operators. Nobody knows the full key. When you spend those tokens, operators release their key shares to you. Now you can reconstruct the full "revocation key" for the outputs you just spent. Why does this matter? If you try to cheat by broadcasting old outputs to L1: * Watchtowers see it * They have the revocation key (you gave it to them when you spent) * They sweep your funds immediately * You lose everything The economic incentive is simple: cheating costs more than you could gain. ## Validity window Transactions expire. You have up to 300 seconds (5 minutes) from when you create a transaction to when operators commit it. After that, it's rejected and you have to start over. This prevents stale transactions from clogging the system. ## What can go wrong | Problem | What happened | | -------------------- | ---------------------------- | | Insufficient balance | You don't have enough tokens | | Output already spent | Someone else spent it first | | Invalid signature | Wrong key | | Frozen | Issuer froze your address | | Transaction expired | Took too long, try again | ## Finality Once operators commit, it's done. Instant. Final. The recipient can spend immediately. No confirmations to wait for. The only "confirmation" that matters is the threshold of operators agreeing. Once they do, the transaction is irreversible at the Spark level. # Burning Source: https://docs.spark.money/learn/tokens/burning Burning destroys tokens permanently. Once burned, they're gone forever. ## How it works The issuer signs a burn transaction specifying which tokens to destroy. Operators validate and commit. Those tokens no longer exist. Total supply decreases. ## Why burn? **Redemptions.** User wants to cash out $100 of stablecoin. You send them $100, burn their 100 tokens. Supply stays matched to reserves. **Deflationary mechanics.** Some tokens burn a portion of each transaction or do periodic buyback-and-burns. **Mistakes.** Minted too many? Burn the excess. ## Who can burn Only the issuer can use the burn function directly. But there's a workaround: anyone can burn tokens by sending them to the burn address. ``` spark1pgssyqszqgpqyqszqgpqyqszqgpqyqszqgpqyqszqgpqyqszqgpqyqszykl0d2 ``` Tokens sent here are gone. The address exists but nobody has the key. This lets regular users burn their own tokens if they want to. ## Irreversibility Burning is permanent. There's no undo. Double-check amounts before you burn. If an issuer burns tokens by mistake, the only fix is minting new ones (if max supply allows). ## Burning vs freezing Freezing is temporary and reversible. Burning is permanent. If you're not sure, freeze first. You can always burn later. # Core Concepts Source: https://docs.spark.money/learn/tokens/core-concepts The building blocks of how tokens work on Spark. ## UTXO model Tokens on Spark work like Bitcoin. You have outputs. To spend, you consume outputs and create new ones. Math has to balance. Inputs equal outputs. ``` Alice has: 100 tokens (one output) Alice sends: 75 to Bob Result: Bob gets: 75 tokens (new output) Alice gets: 25 tokens (change output) Original output: consumed ``` This is different from account-based systems (like Ethereum) where you have a balance that gets debited. Here, you have discrete chunks of tokens that get spent and created. ## TTXOs A TTXO (Token Transaction Output) is a single token holding. It contains: * **Owner**: Who can spend it * **Token**: Which token (identified by a 32-byte identifier) * **Amount**: How many * **Revocation commitment**: For double-spend protection * **Withdrawal parameters**: For L1 exit Each TTXO maps to a pre-signed Bitcoin transaction. If operators disappear, you broadcast that transaction and claim your tokens on L1. That's the self-custody guarantee. ## Operators and threshold Spark Operators validate and co-sign transactions. A threshold of operators must agree for any transaction to go through. | Operators | Threshold | | --------- | --------- | | 2 | 2 | | 3 | 2 | | 5 | 3 | Formula: `(n + 2) / 2` rounded down. This is majority, not 1-of-n. What operators can do: * Delay transactions by refusing to sign * See transaction metadata * Run watchtowers What operators cannot do: * Move your tokens without your signature * Steal your tokens (even if all collude) * Block your exit to L1 ## Revocation The key innovation. How do you prevent double-spending in an off-chain system? When you receive tokens, each output has a revocation commitment. This is a public key where the private key is split among operators via DKG. Nobody knows the full key. When you spend those tokens, operators release their key shares. Now you can reconstruct the full revocation key for those outputs. If you try to cheat by broadcasting old (spent) outputs to L1: * Watchtowers detect it * They have the revocation key * They sweep your funds * You lose everything Cheating costs more than you could gain. That's the security model. ## DKG Distributed Key Generation. How operators create shared secrets without any single party knowing the full secret. Operators pre-generate batches of keys where: * Each operator holds a share * No single operator has the full private key * Threshold cooperation is needed to reconstruct These become revocation commitments for new outputs. ## Watchtowers Services that monitor Bitcoin L1 for attempted double-spends. Each operator runs one. If someone broadcasts a revoked output: 1. Watchtower detects it 2. Uses stored revocation key to sweep 3. Cheater loses everything Only one honest watchtower is needed. They're incentivized to catch cheaters. They get the bond. ## Relationship to Bitcoin Tokens on Spark aren't wrapped or bridged. They're native Bitcoin constructs. Exit transactions are valid Bitcoin transactions. Security comes from Bitcoin's proof-of-work. Operators can disappear and you still exit to L1. No bridge. No custodian. That's the point. # Freezing Source: https://docs.spark.money/learn/tokens/freezing Issuers can optionally freeze addresses. This is not enforced at the protocol level. It's a feature issuers can choose to implement. ## How it works The issuer signs a message saying "freeze this public key." Spark Operators add that key to their frozen list. Any transfer attempt from that key gets rejected. Unfreezing works the same way. Issuer signs, operators remove from list, transfers work again. ## What gets frozen Freezing targets a public key, not specific tokens. So: * All tokens owned by that key become untransferable * Any new tokens sent to that key are also frozen ## Optional by design Freezing is entirely optional. Issuers choose whether to use it. Some issuers may never freeze anyone. Others may need it for compliance reasons. The protocol doesn't require freezing. It's a tool available to issuers who want it. # Glossary Source: https://docs.spark.money/learn/tokens/glossary Quick definitions. ## BTKN Bitcoin Token. The token protocol on Spark. Fast, cheap, native Bitcoin tokens. ## Create Registering a new token type. You do this once per token. Define name, ticker, decimals, max supply. ## DKG Distributed Key Generation. How operators create shared secrets where no single party knows the full key. Used for revocation commitments. ## Final Token Transaction The complete transaction after operators fill in revocation commitments and withdrawal parameters. This is what gets stored and propagated. ## Mint Creating new token units. Only the issuer can mint. ## Partial Token Transaction What the wallet constructs before sending to operators. Missing revocation commitments and other fields that operators fill in. ## Revocation Commitment A public key embedded in each token output. The private key is split among operators. When you spend, operators release their shares so you can reconstruct the full revocation key. ## Revocation Key The private key for a revocation commitment. If you try to double-spend by broadcasting old outputs, watchtowers use this to sweep your funds. ## Spark Entity The group of Spark Operators running the network. ## Spark Operator A single node that validates, co-signs, and enforces transactions. Each one runs a watchtower. ## Threshold How many operators must agree. Formula: `(n + 2) / 2`. For 5 operators, threshold is 3. ## Token Identifier 32-byte unique ID for a token. Displayed in bech32m format. ## Transfer Moving tokens between owners. Inputs must equal outputs. ## TTXO Token Transaction Output. A single token holding. Owner, amount, token type, revocation commitment, withdrawal parameters. ## Unilateral Exit Withdrawing to Bitcoin L1 without operator cooperation. You broadcast pre-signed exit transactions, wait for locktime, claim tokens on L1. ## Validity Duration How long a transaction can wait before operators reject it. Max 300 seconds. ## Watchtower Service monitoring Bitcoin for double-spend attempts. Each operator runs one. If someone broadcasts a revoked output, watchtowers sweep their funds. ## Withdrawal Bond Sats you post when exiting to L1. Returned after locktime if legitimate. Swept by watchtowers if you're cheating. ## Withdrawal Locktime Blocks to wait after broadcasting an exit transaction. Gives watchtowers time to check and respond. # Hello, BTKN! Source: https://docs.spark.money/learn/tokens/hello-btkn BTKN (Bitcoin Token) YO ### What the BTKN? BTKN is the Bitcoin token standard we wished existed when we started building on Bitcoin. It's fast, it's incredibly cheap, and it moves natively on Bitcoin without any of the UX limitations that usually comes with tokens on Bitcoin. For developers, BTKN just makes sense. You can mint tokens in seconds, transfer them instantly, and your users never have to think about gas fees or wait for confirmations. They're native Bitcoin tokens that inherit all of Bitcoin's security guarantees. BTKN is everything we learned building on Bitcoin, distilled into one protocol. We took the best parts of Bitcoin's UTXO model, added instant settlement when you need it, and made sure you can always fall back to Bitcoin's base layer when security matters most. ### TL;DR In more technical terms: BTKN (Bitcoin Token) is our adaptation of the [LRC-20](https://github.com/akitamiabtc/LRC-20/blob/main/LRC20-current.pdf) protocol but specifically optimized for Spark. We discovered LRC-20 in Summer 2023 and were impressed by it. After pushing it to its limits, we realized we needed to make significant improvements and changes that warranted its own identity: BTKN. BTKN has two key components: Bitcoin (L1) as the settlement layer and Spark as the execution engine. On Bitcoin (L1), BTKN works by tweaking Bitcoin addresses in a way that embeds token data inside regular transactions. Bitcoin nodes process these transactions as usual, but BTKN-aware nodes can extract and verify token movements by watching how those keys are adjusted. On Spark, BTKN doesn't need to play the same tricks as L1. We don't tweak keys. Instead, they exist natively as metadata within Spark's TTXOs. When an issuer mints new tokens, they submit a transaction embedding the token's details (amount, ID, and properties) directly into a designated TTXO. Spark Operators validate these transactions, making sure they follow protocol rules and attesting to state changes. They then share this data with BTKN nodes, which continuously track transactions and keep Spark in sync with L1. BTKN tokens on Spark inherit the same L1 guarantees as Bitcoin. You can unilaterally exit your assets at any time. # A bit of history Source: https://docs.spark.money/learn/tokens/history If you've spent any time in crypto, you're used to the idea that tokens live inside smart contracts. On Ethereum, Solana, and most other chains, tokens aren't native; they're managed by programs that live on-chain, enforcing balances and rules. There are no smart contracts on Bitcoin, not in the way you think of them, at least. Bitcoin's base layer is intentionally simple. It doesn't execute arbitrary code. It doesn't have an account model. It doesn't let you just "deploy" a contract and start issuing tokens. So how do you put tokens on Bitcoin? The answer: metaprotocols, systems that extend Bitcoin by embedding additional information into regular Bitcoin transactions. Instead of deploying new logic to the chain, metaprotocols work on top of Bitcoin's existing rules, using its transactions as a foundation while keeping token data indexed off-chain. Over the years, we've seen a wave of attempts to bring tokens to Bitcoin: Ordinals, BRC-20, RGB, Taproot Assets, and more. Each one is impressive in its own right, but none have really scaled. What does scaling actually mean? Liquidity. Tens, if not hundreds, of billions of dollars secured under a single standard. We're incredibly opinionated on how this plays out: liquidity will consolidate around stablecoins. The Bitcoin standard that wins will be the one that integrates with every stablecoin issuer, meeting them exactly where they are. From the biggest players to the most niche issuers, adoption at scale is the only thing that matters. And yet, today? Not a single major stablecoin issuer has been onboarded. Zero. If someone tells you they're planning to? Look at the fine print. Implementation details have a way of disappearing when it's time to ship. We evaluated everything and came to a clear conclusion: we needed to build something new. We saw an opportunity to focus on making tokens that actually work the way people expect them to work. So we built BTKN (Bitcoin Token) from first principles. It combines the best ideas from across the ecosystem with new innovations that only became possible on Spark. The result is a token standard that's native to Bitcoin, optimized for speed, and ready to onboard any asset types on Bitcoin. # Minting Source: https://docs.spark.money/learn/tokens/minting Minting creates new tokens. Only the issuer can mint. ## How it works The issuer signs a transaction saying "create X tokens and give them to Y address." Spark Operators verify the signature, confirm it doesn't exceed max supply, and commit the transaction. New tokens now exist. That's it. No on-chain transaction. No gas. Instant. ## What gets checked | Check | Why | | ---------------- | --------------------------------------------- | | Issuer signature | Only the issuer key can create tokens | | Token exists | You have to create (register) the token first | | Max supply | Can't mint beyond the cap you set | | Not frozen | Frozen issuers can't mint | ## Mint vs Create People confuse these. **Create** registers a new token type. You do this once. You define the name, ticker, decimals, max supply. **Mint** creates new units of an existing token. You do this whenever you want more tokens in circulation. ## Supply If you set a max supply during creation, you can't mint beyond it. If you didn't set one, you can mint forever. Most stablecoin issuers mint on-demand: user deposits \$100, issuer mints 100 tokens. Supply grows with demand. ## Where tokens go By default, minted tokens go to the issuer's address. You can also mint directly to someone else's address. Useful for airdrops or when you're minting in response to a deposit. ## Common patterns **Premint everything**: Mint total supply upfront, then distribute via transfers. Simple accounting. **Mint on demand**: Mint as users onboard. Better for stablecoins where supply should match deposits. **Batch mint**: Send to multiple recipients in one transaction. More efficient. # Transferring Source: https://docs.spark.money/learn/tokens/transferring Transfers move tokens between people. They're instant and final. ## How it works You have token outputs (TTXOs). To send tokens, you spend some outputs and create new ones for the recipient. If you're sending less than what's in your outputs, you get change back. ``` Alice has: 100 tokens (one output) Alice sends: 75 tokens to Bob Result: Bob gets: 75 tokens (new output) Alice gets: 25 tokens (change output) Original 100-token output: gone (revoked) ``` The math has to balance. Inputs = outputs. No tokens created or destroyed. ## Why it's instant No on-chain transaction. No block confirmations. The transfer happens the moment Spark Operators commit it. Bob can spend those tokens immediately. ## Input selection When you have multiple outputs, the SDK picks which ones to spend. Two strategies: **Small first** (default): Spends your smallest outputs first. Over time, this consolidates your holdings into fewer, larger outputs. **Large first**: Spends your biggest outputs first. Useful if you want to preserve small outputs for exact-amount payments later. You can also pick specific outputs manually if you care. ## Sending to multiple people You can send to multiple recipients in one transaction. More efficient than separate transfers. One round trip to operators instead of many. ## Consolidation If you have lots of small outputs, transfer everything to yourself. You'll end up with one output instead of many. Cleaner, faster, cheaper to exit to L1 if you ever need to. ## Limits | Limit | Value | | --------------------------- | ----------- | | Max inputs per transaction | 500 | | Max outputs per transaction | 500 | | Validity window | 300 seconds | If you need to spend more than 500 inputs, split into multiple transactions. ## Finality Once operators commit, it's done. The only way to "reverse" is if the recipient sends tokens back voluntarily. There's no chargeback, no undo, no dispute process at the protocol level. # Transfers on Spark Source: https://docs.spark.money/learn/transfers Leaf ownership is transferred by adjusting the SE's key such that the combined key (SE+User) remains the same before and after the transfer, but control shifts from the sender to the receiver.
Original Combined Key:\ $(\text{PubKey}_{\text{Combined}} = \text{PubKey}_{\text{Sender}} + \text{PubKey}_{\text{SE}})$ After Transfer:\ $(\text{PubKey}_\text{Combined} = \text{PubKey}_\text{Receiver} + \text{PubKey}'_\text{SE})$ We do this by tweaking the SE key with the difference between the sender and receiver private keys. Note that no private keys are ever revealed through this process.
### Transfer Process
To transfer ownership of a leaf, the SE adjusts its key so that the combined public key (SE + User) remains the same, but control shifts from the sender to the receiver. This is achieved by the SE tweaking its key by the difference between the sender's and receiver's private keys. By doing so, the SE's new key, when combined with the receiver's key, equals the original combined key, ensuring the UTXO can still be spent under the original spending conditions. This key adjustment allows the transfer of control without revealing any private keys or requiring an on-chain transaction. The SE securely deletes the old private key associated with the sender and collaborates with the receiver to sign a new exit transaction with a lower timelock than the previous one. This process effectively transfers control of the leaf to the receiver, who now has full control over the UTXO.
# Trust Model Source: https://docs.spark.money/learn/trust-model Spark operates under a “moment-in-time” trust model, meaning that trust is only required at the time of a transaction. As long as at least one (or a configurable threshold) of the Spark operators behaves honestly during a transfer, the system ensures perfect forward security. Even if operators are later compromised or act maliciously, they cannot retroactively compromise past transactions or take money from users. This security is achieved through a fundamental action required of SOs: forgetting the operator key after a transfer. If an operator follows this protocol, the transferred funds are secure, even against future coercion or hacking. Spark strengthens this model by using multiple operators, ensuring that as long as one (or the required threshold) deletes their key, users remain secure and have perfect forward security. This approach contrasts with most other Layer 2 solutions, where operators retain the ability to compromise user funds until they exit the system. The only exception is Lightning, which requires no trusted entities at all. # Welcome to Spark Source: https://docs.spark.money/learn/welcome Spark Introduction Spark is the fastest, lightest, and most developer-friendly way to build financial apps and launch assets, **natively on Bitcoin.** Spark lets developers move Bitcoin and Bitcoin-native assets (including stablecoins) instantly, at near-zero cost, while staying fully connected to Bitcoin's infrastructure. Spark has no bridge and doesn’t wrap your asset: everything is native to Bitcoin. ## Choose Your Path
# What to Build Source: https://docs.spark.money/learn/what-to-build
Spark is completely agnostic and unopinionated about what people build on top of it. You do whatever you want with it. The goal is to give developers low-level primitives, nothing more. That said, we're always buzzing with ideas about what's possible.
* Connect to >100 endpoints through Lightning * Enable instant, dirt-cheap Bitcoin flows * Enable your users to send Bitcoin privately via Lightning * Issue your own branded reward/point token on Spark * Enable native Bitcoin DCA for your users * Give Bitcoin cashback to users performing a specific on-chain action (e.g. swap, send, pay with card, etc.) * Give Bitcoin yield/interest for users holding a specific asset (e.g. BTC, stablecoin etc.) * Launch your branded stablecoin (e.g. via brale.xyz) * Enable your users to buy/sell native bitcoin in a self-custodial way (e.g. via flashnet.xyz) * \[coming soon] Connect to the cheapest on-off ramp network * \[coming soon] Issue debit cards and virtual accounts for your users * Connect to >100 endpoints through Lightning * Enable instant, dirt-cheap Bitcoin deposits and withdrawals * Issue your branded stablecoin (e.g. via Brale.xyz) * Connect to one of the largest and most competitive cross-border payment networks * Open new geographies without compliance or regulatory overhead * Launch on the most distributed crypto network and liquidity * Enable on-chain Bitcoin applications (hedging for traders, lending & swapping, payouts to miners etc.) * Give Bitcoin cashback to users performing a specific on-chain action (e.g. send a payment) * Provide Bitcoin yield (instead of USD pro-rata) to users holding your stablecoin * \[coming soon] Make your stablecoin interoperable with any domestic payment systems * \[coming soon] Monetize and distribute any stablecoin activity * \[coming soon] Let your stablecoin move privately * Receive Bitcoin 100x faster and cheaper through Lightning * Let your merchant's customers pay privately via Lightning * Incentivize custom payment behaviors for users * Connect to faster and cheaper on-off ramps for your merchants * AI agents with Spark wallets * Gate AI scraping behind Spark powered paywalls * AI agents that can execute trades on your behalf # Why Bitcoin Source: https://docs.spark.money/learn/why-on-bitcoin
We get this question a lot: why build Spark on Bitcoin? Why not launch a new L1 or L2? Why take on the challenge of bringing stablecoins to Bitcoin? The short answer: we're Bitcoin pragmatists. Building a network is one of the hardest things you can do, and if you're going to do it, you need every structural advantage on your side. Bitcoin gives us that.
### 1. Bitcoin is the only network with network effects
Distribution is everything. You can build the fastest, cheapest L1, but if you can't get it into people's hands, it doesn't matter. Bitcoin has over 300 million users. It's natively integrated into the largest wallets, banks, and fintech apps. That means Spark is instantly interoperable with hundreds of millions of endpoints from day one.
### 2. Bitcoin is where the money is
Liquidity is the lifeblood of any financial network. If you're building in crypto, you need to bootstrap liquidity before your network can take off. With Bitcoin, that problem is already solved —> 60% of all crypto liquidity is sitting in BTC.
### 3. People actually want to accumulate Bitcoin, not sell it
Most crypto tokens get farmed, dumped, and forgotten. Bitcoin is different. People stack it, not sell it. It's scarce, it's deflationary, and it has the strongest digital gold thesis. For builders, this is amazing. Bitcoin gives you an instant edge for rewards, retention, and engagement. No need to convince users —> it's already the asset they want. That unlocks entirely new product experiences, like BTC-based yield, cashback, and incentives that feel like wealth accumulation rather than just another points system.
### 4. Bitcoin is the only crypto your grandmother has heard of
Ask your grandmother if she's heard of Bitcoin. Now ask her about any other crypto project. This matters. When you build on Bitcoin, you're not starting from scratch —> you're tapping into the only crypto brand with mainstream trust and recognition.
### 5. Bitcoin is the only network that will outlive every timeline
In 1 year, 5 years, 10 years, 50 years, or even 100 years, Bitcoin will still be running. Can you confidently say the same for other scaling solutions? As a builder, do you want to wake up one day and realize that all the liquidity your users rely on is at risk of disappearing?
***
Think we're wrong? Roast us. (Tweet or DM [@spark](https://x.com/spark)). Show us another chain that beats Bitcoin on all five of these dimensions. If you can, we'll listen.
# Withdrawals to L1 Source: https://docs.spark.money/learn/withdrawals There are two ways to exit Spark and withdraw funds to Bitcoin L1: cooperative and unilateral exits. ### Cooperative Exit
In a cooperative exit, the user and SE work together to create a new transaction that spends directly from the deposit transaction to the user's desired Bitcoin address. This is the most efficient and cost-effective method, as it requires only a single on-chain transaction. 1. **Transaction Creation:** The user and SE collaborate to create a transaction that spends from the deposit transaction's output directly to the user's desired Bitcoin address. 2. **Signing:** Both the user and SE sign this transaction using their respective private keys. 3. **Broadcasting:** The signed transaction is broadcast to the Bitcoin network. 4. **Confirmation:** Once confirmed, the funds are available in the user's Bitcoin address.
### Unilateral Exit
If the SE is unavailable or uncooperative, the user can perform a unilateral exit using the pre-signed exit transactions created during the deposit and transfer processes. 1. **Broadcasting Branch Transaction:** The user broadcasts the branch transaction that was signed during the deposit process. 2. **Waiting for Confirmation:** Once the branch transaction is confirmed, the user waits for the relative timelock to expire. 3. **Broadcasting Exit Transaction:** After the timelock expires, the user broadcasts the exit transaction that was signed during the deposit or most recent transfer. 4. **Confirmation:** Once confirmed, the funds are available in the user's Bitcoin address.
The unilateral exit process ensures that users always have control over their funds, even if the SE becomes unavailable or malicious. This is a critical aspect of Spark's self-custody design, providing users with the security and sovereignty that Bitcoin was designed to offer.
# 0-Conf Deposits Source: https://docs.spark.money/learn/zero-conf-deposits Waiting for Bitcoin confirmations is painful. Traditional deposits require 1-6 block confirmations (10-60 minutes) before funds are usable. Spark's 0-conf deposits eliminate this wait entirely. ## The Confirmation Problem When you deposit Bitcoin to most platforms: ``` Send BTC → Wait 10+ minutes → 1 confirmation → Maybe wait more → Funds available ``` This delay exists because of **double-spend risk**: the sender could broadcast a conflicting transaction that steals back the Bitcoin before it confirms. Platforms wait for confirmations to ensure the deposit is final. ## 0-Conf: Instant Credit Spark's 0-conf deposits work differently: ``` Send BTC → Funds available immediately → Spark handles the risk ``` Your deposit is credited **instantly**, even before it appears in a Bitcoin block. You can use your funds right away. ## How It Works ### 1. You Send Bitcoin to Your Deposit Address ```typescript theme={null} const depositAddress = await wallet.getSingleUseDepositAddress(); // Send BTC to this address from any wallet/exchange ``` ### 2. Spark Detects the Unconfirmed Transaction The moment your transaction hits the Bitcoin mempool, Spark sees it. ### 3. Instant Credit (0-Conf) If the deposit qualifies for 0-conf, your Spark balance is credited immediately: ```typescript theme={null} // Balance updates instantly, no confirmation wait const balance = await wallet.getBalance(); console.log(`Available now: ${balance.bitcoin.total} sats`); ``` ### 4. Spark Absorbs the Risk Spark Service Providers (SSPs) take on the double-spend risk. If someone attempts a double-spend, the SSP absorbs the loss, not you. ## Risk Analysis ### Why This Is Safe for You * **You get instant access**: Use your Bitcoin immediately * **No risk to you**: If a double-spend occurs, Spark absorbs it * **Same self-custody**: You maintain unilateral exit rights ### How Spark Manages Risk Spark uses multiple signals to assess 0-conf safety: | Signal | What It Means | | --------------------- | ------------------------------------------ | | **RBF Flag** | Replace-by-fee enabled = higher risk | | **Fee Rate** | Low fees = easier to replace | | **Input Age** | Older inputs = less likely to double-spend | | **Amount** | Larger amounts may require confirmation | | **Sender Reputation** | Known patterns reduce risk | For deposits that don't qualify for 0-conf (high risk), standard confirmation requirements apply. ## Use Cases ### Exchanges and Trading * Deposit BTC and trade immediately * No waiting for confirmations during volatile markets * Better UX than competitors ### Payment Applications * Accept Bitcoin payments instantly * Credit user accounts without delay * Enable real-time commerce ### Wallets * Seamless deposit experience * Funds available the moment they're sent * Competitive with custodial solutions ## Developer Integration ### Check Deposit Status ```typescript theme={null} // Get deposit address const address = await wallet.getSingleUseDepositAddress(); // Monitor for deposits (including 0-conf) wallet.on('deposit', (deposit) => { console.log(`Deposit detected: ${deposit.amount} sats`); console.log(`Confirmed: ${deposit.confirmed}`); console.log(`0-conf credited: ${deposit.instantCredit}`); }); ``` ### Query Pending Deposits ```typescript theme={null} const deposits = await wallet.getPendingDeposits(); for (const deposit of deposits) { console.log(`${deposit.amount} sats - ${deposit.confirmations} confirmations`); } ``` ## Comparison: Deposit Times | Platform Type | Time to Use Funds | | ------------------------ | ------------------------ | | **Spark (0-conf)** | Instant | | Traditional Exchange | 10-60 minutes (1-6 conf) | | Hardware Wallet | 10+ minutes (1 conf) | | Lightning (channel open) | 10-60 minutes | ## Limitations ### Not All Deposits Qualify Some deposits still require confirmations: * Very large amounts * High-risk transaction patterns * RBF-enabled transactions with low fees ### Coming Soon 0-conf deposits are currently in development and rolling out progressively. Check the [Features page](/start/overview) for current status. ## Security Model ### For Users Your funds remain self-custodial throughout. The 0-conf credit is real Bitcoin that you can: * Transfer on Spark instantly * Withdraw to L1 (after confirmation) * Exit unilaterally if needed ### For SSPs Service providers who offer 0-conf take calculated risk in exchange for: * Better user experience * Competitive advantage * Fee revenue from deposits ## The Bottom Line 0-conf deposits solve Bitcoin's UX problem without compromising self-custody: * **Instant**: No more waiting for confirmations * **Safe for users**: Spark absorbs double-spend risk * **Self-custodial**: Your keys, your Bitcoin * **Production-ready**: Battle-tested risk assessment This is how Bitcoin deposits should work. *** ## Learn More * [Deposit from L1 Guide](/wallets/deposit-from-l1) * [How Spark Works](/learn/tldr) * [Deposit API Reference](/api-reference/wallet/claim-deposit) * [Current Features](/start/overview) * [Zero-Conf Bitcoin: The Complete Guide](https://www.spark.money/research/zero-conf-bitcoin-explained) # Create a Wallet Source: https://docs.spark.money/quickstart/create-wallet Create a self-custodial Bitcoin wallet on Spark, fund it, and send your first payment. Create a Wallet Create a Wallet Get up and running with Spark in minutes. This quickstart guide will walk you through creating your first Spark wallet, funding it with Bitcoin, and making your first transaction on Spark. *** Launch the Spark CLI with a single command. Requires Node.js 20+. ```bash CLI theme={null} npx @buildonspark/cli ``` Or install it globally: ```bash CLI theme={null} npm install -g @buildonspark/cli spark-cli ``` Create your first wallet on Spark: ```bash CLI theme={null} > initwallet ``` Example output: ```bash CLI theme={null} Mnemonic: please broccoli hole unfold trigger novel marriage come invest need ostrich never Network: REGTEST ``` Important: Keep your mnemonic securely stored offline. Anyone with access to it can take full control of your funds. To recover an existing wallet: ```bash CLI theme={null} > initwallet ``` Generate a static deposit address: ```bash CLI theme={null} > getstaticdepositaddress bcrt1pz5sxkd4eaycla7av8c9avmdleyertmhkh2zf60vrmn346wwnjayq8phsra ``` You’ll get a static Bitcoin L1 deposit address linked to your wallet, it can’t be changed. Try it out in our Regtest environment using some monopoly money from the [faucet](https://app.lightspark.com/regtest-faucet). After depositing Bitcoin from the faucet, get the associated transaction hash: ```bash CLI theme={null} > getlatesttx # Example: > getlatesttx bcrt1pz5sxkd4eaycla7av8c9avmdleyertmhkh2zf60vrmn346wwnjayq8phsra 2c5ccdc5852eb23662344c142970a1d96f2bed539a1be074cbbff65411ba3270 ``` Once the transaction is confirmed on-chain, you can claim it. To claim your Bitcoin on Spark, start by requesting a quote: ```bash CLI theme={null} > claimstaticdepositquote # Example: > claimstaticdepositquote 2c5ccdc5852eb23662344c142970a1d96f2bed539a1be074cbbff65411ba3270 ``` With the quote info ready, you can now claim your Bitcoin. ```bash CLI theme={null} > claimstaticdeposit # Example: > claimstaticdeposit 2c5ccdc5852eb23662344c142970a1d96f2bed539a1be074cbbff65411ba3270 3901 3045022100a69a1c58893947e46d4310d967c2d7e96f539e3e2656e1c76cbce1b96afc149102200a0aef9518cd0a76c9baecbf60afd52ca4c30d2d8025bcba70288a9df6a39e63 ``` Verify that your balance has increased: ```bash CLI theme={null} > getbalance Sats Balance: 3901 ``` Create a new wallet and fetch its Spark Address. The Spark Address is a static address that can be shared with payers to receive Bitcoin. ```bash CLI theme={null} # Wallet 2 > initwallet Mnemonic: repeat entry hazard estate normal relief pledge act online raw pull bean Network: REGTEST > getsparkaddress sparkrt1pgss95264kxj85cqz8g2cj5f66wy5yhhc0je35f8923de7mk8ttvls7a7x9vp9 ``` Now send from Wallet 1: ```bash CLI theme={null} # Wallet 1 > initwallet > sendtransfer # Example: > sendtransfer 200 sparkrt1pgss95264kxj85cqz8g2cj5f66wy5yhhc0je35f8923de7mk8ttvls7a7x9vp9 ``` That's it. The transfer’s complete. Run `getbalance` on each wallet to confirm the Bitcoin moved. Spark is fully compatible with Lightning. Let's test it by sending a Lightning payment between our 2 wallets. ```bash CLI theme={null} # Wallet 2 - Create invoice > initwallet > createinvoice # Example: > createinvoice 1000 Spark is awesome! ``` ```bash theme={null} # Wallet 1 - Pay invoice > initwallet > payinvoice # Example: > payinvoice lnbcrt10u1p5pqphup[...]cpkql23a 200 ``` The payer specifies the maximum fee they're willing to pay for the invoice (in sats). The SDK then finds the route with the lowest possible fees but it will never exceed that limit. Use `getbalance` on each wallet to verify the payment. You can withdraw Bitcoin from Spark to Bitcoin by sending them to an L1 address. In this example, we’ll withdraw Bitcoin from Wallet 1 and send it to Wallet 2’s L1 address. ```bash CLI theme={null} # Wallet 2 - Get deposit address > initwallet > getstaticdepositaddress ``` Check withdrawal fee: ```bash CLI theme={null} # Wallet 1 - Check fees and withdraw > initwallet > withdrawalfee # Example: > withdrawalfee 15000 bcrt1p6tx52amnr448lv8vyr7fumqt3c2qmlkg4hgvj8swxfcz8cayukvqwk9mu6 ``` If it’s acceptable: ```bash CLI theme={null} > withdraw # Example: > withdraw 15000 bcrt1pslvlzmkwz8f42u8vr2fkhdhyzyh2x5cwy8l0lpdnqr4ptsjrefrq0sd0gl FAST ``` Once the transaction is confirmed, use the same claim process described above to claim your Bitcoin on Spark. # Launch a Token Source: https://docs.spark.money/quickstart/launch-token Create a token on Spark, mint supply, and send your first token transfer. Launch a Token Launch a Token Create and launch your first token on Spark in minutes. This quickstart will walk you through setting up your wallet, issuing a token, minting supply, and sending your first transfer. All on Spark, without touching L1 or waiting for confirmations. *** Launch the Spark CLI with a single command. Requires Node.js 20+. ```bash CLI theme={null} npx @buildonspark/cli ``` Or install it globally: ```bash CLI theme={null} npm install -g @buildonspark/cli spark-cli ``` Create a wallet to serve as the issuer of your token: ```bash CLI theme={null} > initwallet ``` Example output: ```bash CLI theme={null} Mnemonic: rhythm torch mistake reopen device surround cabin wish snake better blind draft Network: REGTEST ``` Important: Keep your mnemonic safe and offline. The wallet you create here becomes the root of trust for your token. It's the only wallet authorized to mint or burn supply. Launch your token directly on Spark: ```bash CLI theme={null} > createtoken # Example: > createtoken MyToken MTK 6 1000000 false ``` Human-readable name of the token. Short symbol for the token (typically 3–6 characters). Decimal precision used for display and arithmetic. Hard cap on total mintable supply. Whether the issuer can freeze or unfreeze token balances. ```bash CLI theme={null} TxID: 9c5d17acb7fe203ab6342b3f556b4e5f3b4dabe1bca3b0b98b1b3d9cf7d92c4d Token ID: btkn1qv6cps0n0ttm3p4gx62rzty4rjhzqwe5eqv2wlt ``` Spark transaction ID of the token creation. Bech32m token identifier (e.g., btkn1...) used in later operations. Your token now exists on Spark. It's ready for minting and transfers. Bring new tokens into circulation. Only the issuer wallet can mint, up to the max you set before. ```bash CLI theme={null} > minttokens 500000 ``` Example response: ```bash CLI theme={null} Minted: 500000 MTK Remaining mintable: 500000 MTK ``` You can verify the token balance: ```bash CLI theme={null} > gettokenbalance Token: MTK Balance: 500000 ``` Tokens on Spark move instantly and cost nothing. You can choose to create a new wallet to find a sender. ```bash CLI theme={null} # Wallet 2 > initwallet Mnemonic: explain bullet cradle segment lava enable someone lemon bracket fossil invite crash > getsparkaddress sparkrt1pgw6rrt6s4y3xghx6cl5v4mm08eylktaygff62mg8uk3u5zqq2zwqf9t9d0 ``` Then send tokens from your issuer wallet: ```bash CLI theme={null} # Wallet 1 (Issuer) > transfertokens btkn1qv6cps0n0ttm3p4gx62rzty4rjhzqwe5eqv2wlt 100000 sparkrt1pgw6rrt6s4y3xghx6cl5v4mm08eylktaygff62mg8uk3u5zqq2zwqf9t9d0 ``` That’s it. The transfer settles instantly on Spark. # Features Source: https://docs.spark.money/start/features The team behind Spark has spent years building on Bitcoin. After countless iterations, Spark represents our best attempt at creating the definitive platform for builders on Bitcoin. We built it from scratch, with strong opinions on what makes a truly great developer experience. Spark exposes powerful, low-level features for a wide range of use cases. ***
Live

In development
*** ### Something we should be building? Most of what's on this page came from working directly with our builders. If you have a use case that needs something we don't support yet, we want to hear about it. Get in touch # Welcome to Spark Source: https://docs.spark.money/start/overview Spark is the fastest, cheapest, and most UX-friendly way to build financial apps and launch assets natively on Bitcoin. It's a Bitcoin platform that lets developers move Bitcoin and Bitcoin-native assets (including stablecoins) instantly, at near-zero cost, while staying fully connected to Bitcoin’s infrastructure. *** ## Quickstart The fastest way to go from 0 to 1 on Spark
*** ## Build something Spark makes Bitcoin fast enough to build on. Payments, wallets, stablecoins, and whatever comes next.
*** ## Get to know Spark Spark integrates with best-in-class infrastructure partners to help you ship faster
*** ## Building on Spark
Luminex Magic Eden Tether Xverse Thesis Breez Wallet of Satoshi
Luminex Magic Eden Tether Xverse Thesis Breez Wallet of Satoshi
# Products Source: https://docs.spark.money/start/products Spark SDKs and developer tools for Bitcoin L2 development. Explore Spark's product ecosystem and offerings. ## Core Products ### Spark Network The foundational layer-2 network for Bitcoin scaling. ### Spark Wallet SDK Developer tools for building Spark-native wallets. ### Spark Issuer SDK Platform for creating and managing tokens on Spark. ## Developer Tools ### Spark CLI Command-line interface for Spark development. ### Spark Explorer Block explorer for the Spark network. ### Spark Faucet Test token distribution for developers. # Use Cases Source: https://docs.spark.money/start/use-cases Spark makes Bitcoin fast enough to build on. We're building the infrastructure that makes Bitcoin the definitive platform for payments, wallets, and stablecoins. Where new kinds of apps can finally exist on the world's most secure ledger. ## Payments Global transfers, tipping, merchant apps, and more payment solutions built on Bitcoin. ## DeFi Liquidity pools, tokenized BTC, swaps, and decentralized finance protocols. ## Rewards Bitcoin cashback, loyalty, micro-incentives, and reward-based applications. # Spark CLI Source: https://docs.spark.money/tools/cli Command-line tool for wallet operations and testing on Spark. # Explorer Source: https://docs.spark.money/tools/explorer Block explorer for viewing Spark transactions and addresses. # Regtest Faucet Source: https://docs.spark.money/tools/faucet Get test Bitcoin for development on Spark regtest. # Test Wallet Source: https://docs.spark.money/tools/test-wallet Browser-based wallet for testing Spark features. A simple test wallet for experimenting with Spark features. ## Features * **Send/Receive** - Test Spark transactions * **Token Management** - Handle multiple token types * **Lightning Integration** - Test Lightning payments * **Developer Tools** - Debug and monitor transactions ## Getting Started 1. **Create Wallet** - Generate a new test wallet 2. **Fund Wallet** - Use the faucet to get test tokens 3. **Start Testing** - Try all Spark features safely ## Test Environment This wallet connects to Spark's testnet, so you can experiment without risk. # Token Lists Source: https://docs.spark.money/tools/token-lists Registry of BTKN tokens on Spark. # Instant Bitcoin Deposits Source: https://docs.spark.money/wallets/0-conf Instant Bitcoin deposits UI Instant Bitcoin deposits UI
This feature is in **private experimental beta** and only available via a **custom integration**. [Contact us](https://www.spark.money/contact) to discuss your use case. **Instant Bitcoin Deposits** enable instant BTC deposits onto Spark, without having to wait for on-chain confirmations. Normally, payments on Bitcoin L1 can take up to 60 minutes or more, depending on the number of confirmations required. This waiting period impacts user experience, and can expose deposits to volatility and missed opportunities. Instant Deposits make L1 BTC usable immediately.
*** ## 0-conf Bitcoin Deposit Flow BTC is sent to a static Spark L1 deposit address generated via the Spark API. When the transaction hits the Bitcoin mempool, the LP detects it and performs a risk evaluation. If the transaction meets the LPs safety criteria, the LP makes the BTC immediately spendable on Spark. *** ## Use cases Give wallets and platforms the fastest way to buy Bitcoin through your onramp. The moment a user clicks “Buy,” BTC lands in their destination wallet. 1. Deliver the smoothest card-to-BTC onramp. BTC is usable seconds after purchase, not 30–60 minutes later. 2. Let users use Bitcoin immediately. Funds can be traded, spent, or bridged as soon as the purchase completes. 3. Reduce customer support load. Fewer “Where is my Bitcoin?” tickets. 4. Turn speed into revenue for your integrators. Let wallets sell “Fast Bitcoin” as a paid upgrade. BTC is purchased by a user through your onramp within a wallet or platform (one of your customers). Buy Bitcoin with on-ramp Buy Bitcoin with on-ramp As the onramp provider, you send BTC directly to a static deposit address generated by your integrator. Deposit to Spark L1 address Deposit to Spark L1 address Once approved, the BTC you sent is immediately usable on Spark by the end user or integrator. Instant funding Instant funding shown The user has full control of the BTC. It can be moved to any wallet or platform use case (trading, betting, or bridging) and sent onward over Bitcoin or Lightning. Settlement is complete and final. Funds available to use Funds available to use Give integrators (apps, DeFi protocols, wallets) and end users the fastest way to move Bitcoin to their preferred chain. The moment BTC is sent, it’s final on Spark and can be atomically swapped to another chain or asset, based on your bridge design. 1. Remove Bitcoin as the slow leg. BTC is final on Spark instantly, so cross-chain swaps execute without waiting on L1 confirmations. 2. Increase successful intent execution. Faster finality means fewer expired quotes, reverts, and failed routes. 3. Unlock more BTC flow. Lower latency and better UX turn Bitcoin into a viable source asset for high-frequency cross-chain volume. 4. Monetize speed and reliability. Offer “instant BTC bridging" as a premium option. The bridge generates a static Spark L1 deposit address. While implementations may vary, the address can be created in a trustless manner (Spark supports native HTLCs to enforce this). Initiate bridge transaction Initiate bridge transaction Once approved by the LP, BTC becomes immediately spendable on Spark and can be detected by the bridge infrastructure. The exact handling depends on each bridge’s architecture and execution model (e.g., solver-based designs). Instant funding Instant funding Once BTC is available on Spark, the bridge can execute the cross-chain swap. Spark supports trustless execution via HTLCs, allowing BTC to be locked until predefined conditions are met. Swap execution Swap execution Enable the best inbound Bitcoin deposit UX for self-custody wallets and wallet platforms. The moment BTC hits the mempool, it becomes spendable on Spark. Users can transact immediately instead of waiting on confirmations. 1. Keep users in-session. Deposits become usable immediately, so users don’t bounce while waiting for confirmations. 2. Unlock “instant receive” as a product feature. Let users trade, spend, bridge, or pay as soon as the deposit hits. 3. Reduce support and confusion. Fewer “pending deposit” tickets and fewer dropped sessions. 4. Monetize speed. Offer Instant Receive as a paid upgrade or included tier benefit. Give your exchange the fastest BTC deposit experience in the market. Give your users a faster way to trade, convert, or withdraw immediately after deposting BTC. 1. Win volatile moments. Instant access lets traders act during fast markets. 2. Increase deposit to trade conversion. Fewer idle deposits, more active balances. 3. Reduce ops and support overhead. Fewer pending deposits and manual interventions. 4. Monetize instant access. Offer instant deposits as a VIP or paid feature (take bps). Make your Bitcoin L1 payment operations more efficient. Reduce exposure to Bitcoin volatility, pay merchants faster, and increase checkout conversion. 1. Real-time payment UX. Accept BTC and complete the payment flow instantly. 2. Faster payouts and settlement. Move from “pending” to “done” in seconds for users and merchants. 3. Reduce FX exposure. Convert to stablecoins or other assets immediately when the flow requires it. 4. Improve cashflow. Faster settlement means funds aren’t stuck in limbo. 5. Increase payment completion rates. Less drop-off from pending states. 6. Charge for speed and certainty. Offer “instant acceptance” or premium execution tiers. *** ## Integration Each integration can differ based on your specific use case or requirements. If you have any questions, just let us know. This feature is private experimental beta. For integrators: within the wallet SDK, once a deposit is detected, you’ll receive both the quote and the claim functions needed to complete the flow. ```js theme={null} const quote = wallet.getInstantDepositQuote_EXPERIMENTAL( txid, vout, PARTNER_JWT || undefined ); if (!quote.is_eligible) { console.log("Not available because", quote.reason); } else { console.log(`You can claim ${quote.amount} of ${quote.total} instantly!`); const result = await wallet.claimInstantDeposit_EXPERIMENTAL(quote.id); } // After the transaction confirms on-chain, Spark will automatically // release any remaining amount (beyond the instant credit) to the wallet balance. ``` ## FAQ Instant Deposits remove the 10-60 minute wait that normally exists for Bitcoin deposits.
  • Build the best deposit UX by letting users trade or spend as soon as the deposit hits, instead of waiting for confirmations
  • You can charge fees for faster deposits and make it an opt-in feature
  • More deposits turn into actual trading and usage because funds are available immediately
  • Fewer support tickets about pending or stuck deposits
Participating LPs on the Spark network run proprietary risk engines that evaluate each incoming transaction and can set threshold safety criteria. No. Only transactions that meet the participating LP's safety criteria qualify for instant funding. If a transaction doesn't qualify, it follows the standard Spark deposit flow, which settles after on-chain confirmations (typically 1–3 confirmations depending on transaction characteristics). Typically under 5 seconds from mempool detection to spendable balance on Spark. Yes. Instant Deposits provide real, native BTC on Spark, not wrapped or synthetic assets. Once available, BTC can move instantly to Bitcoin L1, Lightning, or any Spark address. The Spark protocol guarantees unilateral exit: even if all infrastructure disappeared, funds can always be withdrawn directly to L1 using pre-signed exit transactions. Yes. Instant Deposits build on Spark's native deposit flow, which is fully trust-minimized. No party, including LPs, can move or control deposited funds. BTC remains locked in the on-chain UTXO until the Spark Protocol finalizes settlement. During the instant deposit window, the LP fronts liquidity on Spark based on the unconfirmed transaction's risk profile. Once the Bitcoin transaction confirms on-chain, the protocol settles the deposit and the LP's fronted liquidity is reconciled. Yes. LPs enforce criteria such as deposit size and frequency limits. Limits may vary by LP, integrator, and individual transaction characteristics.

# Addressing Source: https://docs.spark.money/wallets/addressing Spark doesn't force a single address format on your users. Depending on what you're building, your users might interact with standard Bitcoin taproot addresses, Lightning invoices, Spark addresses, or none of the above. You choose what to expose. *** ## Address types ### Bitcoin taproot addresses Every Spark wallet has a standard Bitcoin taproot address (`bc1p...` on mainnet) for L1 deposits. This is the same address format used by any Bitcoin wallet. Your users can receive Bitcoin from Coinbase, a hardware wallet, or any on-chain source. On test networks you'll see different prefixes: `tb1p...` (testnet/signet) and `bcrt1p...` (regtest/local). ```typescript theme={null} const depositAddress = await wallet.getStaticDepositAddress(); // bc1p5d7rjq7g6rdk2yhzks9smtbqtedr4dekq08ge8ztwac72sfr9rusxg3297 ``` This means a Spark-powered app can function as a full L1 Bitcoin wallet. Users deposit to a taproot address, hold Bitcoin on Spark, and withdraw back to L1 whenever they want. ### Lightning invoices Spark wallets can create and pay Lightning invoices without running a node or managing channels. Some apps only expose Lightning. Users never see a Spark address or know Spark exists. ```typescript theme={null} // Create a Lightning invoice const { invoice } = await wallet.createLightningInvoice({ amountSats: 50000, memo: "Payment for order #1234", }); // Pay a Lightning invoice const payment = await wallet.payLightningInvoice({ invoice: "lnbc500u1p...", }); ``` ### Spark addresses Spark addresses are Bech32m-encoded identifiers that map to a wallet's identity public key. Think of them as user IDs. They identify a wallet on the Spark network. Transfers between Spark addresses are instant and free. ```typescript theme={null} const sparkAddress = await wallet.getSparkAddress(); // spark1pgssyuuuhnrrdjswal5c3s3rafw9w3y5dd4cjy3duxlf7hjzkp0rqx6dj6mrhu ``` Most wallets don't expose Spark addresses to end users. They're useful behind the scenes for free, instant transfers between your own users, between services, or for settlement between apps. *** ## What to expose Spark is modular. Pick the addressing that fits your use case: | Use case | What users see | What Spark does behind the scenes | | ---------------------- | ----------------------------------------------------- | ------------------------------------------- | | **Bitcoin wallet** | Taproot address (`bc1p...` / `tb1p...` / `bcrt1p...`) | L1 deposits, Spark balance, L1 withdrawals | | **Payments app** | Lightning invoices | Spark handles routing, no node needed | | **Exchange / fintech** | Nothing (internal accounts) | Spark addresses for free internal transfers | | **P2P transfers** | Spark addresses or usernames | Instant, free Spark-to-Spark settlement | The point: Spark adapts to your product. It can be a full L1 wallet, a Lightning payments backend, a free internal ledger, or all three at once. Your users don't need to know what's underneath. *** ## Spark address format For developers working directly with Spark addresses, here's the format: ```typescript MAINNET theme={null} spark1pgssyuuuhnrrdjswal5c3s3rafw9w3y5dd4cjy3duxlf7hjzkp0rqx6dj6mrhu ``` ```typescript REGTEST theme={null} sparkrt1pgssyuuuhnrrdjswal5c3s3rafw9w3y5dd4cjy3duxlf7hjzkp0rqx6dj6mrhu ``` ```typescript TESTNET theme={null} sparkt1pgssyuuuhnrrdjswal5c3s3rafw9w3y5dd4cjy3duxlf7hjzkp0rqx6dj6mrhu ``` ```typescript SIGNET theme={null} sparks1pgssyuuuhnrrdjswal5c3s3rafw9w3y5dd4cjy3duxlf7hjzkp0rqx6dj6mrhu ``` ```typescript LOCAL theme={null} sparkl1pgssyuuuhnrrdjswal5c3s3rafw9w3y5dd4cjy3duxlf7hjzkp0rqx6dj6mrhu ``` ### Network prefixes | Network | Prefix | Availability | | ----------- | --------- | ------------ | | **Mainnet** | `spark` | Available | | **Regtest** | `sparkrt` | Available | | **Testnet** | `sparkt` | Coming soon | | **Signet** | `sparks` | Coming soon | | **Local** | `sparkl` | Available | ### Key facts * Derived from your wallet's network and identity public key * Same wallet always produces the same Spark address * Network-specific. A mainnet wallet and regtest wallet will have different addresses # API Reference Source: https://docs.spark.money/wallets/api-reference # Balances & Activity Source: https://docs.spark.money/wallets/balances Query balances, view transfer history, and monitor wallet activity in real-time. Balances & Activity Balances & Activity *** ## Check Wallet Balance Get your current Bitcoin balance and token holdings in your Spark wallet. getBalance() Gets the current balance of the wallet, including Bitcoin balance and token balances. ```typescript theme={null} const balance = await wallet.getBalance(); console.log("Balance:", balance.balance, "sats"); console.log("Token Balances:", balance.tokenBalances); ``` The wallet's current balance in satoshis Map of Bech32m token identifiers to token balance objects. Each balance includes `ownedBalance` (total owned), `availableToSendBalance` (excludes pending transfers), and token metadata. *** ## View Transfer History Track all incoming and outgoing transfers for your wallet with pagination support. getTransfers(limit?, offset?) Gets all transfers for the wallet with optional pagination. `getTransfers()` includes Spark transfers, Lightning sends/receives, and cooperative exits. For token transaction details (e.g., sender address), use [`queryTokenTransactionsWithFilters()`](/api-reference/wallet/query-token-transactions-with-filters). ```typescript theme={null} // Get first 20 transfers const transfers = await wallet.getTransfers(); console.log("Transfers:", transfers.transfers); // Get next 10 transfers with pagination const nextTransfers = await wallet.getTransfers(10, 20); console.log("Next page:", nextTransfers.transfers); // Get transfers from the last 24 hours const yesterday = new Date(Date.now() - 24 * 60 * 60 * 1000); const recentTransfers = await wallet.getTransfers(50, 0, yesterday); ``` Maximum number of transfers to return (default: 20) Offset for pagination (default: 0) Only return transfers created after this date (mutually exclusive with `createdBefore`) Only return transfers created before this date (mutually exclusive with `createdAfter`) Array of transfer objects containing transfer details The offset used for this request *** ## Real-time Event Monitoring Monitor wallet activity in real-time using EventEmitter methods for instant updates. on(event, listener) Adds a listener for the specified event to monitor wallet activity. ```typescript theme={null} // Listen for incoming transfer claims wallet.on("transfer:claimed", (transferId, updatedBalance) => { console.log(`Transfer ${transferId} claimed. New balance: ${updatedBalance}`); }); // Listen for deposit confirmations (after 3 L1 confirmations) wallet.on("deposit:confirmed", (depositId, updatedBalance) => { console.log(`Deposit ${depositId} confirmed. New balance: ${updatedBalance}`); }); ``` The event name to listen for (e.g., "transfer:claimed", "deposit:confirmed") The callback function to execute when the event is emitted The SparkWallet instance for method chaining once(event, listener) Adds a one-time listener for the specified event. ```typescript theme={null} // Listen for a single incoming transfer wallet.once("transfer:claimed", (transferId, updatedBalance) => { console.log(`Transfer ${transferId} claimed! New balance: ${updatedBalance}`); }); ``` The event name to listen for The callback function to execute when the event is emitted The SparkWallet instance for method chaining off(event, listener) Removes the specified listener from the specified event. ```typescript theme={null} // Remove a specific listener const handleTransfer = (transferId) => console.log(`Transfer: ${transferId}`); wallet.on("transfer:claimed", handleTransfer); // Later, remove the listener wallet.off("transfer:claimed", handleTransfer); ``` The event name to remove the listener from The specific callback function to remove The SparkWallet instance for method chaining *** ## Available Events Spark wallets emit various events for different types of activity: #### Available Events | Event | Description | | --------------------- | --------------------------------------------------- | | `transfer:claimed` | Emitted when an **incoming** transfer is claimed | | `deposit:confirmed` | Emitted when a pending L1 deposit becomes spendable | | `stream:connected` | Emitted when the event stream connects | | `stream:disconnected` | Emitted when the stream disconnects | | `stream:reconnecting` | Emitted when attempting to reconnect | Events only fire for **incoming** funds. For outgoing operations (Lightning sends, withdrawals), poll the status using `getLightningSendRequest()` or `getCoopExitRequest()`. *** ## Use Sparkscan Explorer Monitor your wallet activity using the Sparkscan block explorer for a visual interface. Sparkscan provides a web interface to view your wallet's transaction history, balance, and activity without needing to implement the API calls yourself. *** ## Example: Complete Balance Monitoring ```typescript theme={null} import { SparkWallet } from "@buildonspark/spark-sdk"; async function setupBalanceMonitoring() { const { wallet } = await SparkWallet.initialize({ options: { network: "REGTEST" } }); // Get initial balance const balance = await wallet.getBalance(); console.log("Initial balance:", balance.balance, "sats"); // Set up event listeners wallet.on("transfer:claimed", (transferId, newBalance) => { console.log(`Transfer ${transferId} claimed. New balance: ${newBalance} sats`); }); // Get recent transfers const transfers = await wallet.getTransfers(10); console.log("Recent transfers:", transfers.transfers); return wallet; } ``` # Create a Wallet Source: https://docs.spark.money/wallets/create-wallet Create and initialize Spark wallets with full key control. Create a Wallet Create a Wallet *** ## Initialize Wallet The `initialize` method is the primary way to create or restore a Spark wallet. Leave `mnemonicOrSeed` blank to generate a new wallet, or provide an existing mnemonic seed phrase to import an existing wallet. ```typescript Create Wallet theme={null} import { SparkWallet } from "@buildonspark/spark-sdk"; // Create a new wallet const { wallet, mnemonic } = await SparkWallet.initialize({ options: { network: "REGTEST" // or "MAINNET" } }); console.log("New wallet created!"); console.log("Mnemonic:", mnemonic); console.log("Address:", await wallet.getSparkAddress()); ``` ```typescript Import Wallet theme={null} import { SparkWallet } from "@buildonspark/spark-sdk"; // Restore wallet from existing mnemonic const { wallet } = await SparkWallet.initialize({ mnemonicOrSeed: "abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon about", accountNumber: 0, // Optional: specify account index options: { network: "REGTEST" // or "MAINNET" } }); console.log("Wallet restored from mnemonic!"); console.log("Address:", await wallet.getSparkAddress()); ``` BIP-39 mnemonic phrase or raw seed. Leave blank to generate a new wallet. Account index for generating multiple identity keys from the same mnemonic (default: 0 on REGTEST, 1 on MAINNET). **Important:** Always specify this explicitly if you use the same mnemonic across networks. Custom signer implementation for advanced use cases Wallet configuration options including network selection The initialized SparkWallet instance The 12-word mnemonic seed phrase for wallet recovery (undefined for raw seed) *** ## Essential Wallet Operations After creating your wallet, you can perform these essential operations getIdentityPublicKey() Gets the identity public key of the wallet ```typescript theme={null} const identityKey = await wallet.getIdentityPublicKey(); console.log("Identity Public Key:", identityKey); ``` The identity public key as a hex string getSparkAddress() Gets the Spark address of the wallet ```typescript theme={null} const sparkAddress = await wallet.getSparkAddress(); console.log("Spark Address:", sparkAddress); ``` The Spark address for receiving Bitcoin and tokens getBalance() Gets the current balance of the wallet, including Bitcoin and token balances. ```typescript theme={null} const balance = await wallet.getBalance(); console.log("Balance:", balance.balance, "sats"); console.log("Token Balances:", balance.tokenBalances); ``` The wallet's current balance in satoshis Map of Bech32m token identifiers to token balance and metadata. `UserTokenMetadata` includes `extraMetadata?: Uint8Array` for arbitrary issuer-defined bytes. cleanupConnections() Properly closes all network connections and cleans up resources when you're done using the wallet. ```typescript theme={null} await wallet.cleanupConnections(); console.log("Wallet connections cleaned up"); ``` No return value - cleans up connections and aborts active streams *** ## Network Configuration Spark supports both mainnet and regtest networks: ```typescript Mainnet theme={null} const { wallet } = await SparkWallet.initialize({ options: { network: "MAINNET" } }); // Use for production applications ``` ```typescript Regtest theme={null} const { wallet } = await SparkWallet.initialize({ options: { network: "REGTEST" } }); // Use for development and testing ``` Always use REGTEST for development and testing. Only use MAINNET for production applications with real Bitcoin. **Account number defaults differ by network.** REGTEST defaults to account 0, MAINNET defaults to account 1. If you test on REGTEST without specifying `accountNumber`, then deploy to MAINNET with the same mnemonic, your wallet will be empty because funds are on a different account. Always explicitly set `accountNumber` for consistent behavior. *** ## Account Derivation You can create multiple accounts from the same mnemonic by specifying different account numbers: ```typescript theme={null} // Account 0 (default) const wallet0 = await SparkWallet.initialize({ mnemonicOrSeed: "your mnemonic here", accountNumber: 0 }); // Account 1 const wallet1 = await SparkWallet.initialize({ mnemonicOrSeed: "your mnemonic here", accountNumber: 1 }); // Each account will have different addresses console.log("Account 0:", await wallet0.getSparkAddress()); console.log("Account 1:", await wallet1.getSparkAddress()); ``` *** ## API Reference ```typescript theme={null} SparkWallet.initialize({ mnemonicOrSeed?: Uint8Array | string, // Optional: existing mnemonic or seed accountNumber?: number, // Optional: account index (default: 0) signer?: SparkSigner, // Optional: custom signer implementation options?: { network?: "MAINNET" | "TESTNET" | "SIGNET" | "REGTEST" | "LOCAL" } }) // Returns: { wallet: SparkWallet, mnemonic: string | undefined } ``` **Wallet Methods:** ```typescript theme={null} // Initialize wallet with mnemonic or seed initialize({ mnemonicOrSeed, signer, options }) // Get the identity public key getIdentityPublicKey() // Get the Spark address getSparkAddress() // Clean up connections cleanupConnections() ``` # Deposit from L1 Source: https://docs.spark.money/wallets/deposit-from-l1 Deposit Bitcoin from L1 using reusable static addresses. Deposit from L1 Deposit from L1 *** ## Deposit Flow The complete process for receiving on-chain Bitcoin into a Spark wallet: Create a static deposit address that can be reused for multiple deposits. ```typescript theme={null} const staticAddress = await wallet.getStaticDepositAddress(); console.log("Static Deposit Address:", staticAddress); ``` Send Bitcoin from any wallet to your deposit address. ```typescript theme={null} // For mainnet: Send real Bitcoin to the address // For regtest: Use the faucet console.log("Send Bitcoin to:", staticAddress); ``` Wait for the transaction to be confirmed on the blockchain. ```typescript theme={null} // Monitor using a block explorer or your infrastructure // You need to monitor the address for new transactions ``` Claim the deposit once it has 3 confirmations. ```typescript theme={null} const quote = await wallet.getClaimStaticDepositQuote(txId); const claimResult = await wallet.claimStaticDeposit({ transactionId: txId, creditAmountSats: quote.creditAmountSats, sspSignature: quote.signature }); ``` *** ## Generate Static Deposit Address For Bitcoin deposits on L1, Spark generates P2TR addresses. These addresses start with `bc1p...` on mainnet, `tb1p...` on testnet/signet, and `bcrt1p...` on regtest/local. Static deposit addresses are reusable, allowing the same address to receive multiple deposits. This approach is user-friendly, minimizes operational overhead, and is ideal for production applications. ```typescript theme={null} const staticDepositAddress = await wallet.getStaticDepositAddress(); console.log("Static Deposit Address:", staticDepositAddress); // This address can be reused for multiple deposits ``` **Mainnet Address Example** `bc1p5d7rjq7g6rdk2yhzks9smtbqtedr4dekq08ge8ztwac72sfr9rusxg3297` *** ## Deposit Bitcoin #### Mainnet Deposits To deposit Bitcoin on the mainnet, send funds to your static deposit address. #### Regtest Deposits For testing purposes on the Regtest network, use the faucet to fund your Spark wallet without using real Bitcoin. If you have custom L1 requirements like confirmation thresholds, transaction chaining, or specific coin selection, [talk to our team](https://www.spark.money/contact). We can expose lower-level controls to fit your infrastructure needs. ## Monitor for Deposit Transactions After sending Bitcoin to your deposit address, you'll need to monitor for incoming transactions using a blockchain explorer or your own infrastructure. ```typescript theme={null} const staticAddress = await wallet.getStaticDepositAddress(); // Example: Monitor for new transactions using a block explorer API // const newTransactions = await yourBlockchainMonitor.checkAddress(staticAddress); ``` Since static addresses can receive multiple deposits, you need to actively monitor the address for new transactions. *** ## Claiming Deposits Once a deposit is found on the blockchain, you can claim it by providing the transaction ID. ```typescript theme={null} // Step 1: Get a quote for your deposit (can be called anytime after transaction) const quote = await wallet.getClaimStaticDepositQuote(txId); console.log("Quote:", quote); // Step 2: Claim the deposit using the quote details const claimResult = await wallet.claimStaticDeposit({ transactionId: txId, creditAmountSats: quote.creditAmountSats, sspSignature: quote.signature }); console.log("Claim successful:", claimResult); ``` You can call `getClaimStaticDepositQuote` anytime after the deposit transaction is made, but `claimStaticDeposit` will only succeed after the deposit transaction has 3 confirmations. If you'd like to receive faster, [get in touch](https://www.spark.money/contact). We can do 0-1 conf as well. *** ## Sending Back to L1 Since your Spark wallet has a standard Bitcoin address, you can also send funds back on-chain without claiming them into Spark first: ```typescript theme={null} // Refund a static deposit const refundTxHex = await wallet.refundStaticDeposit({ depositTransactionId: txId, destinationAddress: "bc1p...", satsPerVbyteFee: 10 }); // You'll need to broadcast this transaction yourself console.log("Refund transaction hex:", refundTxHex); // Or use refundAndBroadcastStaticDeposit to broadcast automatically const txid = await wallet.refundAndBroadcastStaticDeposit({ depositTransactionId: txId, destinationAddress: "bc1p...", satsPerVbyteFee: 10 }); ``` **When to use this:** * You want to forward funds to another Bitcoin address directly * You'd rather skip the claim fee * You're using Spark as a receiving address but want to settle on L1 ## Confirmation Requirements * By default, deposits require **3 confirmations** on L1 * 0-1 confirmation deposits are available. [Get in touch](https://www.spark.money/contact) to enable faster settlement * Funds will be available in your Spark wallet after claiming ## Minimum Deposit Amount The minimum deposit must exceed the dust limit plus fees: * **Dust limit:** \~400 sats * **Claim fee:** \~99 sats (varies with network conditions) Deposits below \~500 sats may fail with "utxo amount minus fees is less than the dust amount". **Important:** Static deposits do NOT auto-claim when your wallet is offline. If a deposit confirms while your wallet instance is not running, you must manually claim it using `claimStaticDeposit()` with the transaction ID when you next initialize the wallet. # Deposit from Lightning Source: https://docs.spark.money/wallets/deposit-from-lightning Receive Bitcoin via Lightning invoices with instant settlement into Spark. Deposit from Lightning Deposit from Lightning *** ## Understanding Lightning Invoices To send and receive Lightning payments, you can generate and pay Lightning invoices. A Lightning invoice (also called a payment request) is a specially formatted string that contains all the information needed to make a Lightning Network payment: * **Amount**: How many satoshis to send (can be omitted for zero-amount invoices) * **Destination**: The recipient's node public key * **Payment Hash**: A unique identifier for the payment * **Description**: Optional memo describing the payment * **Expiry**: How long the invoice is valid for (default 24 hours) Lightning invoices start with "ln" followed by the network identifier (bc for mainnet) and typically look like this: `lnbc1...` **Mainnet invoice Example:** ``` lnbcrt2500n1pj0ytfcpp5qqqsyqcyq5rqwzqfqypqhp58yjmdan79s6qqdhdzgynm4zwqdx40shp5jqp3qymd6qgpy99ppk0jqjzylqg5t7fhqhpl6s4kxmqgmrn59w5k0z0cqqqqqqzqqqqq9qsqqqqqq9qqqqqqgq9qsqxl9l55y5cwa9s2h8nvdh4h7h43tcwjdcysf7v0fprz5uh6vshs4n0tvhgzz2xgcqpg8yqv7 ``` *** ## Lightning Deposit Flow The complete process for receiving Lightning payments into your Spark wallet: Generate a Lightning invoice with the desired amount and description. ```typescript theme={null} const invoice = await wallet.createLightningInvoice({ amountSats: 50000, memo: "Deposit to Spark wallet" }); ``` Provide the invoice to the sender (via QR code, link, or text). ```typescript theme={null} // Display invoice for user to share console.log("Share this invoice:", invoice.invoice.encodedInvoice); console.log("Or share this ID:", invoice.id); ``` Track the payment status until completion. ```typescript theme={null} // Poll for payment status const checkPayment = async (invoiceId) => { const request = await wallet.getLightningReceiveRequest(invoiceId); if (request.status === "TRANSFER_COMPLETED") { console.log("Payment received:", request.amountReceived, "sats"); return true; } else if (request.status === "expired") { console.log("Invoice expired"); return false; } else { console.log("Payment pending..."); return false; } }; ``` *** ## Create Lightning Invoice Generate Lightning invoices to receive Bitcoin payments that will be deposited into your Spark wallet. createLightningInvoice(params) Creates a Lightning invoice for receiving Bitcoin payments. ```typescript theme={null} const invoice = await wallet.createLightningInvoice({ amountSats: 10000, // Amount in satoshis memo: "Payment for services", // Optional description includeSparkAddress: true // Optional: embed Spark address }); console.log("Lightning invoice:", invoice.invoice.encodedInvoice); console.log("Invoice ID:", invoice.id); ``` The amount in satoshis to request Optional memo/description for the invoice (max 120 bytes). Cannot be used with `descriptionHash`. Whether to embed Spark address in the invoice fallback field. Mutually exclusive with `includeSparkInvoice`. Whether to include a Spark invoice in the invoice routing hints. Mutually exclusive with `includeSparkAddress`. Optional: 33-byte compressed identity pubkey for other Spark users Optional: Hash of a longer description (h tag). Used in LNURL/UMA. Cannot be used with `memo`. Unique identifier for the invoice (e.g., `SparkLightningReceiveRequest:...`) Lightning invoice object containing: * `encodedInvoice`: The BOLT11-encoded string (e.g., `lnbc1...`) * `bitcoinNetwork`: Network identifier (`MAINNET` or `REGTEST`) * `paymentHash`: Unique payment identifier * `amount`: Object with `originalValue` (millisatoshis) and `originalUnit` * `createdAt`: Invoice creation timestamp * `expiresAt`: Invoice expiration timestamp * `memo`: Base64-encoded memo string Invoice status (e.g., `INVOICE_CREATED`, `TRANSFER_COMPLETED`) *** ## Spark Payment Integration Spark provides two ways to enable Spark-based payments alongside Lightning invoices: ### Option 1: Spark Invoice in Routing Hints By setting `includeSparkInvoice: true`, a Spark invoice is embedded in the routing hints section of the BOLT11 invoice. This allows Spark-compatible wallets to automatically detect and pay over Spark instead of Lightning, providing a seamless user experience: ```typescript theme={null} const invoice = await wallet.createLightningInvoice({ amountSats: 100, memo: "Invoice with Spark support", includeSparkInvoice: true, }); console.log("Invoice with Spark routing:", invoice); ``` When a payer uses this invoice: * Spark-compatible wallets can pay directly over Spark (faster, potentially lower fees) * Non-Spark wallets will pay via Lightning as normal * The payment is correlated to the original invoice ### Option 2: Spark Address in Fallback Field By passing in `true` for `includeSparkAddress`, a 36-byte string consisting of a recognizable header and a receiver's compressed identity public key `SPK:identitypubkey` will get embedded in the fallback address (f) field of a BOLT11 invoice: ```typescript theme={null} const invoice = await wallet.createLightningInvoice({ amountSats: 100, memo: "Invoice with Spark address", includeSparkAddress: true, }); console.log("Invoice with Spark address:", invoice); ``` ### Fallback Address Format The embedded Spark address in the fallback field will look something like this: ```javascript theme={null} { version: 31, address_hash: "53504b0250949ec35b022e3895fd37750102f94fe813523fa220108328a81790bf67ade5" } ``` **Important:** These two options are mutually exclusive. You cannot use both `includeSparkAddress` and `includeSparkInvoice` in the same invoice. *** ## Creating Invoices for Other Spark Users To generate an invoice for another Spark user, pass in the 33-byte compressed identity pubkey as a string to `receiverIdentityPubkey`: ```typescript theme={null} const invoice = await wallet.createLightningInvoice({ amountSats: 100, memo: "Invoice for another user", receiverIdentityPubkey: "023e33e2920326f64ea31058d44777442d97d7d5cbfcf54e3060bc1695e5261c93", }); console.log("Invoice for another user:", invoice); ``` If a wallet is generating an invoice for itself and wants to embed its own Spark identity in the invoice, it will not need to pass in a `receiverIdentityPubkey` to embed a Spark address. That will get taken care of on the backend. Passing it in shouldn't change anything though. *** ## Zero-Amount Invoices Spark supports creating zero-amount Lightning invoices. Zero-amount invoices don't have a fixed amount and allow the sender to specify the amount when making the payment. Zero-amount invoices are not widely supported across the Lightning Network. Some exchanges, such as Binance, currently do not support them. ### Creating Zero-Amount Invoices To create a zero-amount invoice, pass `0` for the `amountSats` parameter: ```typescript theme={null} // Create a zero-amount invoice const zeroAmountInvoice = await wallet.createLightningInvoice({ amountSats: 0, // Creates a zero-amount invoice memo: "Pay any amount you want", }); console.log("Zero-amount Invoice:", zeroAmountInvoice); ``` *** ## Monitor Lightning Payments Track incoming Lightning payments and their status using receive request monitoring. getLightningReceiveRequest(id) Gets the status of a Lightning receive request by ID. ```typescript theme={null} // Monitor a specific invoice const receiveRequest = await wallet.getLightningReceiveRequest(invoiceId); console.log("Payment status:", receiveRequest.status); console.log("Amount received:", receiveRequest.amountReceived); // Check if payment is complete if (receiveRequest.status === "TRANSFER_COMPLETED") { console.log("Payment received successfully!"); } ``` The ID of the Lightning receive request The receive request details including status and amount *** ## Real-time Payment Monitoring Use event listeners to monitor Lightning payments in real-time. ```typescript theme={null} // Listen for transfer events (Lightning payments trigger transfer events) wallet.on("transfer:claimed", (transferId, updatedBalance) => { console.log(`Transfer ${transferId} claimed. New balance: ${updatedBalance} sats`); }); ``` # Estimate Fees Source: https://docs.spark.money/wallets/estimate-fees Fee breakdown for Lightning payments, cooperative exits, and swaps. ***
`For Bitcoin`
Transaction Type Fee structure
L1 to Spark On-chain fee paid by the user
Spark to Spark Free. Small flat fee coming in 6-12 months
Spark to Lightning 0.25% + routing fee
Lightning to Spark Free for the receiver. 0.15% for the sender (charged on routing nodes via route hints)
Send to L1 `250 × sats_per_vbyte + 750` where `sats_per_vbyte` is the fee rate based on exit speed (slow, medium, or fast)
Unilateral Exit On-chain fee paid by the user

`For BTKN assets`
Transaction Type Fee structure
Spark to Spark Free
Unilateral Exit On-chain fee + bond locked by user
Some of the fees are sourced directly from the Lightspark SSP, specifically for Spark–Lightning interactions. Lightspark is the first SSP on Spark, but the system is open. Anyone can run an SSP (SSP specs coming soon). If you're an SSP, reach out, and we'll include your fee structure. *** ## Fee Estimation Flow The complete process for estimating fees before making transactions: Determine which type of operation you want to estimate fees for. ```typescript theme={null} // Lightning payment const lightningInvoice = "lnbc..."; // Cooperative exit (withdrawal to L1) const withdrawalAmount = 10000; const withdrawalAddress = "bc1p..."; // Swap operation const swapAmount = 5000; ``` Call the appropriate fee estimation method. ```typescript theme={null} // Lightning send fee estimate const lightningFee = await wallet.getLightningSendFeeEstimate({ encodedInvoice: lightningInvoice }); // Cooperative exit fee estimate const exitFee = await wallet.getWithdrawalFeeQuote({ amountSats: withdrawalAmount, withdrawalAddress: withdrawalAddress }); // Swap fee estimate const swapFee = await wallet.getSwapFeeEstimate(swapAmount); ``` Examine the fee estimate response to understand costs. ```typescript theme={null} console.log("Lightning fee estimate:", lightningFee, "sats"); console.log("Exit fee estimate:", exitFee); console.log("Swap fee estimate:", swapFee); ``` Use the fee estimate to set appropriate limits for your transaction. ```typescript theme={null} // Use fee estimate for Lightning payment const payment = await wallet.payLightningInvoice({ invoice: lightningInvoice, maxFeeSats: lightningFee + 5 // Add buffer to fee estimate }); ``` *** ## Lightning Send Fee Estimates Estimate fees for sending Lightning payments before making the transaction. getLightningSendFeeEstimate(params) Gets an estimated fee for sending a Lightning payment. Note: the actual fee assessed may be different from the fee estimate as it will be determined by the actual Lightning node routing. ```typescript theme={null} const feeEstimateSats = await wallet.getLightningSendFeeEstimate({ encodedInvoice: "lnbcrt1u1pnm7ammpp4v84f05tl0kzt6g95g056athdpp8f8azvg6d7epz74z562ymer9jqsp5nc50gazvp0e98u42jlu653rw0eutcl067nqq924hf89q4la4kd9sxq9z0rgqnp4qdnmwu8v22cvq9xsv2l05cn9rre7xlcgdtntxawf8m0zxq3qemgzqrzjqtr2vd60g57hu63rdqk87u3clac6jlfhej4kldrrjvfcw3mphcw8sqqqqrj0q7ew45qqqqqqqqqqqqqq9qcqzpgdq5w3jhxapqd9h8vmmfvdjs9qyyssqj7lf2w4m587g04n4t0ferdv0vnwftzca0xuc9yxycng78cnhrvmyw2mzaa8t76jskpypqnwqhp9xh0vnwxz90jytd34vrmhcngsnl8qplz7ylk" }); console.log("Lightning send fee estimate:", feeEstimateSats, "sats"); ``` The BOLT11-encoded Lightning invoice Estimated fee in satoshis ### Example Response ```typescript theme={null} 100 // fee estimate in satoshis ``` *** ## Sending on L1 Fee Estimates Estimate fees for sending funds on the Bitcoin network. getWithdrawalFeeQuote(params) Gets a fee quote for a cooperative exit (on-chain withdrawal). The quote includes options for different speeds and an expiry time and must be passed to `withdraw` before it expires. ```typescript theme={null} const exitFeeEstimate = await wallet.getWithdrawalFeeQuote({ amountSats: 10000, withdrawalAddress: "bc1p5d7rjq7g6rdk2yhzks9smtbqtedr4dekq08ge8ztwac72sfr9rusxg3297" }); console.log("Cooperative exit fee estimate:", exitFeeEstimate); ``` The amount in satoshis to withdraw The Bitcoin address where the funds should be sent A fee quote for the withdrawal, or null if not available ### Example Response ```typescript theme={null} { feeEstimate: { originalValue: 1000, originalUnit: 'SATOSHI', preferredCurrencyUnit: 'USD', preferredCurrencyValueRounded: 0.50, preferredCurrencyValueApprox: 0.483 } } ``` *** ## Swap Fee Estimates Estimate fees for leaves swap operations. Leaves swaps are internal wallet optimization operations that consolidate or split your sats leaves. getSwapFeeEstimate(amountSats) Gets the estimated fee for a swap of leaves. ```typescript theme={null} const swapFeeEstimate = await wallet.getSwapFeeEstimate(5000); console.log("Swap fee estimate:", swapFeeEstimate); ``` The amount of sats to swap The estimated fee for the swap operation *** ## Fee Deduction Options For cooperative exits, you can choose how fees are handled: ### Deduct Fee from Withdrawal Amount When `deductFeeFromWithdrawalAmount` is set to `true`: ```typescript theme={null} import { ExitSpeed } from "@buildonspark/spark-sdk"; const withdrawal = await wallet.withdraw({ onchainAddress: "bc1p...", exitSpeed: ExitSpeed.MEDIUM, amountSats: 10000, // This is the net amount you'll receive feeQuote: feeQuote, deductFeeFromWithdrawalAmount: true // Fee deducted from amount }); // You receive: 10000 sats - fee ``` ### Pay Fee Separately When `deductFeeFromWithdrawalAmount` is set to `false`: ```typescript theme={null} import { ExitSpeed } from "@buildonspark/spark-sdk"; const withdrawal = await wallet.withdraw({ onchainAddress: "bc1p...", exitSpeed: ExitSpeed.MEDIUM, amountSats: 10000, // This is the gross amount you'll receive feeQuote: feeQuote, deductFeeFromWithdrawalAmount: false // Fee paid separately }); // You receive: 10000 sats (fee paid from wallet balance) ``` # FAQ Source: https://docs.spark.money/wallets/faq ### What languages does it currently support? * **TypeScript** & **React Native** via the official Spark SDK * **Rust, Swift, Kotlin, Python, Flutter, Go, C#, WebAssembly** via the [Breez SDK](https://sdk-doc-spark.breez.technology/) ### What kinds of transfers can I do with Spark? You can perform the following types of transfers with Spark: * Deposit and withdraw funds to and from L1 (Bitcoin mainnet) to a Spark wallet. * Make native Spark-to-Spark transfers (between two Spark addresses). * Send and receive payments via the Lightning Network. ### Is Spark working on Regtest and Mainnet? Yes, we support both. Mainnet is already live and ready for you to use today. Internally, we run a custom Regtest network; if you’d like access, reach out to us directly. We’re also planning to release a Signet environment that anyone can spin up locally, removing the need to rely on our Regtest setup. ### Is the SDK self-custodial? Yes. The Spark Wallet SDK is fully self-custodial. Keys are generated on the user’s side. You have the flexibility to decide how you want to build your product, whether you prefer to manage custody of the funds or allow each user to own their own Spark wallet. ### Does it support MPC/multisig? Not yet. It's coming soon. ### Is there any limits in payment sizes? There are no hard limits on payment size. That said, Spark is still early, and we recommend starting with small amounts as the network matures. The only practical constraints arise if you're moving large amounts in or out of Spark via Lightning, or exiting through a cooperative exit. If you expect to move significant volume between Lightning and Spark, reach out to us. We're happy to work with you to make sure everything flows smoothly. # Identity Key Derivation Source: https://docs.spark.money/wallets/identity-key-derivation Spark wallets use a hierarchical deterministic (HD) key derivation scheme purpose-built for the Spark protocol. This follows the general principles of BIP43, using a custom purpose field derived from the SHA256 hash of "spark" to define application-specific key derivations. This derivation scheme is our recommendation so users can easily migrate between different wallet apps. You can implement a different scheme, but it will lose the ability to migrate to/from other Spark wallets. *** ## Derivation Scheme ### Base Derivation Path ``` m/8797555'/accountNumber'/keyType' ``` Where: * **8797555'**: Custom purpose field (derived from last 3 bytes of SHA256("spark") = 0x863d73) * **accountNumber'**: Account index (hardened derivation) * **keyType'**: Specific key type (hardened derivation) ### Key Types Spark derives 5 key types from the master seed: | Path | Key Type | Purpose | | ------------------ | ------------------------- | ------------------------------------------------------ | | `m/8797555'/n'/0'` | **Identity Key** | Primary wallet identifier and Spark Address generation | | `m/8797555'/n'/1'` | **Signing HD Key** | Base key for leaf key derivation | | `m/8797555'/n'/2'` | **Deposit Key** | Receiving L1 Bitcoin deposits | | `m/8797555'/n'/3'` | **Static Deposit HD Key** | Reusable deposit addresses (SSP integration) | | `m/8797555'/n'/4'` | **HTLC Preimage HD Key** | Lightning HTLC preimage generation | **Path:** `m/8797555'/n'/0'` Primary wallet identifier. Used for authentication, Spark Address generation, and message signing. **Path:** `m/8797555'/n'/1'` HD key used as the base for deriving leaf-specific signing keys. Each Spark leaf gets its own derived key. **Path:** `m/8797555'/n'/2'` Used for receiving L1 Bitcoin deposits into Spark. Single key, not HD-derived. **Path:** `m/8797555'/n'/3'` HD key for generating reusable static deposit addresses. Each index produces a different deposit address. **Path:** `m/8797555'/n'/4'` HD key for generating HTLC preimages in Lightning payments. The `htlcHMAC()` method uses this key. *** ## Account Number ### Default Behavior The `accountNumber` parameter controls which account's keys are derived. **The default value differs by network:** | Network | Default Account Number | | ------- | ---------------------- | | REGTEST | `0` | | MAINNET | `1` | MAINNET defaults to `1` for backwards compatibility with legacy wallets created before multi-account support. If you're integrating with the SDK and using account `0` internally, account for this off-by-one behavior on mainnet. ### Using Account Numbers ```typescript theme={null} // Account 0 (first account) const { wallet: account0 } = await SparkWallet.initialize({ mnemonicOrSeed: mnemonic, accountNumber: 0, options: { network: "REGTEST" } }); // Account 1 (second account, same mnemonic) const { wallet: account1 } = await SparkWallet.initialize({ mnemonicOrSeed: mnemonic, accountNumber: 1, options: { network: "REGTEST" } }); // These are completely separate wallets const address0 = await account0.getSparkAddress(); const address1 = await account1.getSparkAddress(); console.log(address0 !== address1); // true ``` ### Explicit Account Numbers When building wallet software, always specify the account number explicitly to avoid confusion: ```typescript theme={null} // Explicit is better than implicit const { wallet } = await SparkWallet.initialize({ mnemonicOrSeed: mnemonic, accountNumber: 0, // Always specify options: { network: "MAINNET" } }); ``` *** ## Leaf Key Derivation When accepting transfers, Spark assigns a unique leaf ID to each UTXO. The signing key for that leaf is derived from the Signing HD Key using the leaf ID. ### Derivation Formula ```typescript theme={null} // 1. Hash the leaf ID const hash = sha256(leafId); // 2. Extract first 4 bytes as unsigned 32-bit integer (big-endian) const hashValue = hash.slice(0, 4).readUInt32BE(); // 3. Calculate derivation index (hardened) const leafIndex = (hashValue % 0x80000000) + 0x80000000; // 4. Derive child key const leafKey = signingHDKey.deriveChild(leafIndex); ``` ### Full Derivation Path ``` m/8797555'/{accountNumber}'/1'/{leafIndex}' ``` Where `leafIndex` is calculated from the leaf ID as shown above. ### Implementation ```typescript theme={null} import { createHash } from 'crypto'; function getLeafDerivationPath(leafId: string, accountNumber: number): string { // Step 1: Calculate SHA256 hash of leaf ID const hash = createHash('sha256').update(leafId).digest(); // Step 2: Convert first 4 bytes to number const hashValue = hash.readUInt32BE(0); // Step 3: Calculate hardened index const leafIndex = (hashValue % 0x80000000) + 0x80000000; // Step 4: Build derivation path return `m/8797555'/${accountNumber}'/1'/${leafIndex}'`; } // Example const path = getLeafDerivationPath("leaf-abc-123", 0); // Returns something like: m/8797555'/0'/1'/2147483747' ``` ### Using KeyDerivation In the SDK, you typically don't derive leaf keys manually. Instead, use the `KeyDerivation` type: ```typescript theme={null} import { KeyDerivationType } from "@buildonspark/spark-sdk"; // The signer handles derivation internally const publicKey = await signer.getPublicKeyFromDerivation({ type: KeyDerivationType.LEAF, path: leafId // The leaf ID, not the derivation path }); ``` *** ## Static Deposit Key Derivation Static deposit keys are derived from the Static Deposit HD Key at path `/3'`: ```typescript theme={null} // Derive static deposit key at index 5 const staticDepositPath = `m/8797555'/${accountNumber}'/3'/${index + 0x80000000}'`; ``` In the SDK: ```typescript theme={null} // Get static deposit key at index 5 const publicKey = await signer.getStaticDepositSigningKey(5); const privateKey = await signer.getStaticDepositSecretKey(5); ``` *** ## Deposit Address Flow For L1 Bitcoin deposits, the flow is: 1. **Generate Deposit Key**: Uses the Deposit Key at path `m/8797555'/n'/2'` 2. **Create Deposit Address**: Generate Bitcoin address from the key 3. **Receive Deposit**: User sends Bitcoin to the address 4. **Assign Leaf**: After tree creation, funds are assigned to a leaf with its own derived key ```typescript theme={null} // Get the deposit signing key const depositKey = await signer.getDepositSigningKey(); // The SDK handles address generation const depositAddress = await wallet.getSingleUseDepositAddress(); ``` *** ## Usage in SparkWallet ### Initialize with Specific Account ```typescript theme={null} import { SparkWallet } from "@buildonspark/spark-sdk"; const { wallet, mnemonic } = await SparkWallet.initialize({ mnemonicOrSeed: "abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon about", accountNumber: 0, options: { network: "REGTEST", }, }); // Identity key is at m/8797555'/0'/0' const identityKey = await wallet.getIdentityPublicKey(); console.log("Identity key:", identityKey); ``` ### Multiple Accounts from Same Mnemonic ```typescript theme={null} const mnemonic = "abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon about"; // Create two separate accounts const { wallet: wallet0 } = await SparkWallet.initialize({ mnemonicOrSeed: mnemonic, accountNumber: 0, options: { network: "REGTEST" } }); const { wallet: wallet1 } = await SparkWallet.initialize({ mnemonicOrSeed: mnemonic, accountNumber: 1, options: { network: "REGTEST" } }); // Each account has completely isolated keys console.log(await wallet0.getSparkAddress()); // Different address console.log(await wallet1.getSparkAddress()); // Different address ``` *** ## Custom Derivation Paths For non-standard use cases, you can provide a custom key generator: ```typescript theme={null} import { DefaultSparkSigner, SparkValidationError } from "@buildonspark/spark-sdk"; import { HDKey } from "@scure/bip32"; class DerivationPathKeysGenerator { constructor(private readonly basePathTemplate: string) {} async deriveKeysFromSeed(seed: Uint8Array, accountNumber: number) { const hdkey = HDKey.fromMasterSeed(seed); const basePath = this.basePathTemplate.replace("?", String(accountNumber)); const identityKey = hdkey.derive(`${basePath}/0'`); const signingKey = hdkey.derive(`${basePath}/1'`); const depositKey = hdkey.derive(`${basePath}/2'`); const staticDepositKey = hdkey.derive(`${basePath}/3'`); const htlcPreimageKey = hdkey.derive(`${basePath}/4'`); if ( !identityKey.privateKey || !identityKey.publicKey || !signingKey.privateKey || !signingKey.publicKey || !depositKey.privateKey || !depositKey.publicKey || !staticDepositKey.privateKey || !staticDepositKey.publicKey || !htlcPreimageKey.privateKey || !htlcPreimageKey.publicKey ) { throw new SparkValidationError("Failed to derive all required keys from seed"); } return { identityKey: { privateKey: identityKey.privateKey, publicKey: identityKey.publicKey, }, signingHDKey: { hdKey: signingKey, privateKey: signingKey.privateKey, publicKey: signingKey.publicKey, }, depositKey: { privateKey: depositKey.privateKey, publicKey: depositKey.publicKey, }, staticDepositHDKey: { hdKey: staticDepositKey, privateKey: staticDepositKey.privateKey, publicKey: staticDepositKey.publicKey, }, HTLCPreimageHDKey: { hdKey: htlcPreimageKey, privateKey: htlcPreimageKey.privateKey, publicKey: htlcPreimageKey.publicKey, }, }; } } // Custom derivation template (? is replaced with account number) const customGenerator = new DerivationPathKeysGenerator("m/44'/0'/?'"); const signer = new DefaultSparkSigner({ sparkKeysGenerator: customGenerator }); // The keys will be derived from the custom path: // Identity: m/44'/0'/n'/0' // Signing HD: m/44'/0'/n'/1' // Deposit: m/44'/0'/n'/2' // Static Deposit: m/44'/0'/n'/3' // HTLC Preimage: m/44'/0'/n'/4' ``` Custom derivation paths will not be compatible with other Spark wallets. Only use this if you have specific requirements that justify breaking compatibility. *** ## Migration Between Wallets The standardized derivation scheme enables wallet migration: ### Export ```typescript theme={null} // Save these securely const walletBackup = { mnemonic: "your 12 or 24 word phrase", accountNumber: 0 }; ``` ### Import ```typescript theme={null} const { wallet } = await SparkWallet.initialize({ mnemonicOrSeed: walletBackup.mnemonic, accountNumber: walletBackup.accountNumber, options: { network: "MAINNET" } }); // Same identity key, same Spark address const address = await wallet.getSparkAddress(); ``` *** ## Security Considerations ### Hardened Derivation All derivation paths use hardened derivation (indicated by the `'` suffix). This prevents: * **Key leakage**: Child public keys cannot be used to derive parent keys * **Chain code exposure**: Compromising a child key doesn't compromise siblings ### Key Isolation Each account number creates a completely isolated set of keys: ```typescript theme={null} // Account 0 and Account 1 share no key material const account0Keys = deriveAccountKeys(mnemonic, 0); const account1Keys = deriveAccountKeys(mnemonic, 1); // No cryptographic relationship between them assert(account0Keys.identity !== account1Keys.identity); ``` ### Mnemonic Security * Store mnemonic phrases securely (encrypted, offline if possible) * Never transmit mnemonics over the network * Consider using hardware wallets or secure enclaves for production *** ## Quick Reference | Key Type | Path | SDK Method | | -------------- | -------------------------------- | ----------------------------------------------------------------- | | Identity | `m/8797555'/n'/0'` | `signer.getIdentityPublicKey()` | | Signing HD | `m/8797555'/n'/1'` | Internal (leaf derivation base) | | Deposit | `m/8797555'/n'/2'` | `signer.getDepositSigningKey()` | | Static Deposit | `m/8797555'/n'/3'/i'` | `signer.getStaticDepositSigningKey(i)` | | HTLC Preimage | `m/8797555'/n'/4'` | `signer.htlcHMAC(transferId)` | | Leaf | `m/8797555'/n'/1'/hash(leafId)'` | `signer.getPublicKeyFromDerivation({type: "leaf", path: leafId})` | # Overview Source: https://docs.spark.money/wallets/overview Create a Wallet Create a Wallet The Spark Wallet SDK lets you deploy Spark-native wallets in the most scalable and developer-friendly way possible. Whether you're building for your own custody or shipping self-custodial wallets for your users, the SDK is flexible by design. *** ## Installation ### Spark SDK The official Spark SDK for TypeScript and React Native applications. ### Breez SDK The [Breez](https://breez.technology) team has built a fully native SDK in Rust with bindings for Swift, Kotlin, Python, Flutter, Go, C#, and WebAssembly. If you need native mobile performance or a language not covered by the Spark SDK, use the Breez SDK. *** ## Fundamentals *** ## Tools # Privacy Mode Source: https://docs.spark.money/wallets/privacy Hide your transaction history from public view Privacy Mode on SparkScan *** ## Overview Make your wallet fully private with a single call. By default, Spark transactions are visible from public endpoints. When you enable privacy mode, your transaction history becomes invisible. Privacy mode currently applies to Bitcoin transactions only. Token transactions remain visible. *** ## Enable Privacy Mode setPrivacyEnabled() Toggle privacy mode on or off for your wallet. ```typescript Enable Privacy theme={null} import { SparkWallet } from "@buildonspark/spark-sdk"; const { wallet } = await SparkWallet.initialize({ mnemonicOrSeed: "your mnemonic here", options: { network: "MAINNET" } }); // Enable privacy mode await wallet.setPrivacyEnabled(true); console.log("Privacy mode enabled. Your transactions are now hidden."); ``` ```typescript Disable Privacy theme={null} // Disable privacy mode (make transactions public again) await wallet.setPrivacyEnabled(false); console.log("Privacy mode disabled. Transactions are now publicly visible."); ``` `true` to hide transactions from public view, `false` to make them visible. The updated wallet settings, including the new privacy state. *** ## Check Current Settings getWalletSettings() Query your wallet's current privacy configuration. ```typescript theme={null} const settings = await wallet.getWalletSettings(); if (settings?.privateEnabled) { console.log("Privacy mode is ON"); } else { console.log("Privacy mode is OFF"); } ``` Whether privacy mode is currently enabled. The wallet's identity public key. *** ## How It Works When privacy mode is enabled: 1. Block explorers see nothing. Your address and transactions won't appear publicly. 2. Public APIs return empty. Third parties querying your address get no results. 3. You retain full access. Your wallet can still query all your transactions normally. Privacy mode controls query visibility, not on-chain data. Your funds remain fully secure and self-custodial regardless of this setting. *** ## Maintaining Read Access When privacy is enabled, the [Wallet Viewer](/wallets/readonly-client) public client returns empty results. Two options for keeping visibility: ### Master Key A wallet with privacy enabled can specify a master key that always has read access, even when privacy is on. Use [`createWithMasterKey()`](/api-reference/readonly/create-with-master-key) with the wallet's mnemonic to query balances, transfers, and transaction history as normal. This is useful for server-side dashboards or support tooling where you need to see wallet activity without initializing the full wallet. ### Webhooks As of SDK `0.7.2`, wallets can register webhooks to get notified when events complete. This is useful for push notifications — you don't need to poll and you don't need read access to the wallet's transaction history. ```typescript theme={null} // Register a webhook await wallet.registerSparkWalletWebhook({ secret: "16-char-secret00", // 16 character secret for verifying webhook origin url: "https://your-server.com/webhook", eventTypes: [ "SPARK_LIGHTNING_RECEIVE_FINISHED", "SPARK_LIGHTNING_SEND_FINISHED", "SPARK_COOP_EXIT_FINISHED", "SPARK_STATIC_DEPOSIT_FINISHED", ], }); // List registered webhooks const webhooks = await wallet.listSparkWalletWebhooks(); // Delete a webhook by ID await wallet.deleteSparkWalletWebhook({ id: webhooks[0].id }); ``` **Limits:** Up to 5 webhooks per wallet. URLs and event types are unique — registering the same URL with the same event on the same wallet won't create a duplicate. Once you hit 5, the oldest webhook gets evicted (FIFO). For Lightning receives, the webhook fires on the **creator** of the invoice, not the receiver. If wallet A creates an invoice on behalf of wallet B, only wallet A's webhooks get the `SPARK_LIGHTNING_RECEIVE_FINISHED` event. *** ## API Reference ```typescript theme={null} // Enable or disable privacy mode await wallet.setPrivacyEnabled(privacyEnabled: boolean): Promise // Query current wallet settings await wallet.getWalletSettings(): Promise ``` **WalletSettings Type:** ```typescript theme={null} interface WalletSettings { privateEnabled: boolean; ownerIdentityPublicKey: string; } ``` # React Native SDK Source: https://docs.spark.money/wallets/react-native React Native SDK for iOS and Android wallet apps. Requires Xcode/iOS Simulator or Android Studio. React Native SDK **Requirements:** React `>=18.2.0`, React Native `>=0.71.0`, iOS `>=12.0` Complete React Native example with wallet, transfers, and Lightning ## Getting Started To get started, follow the steps below. Install the Spark SDK packages using your package manager of choice. ```bash npm theme={null} npm install @buildonspark/spark-sdk ``` ```bash yarn theme={null} yarn add @buildonspark/spark-sdk ``` ```bash pnpm theme={null} pnpm add @buildonspark/spark-sdk ``` Install the required polyfills for React Native compatibility. ```bash npm theme={null} npm install react-native-get-random-values @azure/core-asynciterator-polyfill buffer text-encoding ``` ```bash yarn theme={null} yarn add react-native-get-random-values @azure/core-asynciterator-polyfill buffer text-encoding ``` ```bash pnpm theme={null} pnpm add react-native-get-random-values @azure/core-asynciterator-polyfill buffer text-encoding ``` **Critical:** Import polyfills at the very top of your `index.js` file, BEFORE importing your app or any other modules. The crypto module will fail to load if this order is wrong. ```js index.js theme={null} // These MUST be the first imports in your app entry point import 'react-native-get-random-values'; import '@azure/core-asynciterator-polyfill'; import { Buffer } from 'buffer'; global.Buffer = Buffer; // Now import your app import { AppRegistry } from 'react-native'; import App from './App'; import { name as appName } from './app.json'; AppRegistry.registerComponent(appName, () => App); ``` For iOS, you must install the native module dependencies. This step is required for bare React Native apps. ```bash theme={null} cd ios && pod install && cd .. ``` If you skip this step, you'll see errors like `Cannot read property 'decryptEcies' of null` when initializing the wallet. Create a wallet instance that will be used to interact with the Spark network. ```tsx wallet.jsx theme={null} import { SparkWallet } from "@buildonspark/spark-sdk"; export const initializeWallet = async () => { const { wallet, mnemonic } = await SparkWallet.initialize({ mnemonicOrSeed: "optional-mnemonic-or-seed", accountNumber: 0, // optional options: { network: "REGTEST" // or "MAINNET" } }); console.log("Wallet initialized successfully:", mnemonic); return wallet; }; ``` You're ready to start building. ```tsx App.jsx theme={null} import { useState, useEffect } from "react"; import { View, Text, Button } from "react-native"; import { SparkWallet } from "@buildonspark/spark-sdk"; export function WalletInfo() { const [wallet, setWallet] = useState(null); const [loading, setLoading] = useState(false); const createWallet = async () => { setLoading(true); try { const { wallet, mnemonic } = await SparkWallet.initialize({ options: { network: "REGTEST" } }); setWallet(wallet); console.log("Mnemonic:", mnemonic); } catch (error) { console.error("Failed to create wallet:", error); } finally { setLoading(false); } }; // Note: getSparkAddress() and getBalance() are async const [address, setAddress] = useState(""); const [balance, setBalance] = useState(0n); useEffect(() => { if (wallet) { wallet.getSparkAddress().then(setAddress); wallet.getBalance().then(b => setBalance(b.balance)); } }, [wallet]); return ( Wallet Information {loading ? ( Loading... ) : wallet ? ( Address: {address} Balance: {balance.toString()} sats ) : (