Skip to main content
Glama
StevenGeller

LDK MCP Server

by StevenGeller

ldk_derive_address

Generate Bitcoin addresses from a BIP39 seed phrase using the BIP84 standard for secure wallet integration.

Instructions

Derive Bitcoin addresses from seed using BIP84

Input Schema

TableJSON Schema
NameRequiredDescriptionDefault
mnemonicYesBIP39 mnemonic phrase
accountIndexNoAccount index (default: 0)
addressIndexNoAddress index (default: 0)
isChangeNoIs change address (default: false)
networkNoBitcoin networktestnet

Implementation Reference

  • Execute function implementing the tool logic: converts mnemonic to seed, derives BIP84 address using WalletService, returns formatted result including Swift iOS integration example.
      execute: async (args: any): Promise<ToolResult> => {
        try {
          const seed = walletService.mnemonicToSeed(args.mnemonic);
          const derivation = walletService.deriveAddress(
            seed,
            args.accountIndex || 0,
            args.isChange || false,
            args.addressIndex || 0
          );
    
          return {
            content: [{
              type: 'text',
              text: JSON.stringify({
                success: true,
                address: derivation.address,
                publicKey: derivation.publicKey,
                derivationPath: derivation.derivationPath,
                network: args.network || 'testnet',
                swiftExample: `
    // Swift code for address derivation in your iOS app
    import BitcoinDevKit
    import LightningDevKit
    
    class AddressManager {
        private let wallet: BitcoinDevKit.Wallet
        private let network: BitcoinDevKit.Network
        
        init(mnemonic: [String], network: BitcoinDevKit.Network = .testnet) throws {
            self.network = network
            
            // Create descriptors
            let mnemonicStr = mnemonic.joined(separator: " ")
            let secretKey = DescriptorSecretKey(
                network: network,
                mnemonic: Mnemonic(mnemonic: mnemonicStr),
                password: nil
            )
            
            // BIP84 descriptors (native segwit)
            let descriptor = Descriptor(
                descriptor: "wpkh(\\(secretKey.asString())/84'/\\(network == .bitcoin ? "0" : "1")'/0'/0/*)",
                network: network
            )
            
            let changeDescriptor = Descriptor(
                descriptor: "wpkh(\\(secretKey.asString())/84'/\\(network == .bitcoin ? "0" : "1")'/0'/1/*)",
                network: network
            )
            
            // Initialize wallet
            let walletDB = DatabaseConfig.memory
            wallet = try BitcoinDevKit.Wallet(
                descriptor: descriptor,
                changeDescriptor: changeDescriptor,
                network: network,
                databaseConfig: walletDB
            )
        }
        
        func getNewAddress() throws -> AddressInfo {
            let addressInfo = try wallet.getAddress(addressIndex: .new)
            
            return AddressInfo(
                address: addressInfo.address.asString(),
                index: addressInfo.index,
                keychain: addressInfo.keychain,
                isChange: addressInfo.keychain == .external ? false : true
            )
        }
        
        func getAddress(at index: UInt32, isChange: Bool = false) throws -> String {
            let keychain: KeychainKind = isChange ? .internal : .external
            let addressInfo = try wallet.getAddress(addressIndex: .peek(index: index))
            
            return addressInfo.address.asString()
        }
        
        func validateAddress(_ address: String) -> Bool {
            do {
                _ = try Address(address: address, network: network)
                return true
            } catch {
                return false
            }
        }
    }
    
    struct AddressInfo {
        let address: String
        let index: UInt32
        let keychain: KeychainKind
        let isChange: Bool
    }
    
    // SwiftUI view for address management
    struct AddressManagerView: View {
        @State private var addresses: [AddressInfo] = []
        @State private var showingNewAddress = false
        @State private var selectedAddress: AddressInfo?
        @State private var copiedAddress: String?
        
        var body: some View {
            List {
                Section {
                    Button(action: generateNewAddress) {
                        Label("Generate New Address", systemImage: "plus.circle")
                            .foregroundColor(.accentColor)
                    }
                }
                
                Section("Receive Addresses") {
                    ForEach(addresses.filter { !$0.isChange }, id: \\.address) { address in
                        AddressRow(
                            address: address,
                            isCopied: copiedAddress == address.address,
                            onCopy: { copyAddress(address.address) }
                        )
                    }
                }
            }
            .navigationTitle("Addresses")
            .task {
                loadAddresses()
            }
            .sheet(isPresented: $showingNewAddress) {
                if let address = selectedAddress {
                    NewAddressView(addressInfo: address) {
                        showingNewAddress = false
                        selectedAddress = nil
                        loadAddresses()
                    }
                }
            }
        }
        
        func generateNewAddress() {
            do {
                let addressManager = try AddressManager(
                    mnemonic: LDKManager.shared.getMnemonic()
                )
                let newAddress = try addressManager.getNewAddress()
                selectedAddress = newAddress
                showingNewAddress = true
            } catch {
                // Handle error
            }
        }
        
        func loadAddresses() {
            // Load existing addresses from wallet
            do {
                let addressManager = try AddressManager(
                    mnemonic: LDKManager.shared.getMnemonic()
                )
                
                var loadedAddresses: [AddressInfo] = []
                
                // Load up to 20 addresses
                for i in 0..<20 {
                    let address = try addressManager.getAddress(at: UInt32(i))
                    loadedAddresses.append(AddressInfo(
                        address: address,
                        index: UInt32(i),
                        keychain: .external,
                        isChange: false
                    ))
                }
                
                addresses = loadedAddresses
            } catch {
                // Handle error
            }
        }
        
        func copyAddress(_ address: String) {
            UIPasteboard.general.string = address
            copiedAddress = address
            
            // Reset after 2 seconds
            DispatchQueue.main.asyncAfter(deadline: .now() + 2) {
                if copiedAddress == address {
                    copiedAddress = nil
                }
            }
        }
    }
    
    struct AddressRow: View {
        let address: AddressInfo
        let isCopied: Bool
        let onCopy: () -> Void
        
        var body: some View {
            VStack(alignment: .leading, spacing: 8) {
                HStack {
                    Text("Index \\(address.index)")
                        .font(.caption)
                        .foregroundColor(.secondary)
                    
                    Spacer()
                    
                    if isCopied {
                        Label("Copied", systemImage: "checkmark")
                            .font(.caption)
                            .foregroundColor(.green)
                    }
                }
                
                HStack {
                    Text(address.address)
                        .font(.system(.caption, design: .monospaced))
                        .lineLimit(1)
                        .truncationMode(.middle)
                    
                    Button(action: onCopy) {
                        Image(systemName: "doc.on.doc")
                            .font(.caption)
                    }
                    .buttonStyle(.borderless)
                }
            }
            .padding(.vertical, 4)
        }
    }`.trim()
              }, null, 2)
            }]
          };
        } catch (error) {
          return {
            content: [{
              type: 'text',
              text: JSON.stringify({
                success: false,
                error: error instanceof Error ? error.message : 'Unknown error'
              }, null, 2)
            }],
            isError: true
          };
        }
      }
  • Input schema defining parameters for mnemonic-based address derivation with BIP84 path customization.
    inputSchema: {
      type: 'object',
      properties: {
        mnemonic: {
          type: 'string',
          description: 'BIP39 mnemonic phrase'
        },
        accountIndex: {
          type: 'number',
          description: 'Account index (default: 0)',
          default: 0
        },
        addressIndex: {
          type: 'number', 
          description: 'Address index (default: 0)',
          default: 0
        },
        isChange: {
          type: 'boolean',
          description: 'Is change address (default: false)',
          default: false
        },
        network: {
          type: 'string',
          enum: ['mainnet', 'testnet', 'regtest'],
          description: 'Bitcoin network',
          default: 'testnet'
        }
      },
      required: ['mnemonic']
    },
  • src/index.ts:55-55 (registration)
    Registration of deriveAddressTool in the MCP server's central tools array for discovery and execution dispatching.
    deriveAddressTool,
  • Core helper function in WalletService performing BIP84 derivation using bip32 and bitcoinjs-lib to generate native segwit (p2wpkh) addresses.
    deriveAddress(
      seed: Buffer,
      accountIndex: number = 0,
      isChange: boolean = false,
      addressIndex: number = 0
    ): {
      address: string;
      publicKey: string;
      privateKey: string;
      derivationPath: string;
    } {
      const root = bip32.fromSeed(seed, this.network);
      
      // BIP84 (native segwit) derivation path
      const purpose = 84;
      const coinType = this.network === bitcoin.networks.bitcoin ? 0 : 1;
      const change = isChange ? 1 : 0;
      
      const path = `m/${purpose}'/${coinType}'/${accountIndex}'/${change}/${addressIndex}`;
      const child = root.derivePath(path);
      
      const { address } = bitcoin.payments.p2wpkh({
        pubkey: child.publicKey,
        network: this.network
      });
    
      if (!address) {
        throw new Error('Failed to derive address');
      }
    
      return {
        address,
        publicKey: child.publicKey.toString('hex'),
        privateKey: child.privateKey!.toString('hex'),
        derivationPath: path
      };
    }
