{"_id":"@0xsend/external-signing","_rev":"5-0242d17007cfd228a2073ca9fcc2dd27","name":"@0xsend/external-signing","dist-tags":{"latest":"0.4.0"},"versions":{"0.2.0":{"name":"@0xsend/external-signing","version":"0.2.0","author":{"name":"Send Foundation"},"license":"MIT","_id":"@0xsend/external-signing@0.2.0","maintainers":[{"name":"eho-send","email":"eho@send.it"},{"name":"0xbigboss","email":"0xbigboss@proton.me"},{"name":"allen-send","email":"allen@send.it"}],"homepage":"https://github.com/canton-foundation/canton-monorepo#readme","bugs":{"url":"https://github.com/canton-foundation/canton-monorepo/issues"},"dist":{"shasum":"b9f8f22a8b9e66c95f6c8f059b0d8737239bee9c","tarball":"https://registry.npmjs.org/@0xsend/external-signing/-/external-signing-0.2.0.tgz","fileCount":19,"integrity":"sha512-0QlCTt8ntI3GyWbPaPi7rap2Vsm6ufU1eSBLZ3iQm5atpB1sqtF5WnASiBRHA3x67NbN2UMqCHuQncz3JOOhIQ==","signatures":[{"sig":"MEUCIQDJ+Mdw9RjpbIw20GoNdKGeV8r/Vf9Xa8wVXJoaKJD3lAIgOeug8PpnecEk5GsjXH91eA0A+BxOpnG0T910tgR0hLY=","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":108136},"main":"src/index.ts","type":"module","types":"src/index.ts","exports":{".":"./src/index.ts"},"gitHead":"16490a24adba8547b5bf02deffcee0660aacc838","scripts":{"lint":"tsc --noEmit","test":"vitest run","build":"node scripts/build.mjs","typecheck":"tsc --noEmit","test:watch":"vitest --watch","test:integration":"vitest run --config vitest.integration.config.ts"},"_npmUser":{"name":"eho-send","email":"eho@send.it"},"repository":{"url":"git+https://github.com/canton-foundation/canton-monorepo.git","type":"git","directory":"packages/canton-external-signing"},"_npmVersion":"10.8.2","description":"TypeScript library for external signing on the Canton Network","directories":{},"_nodeVersion":"20.18.0","dependencies":{"zod":"^4.1.13","jose":"^6.0.11","debug":"^4.4.1","@daml/types":"3.3.0-snapshot.20250502.13767.0.v2fc6c7e2","@daml/ledger":"3.3.0-snapshot.20250502.13767.0.v2fc6c7e2","@noble/curves":"^2.0.1","@noble/hashes":"^2.0.1"},"publishConfig":{"main":"./dist/index.js","types":"./dist/index.d.ts","access":"public","exports":{".":{"types":"./dist/index.d.ts","default":"./dist/index.js"}},"registry":"https://registry.npmjs.org"},"_hasShrinkwrap":false,"devDependencies":{"vitest":"^3.2.4","esbuild":"^0.27.0","typescript":"^5.8.3","@types/node":"^24.0.12","@types/debug":"^4","@canton/test-utils":"1.0.0"},"_npmOperationalInternal":{"tmp":"tmp/external-signing_0.2.0_1777160542949_0.1200798625374393","host":"s3://npm-registry-packages-npm-production"}},"0.2.1":{"name":"@0xsend/external-signing","version":"0.2.1","author":{"name":"Send Foundation"},"license":"MIT","_id":"@0xsend/external-signing@0.2.1","maintainers":[{"name":"eho-send","email":"eho@send.it"},{"name":"0xbigboss","email":"0xbigboss@proton.me"},{"name":"allen-send","email":"allen@send.it"}],"homepage":"https://github.com/canton-foundation/canton-monorepo#readme","bugs":{"url":"https://github.com/canton-foundation/canton-monorepo/issues"},"dist":{"shasum":"18ee68a25d53beed6d3c454ff66cbf242c9b71b0","tarball":"https://registry.npmjs.org/@0xsend/external-signing/-/external-signing-0.2.1.tgz","fileCount":19,"integrity":"sha512-U41rSDWRypy7wg853M9clmzQDYBoWUdJ0OwxOwA2r7wsLnrIu75//LnkhYtPzdcNjFj+H3Kffl/OlEY1NgccIQ==","signatures":[{"sig":"MEYCIQCHWY8LYkTP3filMjMTteNKC2HJaVCuqKwCB/Fb9IQP4AIhAOSFU5RsU6eNYhn3ng+t+BZgF9knpsJVZvrLDO97mkDL","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":108136},"main":"src/index.ts","type":"module","types":"src/index.ts","exports":{".":"./src/index.ts"},"gitHead":"46acbbe70c39300ac90794357c36d0be3e4159dc","scripts":{"lint":"tsc --noEmit","test":"vitest run","build":"node scripts/build.mjs","typecheck":"tsc --noEmit","test:watch":"vitest --watch","test:integration":"vitest run --config vitest.integration.config.ts"},"_npmUser":{"name":"GitHub Actions","email":"npm-oidc-no-reply@github.com","trustedPublisher":{"id":"github","oidcConfigId":"oidc:33c8c8d8-641e-40de-a462-720105ac73f0"}},"repository":{"url":"git+https://github.com/canton-foundation/canton-monorepo.git","type":"git","directory":"packages/canton-external-signing"},"_npmVersion":"11.13.0","description":"TypeScript library for external signing on the Canton Network","directories":{},"_nodeVersion":"22.15.0","dependencies":{"zod":"^4.1.13","jose":"^6.0.11","debug":"^4.4.1","@daml/types":"3.3.0-snapshot.20250502.13767.0.v2fc6c7e2","@daml/ledger":"3.3.0-snapshot.20250502.13767.0.v2fc6c7e2","@noble/curves":"^2.0.1","@noble/hashes":"^2.0.1"},"publishConfig":{"main":"./dist/index.js","types":"./dist/index.d.ts","access":"public","exports":{".":{"types":"./dist/index.d.ts","default":"./dist/index.js"}},"registry":"https://registry.npmjs.org"},"_hasShrinkwrap":false,"devDependencies":{"vitest":"^3.2.4","esbuild":"^0.27.0","typescript":"^5.8.3","@types/node":"^24.0.12","@types/debug":"^4","@canton/test-utils":"1.0.0"},"_npmOperationalInternal":{"tmp":"tmp/external-signing_0.2.1_1777167266768_0.021373826955125796","host":"s3://npm-registry-packages-npm-production"}},"0.2.2":{"name":"@0xsend/external-signing","version":"0.2.2","author":{"name":"Send Foundation"},"license":"MIT","_id":"@0xsend/external-signing@0.2.2","maintainers":[{"name":"eho-send","email":"eho@send.it"},{"name":"0xbigboss","email":"0xbigboss@proton.me"},{"name":"allen-send","email":"allen@send.it"}],"dist":{"shasum":"7a73131d1ec15127d720fba87ec16cd39a5a911a","tarball":"https://registry.npmjs.org/@0xsend/external-signing/-/external-signing-0.2.2.tgz","fileCount":19,"integrity":"sha512-4Hwy7Eg56rJb/OggUiC3gYAdTPPBc8s7QAAFx9ApPtOdzIa0Nw7K+VvTnz+iAO2BJui65ICNhVgDzi2Q35Y/wQ==","signatures":[{"sig":"MEUCIHiKu31TkcctpINpLHWJ/e0xkCgaXJHvaxB4+K030BBfAiEAqQxwQt5ZPpFVzYhJ8QvebYQslitHP8ibGXKlnKs2U1Q=","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":108056},"main":"src/index.ts","type":"module","types":"src/index.ts","exports":{".":"./src/index.ts"},"gitHead":"066ce0ee5bceba88046bda63e18866ab045953ff","scripts":{"lint":"tsc --noEmit","test":"vitest run","build":"node scripts/build.mjs","typecheck":"tsc --noEmit","test:watch":"vitest --watch","test:integration":"vitest run --config vitest.integration.config.ts"},"_npmUser":{"name":"GitHub Actions","email":"npm-oidc-no-reply@github.com","trustedPublisher":{"id":"github","oidcConfigId":"oidc:33c8c8d8-641e-40de-a462-720105ac73f0"}},"repository":{"url":"https://github.com/0xsend","type":"git"},"_npmVersion":"11.13.0","description":"TypeScript library for external signing on the Canton Network","directories":{},"_nodeVersion":"22.15.0","dependencies":{"zod":"^4.1.13","jose":"^6.0.11","debug":"^4.4.1","@daml/types":"3.3.0-snapshot.20250502.13767.0.v2fc6c7e2","@daml/ledger":"3.3.0-snapshot.20250502.13767.0.v2fc6c7e2","@noble/curves":"^2.0.1","@noble/hashes":"^2.0.1"},"publishConfig":{"main":"./dist/index.js","types":"./dist/index.d.ts","access":"public","exports":{".":{"types":"./dist/index.d.ts","default":"./dist/index.js"}},"registry":"https://registry.npmjs.org"},"_hasShrinkwrap":false,"devDependencies":{"vitest":"^3.2.4","esbuild":"^0.27.0","typescript":"^5.8.3","@types/node":"^24.0.12","@types/debug":"^4","@canton/test-utils":"1.0.0"},"_npmOperationalInternal":{"tmp":"tmp/external-signing_0.2.2_1777168189001_0.27623248556193025","host":"s3://npm-registry-packages-npm-production"}},"0.3.0":{"name":"@0xsend/external-signing","version":"0.3.0","author":{"name":"Send Foundation"},"license":"MIT","_id":"@0xsend/external-signing@0.3.0","maintainers":[{"name":"eho-send","email":"eho@send.it"},{"name":"0xbigboss","email":"0xbigboss@proton.me"},{"name":"allen-send","email":"allen@send.it"}],"dist":{"shasum":"e452da2aec9bf727201e3008132d48919edcfae5","tarball":"https://registry.npmjs.org/@0xsend/external-signing/-/external-signing-0.3.0.tgz","fileCount":27,"integrity":"sha512-T+tV2xBRptccquayTR/Tcv6gcp5IDO+BQuql+4QnOu8T3PF+rBsTUOPm4qKmkuUet3zCnN7XMAZK5mlUgcUI0Q==","signatures":[{"sig":"MEUCIQD7up7ZsMwj4+NsVpfW9rsRmhTGqYfZ1vPefJyUKg1RowIgRO3weyDjRzU0drPBbU6uYQMN9uRgckagPFhvatsehRk=","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":126111},"main":"src/index.ts","type":"module","types":"src/index.ts","exports":{".":"./src/index.ts","./node/keystore":"./src/node/keystore/index.ts"},"gitHead":"cc8104a303aa5f4f0a32546699bee4ae850ac6b6","scripts":{"lint":"tsc --noEmit","test":"vitest run","build":"node scripts/build.mjs","typecheck":"tsc --noEmit","test:watch":"vitest --watch","test:integration":"vitest run --config vitest.integration.config.ts"},"_npmUser":{"name":"GitHub Actions","email":"npm-oidc-no-reply@github.com","trustedPublisher":{"id":"github","oidcConfigId":"oidc:33c8c8d8-641e-40de-a462-720105ac73f0"}},"repository":{"url":"https://github.com/0xsend","type":"git"},"_npmVersion":"11.16.0","description":"TypeScript library for external signing on the Canton Network","directories":{},"_nodeVersion":"22.15.0","dependencies":{"zod":"^4.1.13","jose":"^6.0.11","debug":"^4.4.1","argon2":"^0.41.1","lodash":"^4.17.21","@daml/types":"3.3.0-snapshot.20250502.13767.0.v2fc6c7e2","@daml/ledger":"3.3.0-snapshot.20250502.13767.0.v2fc6c7e2","@noble/curves":"^2.0.1","@noble/hashes":"^2.0.1"},"publishConfig":{"main":"./dist/index.js","types":"./dist/index.d.ts","access":"public","exports":{".":{"types":"./dist/index.d.ts","default":"./dist/index.js"},"./node/keystore":{"types":"./dist/node/keystore/index.d.ts","default":"./dist/node/keystore/index.js"}},"registry":"https://registry.npmjs.org"},"_hasShrinkwrap":false,"devDependencies":{"vitest":"^3.2.4","esbuild":"^0.27.0","typescript":"^5.8.3","@types/node":"^24.0.12","@types/debug":"^4","@types/lodash":"^4.17.20","@canton/test-utils":"2.0.0"},"_npmOperationalInternal":{"tmp":"tmp/external-signing_0.3.0_1780085892498_0.1423878652093511","host":"s3://npm-registry-packages-npm-production"}},"0.4.0":{"name":"@0xsend/external-signing","version":"0.4.0","description":"TypeScript library for external signing on the Canton Network","type":"module","license":"MIT","author":{"name":"Send Foundation"},"repository":{"type":"git","url":"https://github.com/0xsend"},"main":"src/index.ts","types":"src/index.ts","exports":{".":"./src/index.ts","./node/keystore":"./src/node/keystore/index.ts"},"publishConfig":{"access":"public","registry":"https://registry.npmjs.org","main":"./dist/index.js","types":"./dist/index.d.ts","exports":{".":{"types":"./dist/index.d.ts","default":"./dist/index.js"},"./node/keystore":{"types":"./dist/node/keystore/index.d.ts","default":"./dist/node/keystore/index.js"}}},"scripts":{"build":"node scripts/build.mjs","lint":"tsc --noEmit","typecheck":"tsc --noEmit","test":"vitest run","test:integration":"vitest run --config vitest.integration.config.ts","test:watch":"vitest --watch"},"dependencies":{"@daml/ledger":"3.3.0-snapshot.20250502.13767.0.v2fc6c7e2","@daml/types":"3.3.0-snapshot.20250502.13767.0.v2fc6c7e2","@noble/curves":"^2.0.1","@noble/hashes":"^2.0.1","argon2":"^0.41.1","debug":"^4.4.1","jose":"^6.0.11","lodash":"^4.17.21","zod":"^4.1.13"},"devDependencies":{"@canton/test-utils":"3.0.0","@types/debug":"^4","@types/lodash":"^4.17.20","@types/node":"^24.0.12","esbuild":"^0.27.0","typescript":"^5.8.3","vitest":"^3.2.4"},"gitHead":"c1e5cc9b5d5de6eb54febdbc064a8d03a2152580","_id":"@0xsend/external-signing@0.4.0","_nodeVersion":"22.15.0","_npmVersion":"11.18.0","dist":{"integrity":"sha512-Y09CWIScvyY1IT7hW/v4zdLdm/FWQf7mq5lP5wPDZHU+vlJmvmzZNodsVGfyTp/lFYm5dI40H9Rl+zaMorEr7w==","shasum":"641a7215c7b060eae1284401e5ef6a6973065b25","tarball":"https://registry.npmjs.org/@0xsend/external-signing/-/external-signing-0.4.0.tgz","fileCount":27,"unpackedSize":127312,"signatures":[{"keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U","sig":"MEQCIHIjD1CYmkoC+ymZPQ9vfO+v+3KaOesgWGNya5gVpiSNAiATUuc64fpR6SisrKzp0qBtyuzjbc1GN1BmFvqh5h57UQ=="}]},"_npmUser":{"name":"GitHub Actions","email":"npm-oidc-no-reply@github.com","trustedPublisher":{"id":"github","oidcConfigId":"oidc:33c8c8d8-641e-40de-a462-720105ac73f0"}},"directories":{},"maintainers":[{"name":"eho-send","email":"eho@send.it"},{"name":"alleneubank","email":"adeubank@gmail.com"},{"name":"allen-send","email":"allen@send.it"}],"_npmOperationalInternal":{"host":"s3://npm-registry-packages-npm-production","tmp":"tmp/external-signing_0.4.0_1784304896750_0.4804812475198639"},"_hasShrinkwrap":false}},"time":{"created":"2026-04-25T23:42:22.836Z","modified":"2026-07-17T16:14:57.146Z","0.2.0":"2026-04-25T23:42:23.085Z","0.2.1":"2026-04-26T01:34:26.949Z","0.2.2":"2026-04-26T01:49:49.127Z","0.3.0":"2026-05-29T20:18:12.670Z","0.4.0":"2026-07-17T16:14:56.928Z"},"author":{"name":"Send Foundation"},"license":"MIT","repository":{"type":"git","url":"https://github.com/0xsend"},"description":"TypeScript library for external signing on the Canton Network","maintainers":[{"name":"eho-send","email":"eho@send.it"},{"name":"alleneubank","email":"adeubank@gmail.com"},{"name":"allen-send","email":"allen@send.it"}],"readme":"# Canton External Signing\n\nA TypeScript library for integrating external signing mechanisms with the Canton Network, enabling secure transaction signing using external key management systems, hardware security modules (HSMs), or software wallets.\n\n## Overview\n\nCanton External Signing provides a flexible framework for signing Canton Network transactions without exposing private keys to the application. This is essential for production deployments where key security is paramount.\n\n### Key Features\n\n- 🔐 **Secure Key Management** - Keep private keys in HSMs, secure enclaves, or external wallets\n- 🔑 **Multiple Signer Support** - Register and manage multiple signing identities\n- ⚡ **Async Signing** - Non-blocking signature operations with timeout support\n- 🛡️ **Type Safety** - Full TypeScript support with strict typing\n- 🧪 **Well Tested** - Comprehensive test suite included\n- 🔌 **Extensible** - Easy to implement custom signers\n- 📝 **Prepare/Submit Pattern** - Canton handles transaction building, client only signs\n\n## Architecture\n\n### Overall System Architecture\n\n```mermaid\ngraph TB\n    subgraph \"Browser/Client\"\n        WA[Web App]\n        ES[External Signer]\n        KS[Key Storage]\n    end\n\n    subgraph \"Canton Network\"\n        LED[Ledger API]\n        VAL[Validator]\n        SYNC[Synchronizer]\n    end\n\n    WA -->|1. Prepare Tx| LED\n    LED -->|2. Return Hash| WA\n    WA -->|3. Sign Hash| ES\n    ES -->|4. Access Key| KS\n    KS -->|5. Return Signature| ES\n    ES -->|6. Return Signature| WA\n    WA -->|7. Submit Signed Tx| LED\n    LED -->|8. Validate| VAL\n    VAL -->|9. Commit| SYNC\n```\n\n### Signing Flow Sequence\n\n```mermaid\nsequenceDiagram\n    participant User\n    participant WebApp\n    participant Signer\n    participant Canton\n\n    User->>WebApp: Initiate Transaction\n    WebApp->>Canton: Prepare Command\n    Canton->>WebApp: Return Tx Hash\n    WebApp->>Signer: Request Signature\n    Signer->>Signer: Generate Signature\n    Signer->>WebApp: Return Signature\n    WebApp->>Canton: Submit Signed Tx\n    Canton->>WebApp: Confirmation\n    WebApp->>User: Show Result\n```\n\n### Component Architecture\n\n```mermaid\ngraph LR\n    subgraph \"External Signing Package\"\n        API[API Client]\n        SM[Signer Manager]\n        CS[Crypto Services]\n        ST[Storage]\n    end\n\n    subgraph \"Signer Implementations\"\n        MEM[Memory Signer]\n        HSM[HSM Signer]\n        HW[Hardware Wallet]\n        ED[Ed25519 Signer]\n    end\n\n    API --> SM\n    SM --> CS\n    SM --> ST\n    CS --> MEM\n    CS --> HSM\n    CS --> HW\n    CS --> ED\n```\n\n## Installation\n\n```bash\n# Using yarn (recommended for canton-monorepo)\nyarn add @0xsend/external-signing\n\n# Using npm\nnpm install @0xsend/external-signing\n```\n\n## Quick Start\n\n### Basic Example\n\n```typescript\nimport {\n  CantonExternalClient,\n  InMemorySigner,\n  createTestSigner,\n} from \"@0xsend/external-signing\";\n\n// 1. Create a client instance\nconst client = new CantonExternalClient({\n  ledgerApiUrl: \"https://canton.example.com/\",\n  token: \"your-jwt-token\",\n});\n\n// 2. Create and register a signer\nconst signer = await createTestSigner(\"my-signer\", \"ES256\");\nclient.registerSigner(signer);\n\n// 3. Create a DAML command\nconst command = {\n  template: MyTemplate,\n  argument: {\n    owner: \"alice\",\n    value: 100,\n  },\n  party: \"alice::1234...\",\n};\n\n// 4. Submit with external signing\nconst result = await client.submitCreateCommand(command, \"my-signer\", {\n  verifySignature: true,\n});\n\nconsole.log(\"Transaction ID:\", result.transactionId);\n```\n\n### Using the Prepare/Submit Pattern\n\nFor more control over the signing process, use the prepare/submit pattern:\n\n```typescript\nimport { CantonExternalClient, Ed25519Signer } from \"@0xsend/external-signing\";\n\n// Create client with external party API\nconst client = new CantonExternalClient({\n  validatorUrl: \"https://validator.example.com/\",\n  authToken: \"your-auth-token\",\n});\n\n// Create an Ed25519 signer\nconst signer = new Ed25519Signer();\n\n// Create external party\nconst party = await client.createExternalParty(\"alice\", signer);\nconsole.log(\"Party ID:\", party.partyId);\n\n// Execute a transfer with prepare/submit\nconst transferParams = {\n  sender_party_id: party.partyId,\n  receiver_party_id: \"bob::5678...\",\n  amount: \"100.00\",\n  transfer_preapproval_contract_id: \"contract-id\",\n};\n\nconst result = await client.executeTransferPreapproval(transferParams, signer);\n```\n\n## API Reference\n\n### CantonExternalClient\n\nThe main client for interacting with Canton using external signers.\n\n```typescript\nclass CantonExternalClient {\n  constructor(config: CantonLedgerConfig);\n\n  // Signer management\n  registerSigner(signer: ExternalSigner): void;\n  unregisterSigner(signerId: string): boolean;\n  getSigner(signerId: string): ExternalSigner | undefined;\n  listSigners(): string[];\n\n  // Command submission\n  submitCreateCommand<T>(\n    command: DamlCommand<T>,\n    signerId: string,\n    options?: ExternalSigningOptions\n  ): Promise<CommandSubmissionResult>;\n\n  submitExerciseCommand<T, R>(\n    command: DamlExerciseCommand<T, R>,\n    signerId: string,\n    options?: ExternalSigningOptions\n  ): Promise<CommandSubmissionResult>;\n}\n```\n\n### ExternalSigner Interface\n\nAll signers must implement this interface:\n\n```typescript\ninterface ExternalSigner {\n  readonly id: string;\n\n  sign(data: Uint8Array): Promise<Signature>;\n  getPublicKeyHex(): Promise<string>;\n  getPublicKey(): Promise<Uint8Array>;\n\n  getSupportedAlgorithms?(): string[];\n}\n```\n\n### Built-in Signers\n\n#### InMemorySigner\n\nFor development and testing:\n\n```typescript\nconst signer = new InMemorySigner(\"signer-id\", \"ES256\");\nawait signer.initialize();\n```\n\n#### Ed25519Signer\n\nFor production use with Ed25519 keys:\n\n```typescript\nconst signer = new Ed25519Signer();\n// Or restore from private key\nconst signer = Ed25519Signer.fromPrivateKey(privateKeyHex);\n```\n\n#### MockHSMSigner\n\nSimulates HSM behavior for testing:\n\n```typescript\nconst signer = new MockHSMSigner(\n  \"hsm-signer\",\n  \"key-id\",\n  \"ES256\",\n  100 // simulated latency in ms\n);\nawait signer.initialize(\"PIN-CODE\");\n```\n\n## Core Concepts\n\n### Canton's Prepare/Submit Pattern\n\nCanton uses a server-side transaction preparation pattern:\n\n1. **Prepare**: Canton builds the transaction and returns a hash\n2. **Sign**: Client signs only the hash (no serialization needed)\n3. **Submit**: Client submits the signature back to Canton\n\n```typescript\n// Canton prepares the transaction\nconst prepared = await api.prepareTransferPreapproval(params);\n// prepared = { transaction: \"<base64>\", tx_hash: \"<hex>\" }\n\n// Client signs the hash\nconst signature = await signer.sign(hexToBytes(prepared.tx_hash));\n\n// Submit the signature\nawait api.submitTransferPreapproval({\n  party_id: params.sender_party_id,\n  transaction: prepared.transaction,\n  signed_tx_hash: signature.value,\n  public_key: publicKeyHex,\n});\n```\n\n### External Party Management\n\nThe library provides complete external party lifecycle management:\n\n```typescript\n// Create a new party with generated Ed25519 keys\nconst alice = await manager.createParty(\"alice\");\n\n// Import existing party from backup\nconst bob = await manager.importParty(\n  \"bob\",\n  privateKeyHex,\n  knownPartyId // optional\n);\n\n// List all managed parties\nconst parties = await manager.listParties();\n\n// Export private key for backup\nconst privateKey = await manager.exportPartyKey(alice.partyId);\n```\n\n## Integration Guide\n\n### Web Application Integration\n\n#### React Example\n\n```typescript\nimport { useState, useEffect } from \"react\";\nimport { CantonExternalClient, Ed25519Signer } from \"@0xsend/external-signing\";\n\nfunction WalletComponent() {\n  const [client, setClient] = useState<CantonExternalClient>();\n  const [signer, setSigner] = useState<Ed25519Signer>();\n\n  useEffect(() => {\n    // Initialize client\n    const client = new CantonExternalClient({\n      ledgerApiUrl: process.env.CANTON_LEDGER_URL,\n      token: localStorage.getItem(\"auth-token\"),\n    });\n\n    // Create or restore signer\n    const signer = new Ed25519Signer();\n    client.registerSigner(signer);\n\n    setClient(client);\n    setSigner(signer);\n  }, []);\n\n  const handleTransfer = async () => {\n    if (!client || !signer) return;\n\n    const command = {\n      template: TransferTemplate,\n      argument: {\n        /* ... */\n      },\n      party: getCurrentParty(),\n    };\n\n    try {\n      const result = await client.submitCreateCommand(command, signer.id);\n      console.log(\"Transfer successful:\", result.transactionId);\n    } catch (error) {\n      console.error(\"Transfer failed:\", error);\n    }\n  };\n\n  return <button onClick={handleTransfer}>Send Transfer</button>;\n}\n```\n\n#### Next.js Integration\n\n```typescript\n// app/lib/canton-client.ts\nimport { CantonExternalClient } from \"@0xsend/external-signing\";\n\nlet client: CantonExternalClient;\n\nexport function getCantonClient() {\n  if (!client) {\n    client = new CantonExternalClient({\n      ledgerApiUrl: process.env.NEXT_PUBLIC_CANTON_URL!,\n      // Token handled by middleware\n    });\n  }\n  return client;\n}\n\n// app/actions/transfer.ts\n(\"use server\");\n\nimport { getCantonClient } from \"@/lib/canton-client\";\nimport { Ed25519Signer } from \"@0xsend/external-signing\";\n\nexport async function executeTransfer(amount: string, recipient: string) {\n  const client = getCantonClient();\n  const signer = new Ed25519Signer();\n\n  // ... implement transfer logic\n}\n```\n\n### Custom Signer Implementation\n\nImplement your own signer for custom requirements:\n\n```typescript\nimport { ExternalSigner, Signature } from \"@0xsend/external-signing\";\n\nclass MyCustomSigner implements ExternalSigner {\n  readonly id: string;\n\n  constructor(id: string) {\n    this.id = id;\n  }\n\n  async sign(data: Uint8Array): Promise<Signature> {\n    // Your signing logic here\n    // e.g., call to HSM, hardware wallet, etc.\n\n    return {\n      algorithm: \"ES256\",\n      value: signatureHex,\n      publicKey: publicKeyHex,\n      keyId: this.id,\n    };\n  }\n\n  async getPublicKeyHex(): Promise<string> {\n    // Return hex-encoded public key\n  }\n\n  async getPublicKey(): Promise<Uint8Array> {\n    // Return raw public key bytes\n  }\n\n  getSupportedAlgorithms(): string[] {\n    return [\"ES256\", \"ES384\"];\n  }\n}\n```\n\n### Error Handling\n\nThe library provides specific error types for different scenarios:\n\n```typescript\nimport {\n  ExternalSigningError,\n  ExternalSigningErrorCode,\n} from \"@0xsend/external-signing\";\n\ntry {\n  await client.submitCreateCommand(command, signerId);\n} catch (error) {\n  if (error instanceof ExternalSigningError) {\n    switch (error.code) {\n      case ExternalSigningErrorCode.SIGNER_NOT_FOUND:\n        console.error(\"Signer not registered\");\n        break;\n      case ExternalSigningErrorCode.SIGNING_FAILED:\n        console.error(\"Signing operation failed\");\n        break;\n      case ExternalSigningErrorCode.TIMEOUT:\n        console.error(\"Signing timed out\");\n        break;\n      // ... handle other error codes\n    }\n  }\n}\n```\n\n## Authentication and API Requirements\n\n### Important: Admin Authentication Required\n\nExternal party management endpoints require **admin authentication**. Regular user tokens will receive 401 Unauthorized errors.\n\n#### JWT Token Requirements\n\nFor Canton localnet with JWKS authentication:\n\n```typescript\n// Get admin token from Keycloak using client credentials\nasync function getAdminToken(): Promise<string> {\n  const response = await fetch(\n    \"http://auth-dev.cantonwallet.com/realms/localnet/protocol/openid-connect/token\",\n    {\n      method: \"POST\",\n      headers: { \"Content-Type\": \"application/x-www-form-urlencoded\" },\n      body: new URLSearchParams({\n        grant_type: \"client_credentials\",\n        client_id: \"localnet-validator\",\n        client_secret: process.env.KEYCLOAK_CLIENT_SECRET!,\n        audience:\n          \"https://ledger-api.canton.local https://canton.network.global\",\n      }),\n    }\n  );\n\n  const data = await response.json();\n  return data.access_token;\n}\n\n// Use admin token for external party operations\nconst adminToken = await getAdminToken();\nconst manager = new ExternalPartyManager(\n  {\n    validatorUrl: \"http://localhost:45003\",\n    authToken: adminToken,\n  },\n  storage\n);\n```\n\n#### Legacy HMAC Authentication (Development Only)\n\nFor backwards compatibility with unsafe HMAC tokens:\n\n```typescript\nimport { SignJWT } from \"jose\";\n\n// Only for development environments with SPLICE_APP_UI_UNSAFE=true\nasync function generateUnsafeAdminToken(): Promise<string> {\n  const secret = new TextEncoder().encode(\"unsafe\");\n\n  const token = await new SignJWT({\n    sub: \"ledger-api-user\",\n    aud: [\"https://ledger-api.canton.local\", \"https://canton.network.global\"],\n    scope: \"daml_ledger_api\",\n  })\n    .setProtectedHeader({ alg: \"HS256\" })\n    .setIssuedAt()\n    .setExpirationTime(\"24h\")\n    .sign(secret);\n\n  return token;\n}\n```\n\n## Security Considerations\n\n### Best Practices\n\n1. **Never expose private keys** - Keep them in secure storage\n2. **Use hardware security** - HSMs or secure enclaves for production\n3. **Implement key rotation** - Regular key updates\n4. **Audit signing requests** - Log all signature operations\n5. **Validate inputs** - Always validate transaction data before signing\n\n### Production Checklist\n\n- [ ] Private keys stored in HSM or secure enclave\n- [ ] Signing operations require authentication\n- [ ] Transaction limits implemented\n- [ ] Audit logging enabled\n- [ ] Key backup and recovery procedures\n- [ ] Regular security audits\n\n## Testing\n\n### Running Tests\n\n```bash\n# Unit tests\nyarn test src/testxsend/external-signing.test.ts\n\n# Integration tests (requires Canton localnet)\nyarn test:integration\n\n# Watch mode\nyarn test:watch\n```\n\n### Writing Tests\n\n```typescript\nimport { createTestSigner } from \"@0xsend/external-signing\";\n\ndescribe(\"My Canton Integration\", () => {\n  it(\"should sign and submit transaction\", async () => {\n    const signer = await createTestSigner(\"test-signer\");\n    const client = new CantonExternalClient({\n      ledgerApiUrl: \"http://localhost:5001/\",\n    });\n\n    client.registerSigner(signer);\n\n    // ... test your integration\n  });\n});\n```\n\n## Troubleshooting\n\n### Common Issues\n\n**Ledger URL must end with '/'**\n\n```typescript\n// ❌ Wrong\nledgerApiUrl: \"http://localhost:5001\";\n\n// ✅ Correct\nledgerApiUrl: \"http://localhost:5001/\";\n```\n\n**Signer not found error**\n\n```typescript\n// Make sure to register the signer first\nclient.registerSigner(signer);\n```\n\n**Timeout errors**\n\n```typescript\n// Increase timeout for slow signers\nawait client.submitCreateCommand(command, signerId, {\n  signingTimeoutMs: 30000, // 30 seconds\n});\n```\n\n**401 Unauthorized for external party operations**\n\n```typescript\n// Use admin token with 'ledger-api-user' subject\nconst adminToken = await generateAdminToken();\n```\n\n## Development\n\n### Project Structure\n\n```\npackages/canton-external-signing/\n├── src/\n│   ├── api/              # Canton API integrations\n│   ├── client/           # Client implementations\n│   ├── party/            # External party management\n│   ├── signers/          # Signer implementations\n│   ├── storage/          # Storage interfaces\n│   ├── types/            # TypeScript types\n│   ├── utils/            # Utilities\n│   └── test/             # Test files\n├── README.md\n└── package.json\n```\n\n### Building\n\n```bash\n# Type checking\nyarn typecheck\n\n# Run linter\nyarn lint\n\n# Build (if needed)\nyarn build\n```\n\n## Contributing\n\n1. Fork the repository\n2. Create your feature branch (`git checkout -b feature/amazing-feature`)\n3. Commit your changes (`git commit -m 'Add amazing feature'`)\n4. Push to the branch (`git push origin feature/amazing-feature`)\n5. Open a Pull Request\n\n## License\n\nThis package is part of the Canton monorepo and follows the same license terms.\n\n## Support\n\nFor issues and questions:\n\n- GitHub Issues: [canton-monorepo/issues](https://github.com/canton-network/canton-monorepo/issues)\n- Documentation: [Canton Network Docs](https://docs.canton.network)\n- Canton Community: Join the discussions\n","readmeFilename":"README.md"}