Customizing the SDK
Using the SDK Builder gives you more control over the initialization and modular components used when the SDK is running. Below you can find examples of initializing the SDK using the SDK Builder and implementing modular components.
The shared-pool, shared-chain-service, and shared-connection-manager components on this page are designed for multi-tenant server deployments — they're most useful in combination with the Server mode SDK profile.
- Storage to manage stored data
- PostgreSQL Backend as an alternative storage backend
- MySQL Backend as an alternative storage backend
- Bitcoin Chain Service to provide network data
- Shared REST Chain Service to share the chain service HTTP client across SDK instances
- LNURL Client to make REST requests
- Fiat Service to provide Fiat currencies and exchange rates
- Change the Account Number to derive an independent wallet from the same seed
- Payment Observer to be notified before payments occur
- Shared SDK Context to share connection pools and HTTP/gRPC clients across SDK instances
// Construct the seed using a mnemonic, entropy or passkey
let mnemonic = "<mnemonic words>".to_string();
let seed = Seed::Mnemonic {
mnemonic,
passphrase: None,
};
// Create the default config
let mut config = default_config(Network::Mainnet);
config.api_key = Some("<breez api key>".to_string());
// Build the SDK using the config, seed and default storage
let builder = SdkBuilder::new(config, seed).with_default_storage("./.data".to_string());
// You can also pass your custom implementations:
// let builder = builder.with_storage_backend(custom_storage(<your storage implementation>))
// let builder = builder.with_chain_service(<your chain service implementation>)
// let builder = builder.with_rest_client(<your rest client implementation>)
// let builder = builder.with_account_number(<account number>)
// let builder = builder.with_payment_observer(<your payment observer implementation>);
let sdk = builder.build().await?;
// Construct the seed using a mnemonic, entropy or passkey
let mnemonic = "<mnemonic words>"
let seed = Seed.mnemonic(mnemonic: mnemonic, passphrase: nil)
// Create the default config
var config = defaultConfig(network: Network.mainnet)
config.apiKey = "<breez api key>"
// Build the SDK using the config, seed and default storage
let builder = SdkBuilder(config: config, seed: seed)
await builder.withDefaultStorage(storageDir: "./.data")
// You can also pass your custom implementations:
// await builder.withStorage(<your storage implementation>)
// await builder.withChainService(<your chain service implementation>)
// await builder.withRestClient(<your rest client implementation>)
// await builder.withAccountNumber(accountNumber: <account number>)
// await builder.withPaymentObserver(<your payment observer implementation>)
let sdk = try await builder.build()
// Construct the seed using a mnemonic, entropy or passkey
val mnemonic = "<mnemonic words>"
val seed = Seed.Mnemonic(mnemonic, null)
// Create the default config
val config = defaultConfig(Network.MAINNET)
config.apiKey = "<breez api key>"
try {
// Build the SDK using the config, seed and default storage
val builder = SdkBuilder(config, seed)
builder.withDefaultStorage("./.data")
// You can also pass your custom implementations:
// builder.withStorage(<your storage implementation>)
// builder.withChainService(<your chain service implementation>)
// builder.withRestClient(<your rest client implementation>)
// builder.withAccountNumber(<account number>)
// builder.withPaymentObserver(<your payment observer implementation>)
val sdk = builder.build()
} catch (e: Exception) {
// handle error
}
// Construct the seed using a mnemonic, entropy or passkey
var mnemonic = "<mnemonic words>";
var seed = new Seed.Mnemonic(mnemonic: mnemonic, passphrase: null);
// Create the default config
var config = BreezSdkSparkMethods.DefaultConfig(Network.Mainnet) with
{
apiKey = "<breez api key>"
};
// Build the SDK using the config, seed and default storage
var builder = new SdkBuilder(config: config, seed: seed);
await builder.WithDefaultStorage(storageDir: "./.data");
// You can also pass your custom implementations:
// await builder.WithStorage(<your storage implementation>)
// await builder.WithChainService(<your chain service implementation>)
// await builder.WithRestClient(<your rest client implementation>)
// await builder.WithAccountNumber(<account number>)
// await builder.WithPaymentObserver(<your payment observer implementation>);
var sdk = await builder.Build();
// Call init when using the SDK in a web environment before calling any other SDK
// methods. This is not needed when using the SDK in a Node.js/Deno environment.
await init()
// Construct the seed using a mnemonic, entropy or passkey
const mnemonic = '<mnemonic words>'
const seed: Seed = { type: 'mnemonic', mnemonic, passphrase: undefined }
// Create the default config
const config = defaultConfig('mainnet')
config.apiKey = '<breez api key>'
// Build the SDK using the config, seed and default storage
let builder = SdkBuilder.new(config, seed)
builder = await builder.withDefaultStorage('./.data')
// You can also pass your custom implementations:
// builder = builder.withStorage(<your storage implementation>)
// builder = builder.withChainService(<your chain service implementation>)
// builder = builder.withRestClient(<your rest client implementation>)
// builder = builder.withAccountNumber(<account number>)
// builder = builder.withPaymentObserver(<your payment observer implementation>)
const sdk = await builder.build()
// Construct the seed using a mnemonic, entropy or passkey
const mnemonic = '<mnemonics words>'
const seed = new Seed.Mnemonic({ mnemonic, passphrase: undefined })
// Create the default config
const config = defaultConfig(Network.Mainnet)
config.apiKey = '<breez api key>'
// Build the SDK using the config, seed and default storage
const builder = new SdkBuilder(config, seed)
await builder.withDefaultStorage(`${RNFS.DocumentDirectoryPath}/data`)
// You can also pass your custom implementations:
// await builder.withStorage(<your storage implementation>)
// await builder.withChainService(<your chain service implementation>)
// await builder.withRestClient(<your rest client implementation>)
// await builder.withAccountNumber(<account number>)
// await builder.withPaymentObserver(<your payment observer implementation>)
const sdk = await builder.build()
// Construct the seed using a mnemonic, entropy or passkey
String mnemonic = "<mnemonic words>";
final seed = Seed.mnemonic(mnemonic: mnemonic, passphrase: null);
// Create the default config
final config = defaultConfig(network: Network.mainnet)
.copyWith(apiKey: "<breez api key>");
// Build the SDK using the config, seed and default storage
final builder = SdkBuilder(config: config, seed: seed);
builder.withDefaultStorage(storageDir: "./.data");
// You can also pass your custom implementations:
// builder.withRestChainService(
// url: "https://custom.chain.service",
// credentials: Credentials(
// username: "service-username", password: "service-password"));
// builder.withAccountNumber(accountNumber: <account number>);
final sdk = await builder.build();
# Construct the seed using a mnemonic, entropy or passkey
mnemonic = "<mnemonic words>"
seed = Seed.MNEMONIC(mnemonic=mnemonic, passphrase=None)
# Create the default config
config = default_config(network=Network.MAINNET)
config.api_key = "<breez api key>"
try:
# Build the SDK using the config, seed and default storage
builder = SdkBuilder(config=config, seed=seed)
await builder.with_default_storage(storage_dir="./.data")
# You can also pass your custom implementations:
# await builder.with_storage(<your storage implementation>)
# await builder.with_chain_service(<your chain service implementation>)
# await builder.with_rest_client(<your rest client implementation>)
# await builder.with_account_number(<account number>)
# await builder.with_payment_observer(<your payment observer implementation>)
sdk = await builder.build()
return sdk
except Exception as error:
logging.error(error)
raise
// Construct the seed using a mnemonic, entropy or passkey
mnemonic := "<mnemonic words>"
var seed breez_sdk_spark.Seed = breez_sdk_spark.SeedMnemonic{
Mnemonic: mnemonic,
Passphrase: nil,
}
// Create the default config
apiKey := "<breez api key>"
config := breez_sdk_spark.DefaultConfig(breez_sdk_spark.NetworkMainnet)
config.ApiKey = &apiKey
// Build the SDK using the config, seed and default storage
builder := breez_sdk_spark.NewSdkBuilder(config, seed)
builder.WithDefaultStorage("./.data")
// You can also pass your custom implementations:
// builder.WithStorage(<your storage implementation>)
// builder.WithChainService(<your chain service implementation>)
// builder.WithRestClient(<your rest client implementation>)
// builder.WithAccountNumber(<account number>)
// builder.WithPaymentObserver(<your payment observer implementation>)
sdk, err := builder.Build()
return sdk, err
With Storage API docs
When using the SDK Builder, you either have to provide a Storage implementation or use the default storage from the SDK.
Note: Flutter currently only supports using the default storage.
With PostgreSQL Backend API docs
The SDK includes a PostgreSQL backend as an alternative to file-based storage. Build a storage config with postgres_storagepostgres_storagepostgresStoragepostgresStoragepostgresStoragepostgresStoragepostgresStoragePostgresStoragePostgresStorage and pass it to the builder via with_storage_backendwith_storage_backendwithStorageBackendwithStorageBackendwithStorageBackendwithStorageBackendwithStorageBackendWithStorageBackendWithStorageBackend — this configures PostgreSQL for all stores (storage, tree store, and token store), which is suitable for server-side deployments with horizontal scaling. To share a single connection pool across multiple SDK instances, supply the same config through a Shared SDK Context; per-tenant scoping (rows isolated by seed identity) is preserved either way.
If your service owns the database schema and applies SDK-compatible migrations externally, set run_migrationrun_migrationrunMigrationrunMigrationrunMigrationrunMigrationrunMigrationRunMigrationRunMigration to false on the storage config. The SDK will trust the existing schema and skip all migration runs, including writes to schema migration tables.
Note: Not available for React Native or Flutter. For JavaScript/TypeScript, only supported in Node.js (not in the browser).
// Construct the seed using a mnemonic, entropy or passkey
let mnemonic = "<mnemonic words>".to_string();
let seed = Seed::Mnemonic {
mnemonic,
passphrase: None,
};
// Create the default config
let mut config = default_config(Network::Mainnet);
config.api_key = Some("<breez api key>".to_string());
// Configure PostgreSQL backend
// Connection string format: "host=localhost user=postgres password=secret dbname=spark"
// Or URI format: "postgres://user:password@host:port/dbname?sslmode=require"
let mut postgres_config =
default_postgres_storage_config("host=localhost user=postgres dbname=spark".to_string());
// Optionally pool settings can be adjusted. Some examples:
postgres_config.max_pool_size = 8; // Max connections in pool
postgres_config.wait_timeout_secs = Some(30); // Timeout waiting for connection
// If your service owns SDK-compatible schema migrations:
postgres_config.run_migration = false;
// Build the SDK with the PostgreSQL storage backend (storage, tree store,
// and token store). Per-tenant scoping (rows isolated by seed identity) is
// applied automatically.
let sdk = SdkBuilder::new(config, seed)
.with_storage_backend(postgres_storage(postgres_config)?)
.build()
.await?;
// Construct the seed using a mnemonic, entropy or passkey
let mnemonic = "<mnemonic words>"
let seed = Seed.mnemonic(mnemonic: mnemonic, passphrase: nil)
// Create the default config
var config = defaultConfig(network: Network.mainnet)
config.apiKey = "<breez api key>"
// Configure PostgreSQL backend
// Connection string format: "host=localhost user=postgres password=secret dbname=spark"
// Or URI format: "postgres://user:password@host:port/dbname?sslmode=require"
var postgresConfig = defaultPostgresStorageConfig(
connectionString: "host=localhost user=postgres dbname=spark"
)
// Optionally pool settings can be adjusted. Some examples:
postgresConfig.maxPoolSize = UInt32(8) // Max connections in pool
postgresConfig.waitTimeoutSecs = UInt64(30) // Timeout waiting for connection
// If your service owns SDK-compatible schema migrations:
postgresConfig.runMigration = false
// Build the SDK with the PostgreSQL storage backend (storage, tree store,
// and token store). Per-tenant scoping (rows isolated by seed identity)
// is applied automatically.
let builder = SdkBuilder(config: config, seed: seed)
await builder.withStorageBackend(
storage: try postgresStorage(config: postgresConfig))
let sdk = try await builder.build()
// Construct the seed using a mnemonic, entropy or passkey
val mnemonic = "<mnemonic words>"
val seed = Seed.Mnemonic(mnemonic, null)
// Create the default config
val config = defaultConfig(Network.MAINNET)
config.apiKey = "<breez api key>"
// Configure PostgreSQL backend
// Connection string format: "host=localhost user=postgres password=secret dbname=spark"
// Or URI format: "postgres://user:password@host:port/dbname?sslmode=require"
val postgresConfig = defaultPostgresStorageConfig("host=localhost user=postgres dbname=spark")
// Optionally pool settings can be adjusted. Some examples:
postgresConfig.maxPoolSize = 8u // Max connections in pool
postgresConfig.waitTimeoutSecs = 30u // Timeout waiting for connection
// If your service owns SDK-compatible schema migrations:
postgresConfig.runMigration = false
try {
// Build the SDK with the PostgreSQL storage backend (storage, tree
// store, and token store). Per-tenant scoping (rows isolated by
// seed identity) is applied automatically.
val builder = SdkBuilder(config, seed)
builder.withStorageBackend(postgresStorage(postgresConfig))
val sdk = builder.build()
} catch (e: Exception) {
// handle error
}
// Construct the seed using a mnemonic, entropy or passkey
var mnemonic = "<mnemonic words>";
var seed = new Seed.Mnemonic(mnemonic: mnemonic, passphrase: null);
// Create the default config
var config = BreezSdkSparkMethods.DefaultConfig(Network.Mainnet) with
{
apiKey = "<breez api key>"
};
// Configure PostgreSQL backend
// Connection string format: "host=localhost user=postgres password=secret dbname=spark"
// Or URI format: "postgres://user:password@host:port/dbname?sslmode=require"
var postgresConfig = BreezSdkSparkMethods.DefaultPostgresStorageConfig(
connectionString: "host=localhost user=postgres dbname=spark"
);
// Optionally pool settings can be adjusted. Some examples:
postgresConfig = postgresConfig with
{
maxPoolSize = 8u, // Max connections in pool
waitTimeoutSecs = 30ul, // Timeout waiting for connection
// If your service owns SDK-compatible schema migrations:
runMigration = false
};
// Build the SDK with the PostgreSQL storage backend (storage, tree
// store, and token store). Per-tenant scoping (rows isolated by
// seed identity) is applied automatically.
var builder = new SdkBuilder(config: config, seed: seed);
await builder.WithStorageBackend(
storage: BreezSdkSparkMethods.PostgresStorage(postgresConfig)
);
var sdk = await builder.Build();
// Construct the seed using a mnemonic, entropy or passkey
const mnemonic = '<mnemonic words>'
const seed: Seed = { type: 'mnemonic', mnemonic, passphrase: undefined }
// Create the default config
const config = defaultConfig('mainnet')
config.apiKey = '<breez api key>'
// Configure PostgreSQL backend
// Connection string format: "host=localhost user=postgres password=secret dbname=spark"
// Or URI format: "postgres://user:password@host:port/dbname?sslmode=require"
const pgConfig = defaultPostgresStorageConfig('host=localhost user=postgres dbname=spark')
// Optionally pool settings can be adjusted. Some examples:
pgConfig.maxPoolSize = 8 // Max connections in pool
pgConfig.createTimeoutSecs = 30 // Timeout for establishing a new connection
pgConfig.recycleTimeoutSecs = 30 // Timeout for recycling an idle connection
// If your service owns SDK-compatible schema migrations:
pgConfig.runMigration = false
// Build the SDK with the PostgreSQL storage backend (storage, tree store,
// and token store). Per-tenant scoping (rows isolated by seed identity) is
// applied automatically.
let builder = SdkBuilder.new(config, seed)
builder = builder.withStorageBackend(postgresStorage(pgConfig))
const sdk = await builder.build()
async def init_sdk_postgres():
# Construct the seed using a mnemonic, entropy or passkey
mnemonic = "<mnemonic words>"
seed = Seed.MNEMONIC(mnemonic=mnemonic, passphrase=None)
# Create the default config
config = default_config(network=Network.MAINNET)
config.api_key = "<breez api key>"
# Configure PostgreSQL storage
# Connection string format: "host=localhost user=postgres password=secret dbname=spark"
# Or URI format: "postgres://user:password@host:port/dbname?sslmode=require"
postgres_config = default_postgres_storage_config(
connection_string="host=localhost user=postgres dbname=spark"
)
# Optionally pool settings can be adjusted. Some examples:
postgres_config.max_pool_size = 8 # Max connections in pool
postgres_config.wait_timeout_secs = 30 # Timeout waiting for connection
# If your service owns SDK-compatible schema migrations:
postgres_config.run_migration = False
try:
# Build the SDK with the PostgreSQL storage backend (storage, tree
# store, and token store). Per-tenant scoping (rows isolated by seed
# identity) is applied automatically.
builder = SdkBuilder(config=config, seed=seed)
await builder.with_storage_backend(
storage=postgres_storage(config=postgres_config)
)
sdk = await builder.build()
return sdk
except Exception as error:
logging.error(error)
raise
// Construct the seed using a mnemonic, entropy or passkey
mnemonic := "<mnemonic words>"
var seed breez_sdk_spark.Seed = breez_sdk_spark.SeedMnemonic{
Mnemonic: mnemonic,
Passphrase: nil,
}
// Create the default config
apiKey := "<breez api key>"
config := breez_sdk_spark.DefaultConfig(breez_sdk_spark.NetworkMainnet)
config.ApiKey = &apiKey
// Configure PostgreSQL backend
// Connection string format: "host=localhost user=postgres password=secret dbname=spark"
// Or URI format: "postgres://user:password@host:port/dbname?sslmode=require"
postgresConfig := breez_sdk_spark.DefaultPostgresStorageConfig(
"host=localhost user=postgres dbname=spark",
)
// Optionally pool settings can be adjusted. Some examples:
postgresConfig.MaxPoolSize = 8 // Max connections in pool
waitTimeoutSecs := uint64(30)
postgresConfig.WaitTimeoutSecs = &waitTimeoutSecs // Timeout waiting for connection
// If your service owns SDK-compatible schema migrations:
postgresConfig.RunMigration = false
// Build the SDK with the PostgreSQL storage backend (storage, tree store,
// and token store). Per-tenant scoping (rows isolated by seed identity)
// is applied automatically.
builder := breez_sdk_spark.NewSdkBuilder(config, seed)
storageBackend, err := breez_sdk_spark.PostgresStorage(postgresConfig)
if err != nil {
return nil, err
}
builder.WithStorageBackend(storageBackend)
sdk, err := builder.Build()
if err != nil {
return nil, err
}
Developer note
Sharing the same PostgreSQL database with multiple SDK instances is incompatible with real-time sync. See Real-time sync server URL for how to disable it.
The PostgreSQL tree store can use the same or a separate PostgreSQL database as the PostgreSQL storage. The tree store uses its own set of tables prefixed with tree_.
With MySQL Backend API docs
The SDK includes a MySQL backend (MySQL 8.0+) as an alternative to file-based storage. Build a storage config with mysql_storagemysql_storagemysqlStoragemysqlStoragemysqlStoragemysqlStoragemysqlStorageMysqlStorageMysqlStorage and pass it to the builder via with_storage_backendwith_storage_backendwithStorageBackendwithStorageBackendwithStorageBackendwithStorageBackendwithStorageBackendWithStorageBackendWithStorageBackend — this configures MySQL for all stores (storage, tree store, and token store), which is suitable for server-side deployments with horizontal scaling. To share a single connection pool across multiple SDK instances, supply the same config through a Shared SDK Context; per-tenant scoping (rows isolated by seed identity) is preserved either way.
If your service owns the database schema and applies SDK-compatible migrations externally, set run_migrationrun_migrationrunMigrationrunMigrationrunMigrationrunMigrationrunMigrationRunMigrationRunMigration to false on the storage config. The SDK will trust the existing schema and skip all migration runs, including writes to schema migration tables.
Note: Not available for React Native or Flutter. For JavaScript/TypeScript, only supported in Node.js (not in the browser).
// Construct the seed using a mnemonic, entropy or passkey
let mnemonic = "<mnemonic words>".to_string();
let seed = Seed::Mnemonic {
mnemonic,
passphrase: None,
};
// Create the default config
let mut config = default_config(Network::Mainnet);
config.api_key = Some("<breez api key>".to_string());
// Configure MySQL backend (MySQL 8.0+).
// Connection string format (URL only):
// "mysql://user:password@host:3306/dbname?ssl-mode=required"
let mut mysql_config =
default_mysql_storage_config("mysql://user:password@localhost:3306/spark".to_string());
// Optionally pool settings can be adjusted. Some examples:
mysql_config.max_pool_size = 8; // Max connections in pool
mysql_config.recycle_timeout_secs = Some(60); // Recycle idle connections after this many seconds
// Provide a custom CA certificate when using ssl-mode=verify_ca or verify_identity:
// mysql_config.root_ca_pem = Some("-----BEGIN CERTIFICATE-----\n...".to_string());
// Build the SDK with the MySQL storage backend (storage, tree store, and
// token store). Per-tenant scoping (rows isolated by seed identity) is
// applied automatically.
let sdk = SdkBuilder::new(config, seed)
.with_storage_backend(mysql_storage(mysql_config)?)
.build()
.await?;
// Construct the seed using a mnemonic, entropy or passkey
let mnemonic = "<mnemonic words>"
let seed = Seed.mnemonic(mnemonic: mnemonic, passphrase: nil)
// Create the default config
var config = defaultConfig(network: Network.mainnet)
config.apiKey = "<breez api key>"
// Configure MySQL backend (MySQL 8.0+).
// Connection string format (URL only):
// "mysql://user:password@host:3306/dbname?ssl-mode=required"
var mysqlConfig = defaultMysqlStorageConfig(
connectionString: "mysql://user:password@localhost:3306/spark"
)
// Optionally pool settings can be adjusted. Some examples:
mysqlConfig.maxPoolSize = UInt32(8) // Max connections in pool
mysqlConfig.recycleTimeoutSecs = UInt64(60) // Recycle idle connections after this many seconds
// Provide a custom CA certificate when using ssl-mode=verify_ca or verify_identity:
// mysqlConfig.rootCaPem = "-----BEGIN CERTIFICATE-----\n..."
// Build the SDK with the MySQL storage backend (storage, tree store, and
// token store). Per-tenant scoping (rows isolated by seed identity) is
// applied automatically.
let builder = SdkBuilder(config: config, seed: seed)
await builder.withStorageBackend(
storage: try mysqlStorage(config: mysqlConfig))
let sdk = try await builder.build()
// Construct the seed using a mnemonic, entropy or passkey
val mnemonic = "<mnemonic words>"
val seed = Seed.Mnemonic(mnemonic, null)
// Create the default config
val config = defaultConfig(Network.MAINNET)
config.apiKey = "<breez api key>"
// Configure MySQL backend (MySQL 8.0+).
// Connection string format (URL only):
// "mysql://user:password@host:3306/dbname?ssl-mode=required"
val mysqlConfig = defaultMysqlStorageConfig("mysql://user:password@localhost:3306/spark")
// Optionally pool settings can be adjusted. Some examples:
mysqlConfig.maxPoolSize = 8u // Max connections in pool
mysqlConfig.recycleTimeoutSecs = 60u // Recycle idle connections after this many seconds
try {
// Build the SDK with the MySQL storage backend (storage, tree
// store, and token store). Per-tenant scoping (rows isolated by
// seed identity) is applied automatically.
val builder = SdkBuilder(config, seed)
builder.withStorageBackend(mysqlStorage(mysqlConfig))
val sdk = builder.build()
} catch (e: Exception) {
// handle error
}
// Construct the seed using a mnemonic, entropy or passkey
var mnemonic = "<mnemonic words>";
var seed = new Seed.Mnemonic(mnemonic: mnemonic, passphrase: null);
// Create the default config
var config = BreezSdkSparkMethods.DefaultConfig(Network.Mainnet) with
{
apiKey = "<breez api key>"
};
// Configure MySQL backend (MySQL 8.0+).
// Connection string format (URL only):
// "mysql://user:password@host:3306/dbname?ssl-mode=required"
var mysqlConfig = BreezSdkSparkMethods.DefaultMysqlStorageConfig(
connectionString: "mysql://user:password@localhost:3306/spark"
);
// Optionally pool settings can be adjusted. Some examples:
mysqlConfig = mysqlConfig with
{
maxPoolSize = 8u, // Max connections in pool
recycleTimeoutSecs = 60ul // Recycle idle connections after this many seconds
};
// Build the SDK with the MySQL storage backend (storage, tree
// store, and token store). Per-tenant scoping (rows isolated by
// seed identity) is applied automatically.
var builder = new SdkBuilder(config: config, seed: seed);
await builder.WithStorageBackend(
storage: BreezSdkSparkMethods.MysqlStorage(mysqlConfig)
);
var sdk = await builder.Build();
// Construct the seed using a mnemonic, entropy or passkey
const mnemonic = '<mnemonic words>'
const seed: Seed = { type: 'mnemonic', mnemonic, passphrase: undefined }
// Create the default config
const config = defaultConfig('mainnet')
config.apiKey = '<breez api key>'
// Configure MySQL backend (MySQL 8.0+).
// Connection string format (URL only):
// "mysql://user:password@host:3306/dbname?ssl-mode=required"
const mysqlConfig = defaultMysqlStorageConfig('mysql://user:password@localhost:3306/spark')
// Optionally pool settings can be adjusted. Some examples:
mysqlConfig.maxPoolSize = 8 // Max connections in pool
mysqlConfig.createTimeoutSecs = 30 // Timeout for establishing a new connection
mysqlConfig.recycleTimeoutSecs = 60 // Recycle idle connections after this many seconds
// Build the SDK with the MySQL storage backend (storage, tree store, and
// token store). Per-tenant scoping (rows isolated by seed identity) is
// applied automatically.
let builder = SdkBuilder.new(config, seed)
builder = builder.withStorageBackend(mysqlStorage(mysqlConfig))
const sdk = await builder.build()
async def init_sdk_mysql():
# Construct the seed using a mnemonic, entropy or passkey
mnemonic = "<mnemonic words>"
seed = Seed.MNEMONIC(mnemonic=mnemonic, passphrase=None)
# Create the default config
config = default_config(network=Network.MAINNET)
config.api_key = "<breez api key>"
# Configure MySQL backend (MySQL 8.0+).
# Connection string format (URL only):
# "mysql://user:password@host:3306/dbname?ssl-mode=required"
mysql_config = default_mysql_storage_config(
connection_string="mysql://user:password@localhost:3306/spark"
)
# Optionally pool settings can be adjusted. Some examples:
mysql_config.max_pool_size = 8 # Max connections in pool
mysql_config.recycle_timeout_secs = 60 # Recycle idle connections after this many seconds
# Provide a custom CA certificate when using ssl-mode=verify_ca or verify_identity:
# mysql_config.root_ca_pem = "-----BEGIN CERTIFICATE-----\n..."
try:
# Build the SDK with the MySQL storage backend (storage, tree store,
# and token store). Per-tenant scoping (rows isolated by seed identity)
# is applied automatically.
builder = SdkBuilder(config=config, seed=seed)
await builder.with_storage_backend(
storage=mysql_storage(config=mysql_config)
)
sdk = await builder.build()
return sdk
except Exception as error:
logging.error(error)
raise
// Construct the seed using a mnemonic, entropy or passkey
mnemonic := "<mnemonic words>"
var seed breez_sdk_spark.Seed = breez_sdk_spark.SeedMnemonic{
Mnemonic: mnemonic,
Passphrase: nil,
}
// Create the default config
apiKey := "<breez api key>"
config := breez_sdk_spark.DefaultConfig(breez_sdk_spark.NetworkMainnet)
config.ApiKey = &apiKey
// Configure MySQL backend (MySQL 8.0+).
// Connection string format (URL only):
// "mysql://user:password@host:3306/dbname?ssl-mode=required"
mysqlConfig := breez_sdk_spark.DefaultMysqlStorageConfig(
"mysql://user:password@localhost:3306/spark",
)
// Optionally pool settings can be adjusted. Some examples:
mysqlConfig.MaxPoolSize = 8 // Max connections in pool
recycleTimeoutSecs := uint64(60)
// Recycle idle connections after this many seconds
mysqlConfig.RecycleTimeoutSecs = &recycleTimeoutSecs
// Provide a custom CA certificate when using ssl-mode=verify_ca or verify_identity:
// rootCa := "-----BEGIN CERTIFICATE-----\n..."
// mysqlConfig.RootCaPem = &rootCa
// Build the SDK with the MySQL storage backend (storage, tree store, and
// token store). Per-tenant scoping (rows isolated by seed identity) is
// applied automatically.
builder := breez_sdk_spark.NewSdkBuilder(config, seed)
storageBackend, err := breez_sdk_spark.MysqlStorage(mysqlConfig)
if err != nil {
return nil, err
}
builder.WithStorageBackend(storageBackend)
sdk, err := builder.Build()
if err != nil {
return nil, err
}
Developer note
MySQL only accepts URL-form connection strings (mysql://user:password@host:3306/dbname); the key=value form supported by PostgreSQL is not available. TLS is enabled by appending ?ssl-mode=required (or verify_ca / verify_identity); when using verify_ca or verify_identity you can supply a custom root_ca_pem.
Sharing the same MySQL database with multiple SDK instances is incompatible with real-time sync. See Real-time sync server URL for how to disable it.
The MySQL tree store can use the same or a separate MySQL database as the MySQL storage. The tree store uses its own set of tables prefixed with tree_.
With Chain Service API docs
The SDK provides a default Bitcoin Chain Service implementation. If you want to use your own, you can provide it either by using With REST Chain Service or by implementing the Bitcoin Chain Service interface.
With REST Chain Service API docs
The SDK provides a default Bitcoin Chain Service implementation. If you want to use your own, you can provide it either by using With Chain Service or by providing a URL and optional credentials.
let url = "<your REST chain service URL>".to_string();
let chain_api_type = ChainApiType::MempoolSpace;
let optional_credentials = Credentials {
username: "<username>".to_string(),
password: "<password>".to_string(),
};
builder.with_rest_chain_service(url, chain_api_type, Some(optional_credentials))
let url = "<your REST chain service URL>"
let chainApiType = ChainApiType.mempoolSpace
let optionalCredentials = Credentials(
username: "<username>",
password: "<password>"
)
await builder.withRestChainService(
url: url,
apiType: chainApiType,
credentials: optionalCredentials
)
val url = "<your REST chain service URL>"
val chainApiType = ChainApiType.MEMPOOL_SPACE
val optionalCredentials = Credentials(
username = "<username>",
password = "<password>"
)
builder.withRestChainService(
url = url,
apiType = chainApiType,
credentials = optionalCredentials
)
var url = "<your REST chain service URL>";
var chainApiType = ChainApiType.MempoolSpace;
var optionalCredentials = new Credentials(
username: "<username>",
password: "<password>"
);
await builder.WithRestChainService(
url: url,
apiType: chainApiType,
credentials: optionalCredentials
);
const url = '<your REST chain service URL>'
const chainApiType = 'mempoolSpace'
const optionalCredentials: Credentials = {
username: '<username>',
password: '<password>'
}
builder = builder.withRestChainService(url, chainApiType, optionalCredentials)
const url = '<your REST chain service URL>'
const chainApiType = ChainApiType.MempoolSpace
const optionalCredentials: Credentials = {
username: '<username>',
password: '<password>'
}
await builder.withRestChainService(url, chainApiType, optionalCredentials)
String url = "<your REST chain service URL>";
var chainApiType = ChainApiType.mempoolSpace;
var optionalCredentials = Credentials(
username: "<username>",
password: "<password>",
);
builder.withRestChainService(
url: url,
apiType: chainApiType,
credentials: optionalCredentials,
);
url = "<your REST chain service URL>"
chain_api_type = ChainApiType.MEMPOOL_SPACE
optional_credentials = Credentials(
username="<username>",
password="<password>",
)
await builder.with_rest_chain_service(
url=url,
api_type=chain_api_type,
credentials=optional_credentials,
)
url := "<your REST chain service URL>"
chainApiType := breez_sdk_spark.ChainApiTypeMempoolSpace
optionalCredentials := &breez_sdk_spark.Credentials{
Username: "<username>",
Password: "<password>",
}
builder.WithRestChainService(url, chainApiType, optionalCredentials)
With Shared REST Chain Service API docs
With REST Chain Service builds a fresh chain service inside each SDK instance. Server processes hosting many wallets at once can share a single REST chain service between every SDK, so they reuse the same pooled HTTP client (and its HTTP/2 connection pool) instead of each opening a fresh one.
Construct one via new_rest_chain_servicenew_rest_chain_servicenewRestChainServicenewRestChainServicenewRestChainServicenewRestChainServicenewRestChainServiceNewRestChainServiceNewRestChainService and pass it to each SdkBuilderSdkBuilderSdkBuilderSdkBuilderSdkBuilderSdkBuilderSdkBuilderSdkBuilderSdkBuilder via with_chain_servicewith_chain_servicewithChainServicewithChainServicewithChainServicewithChainServicewithChainServiceWithChainServiceWithChainService. All SDK instances sharing the chain service must be configured for the same network.
With Fiat Service API docs
The SDK by default provides a list of available Fiat currencies and current exchange rates. If you want to use your own, you can provide it by implementing the Fiat Service interface.
With LNURL Client API docs
The LNURL Client is used to make REST requests specifically when interacting with LNURL. If you want to use your own, you can it provide by implementing the REST Service interface.
With Account Number API docs
The SDK derives all wallet keys from the seed at the derivation path m/8797555'/<account number>'. By default the account number is 0 on Regtest and 1 on all other networks. Set a different account number to derive an independent wallet from the same seed:
let account_number = 21;
builder.with_account_number(account_number)
let accountNumber = UInt32(21)
await builder.withAccountNumber(accountNumber: accountNumber)
val accountNumber = 21u
builder.withAccountNumber(accountNumber)
var accountNumber = 21u;
await builder.WithAccountNumber(accountNumber);
builder = builder.withAccountNumber(21)
await builder.withAccountNumber(21)
var accountNumber = 21;
builder.withAccountNumber(accountNumber: accountNumber);
account_number = 21
await builder.with_account_number(account_number=account_number)
accountNumber := uint32(21)
builder.WithAccountNumber(accountNumber)
With Payment Observer API docs
By implementing the Payment Observer interface you can be notified before a payment is sent. It includes information about the provisional payment including the payment ID, amount to be sent (in satoshis or token base units) and payment details based on the payment method.
Note: Flutter currently does not support this.
pub(crate) struct ExamplePaymentObserver {}
#[async_trait]
impl PaymentObserver for ExamplePaymentObserver {
async fn before_send(
&self,
payments: Vec<ProvisionalPayment>,
) -> Result<(), PaymentObserverError> {
for payment in payments {
info!(
"About to send payment: {:?} of amount {:?}",
payment.payment_id, payment.amount
);
}
Ok(())
}
async fn after_send(&self, updates: Vec<PaymentIdUpdate>) -> Result<(), PaymentObserverError> {
for update in updates {
info!(
"Token tx broadcast: {} -> {}",
update.provisional_payment_id, update.final_payment_id
);
}
Ok(())
}
}
pub(crate) fn with_payment_observer(builder: SdkBuilder) -> SdkBuilder {
let observer = ExamplePaymentObserver {};
builder.with_payment_observer(Arc::new(observer))
}
class ExamplePaymentObserver: PaymentObserver {
func beforeSend(payments: [ProvisionalPayment]) async {
for payment in payments {
print("About to send payment: \(payment.paymentId) of amount \(payment.amount)")
}
}
func afterSend(updates: [PaymentIdUpdate]) async {
for update in updates {
print("Token tx broadcast: \(update.provisionalPaymentId) -> \(update.finalPaymentId)")
}
}
}
func withPaymentObserver(builder: SdkBuilder) async {
let paymentObserver = ExamplePaymentObserver()
await builder.withPaymentObserver(paymentObserver: paymentObserver)
}
class ExamplePaymentObserver : PaymentObserver {
override suspend fun beforeSend(payments: List<ProvisionalPayment>) {
for (payment in payments) {
// Log.v("PaymentObserver", "About to send payment:
// ${payment.paymentId} of amount ${payment.amount}")
}
}
override suspend fun afterSend(updates: List<PaymentIdUpdate>) {
for (update in updates) {
// Log.v("PaymentObserver", "Token tx broadcast:
// ${update.provisionalPaymentId} -> ${update.finalPaymentId}")
}
}
}
suspend fun withPaymentObserver(builder: SdkBuilder) {
val paymentObserver = ExamplePaymentObserver()
builder.withPaymentObserver(paymentObserver)
}
class ExamplePaymentObserver : PaymentObserver
{
public async Task BeforeSend(ProvisionalPayment[] payments)
{
foreach (var payment in payments)
{
Console.WriteLine($"About to send payment {payment.paymentId} " +
$"of amount {payment.amount}");
}
}
public async Task AfterSend(PaymentIdUpdate[] updates)
{
foreach (var update in updates)
{
Console.WriteLine($"Token tx broadcast: {update.provisionalPaymentId} -> " +
$"{update.finalPaymentId}");
}
}
}
async Task WithPaymentObserver(SdkBuilder builder)
{
var paymentObserver = new ExamplePaymentObserver();
await builder.WithPaymentObserver(paymentObserver);
}
class ExamplePaymentObserver {
beforeSend = async (payments: ProvisionalPayment[]) => {
for (const payment of payments) {
console.log(`About to send payment: ${payment.paymentId} of amount ${payment.amount}`)
}
}
afterSend = async (updates: PaymentIdUpdate[]) => {
for (const update of updates) {
console.log(`Token tx broadcast: ${update.provisionalPaymentId} -> ${update.finalPaymentId}`)
}
}
}
const exampleWithPaymentObserver = (builder: SdkBuilder): SdkBuilder => {
const paymentObserver = new ExamplePaymentObserver()
return builder.withPaymentObserver(paymentObserver)
}
class ExamplePaymentObserver {
beforeSend = async (payments: ProvisionalPayment[]) => {
for (const payment of payments) {
console.log(`About to send payment: ${payment.paymentId} of amount ${payment.amount}`)
}
}
afterSend = async (updates: PaymentIdUpdate[]) => {
for (const update of updates) {
console.log(`Token tx broadcast: ${update.provisionalPaymentId} -> ${update.finalPaymentId}`)
}
}
}
const exampleWithPaymentObserver = async (builder: SdkBuilder) => {
const paymentObserver = new ExamplePaymentObserver()
await builder.withPaymentObserver(paymentObserver)
}
class ExamplePaymentObserver(PaymentObserver):
async def before_send(self, payments: typing.List[ProvisionalPayment]):
for payment in payments:
logging.debug(f"About to send payment {payment.payment_id} of amount {payment.amount}")
async def after_send(self, updates: typing.List[PaymentIdUpdate]):
for update in updates:
logging.debug(
f"Token tx broadcast: {update.provisional_payment_id} -> {update.final_payment_id}"
)
async def with_payment_observer(builder: SdkBuilder):
payment_observer = ExamplePaymentObserver()
await builder.with_payment_observer(payment_observer=payment_observer)
type ExamplePaymentObserver struct{}
func (ExamplePaymentObserver) BeforeSend(payments []breez_sdk_spark.ProvisionalPayment) error {
for _, payment := range payments {
log.Printf("About to send payment: %v of amount %v", payment.PaymentId, payment.Amount)
}
return nil
}
func (ExamplePaymentObserver) AfterSend(updates []breez_sdk_spark.PaymentIdUpdate) error {
for _, update := range updates {
log.Printf("Token tx broadcast: %v -> %v", update.ProvisionalPaymentId, update.FinalPaymentId)
}
return nil
}
func WithPaymentObserver(builder *breez_sdk_spark.SdkBuilder) {
observer := ExamplePaymentObserver{}
builder.WithPaymentObserver(observer)
}
With Shared SDK Context API docs
An SDK Context bundles every process-shareable resource: the HTTP client (used for SSP GraphQL, chain service and LNURL), the gRPC channels to the Spark operators, the gRPC client to the Breez backend, and — optionally — a PostgreSQL or MySQL connection pool. By default each SDK builds its own. Server processes hosting many wallets at once can construct one SDK Context and pass it to every SdkBuilderSdkBuilderSdkBuilderSdkBuilderSdkBuilderSdkBuilderSdkBuilderSdkBuilderSdkBuilder so they reuse the same pooled clients instead of each opening fresh ones.
Construct one via new_shared_sdk_contextnew_shared_sdk_contextnewSharedSdkContextnewSharedSdkContextnewSharedSdkContextnewSharedSdkContextnewSharedSdkContextNewSharedSdkContextNewSharedSdkContext and pass it to each SdkBuilderSdkBuilderSdkBuilderSdkBuilderSdkBuilderSdkBuilderSdkBuilderSdkBuilderSdkBuilder via with_shared_contextwith_shared_contextwithSharedContextwithSharedContextwithSharedContextwithSharedContextwithSharedContextWithSharedContextWithSharedContext. Connections close when the last reference to the SDK Context is dropped; calling disconnectdisconnectdisconnectdisconnectdisconnectdisconnectdisconnectDisconnectDisconnect on an SDK instance does not affect them.
The connections_per_operatorconnections_per_operatorconnectionsPerOperatorconnectionsPerOperatorconnectionsPerOperatorconnectionsPerOperatorconnectionsPerOperatorConnectionsPerOperatorConnectionsPerOperator setting on SdkContextConfigSdkContextConfigSdkContextConfigSdkContextConfigSdkContextConfigSdkContextConfigSdkContextConfigSdkContextConfigSdkContextConfig controls how many gRPC connections the context opens to each Spark operator:
None— one connection per operator, multiplexed across every SDK sharing this context. The right choice for almost every deployment.Some(n)— opensnconnections per operator and balances requests across them. Worth setting only if the single shared connection has become a bottleneck — for example, latency that climbs with throughput, or operators deployed behind an L7 load balancer where you want client-side fan-out across backend instances.
Developer note
All SDK instances sharing an SDK Context must be configured for the same network and operator pool. The user agent of the first SDK to construct the context is reused for all subsequent instances.
Browser
The SDK Context's gRPC channel pooling is not effective in the browser. Browsers maintain a single HTTP/2 connection per origin and multiplex everything over it; the SDK cannot create or share more.
Node.js
Node's global fetch (undici) negotiates HTTP/2 with the Spark operators automatically and opens additional connections per origin as needed, so most deployments need no tuning. If you do want to cap or expand the per-origin pool, configure undici globally before initialising the SDK:
import { Agent, setGlobalDispatcher } from 'undici'
setGlobalDispatcher(new Agent({ connections: 8 }))
This affects every fetch in the process, including the SDK's gRPC-web traffic.