@caypo/canton-sdk 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (44) hide show
  1. package/.turbo/turbo-build.log +26 -0
  2. package/.turbo/turbo-test.log +23 -0
  3. package/README.md +120 -0
  4. package/SPEC.md +223 -0
  5. package/dist/amount-L2SDLRZT.js +15 -0
  6. package/dist/amount-L2SDLRZT.js.map +1 -0
  7. package/dist/chunk-GSDB5FKZ.js +110 -0
  8. package/dist/chunk-GSDB5FKZ.js.map +1 -0
  9. package/dist/index.cjs +1158 -0
  10. package/dist/index.cjs.map +1 -0
  11. package/dist/index.d.cts +673 -0
  12. package/dist/index.d.ts +673 -0
  13. package/dist/index.js +986 -0
  14. package/dist/index.js.map +1 -0
  15. package/package.json +50 -0
  16. package/src/__tests__/agent.test.ts +217 -0
  17. package/src/__tests__/amount.test.ts +202 -0
  18. package/src/__tests__/client.test.ts +516 -0
  19. package/src/__tests__/e2e/canton-client.e2e.test.ts +190 -0
  20. package/src/__tests__/e2e/mpp-flow.e2e.test.ts +346 -0
  21. package/src/__tests__/e2e/setup.ts +112 -0
  22. package/src/__tests__/e2e/usdcx.e2e.test.ts +114 -0
  23. package/src/__tests__/keystore.test.ts +197 -0
  24. package/src/__tests__/pay-client.test.ts +257 -0
  25. package/src/__tests__/safeguards.test.ts +333 -0
  26. package/src/__tests__/usdcx.test.ts +374 -0
  27. package/src/accounts/checking.ts +118 -0
  28. package/src/agent.ts +132 -0
  29. package/src/canton/amount.ts +167 -0
  30. package/src/canton/client.ts +218 -0
  31. package/src/canton/errors.ts +45 -0
  32. package/src/canton/holdings.ts +90 -0
  33. package/src/canton/index.ts +51 -0
  34. package/src/canton/types.ts +214 -0
  35. package/src/canton/usdcx.ts +166 -0
  36. package/src/index.ts +97 -0
  37. package/src/mpp/pay-client.ts +170 -0
  38. package/src/safeguards/manager.ts +183 -0
  39. package/src/traffic/manager.ts +95 -0
  40. package/src/wallet/config.ts +88 -0
  41. package/src/wallet/keystore.ts +164 -0
  42. package/tsconfig.json +8 -0
  43. package/tsup.config.ts +9 -0
  44. package/vitest.config.ts +7 -0
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/index.ts","../src/canton/errors.ts","../src/canton/client.ts","../src/canton/holdings.ts","../src/canton/usdcx.ts","../src/wallet/keystore.ts","../src/wallet/config.ts","../src/safeguards/manager.ts","../src/accounts/checking.ts","../src/mpp/pay-client.ts","../src/traffic/manager.ts","../src/agent.ts"],"sourcesContent":["/**\n * @caypo/canton-sdk — Core SDK for Canton Network.\n * JSON Ledger API v2 client, USDCx operations, agent accounts.\n */\n\nexport { MPP_CANTON_VERSION } from \"@caypo/mpp-canton\";\n\nexport const CANTON_SDK_VERSION = \"0.1.0\";\n\nexport const DEFAULT_LEDGER_PORT = 7575;\n\n// Canton JSON Ledger API v2 client\nexport { CantonClient } from \"./canton/client.js\";\nexport { CantonApiError, CantonAuthError, CantonTimeoutError } from \"./canton/errors.js\";\nexport type {\n ActiveContract,\n ActiveContractsRequest,\n AnyPartyFilter,\n ArchivedEvent,\n CantonClientConfig,\n CantonErrorCode,\n Command,\n CreateCommand,\n CreatedEvent,\n EventFormat,\n ExerciseCommand,\n ExercisedEvent,\n FlatTransaction,\n IdentifierFilter,\n LedgerEndResponse,\n LedgerError,\n PartyDetails,\n PartyFilter,\n PartyLocalMetadata,\n QueryActiveContractsParams,\n SubmitAndWaitRequest,\n SubmitAndWaitResponse,\n SubmitParams,\n TransactionResponse,\n TransactionTree,\n TransactionTreeEvent,\n} from \"./canton/types.js\";\n\n// USDCx operations\nexport {\n USDCxService,\n USDCX_HOLDING_TEMPLATE_ID,\n USDCX_INSTRUMENT_ID,\n TRANSFER_FACTORY_TEMPLATE_ID,\n} from \"./canton/usdcx.js\";\nexport type { TransferParams, TransferResult } from \"./canton/usdcx.js\";\n\n// Amount utilities (string-based decimal arithmetic)\nexport {\n addAmounts,\n compareAmounts,\n isValidAmount,\n subtractAmounts,\n toCantonAmount,\n} from \"./canton/amount.js\";\n\n// Holding selection\nexport { InsufficientBalanceError, selectHoldings } from \"./canton/holdings.js\";\nexport type { HoldingSelection, USDCxHolding } from \"./canton/holdings.js\";\n\n// Wallet keystore\nexport { Keystore } from \"./wallet/keystore.js\";\nexport type { WalletData } from \"./wallet/keystore.js\";\n\n// Agent configuration\nexport { loadConfig, saveConfig, DEFAULT_CONFIG } from \"./wallet/config.js\";\nexport type {\n AgentConfig,\n TrafficConfig,\n SafeguardsConfig,\n MppConfig,\n} from \"./wallet/config.js\";\n\n// Safeguards\nexport { SafeguardManager } from \"./safeguards/manager.js\";\nexport type { SafeguardConfig, CheckResult } from \"./safeguards/manager.js\";\n\n// High-level agent\nexport { CantonAgent } from \"./agent.js\";\nexport type { CantonAgentConfig, WalletInfo } from \"./agent.js\";\n\n// Checking account\nexport { CheckingAccount } from \"./accounts/checking.js\";\nexport type { SendOptions, TransactionRecord } from \"./accounts/checking.js\";\n\n// Traffic manager\nexport { TrafficManager } from \"./traffic/manager.js\";\nexport type { TrafficBalance, AutoPurchaseConfig } from \"./traffic/manager.js\";\n\n// MPP pay client\nexport { MppPayClient, parseWwwAuthenticate } from \"./mpp/pay-client.js\";\nexport type { PayOptions, PayResult, PaymentChallenge } from \"./mpp/pay-client.js\";\n","/**\n * Canton SDK — Custom Error Classes\n */\n\nimport type { CantonErrorCode, LedgerError } from \"./types.js\";\n\nexport class CantonApiError extends Error {\n readonly code: CantonErrorCode;\n readonly ledgerCause: string;\n readonly grpcCodeValue: number;\n readonly errorCategory: number;\n readonly context: Record<string, string>;\n\n constructor(ledgerError: LedgerError) {\n super(`Canton API error [${ledgerError.code}]: ${ledgerError.cause}`);\n this.name = \"CantonApiError\";\n this.code = ledgerError.code;\n this.ledgerCause = ledgerError.cause;\n this.grpcCodeValue = ledgerError.grpcCodeValue;\n this.errorCategory = ledgerError.errorCategory;\n this.context = ledgerError.context;\n }\n}\n\nexport class CantonTimeoutError extends Error {\n readonly timeoutMs: number;\n readonly path: string;\n\n constructor(path: string, timeoutMs: number) {\n super(`Canton request to ${path} timed out after ${timeoutMs}ms`);\n this.name = \"CantonTimeoutError\";\n this.timeoutMs = timeoutMs;\n this.path = path;\n }\n}\n\nexport class CantonAuthError extends Error {\n readonly statusCode: number;\n\n constructor(statusCode: number, message?: string) {\n super(message ?? `Canton authentication failed (HTTP ${statusCode})`);\n this.name = \"CantonAuthError\";\n this.statusCode = statusCode;\n }\n}\n","/**\n * Canton JSON Ledger API v2 — Client\n *\n * All requests target config.ledgerUrl + path.\n * All requests include Authorization: Bearer <token>.\n * Errors are parsed into typed CantonApiError / CantonAuthError / CantonTimeoutError.\n */\n\nimport { CantonApiError, CantonAuthError, CantonTimeoutError } from \"./errors.js\";\nimport type {\n ActiveContract,\n CantonClientConfig,\n CreatedEvent,\n LedgerError,\n PartyDetails,\n QueryActiveContractsParams,\n SubmitAndWaitResponse,\n SubmitParams,\n TransactionResponse,\n TransactionTree,\n} from \"./types.js\";\n\nconst DEFAULT_TIMEOUT = 30_000;\n\nexport class CantonClient {\n private readonly ledgerUrl: string;\n private readonly token: string;\n private readonly userId: string;\n private readonly timeout: number;\n\n constructor(config: CantonClientConfig) {\n this.ledgerUrl = config.ledgerUrl.replace(/\\/+$/, \"\");\n this.token = config.token;\n this.userId = config.userId;\n this.timeout = config.timeout ?? DEFAULT_TIMEOUT;\n }\n\n // ---------------------------------------------------------------------------\n // Command Submission\n // ---------------------------------------------------------------------------\n\n async submitAndWait(params: SubmitParams): Promise<SubmitAndWaitResponse> {\n return this.request<SubmitAndWaitResponse>(\"POST\", \"/v2/commands/submit-and-wait\", {\n commands: params.commands,\n userId: this.userId,\n commandId: params.commandId,\n actAs: params.actAs,\n readAs: params.readAs,\n });\n }\n\n async submitAndWaitForTransaction(params: SubmitParams): Promise<TransactionResponse> {\n return this.request<TransactionResponse>(\n \"POST\",\n \"/v2/commands/submit-and-wait-for-transaction\",\n {\n commands: params.commands,\n userId: this.userId,\n commandId: params.commandId,\n actAs: params.actAs,\n readAs: params.readAs,\n },\n );\n }\n\n // ---------------------------------------------------------------------------\n // Active Contract Queries\n // ---------------------------------------------------------------------------\n\n async queryActiveContracts(params: QueryActiveContractsParams): Promise<ActiveContract[]> {\n const body = {\n eventFormat: {\n filtersByParty: params.filtersByParty,\n filtersForAnyParty: params.filtersForAnyParty,\n verbose: true,\n },\n activeAtOffset: params.activeAtOffset,\n };\n\n const response = await this.request<{\n contractEntry?: Array<{ createdEvent?: CreatedEvent }>;\n }>(\"POST\", \"/v2/state/active-contracts\", body);\n\n if (!response.contractEntry) {\n return [];\n }\n\n return response.contractEntry\n .filter((entry) => entry.createdEvent != null)\n .map((entry) => {\n const evt = entry.createdEvent!;\n return {\n contractId: evt.contractId,\n templateId: evt.templateId,\n createArgument: evt.createArgument,\n createdAt: \"\",\n signatories: evt.signatories,\n observers: evt.observers,\n };\n });\n }\n\n // ---------------------------------------------------------------------------\n // Transaction Lookup\n // ---------------------------------------------------------------------------\n\n async getTransactionById(updateId: string): Promise<TransactionTree | null> {\n try {\n return await this.request<TransactionTree>(\n \"GET\",\n `/v2/updates/transaction-by-id/${encodeURIComponent(updateId)}`,\n );\n } catch (err) {\n if (err instanceof CantonApiError && err.code === \"NOT_FOUND\") {\n return null;\n }\n throw err;\n }\n }\n\n // ---------------------------------------------------------------------------\n // Ledger State\n // ---------------------------------------------------------------------------\n\n async getLedgerEnd(): Promise<number> {\n const response = await this.request<{ offset: number }>(\"GET\", \"/v2/state/ledger-end\");\n return response.offset;\n }\n\n // ---------------------------------------------------------------------------\n // Party Management\n // ---------------------------------------------------------------------------\n\n async allocateParty(hint: string): Promise<PartyDetails> {\n const response = await this.request<{ partyDetails: PartyDetails }>(\"POST\", \"/v2/parties\", {\n partyIdHint: hint,\n identityProviderId: \"\",\n });\n return response.partyDetails;\n }\n\n async listParties(): Promise<PartyDetails[]> {\n const response = await this.request<{ partyDetails: PartyDetails[] }>(\"GET\", \"/v2/parties\");\n return response.partyDetails;\n }\n\n // ---------------------------------------------------------------------------\n // Health Check\n // ---------------------------------------------------------------------------\n\n async isHealthy(): Promise<boolean> {\n try {\n const response = await fetch(`${this.ledgerUrl}/livez`, {\n method: \"GET\",\n headers: { Authorization: `Bearer ${this.token}` },\n signal: AbortSignal.timeout(this.timeout),\n });\n return response.ok;\n } catch {\n return false;\n }\n }\n\n // ---------------------------------------------------------------------------\n // Internal\n // ---------------------------------------------------------------------------\n\n private async request<T>(method: string, path: string, body?: unknown): Promise<T> {\n const url = `${this.ledgerUrl}${path}`;\n\n const headers: Record<string, string> = {\n Authorization: `Bearer ${this.token}`,\n };\n\n if (body !== undefined) {\n headers[\"Content-Type\"] = \"application/json\";\n }\n\n let response: Response;\n try {\n response = await fetch(url, {\n method,\n headers,\n body: body !== undefined ? JSON.stringify(body) : undefined,\n signal: AbortSignal.timeout(this.timeout),\n });\n } catch (err: unknown) {\n if (err instanceof DOMException && err.name === \"TimeoutError\") {\n throw new CantonTimeoutError(path, this.timeout);\n }\n if (err instanceof DOMException && err.name === \"AbortError\") {\n throw new CantonTimeoutError(path, this.timeout);\n }\n throw err;\n }\n\n if (response.status === 401 || response.status === 403) {\n const text = await response.text().catch(() => \"\");\n throw new CantonAuthError(response.status, text || undefined);\n }\n\n if (!response.ok) {\n const errorBody = await response.json().catch(() => null);\n if (errorBody && typeof errorBody === \"object\" && \"code\" in errorBody) {\n throw new CantonApiError(errorBody as LedgerError);\n }\n throw new Error(`Canton API error: HTTP ${response.status} on ${method} ${path}`);\n }\n\n // Some endpoints (like livez) may return empty body\n const text = await response.text();\n if (!text) {\n return {} as T;\n }\n\n return JSON.parse(text) as T;\n }\n}\n","/**\n * Holding selection algorithm for USDCx transfers.\n *\n * Canton uses a UTXO-like model: a party can have multiple Holding contracts.\n * When transferring, we need to select holdings that cover the required amount.\n */\n\nimport { addAmounts, compareAmounts } from \"./amount.js\";\n\nexport interface USDCxHolding {\n contractId: string;\n owner: string;\n amount: string;\n templateId: string;\n}\n\nexport interface HoldingSelection {\n type: \"single\" | \"merge-then-transfer\";\n contractIds: string[];\n}\n\nexport class InsufficientBalanceError extends Error {\n readonly available: string;\n readonly required: string;\n\n constructor(available: string, required: string) {\n super(`Insufficient balance: have ${available}, need ${required}`);\n this.name = \"InsufficientBalanceError\";\n this.available = available;\n this.required = required;\n }\n}\n\n/**\n * Select holdings that cover the required amount.\n *\n * Strategy:\n * 1. Sort holdings by amount descending.\n * 2. Look for a single holding >= required (prefer smallest sufficient one).\n * 3. If none, accumulate multiple holdings until we cover the amount.\n * 4. Throw InsufficientBalanceError if total < required.\n */\nexport function selectHoldings(\n holdings: USDCxHolding[],\n requiredAmount: string,\n): HoldingSelection {\n if (holdings.length === 0) {\n throw new InsufficientBalanceError(\"0\", requiredAmount);\n }\n\n // Sort descending by amount\n const sorted = [...holdings].sort((a, b) => compareAmounts(b.amount, a.amount));\n\n // 1. Try to find a single holding that covers the amount.\n // Among all sufficient holdings, pick the smallest (best fit).\n let bestSingle: USDCxHolding | null = null;\n for (const h of sorted) {\n if (compareAmounts(h.amount, requiredAmount) >= 0) {\n // This one is sufficient — it might be the best fit\n // since we iterate descending, each subsequent one is smaller but still sufficient\n bestSingle = h;\n }\n }\n\n if (bestSingle) {\n return {\n type: \"single\",\n contractIds: [bestSingle.contractId],\n };\n }\n\n // 2. Accumulate multiple holdings (greedy — take largest first)\n let accumulated = \"0\";\n const selected: string[] = [];\n\n for (const h of sorted) {\n selected.push(h.contractId);\n accumulated = addAmounts(accumulated, h.amount);\n\n if (compareAmounts(accumulated, requiredAmount) >= 0) {\n return {\n type: \"merge-then-transfer\",\n contractIds: selected,\n };\n }\n }\n\n // 3. Not enough\n throw new InsufficientBalanceError(accumulated, requiredAmount);\n}\n","/**\n * USDCx Operations — Query holdings, transfer, merge.\n *\n * Uses CIP-56 token standard:\n * - Splice.Api.Token.HoldingV1:Holding for balance queries\n * - TransferFactory_Transfer for 1-step transfers (requires TransferPreapproval)\n */\n\nimport { addAmounts, toCantonAmount } from \"./amount.js\";\nimport type { CantonClient } from \"./client.js\";\nimport { selectHoldings, type HoldingSelection, type USDCxHolding } from \"./holdings.js\";\nimport type { SubmitAndWaitResponse } from \"./types.js\";\n\n/** CIP-56 Holding template ID — used for active contract queries */\nexport const USDCX_HOLDING_TEMPLATE_ID = \"Splice.Api.Token.HoldingV1:Holding\";\n\n/** TransferFactory template ID for 1-step transfers */\nexport const TRANSFER_FACTORY_TEMPLATE_ID = \"Splice.Api.Token.TransferFactoryV1:TransferFactory\";\n\n/** USDCx instrument identifier */\nexport const USDCX_INSTRUMENT_ID = \"USDCx\";\n\nexport interface TransferResult {\n updateId: string;\n completionOffset: number;\n commandId: string;\n}\n\nexport interface TransferParams {\n recipient: string;\n amount: string;\n commandId?: string;\n}\n\nexport class USDCxService {\n constructor(\n private readonly client: CantonClient,\n private readonly partyId: string,\n ) {}\n\n /**\n * Query all USDCx Holding contracts for this party.\n */\n async getHoldings(): Promise<USDCxHolding[]> {\n const offset = await this.client.getLedgerEnd();\n\n const contracts = await this.client.queryActiveContracts({\n filtersByParty: {\n [this.partyId]: {\n cumulative: [\n {\n identifierFilter: {\n TemplateFilter: {\n value: { templateId: USDCX_HOLDING_TEMPLATE_ID },\n },\n },\n },\n ],\n },\n },\n activeAtOffset: offset,\n });\n\n return contracts.map((c) => ({\n contractId: c.contractId,\n owner: (c.createArgument.owner as string) ?? this.partyId,\n amount: c.createArgument.amount as string,\n templateId: c.templateId,\n }));\n }\n\n /**\n * Calculate total USDCx balance by summing all holding amounts.\n * Returns a string with up to 10 decimal places.\n */\n async getBalance(): Promise<string> {\n const holdings = await this.getHoldings();\n\n if (holdings.length === 0) {\n return \"0\";\n }\n\n let total = \"0\";\n for (const h of holdings) {\n total = addAmounts(total, h.amount);\n }\n\n return total;\n }\n\n /**\n * Transfer USDCx using TransferFactory_Transfer (1-step).\n * Requires the recipient to have an active TransferPreapproval.\n */\n async transfer(params: TransferParams): Promise<TransferResult> {\n const commandId = params.commandId ?? crypto.randomUUID();\n const amount = toCantonAmount(params.amount);\n\n // 1. Query holdings\n const holdings = await this.getHoldings();\n\n // 2. Select holdings covering the amount (throws InsufficientBalanceError if not enough)\n const selection: HoldingSelection = selectHoldings(holdings, params.amount);\n\n // 3. Build and submit ExerciseCommand for TransferFactory_Transfer\n const result: SubmitAndWaitResponse = await this.client.submitAndWait({\n commands: [\n {\n ExerciseCommand: {\n templateId: TRANSFER_FACTORY_TEMPLATE_ID,\n contractId: selection.contractIds[0],\n choice: \"TransferFactory_Transfer\",\n choiceArgument: {\n sender: this.partyId,\n receiver: params.recipient,\n amount,\n instrumentId: USDCX_INSTRUMENT_ID,\n inputHoldingCids: selection.contractIds,\n meta: {},\n },\n },\n },\n ],\n commandId,\n actAs: [this.partyId],\n readAs: [this.partyId],\n });\n\n return {\n updateId: result.updateId,\n completionOffset: result.completionOffset,\n commandId,\n };\n }\n\n /**\n * Merge multiple holdings into fewer UTXOs.\n * Returns the commandId of the merge transaction.\n */\n async mergeHoldings(holdingCids: string[]): Promise<string> {\n if (holdingCids.length < 2) {\n throw new Error(\"Need at least 2 holdings to merge\");\n }\n\n const commandId = crypto.randomUUID();\n\n await this.client.submitAndWait({\n commands: [\n {\n ExerciseCommand: {\n templateId: USDCX_HOLDING_TEMPLATE_ID,\n contractId: holdingCids[0],\n choice: \"Merge\",\n choiceArgument: {\n holdingCids: holdingCids.slice(1),\n },\n },\n },\n ],\n commandId,\n actAs: [this.partyId],\n });\n\n return commandId;\n }\n}\n","/**\n * Wallet keystore — AES-256-GCM encrypted storage for Canton agent credentials.\n *\n * Storage format (JSON, base64-encoded fields):\n * { iv, salt, encrypted, tag }\n *\n * Key derivation: PIN → PBKDF2 (100k iterations, SHA-256) → 32-byte AES key.\n * Uses Node.js built-in crypto module only.\n */\n\nimport { createCipheriv, createDecipheriv, pbkdf2Sync, randomBytes } from \"node:crypto\";\nimport { mkdir, readFile, writeFile } from \"node:fs/promises\";\nimport { dirname, join } from \"node:path\";\nimport { homedir } from \"node:os\";\n\nconst PBKDF2_ITERATIONS = 100_000;\nconst PBKDF2_KEYLEN = 32;\nconst PBKDF2_DIGEST = \"sha256\";\nconst SALT_LEN = 32;\nconst IV_LEN = 16;\nconst ALGORITHM = \"aes-256-gcm\";\n\nconst DEFAULT_WALLET_PATH = join(homedir(), \".caypo\", \"wallet.key\");\n\nexport interface WalletData {\n partyId: string;\n jwt: string;\n userId: string;\n privateKey: string;\n}\n\ninterface EncryptedFile {\n iv: string;\n salt: string;\n encrypted: string;\n tag: string;\n}\n\nfunction deriveKey(pin: string, salt: Buffer): Buffer {\n return pbkdf2Sync(pin, salt, PBKDF2_ITERATIONS, PBKDF2_KEYLEN, PBKDF2_DIGEST);\n}\n\nfunction encrypt(data: string, pin: string): EncryptedFile {\n const salt = randomBytes(SALT_LEN);\n const key = deriveKey(pin, salt);\n const iv = randomBytes(IV_LEN);\n\n const cipher = createCipheriv(ALGORITHM, key, iv);\n const encrypted = Buffer.concat([cipher.update(data, \"utf8\"), cipher.final()]);\n const tag = cipher.getAuthTag();\n\n return {\n iv: iv.toString(\"base64\"),\n salt: salt.toString(\"base64\"),\n encrypted: encrypted.toString(\"base64\"),\n tag: tag.toString(\"base64\"),\n };\n}\n\nfunction decrypt(file: EncryptedFile, pin: string): string {\n const salt = Buffer.from(file.salt, \"base64\");\n const iv = Buffer.from(file.iv, \"base64\");\n const encrypted = Buffer.from(file.encrypted, \"base64\");\n const tag = Buffer.from(file.tag, \"base64\");\n\n const key = deriveKey(pin, salt);\n\n const decipher = createDecipheriv(ALGORITHM, key, iv);\n decipher.setAuthTag(tag);\n\n try {\n const decrypted = Buffer.concat([decipher.update(encrypted), decipher.final()]);\n return decrypted.toString(\"utf8\");\n } catch {\n throw new Error(\"Invalid PIN or corrupted wallet file\");\n }\n}\n\nexport class Keystore {\n private data: WalletData;\n private filePath: string;\n\n private constructor(data: WalletData, filePath: string) {\n this.data = data;\n this.filePath = filePath;\n }\n\n /** Party ID of this wallet. */\n get address(): string {\n return this.data.partyId;\n }\n\n /**\n * Create a new encrypted wallet.\n * Generates a random 32-byte private key and saves encrypted to disk.\n */\n static async create(\n pin: string,\n params: { partyId: string; jwt: string; userId: string },\n path?: string,\n ): Promise<Keystore> {\n const filePath = path ?? DEFAULT_WALLET_PATH;\n\n const walletData: WalletData = {\n partyId: params.partyId,\n jwt: params.jwt,\n userId: params.userId,\n privateKey: randomBytes(32).toString(\"hex\"),\n };\n\n const encryptedFile = encrypt(JSON.stringify(walletData), pin);\n\n await mkdir(dirname(filePath), { recursive: true });\n await writeFile(filePath, JSON.stringify(encryptedFile), \"utf8\");\n\n return new Keystore(walletData, filePath);\n }\n\n /**\n * Load and decrypt an existing wallet.\n * Throws if the PIN is wrong or the file is corrupted.\n */\n static async load(pin: string, path?: string): Promise<Keystore> {\n const filePath = path ?? DEFAULT_WALLET_PATH;\n\n const raw = await readFile(filePath, \"utf8\");\n const encryptedFile: EncryptedFile = JSON.parse(raw);\n\n const decrypted = decrypt(encryptedFile, pin);\n const walletData: WalletData = JSON.parse(decrypted);\n\n return new Keystore(walletData, filePath);\n }\n\n /** Get credentials for Canton Ledger API access. */\n getCredentials(): { partyId: string; jwt: string; userId: string } {\n return {\n partyId: this.data.partyId,\n jwt: this.data.jwt,\n userId: this.data.userId,\n };\n }\n\n /** Change the encryption PIN. Re-encrypts wallet data with new PIN. */\n async changePin(oldPin: string, newPin: string): Promise<void> {\n // Verify old PIN by re-loading\n const raw = await readFile(this.filePath, \"utf8\");\n const encryptedFile: EncryptedFile = JSON.parse(raw);\n decrypt(encryptedFile, oldPin); // throws if wrong\n\n // Re-encrypt with new PIN\n const newEncrypted = encrypt(JSON.stringify(this.data), newPin);\n await writeFile(this.filePath, JSON.stringify(newEncrypted), \"utf8\");\n }\n\n /** Export the raw private key. Dangerous — only call with explicit user consent. */\n exportKey(pin: string): string {\n // We don't re-verify PIN against file here since the Keystore is already decrypted.\n // The pin parameter acts as a confirmation gate — callers should verify it matches.\n // For extra safety, we could re-read the file, but that's an IO overhead for a rare op.\n void pin;\n return this.data.privateKey;\n }\n}\n","/**\n * Agent configuration — load/save ~/.caypo/config.json\n */\n\nimport { mkdir, readFile, writeFile } from \"node:fs/promises\";\nimport { dirname, join } from \"node:path\";\nimport { homedir } from \"node:os\";\n\nconst DEFAULT_CONFIG_PATH = join(homedir(), \".caypo\", \"config.json\");\n\nexport interface TrafficConfig {\n autoPurchase: boolean;\n minBalance: number;\n purchaseAmountCC: string;\n}\n\nexport interface SafeguardsConfig {\n txLimit: string;\n dailyLimit: string;\n}\n\nexport interface MppConfig {\n gatewayUrl: string;\n maxAutoPayPrice: string;\n}\n\nexport interface AgentConfig {\n version: number;\n network: \"mainnet\" | \"testnet\" | \"devnet\";\n ledgerUrl: string;\n partyId: string;\n userId: string;\n keystorePath: string;\n traffic: TrafficConfig;\n safeguards: SafeguardsConfig;\n mpp: MppConfig;\n}\n\nexport const DEFAULT_CONFIG: AgentConfig = {\n version: 2,\n network: \"testnet\",\n ledgerUrl: \"http://localhost:7575\",\n partyId: \"\",\n userId: \"ledger-api-user\",\n keystorePath: join(homedir(), \".caypo\", \"wallet.key\"),\n traffic: {\n autoPurchase: true,\n minBalance: 1000,\n purchaseAmountCC: \"5.0\",\n },\n safeguards: {\n txLimit: \"100\",\n dailyLimit: \"1000\",\n },\n mpp: {\n gatewayUrl: \"https://mpp.cayvox.io\",\n maxAutoPayPrice: \"1.00\",\n },\n};\n\n/**\n * Load agent configuration from disk.\n * Returns DEFAULT_CONFIG merged with file contents.\n */\nexport async function loadConfig(path?: string): Promise<AgentConfig> {\n const filePath = path ?? DEFAULT_CONFIG_PATH;\n\n try {\n const raw = await readFile(filePath, \"utf8\");\n const parsed = JSON.parse(raw) as Partial<AgentConfig>;\n return { ...DEFAULT_CONFIG, ...parsed };\n } catch (err: unknown) {\n if ((err as NodeJS.ErrnoException).code === \"ENOENT\") {\n return { ...DEFAULT_CONFIG };\n }\n throw err;\n }\n}\n\n/**\n * Save agent configuration to disk.\n * Creates parent directory if it doesn't exist.\n */\nexport async function saveConfig(config: AgentConfig, path?: string): Promise<void> {\n const filePath = path ?? DEFAULT_CONFIG_PATH;\n await mkdir(dirname(filePath), { recursive: true });\n await writeFile(filePath, JSON.stringify(config, null, 2), \"utf8\");\n}\n","/**\n * SafeguardManager — pre-transaction checks: tx limit, daily limit, lock.\n *\n * Storage: ~/.caypo/safeguards.json\n * All amounts are strings (no floating point).\n */\n\nimport { mkdir, readFile, writeFile } from \"node:fs/promises\";\nimport { dirname, join } from \"node:path\";\nimport { homedir } from \"node:os\";\nimport { addAmounts, compareAmounts, subtractAmounts } from \"../canton/amount.js\";\n\nconst DEFAULT_SAFEGUARDS_PATH = join(homedir(), \".caypo\", \"safeguards.json\");\n\nexport interface SafeguardConfig {\n txLimit: string;\n dailyLimit: string;\n locked: boolean;\n lockedPinHash: string;\n dailySpent: string;\n lastResetDate: string; // YYYY-MM-DD\n}\n\nexport interface CheckResult {\n allowed: boolean;\n reason?: string;\n dailyRemaining: string;\n}\n\nconst DEFAULT_SAFEGUARD_CONFIG: SafeguardConfig = {\n txLimit: \"100\",\n dailyLimit: \"1000\",\n locked: false,\n lockedPinHash: \"\",\n dailySpent: \"0\",\n lastResetDate: today(),\n};\n\nfunction today(): string {\n return new Date().toISOString().slice(0, 10);\n}\n\n/** Simple hash for PIN verification (not cryptographic — just a gate). */\nfunction hashPin(pin: string): string {\n // Use a basic hash since this is just a confirmation gate, not security-critical.\n // The real security is the encrypted keystore.\n let hash = 0;\n for (let i = 0; i < pin.length; i++) {\n const ch = pin.charCodeAt(i);\n hash = ((hash << 5) - hash + ch) | 0;\n }\n return String(hash);\n}\n\nexport class SafeguardManager {\n private config: SafeguardConfig;\n private readonly filePath: string;\n\n constructor(config?: SafeguardConfig, filePath?: string) {\n this.config = config ? { ...config } : { ...DEFAULT_SAFEGUARD_CONFIG };\n this.filePath = filePath ?? DEFAULT_SAFEGUARDS_PATH;\n }\n\n /**\n * Load safeguards from disk. Returns a new SafeguardManager.\n * If file doesn't exist, uses defaults.\n */\n static async load(path?: string): Promise<SafeguardManager> {\n const filePath = path ?? DEFAULT_SAFEGUARDS_PATH;\n\n try {\n const raw = await readFile(filePath, \"utf8\");\n const parsed = JSON.parse(raw) as Partial<SafeguardConfig>;\n const config: SafeguardConfig = { ...DEFAULT_SAFEGUARD_CONFIG, ...parsed };\n return new SafeguardManager(config, filePath);\n } catch (err: unknown) {\n if ((err as NodeJS.ErrnoException).code === \"ENOENT\") {\n return new SafeguardManager(undefined, filePath);\n }\n throw err;\n }\n }\n\n /** Get current safeguard settings. */\n settings(): SafeguardConfig {\n return { ...this.config };\n }\n\n /** Set per-transaction limit. */\n setTxLimit(amount: string): void {\n this.config.txLimit = amount;\n void this.save();\n }\n\n /** Set daily spending limit. */\n setDailyLimit(amount: string): void {\n this.config.dailyLimit = amount;\n void this.save();\n }\n\n /** Lock the wallet. All transactions will be rejected until unlocked. */\n lock(pin?: string): void {\n this.config.locked = true;\n if (pin) {\n this.config.lockedPinHash = hashPin(pin);\n }\n void this.save();\n }\n\n /** Unlock the wallet. Requires PIN if one was set during lock. */\n unlock(pin: string): void {\n if (this.config.lockedPinHash && hashPin(pin) !== this.config.lockedPinHash) {\n throw new Error(\"Invalid PIN\");\n }\n this.config.locked = false;\n this.config.lockedPinHash = \"\";\n void this.save();\n }\n\n /**\n * Check if a transaction for the given amount is allowed.\n * Auto-resets daily counter if the date has changed.\n */\n check(amount: string): CheckResult {\n this.autoResetDaily();\n\n const dailyRemaining = subtractAmounts(this.config.dailyLimit, this.config.dailySpent);\n\n if (this.config.locked) {\n return { allowed: false, reason: \"Wallet is locked\", dailyRemaining };\n }\n\n if (compareAmounts(amount, this.config.txLimit) > 0) {\n return {\n allowed: false,\n reason: `Amount ${amount} exceeds per-transaction limit of ${this.config.txLimit}`,\n dailyRemaining,\n };\n }\n\n const projectedDaily = addAmounts(this.config.dailySpent, amount);\n if (compareAmounts(projectedDaily, this.config.dailyLimit) > 0) {\n return {\n allowed: false,\n reason: `Amount ${amount} would exceed daily limit of ${this.config.dailyLimit} (spent: ${this.config.dailySpent})`,\n dailyRemaining,\n };\n }\n\n return { allowed: true, dailyRemaining };\n }\n\n /** Record a completed spend. Call after successful transaction. */\n recordSpend(amount: string): void {\n this.autoResetDaily();\n this.config.dailySpent = addAmounts(this.config.dailySpent, amount);\n void this.save();\n }\n\n /** Manually reset the daily counter. */\n resetDaily(): void {\n this.config.dailySpent = \"0\";\n this.config.lastResetDate = today();\n void this.save();\n }\n\n private autoResetDaily(): void {\n const currentDate = today();\n if (this.config.lastResetDate !== currentDate) {\n this.config.dailySpent = \"0\";\n this.config.lastResetDate = currentDate;\n }\n }\n\n private async save(): Promise<void> {\n try {\n await mkdir(dirname(this.filePath), { recursive: true });\n await writeFile(this.filePath, JSON.stringify(this.config, null, 2), \"utf8\");\n } catch {\n // Best-effort save — in-memory state is still authoritative\n }\n }\n}\n","/**\n * CheckingAccount — high-level USDCx checking account.\n *\n * Wraps USDCxService with safeguard checks and transaction history.\n */\n\nimport type { CantonClient } from \"../canton/client.js\";\nimport type { USDCxService } from \"../canton/usdcx.js\";\nimport type { TransferResult } from \"../canton/usdcx.js\";\nimport type { SafeguardManager } from \"../safeguards/manager.js\";\n\nexport interface SendOptions {\n memo?: string;\n commandId?: string;\n}\n\nexport interface TransactionRecord {\n updateId: string;\n commandId: string;\n effectiveAt: string;\n offset: number;\n type: \"send\" | \"receive\" | \"unknown\";\n amount?: string;\n counterparty?: string;\n}\n\nexport class CheckingAccount {\n constructor(\n private readonly usdcx: USDCxService,\n private readonly safeguards: SafeguardManager,\n private readonly client: CantonClient,\n private readonly partyId: string,\n ) {}\n\n /** Get USDCx balance: total available and number of UTXO holdings. */\n async balance(): Promise<{ available: string; holdingCount: number }> {\n const holdings = await this.usdcx.getHoldings();\n let available = \"0\";\n\n // Use addAmounts from the holdings themselves\n const { addAmounts } = await import(\"../canton/amount.js\");\n for (const h of holdings) {\n available = addAmounts(available, h.amount);\n }\n\n return { available, holdingCount: holdings.length };\n }\n\n /**\n * Send USDCx to a recipient.\n * Checks safeguards before executing the transfer.\n */\n async send(\n recipient: string,\n amount: string,\n opts?: SendOptions,\n ): Promise<TransferResult> {\n // 1. Check safeguards\n const check = this.safeguards.check(amount);\n if (!check.allowed) {\n throw new Error(`Safeguard rejected: ${check.reason}`);\n }\n\n // 2. Execute transfer\n const result = await this.usdcx.transfer({\n recipient,\n amount,\n commandId: opts?.commandId,\n });\n\n // 3. Record spend\n this.safeguards.recordSpend(amount);\n\n return result;\n }\n\n /** Party ID for receiving payments. */\n address(): string {\n return this.partyId;\n }\n\n /**\n * Query transaction history via /v2/updates/flats.\n * Returns recent flat transactions involving this party.\n */\n async history(opts?: { limit?: number }): Promise<TransactionRecord[]> {\n const limit = opts?.limit ?? 20;\n const offset = await this.client.getLedgerEnd();\n\n // Query recent transactions using active contracts approach\n // Note: full streaming history requires WebSocket /v2/updates/flats\n // For now, return what we can derive from recent contract state\n const contracts = await this.client.queryActiveContracts({\n filtersByParty: {\n [this.partyId]: {\n cumulative: [\n {\n identifierFilter: {\n WildcardFilter: { value: { includeCreatedEventBlob: false } },\n },\n },\n ],\n },\n },\n activeAtOffset: offset,\n });\n\n // Map contracts to transaction records (limited view — full history needs WebSocket streaming)\n return contracts.slice(0, limit).map((c) => ({\n updateId: \"\",\n commandId: \"\",\n effectiveAt: c.createdAt,\n offset: 0,\n type: \"unknown\" as const,\n amount: typeof c.createArgument.amount === \"string\" ? c.createArgument.amount : undefined,\n }));\n }\n}\n","/**\n * MppPayClient — automatic HTTP 402 payment handling.\n *\n * Flow:\n * 1. Fetch URL\n * 2. If 402, parse WWW-Authenticate header for canton payment challenge\n * 3. Check maxPrice against challenge amount\n * 4. Check safeguards\n * 5. Execute USDCx transfer via TransferFactory_Transfer\n * 6. Build credential (base64-encoded payload)\n * 7. Retry request with Authorization: Payment <credential>\n * 8. Return { response, receipt?, paid }\n */\n\nimport { compareAmounts } from \"../canton/amount.js\";\nimport type { USDCxService } from \"../canton/usdcx.js\";\nimport { toCantonAmount } from \"../canton/amount.js\";\nimport type { SafeguardManager } from \"../safeguards/manager.js\";\n\nexport interface PayOptions {\n method?: string;\n headers?: Record<string, string>;\n body?: string;\n maxPrice?: string;\n}\n\nexport interface PayResult {\n response: Response;\n paid: boolean;\n receipt?: {\n updateId: string;\n completionOffset: number;\n commandId: string;\n amount: string;\n };\n}\n\nexport interface PaymentChallenge {\n amount: string;\n currency: string;\n recipient: string;\n network: string;\n description?: string;\n}\n\n/**\n * Parse a WWW-Authenticate header for Canton payment parameters.\n *\n * Expected format:\n * Payment method=\"canton\", amount=\"0.01\", currency=\"USDCx\",\n * recipient=\"Gateway::1220...\", network=\"mainnet\"\n */\nexport function parseWwwAuthenticate(header: string): PaymentChallenge | null {\n if (!header.startsWith(\"Payment\")) {\n return null;\n }\n\n const params: Record<string, string> = {};\n const re = /(\\w+)=\"([^\"]*)\"/g;\n let match;\n while ((match = re.exec(header)) !== null) {\n params[match[1]] = match[2];\n }\n\n if (params.method !== \"canton\" || !params.amount || !params.recipient || !params.network) {\n return null;\n }\n\n return {\n amount: params.amount,\n currency: params.currency ?? \"USDCx\",\n recipient: params.recipient,\n network: params.network,\n description: params.description,\n };\n}\n\nexport class MppPayClient {\n constructor(\n private readonly usdcx: USDCxService,\n private readonly safeguards: SafeguardManager,\n private readonly partyId: string,\n private readonly network: string,\n ) {}\n\n /**\n * Pay for an API call via MPP 402 flow.\n * If the response is not 402, returns it as-is with paid=false.\n */\n async pay(url: string, opts?: PayOptions): Promise<PayResult> {\n const requestInit: RequestInit = {\n method: opts?.method ?? \"GET\",\n headers: opts?.headers,\n body: opts?.body,\n };\n\n // 1. Initial request\n const firstResponse = await fetch(url, requestInit);\n\n if (firstResponse.status !== 402) {\n return { response: firstResponse, paid: false };\n }\n\n // 2. Parse 402 challenge\n const authHeader = firstResponse.headers.get(\"WWW-Authenticate\") ?? \"\";\n const challenge = parseWwwAuthenticate(authHeader);\n\n if (!challenge) {\n throw new Error(\"402 received but no valid Canton payment challenge in WWW-Authenticate\");\n }\n\n // 3. Network check\n if (challenge.network !== this.network) {\n throw new Error(\n `Network mismatch: challenge requires ${challenge.network}, agent on ${this.network}`,\n );\n }\n\n // 4. Price check\n if (opts?.maxPrice && compareAmounts(challenge.amount, opts.maxPrice) > 0) {\n throw new Error(\n `Price ${challenge.amount} exceeds maxPrice ${opts.maxPrice}`,\n );\n }\n\n // 5. Safeguard check\n const check = this.safeguards.check(challenge.amount);\n if (!check.allowed) {\n throw new Error(`Safeguard rejected: ${check.reason}`);\n }\n\n // 6. Execute USDCx transfer\n const transferResult = await this.usdcx.transfer({\n recipient: challenge.recipient,\n amount: challenge.amount,\n });\n\n // 7. Record spend\n this.safeguards.recordSpend(challenge.amount);\n\n // 8. Build credential and retry\n const credential = Buffer.from(\n JSON.stringify({\n updateId: transferResult.updateId,\n completionOffset: transferResult.completionOffset,\n sender: this.partyId,\n commandId: transferResult.commandId,\n }),\n ).toString(\"base64\");\n\n const retryResponse = await fetch(url, {\n ...requestInit,\n headers: {\n ...opts?.headers,\n Authorization: `Payment ${credential}`,\n },\n });\n\n return {\n response: retryResponse,\n paid: true,\n receipt: {\n updateId: transferResult.updateId,\n completionOffset: transferResult.completionOffset,\n commandId: transferResult.commandId,\n amount: challenge.amount,\n },\n };\n }\n}\n","/**\n * TrafficManager — Canton traffic budget management.\n *\n * Canton does NOT have per-transaction gas fees. Instead, each validator\n * has a traffic budget (bandwidth allocation). Additional traffic can be\n * purchased by burning Canton Coin (CC).\n *\n * NOTE: The actual traffic API depends on the validator/participant setup.\n * This provides the interface with a reasonable stub implementation.\n * Real implementation requires validator admin API access.\n */\n\nimport type { CantonClient } from \"../canton/client.js\";\n\nexport interface TrafficBalance {\n totalPurchased: number;\n consumed: number;\n remaining: number;\n}\n\nexport interface AutoPurchaseConfig {\n enabled: boolean;\n minBalance: number;\n purchaseAmount: string;\n}\n\nexport class TrafficManager {\n private autoPurchaseConfig: AutoPurchaseConfig = {\n enabled: false,\n minBalance: 1000,\n purchaseAmount: \"5.0\",\n };\n\n constructor(\n private readonly client: CantonClient,\n private readonly partyId: string,\n ) {}\n\n /**\n * Check validator's traffic balance.\n *\n * TODO: Implement using actual validator admin API.\n * The traffic balance is a validator-level concept, not per-party.\n * For now, returns a stub indicating sufficient traffic.\n */\n async trafficBalance(): Promise<TrafficBalance> {\n // Verify we can reach the ledger (health check as proxy)\n const healthy = await this.client.isHealthy();\n\n if (!healthy) {\n return { totalPurchased: 0, consumed: 0, remaining: 0 };\n }\n\n // TODO: Query actual traffic balance from validator admin API\n // The endpoint varies by validator implementation:\n // - Splice: GET /v0/admin/participant/traffic-state\n // - Direct Canton: participant admin gRPC\n return {\n totalPurchased: 10_000_000,\n consumed: 0,\n remaining: 10_000_000,\n };\n }\n\n /**\n * Purchase additional traffic by burning Canton Coin (CC).\n *\n * TODO: Implement using actual CC burn mechanism.\n */\n async purchaseTraffic(ccAmount: string): Promise<{ txId: string }> {\n void ccAmount;\n // TODO: Exercise the traffic purchase choice on the validator\n // This involves burning CC tokens to increase the validator's traffic budget.\n throw new Error(\"Traffic purchase not yet implemented — requires validator admin API\");\n }\n\n /**\n * Check if there's sufficient traffic for a standard operation.\n * Returns true if remaining traffic > minimum threshold.\n */\n async hasSufficientTraffic(): Promise<boolean> {\n const balance = await this.trafficBalance();\n return balance.remaining > this.autoPurchaseConfig.minBalance;\n }\n\n /** Configure auto-purchase settings. */\n setAutoPurchase(config: AutoPurchaseConfig): void {\n this.autoPurchaseConfig = { ...config };\n }\n\n /** Get current auto-purchase configuration. */\n getAutoPurchaseConfig(): AutoPurchaseConfig {\n return { ...this.autoPurchaseConfig };\n }\n}\n","/**\n * CantonAgent — high-level entry point for the Canton SDK.\n *\n * Creates and wires together all sub-services:\n * - CheckingAccount (USDCx balance, send, history)\n * - SafeguardManager (tx limits, daily limits, lock)\n * - TrafficManager (validator traffic budgets)\n * - MppPayClient (HTTP 402 auto-pay)\n */\n\nimport { CheckingAccount } from \"./accounts/checking.js\";\nimport { CantonClient } from \"./canton/client.js\";\nimport { USDCxService } from \"./canton/usdcx.js\";\nimport { MppPayClient } from \"./mpp/pay-client.js\";\nimport { SafeguardManager } from \"./safeguards/manager.js\";\nimport { TrafficManager } from \"./traffic/manager.js\";\nimport {\n loadConfig,\n type AgentConfig,\n} from \"./wallet/config.js\";\n\nexport interface WalletInfo {\n address: string;\n partyId: string;\n network: string;\n}\n\nexport interface CantonAgentConfig {\n ledgerUrl?: string;\n token?: string;\n userId?: string;\n partyId?: string;\n network?: \"mainnet\" | \"testnet\" | \"devnet\";\n configPath?: string;\n safeguardsPath?: string;\n}\n\nexport class CantonAgent {\n readonly checking: CheckingAccount;\n readonly safeguards: SafeguardManager;\n readonly traffic: TrafficManager;\n readonly mpp: MppPayClient;\n readonly wallet: WalletInfo;\n\n private readonly client: CantonClient;\n private readonly usdcx: USDCxService;\n\n private constructor(\n client: CantonClient,\n usdcx: USDCxService,\n checking: CheckingAccount,\n safeguards: SafeguardManager,\n traffic: TrafficManager,\n mpp: MppPayClient,\n wallet: WalletInfo,\n ) {\n this.client = client;\n this.usdcx = usdcx;\n this.checking = checking;\n this.safeguards = safeguards;\n this.traffic = traffic;\n this.mpp = mpp;\n this.wallet = wallet;\n }\n\n /**\n * Create a new CantonAgent from config.\n *\n * Loads configuration from ~/.caypo/config.json (or overrides),\n * initializes all sub-services, and wires them together.\n */\n static async create(config?: CantonAgentConfig): Promise<CantonAgent> {\n // 1. Load config (file + overrides)\n const fileConfig = await loadConfig(config?.configPath);\n const merged: AgentConfig = {\n ...fileConfig,\n ...(config?.ledgerUrl && { ledgerUrl: config.ledgerUrl }),\n ...(config?.partyId && { partyId: config.partyId }),\n ...(config?.userId && { userId: config.userId }),\n ...(config?.network && { network: config.network }),\n };\n\n const token = config?.token ?? \"\";\n\n // 2. Create low-level client\n const client = new CantonClient({\n ledgerUrl: merged.ledgerUrl,\n token,\n userId: merged.userId,\n });\n\n // 3. Create services\n const usdcx = new USDCxService(client, merged.partyId);\n const safeguards = await SafeguardManager.load(config?.safeguardsPath);\n const traffic = new TrafficManager(client, merged.partyId);\n const checking = new CheckingAccount(usdcx, safeguards, client, merged.partyId);\n const mpp = new MppPayClient(usdcx, safeguards, merged.partyId, merged.network);\n\n const wallet: WalletInfo = {\n address: merged.partyId,\n partyId: merged.partyId,\n network: merged.network,\n };\n\n return new CantonAgent(client, usdcx, checking, safeguards, traffic, mpp, wallet);\n }\n\n /**\n * Create a CantonAgent from explicit parameters (no file I/O).\n * Useful for testing and programmatic setup.\n */\n static fromParams(params: {\n client: CantonClient;\n partyId: string;\n network: string;\n safeguards?: SafeguardManager;\n }): CantonAgent {\n const safeguards = params.safeguards ?? new SafeguardManager();\n const usdcx = new USDCxService(params.client, params.partyId);\n const traffic = new TrafficManager(params.client, params.partyId);\n const checking = new CheckingAccount(usdcx, safeguards, params.client, params.partyId);\n const mpp = new MppPayClient(usdcx, safeguards, params.partyId, params.network);\n\n const wallet: WalletInfo = {\n address: params.partyId,\n partyId: params.partyId,\n network: params.network,\n };\n\n return new CantonAgent(params.client, usdcx, checking, safeguards, traffic, mpp, wallet);\n }\n}\n"],"mappings":"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","names":["text","mkdir","readFile","writeFile","dirname","join","homedir","mkdir","readFile","writeFile","dirname","join","homedir","join","homedir","readFile","mkdir","dirname","writeFile","addAmounts"]}
package/package.json ADDED
@@ -0,0 +1,50 @@
1
+ {
2
+ "name": "@caypo/canton-sdk",
3
+ "version": "0.1.0",
4
+ "description": "Core SDK for AI agent banking on Canton Network — USDCx transfers, encrypted wallets, safeguards, and MPP auto-pay",
5
+ "keywords": [
6
+ "canton",
7
+ "sdk",
8
+ "payments",
9
+ "ai-agents",
10
+ "stablecoin",
11
+ "usdcx",
12
+ "blockchain",
13
+ "machine-payments",
14
+ "wallet",
15
+ "defi"
16
+ ],
17
+ "author": "Cayvox Labs <anil@cayvox.com>",
18
+ "homepage": "https://github.com/anilkaracay/Caypo/tree/main/packages/sdk",
19
+ "repository": {
20
+ "type": "git",
21
+ "url": "https://github.com/anilkaracay/Caypo.git",
22
+ "directory": "packages/sdk"
23
+ },
24
+ "type": "module",
25
+ "main": "./dist/index.js",
26
+ "types": "./dist/index.d.ts",
27
+ "exports": {
28
+ ".": {
29
+ "types": "./dist/index.d.ts",
30
+ "import": "./dist/index.js",
31
+ "require": "./dist/index.cjs"
32
+ }
33
+ },
34
+ "dependencies": {
35
+ "@caypo/mpp-canton": "0.1.0"
36
+ },
37
+ "devDependencies": {
38
+ "tsup": "^8.4.0",
39
+ "typescript": "^5.8.0",
40
+ "vitest": "^3.1.0"
41
+ },
42
+ "license": "(Apache-2.0 OR MIT)",
43
+ "scripts": {
44
+ "build": "tsup",
45
+ "test": "vitest run",
46
+ "lint": "eslint src/",
47
+ "typecheck": "tsc --noEmit",
48
+ "clean": "rm -rf dist"
49
+ }
50
+ }
@@ -0,0 +1,217 @@
1
+ import { beforeEach, describe, expect, it, vi } from "vitest";
2
+ import { CantonAgent } from "../agent.js";
3
+ import type { CantonClient } from "../canton/client.js";
4
+ import { SafeguardManager } from "../safeguards/manager.js";
5
+
6
+ // ---------------------------------------------------------------------------
7
+ // Mock CantonClient
8
+ // ---------------------------------------------------------------------------
9
+
10
+ const PARTY = "Agent::1220abcdef1234567890";
11
+ const USDCX_TEMPLATE = "Splice.Api.Token.HoldingV1:Holding";
12
+
13
+ function mockClient(): CantonClient {
14
+ return {
15
+ getLedgerEnd: vi.fn().mockResolvedValue(42),
16
+ queryActiveContracts: vi.fn().mockResolvedValue([
17
+ {
18
+ contractId: "cid-1",
19
+ templateId: USDCX_TEMPLATE,
20
+ createArgument: { owner: PARTY, amount: "50.000000" },
21
+ createdAt: "",
22
+ signatories: [PARTY],
23
+ observers: [],
24
+ },
25
+ {
26
+ contractId: "cid-2",
27
+ templateId: USDCX_TEMPLATE,
28
+ createArgument: { owner: PARTY, amount: "30.000000" },
29
+ createdAt: "",
30
+ signatories: [PARTY],
31
+ observers: [],
32
+ },
33
+ ]),
34
+ submitAndWait: vi.fn().mockResolvedValue({ updateId: "upd-1", completionOffset: 43 }),
35
+ submitAndWaitForTransaction: vi.fn(),
36
+ getTransactionById: vi.fn(),
37
+ allocateParty: vi.fn(),
38
+ listParties: vi.fn(),
39
+ isHealthy: vi.fn().mockResolvedValue(true),
40
+ } as unknown as CantonClient;
41
+ }
42
+
43
+ // ---------------------------------------------------------------------------
44
+ // CantonAgent.fromParams
45
+ // ---------------------------------------------------------------------------
46
+
47
+ describe("CantonAgent.fromParams", () => {
48
+ it("creates agent with all sub-services", () => {
49
+ const client = mockClient();
50
+ const agent = CantonAgent.fromParams({
51
+ client,
52
+ partyId: PARTY,
53
+ network: "testnet",
54
+ });
55
+
56
+ expect(agent.checking).toBeDefined();
57
+ expect(agent.safeguards).toBeDefined();
58
+ expect(agent.traffic).toBeDefined();
59
+ expect(agent.mpp).toBeDefined();
60
+ expect(agent.wallet).toBeDefined();
61
+ });
62
+
63
+ it("wallet has correct address and network", () => {
64
+ const client = mockClient();
65
+ const agent = CantonAgent.fromParams({
66
+ client,
67
+ partyId: PARTY,
68
+ network: "testnet",
69
+ });
70
+
71
+ expect(agent.wallet.address).toBe(PARTY);
72
+ expect(agent.wallet.partyId).toBe(PARTY);
73
+ expect(agent.wallet.network).toBe("testnet");
74
+ });
75
+ });
76
+
77
+ // ---------------------------------------------------------------------------
78
+ // CheckingAccount via CantonAgent
79
+ // ---------------------------------------------------------------------------
80
+
81
+ describe("CantonAgent.checking", () => {
82
+ let agent: CantonAgent;
83
+
84
+ beforeEach(() => {
85
+ const client = mockClient();
86
+ agent = CantonAgent.fromParams({
87
+ client,
88
+ partyId: PARTY,
89
+ network: "testnet",
90
+ });
91
+ });
92
+
93
+ it("balance returns sum of holdings", async () => {
94
+ const bal = await agent.checking.balance();
95
+
96
+ expect(bal.available).toBe("80");
97
+ expect(bal.holdingCount).toBe(2);
98
+ });
99
+
100
+ it("address returns party ID", () => {
101
+ expect(agent.checking.address()).toBe(PARTY);
102
+ });
103
+
104
+ it("send checks safeguards and transfers", async () => {
105
+ const result = await agent.checking.send("Bob::1220bbbb", "10");
106
+
107
+ expect(result.updateId).toBe("upd-1");
108
+ expect(result.completionOffset).toBe(43);
109
+ expect(result.commandId).toBeTruthy();
110
+ });
111
+
112
+ it("send records spend in safeguards", async () => {
113
+ await agent.checking.send("Bob::1220bbbb", "25");
114
+
115
+ const settings = agent.safeguards.settings();
116
+ expect(settings.dailySpent).toBe("25");
117
+ });
118
+
119
+ it("send rejects when safeguards block it", async () => {
120
+ agent.safeguards.lock("1234");
121
+
122
+ await expect(agent.checking.send("Bob::1220bbbb", "10")).rejects.toThrow(
123
+ "Safeguard rejected",
124
+ );
125
+ });
126
+
127
+ it("send rejects when amount exceeds tx limit", async () => {
128
+ agent.safeguards.setTxLimit("5");
129
+
130
+ await expect(agent.checking.send("Bob::1220bbbb", "10")).rejects.toThrow(
131
+ "per-transaction limit",
132
+ );
133
+ });
134
+
135
+ it("send rejects when daily limit exceeded", async () => {
136
+ agent.safeguards.setDailyLimit("20");
137
+ agent.safeguards.recordSpend("15");
138
+
139
+ await expect(agent.checking.send("Bob::1220bbbb", "10")).rejects.toThrow(
140
+ "daily limit",
141
+ );
142
+ });
143
+ });
144
+
145
+ // ---------------------------------------------------------------------------
146
+ // TrafficManager via CantonAgent
147
+ // ---------------------------------------------------------------------------
148
+
149
+ describe("CantonAgent.traffic", () => {
150
+ it("reports sufficient traffic when healthy", async () => {
151
+ const client = mockClient();
152
+ const agent = CantonAgent.fromParams({
153
+ client,
154
+ partyId: PARTY,
155
+ network: "testnet",
156
+ });
157
+
158
+ const sufficient = await agent.traffic.hasSufficientTraffic();
159
+ expect(sufficient).toBe(true);
160
+ });
161
+
162
+ it("reports insufficient traffic when unhealthy", async () => {
163
+ const client = mockClient();
164
+ (client.isHealthy as ReturnType<typeof vi.fn>).mockResolvedValue(false);
165
+
166
+ const agent = CantonAgent.fromParams({
167
+ client,
168
+ partyId: PARTY,
169
+ network: "testnet",
170
+ });
171
+
172
+ const sufficient = await agent.traffic.hasSufficientTraffic();
173
+ expect(sufficient).toBe(false);
174
+ });
175
+
176
+ it("trafficBalance returns numbers", async () => {
177
+ const client = mockClient();
178
+ const agent = CantonAgent.fromParams({
179
+ client,
180
+ partyId: PARTY,
181
+ network: "testnet",
182
+ });
183
+
184
+ const balance = await agent.traffic.trafficBalance();
185
+ expect(typeof balance.totalPurchased).toBe("number");
186
+ expect(typeof balance.consumed).toBe("number");
187
+ expect(typeof balance.remaining).toBe("number");
188
+ expect(balance.remaining).toBeGreaterThan(0);
189
+ });
190
+ });
191
+
192
+ // ---------------------------------------------------------------------------
193
+ // Custom safeguards
194
+ // ---------------------------------------------------------------------------
195
+
196
+ describe("CantonAgent with custom safeguards", () => {
197
+ it("accepts custom SafeguardManager", () => {
198
+ const client = mockClient();
199
+ const safeguards = new SafeguardManager({
200
+ txLimit: "5",
201
+ dailyLimit: "50",
202
+ locked: false,
203
+ lockedPinHash: "",
204
+ dailySpent: "0",
205
+ lastResetDate: new Date().toISOString().slice(0, 10),
206
+ });
207
+
208
+ const agent = CantonAgent.fromParams({
209
+ client,
210
+ partyId: PARTY,
211
+ network: "testnet",
212
+ safeguards,
213
+ });
214
+
215
+ expect(agent.safeguards.settings().txLimit).toBe("5");
216
+ });
217
+ });
@@ -0,0 +1,202 @@
1
+ import { describe, expect, it } from "vitest";
2
+ import {
3
+ addAmounts,
4
+ compareAmounts,
5
+ isValidAmount,
6
+ subtractAmounts,
7
+ toCantonAmount,
8
+ } from "../canton/amount.js";
9
+
10
+ // ---------------------------------------------------------------------------
11
+ // isValidAmount
12
+ // ---------------------------------------------------------------------------
13
+
14
+ describe("isValidAmount", () => {
15
+ it.each([
16
+ ["0", true],
17
+ ["1", true],
18
+ ["123", true],
19
+ ["0.0", true],
20
+ ["0.000001", true],
21
+ ["999999.999999", true],
22
+ ["1.0000000000", true],
23
+ ["100", true],
24
+ ["0.1", true],
25
+ // invalid
26
+ ["", false],
27
+ ["-1", false],
28
+ ["-0.5", false],
29
+ ["abc", false],
30
+ [".5", false],
31
+ ["1.", false],
32
+ ["1.2.3", false],
33
+ ["1e5", false],
34
+ [" 1", false],
35
+ ["1 ", false],
36
+ ["+1", false],
37
+ ["00", true], // leading zeros are valid digits
38
+ ])("isValidAmount(%s) === %s", (input, expected) => {
39
+ expect(isValidAmount(input)).toBe(expected);
40
+ });
41
+ });
42
+
43
+ // ---------------------------------------------------------------------------
44
+ // compareAmounts
45
+ // ---------------------------------------------------------------------------
46
+
47
+ describe("compareAmounts", () => {
48
+ it.each([
49
+ // equal
50
+ ["0", "0", 0],
51
+ ["1", "1", 0],
52
+ ["1.0", "1", 0],
53
+ ["1.00", "1.0", 0],
54
+ ["0.100", "0.1", 0],
55
+ ["123.456", "123.456", 0],
56
+ ["0.0000000000", "0", 0],
57
+
58
+ // less than
59
+ ["0", "1", -1],
60
+ ["0.999999", "1", -1],
61
+ ["1", "2", -1],
62
+ ["1.5", "1.6", -1],
63
+ ["99", "100", -1],
64
+ ["0.000001", "0.000002", -1],
65
+ ["123.455", "123.456", -1],
66
+
67
+ // greater than
68
+ ["1", "0", 1],
69
+ ["1", "0.999999", 1],
70
+ ["2", "1", 1],
71
+ ["1.6", "1.5", 1],
72
+ ["100", "99", 1],
73
+ ["0.000002", "0.000001", 1],
74
+ ["999999.999999", "999999.999998", 1],
75
+ ["10", "9.9999999999", 1],
76
+ ] as [string, string, -1 | 0 | 1][])("compareAmounts(%s, %s) === %i", (a, b, expected) => {
77
+ expect(compareAmounts(a, b)).toBe(expected);
78
+ });
79
+ });
80
+
81
+ // ---------------------------------------------------------------------------
82
+ // addAmounts
83
+ // ---------------------------------------------------------------------------
84
+
85
+ describe("addAmounts", () => {
86
+ it.each([
87
+ ["0", "0", "0"],
88
+ ["1", "0", "1"],
89
+ ["0", "1", "1"],
90
+ ["1", "1", "2"],
91
+ ["1.5", "2.5", "4"],
92
+ ["0.1", "0.2", "0.3"],
93
+ ["0.000001", "0.000001", "0.000002"],
94
+ ["999999", "1", "1000000"],
95
+ ["99.99", "0.01", "100"],
96
+ ["0.999999", "0.000001", "1"],
97
+ ["123.456", "654.321", "777.777"],
98
+ ["0", "0.0", "0"],
99
+ ["1.1", "2.22", "3.32"],
100
+ ["9.9", "9.9", "19.8"],
101
+ ["0.123456789", "0.000000001", "0.12345679"],
102
+ ["999999.999999", "0.000001", "1000000"],
103
+ ["50.5", "50.5", "101"],
104
+ ["1", "0.0000000001", "1.0000000001"],
105
+ ["100", "200", "300"],
106
+ ["0.1", "0.1", "0.2"],
107
+ // carry propagation through many digits
108
+ ["9999", "1", "10000"],
109
+ ["99.99", "99.99", "199.98"],
110
+ ])("addAmounts(%s, %s) === %s", (a, b, expected) => {
111
+ expect(addAmounts(a, b)).toBe(expected);
112
+ });
113
+ });
114
+
115
+ // ---------------------------------------------------------------------------
116
+ // subtractAmounts
117
+ // ---------------------------------------------------------------------------
118
+
119
+ describe("subtractAmounts", () => {
120
+ it.each([
121
+ ["0", "0", "0"],
122
+ ["1", "0", "1"],
123
+ ["1", "1", "0"],
124
+ ["2", "1", "1"],
125
+ ["4", "1.5", "2.5"],
126
+ ["1", "0.5", "0.5"],
127
+ ["100", "0.01", "99.99"],
128
+ ["1", "0.000001", "0.999999"],
129
+ ["1000000", "1", "999999"],
130
+ ["1000000", "999999.999999", "0.000001"],
131
+ ["777.777", "123.456", "654.321"],
132
+ ["10", "3", "7"],
133
+ ["1.0", "0.1", "0.9"],
134
+ ["0.3", "0.1", "0.2"],
135
+ ["100.5", "50.5", "50"],
136
+ ["0.000002", "0.000001", "0.000001"],
137
+ ["999999.999999", "999999.999998", "0.000001"],
138
+ ["10", "0.0000000001", "9.9999999999"],
139
+ ["200", "100", "100"],
140
+ ["1.23", "0.23", "1"],
141
+ ["5.5", "2.3", "3.2"],
142
+ // borrow propagation
143
+ ["10000", "1", "9999"],
144
+ ["100.00", "0.01", "99.99"],
145
+ ])("subtractAmounts(%s, %s) === %s", (a, b, expected) => {
146
+ expect(subtractAmounts(a, b)).toBe(expected);
147
+ });
148
+
149
+ it("throws when a < b", () => {
150
+ expect(() => subtractAmounts("1", "2")).toThrow("Cannot subtract: 1 < 2");
151
+ expect(() => subtractAmounts("0", "0.000001")).toThrow();
152
+ expect(() => subtractAmounts("99.99", "100")).toThrow();
153
+ });
154
+ });
155
+
156
+ // ---------------------------------------------------------------------------
157
+ // toCantonAmount
158
+ // ---------------------------------------------------------------------------
159
+
160
+ describe("toCantonAmount", () => {
161
+ it.each([
162
+ // default 10 decimals
163
+ ["1", undefined, "1.0000000000"],
164
+ ["0", undefined, "0.0000000000"],
165
+ ["1.5", undefined, "1.5000000000"],
166
+ ["0.000001", undefined, "0.0000010000"],
167
+ ["123.456789", undefined, "123.4567890000"],
168
+
169
+ // 6 decimals (USDCx)
170
+ ["1", 6, "1.000000"],
171
+ ["0.01", 6, "0.010000"],
172
+ ["999999.999999", 6, "999999.999999"],
173
+ ["1.1234567", 6, "1.123456"],
174
+
175
+ // 0 decimals
176
+ ["1.5", 0, "1."],
177
+
178
+ // 2 decimals
179
+ ["1", 2, "1.00"],
180
+ ["1.5", 2, "1.50"],
181
+ ["1.999", 2, "1.99"],
182
+ ] as [string, number | undefined, string][])("toCantonAmount(%s, %s) === %s", (input, decimals, expected) => {
183
+ expect(toCantonAmount(input, decimals)).toBe(expected);
184
+ });
185
+ });
186
+
187
+ // ---------------------------------------------------------------------------
188
+ // Consistency: add then subtract returns original
189
+ // ---------------------------------------------------------------------------
190
+
191
+ describe("add/subtract consistency", () => {
192
+ it.each([
193
+ ["100", "50"],
194
+ ["1.5", "0.3"],
195
+ ["999999.999999", "0.000001"],
196
+ ["0.123456", "0.123456"],
197
+ ["10000", "9999"],
198
+ ])("addAmounts(%s, %s) then subtract %s returns %s", (a, b) => {
199
+ const sum = addAmounts(a, b);
200
+ expect(subtractAmounts(sum, b)).toBe(a);
201
+ });
202
+ });