Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

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 Signer

The SDK provides a convenient factory function default_external_signerdefault_external_signerdefaultExternalSignerdefaultExternalSignerdefaultExternalSignerdefaultExternalSignerdefaultExternalSignerDefaultExternalSignerDefaultExternalSigner that creates a signer from a mnemonic. This is the easiest way to get started:

Rust
fn create_signer() -> Result<Arc<dyn ExternalSigner>, SdkError> {
    let mnemonic = "<mnemonic words>".to_string();
    let network = Network::Mainnet;

    let signer = default_external_signer(
        mnemonic,
        None, // passphrase
        network,
        Some(KeySetConfig {
            key_set_type: KeySetType::Default,
            use_address_index: false,
            account_number: Some(0),
        }),
    )?;

    Ok(signer)
}
Swift
func createSigner() throws -> ExternalSigner {
    let mnemonic = "<mnemonic words>"
    let network = Network.mainnet

    let signer = try defaultExternalSigner(
        mnemonic: mnemonic,
        passphrase: nil,
        network: network,
        keySetConfig: KeySetConfig(
            keySetType: KeySetType.default,
            useAddressIndex: false,
            accountNumber: 0
        )
    )

    return signer
}
Kotlin
fun createSigner(): breez_sdk_spark.ExternalSigner {
    val mnemonic = "<mnemonic words>"
    val network = Network.MAINNET
    val keySetType = KeySetType.DEFAULT
    val useAddressIndex = false
    val accountNumber = 0U

    val keySetConfig = KeySetConfig(
        keySetType = keySetType,
        useAddressIndex = useAddressIndex,
        accountNumber = accountNumber
    )

    val signer = defaultExternalSigner(
        mnemonic = mnemonic,
        passphrase = null,
        network = network,
        keySetConfig = keySetConfig
    )

    return signer
}
C#
public static ExternalSigner CreateSigner()
{
    var mnemonic = "<mnemonic words>";
    var network = Network.Mainnet;
    var keySetType = KeySetType.Default;
    var useAddressIndex = false;
    uint accountNumber = 0;

    var keySetConfig = new KeySetConfig(
        keySetType: keySetType,
        useAddressIndex: useAddressIndex,
        accountNumber: accountNumber
    );

    var signer = BreezSdkSparkMethods.DefaultExternalSigner(
        mnemonic: mnemonic,
        passphrase: null,
        network: network,
        keySetConfig: keySetConfig
    );

    return signer;
}
Javascript
const createSigner = () => {
  const mnemonic = '<mnemonic words>'
  const keySetConfig: KeySetConfig = {
    keySetType: 'default',
    useAddressIndex: false,
    accountNumber: 0
  }

  // Create the default signer from the SDK
  const signer = defaultExternalSigner(mnemonic, null, 'mainnet', keySetConfig)

  return signer
}
React Native
const createSigner = () => {
  const mnemonic = '<mnemonic words>'
  const keySetConfig: KeySetConfig = {
    keySetType: KeySetType.Default,
    useAddressIndex: false,
    accountNumber: 0
  }

  // Create the default signer from the SDK
  const signer = defaultExternalSigner(mnemonic, undefined, Network.Mainnet, keySetConfig)

  return signer
}
Python
def create_signer() -> ExternalSigner:
    mnemonic = "<mnemonic words>"
    network = Network.MAINNET
    key_set_type = KeySetType.DEFAULT
    use_address_index = False
    account_number = 0

    key_set_config = KeySetConfig(
        key_set_type=key_set_type,
        use_address_index=use_address_index,
        account_number=account_number,
    )

    signer = default_external_signer(
        mnemonic=mnemonic,
        passphrase=None,
        network=network,
        key_set_config=key_set_config,
    )

    return signer
