{"templateId":"markdown","sharedDataIds":{"sidebar":"sidebar-sidebars.yaml"},"props":{"metadata":{"markdoc":{"tagList":[]},"type":"markdown"},"seo":{"title":"Implement An External Signer","siteUrl":"https://opensource.ripple.com/","meta":[{"name":"google-site-verification","content":"bLwyBi1imklcIuQxZ7JeI_kRF5Mg7yfr6arpEQV2nsE"}],"llmstxt":{"hide":false,"sections":[{"title":"Table of contents","includeFiles":["**/*"],"excludeFiles":[]}],"excludeFiles":[]},"description":"Implement the ExternalSignerPort seam end to end with a mock signer, and switch between secp256k1 and ed25519."},"dynamicMarkdocComponents":[],"compilationErrors":[],"ast":{"$$mdtype":"Tag","name":"article","attributes":{},"children":[{"$$mdtype":"Tag","name":"Heading","attributes":{"level":1,"id":"implement-an-external-signer","__idx":0},"children":["Implement An External Signer"]},{"$$mdtype":"Tag","name":"p","attributes":{},"children":["The ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["ExternalSignerPort"]}," seam lets you plug in your own signer. This sample implements it with an in-process key so it actually signs and submits against a mock ledger, and shows how to switch between the secp256k1 and ed25519 schemes. In production you swap the mock for a KMS or HSM signer — nothing else changes."]},{"$$mdtype":"Tag","name":"CodeBlock","attributes":{"data-language":"ts","header":{"controls":{"copy":{}}},"source":"/**\n * External signing end to end — and switching algorithms.\n *\n * The `ExternalSignerPort` seam covers both XRPL signature schemes; the SDK\n * routes the crypto by algorithm (secp256k1: SHA-512Half digest → low-S → DER;\n * ed25519: sign the message directly). The procedure below is identical for\n * either — you just pass a different port.\n *\n * As shipped this file is illustrative: the in-process demo signers at the\n * bottom are commented out. Uncomment them (or plug in your own KMS/HSM-backed\n * `ExternalSignerPort`) for the snippet to run.\n */\nimport { ExternalSigner, SimpleXRPL } from 'simplexrpl'\nimport type {\n  Ed25519SignerPort,\n  LedgerPort,\n  Secp256k1SignerPort,\n  SubmitResponse,\n  Transaction,\n  TxResponse,\n} from 'simplexrpl'\n\n// === What you write with simpleXRPL ===\n// Bind your external signer, then build → sign → submit. The pipeline is the\n// same whether the signer is secp256k1 or ed25519.\nasync function signAndSubmit(\n  signer: Secp256k1SignerPort | Ed25519SignerPort,\n  label: string,\n): Promise<void> {\n  const custody = await ExternalSigner.create({ signer })\n  const client = await SimpleXRPL.init({\n    xrpldUrl: 'wss://s.altnet.rippletest.net:51233', // XRPL Testnet\n    signers: [custody],\n    ledger: inMemoryLedger(), // omit in production to use the live XRPL connection\n  })\n  const result = await client.xrp.transfer({\n    to: client.account.create().address,\n    amount: '10',\n  })\n  console.log(\n    `${label}: account ${custody.primary.address} signed & submitted ` +\n      `(source=${result.source}, hash=${result.txHash})`,\n  )\n  await client.disconnect()\n}\n\n// === Test scaffolding — NOT production code ===\n// In a real app you omit `ledger` from `SimpleXRPL.init` and the SDK uses the\n// live XRPL connection. This in-memory stand-in lets the example run offline:\n// it fills the network fields and reports a successful submission without\n// touching a network.\n\n/** An in-memory `LedgerPort`: accepts any signed blob and reports success. */\nfunction inMemoryLedger(): LedgerPort {\n  return {\n    autofill: async (tx: Transaction): Promise<Transaction> => ({\n      ...tx,\n      Sequence: 1,\n      Fee: '12',\n      LastLedgerSequence: 100,\n    }),\n    submit: async (): Promise<SubmitResponse> =>\n      ({ result: {} }) as unknown as SubmitResponse,\n    submitAndWait: async (): Promise<TxResponse> =>\n      ({\n        result: { hash: 'MOCKHASH', meta: { TransactionResult: 'tesSUCCESS' } },\n      }) as unknown as TxResponse,\n    request: async <T>(): Promise<T> => ({}) as T,\n  }\n}\n\n// === Demo signers — uncomment to run, or replace with your own KMS/HSM ===\n// These use in-process keys via `@noble/curves` so the file runs offline; a\n// real port delegates the digest/message to your KMS or HSM. Switch algorithms\n// by swapping the port — `signAndSubmit` above doesn't change.\n//\n// import { ed25519 } from '@noble/curves/ed25519'\n// import { secp256k1 } from '@noble/curves/secp256k1'\n//\n// function mockSecp256k1(privHex: string): Secp256k1SignerPort {\n//   const priv = Buffer.from(privHex, 'hex')\n//   return {\n//     algorithm: 'secp256k1',\n//     publicKey: async (): Promise<string> =>\n//       Buffer.from(secp256k1.getPublicKey(priv, true))\n//         .toString('hex')\n//         .toUpperCase(),\n//     signDigest: async (digest: Uint8Array) => {\n//       const sig = secp256k1.sign(digest, priv)\n//       return { r: sig.r, s: sig.s }\n//     },\n//   }\n// }\n//\n// function mockEd25519(privHex: string): Ed25519SignerPort {\n//   const priv = Buffer.from(privHex, 'hex')\n//   return {\n//     algorithm: 'ed25519',\n//     publicKey: async (): Promise<string> =>\n//       `ED${Buffer.from(ed25519.getPublicKey(priv)).toString('hex')}`.toUpperCase(),\n//     signMessage: async (message: Uint8Array): Promise<Uint8Array> =>\n//       ed25519.sign(message, priv),\n//   }\n// }\n//\n// await signAndSubmit(\n//   mockSecp256k1(\n//     'c9537c5a2f3f7e1d4b6a8c0e2f4d6b8a1c3e5f7091b3d5f7a9c1e3050709b0d0f',\n//   ),\n//   'secp256k1',\n// )\n// await signAndSubmit(\n//   mockEd25519(\n//     '9d61b19deffebc3a6c1f6b2d7e5f8a0b1c2d3e4f5061728394a5b6c7d8e9f001',\n//   ),\n//   'ed25519',\n// )\n","lang":"ts"},"children":[]},{"$$mdtype":"Tag","name":"Heading","attributes":{"level":2,"id":"see-also","__idx":1},"children":["See Also"]},{"$$mdtype":"Tag","name":"ul","attributes":{},"children":[{"$$mdtype":"Tag","name":"li","attributes":{},"children":[{"$$mdtype":"Tag","name":"a","attributes":{"href":"/docs/simplexrpl/references/verticals/account/create"},"children":["account.create()"]}]},{"$$mdtype":"Tag","name":"li","attributes":{},"children":[{"$$mdtype":"Tag","name":"a","attributes":{"href":"/docs/simplexrpl/references/verticals/xrp/transfer"},"children":["xrp.transfer()"]}]}]}]},"headings":[{"value":"Implement An External Signer","id":"implement-an-external-signer","depth":1},{"value":"See Also","id":"see-also","depth":2}],"frontmatter":{"seo":{"description":"Implement the ExternalSignerPort seam end to end with a mock signer, and switch between secp256k1 and ed25519.","title":"Implement An External Signer"},"labels":["simpleXRPL","SDK"]},"editPage":{"to":"https://github.com/ripple/opensource.ripple.com/tree/main/docs/simpleXRPL/tutorials/external-signer.md"},"lastModified":"2026-08-21T20:52:47.000Z","pagePropGetterError":{"message":"","name":""}},"slug":"/docs/simplexrpl/tutorials/external-signer","userData":{"isAuthenticated":false,"teams":["anonymous"]},"isPublic":true}