# 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 |
| [`createWithViewerKey()`](/api-reference/readonly/create-with-viewer-key) | Owner or authorized viewer identity | Query private wallets you own or have been granted access to |
| [`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");
```
Bitcoin balance and wallet-history queries return empty results (zero balances, empty arrays) when a private wallet denies read access. This deliberately avoids revealing whether the wallet is private or empty; unrelated authentication or network errors can still occur. Use [`createWithViewerKey()`](/api-reference/readonly/create-with-viewer-key) as the owner or an authorized viewer to query a private wallet. An unrelated authenticated identity does not gain access.
# 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.
The signer must authenticate as the owner or the viewer identity authorized by the owner to read a private wallet. Supplying a signer alone does not grant access. See [Privacy Mode](/wallets/privacy).
## 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`.
# createWithViewerKey
Source: https://docs.spark.money/api-reference/readonly/create-with-viewer-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 a private wallet as its owner or as the viewer identity authorized by that owner.
Use a separate viewer mnemonic for services. Derive the public key with `deriveViewerIdentityPublicKey(config, mnemonicOrSeed, accountNumber)` and have the owner authorize it with [`setViewerIdentityPublicKey()`](/api-reference/wallet/set-viewer-identity-public-key). Use the same network and account number for derivation and authentication. See the [complete setup](/wallets/privacy#grant-a-service-read-access).
## Method Signature
```typescript theme={null}
static async createWithViewerKey(
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.createWithViewerKey(
{ network: "MAINNET" },
viewerMnemonic, // The service’s own mnemonic, not the owner’s wallet mnemonic
);
// Query a wallet that granted this identity read access.
const balance = await client.getAvailableBalance("sp1...");
```
```typescript From Seed theme={null}
import { SparkReadonlyClient } from "@buildonspark/spark-sdk";
const client = await SparkReadonlyClient.createWithViewerKey(
{ network: "MAINNET" },
seedBytes, // Uint8Array
1, // account number
);
```
# 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.
The viewer does not claim incoming transfers. A pending receipt can exist while both available and owned balances are zero; see [balance semantics](/wallets/balances#received-does-not-mean-spendable). Private wallets also return empty results to unauthorized clients, so emptiness is not a permission check.
## 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);
```
# getOwnedBalance
Source: https://docs.spark.money/api-reference/readonly/get-owned-balance
Query owned Bitcoin, including funds locked in outgoing transfers and swaps.
Returns owned satoshis for a Spark address, including available funds and funds still owned in pending outgoing transfers or swaps. It excludes ordinary incoming payments that the receiver has not claimed. Use `getAvailableBalance()` to determine spendable funds.
## Method signature
```typescript theme={null}
async getOwnedBalance(sparkAddress: string): Promise
```
Uses an authenticated client created with `createWithViewerKey()` or `createWithSigner()` and paginates internally. Private-wallet queries require owner or viewer authorization. A zero response does not establish whether a wallet is empty or access is missing.
```typescript theme={null}
const owned = await client.getOwnedBalance(ownerSparkAddress);
const available = await client.getAvailableBalance(ownerSparkAddress);
```
The two queries are separate snapshots; wallet activity can change between them. See [balance semantics](/wallets/balances#received-does-not-mean-spendable).
# 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.
The viewer does not claim incoming transfers. A pending receipt can exist while both available and owned balances are zero; see [balance semantics](/wallets/balances#received-does-not-mean-spendable). Private wallets also return empty results to unauthorized clients, so emptiness is not a permission check.
## 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;
previousCursor: string;
hasNextPage: boolean;
hasPreviousPage: boolean;
} | undefined;
}>
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 `hasNextPage`, `hasPreviousPage`, `nextCursor`, and `previousCursor`. Check that it is present before reading a cursor.
Pages are ascending. Use `PREVIOUS` with no cursor for the newest page, reverse `page.transactions` for display, then follow `page.pageResponse.previousCursor` for older pages. See the [newest-first example](/api-reference/wallet/query-token-transactions-with-filters#newest-first-activity); the viewer returns `transactions` instead of `tokenTransactionsWithStatus`. There is no `order` or `offset` parameter.
## 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
Raw protobuf `Transfer` objects with numeric enums, not the `WalletTransfer` objects returned by `SparkWallet`. Use participants and leaf values to derive wallet direction and amount; see [transfer object differences](/wallets/readonly-client#transfer-objects-differ-from-the-wallet-api).
The offset to use for the next page, or `-1` when there are no more results.
## 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.
A transfer lookup is not a permission check for a particular wallet. Transfer details may be accessible through another participant, so a successful lookup does not prove that the wallet granted your identity access. See [Privacy Mode](/wallets/privacy).
## 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);
```
# claimInstantStaticDeposit
Source: https://docs.spark.money/api-reference/wallet/claim-instant-static-deposit
Claim an instant static deposit using a quote and fulfillment plan.
Claims an instant static deposit using a quote from `getInstantStaticDepositQuote` and one of its fulfillment plans.
This method supports both 0-conf plans and plans that require one or more confirmations. The SDK signs the correct claim statement based on the selected plan.
## Method Signature
```typescript theme={null}
async claimInstantStaticDeposit({
quote,
plan,
transactionId,
outputIndex,
}: {
quote: StaticDepositQuote;
plan: InstantStaticDepositPlan;
transactionId: string;
outputIndex: number;
}): Promise<{ claimId: string }>
```
## Parameters
The `quote` object returned by `getInstantStaticDepositQuote`.
The fulfillment plan to claim. Select a plan with `confirmations === 0` for the 0-conf path.
The Bitcoin transaction ID of the deposit transaction.
The output index that pays the Spark static deposit address.
## Returns
The identifier for the instant static deposit claim.
## Example
```typescript theme={null}
const quoteResult =
await wallet.getInstantStaticDepositQuote(
transactionId,
outputIndex,
partnerId
);
const zeroConfPlan = quoteResult.fulfillmentPlans.find(
(plan) => plan.confirmations === 0
);
if (!zeroConfPlan) {
throw new Error("0-conf is not available for this deposit");
}
const claim = await wallet.claimInstantStaticDeposit({
quote: quoteResult.quote,
plan: zeroConfPlan,
transactionId: quoteResult.quote.transactionId,
outputIndex: quoteResult.quote.outputIndex,
});
console.log("Claim ID:", claim.claimId);
```
# 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 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");
}
```
# cleanup
Source: https://docs.spark.money/api-reference/wallet/cleanup
Stop wallet background work and close connections.
Disposes the wallet: clears claim and optimization intervals, aborts the event stream, removes listeners, and closes connections and logging resources. Call this during application shutdown or in a script's `finally` block. Do not reuse a disposed wallet.
```typescript theme={null}
async cleanup(): Promise
```
```typescript theme={null}
const { wallet } = await SparkWallet.initialize({
mnemonicOrSeed: mnemonic,
options: { network: "MAINNET" },
});
try {
console.log(await wallet.getSparkAddress());
} finally {
await wallet.cleanup();
}
```
Prefer cleanup over forcing the process to exit while work may still be pending.
# clearViewerIdentityPublicKey
Source: https://docs.spark.money/api-reference/wallet/clear-viewer-identity-public-key
Revokes the current viewer grant.
Revokes the current viewer grant. This does not change `privateEnabled`: if privacy is off, public clients can still read the wallet.
## Method signature
```typescript theme={null}
async clearViewerIdentityPublicKey(): Promise
```
Returns the updated settings, or `undefined` if no settings were returned. To confirm the configured key, call `const settings = await wallet.getWalletSettings()` and inspect `settings?.viewerIdentityPublicKey`.
See [Grant a service read access](/wallets/privacy#grant-a-service-read-access) for key derivation, setup, and revocation.
# 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`.
Save the returned request `id` and `invoice.paymentHash` for reconciliation. When creating an invoice for another receiver, keep the requesting wallet identity available for future lookups and register [webhooks](/wallets/webhooks) on that wallet. See [payment correlation](/wallets/deposit-from-lightning#match-the-invoice-request-and-transfer).
## 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<{
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
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.
Incoming transfers are excluded from `satsBalance.owned` until claimed. A Lightning payment status or a pending transfer is not proof of spendable funds. See [Received does not mean spendable](/wallets/balances#received-does-not-mean-spendable).
## 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<{
satsBalance: {
available: bigint;
owned: bigint;
incoming: bigint;
};
tokenBalances: TokenBalanceMap;
}>
```
## Returns
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 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 signature from the quote 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 can be absent while a withdrawal is progressing. Poll the request and check for the field before reading transfer 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);
```
# getInstantStaticDepositQuote
Source: https://docs.spark.money/api-reference/wallet/get-instant-static-deposit-quote
Get an instant static deposit quote and fulfillment plans.
Gets an instant static deposit quote for a `SparkWallet`. The response contains pricing information and fulfillment plans for claiming a static deposit, including plans that may be available at 0 confirmations.
0-conf static deposits are currently gated. Contact the team to enable support for your integration.
## Method Signature
```typescript theme={null}
async getInstantStaticDepositQuote(
transactionId: string,
outputIndex?: number,
partnerId?: string
): Promise
```
## Parameters
The Bitcoin transaction ID of the static deposit transaction.
The output index that pays the Spark static deposit address. If omitted, the SDK attempts to auto-detect it.
Optional partner identifier for enabled integrations.
## Returns
Quote object containing `id`, `network`, `transactionId`, `outputIndex`, `depositAmount`, `creditAmount`, and `quoteSignature`.
Available claim plans. Use `confirmations === 0` to identify a 0-conf plan.
## Example
```typescript theme={null}
const quoteResult =
await wallet.getInstantStaticDepositQuote(
transactionId,
outputIndex,
partnerId
);
const zeroConfPlan = quoteResult.fulfillmentPlans.find(
(plan) => plan.confirmations === 0
);
console.log("Credit amount:", quoteResult.quote.creditAmount.originalValue);
console.log("0-conf available:", Boolean(zeroConfPlan));
```
## Types
```typescript theme={null}
type InstantStaticDepositQuoteOutput = {
quote: {
id: string;
network: BitcoinNetwork;
transactionId: string;
outputIndex: number;
depositAmount: CurrencyAmount;
creditAmount: CurrencyAmount;
quoteSignature: string;
};
fulfillmentPlans: InstantStaticDepositPlan[];
};
type InstantStaticDepositPlan = {
id: string;
amount: CurrencyAmount;
confirmations: number;
status: string;
transferSparkId: string | null;
};
```
# 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 or not accessible. Only the requester or receiver can query it; a wallet-history viewer grant does not grant access to the receive request. `transfer` may be absent while the request is still progressing.
Save the full request ID returned by `createLightningInvoice()`. Use `request.transfer?.sparkId` to join to Spark transfer history, and `request.invoice.paymentHash` for the Lightning payment. See [payment correlation and units](/wallets/deposit-from-lightning#match-the-invoice-request-and-transfer).
## 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 and associated Lightning payment information.
Gets a transfer with its associated payment request, including Lightning invoice details.
## Method Signature
```typescript theme={null}
async getTransferFromSsp(id: string): Promise
```
## Parameters
The Spark transfer ID (`sparkId`), not a Lightning payment hash or receive-request ID
## 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 wallet payment and deposit requests.
Gets payment and deposit requests associated with the wallet.
## Method Signature
```typescript theme={null}
async getUserRequests(
params?: GetUserRequestsParams
): Promise
```
## Parameters
Optional filtering parameters
## Returns
User requests connection, or null if not available
For reconciliation, keep the request IDs returned when you initiate an operation and use the corresponding lookup method, such as [`getLightningReceiveRequest()`](/api-reference/wallet/get-lightning-receive-request). If a history query is unexpectedly empty, check the wallet identity, network, filters, and authentication errors before treating it as proof that no requests exist. Do not discard a stored pending payment solely because it is missing from a list response.
## 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. New wallets start with `privateEnabled: false`. `viewerIdentityPublicKey`, when present, identifies the one authorized viewer. Setting another viewer replaces it; authentication alone does not grant private-wallet access. See [Privacy Mode](/wallets/privacy).
## 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 `SparkWallet` instance. Initialization also starts event subscriptions and transfer claiming, even when you only call invoice-creation methods. Periodic token-output optimization is enabled by default; set `options.tokenOptimizationOptions.enabled` to `false` to disable that task. Call [`cleanup()`](/api-reference/wallet/cleanup) when finished. For read-only queries without this background work, use [`SparkReadonlyClient`](/wallets/readonly-client).
## 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 leaf swap, adding latency. Optimization can avoid that extra swap; transfer time still depends on network and wallet conditions.
### 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 a leaf 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 leaves when their denominations 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. 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`.
Before calling, reject expired BOLT11 invoices. In SDK 0.12.1, a swap can lock funds before the payment request is rejected. A failed call does not guarantee that no transfer started or that funds are immediately spendable. See [expiry and failed-send handling](/wallets/withdraw-to-lightning#check-expiry-before-paying).
## Method Signature
```typescript theme={null}
interface PayLightningInvoiceParams {
invoice: string;
maxFeeSats: number;
preferSpark?: boolean;
amountSatsToSend?: number;
transferId?: UUID;
}
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. Rejected if the invoice already specifies an amount.
Optional transfer UUID for retrying the same logical payment. Generate it with `generateTransferId()` from the SDK and persist it before the first attempt. Reuse the same ID and payment parameters when retrying; do not generate a new ID for each attempt. This is a `UUID` object, not an arbitrary string. Reconcile a failed attempt before retrying.
## 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}
import { generateTransferId } from "@buildonspark/spark-sdk";
// 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
// Allocate once per logical payment and persist before the first attempt.
const transferId = generateTransferId();
const idempotentPayment = await wallet.payLightningInvoice({
invoice: "lnbc100n...",
maxFeeSats: 5,
transferId,
});
// Retain transferId for reconciliation and any retry of this same 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.previousCursor`)
Pagination direction (defaults to `"NEXT"`)
## Returns
Token transactions response object
Matching token transactions for the requested page
Pagination metadata including `nextCursor` / `previousCursor`
## Newest-first activity
This method uses cursor pagination and returns each page in ascending order. The default `NEXT` direction starts at the oldest page.
For a newest-first feed, start with `PREVIOUS` and no cursor, reverse the page for display, and use `previousCursor` to load older pages. Keep the filters and page size unchanged between requests.
```typescript theme={null}
const filters = {
sparkAddresses: [await wallet.getSparkAddress()],
pageSize: 25,
};
const page = await wallet.queryTokenTransactionsWithFilters({
...filters,
direction: "PREVIOUS",
});
const newestFirst = [...page.tokenTransactionsWithStatus].reverse();
if (page.pageResponse?.hasPreviousPage && page.pageResponse.previousCursor) {
const older = await wallet.queryTokenTransactionsWithFilters({
...filters,
direction: "PREVIOUS",
cursor: page.pageResponse.previousCursor,
});
const olderNewestFirst = [...older.tokenTransactionsWithStatus].reverse();
}
```
## Forward pagination 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,
satsPerVbyteFee,
}: {
depositTransactionId: string;
outputIndex?: number;
destinationAddress: string;
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
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 Bitcoin wallet queries. New wallets default to `false`. Token activity remains public. Enabling privacy retains access for the owner and the single authorized viewer. See [Privacy Mode](/wallets/privacy) for scope and limitations.
## Method Signature
```typescript theme={null}
async setPrivacyEnabled(privacyEnabled: boolean): Promise
```
## Parameters
Whether to enable privacy mode
## Returns
Updated wallet settings, or undefined if no settings were returned
## 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);
```
# setViewerIdentityPublicKey
Source: https://docs.spark.money/api-reference/wallet/set-viewer-identity-public-key
Grants one identity read access to Bitcoin wallet queries while privacy is enabled.
Grants one identity read access to Bitcoin wallet queries while privacy is enabled. The argument is a hex-encoded, 33-byte compressed identity public key. Setting it replaces any existing viewer; it does not enable privacy or grant spending access.
## Method signature
```typescript theme={null}
async setViewerIdentityPublicKey(viewerIdentityPublicKey: string): Promise
```
Returns the updated settings, or `undefined` if no settings were returned. To confirm the configured key, call `const settings = await wallet.getWalletSettings()` and inspect `settings?.viewerIdentityPublicKey`.
See [Grant a service read access](/wallets/privacy#grant-a-service-read-access) for key derivation, setup, and revocation.
# 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;
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`)
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 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.
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 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 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.
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.
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.
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 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.
## 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.cleanup();
```
# 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
```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
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.
## 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.
## 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.
## 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 operators, or a threshold number of them, assist with the transfer of off-chain UTXOs.
## Spark Service Provider (SSP)
A service provider that facilitates efficient deposits into and withdrawals from Spark. Any number of SSPs can exist within a single Spark. SSPs or another entity can optionally provide Lightning services, enabling 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?
Spark is supported by its founding Spark Operators (SOs): [Lightspark](https://www.lightspark.com), [Flashnet](https://flashnet.xyz), and [Breez](https://breez.technology/), each selected for proven security and operational expertise. We take a thoughtful and deliberate approach to growing the operator set because security and reliability are our top priorities. Additional SOs will join as the network scales.
### 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.
### Who provides liquidity for deposits and withdrawals?
Lightspark provides liquidity for deposits and withdrawals. Other providers can participate too, giving users more options and reducing reliance on any one provider.
### 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)$
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 Lightning payment provider, agreeing to conditionally transfer leaves upon payment completion.
3. SE locks transfers on Alice's specified leaves until a specified time.
4. The payment provider makes a Lightning payment to pay Bob's invoice.
5. The payment provider submits proof of Lightning payment to the SE.
6. SE finalizes the transfer of Alice's leaves to the Lightning payment provider 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@54.201.244.204:10016
039174f846626c6053ba80f5443d0db33da384f1dde135bf7080ba1eec465019c3@54.149.30.52:10015
```
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.
# 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.
# 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.
# 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
### 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.
# 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.
# 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.
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.
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
# 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.
# 0-Conf Deposits
Source: https://docs.spark.money/wallets/0-conf
Use the Spark SDK instant static deposit APIs to quote and claim Bitcoin deposits before final confirmation.
0-conf deposits make inbound Bitcoin feel instant. Instead of waiting for the normal L1 confirmation threshold before a deposit becomes usable, an enabled integration can quote the transaction, select an available fulfillment plan, and claim the credit as soon as the transaction is accepted for instant funding.
For wallets, onramps, bridges, exchanges, and payment apps, this removes the dead time between "BTC sent" and "funds available." Users can keep trading, bridging, spending, or settling while the Bitcoin transaction continues toward final confirmation in the background.
0-conf static deposits are currently gated. Contact the team to enable support for your integration.
The first-party JavaScript SDK exposes this flow through:
* `wallet.getInstantStaticDepositQuote(transactionId, outputIndex?, partnerId?)`
* `wallet.claimInstantStaticDeposit({ quote, plan, transactionId, outputIndex })`
These `SparkWallet` methods quote the deposit, return the available funding plans, and claim the selected plan.
***
## Flow
Use the same static deposit address flow as a normal L1 deposit.
```typescript theme={null}
const depositAddress = await wallet.getStaticDepositAddress();
```
Broadcast a Bitcoin transaction paying the static deposit address. Record the transaction ID and the output index that pays the Spark deposit address.
```typescript theme={null}
const transactionId = "abc123...";
const outputIndex = 0;
```
Ask for an instant static deposit quote. Pass `partnerId` if one has been issued for your integration.
```typescript theme={null}
const quoteResult =
await wallet.getInstantStaticDepositQuote(
transactionId,
outputIndex,
partnerId
);
```
The quote response includes one or more fulfillment plans. A plan with `confirmations === 0` is the 0-conf path. Plans with higher confirmation counts can also be claimed through the same SDK method.
```typescript theme={null}
const plan = quoteResult.fulfillmentPlans.find(
(plan) => plan.confirmations === 0
) ?? quoteResult.fulfillmentPlans[0];
if (!plan) {
throw new Error("No instant deposit fulfillment plan available");
}
```
Claim with the quote and selected plan. The SDK signs the correct user statement for the selected plan and submits the instant static deposit claim.
```typescript theme={null}
const claim = await wallet.claimInstantStaticDeposit({
quote: quoteResult.quote,
plan,
transactionId: quoteResult.quote.transactionId,
outputIndex: quoteResult.quote.outputIndex,
});
console.log("Instant deposit claim:", claim.claimId);
```
***
## Complete Example
```typescript theme={null}
const depositAddress = await wallet.getStaticDepositAddress();
console.log("Send BTC to:", depositAddress);
// After the Bitcoin transaction is broadcast:
const transactionId = "abc123...";
const outputIndex = 0;
const partnerId = "your-partner-id"; // Optional. Use only if Spark provides one.
const quoteResult =
await wallet.getInstantStaticDepositQuote(
transactionId,
outputIndex,
partnerId
);
const zeroConfPlan = quoteResult.fulfillmentPlans.find(
(plan) => plan.confirmations === 0
);
if (!zeroConfPlan) {
throw new Error("0-conf is not available for this deposit");
}
const claim = await wallet.claimInstantStaticDeposit({
quote: quoteResult.quote,
plan: zeroConfPlan,
transactionId: quoteResult.quote.transactionId,
outputIndex: quoteResult.quote.outputIndex,
});
console.log("Claim ID:", claim.claimId);
```
***
## Quote Response
`getInstantStaticDepositQuote` returns:
```typescript theme={null}
type InstantStaticDepositQuoteOutput = {
quote: {
id: string;
network: BitcoinNetwork;
transactionId: string;
outputIndex: number;
depositAmount: CurrencyAmount;
creditAmount: CurrencyAmount;
quoteSignature: string;
};
fulfillmentPlans: Array<{
id: string;
amount: CurrencyAmount;
confirmations: number;
status: string;
transferSparkId: string | null;
}>;
};
```
Use `quote.creditAmount.originalValue` when you need the credited sat amount. Use `plan.confirmations` to distinguish the 0-conf plan from plans that require one or more confirmations.
***
## Claim Response
`claimInstantStaticDeposit` returns:
```typescript theme={null}
type InstantStaticDepositClaim = {
claimId: string;
};
```
After claiming, monitor wallet balance, transfer state, or your own deposit workflow using your normal Spark wallet integration.
***
## Notes
* The deposit must be to a static deposit address from `getStaticDepositAddress()`.
* Pass `outputIndex` when you know it. The quote method can auto-detect it, but the claim call requires an output index.
* 0-conf availability is determined by the quote and fulfillment plans. If the deposit is not eligible or your integration is not enabled, the quote or claim may fail.
## Related APIs
* [`getInstantStaticDepositQuote`](/api-reference/wallet/get-instant-static-deposit-quote)
* [`claimInstantStaticDeposit`](/api-reference/wallet/claim-instant-static-deposit)
* [`getStaticDepositAddress`](/api-reference/wallet/get-static-deposit-address)
* [`getClaimStaticDepositQuote`](/api-reference/wallet/get-claim-static-deposit-quote)
# 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.
***
## 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("Available:", balance.satsBalance.available, "sats");
console.log("Owned:", balance.satsBalance.owned, "sats");
console.log("Incoming:", balance.satsBalance.incoming, "sats");
console.log("Token Balances:", balance.tokenBalances);
```
Bitcoin balances in satoshis: `available` (spendable), `owned` (includes funds locked in outgoing transfers and swaps), and `incoming` (pending inbound transfers not yet claimed).
Map of Bech32m token identifiers to token balance objects. Each balance includes `ownedBalance` (total owned), `availableToSendBalance` (excludes pending transfers), and token metadata.
***
## Received does not mean spendable
An incoming Bitcoin transfer must be claimed by the receiving wallet before it becomes owned and available. A payment to an offline receiver can appear in `getPendingTransfers()` while both read-only `getAvailableBalance()` and `getOwnedBalance()` remain zero. The read-only client cannot claim it.
An initialized `SparkWallet` claims incoming transfers automatically. Use `satsBalance.incoming` for pending funds known to the wallet, and `transfer:claimed` followed by a balance refresh to update spendable funds. A Lightning receive request reaching `TRANSFER_COMPLETED` is not a substitute for checking the receiving wallet’s balance.
`owned` is not “available plus every incoming payment.” Show pending receipts separately, and use `available` when deciding whether a payment can be sent.
## 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
***
For newest-first token history, use the [cursor pagination example](/api-reference/wallet/query-token-transactions-with-filters#newest-first-activity).
## 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.satsBalance.available, "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.
***
## 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.satsBalance.available, "sats");
console.log("Token Balances:", balance.tokenBalances);
```
Bitcoin balances in satoshis: `available`, `owned`, and `incoming`. Use `available` for spendable funds.
Map of Bech32m token identifiers to token balance and metadata. `UserTokenMetadata` includes `extraMetadata?: Uint8Array` for arbitrary issuer-defined bytes.
cleanup()
Initialization starts an active wallet: it subscribes to events, claims incoming transfers, and runs periodic maintenance such as token-output optimization. This also applies to a wallet used only to create invoices for other users. For queries alone, use [`SparkReadonlyClient`](/wallets/readonly-client).
Automatic token-output optimization is enabled by default. To disable that task, pass `tokenOptimizationOptions: { enabled: false }` inside the initialization `options`. The wallet still subscribes to events and claims incoming transfers; this is not a read-only mode.
Call `cleanup()` when the wallet is no longer needed. It clears background intervals, aborts streams, removes listeners, and closes connections. Open resources can keep a Node.js script alive after its main function finishes.
```typescript theme={null}
try {
console.log(await wallet.getSparkAddress());
// Perform this script’s wallet work here.
} finally {
await wallet.cleanup();
}
```
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
cleanup()
```
# Deposit from L1
Source: https://docs.spark.money/wallets/deposit-from-l1
Deposit Bitcoin from L1 using reusable static addresses.
***
## 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.
***
## 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 (use the returned `invoice.expiresAt`)
Lightning invoices start with "ln" followed by the network identifier (bc for mainnet) and typically look like this: `lnbc1...`
***
## 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);
// Save invoice.id in your application; it is a lookup ID, not a payable invoice.
```
Track the payment status until completion.
```typescript theme={null}
// Poll for payment status
const checkPayment = async (invoiceId: string) => {
const request = await wallet.getLightningReceiveRequest(invoiceId);
if (!request) {
console.log("Request unavailable: check the ID and querying wallet");
return false;
}
console.log("Receive status:", request.status);
// This is the Lightning receive request status; check the receiver's balance for spendability.
return request.status === "TRANSFER_COMPLETED";
};
```
***
## 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.
The requesting wallet receives the `SPARK_LIGHTNING_RECEIVE_FINISHED` webhook for an invoice it creates on behalf of someone else. Register the webhook on that wallet; do not assume the receiver alone will receive the notification. The receive-request lookup is restricted to the requester or receiver. Preserve the requesting wallet's identity across process restarts, or query as the receiver.
In the SDK, invoice creation uses `SparkWallet`, which also runs background claims and maintenance. See [wallet lifecycle](/wallets/create-wallet).
***
## 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);
if (!receiveRequest) {
throw new Error("Receive request not found or not accessible to this wallet");
}
console.log("Payment status:", receiveRequest.status);
console.log("Transfer amount:", receiveRequest.transfer?.totalAmount);
// 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, or `null` if not found or not accessible to the querying wallet
***
## Match the invoice, request, and transfer
Save the receive request's `id` and `invoice.paymentHash` when creating the invoice. The BOLT11 payment hash identifies the Lightning payment; the Spark transfer ID identifies the movement of funds on Spark. They are different identifiers.
Once the receive request has a transfer, `request.transfer.sparkId` links it to operator transfer history:
```typescript theme={null}
const request = await wallet.getLightningReceiveRequest(receiveRequestId);
if (request?.transfer) {
console.log("Lightning payment hash:", request.invoice.paymentHash);
console.log("Spark transfer ID:", request.transfer.sparkId);
}
```
The operator's `Transfer` does not carry the Lightning payment hash. If you start with a Spark transfer ID, use [`getTransferFromSsp()`](/api-reference/wallet/get-transfer-from-ssp) to retrieve its associated payment request. A plain Lightning receive can have no `sparkInvoice`; that optional field is not the correlation key, and `includeSparkAddress: false` does not populate it.
Only the requester or receiver can look up the receive request. An unrelated wallet gets `null`, which does not establish that the invoice does not exist. A viewer grant for Bitcoin history does not grant access to Lightning receive requests.
### Amounts and units
| Field | Unit |
| - | - |
| `createLightningInvoice({ amountSats })` | Satoshis |
| `request.invoice.amount` | Read `originalValue` together with `originalUnit`; Lightning invoices use millisatoshis |
| `request.transfer.totalAmount` | Currency amount object; Spark transfer amounts use satoshis |
| Webhook `htlc_amount` | `{ value, unit }` or `null`; inspect `unit` instead of assuming millisatoshis |
| Webhook `invoice_amount` | `{ value, unit }`; normally `SATOSHI` |
For example, a 1,000-sat invoice has `originalValue: 1000000` with `originalUnit: "MILLISATOSHI"`. Do not compare bare amount values across these surfaces. The request has no `amountReceived` field.
### Completion and event order
A Lightning receive request status such as `PAYMENT_PREIMAGE_RECOVERED` or `TRANSFER_COMPLETED`, an operator transfer status, and the wallet's `transfer:claimed` event describe different steps. A completed Lightning receive request does not establish that the receiver has claimed the funds. See [received versus spendable](/wallets/balances#received-does-not-mean-spendable).
The incoming Spark transfer can appear before the receive request is finalized and its webhook is delivered. These describe successive parts of the receive flow. Your application may observe events and lookup responses at different times, so do not require a particular notification arrival order. Persist the identifiers, handle repeated notifications, and re-query the request if its transfer is not yet available. Stream heartbeats are connection activity, not payments; do not depend on a fixed heartbeat interval.
For server notifications, see [Webhooks](/wallets/webhooks), including the difference between webhook IDs and SDK request IDs.
## 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
Use the fee quote returned for the operation you are about to perform. Lightning routing fees can vary between payments.
***
## 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/types";
if (!feeQuote) throw new Error("No fee quote available");
const withdrawal = await wallet.withdraw({
onchainAddress: "bc1p...",
exitSpeed: ExitSpeed.MEDIUM,
amountSats: 10000, // Fee is deducted from this amount
feeQuoteId: feeQuote.id,
feeAmountSats: feeQuote.l1BroadcastFeeMedium.originalValue + feeQuote.userFeeMedium.originalValue,
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/types";
if (!feeQuote) throw new Error("No fee quote available");
const withdrawal = await wallet.withdraw({
onchainAddress: "bc1p...",
exitSpeed: ExitSpeed.MEDIUM,
amountSats: 10000, // Recipient receives this amount
feeQuoteId: feeQuote.id,
feeAmountSats: feeQuote.l1BroadcastFeeMedium.originalValue + feeQuote.userFeeMedium.originalValue,
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 (static deposit flows) |
| `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
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
Control public access to Bitcoin balances and transfer history
***
## Overview
New wallets start with privacy off (`privateEnabled: false`). Public clients can query their Bitcoin balances and transfer history. Call `setPrivacyEnabled(true)` to restrict those wallet queries to the owner and a viewer the owner has authorized.
Privacy mode applies to Bitcoin queries only. Token activity, including USDB transfers, remains public. It does not hide Bitcoin on-chain data or information already shared with another party.
## Enable privacy
```typescript theme={null}
const settings = await wallet.setPrivacyEnabled(true);
if (!settings?.privateEnabled) {
throw new Error("Could not confirm that privacy is enabled");
}
```
To make wallet queries public again, call `wallet.setPrivacyEnabled(false)`.
Check the setting from the owner's wallet with [`getWalletSettings()`](/api-reference/wallet/get-wallet-settings). A missing response is not confirmation that privacy is off.
```typescript theme={null}
const settings = await wallet.getWalletSettings();
if (settings) {
console.log("Privacy enabled:", settings.privateEnabled);
console.log("Authorized viewer:", settings.viewerIdentityPublicKey);
}
```
## Grant a service read access
A wallet can authorize **one viewer identity key at a time**. Setting a new key replaces the previous grant. The grant permits reads; it does not permit spending or claiming incoming transfers.
Use a separate mnemonic or seed for the viewer. Do not give a service the owner's wallet mnemonic just to let it read balances.
```typescript theme={null}
import {
SparkReadonlyClient,
deriveViewerIdentityPublicKey,
} from "@buildonspark/spark-sdk";
const config = { network: "MAINNET" } as const;
// On the service: derive its public identity from its own mnemonic.
const viewerPublicKey = await deriveViewerIdentityPublicKey(config, viewerMnemonic);
// On the owner's wallet: authorize the service's public key.
const grant = await wallet.setViewerIdentityPublicKey(viewerPublicKey);
if (grant?.viewerIdentityPublicKey !== viewerPublicKey) {
throw new Error("Could not confirm the viewer grant");
}
await wallet.setPrivacyEnabled(true);
// On the service: authenticate with the same configuration and mnemonic.
const client = await SparkReadonlyClient.createWithViewerKey(config, viewerMnemonic);
const available = await client.getAvailableBalance(ownerSparkAddress);
```
The viewer identity is HD-derived. Use `deriveViewerIdentityPublicKey()` with the same network and account number as `createWithViewerKey()`; the public key of the seed itself is not the viewer identity.
Granting a viewer does not enable privacy. While privacy is off, public clients can still read the wallet. To revoke the grant:
```typescript theme={null}
await wallet.clearViewerIdentityPublicKey();
```
Authentication alone does not grant access to someone else's private wallet: the owner must authorize that identity.
## Interpret empty responses
Without owner or viewer authorization, private-wallet balance queries return `0n` and wallet transfer lists return empty arrays. This is intentional: returning a distinct permission error would reveal that a private wallet exists. These responses do not distinguish a private wallet from an empty wallet. Do not use a zero balance or an empty history as proof that a grant succeeded or failed. Authentication, network, and other request errors can still occur.
Confirm the configured viewer key through the owner's `getWalletSettings()`. The read-only client has no public permission-check method. An event-stream error is not a supported permission probe; see [Wallet Viewer limitations](/wallets/readonly-client#events-and-client-boundaries).
Privacy is a wallet-query setting, not a guarantee that every record of a transfer is hidden. Transfer details may also be accessible through another participant. Do not use `getTransfersByIds()` to test whether a wallet granted you access, and avoid exposing transfer IDs unnecessarily.
## Webhooks
Webhooks report request outcomes independently of wallet-history read access. They do not grant access to private queries and do not claim incoming funds. See [Webhooks](/wallets/webhooks) for registration, signature verification, and payment matching.
## API reference
* [`setPrivacyEnabled()`](/api-reference/wallet/set-privacy-enabled)
* [`getWalletSettings()`](/api-reference/wallet/get-wallet-settings)
* [`setViewerIdentityPublicKey()`](/api-reference/wallet/set-viewer-identity-public-key)
* [`clearViewerIdentityPublicKey()`](/api-reference/wallet/clear-viewer-identity-public-key)
* [`createWithViewerKey()`](/api-reference/readonly/create-with-viewer-key)
# 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.
**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.satsBalance.available));
}
}, [wallet]);
return (
Wallet Information
{loading ? (
Loading...
) : wallet ? (
Address: {address}Balance: {balance.toString()} sats
) : (
)}
);
}
```
### Initialize a Wallet
A 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.
```tsx theme={null}
// Initialize a new wallet instance
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);
```
### 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 wallet.
## Troubleshooting
### `Cannot read property 'decryptEcies' of null`
This error occurs when native crypto modules aren't loaded properly. Fix it by:
1. **Ensure polyfills are imported first** in your `index.js` - they must come before any other imports
2. **Run `pod install`** in your `ios/` directory for iOS builds
3. **Rebuild your app** completely (not just a hot reload)
```bash theme={null}
# iOS
cd ios && pod install && cd ..
npx react-native run-ios
# Android
npx react-native run-android
```
### Wallet initialization fails silently
Make sure you're awaiting the `initialize()` call and handling errors:
```tsx theme={null}
try {
const { wallet, mnemonic } = await SparkWallet.initialize({
options: { network: "MAINNET" }
});
} catch (error) {
console.error("Wallet init failed:", error);
}
```
# Wallet Viewer
Source: https://docs.spark.money/wallets/readonly-client
Query wallet data without initializing a full wallet
## Overview
The `SparkReadonlyClient` lets you query wallet data without creating a full `SparkWallet`. It exposes read-only methods for balances, transfers, deposits, invoices, and token transactions.
Three use cases:
| Mode | Auth | Use case |
| - | - | - |
| **Public** | None | Dashboards, explorers, analytics. Query any non-private wallet |
| **Viewer Key** | Owner identity or authorized viewer identity | Query a private wallet without running a full wallet |
| **Custom Signer** | Identity key via signer | Partners (e.g., Privy) passing in a signer without a mnemonic |
***
## Installation
The wallet viewer ships with the standard SDK package.
```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
```
***
## Create a Public Client
No authentication. Queries public (non-private) wallet data.
```typescript theme={null}
import { SparkReadonlyClient } from "@buildonspark/spark-sdk";
const client = SparkReadonlyClient.createPublic({
network: "MAINNET",
});
const balance = await client.getAvailableBalance("sp1...");
console.log("Balance:", balance, "sats");
```
Public clients can query any wallet that hasn't enabled [privacy mode](/wallets/privacy). Bitcoin balance and wallet-history queries return empty results when read access is denied. This does not suppress authentication or network errors.
***
## Create with Viewer Key
Authenticates with an identity key derived from a mnemonic or seed. For a separate service identity, the wallet owner must first grant that key access with `setViewerIdentityPublicKey()`. Follow the [grant and revoke flow](/wallets/privacy#grant-a-service-read-access). Keep the viewer mnemonic separate from the owner’s wallet mnemonic.
```typescript theme={null}
import { SparkReadonlyClient } from "@buildonspark/spark-sdk";
const client = await SparkReadonlyClient.createWithViewerKey(
{ network: "MAINNET" },
viewerMnemonic,
);
// Query a wallet that authorized this viewer identity.
const balance = await client.getAvailableBalance("sp1...");
const tokens = await client.getTokenBalance("sp1...");
```
***
## Create with Custom Signer
For integrations where a partner provides a `SparkSigner` implementation instead of a mnemonic.
```typescript theme={null}
import { SparkReadonlyClient } from "@buildonspark/spark-sdk";
const client = SparkReadonlyClient.createWithSigner(
{ network: "MAINNET" },
myCustomSigner,
);
```
***
## Query Examples
### Balances
```typescript theme={null}
// Bitcoin balance
const sats = await client.getAvailableBalance("sp1...");
// Token balances (all tokens)
const tokenBalances = await client.getTokenBalance("sp1...");
for (const [tokenId, info] of tokenBalances) {
console.log(`${info.tokenMetadata.tokenTicker}: ${info.availableToSendBalance}`);
}
// Token balances (specific tokens)
const filtered = await client.getTokenBalance("sp1...", ["btkn1..."]);
```
### Transfers
```typescript theme={null}
// Paginated transfer history
const { transfers, offset } = await client.getTransfers({
sparkAddress: "sp1...",
limit: 25,
offset: 0,
});
// Transfers from last 24 hours
const recent = await client.getTransfers({
sparkAddress: "sp1...",
createdAfter: new Date(Date.now() - 86_400_000),
});
// Specific transfers by ID
const specific = await client.getTransfersByIds(["transfer-id-1", "transfer-id-2"]);
// Pending inbound transfers
const pending = await client.getPendingTransfers("sp1...");
```
### Deposits and UTXOs
```typescript theme={null}
// Unused deposit addresses
const { depositAddresses } = await client.getUnusedDepositAddresses({
sparkAddress: "sp1...",
});
// Static deposit addresses
const staticAddrs = await client.getStaticDepositAddresses("sp1...");
// UTXOs for a specific deposit address
const { utxos } = await client.getUtxosForDepositAddress({
depositAddress: "bc1...",
});
```
### Invoices and Token Transactions
```typescript theme={null}
// Invoice statuses
const { invoiceStatuses } = await client.getSparkInvoices({
invoices: ["spark1..."],
});
// Token transactions with cursor pagination
const page = await client.getTokenTransactions({
sparkAddresses: ["sp1..."],
pageSize: 25,
});
if (page.pageResponse?.nextCursor) {
const next = await client.getTokenTransactions({
sparkAddresses: ["sp1..."],
cursor: page.pageResponse.nextCursor,
direction: "NEXT",
});
}
```
***
## Privacy Model
New wallets are public by default. With privacy enabled, Bitcoin balance and wallet-history queries require the owner's identity or the single viewer identity authorized by the owner. An unrelated authenticated client has no more access than a public client.
Denied reads return zero balances or empty lists, so the response cannot tell you whether the wallet is empty or access is missing. Token activity remains public. See [Privacy Mode](/wallets/privacy) for the scope of the setting and transfer-ID lookup considerations.
## Events and client boundaries
`SparkReadonlyClient` queries data. It does not claim transfers, optimize wallet outputs, or expose a public event-stream subscription method in SDK 0.12.1. Use polling for a viewer, [`SparkWallet` events](/wallets/balances#real-time-event-monitoring) for an active wallet, or [webhooks](/wallets/webhooks) for request outcomes.
The protected `connectionManager` is not a supported subscription API. Public-client credentials cannot authenticate an operator event stream; a low-level attempt can fail with `UNAUTHENTICATED`, including an invalid-token-encoding message. Do not use that path to test whether a user granted read access.
### Transfer objects differ from the wallet API
| | `SparkWallet.getTransfers()` | `SparkReadonlyClient.getTransfers()` |
| - | - | - |
| Result objects | `WalletTransfer` | Protobuf `Transfer` |
| Enums | Strings such as `"PREIMAGE_SWAP"` | Numeric values from the protobuf enums |
| Wallet perspective | `transferDirection`, `valueReceivedByWallet` | Derive from participants and leaves |
Do not pass viewer results directly to code expecting `WalletTransfer`. For raw transfers, use `senders[]`, `receivers[]`, and the relevant leaf values. Import enum definitions from `@buildonspark/spark-sdk/proto/spark` instead of hard-coding numeric values.
Token history also has a different result shape: the viewer returns `transactions`, while `queryTokenTransactionsWithFilters()` returns `tokenTransactionsWithStatus`. Both use cursors. The viewer does not expose the wallet's [`queryTokenTransactionsByTxHashes()`](/api-reference/wallet/query-token-transactions-by-tx-hashes) method. To determine send versus receive, inspect the transaction's inputs and outputs relative to the wallet; appearing in its history does not establish direction.
***
## API Reference
See the full method reference at [Wallet Viewer API](/api-reference/readonly-overview).
# Spark Invoices
Source: https://docs.spark.money/wallets/spark-invoices
Spark Invoices are native payment requests for the Spark network. Unlike Lightning invoices, Spark invoices support both Bitcoin (sats) and tokens, with optional amounts, expiry times, and signature verification.
***
## Overview
Spark Invoices provide:
* **Sats & Token Support** - Request payment in Bitcoin or any Spark token
* **Optional Amounts** - Create invoices with or without a preset amount
* **Expiration** - Set custom expiry times for invoices
* **Signature Verification** - Cryptographically signed by the receiver
* **Batch Payments** - Fulfill multiple invoices in a single call
Spark invoices are receiver-generated and receiver-signed. The receiver must create the invoice for signature validation.
***
## Create a Sats Invoice
Request a Bitcoin payment using a Spark invoice.
createSatsInvoice(params)
Creates a Spark invoice for receiving Bitcoin.
```typescript theme={null}
const invoice = await wallet.createSatsInvoice({
amount: 10000, // Optional: amount in sats
memo: "Payment for coffee", // Optional: description
expiryTime: new Date(Date.now() + 3600 * 1000) // Optional: 1 hour from now
});
console.log("Spark Invoice:", invoice);
```
Amount in satoshis to request. If omitted, payer specifies the amount.
Optional memo/description for the invoice (max 120 bytes)
Optional: restrict payment to a specific sender's Spark address
Optional expiration time as a Date object (default: 30 days from now)
***
## Create a Token Invoice
Request a token payment using a Spark invoice.
createTokensInvoice(params)
Creates a Spark invoice for receiving tokens.
```typescript theme={null}
const invoice = await wallet.createTokensInvoice({
tokenIdentifier: "btkn1...", // Token identifier
amount: 1000n, // Optional: token amount
memo: "Token payment", // Optional: description
expiryTime: new Date(Date.now() + 3600 * 1000) // Optional: 1 hour expiry
});
console.log("Token Invoice:", invoice);
```
The Bech32m token identifier (e.g., `btkn1...`)
Token amount to request. If omitted, payer specifies the amount.
Optional memo/description for the invoice (max 120 bytes)
Optional: restrict payment to a specific sender's Spark address
Optional expiration time as a Date object (default: 30 days from now)
***
## Fulfill Spark Invoices
Pay one or more Spark invoices.
fulfillSparkInvoice(params)
Fulfills Spark invoices. Supports batch payments with a mix of sats and token invoices.
```typescript theme={null}
// Pay a single invoice
const result = await wallet.fulfillSparkInvoice([
{ invoice: "spark1..." }
]);
// Pay multiple invoices (batch)
const batchResult = await wallet.fulfillSparkInvoice([
{ invoice: "spark1..." }, // Sats invoice
{ invoice: "spark1..." }, // Token invoice
]);
// Pay invoice with no preset amount
const customAmount = await wallet.fulfillSparkInvoice([
{ invoice: "spark1...", amount: 5000n } // Specify amount for zero-amount invoice
]);
```
Array of objects with `invoice: SparkAddressFormat` and optional `amount: bigint` for zero-amount invoices
`fulfillSparkInvoice` can process multiple invoices in a single call, including a mix of sats and token invoices for different assets.
***
## Query Spark Invoices
Check the status of Spark invoices.
querySparkInvoices(invoices)
Query the status of one or more Spark invoices.
```typescript theme={null}
const invoiceStatus = await wallet.querySparkInvoices([
"spark1...",
"spark1..."
]);
for (const status of invoiceStatus) {
console.log("Invoice status:", status);
}
```
Array of raw Spark invoice strings to query
***
## Invoice Format
Spark invoices are Bech32m-encoded strings that contain:
* **Network identifier** - Derived from the HRP (human-readable prefix)
* **Receiver's identity public key** - Who will receive the payment
* **Payment type** - Sats or tokens (with token identifier)
* **Amount** - Optional preset amount
* **Memo** - Optional description
* **Expiry** - Optional expiration time
* **Signature** - BIP340 Schnorr signature from the receiver
***
## Use Cases
### Point of Sale
```typescript theme={null}
// Merchant creates invoice for specific amount
const invoice = await merchantWallet.createSatsInvoice({
amount: 25000,
memo: "Order #1234"
});
// Display QR code to customer
displayQR(invoice);
// Customer pays
await customerWallet.fulfillSparkInvoice([
{ invoice }
]);
```
### Donations (Variable Amount)
```typescript theme={null}
// Create invoice without preset amount
const donationInvoice = await wallet.createSatsInvoice({
memo: "Donation to Project X"
});
// Donor chooses amount when paying
await donorWallet.fulfillSparkInvoice([
{ invoice: donationInvoice, amount: 100000n }
]);
```
### Batch Payouts
```typescript theme={null}
// Pay multiple recipients in one call
await wallet.fulfillSparkInvoice([
{ invoice: employeeInvoice1 },
{ invoice: employeeInvoice2 },
{ invoice: employeeInvoice3 }
]);
```
# Spark Signer Interface
Source: https://docs.spark.money/wallets/spark-signer
The Spark SDK provides the `SparkSigner` interface to enable flexible implementation of signing operations. This abstraction allows you to customize how cryptographic operations are performed, enabling support for secure enclaves, hardware wallets, remote signing services, and other specialized key management systems.
The SDK includes `DefaultSparkSigner` which handles standard single-signature operations and stores nonces internally for security. For server-side enclave integrations, `UnsafeStatelessSparkSigner` is available.
***
## Core Concepts
### Key Types
Spark wallets derive 5 key types from a master seed using BIP32:
| Path | Key Type | Purpose |
| - | - | - |
| `m/8797555'/n'/0'` | Identity Key | Primary wallet identifier and authentication |
| `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 |
| `m/8797555'/n'/4'` | HTLC Preimage HD Key | Lightning HTLC operations |
### The KeyDerivation System
The signer uses a discriminated union type to specify how to derive or retrieve a private key for signing operations:
```typescript theme={null}
enum KeyDerivationType {
LEAF = "leaf", // Derive from signing HD key using sha256(path)
DEPOSIT = "deposit", // Use the deposit key directly
STATIC_DEPOSIT = "static_deposit", // Use static deposit key at index
ECIES = "ecies", // Decrypt private key from ciphertext
RANDOM = "random", // Generate a random key (for adaptor signatures)
}
type KeyDerivation =
| { type: KeyDerivationType.LEAF; path: string }
| { type: KeyDerivationType.DEPOSIT }
| { type: KeyDerivationType.RANDOM }
| { type: KeyDerivationType.STATIC_DEPOSIT; path: number }
| { type: KeyDerivationType.ECIES; path: Uint8Array };
```
This abstraction is used throughout the signer interface, particularly in `signFrost()` and `getPublicKeyFromDerivation()`.
### Security Model
* All private keys are derived from a master seed using BIP32 hierarchical deterministic key derivation
* Private keys never leave the signer. Only signatures and public keys are returned.
* `DefaultSparkSigner` stores nonces internally to prevent reuse attacks
* For enclave integrations, `UnsafeStatelessSparkSigner` exposes nonces externally
***
## Implementations
### DefaultSparkSigner
The recommended implementation for client-side applications. It stores signing nonces internally to prevent reuse attacks.
```typescript theme={null}
import { DefaultSparkSigner } from "@buildonspark/spark-sdk";
const signer = new DefaultSparkSigner();
const mnemonic = await signer.generateMnemonic();
const seed = await signer.mnemonicToSeed(mnemonic);
await signer.createSparkWalletFromSeed(seed, 0);
```
### UnsafeStatelessSparkSigner
For server-side enclave integrations where nonces need to be managed externally. This signer returns nonces in `getRandomSigningCommitment()` instead of storing them internally.
```typescript theme={null}
import { UnsafeStatelessSparkSigner } from "@buildonspark/spark-sdk";
// Only use in secure server environments
const signer = new UnsafeStatelessSparkSigner();
```
`UnsafeStatelessSparkSigner` exposes nonces externally. Only use this in secure server environments where you can properly protect nonce data.
### Custom Signer Implementation
You can extend `DefaultSparkSigner` to implement custom signing logic, such as forwarding requests to a secure enclave:
```typescript theme={null}
import { DefaultSparkSigner, SignFrostParams } from "@buildonspark/spark-sdk";
class EnclaveSigner extends DefaultSparkSigner {
private enclave: EnclaveClient;
constructor(enclave: EnclaveClient) {
super();
this.enclave = enclave;
}
async signFrost(params: SignFrostParams): Promise {
// Forward signing request to secure enclave
return this.enclave.signFrost(params);
}
async createSparkWalletFromSeed(
seed: Uint8Array | string,
accountNumber?: number
): Promise {
// Initialize keys in enclave
return this.enclave.initializeWallet(seed, accountNumber);
}
}
// Use with SparkWallet
const { wallet } = await SparkWallet.initialize({
signer: new EnclaveSigner(myEnclave),
options: { network: "MAINNET" }
});
```
### Custom Key Derivation Paths
If you need a non-standard derivation scheme, you can pass a custom `sparkKeysGenerator` to `DefaultSparkSigner`.
This example shows a simple generator that replaces `?` with the `accountNumber` and then derives the five Spark key roots under that account:
```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,
},
};
}
}
// Use ? as placeholder for account number
const customGenerator = new DerivationPathKeysGenerator("m/44'/0'/?'");
const signer = new DefaultSparkSigner({
sparkKeysGenerator: customGenerator
});
```
***
## Wallet Initialization
### Generate Mnemonic
generateMnemonic()
Generates a new BIP39 mnemonic phrase for wallet creation.
```typescript theme={null}
const mnemonic = await signer.generateMnemonic();
console.log(mnemonic); // "abandon ability able about above absent..."
```
A 12-word BIP39 mnemonic phrase
### Convert Mnemonic to Seed
mnemonicToSeed(mnemonic)
Converts a BIP39 mnemonic phrase to a cryptographic seed.
```typescript theme={null}
const seed = await signer.mnemonicToSeed(mnemonic);
console.log("Seed length:", seed.length); // 64 bytes
```
Valid BIP39 mnemonic phrase
64-byte seed derived from the mnemonic
### Initialize from Seed
createSparkWalletFromSeed(seed, accountNumber?)
Initializes the signer with a master seed and derives all necessary keys.
```typescript theme={null}
const seed = await signer.mnemonicToSeed(mnemonic);
const identityPubKey = await signer.createSparkWalletFromSeed(seed, 0);
console.log("Identity public key:", identityPubKey);
```
Master seed as bytes or hex string
Account index for key derivation. Defaults to 0 on REGTEST, 1 on MAINNET for backwards compatibility.
Hex-encoded identity public key
***
## Key Management
### Get Identity Public Key
getIdentityPublicKey()
Retrieves the wallet's identity public key.
```typescript theme={null}
const identityKey = await signer.getIdentityPublicKey();
console.log("Identity key:", identityKey);
```
The identity public key (33 bytes, compressed)
### Get Deposit Signing Key
getDepositSigningKey()
Retrieves the deposit signing public key used for L1 Bitcoin deposits.
```typescript theme={null}
const depositKey = await signer.getDepositSigningKey();
console.log("Deposit signing key:", depositKey);
```
The deposit signing public key
### Get Static Deposit Signing Key
getStaticDepositSigningKey(idx)
Retrieves a static deposit signing public key by index.
```typescript theme={null}
const signingKey = await signer.getStaticDepositSigningKey(0);
console.log("Static deposit signing key:", signingKey);
```
Index for the static deposit key
The static deposit signing public key
### Get Static Deposit Secret Key
getStaticDepositSecretKey(idx)
Retrieves a static deposit private key by index. Used when the private key needs to be shared with the deposit service during static deposit flows.
```typescript theme={null}
const secretKey = await signer.getStaticDepositSecretKey(0);
```
Index for the static deposit key
The static deposit private key
### Get Public Key from Derivation
getPublicKeyFromDerivation(keyDerivation)
Derives a public key based on a `KeyDerivation` specification.
```typescript theme={null}
import { KeyDerivationType } from "@buildonspark/spark-sdk";
// Get public key for a leaf
const leafPubKey = await signer.getPublicKeyFromDerivation({
type: KeyDerivationType.LEAF,
path: "leaf-123"
});
// Get deposit public key
const depositPubKey = await signer.getPublicKeyFromDerivation({
type: KeyDerivationType.DEPOSIT
});
```
Specifies how to derive the key (LEAF, DEPOSIT, STATIC\_DEPOSIT, ECIES, or RANDOM)
The derived public key
***
## Digital Signatures
### Sign with Identity Key
signMessageWithIdentityKey(message, compact?)
Signs a message using the wallet's identity key with ECDSA.
```typescript theme={null}
const message = new TextEncoder().encode("Hello, Spark!");
const signature = await signer.signMessageWithIdentityKey(message);
console.log("Signature:", signature);
// With compact format
const compactSignature = await signer.signMessageWithIdentityKey(message, true);
```
Message to sign
Use compact signature format instead of DER
ECDSA signature (DER or compact format)
### Validate Signature
validateMessageWithIdentityKey(message, signature)
Validates an ECDSA signature against the identity key.
```typescript theme={null}
const message = new TextEncoder().encode("Hello, Spark!");
const signature = await signer.signMessageWithIdentityKey(message);
const isValid = await signer.validateMessageWithIdentityKey(message, signature);
console.log("Signature valid:", isValid);
```
Original message
Signature to validate
True if signature is valid
### Sign with Schnorr (Identity Key)
signSchnorrWithIdentityKey(message)
Creates a Schnorr signature using the identity key.
```typescript theme={null}
const message = new TextEncoder().encode("Hello, Spark!");
const schnorrSignature = await signer.signSchnorrWithIdentityKey(message);
console.log("Schnorr signature:", schnorrSignature);
```
Message to sign
Schnorr signature (64 bytes)
### Sign Transaction Index
signTransactionIndex(tx, index, publicKey)
Signs a specific input of a Bitcoin transaction. The method looks up the private key based on the provided public key (must be either identity or deposit key).
```typescript theme={null}
import { Transaction } from "@scure/btc-signer";
const tx = new Transaction();
// ... build transaction ...
const identityKey = await signer.getIdentityPublicKey();
signer.signTransactionIndex(tx, 0, identityKey);
```
The Bitcoin transaction to sign (from @scure/btc-signer)
Input index to sign
Public key identifying which private key to use
***
## FROST Protocol (Threshold Signatures)
Spark uses FROST (Flexible Round-Optimized Schnorr Threshold) signatures for collaborative signing between users and Signing Operators.
### Get Random Signing Commitment
getRandomSigningCommitment()
Generates a random signing commitment for FROST protocol. In `DefaultSparkSigner`, the nonce is stored internally. In `UnsafeStatelessSparkSigner`, the nonce is returned in the response.
```typescript theme={null}
const commitment = await signer.getRandomSigningCommitment();
console.log("Commitment:", commitment.commitment);
// commitment.nonce is only present in UnsafeStatelessSparkSigner
```
Object containing the signing commitment, and optionally the nonce (for stateless signers)
### Get Nonce for Commitment
getNonceForSelfCommitment(selfCommitment)
Retrieves the nonce associated with a previously generated commitment. In `DefaultSparkSigner`, this looks up the internally stored nonce. In `UnsafeStatelessSparkSigner`, this returns the nonce from the commitment object.
```typescript theme={null}
const commitment = await signer.getRandomSigningCommitment();
const nonce = signer.getNonceForSelfCommitment(commitment);
```
The commitment returned from getRandomSigningCommitment()
The signing nonce, or undefined if not found
### FROST Signing
signFrost(params)
Performs FROST signing operation. This produces the user's signature share that will be combined with Signing Operator shares.
```typescript theme={null}
import { KeyDerivationType } from "@buildonspark/spark-sdk";
const commitment = await signer.getRandomSigningCommitment();
const params = {
message: sighash, // Transaction sighash to sign
keyDerivation: { type: KeyDerivationType.LEAF, path: leafId },
publicKey: leafPublicKey,
verifyingKey: leaf.verifyingPublicKey,
selfCommitment: commitment,
statechainCommitments: soCommitments, // From Signing Operators
adaptorPubKey: undefined // Optional adaptor for atomic swaps
};
const signatureShare = await signer.signFrost(params);
```
FROST signing parameters:
* `message`: The message (sighash) to sign
* `keyDerivation`: How to derive the signing key
* `publicKey`: The user's public key for this leaf
* `verifyingKey`: The aggregated public key (user + SOs)
* `selfCommitment`: User's signing commitment
* `statechainCommitments`: Signing Operators' commitments
* `adaptorPubKey`: Optional adaptor public key
FROST signature share
### Aggregate FROST Signatures
aggregateFrost(params)
Aggregates FROST signature shares (user's + Signing Operators') into a final Schnorr signature.
```typescript theme={null}
const params = {
message: sighash,
publicKey: leafPublicKey,
verifyingKey: leaf.verifyingPublicKey,
selfCommitment: commitment,
selfSignature: userSignatureShare,
statechainCommitments: soCommitments,
statechainSignatures: soSignatures,
statechainPublicKeys: soPublicKeys
};
const finalSignature = await signer.aggregateFrost(params);
```
FROST aggregation parameters including all signature shares and public keys
Final aggregated Schnorr signature (64 bytes)
***
## Secret Sharing
These methods implement Shamir's Secret Sharing with verifiable proofs, used internally for key splitting operations.
### Split Secret with Proofs
splitSecretWithProofs(params)
Splits a secret into shares using Shamir's Secret Sharing with verifiable proofs.
```typescript theme={null}
const params = {
secret: privateKey,
curveOrder: secp256k1.CURVE.n,
threshold: 3,
numShares: 5
};
const shares = await signer.splitSecretWithProofs(params);
```
* `secret`: The secret to split (as Uint8Array)
* `curveOrder`: The curve order (bigint)
* `threshold`: Minimum shares needed to reconstruct
* `numShares`: Total number of shares to generate
Array of verifiable secret shares
### Subtract and Split with Proofs
subtractAndSplitSecretWithProofsGivenDerivations(params)
Subtracts two derived private keys and splits the result into verifiable shares. Used in transfer flows.
```typescript theme={null}
import { KeyDerivationType } from "@buildonspark/spark-sdk";
const shares = await signer.subtractAndSplitSecretWithProofsGivenDerivations({
first: { type: KeyDerivationType.LEAF, path: "old-leaf" },
second: { type: KeyDerivationType.LEAF, path: "new-leaf" },
curveOrder: secp256k1.CURVE.n,
threshold: 3,
numShares: 5
});
```
### Subtract, Split, and Encrypt
subtractSplitAndEncrypt(params)
Subtracts keys, splits into shares, and encrypts the second key for the receiver. Used in transfer operations.
```typescript theme={null}
const result = await signer.subtractSplitAndEncrypt({
first: { type: KeyDerivationType.LEAF, path: oldLeafId },
second: { type: KeyDerivationType.LEAF, path: newLeafId },
curveOrder: secp256k1.CURVE.n,
threshold: 3,
numShares: 5,
receiverPublicKey: receiverIdentityKey
});
console.log(result.shares); // Verifiable secret shares
console.log(result.secretCipher); // Encrypted key for receiver
```
***
## Encryption
### Decrypt ECIES
decryptEcies(ciphertext)
Decrypts ECIES-encrypted data using the identity key. Returns the public key corresponding to the decrypted private key.
```typescript theme={null}
const ciphertext = encryptedKeyFromSender;
const publicKey = await signer.decryptEcies(ciphertext);
```
ECIES-encrypted private key
Public key corresponding to the decrypted private key
***
## HTLC Operations
### Generate HTLC HMAC
htlcHMAC(transferID)
Generates an HMAC for HTLC (Hash Time-Locked Contract) operations using the HTLC preimage key.
```typescript theme={null}
const hmac = await signer.htlcHMAC(transferId);
```
The transfer ID to generate HMAC for
HMAC output (32 bytes)
***
## Complete Example
```typescript theme={null}
import {
SparkWallet,
DefaultSparkSigner,
KeyDerivationType
} from "@buildonspark/spark-sdk";
async function demonstrateSparkSigner() {
// 1. Create and initialize signer
const signer = new DefaultSparkSigner();
const mnemonic = await signer.generateMnemonic();
console.log("Generated mnemonic:", mnemonic);
const seed = await signer.mnemonicToSeed(mnemonic);
const identityKeyHex = await signer.createSparkWalletFromSeed(seed, 0);
console.log("Identity key:", identityKeyHex);
// 2. Get keys
const identityKey = await signer.getIdentityPublicKey();
const depositKey = await signer.getDepositSigningKey();
const staticDepositKey = await signer.getStaticDepositSigningKey(0);
console.log("Keys initialized");
// 3. Sign and validate a message
const message = new TextEncoder().encode("Hello, Spark!");
const signature = await signer.signMessageWithIdentityKey(message);
const isValid = await signer.validateMessageWithIdentityKey(message, signature);
console.log("Signature valid:", isValid);
// 4. Schnorr signature
const schnorrSig = await signer.signSchnorrWithIdentityKey(message);
console.log("Schnorr signature length:", schnorrSig.length);
// 5. Get public key from derivation
const leafPubKey = await signer.getPublicKeyFromDerivation({
type: KeyDerivationType.LEAF,
path: "my-leaf-id"
});
console.log("Leaf public key:", leafPubKey);
// 6. Use with SparkWallet
const { wallet } = await SparkWallet.initialize({
mnemonicOrSeed: mnemonic,
accountNumber: 0,
options: { network: "REGTEST" }
});
const address = await wallet.getSparkAddress();
console.log("Spark address:", address);
}
```
***
## Integration Patterns
### Remote Signer (Enclave Pattern)
For wallet providers that need to keep keys in a secure enclave:
```typescript theme={null}
class RemoteSigner extends DefaultSparkSigner {
private apiClient: EnclaveAPIClient;
private userId: string;
constructor(apiClient: EnclaveAPIClient, userId: string) {
super();
this.apiClient = apiClient;
this.userId = userId;
}
async signFrost(params: SignFrostParams): Promise {
// Forward to enclave
return this.apiClient.signFrost(this.userId, params);
}
async aggregateFrost(params: AggregateFrostParams): Promise {
return this.apiClient.aggregateFrost(this.userId, params);
}
async createSparkWalletFromSeed(
seed: Uint8Array | string,
accountNumber?: number
): Promise {
// Keys are managed in the enclave
return this.apiClient.initializeWallet(this.userId, seed, accountNumber);
}
}
```
### Multi-User Wallet Pattern
For services managing wallets for multiple users:
```typescript theme={null}
class MultiUserSigner extends UnsafeStatelessSparkSigner {
private keyStore: KeyStore;
async signFrost(params: SignFrostParams): Promise {
// Look up user's key material from secure storage
const userKeys = await this.keyStore.getKeys(params.publicKey);
// Perform signing with user's keys
return super.signFrost({
...params,
// Additional context if needed
});
}
}
```
For multi-user wallets, consider the trust model carefully. See the [Alby architecture blog post](https://getalby.com/blog/a-trust-minimized-multi-user-nwc-wallet-with-ark-spark) for a trust-minimized approach using NWC.
# Sparkscan
Source: https://docs.spark.money/wallets/sparkscan
Sparkscan is the official block explorer for the Spark network, providing visibility into transactions, addresses, and network activity.
## Features
* **Transaction Explorer**: View detailed transaction information and status
* **Address Lookup**: Search for addresses and view their transaction history
* **Network Statistics**: Monitor network activity and performance metrics
* **Real-time Updates**: Live transaction feeds and network monitoring
## Usage
Visit [Sparkscan](https://sparkscan.io) to explore the Spark network and track your transactions.
## API Access
Sparkscan also provides API endpoints for programmatic access to blockchain data, enabling developers to integrate network data into their applications.
# Testing Guide
Source: https://docs.spark.money/wallets/testing-guide
Learn how to test your Spark wallet integration effectively.
## Overview
Testing your Spark wallet implementation is crucial for ensuring reliability and user experience. This guide covers testing strategies, tools, and best practices.
## Testing Environment
### Regtest Network
```typescript theme={null}
// Configure for regtest testing
const wallet = await initialize({
mnemonicOrSeed: 'your-test-mnemonic',
signer: yourSigner,
options: {
network: 'regtest',
apiUrl: 'https://regtest-api.spark.network'
}
});
```
### Test Data
```typescript theme={null}
// Use test tokens and addresses
const testTokenId = 'test-token-id';
const testAddress = 'test-spark-address';
```
## Unit Testing
```typescript theme={null}
// Test wallet initialization
describe('Wallet Initialization', () => {
it('should initialize with valid mnemonic', async () => {
const wallet = await initialize({
mnemonicOrSeed: 'test mnemonic',
signer: mockSigner
});
expect(wallet).toBeDefined();
});
});
```
## Integration Testing
```typescript theme={null}
// Test wallet operations
describe('Wallet Operations', () => {
it('should transfer tokens', async () => {
const result = await wallet.transfer({
to: testAddress,
amount: '1000',
tokenId: testTokenId
});
expect(result.success).toBe(true);
});
});
```
## Best Practices
* Use regtest network for development
* Mock external dependencies
* Test error conditions
* Verify transaction states
* Test with different network conditions
# Spark CLI
Source: https://docs.spark.money/wallets/tools/cli
# Transfer Bitcoin
Source: https://docs.spark.money/wallets/transfer-bitcoin
Send Bitcoin instantly between Spark wallets with zero fees.
***
## Transfer Bitcoin
Send Bitcoin to another Spark wallet using a simple transfer method.
transfer(params)
Transfers Bitcoin to another Spark wallet on the Spark network.
```typescript theme={null}
const transferResult = await wallet.transfer({
receiverSparkAddress: "spark1p...", // Recipient's Spark address
amountSats: 50000, // Amount in satoshis
});
console.log("Transfer successful:", transferResult);
```
The recipient's Spark address
The amount in satoshis to transfer
The completed transfer details including transaction ID and status
***
## Check Transfer Status
Monitor your transfers and track their status using transfer queries and events.
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, use [`queryTokenTransactionsWithFilters()`](/api-reference/wallet/query-token-transactions-with-filters).
```typescript theme={null}
// Get recent transfers
const transfers = await wallet.getTransfers(10);
console.log("Recent transfers:", transfers.transfers);
// Check specific transfer status
const recentTransfer = transfers.transfers[0];
console.log("Transfer ID:", recentTransfer.id);
console.log("Transfer status:", recentTransfer.status);
console.log("Amount:", recentTransfer.totalValue, "sats");
```
Maximum number of transfers to return (default: 20)
Offset for pagination (default: 0)
Array of transfer objects containing transfer details
The offset used for this request
***
## Real-time Transfer Monitoring
Monitor transfer status in real-time using event listeners.
```typescript theme={null}
// Listen for incoming transfer events
wallet.on("transfer:claimed", (transferId, updatedBalance) => {
console.log(`Incoming transfer ${transferId} claimed! New balance: ${updatedBalance} sats`);
});
// Note: There are no events for outgoing transfers.
// The transfer() method returns immediately when the transfer completes.
```
The `transfer:claimed` event only fires for **incoming** transfers. For outgoing transfers, the `transfer()` method returns a `WalletTransfer` object when complete.
# Transfer Tokens
Source: https://docs.spark.money/wallets/transfer-tokens
Send tokens to any Spark address with single or batch transfers.
***
## Transfer Tokens
Send tokens to another Spark wallet using the token identifier and amount.
transferTokens(params)
Transfers tokens to another user on the Spark network.
```typescript theme={null}
const transferResult = await wallet.transferTokens({
tokenIdentifier: "btkn1p...", // Bech32m token identifier
tokenAmount: BigInt(1000), // Amount of tokens to transfer
receiverSparkAddress: "spark1p...", // Recipient's Spark address
});
console.log("Transfer successful:", transferResult);
```
The Bech32m token identifier (e.g., btkn1...) of the token to transfer
The amount of tokens to transfer
The recipient's Spark address
Strategy for selecting token outputs. `SMALL_FIRST` uses smallest outputs first, `LARGE_FIRST` uses largest.
Optional: Specific outputs to use for the transfer. Overrides `outputSelectionStrategy`.
The transaction ID of the token transfer
***
## Get Token Balances
Before transferring tokens, check what tokens you own and their balances using `getBalance()`.
```typescript theme={null}
const { balance, tokenBalances } = await wallet.getBalance();
console.log("Sats balance:", balance);
// Iterate over token balances
for (const [tokenId, tokenData] of tokenBalances) {
console.log(`Token ${tokenId}:`);
console.log(" Owned balance:", tokenData.ownedBalance);
console.log(" Available to send:", tokenData.availableToSendBalance);
console.log(" Name:", tokenData.tokenMetadata.tokenName);
console.log(" Ticker:", tokenData.tokenMetadata.tokenTicker);
}
// Check balance of a specific token
const specificToken = tokenBalances.get("btkn1...");
if (specificToken) {
console.log("Token balance (owned):", specificToken.ownedBalance);
console.log("Token balance (available):", specificToken.availableToSendBalance);
}
```
# TypeScript SDK
Source: https://docs.spark.money/wallets/typescript
TypeScript SDK for building Bitcoin wallet applications on Node.js and browsers. 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 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
```
Create a wallet instance that will be used to interact with the Spark network.
```ts wallet.ts 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.
```ts app.ts theme={null}
import { SparkWallet } from "@buildonspark/spark-sdk";
async function main() {
try {
// Initialize wallet
const { wallet, mnemonic } = await SparkWallet.initialize({
options: { network: "REGTEST" }
});
console.log("Wallet created successfully!");
console.log("Mnemonic:", mnemonic);
console.log("Address:", await wallet.getSparkAddress());
const { balance } = await wallet.getBalance();
console.log("Balance:", balance, "sats");
// Example: Send Bitcoin
const transfer = await wallet.transfer({
receiverSparkAddress: "spark1...",
amountSats: 1000,
});
console.log("Transfer completed:", transfer.id);
} 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 Wallet Operations
### Initialize a Wallet
A 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}
// Initialize a new wallet instance
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);
```
### 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 wallet.
## TypeScript Features
### Type Safety
The Spark TypeScript SDK provides full type safety for all wallet operations:
```ts theme={null}
import { SparkWallet } from "@buildonspark/spark-sdk";
// TypeScript will provide autocomplete and type checking
const { wallet } = await SparkWallet.initialize({
options: { network: "REGTEST" }
});
// All methods are fully typed
const address: string = await wallet.getSparkAddress();
const { balance, tokenBalances } = await wallet.getBalance();
// balance: bigint (satoshis)
// tokenBalances: Map
```
### Interface Definitions
All wallet methods return properly typed interfaces:
```ts theme={null}
interface InitResult {
wallet: SparkWallet;
mnemonic?: string; // undefined if raw seed was provided
}
interface BalanceResult {
balance: bigint;
tokenBalances: Map;
}
interface WalletTransfer {
id: string;
senderIdentityPublicKey: string;
receiverIdentityPublicKey: string;
status: TransferStatus; // See TransferStatus enum below
totalValue: number;
expiryTime: Date | undefined;
leaves: WalletTransferLeaf[];
createdTime: Date | undefined;
updatedTime: Date | undefined;
type: TransferType; // "TRANSFER" | "COOPERATIVE_EXIT" | "PREIMAGE_SWAP" | "UTXO_SWAP"
transferDirection: "INCOMING" | "OUTGOING";
userRequest: UserRequestType | undefined;
sparkInvoice: string | undefined; // Spark invoice associated with this transfer
}
// Common status values you'll encounter:
type TransferStatus =
| "TRANSFER_STATUS_COMPLETED"
| "TRANSFER_STATUS_EXPIRED"
| "TRANSFER_STATUS_RETURNED"
| "TRANSFER_STATUS_SENDER_INITIATED"
// ... and other intermediate states
```
# Unilateral Exit
Source: https://docs.spark.money/wallets/unilateral-exit
A unilateral exit allows you to withdraw funds from Spark without requiring anyone else's permission. If you choose not to trust Spark anymore or if Spark were to shut down, you can still retrieve your money.
**Critical Requirements:**
* Only exit leaves **> 16,348 sats** (smaller
amounts cost more in fees than they're worth)
* Must complete entire process
**in single session** - wallet unusable until finished
* Transactions must be
broadcast **from root to leaves** in exact order shown
* **Bitcoin Core V29
required** for ephemeral anchors
* **Beta feature:** we're fixing bugs as we
find them
## Tree Structure Basics
Your Spark funds are organized in a tree:
* Each "leaf" = portion of your funds with different values/timelocks
* Deeper leaves = more transactions required to exit
* Broadcasting order matters: root → leaves
## Getting Started
### 1. Check Your Leaves
```bash theme={null}
sparkcli getleaves
```
Example output:
```
Leaf ID: 019765d1-ad06-79ed-b30e-0afc064c5a1b
Value: 16384 sats
Status: AVAILABLE
Tree ID: 019765d1-ac2e-7ec8-ac37-c77ea070237d
```
### 2. Start Interactive Exit
```bash theme={null}
sparkcli unilateralexit --testmode=true
```
**Important:** Use `--testmode=true` ONLY on Regtest. Mainnet users must use
the regular flow without testmode to properly handle timelocks and manual fee
signing.
The CLI guides you through:
* Leaf selection
* Timelock handling (auto-refreshed in test mode)
* UTXO generation for fees
* Transaction creation and signing
* Broadcast order instructions
## Regtest Testing
**Test on regtest first** - don't learn on mainnet.
### Setup
* Initialize wallet and deposit 131,072 sats for testing
* Use [regtest faucet](https://app.lightspark.com/regtest-faucet) (max 50,000 per deposit)
* CLI auto-generates test wallet for fees
### Broadcasting
Use [https://regtest-mempool.us-west-2.sparkinfra.net/tx/push](https://regtest-mempool.us-west-2.sparkinfra.net/tx/push) to submit packages in order. Wait \~1 min between packages for confirmation.
## Mainnet Usage
### Prerequisites
* Sufficient L1 Bitcoin UTXOs for transaction fees
* Only exit leaves > 16,348 sats
* Recommended: 131,072 sats deposit for better leaf sizes
### Mainnet vs Regtest
* **UTXOs**: Provide your own Bitcoin UTXOs when prompted
* **Broadcasting**: Use `bitcoin-cli submitpackage '["node_tx", "fee_bump_tx"]'`
* **Costs**: Real network fees apply
### Process
1. Check leaves with `sparkcli getleaves`
2. Run `sparkcli unilateralexit` (no testmode - you'll handle timelocks manually)
3. CLI will walk you through it (bring your own UTXOs)
4. Broadcast in exact order shown by CLI
## Essential Commands
```bash theme={null}
sparkcli unilateralexit --testmode=true
sparkcli unilateralexit
sparkcli getleaves
sparkcli checktimelock
sparkcli testonly_expiretimelock
sparkcli signfeebump
```
# Webhooks
Source: https://docs.spark.money/wallets/webhooks
Verify payment notifications and match them to wallet requests.
Webhooks notify your application when a request finishes. Use them to wake a backend or update a payment record without keeping a wallet event listener running. A webhook does not claim incoming funds or grant access to private wallet history.
## Register a webhook
For Lightning receives, register on the wallet that creates the invoice. That wallet receives notifications even when the invoice names another receiver. Receiver-side delivery is not a substitute for registering on the requesting wallet.
```typescript theme={null}
import { randomBytes } from "node:crypto";
import { SparkWalletWebhookEventType } from "@buildonspark/spark-sdk/types";
// Generate once, store securely, and use the same string in your verifier.
const secret = randomBytes(32).toString("hex");
const registration = await wallet.registerSparkWalletWebhook({
secret,
url: "https://example.com/webhooks/spark",
event_types: [SparkWalletWebhookEventType.SPARK_LIGHTNING_RECEIVE_FINISHED],
});
if (!registration) {
throw new Error("Webhook registration was not confirmed");
}
console.log("Webhook ID:", registration.webhook_id);
```
The SDK's webhook methods use `event_types` and `webhook_id`, even though most wallet methods use camelCase. Use a random ASCII secret of at least 16 characters. The example generates 32 random bytes and registers them as a 64-character hex string.
The endpoint must use HTTPS, and its hostname must resolve when registering. A newly created tunnel can fail with `Failed to resolve hostname`; wait for DNS, then retry registration with bounded backoff. Do not mark notifications as enabled until registration succeeds.
```typescript theme={null}
const result = await wallet.listSparkWalletWebhooks();
for (const webhook of result?.webhooks ?? []) {
console.log(webhook.webhook_id, webhook.url, webhook.event_types);
}
// To remove a stored registration:
await wallet.deleteSparkWalletWebhook({ webhook_id: webhookId });
```
Up to five webhook URLs can be registered per wallet. Registering the same URL updates its secret and event list; it does not create another registration or append events. Adding a sixth URL evicts the oldest registration, so list existing registrations before adding another.
## Verify the raw request body
Each webhook includes `X-Spark-Signature`: a hex-encoded HMAC-SHA256 of the **raw HTTP body**, keyed with the registered secret. Use SHA-256; implementations for other products with a similarly named header are not interchangeable.
```typescript theme={null}
import { createHmac, timingSafeEqual } from "node:crypto";
export function verifySparkWebhook(
rawBody: Buffer,
signature: string | undefined,
secret: string,
): boolean {
if (!signature || !/^[0-9a-fA-F]{64}$/.test(signature)) return false;
const expected = createHmac("sha256", secret).update(rawBody).digest();
const received = Buffer.from(signature, "hex");
return received.length === expected.length && timingSafeEqual(received, expected);
}
```
Capture the bytes before JSON parsing and pass the header value to this function. Do not verify `JSON.stringify(parsedBody)`: whitespace and serialization changes alter the signature. The generated secret is used as the literal string provided when registering the webhook, not decoded from hex into bytes.
Reject missing or invalid signatures. After verification, validate the payload and durably record or queue the event before acknowledging it. Signature verification does not prevent replay; processing must also be idempotent.
## Match a Lightning receive
Persist the full receive-request ID and `invoice.paymentHash` when creating an invoice. The webhook's receive-request `id` is a bare UUID, while the SDK returns `SparkLightningReceiveRequest:`. A direct string comparison misses the match.
Normalize the request ID for the database lookup while retaining the original SDK ID for API calls:
```typescript theme={null}
function receiveRequestKey(id: string): string {
const prefix = "SparkLightningReceiveRequest:";
return id.startsWith(prefix) ? id.slice(prefix.length) : id;
}
// Store receiveRequestKey(invoice.id) alongside the original invoice.id.
// Apply the same normalization to the verified webhook's receive-request id.
```
This normalization is specific to Lightning receive requests. Do not strip prefixes from every request identifier, and do not mistake a delivery ID or webhook registration ID for a receive-request ID.
The receive notification includes request details such as `id`, `status`, `request_status`, `payment_preimage`, `receiver_identity_public_key`, and amounts. The receive payload does not include the Spark transfer ID. Look up the stored SDK request ID as the requester or receiver and read `request.transfer?.sparkId`. If the transfer is not present yet, retry the lookup later. See [invoice-to-transfer correlation](/wallets/deposit-from-lightning#match-the-invoice-request-and-transfer).
| Receive field | Meaning |
| - | - |
| `status` | Receive-flow status, such as `TRANSFER_COMPLETED` |
| `request_status` | Overall request outcome, such as `SUCCEEDED` |
| `htlc_amount` | Received amount as `{ value, unit }`, or `null` before an amount is recorded; read the unit from the payload |
| `invoice_amount` | Requested amount as `{ value, unit }`; normally `SATOSHI` |
A `SPARK_LIGHTNING_RECEIVE_FINISHED` notification reports a terminal outcome; the name alone is not proof of success. Check `request_status` and the receive-flow `status` before crediting a payment. Reconcile pending requests through lookups as well: do not assume every failure or cancellation will produce a notification. Request completion is separate from the receiver claiming the funds and making them spendable.
Amounts are objects, not bare numbers. For example, `{ "value": 1000000, "unit": "MILLISATOSHI" }` represents 1,000 sats. Do not assume `htlc_amount` always uses the same unit or is always present. SDK amount objects use `originalValue` and `originalUnit` instead.
## Delivery and retries
Webhook delivery is retried for HTTP `408`, `429`, and `5xx` responses, as well as unexpected transport failures. Other client errors, including `401`, are not retried. A registration-time DNS error means no webhook was registered; it is separate from a delivery failure after registration.
Use a retryable response when you cannot durably accept an event. A short observation window without another delivery is not evidence that retries are disabled. Do not depend on a fixed retry interval, count, or ordering between webhooks, operator events, and payment-request lookups.
Handle repeated notifications without applying the same payment twice. Keep pending requests in your database and reconcile them through payment-request lookups after downtime; a missing webhook is not proof that a payment failed.
## Event types
| Event | Request |
| - | - |
| `SPARK_LIGHTNING_RECEIVE_FINISHED` | Lightning receive |
| `SPARK_LIGHTNING_SEND_FINISHED` | Lightning send |
| `SPARK_COOP_EXIT_FINISHED` | Cooperative exit |
| `SPARK_STATIC_DEPOSIT_FINISHED` | Static deposit |
# Withdraw to L1
Source: https://docs.spark.money/wallets/withdraw-to-l1
Send Bitcoin to any on-chain Bitcoin address.
***
## Withdrawal Flow
The complete process for sending Bitcoin to an on-chain Bitcoin address:
Request a fee quote for your withdrawal with different speed options.
```typescript theme={null}
const feeQuote = await wallet.getWithdrawalFeeQuote({
amountSats: 50000,
withdrawalAddress: "bc1p..." // Your Bitcoin address
});
console.log("Fee quote:", feeQuote);
```
Start the withdrawal process using the fee quote.
```typescript theme={null}
// Calculate fee based on exit speed
import { ExitSpeed } from "@buildonspark/spark-sdk";
if (!feeQuote) throw new Error("No fee quote available");
const exitSpeed = ExitSpeed.FAST; // or ExitSpeed.MEDIUM, ExitSpeed.SLOW
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;
}
const withdrawal = await wallet.withdraw({
onchainAddress: "bc1p...", // Your Bitcoin address
exitSpeed,
amountSats: 50000,
feeQuoteId: feeQuote.id,
feeAmountSats: feeAmountSats,
deductFeeFromWithdrawalAmount: false
});
console.log("Withdrawal initiated:", withdrawal);
```
Track the withdrawal status until completion.
```typescript theme={null}
const exitRequest = await wallet.getCoopExitRequest(withdrawal.id);
console.log("Withdrawal status:", exitRequest.status);
console.log("Transaction ID:", exitRequest.coopExitTxid);
```
If you have custom L1 requirements like fee control, transaction chaining, or specific broadcast behavior, [talk to our team](https://www.spark.money/contact). We can expose lower-level controls to fit your infrastructure needs.
***
## Get Withdrawal Fee Quote
Request fee quotes for different withdrawal speeds before initiating a withdrawal.
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 feeQuote = await wallet.getWithdrawalFeeQuote({
amountSats: 100000,
withdrawalAddress: "bc1p5d7rjq7g6rdk2yhzks9smtbqtedr4dekq08ge8ztwac72sfr9rusxg3297"
});
console.log("Fee quote:", feeQuote);
if (!feeQuote) throw new Error("No fee quote available");
console.log("Fast total fee (sats):", feeQuote.userFeeFast.originalValue + feeQuote.l1BroadcastFeeFast.originalValue);
console.log("Medium total fee (sats):", feeQuote.userFeeMedium.originalValue + feeQuote.l1BroadcastFeeMedium.originalValue);
console.log("Slow total fee (sats):", feeQuote.userFeeSlow.originalValue + feeQuote.l1BroadcastFeeSlow.originalValue);
```
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
***
## Initiate Withdrawal
Start the withdrawal process using a fee quote.
withdraw(params)
Initiates a cooperative exit to withdraw Bitcoin from Spark to Layer 1.
```typescript theme={null}
// Calculate fee based on exit speed
import { ExitSpeed } from "@buildonspark/spark-sdk";
if (!feeQuote) throw new Error("No fee quote available");
const exitSpeed = ExitSpeed.FAST; // ExitSpeed.FAST, ExitSpeed.MEDIUM, or ExitSpeed.SLOW
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;
}
const withdrawal = await wallet.withdraw({
onchainAddress: "bc1p5d7rjq7g6rdk2yhzks9smtbqtedr4dekq08ge8ztwac72sfr9rusxg3297",
exitSpeed,
amountSats: 100000,
feeQuoteId: feeQuote.id,
feeAmountSats: feeAmountSats,
deductFeeFromWithdrawalAmount: false
});
if (!withdrawal) throw new Error("Withdrawal failed");
console.log("Withdrawal ID:", withdrawal.id);
console.log("Status:", withdrawal.status);
```
The Bitcoin address to send the funds to
The withdrawal speed: `ExitSpeed.FAST`, `ExitSpeed.MEDIUM`, or `ExitSpeed.SLOW`
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
Whether to deduct the fee from the withdrawal amount (default: true)
The withdrawal request details including ID and status
***
## Monitor Withdrawal Status
Track the status of your withdrawal request.
getCoopExitRequest(id)
Gets a cooperative exit request by ID to check withdrawal status.
```typescript theme={null}
const exitRequest = await wallet.getCoopExitRequest(withdrawalId);
console.log("Withdrawal status:", exitRequest.status);
console.log("Transaction ID:", exitRequest.coopExitTxid);
console.log("Fee:", exitRequest.fee.originalValue, exitRequest.fee.originalUnit);
// Check if withdrawal is complete
if (exitRequest.status === "SUCCEEDED") {
console.log("Withdrawal completed successfully!");
}
```
The ID of the cooperative exit request
The cooperative exit request details or null if not found
***
## Real-time Withdrawal Monitoring
Monitor withdrawal status by polling `getCoopExitRequest()`.
Withdrawals don't emit events. Poll the status using `getCoopExitRequest()` with the withdrawal ID returned from `withdraw()`.
```typescript theme={null}
// Check withdrawal status periodically
const checkWithdrawalStatus = async (withdrawalId) => {
const exitRequest = await wallet.getCoopExitRequest(withdrawalId);
switch (exitRequest.status) {
case "SUCCEEDED":
console.log("Withdrawal completed!");
console.log("On-chain txid:", exitRequest.coopExitTxid);
return true;
case "FAILED":
console.log("Withdrawal failed");
return false;
default:
console.log("Withdrawal pending...");
setTimeout(() => checkWithdrawalStatus(withdrawalId), 30000); // Check again in 30 seconds
return false;
}
};
```
# Withdraw to Lightning
Source: https://docs.spark.money/wallets/withdraw-to-lightning
Pay any Lightning invoice directly from your Spark wallet.
***
## Lightning Withdrawal Flow
The complete process for withdrawing Bitcoin from Spark to the Lightning Network:
Obtain a fresh Lightning invoice from the recipient or Lightning service, and [check its expiry](#check-expiry-before-paying) before paying.
```typescript theme={null}
// A fresh BOLT11 invoice supplied by the recipient
const invoice = encodedInvoice;
console.log("Lightning invoice:", invoice);
```
Get a fee estimate for the Lightning payment to understand costs.
```typescript theme={null}
const feeEstimate = await wallet.getLightningSendFeeEstimate({
encodedInvoice: invoice
});
console.log("Fee estimate:", feeEstimate);
```
Send the Lightning payment with appropriate fee limits.
```typescript theme={null}
const payment = await wallet.payLightningInvoice({
invoice: invoice,
maxFeeSats: 5, // Maximum fee to pay
preferSpark: false // Keep this example on Lightning for payment-status polling
});
console.log("Payment initiated:", payment);
```
Track the payment status until completion.
```typescript theme={null}
const paymentStatus = await wallet.getLightningSendRequest(payment.id);
console.log("Payment status:", paymentStatus?.status ?? "Request unavailable");
```
***
## Check expiry before paying
Decode the BOLT11 invoice and reject it if its timestamp plus expiry is at or before the current time. Use the BOLT11 default expiry of 3,600 seconds when the `x` tag is absent. Check again immediately before calling `payLightningInvoice()` if the invoice has been sitting on a confirmation screen; request a fresh invoice when it expires.
SDK 0.12.1 does not check BOLT11 expiry locally before starting the Lightning swap. The swap can lock funds before the payment request is rejected because the invoice has expired. A thrown error does not necessarily mean the funds are available to retry. The swap's 16-day expiry is not a guaranteed refund time.
For retryable integrations, generate and persist a `transferId` before the first attempt; see [`payLightningInvoice()`](/api-reference/wallet/pay-lightning-invoice). If a send fails after starting, retain the payment hash and any request or transfer IDs. Check the Lightning send request and the wallet's available balance before retrying. Funds locked in an outgoing transfer can remain in `owned` while being excluded from `available`. Do not create a second payment just because the first call threw, or rely on a low-level `CancelTransferRequest`: the SDK has no public client cancellation method for this flow.
## Pay Lightning Invoice
Send Bitcoin from your Spark wallet to any Lightning Network invoice.
payLightningInvoice(params)
Pays a Lightning invoice using your Spark wallet balance.
```typescript theme={null}
const payment = await wallet.payLightningInvoice({
invoice: encodedInvoice,
maxFeeSats: 5,
preferSpark: true,
});
console.log("Payment Response:", payment);
```
The BOLT11-encoded Lightning invoice to pay
Maximum fee in satoshis to pay for the invoice
When true, Spark wallets will initiate a Spark transfer instead of a Lightning transfer if a valid Spark address is found in the invoice
Amount in satoshis to send (only used for zero-amount invoices)
A `LightningSendRequest` for a Lightning payment, or a `WalletTransfer` if `preferSpark` routes the payment over Spark. Poll the Lightning send request only for the Lightning result.
***
## Get Fee Estimate
Estimate the fees for a Lightning payment before sending.
getLightningSendFeeEstimate(params)
Gets an estimated fee for sending a Lightning payment.
```typescript theme={null}
const feeEstimate = await wallet.getLightningSendFeeEstimate({
encodedInvoice: encodedInvoice
});
console.log("Estimated fee:", feeEstimate);
```
The BOLT11-encoded Lightning invoice
The estimated fee for the Lightning payment
***
## Monitor Payment Status
Track the status of your Lightning payment.
getLightningSendRequest(id)
Gets a Lightning send request by ID to check payment status.
```typescript theme={null}
const paymentStatus = await wallet.getLightningSendRequest(paymentId);
if (!paymentStatus) {
throw new Error("Lightning send request not found or not accessible");
}
console.log("Payment status:", paymentStatus.status);
console.log("Payment fee:", paymentStatus.fee);
// Check if payment is complete
if (paymentStatus.status === "TRANSFER_COMPLETED") {
console.log("Payment completed successfully!");
}
```
The ID of the Lightning send request
The Lightning send request details including status and amount
***
## Zero-Amount Invoices
Spark supports paying zero-amount Lightning invoices, which allow you 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.
### Paying Zero-Amount Invoices
When paying a zero-amount invoice, you need to specify the amount using the `amountSatsToSend` parameter:
```typescript theme={null}
// Pay a zero-amount invoice with a specific amount
const payment = await wallet.payLightningInvoice({
invoice: "lnbc...", // Zero-amount Lightning invoice
maxFeeSats: 5,
amountSatsToSend: 1000, // Specify the amount to send (in satoshis)
});
console.log("Zero-amount Payment Response:", payment);
```
Use `amountSatsToSend` only for zero-amount invoices. The SDK rejects it when the invoice already specifies an amount.
***
## Spark Transfer Preference
When paying Lightning invoices, you can enable Spark transfer preference to automatically use Spark transfers when possible.
```typescript theme={null}
// Pay with Spark preference enabled
const payment = await wallet.payLightningInvoice({
invoice: "lnbc...", // Lightning invoice (potentially with embedded Spark address)
maxFeeSats: 5,
preferSpark: true, // Defaults to false
});
console.log("Payment Response:", payment);
```
When `preferSpark` is set to `true`, Spark wallets will:
* Initiate a Spark transfer instead of a Lightning transfer if a valid Spark address is found in the invoice
* Fall back to regular Lightning payment if no Spark address is found
***
## Real-time Payment Monitoring
Monitor Lightning payment status by polling.
The `transfer:claimed` event does **not** fire for outgoing Lightning payments. Use `getLightningSendRequest()` to poll payment status.
```typescript theme={null}
// Query the current state; schedule further checks in your application.
const checkPaymentStatus = async (paymentId: string) => {
const request = await wallet.getLightningSendRequest(paymentId);
if (!request) {
console.log("Request unavailable: check the ID and querying wallet");
return;
}
console.log("Lightning send status:", request.status);
if (request.status === "TRANSFER_COMPLETED") {
console.log("Lightning send completed");
}
// An unsuccessful request does not establish that locked funds are available.
};
// For INCOMING transfers/deposits, use events:
wallet.on("transfer:claimed", (transferId, updatedBalance) => {
console.log(`Incoming transfer ${transferId} claimed. New balance: ${updatedBalance} sats`);
});
```
***
## Fee Recommendations
We recommend setting the maximum routing fee to whichever is greater:
* **5 sats** (minimum fee)
* **17 bps × transaction amount** (0.17% of the transaction)
```typescript theme={null}
// Calculate recommended fee
const amountSats = 10000; // Your payment amount
const recommendedFee = Math.max(5, Math.ceil(amountSats * 0.0017)); // 17 bps = 0.17%
const payment = await wallet.payLightningInvoice({
invoice: "lnbc...",
maxFeeSats: recommendedFee
});
```