Go
func createSigner() (breez_sdk_spark.ExternalSigner, error) {
	mnemonic := "<mnemonic words>"
	network := breez_sdk_spark.NetworkMainnet
	keySetType := breez_sdk_spark.KeySetTypeDefault
	useAddressIndex := false
	var accountNumber uint32 = 0

	keySetConfig := breez_sdk_spark.KeySetConfig{
		KeySetType:      keySetType,
		UseAddressIndex: useAddressIndex,
		AccountNumber:   &accountNumber,
	}

	signer, err := breez_sdk_spark.DefaultExternalSigner(
		mnemonic,
		nil, // passphrase
		network,
		&keySetConfig,
	)
	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 signer, nil
}

Once you have a signer, you can use it when connecting to the SDK with the connect_with_signerconnect_with_signerconnectWithSignerconnectWithSignerconnectWithSignerconnectWithSignerconnectWithSignerConnectWithSignerConnectWithSigner method instead of the regular connectconnectconnectconnectconnectconnectconnectConnectConnect method:

Rust
async fn connect_example(signer: Arc<dyn ExternalSigner>) -> 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 signer
    let sdk = connect_with_signer(ConnectWithSignerRequest {
        config,
        signer,
        storage_dir: "./.data".to_string(),
    })
    .await?;

    Ok(sdk)
}
Swift
func connectExample(signer: ExternalSigner) async throws -> BreezSdk {
    // Create the config
    var config = defaultConfig(network: .mainnet)
    config.apiKey = "<breez api key>"

    // Connect using the external signer
    let sdk = try await BreezSdkSpark.connectWithSigner(request: ConnectWithSignerRequest(
        config: config,
        signer: signer,
        storageDir: "./.data"
    ))

    return sdk
}
Kotlin
suspend fun connectWithSigner(signer: breez_sdk_spark.ExternalSigner) {
    // Create the config
    val config = defaultConfig(Network.MAINNET)
    config.apiKey = "<breez api key>"

    try {
        // Connect using the external signer
        val sdk = connectWithSigner(ConnectWithSignerRequest(
            config = config,
            signer = signer,
            storageDir = "./.data"
        ))
    } catch (e: Exception) {
        // handle error
    }
}
C#
public static async Task<BreezSdk> ConnectWithSigner(ExternalSigner signer)
{
    // Create the config
    var config = BreezSdkSparkMethods.DefaultConfig(Network.Mainnet) with
    {
        apiKey = "<breez api key>"
    };

    // Connect using the external signer
    var sdk = await BreezSdkSparkMethods.ConnectWithSigner(new ConnectWithSignerRequest(
        config: config,
        signer: signer,
        storageDir: "./.data"
    ));

    return sdk;
}
Javascript
const exampleConnectWithSigner = async (signer: ReturnType<typeof defaultExternalSigner>) => {
  // Create the config
  const config = defaultConfig('mainnet')
  config.apiKey = '<breez api key>'

  // Connect using the external signer
  const sdk = await connectWithSigner(
    config,
    signer,
    'breez_spark_db' // For WASM, this is the IndexedDB database name
  )
}
React Native
const exampleConnectWithSigner = async (signer: ReturnType<typeof defaultExternalSigner>) => {
  // Create the config
  const config = defaultConfig(Network.Mainnet)
  config.apiKey = '<breez api key>'

  // Connect using the external signer
  const sdk = await connectWithSigner({
    config,
    signer,
    storageDir: `${RNFS.DocumentDirectoryPath}/data`
  })
}
Python
async def example_connect_with_signer(signer: ExternalSigner) -> BreezSdk:
    # Create the config
    config = default_config(Network.MAINNET)
    config.api_key = "<breez api key>"

    # Connect using the external signer
    sdk = await connect_with_signer(ConnectWithSignerRequest(
        config=config,
        signer=signer,
        storage_dir="./.data"
    ))

    return sdk
Go
func connectWithSigner(signer breez_sdk_spark.ExternalSigner) (*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 signer
	sdk, err := breez_sdk_spark.ConnectWithSigner(breez_sdk_spark.ConnectWithSignerRequest{
		Config:     config,
		Signer:     signer,
		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 ExternalSigner interface in your application. This interface defines all the cryptographic operations the SDK needs.

The DefaultSigner implementation 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 implementation provides a solid reference for what's expected.

Most applications should use the default external signer 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.