Using an External Signer
The External Signer feature allows you to provide custom signing logic for the SDK rather than relying on the SDK's internal key management. This is useful when you want to:
- Keep keys in a secured environment
- Implement custom key derivation logic
- Integrate with existing wallet infrastructure
Using the Default External Signers
The external signer interface is split into two parts: an ExternalBreezSigner for SDK-layer signing (LNURL-auth, sync, message signing, ECIES) and an ExternalSparkSigner for the Spark wallet flows (transfers, claims, FROST signing, deposits).
The SDK provides a convenient factory function default_external_signersdefault_external_signersdefaultExternalSignersdefaultExternalSignersdefaultExternalSignersdefaultExternalSignersdefaultExternalSignersDefaultExternalSignersDefaultExternalSigners that creates both signers from a mnemonic:
fn create_signers() -> Result<ExternalSigners, SdkError> {
let mnemonic = "<mnemonic words>".to_string();
let network = Network::Mainnet;
let signers = default_external_signers(
mnemonic,
None, // passphrase
network,
Some(0), // account number
)?;
Ok(signers)
}
func createSigners() throws -> ExternalSigners {
let mnemonic = "<mnemonic words>"
let network = Network.mainnet
let signers = try defaultExternalSigners(
mnemonic: mnemonic,
passphrase: nil,
network: network,
accountNumber: 0
)
return signers
}
fun createSigners(): breez_sdk_spark.ExternalSigners {
val mnemonic = "<mnemonic words>"
val network = Network.MAINNET
val accountNumber = 0U
val signers = defaultExternalSigners(
mnemonic = mnemonic,
passphrase = null,
network = network,
accountNumber = accountNumber
)
return signers
}
public static ExternalSigners CreateSigners()
{
var mnemonic = "<mnemonic words>";
var network = Network.Mainnet;
uint accountNumber = 0;
var signers = BreezSdkSparkMethods.DefaultExternalSigners(
mnemonic: mnemonic,
passphrase: null,
network: network,
accountNumber: accountNumber
);
return signers;
}
const createSigners = () => {
const mnemonic = '<mnemonic words>'
const accountNumber = 0
// Create the default signers from the SDK
const signers = defaultExternalSigners(mnemonic, null, 'mainnet', accountNumber)
return signers
}
const createSigners = () => {
const mnemonic = '<mnemonic words>'
const accountNumber = 0
// Create the default signers from the SDK
const signers = defaultExternalSigners(mnemonic, undefined, Network.Mainnet, accountNumber)
return signers
}
def create_signers() -> ExternalSigners:
mnemonic = "<mnemonic words>"
network = Network.MAINNET
account_number = 0
signers = default_external_signers(
mnemonic=mnemonic,
passphrase=None,
network=network,
account_number=account_number,
)
return signers
func createSigners() (breez_sdk_spark.ExternalSigners, error) {
mnemonic := "<mnemonic words>"
network := breez_sdk_spark.NetworkMainnet
var accountNumber uint32 = 0
signers, err := breez_sdk_spark.DefaultExternalSigners(
mnemonic,
nil, // passphrase
network,
&accountNumber,
)
if err != nil {
var sdkErr *breez_sdk_spark.SdkError
if errors.As(err, &sdkErr) {
// Handle SdkError - can inspect specific variants if needed
// e.g., switch on sdkErr variant for InsufficientFunds, NetworkError, etc.
}
return breez_sdk_spark.ExternalSigners{}, err
}
return signers, nil
}
Provide both signers to the connect_with_signerconnect_with_signerconnectWithSignerconnectWithSignerconnectWithSignerconnectWithSignerconnectWithSignerConnectWithSignerConnectWithSigner method instead of the regular connectconnectconnectconnectconnectconnectconnectConnectConnect method:
async fn connect_example(signers: ExternalSigners) -> Result<BreezSdk, SdkError> {
// Create the config
let mut config = default_config(Network::Mainnet);
config.api_key = Some("<breez api key>".to_string());
// Connect using the external signers
let sdk = connect_with_signer(ConnectWithSignerRequest {
config,
breez_signer: signers.breez_signer,
spark_signer: signers.spark_signer,
storage_dir: "./.data".to_string(),
})
.await?;
Ok(sdk)
}
func connectExample(signers: ExternalSigners) async throws -> BreezSdk {
// Create the config
var config = defaultConfig(network: .mainnet)
config.apiKey = "<breez api key>"
// Connect using the external signers
let sdk = try await BreezSdkSpark.connectWithSigner(request: ConnectWithSignerRequest(
config: config,
breezSigner: signers.breezSigner,
sparkSigner: signers.sparkSigner,
storageDir: "./.data"
))
return sdk
}
suspend fun connectWithSigner(signers: breez_sdk_spark.ExternalSigners) {
// Create the config
val config = defaultConfig(Network.MAINNET)
config.apiKey = "<breez api key>"
try {
// Connect using the external signers
val sdk = connectWithSigner(ConnectWithSignerRequest(
config = config,
breezSigner = signers.breezSigner,
sparkSigner = signers.sparkSigner,
storageDir = "./.data"
))
} catch (e: Exception) {
// handle error
}
}
public static async Task<BreezSdk> ConnectWithSigner(ExternalSigners signers)
{
// Create the config
var config = BreezSdkSparkMethods.DefaultConfig(Network.Mainnet) with
{
apiKey = "<breez api key>"
};
// Connect using the external signers
var sdk = await BreezSdkSparkMethods.ConnectWithSigner(new ConnectWithSignerRequest(
config: config,
breezSigner: signers.breezSigner,
sparkSigner: signers.sparkSigner,
storageDir: "./.data"
));
return sdk;
}
const exampleConnectWithSigner = async (
signers: ReturnType<typeof defaultExternalSigners>
) => {
// Create the config
const config = defaultConfig('mainnet')
config.apiKey = '<breez api key>'
// Connect using the external signers
const sdk = await connectWithSigner(
config,
signers.breezSigner,
signers.sparkSigner,
'breez_spark_db' // For WASM, this is the IndexedDB database name
)
}
const exampleConnectWithSigner = async (
signers: ReturnType<typeof defaultExternalSigners>
) => {
// Create the config
const config = defaultConfig(Network.Mainnet)
config.apiKey = '<breez api key>'
// Connect using the external signers
const sdk = await connectWithSigner({
config,
breezSigner: signers.breezSigner,
sparkSigner: signers.sparkSigner,
storageDir: `${RNFS.DocumentDirectoryPath}/data`
})
}
async def example_connect_with_signer(signers: ExternalSigners) -> BreezSdk:
# Create the config
config = default_config(Network.MAINNET)
config.api_key = "<breez api key>"
# Connect using the external signers
sdk = await connect_with_signer(ConnectWithSignerRequest(
config=config,
breez_signer=signers.breez_signer,
spark_signer=signers.spark_signer,
storage_dir="./.data"
))
return sdk
func connectWithSigner(
signers breez_sdk_spark.ExternalSigners,
) (*breez_sdk_spark.BreezSdk, error) {
// Create the config
config := breez_sdk_spark.DefaultConfig(breez_sdk_spark.NetworkMainnet)
apiKey := "<breez api key>"
config.ApiKey = &apiKey
// Connect using the external signers
sdk, err := breez_sdk_spark.ConnectWithSigner(breez_sdk_spark.ConnectWithSignerRequest{
Config: config,
BreezSigner: signers.BreezSigner,
SparkSigner: signers.SparkSigner,
StorageDir: "./.data",
})
if err != nil {
var sdkErr *breez_sdk_spark.SdkError
if errors.As(err, &sdkErr) {
// Handle SdkError - can inspect specific variants if needed
// e.g., switch on sdkErr variant for InsufficientFunds, NetworkError, etc.
}
return nil, err
}
return sdk, nil
}
Developer note
When using an external signer, you don't provide a seed directly to the SDK. Instead, the signer handles all cryptographic operations internally.Implementing a Custom Signer
If you need full control over the signing process, you can implement the ExternalBreezSigner and ExternalSparkSigner interfaces in your application. These interfaces define all the cryptographic operations the SDK needs.
The default implementations of the two interfaces, DefaultExternalSigner and DefaultExternalSparkSigner, can be used as a reference for what's expected.
Developer note
Implementing a custom signer requires deep understanding of Bitcoin cryptography. The default signer implementations provide a solid reference for what's expected.
Most applications should use the default external signers factory function rather than implementing their own.
Flutter Limitation
External signers are not supported in Flutter due to limitations with passing trait objects through the flutter_rust_bridge FFI. Flutter applications should use the standard connect method with mnemonic-based key management.