Behavior2/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

With no annotations provided, the description carries the full burden of behavioral disclosure. It states the derivation action but fails to mention critical aspects like security implications (e.g., handling sensitive mnemonic phrases), performance characteristics, or error conditions. This leaves significant gaps in understanding the tool's behavior.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness5/5

Is the description appropriately sized, front-loaded, and free of redundancy?

The description is a single, efficient sentence that directly states the tool's purpose without any unnecessary words. It is front-loaded and appropriately sized for its function, making it highly concise and well-structured.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness3/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

Given the complexity of Bitcoin address derivation, no annotations, and no output schema, the description is minimally adequate but incomplete. It covers the basic purpose but lacks details on security, output format, or error handling, which are crucial for safe and effective use in this context.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters3/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

The input schema has 100% description coverage, providing clear documentation for all 5 parameters. The description adds no additional parameter semantics beyond what's in the schema, so it meets the baseline score of 3 without compensating for any gaps.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose4/5

Does the description clearly state what the tool does and how it differs from similar tools?

The description clearly states the action ('Derive') and resource ('Bitcoin addresses') with the specific method ('from seed using BIP84'), making the purpose unambiguous. However, it doesn't explicitly differentiate from sibling tools like 'ldk_generate_mnemonic' or 'ldk_get_balance', which prevents a perfect score.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines2/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

No guidance is provided on when to use this tool versus alternatives, such as other Bitcoin address derivation methods or related sibling tools. The description lacks context about prerequisites or typical use cases, leaving the agent without usage direction.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

Install Server

Other Tools

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/StevenGeller/ldk-mcp'

If you have feedback or need assistance with the MCP directory API, please join our Discord server