settld 0.1.2 → 0.2.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 (483) hide show
  1. package/README.md +93 -3
  2. package/SETTLD_VERSION +1 -1
  3. package/bin/settld-mcp +2 -0
  4. package/bin/settld.js +71 -0
  5. package/conformance/kernel-v0/README.md +7 -0
  6. package/conformance/kernel-v0/run.mjs +292 -4
  7. package/docs/ACCESS.md +57 -0
  8. package/docs/ADOPTION_CHECKLIST.md +44 -0
  9. package/docs/ALERTS.md +198 -0
  10. package/docs/ARCHITECTURE.md +69 -0
  11. package/docs/ARCHITECTURE_FOUNDER_GUIDE.md +284 -0
  12. package/docs/ARTIFACTS.md +60 -0
  13. package/docs/CERTIFICATION_CHECKLIST.md +33 -0
  14. package/docs/CIRCLE_SANDBOX_E2E.md +152 -0
  15. package/docs/CONFIG.md +297 -0
  16. package/docs/CONTRACTS_APIS.md +23 -0
  17. package/docs/DEPRECATION.md +31 -0
  18. package/docs/DOMAIN_MODEL.md +92 -0
  19. package/docs/EVENT_ENVELOPE.md +53 -0
  20. package/docs/FINANCE_PACK_FORMAT.md +53 -0
  21. package/docs/INCIDENT_TAXONOMY.md +30 -0
  22. package/docs/JOB_STATE_MACHINE.md +66 -0
  23. package/docs/KERNEL_COMPATIBLE.md +60 -0
  24. package/docs/KERNEL_V0.md +40 -0
  25. package/docs/KEY_ROTATION.md +80 -0
  26. package/docs/LEDGER.md +82 -0
  27. package/docs/LIVENESS.md +76 -0
  28. package/docs/MVP_BUILD_ORDER.md +36 -0
  29. package/docs/ONCALL_PLAYBOOK.md +39 -0
  30. package/docs/OPERATIONS_SIGNING.md +20 -0
  31. package/docs/OVERVIEW.md +190 -0
  32. package/docs/PERF_BASELINE.md +85 -0
  33. package/docs/PRD.md +77 -0
  34. package/docs/QUICKSTART_KERNEL_V0.md +96 -0
  35. package/docs/QUICKSTART_MCP.md +377 -0
  36. package/docs/QUICKSTART_MCP_HOSTS.md +210 -0
  37. package/docs/QUICKSTART_POLICY_PACKS.md +65 -0
  38. package/docs/QUICKSTART_PRODUCE.md +61 -0
  39. package/docs/QUICKSTART_PROFILES.md +198 -0
  40. package/docs/QUICKSTART_RELEASE_VERIFY.md +39 -0
  41. package/docs/QUICKSTART_SDK.md +125 -0
  42. package/docs/QUICKSTART_SDK_PYTHON.md +111 -0
  43. package/docs/QUICKSTART_VERIFY.md +54 -0
  44. package/docs/QUICKSTART_X402_GATEWAY.md +317 -0
  45. package/docs/README.md +33 -0
  46. package/docs/RELEASE_CHECKLIST.md +182 -0
  47. package/docs/RELEASING.md +82 -0
  48. package/docs/REPO_SETTINGS.md +37 -0
  49. package/docs/RUNBOOK.md +86 -0
  50. package/docs/SKILLS.md +42 -0
  51. package/docs/SKILL_BUNDLE_FORMAT.md +48 -0
  52. package/docs/SLO.md +131 -0
  53. package/docs/SUMMARY.md +17 -0
  54. package/docs/SUPPORT.md +31 -0
  55. package/docs/THREAT_MODEL.md +36 -0
  56. package/docs/TRUST.md +59 -0
  57. package/docs/WORKFLOW.md +35 -0
  58. package/docs/X402_BATCH_SETTLEMENT.md +126 -0
  59. package/docs/blog/2026-02-14-your-ai-agent-just-spent-500-where-is-the-receipt.md +73 -0
  60. package/docs/examples/x402-provider-payout-registry.example.json +14 -0
  61. package/docs/gitbook/README.md +64 -0
  62. package/docs/gitbook/SETUP.md +25 -0
  63. package/docs/gitbook/SUMMARY.md +15 -0
  64. package/docs/gitbook/api-reference.md +73 -0
  65. package/docs/gitbook/closepacks.md +55 -0
  66. package/docs/gitbook/conformance.md +59 -0
  67. package/docs/gitbook/core-primitives.md +85 -0
  68. package/docs/gitbook/dispute-lifecycle.md +33 -0
  69. package/docs/gitbook/faq.md +21 -0
  70. package/docs/gitbook/guides.md +49 -0
  71. package/docs/gitbook/operations-runbook.md +36 -0
  72. package/docs/gitbook/quickstart.md +103 -0
  73. package/docs/gitbook/replay-and-audit.md +30 -0
  74. package/docs/gitbook/sdk-reference.md +35 -0
  75. package/docs/gitbook/security-model.md +58 -0
  76. package/docs/integrations/README.md +15 -0
  77. package/docs/integrations/github-actions-verify.yml +31 -0
  78. package/docs/integrations/github-actions.md +34 -0
  79. package/docs/integrations/openclaw/CLAWHUB_PUBLISH_CHECKLIST.md +65 -0
  80. package/docs/integrations/openclaw/PUBLIC_QUICKSTART.md +95 -0
  81. package/docs/integrations/openclaw/settld-mcp-skill/SKILL.md +69 -0
  82. package/docs/integrations/openclaw/settld-mcp-skill/mcp-server.example.json +12 -0
  83. package/docs/kernel-compatible/capabilities.json +36 -0
  84. package/docs/marketing/agent-commerce-substrate.md +78 -0
  85. package/docs/marketing/hn-repost-2026-02-17.md +102 -0
  86. package/docs/marketing/show-hn-post.md +45 -0
  87. package/docs/ops/ARTIFACT_VERIFICATION_STATUS.md +43 -0
  88. package/docs/ops/BILLING_WEBHOOK_REPLAY.md +105 -0
  89. package/docs/ops/CI_FLAKE_BUDGET.md +31 -0
  90. package/docs/ops/DISPUTE_FINANCE_RECONCILIATION_PACKET.md +56 -0
  91. package/docs/ops/GO_LIVE_GATE_S13.md +27 -0
  92. package/docs/ops/HOSTED_BASELINE_R2.md +129 -0
  93. package/docs/ops/KERNEL_V0_SHIP_GATE.md +69 -0
  94. package/docs/ops/LIGHTHOUSE_PRODUCTION_CLOSE.md +51 -0
  95. package/docs/ops/MCP_COMPATIBILITY_MATRIX.md +30 -0
  96. package/docs/ops/MINIMUM_PRODUCTION_TOPOLOGY.md +89 -0
  97. package/docs/ops/P0_BACKEND_PROGRESS.md +150 -0
  98. package/docs/ops/PAYMENTS_ALPHA_R5.md +105 -0
  99. package/docs/ops/PILOT_ONBOARDING_RUNBOOK.md +112 -0
  100. package/docs/ops/PRODUCTION_DEPLOYMENT_CHECKLIST.md +140 -0
  101. package/docs/ops/R1_SLOS.md +66 -0
  102. package/docs/ops/RELEASE_SIGNING_INCIDENT.md +58 -0
  103. package/docs/ops/SELF_SERVE_LAUNCH_AUTOMATION.md +89 -0
  104. package/docs/ops/THROUGHPUT_DRILL_10X.md +48 -0
  105. package/docs/ops/TRUST_CONFIG_WIZARD.md +60 -0
  106. package/docs/ops/X402_PILOT_WEEKLY_METRICS.md +76 -0
  107. package/docs/ops/tool-call-disputes-holdback.md +52 -0
  108. package/docs/pilot-kit/PILOT_PACKAGE_SCORECARD_X402.md +46 -0
  109. package/docs/pilot-kit/README.md +29 -0
  110. package/docs/pilot-kit/architecture-one-pager.md +48 -0
  111. package/docs/pilot-kit/buyer-email.txt +19 -0
  112. package/docs/pilot-kit/buyer-one-pager.md +31 -0
  113. package/docs/pilot-kit/gtm-pilot-playbook.md +182 -0
  114. package/docs/pilot-kit/offline-verify.md +33 -0
  115. package/docs/pilot-kit/procurement-one-pager.md +50 -0
  116. package/docs/pilot-kit/rfp-clause.md +46 -0
  117. package/docs/pilot-kit/roi-calculator-template.csv +2 -0
  118. package/docs/pilot-kit/security-qa.md +153 -0
  119. package/docs/pilot-kit/security-summary.md +35 -0
  120. package/docs/plans/2026-02-13-mcp-spike-design.md +113 -0
  121. package/docs/plans/2026-02-20-trust-os-v1-jira-backlog.md +348 -0
  122. package/docs/plans/2026-02-21-agent-economic-actor-operating-model.md +169 -0
  123. package/docs/plans/2026-02-21-trust-os-v1-strategy.md +241 -0
  124. package/docs/research/2026-02-21-agent-spend-host-landscape.md +57 -0
  125. package/docs/spec/AcceptanceCriteria.v1.md +17 -0
  126. package/docs/spec/AcceptanceEvaluation.v1.md +10 -0
  127. package/docs/spec/AgentEvent.v1.md +47 -0
  128. package/docs/spec/AgentIdentity.v1.md +62 -0
  129. package/docs/spec/AgentPassport.v1.md +95 -0
  130. package/docs/spec/AgentReputation.v1.md +59 -0
  131. package/docs/spec/AgentReputation.v2.md +52 -0
  132. package/docs/spec/AgentRun.v1.md +47 -0
  133. package/docs/spec/AgentRunSettlement.v1.md +52 -0
  134. package/docs/spec/AgentWallet.v1.md +43 -0
  135. package/docs/spec/AgreementDelegation.v1.md +109 -0
  136. package/docs/spec/ArbitrationCase.v1.md +67 -0
  137. package/docs/spec/ArbitrationOutcomeMapping.v1.md +62 -0
  138. package/docs/spec/ArbitrationVerdict.v1.md +60 -0
  139. package/docs/spec/BundleHeadAttestation.v1.md +32 -0
  140. package/docs/spec/CANONICAL_JSON.md +31 -0
  141. package/docs/spec/CRYPTOGRAPHY.md +61 -0
  142. package/docs/spec/ClosePack.v1.md +49 -0
  143. package/docs/spec/ClosePackManifest.v1.md +24 -0
  144. package/docs/spec/DelegationGrant.v1.md +90 -0
  145. package/docs/spec/DisputeCaseLifecycle.v1.md +51 -0
  146. package/docs/spec/DisputeOpenEnvelope.v1.md +43 -0
  147. package/docs/spec/ERRORS.md +76 -0
  148. package/docs/spec/ESCROW_NETTING_INVARIANTS.md +71 -0
  149. package/docs/spec/EvidenceIndex.v1.md +20 -0
  150. package/docs/spec/ExecutionIntent.v1.md +90 -0
  151. package/docs/spec/FinancePackBundleManifest.v1.md +24 -0
  152. package/docs/spec/FundingHold.v1.md +60 -0
  153. package/docs/spec/GovernancePolicy.v1.md +34 -0
  154. package/docs/spec/GovernancePolicy.v2.md +30 -0
  155. package/docs/spec/INVARIANTS.md +389 -0
  156. package/docs/spec/InteractionDirectionMatrix.v1.md +30 -0
  157. package/docs/spec/InvoiceBundleManifest.v1.md +24 -0
  158. package/docs/spec/InvoiceClaim.v1.md +11 -0
  159. package/docs/spec/MONEY_RAIL_STATE_MACHINE.md +58 -0
  160. package/docs/spec/MarketplaceAcceptance.v2.md +46 -0
  161. package/docs/spec/MarketplaceOffer.v2.md +54 -0
  162. package/docs/spec/MeteringReport.v1.md +18 -0
  163. package/docs/spec/OperatorAction.v1.md +90 -0
  164. package/docs/spec/PRODUCER_ERRORS.md +42 -0
  165. package/docs/spec/PolicyDecision.v1.md +83 -0
  166. package/docs/spec/PricingMatrix.v1.md +20 -0
  167. package/docs/spec/PricingMatrixSignatures.v1.md +30 -0
  168. package/docs/spec/PricingMatrixSignatures.v2.md +29 -0
  169. package/docs/spec/ProduceCliOutput.v1.md +46 -0
  170. package/docs/spec/ProofBundleManifest.v1.md +24 -0
  171. package/docs/spec/README.md +109 -0
  172. package/docs/spec/REFERENCE_IMPLEMENTATIONS.md +29 -0
  173. package/docs/spec/REFERENCE_VERIFIER_BEHAVIOR.md +68 -0
  174. package/docs/spec/REMOTE_SIGNER.md +66 -0
  175. package/docs/spec/ReleaseIndex.v1.md +32 -0
  176. package/docs/spec/ReleaseIndexSignatures.v1.md +17 -0
  177. package/docs/spec/ReleaseTrust.v1.md +13 -0
  178. package/docs/spec/ReleaseTrust.v2.md +26 -0
  179. package/docs/spec/RemoteSignerRequest.v1.md +21 -0
  180. package/docs/spec/RemoteSignerResponse.v1.md +16 -0
  181. package/docs/spec/ReputationEvent.v1.md +63 -0
  182. package/docs/spec/RevocationList.v1.md +28 -0
  183. package/docs/spec/SIGNER_PROVIDER_PLUGIN.md +32 -0
  184. package/docs/spec/STRICTNESS.md +68 -0
  185. package/docs/spec/SUPPLY_CHAIN.md +33 -0
  186. package/docs/spec/SettlementAdjustment.v1.md +45 -0
  187. package/docs/spec/SettlementDecisionRecord.v1.md +48 -0
  188. package/docs/spec/SettlementDecisionRecord.v2.md +53 -0
  189. package/docs/spec/SettlementDecisionReport.v1.md +44 -0
  190. package/docs/spec/SettlementKernel.v1.md +59 -0
  191. package/docs/spec/SettlementReceipt.v1.md +63 -0
  192. package/docs/spec/SlaDefinition.v1.md +24 -0
  193. package/docs/spec/SlaEvaluation.v1.md +12 -0
  194. package/docs/spec/THREAT_MODEL.md +113 -0
  195. package/docs/spec/TOOL_PROVENANCE.md +30 -0
  196. package/docs/spec/TRUST_ANCHORS.md +84 -0
  197. package/docs/spec/TenantSettings.v1.md +90 -0
  198. package/docs/spec/TenantSettings.v2.md +99 -0
  199. package/docs/spec/TimestampProof.v1.md +25 -0
  200. package/docs/spec/ToolCallAgreement.v1.md +34 -0
  201. package/docs/spec/ToolCallEvidence.v1.md +47 -0
  202. package/docs/spec/ToolManifest.v1.md +47 -0
  203. package/docs/spec/VERIFIER_ENVIRONMENT.md +38 -0
  204. package/docs/spec/VERSIONING.md +107 -0
  205. package/docs/spec/VerificationReport.v1.md +50 -0
  206. package/docs/spec/VerifyAboutOutput.v1.md +10 -0
  207. package/docs/spec/VerifyCliOutput.v1.md +28 -0
  208. package/docs/spec/WARNINGS.md +83 -0
  209. package/docs/spec/error-codes.v1.txt +285 -0
  210. package/docs/spec/examples/agreement_delegation_v1.example.json +21 -0
  211. package/docs/spec/examples/arbitration_case_v1.example.json +26 -0
  212. package/docs/spec/examples/arbitration_verdict_v1.example.json +32 -0
  213. package/docs/spec/examples/dispute_open_envelope_v1.example.json +18 -0
  214. package/docs/spec/examples/produce_cli_output_v1.example.json +32 -0
  215. package/docs/spec/examples/release_index_signature_v1.example.json +9 -0
  216. package/docs/spec/examples/release_index_signatures_v1.example.json +14 -0
  217. package/docs/spec/examples/release_index_v1.example.json +15 -0
  218. package/docs/spec/examples/release_trust_v1.example.json +7 -0
  219. package/docs/spec/examples/release_trust_v2.example.json +22 -0
  220. package/docs/spec/examples/remote_signer_request_v1.example.json +18 -0
  221. package/docs/spec/examples/remote_signer_response_v1.example.json +8 -0
  222. package/docs/spec/examples/reputation_event_v1.example.json +29 -0
  223. package/docs/spec/examples/verification_report_v1.example.json +24 -0
  224. package/docs/spec/examples/verify_about_output_v1.example.json +29 -0
  225. package/docs/spec/examples/verify_cli_output_v1.example.json +13 -0
  226. package/docs/spec/legacy/MarketplaceAcceptance.v1.md +48 -0
  227. package/docs/spec/legacy/MarketplaceOffer.v1.md +56 -0
  228. package/docs/spec/legacy/schemas/MarketplaceAcceptance.v1.schema.json +53 -0
  229. package/docs/spec/legacy/schemas/MarketplaceOffer.v1.schema.json +61 -0
  230. package/docs/spec/producer-error-codes.v1.txt +14 -0
  231. package/docs/spec/schemas/AcceptanceCriteria.v1.schema.json +24 -0
  232. package/docs/spec/schemas/AcceptanceEvaluation.v1.schema.json +26 -0
  233. package/docs/spec/schemas/AgentEvent.v1.schema.json +49 -0
  234. package/docs/spec/schemas/AgentIdentity.v1.schema.json +129 -0
  235. package/docs/spec/schemas/AgentPassport.v1.schema.json +112 -0
  236. package/docs/spec/schemas/AgentReputation.v1.schema.json +151 -0
  237. package/docs/spec/schemas/AgentReputation.v2.schema.json +120 -0
  238. package/docs/spec/schemas/AgentRun.v1.schema.json +71 -0
  239. package/docs/spec/schemas/AgentRunSettlement.v1.schema.json +75 -0
  240. package/docs/spec/schemas/AgentWallet.v1.schema.json +54 -0
  241. package/docs/spec/schemas/AgreementDelegation.v1.schema.json +50 -0
  242. package/docs/spec/schemas/ArbitrationCase.v1.schema.json +133 -0
  243. package/docs/spec/schemas/ArbitrationVerdict.v1.schema.json +149 -0
  244. package/docs/spec/schemas/BundleHeadAttestation.v1.schema.json +21 -0
  245. package/docs/spec/schemas/ClosePackManifest.v1.schema.json +38 -0
  246. package/docs/spec/schemas/DelegationGrant.v1.schema.json +102 -0
  247. package/docs/spec/schemas/DisputeOpenEnvelope.v1.schema.json +78 -0
  248. package/docs/spec/schemas/EvidenceIndex.v1.schema.json +41 -0
  249. package/docs/spec/schemas/ExecutionIntent.v1.schema.json +85 -0
  250. package/docs/spec/schemas/FinancePackBundleManifest.v1.schema.json +38 -0
  251. package/docs/spec/schemas/FundingHold.v1.schema.json +46 -0
  252. package/docs/spec/schemas/GovernancePolicy.v1.schema.json +45 -0
  253. package/docs/spec/schemas/GovernancePolicy.v2.schema.json +70 -0
  254. package/docs/spec/schemas/InteractionDirectionMatrix.v1.schema.json +43 -0
  255. package/docs/spec/schemas/InvoiceBundleManifest.v1.schema.json +38 -0
  256. package/docs/spec/schemas/InvoiceClaim.v1.schema.json +39 -0
  257. package/docs/spec/schemas/MarketplaceAcceptance.v2.schema.json +53 -0
  258. package/docs/spec/schemas/MarketplaceOffer.v2.schema.json +61 -0
  259. package/docs/spec/schemas/MeteringReport.v1.schema.json +45 -0
  260. package/docs/spec/schemas/OperatorAction.v1.schema.json +113 -0
  261. package/docs/spec/schemas/PolicyDecision.v1.schema.json +74 -0
  262. package/docs/spec/schemas/PricingMatrix.v1.schema.json +24 -0
  263. package/docs/spec/schemas/PricingMatrixSignatures.v1.schema.json +24 -0
  264. package/docs/spec/schemas/PricingMatrixSignatures.v2.schema.json +24 -0
  265. package/docs/spec/schemas/ProduceCliOutput.v1.schema.json +107 -0
  266. package/docs/spec/schemas/ProofBundleManifest.v1.schema.json +37 -0
  267. package/docs/spec/schemas/PublicKeys.v1.schema.json +33 -0
  268. package/docs/spec/schemas/ReleaseIndex.v1.schema.json +45 -0
  269. package/docs/spec/schemas/ReleaseIndexSignature.v1.schema.json +16 -0
  270. package/docs/spec/schemas/ReleaseIndexSignatures.v1.schema.json +16 -0
  271. package/docs/spec/schemas/ReleaseTrust.v1.schema.json +15 -0
  272. package/docs/spec/schemas/ReleaseTrust.v2.schema.json +37 -0
  273. package/docs/spec/schemas/RemoteSignerPublicKeyResponse.v1.schema.json +14 -0
  274. package/docs/spec/schemas/RemoteSignerRequest.v1.schema.json +24 -0
  275. package/docs/spec/schemas/RemoteSignerResponse.v1.schema.json +10 -0
  276. package/docs/spec/schemas/RemoteSignerSignRequest.v1.schema.json +27 -0
  277. package/docs/spec/schemas/RemoteSignerSignResponse.v1.schema.json +16 -0
  278. package/docs/spec/schemas/ReputationEvent.v1.schema.json +164 -0
  279. package/docs/spec/schemas/RevocationList.v1.schema.json +51 -0
  280. package/docs/spec/schemas/SettlementAdjustment.v1.schema.json +44 -0
  281. package/docs/spec/schemas/SettlementDecisionRecord.v1.schema.json +66 -0
  282. package/docs/spec/schemas/SettlementDecisionRecord.v2.schema.json +149 -0
  283. package/docs/spec/schemas/SettlementDecisionReport.v1.schema.json +61 -0
  284. package/docs/spec/schemas/SettlementReceipt.v1.schema.json +135 -0
  285. package/docs/spec/schemas/SlaDefinition.v1.schema.json +33 -0
  286. package/docs/spec/schemas/SlaEvaluation.v1.schema.json +26 -0
  287. package/docs/spec/schemas/TenantSettings.v1.schema.json +90 -0
  288. package/docs/spec/schemas/TenantSettings.v2.schema.json +161 -0
  289. package/docs/spec/schemas/TimestampProof.v1.schema.json +17 -0
  290. package/docs/spec/schemas/ToolCallAgreement.v1.schema.json +34 -0
  291. package/docs/spec/schemas/ToolCallEvidence.v1.schema.json +45 -0
  292. package/docs/spec/schemas/ToolManifest.v1.schema.json +54 -0
  293. package/docs/spec/schemas/VerificationReport.v1.schema.json +83 -0
  294. package/docs/spec/schemas/VerifyAboutOutput.v1.schema.json +54 -0
  295. package/docs/spec/schemas/VerifyCliOutput.v1.schema.json +75 -0
  296. package/docs/spec/schemas/VerifyReleaseOutput.v1.schema.json +47 -0
  297. package/docs/spec/x402-error-codes.v1.txt +35 -0
  298. package/docs/templates/buyer-email.txt +18 -0
  299. package/docs/templates/buyer-one-pager.md +24 -0
  300. package/package.json +53 -6
  301. package/scripts/acceptance/full-stack.mjs +734 -0
  302. package/scripts/acceptance/full-stack.sh +99 -0
  303. package/scripts/audit/build-audit-packet.mjs +242 -0
  304. package/scripts/backup-pg.sh +45 -0
  305. package/scripts/backup-restore/README.md +18 -0
  306. package/scripts/backup-restore/capture-state.mjs +130 -0
  307. package/scripts/backup-restore/client.mjs +97 -0
  308. package/scripts/backup-restore/seed-workload.mjs +235 -0
  309. package/scripts/backup-restore/verify-state.mjs +139 -0
  310. package/scripts/backup-restore-test.sh +217 -0
  311. package/scripts/chaos.js +221 -0
  312. package/scripts/ci/build-launch-cutover-packet.mjs +304 -0
  313. package/scripts/ci/build-self-serve-benchmark-report.mjs +122 -0
  314. package/scripts/ci/changelog-guard.mjs +145 -0
  315. package/scripts/ci/check-kernel-v0-launch-gate.mjs +233 -0
  316. package/scripts/ci/check-secret-hygiene.mjs +78 -0
  317. package/scripts/ci/check-version-consistency.mjs +42 -0
  318. package/scripts/ci/cli-pack-smoke.mjs +160 -0
  319. package/scripts/ci/flake-budget-guard.mjs +68 -0
  320. package/scripts/ci/generate-error-codes.mjs +54 -0
  321. package/scripts/ci/lib/lighthouse-tracker.mjs +90 -0
  322. package/scripts/ci/lib/self-serve-launch-gate.mjs +89 -0
  323. package/scripts/ci/npm-pack-smoke.mjs +454 -0
  324. package/scripts/ci/run-10x-throughput-drill.mjs +318 -0
  325. package/scripts/ci/run-10x-throughput-incident-rehearsal.mjs +368 -0
  326. package/scripts/ci/run-arbitration-workspace-browser-e2e.sh +22 -0
  327. package/scripts/ci/run-circle-sandbox-smoke.mjs +237 -0
  328. package/scripts/ci/run-go-live-gate.mjs +150 -0
  329. package/scripts/ci/run-kernel-v0-ship-gate.mjs +97 -0
  330. package/scripts/ci/run-mcp-host-cert-matrix.mjs +201 -0
  331. package/scripts/ci/run-mcp-host-smoke.mjs +473 -0
  332. package/scripts/ci/run-offline-verification-parity-gate.mjs +762 -0
  333. package/scripts/ci/run-onboarding-host-success-gate.mjs +516 -0
  334. package/scripts/ci/run-onboarding-policy-slo-gate.mjs +537 -0
  335. package/scripts/ci/run-production-cutover-gate.mjs +540 -0
  336. package/scripts/ci/run-public-openclaw-npx-smoke.mjs +148 -0
  337. package/scripts/ci/run-release-promotion-guard.mjs +756 -0
  338. package/scripts/ci/run-self-serve-launch-gate.mjs +56 -0
  339. package/scripts/ci/runtime-import-smoke.mjs +58 -0
  340. package/scripts/ci/update-lighthouse-tracker.mjs +112 -0
  341. package/scripts/closepack/lib.mjs +286 -0
  342. package/scripts/collect-debug.sh +263 -0
  343. package/scripts/demo/compositional-settlement-3hop.mjs +237 -0
  344. package/scripts/demo/delivery-robot/export-ui-fixture.mjs +188 -0
  345. package/scripts/demo/delivery-robot/generate.mjs +377 -0
  346. package/scripts/demo/kernel-agent-goes-shopping.mjs +202 -0
  347. package/scripts/demo/magic-link-first-green.mjs +118 -0
  348. package/scripts/demo/magic-link-kind-smoke.mjs +577 -0
  349. package/scripts/demo/mcp-paid-exa.mjs +1110 -0
  350. package/scripts/dev/billing-doctor.sh +145 -0
  351. package/scripts/dev/billing-smoke-prod.sh +219 -0
  352. package/scripts/dev/billing-webhook-replay.sh +161 -0
  353. package/scripts/dev/env.dev.example +29 -0
  354. package/scripts/dev/env.sh +37 -0
  355. package/scripts/dev/new-sdk-key.sh +81 -0
  356. package/scripts/dev/sdk-first-run.sh +21 -0
  357. package/scripts/dev/smoke-x402-gateway.sh +115 -0
  358. package/scripts/dev/start-api.sh +24 -0
  359. package/scripts/doctor/mcp-host.mjs +120 -0
  360. package/scripts/examples/produce-and-verify-jobproof.mjs +191 -0
  361. package/scripts/examples/sdk-first-paid-rfq.py +105 -0
  362. package/scripts/examples/sdk-first-verified-run.mjs +85 -0
  363. package/scripts/examples/sdk-first-verified-run.py +99 -0
  364. package/scripts/examples/sdk-tenant-analytics.mjs +103 -0
  365. package/scripts/examples/sdk-tenant-analytics.py +118 -0
  366. package/scripts/finance-pack/bundle.mjs +284 -0
  367. package/scripts/fixtures/generate-bundle-fixtures.mjs +877 -0
  368. package/scripts/governance/export.mjs +169 -0
  369. package/scripts/load/delivery-stress.k6.js +183 -0
  370. package/scripts/load/ingest-burst.k6.js +236 -0
  371. package/scripts/load/run-delivery-load.js +66 -0
  372. package/scripts/load/webhook-receiver.js +131 -0
  373. package/scripts/magic-link/migrate-run-records-to-db.mjs +35 -0
  374. package/scripts/mcp/probe.mjs +238 -0
  375. package/scripts/mcp/settld-mcp-http-gateway.mjs +178 -0
  376. package/scripts/mcp/settld-mcp-server.mjs +1511 -0
  377. package/scripts/openapi/write.mjs +13 -0
  378. package/scripts/ops/bootstrap-tenant-conformance.mjs +185 -0
  379. package/scripts/ops/build-x402-pilot-reliability-report.mjs +489 -0
  380. package/scripts/ops/check-x402-receipt-sample.mjs +181 -0
  381. package/scripts/ops/design-partner-run-packet.mjs +466 -0
  382. package/scripts/ops/dispute-finance-reconciliation-packet.mjs +313 -0
  383. package/scripts/ops/hosted-baseline-evidence.mjs +890 -0
  384. package/scripts/ops/money-rails-chargeback-evidence.mjs +509 -0
  385. package/scripts/ops/money-rails-reconcile-evidence.mjs +180 -0
  386. package/scripts/ops/p0-seed-money-rail-operation.mjs +432 -0
  387. package/scripts/ops/run-x402-hitl-smoke.mjs +607 -0
  388. package/scripts/pilot/finance-pack.mjs +495 -0
  389. package/scripts/pilot/fixtures/robot-keypair.json +4 -0
  390. package/scripts/pilot/fixtures/server-signer.json +4 -0
  391. package/scripts/policy/cli.mjs +600 -0
  392. package/scripts/profile/cli.mjs +1324 -0
  393. package/scripts/proof-bundle/job.mjs +109 -0
  394. package/scripts/proof-bundle/lib.mjs +92 -0
  395. package/scripts/proof-bundle/month.mjs +103 -0
  396. package/scripts/provider/conformance-run.mjs +159 -0
  397. package/scripts/provider/keys-generate.mjs +135 -0
  398. package/scripts/provider/publish.mjs +420 -0
  399. package/scripts/quickstart/x402.mjs +334 -0
  400. package/scripts/register-entity-secret.mjs +102 -0
  401. package/scripts/release/build-artifacts.mjs +181 -0
  402. package/scripts/release/generate-release-index.mjs +112 -0
  403. package/scripts/release/release-index-lib.mjs +232 -0
  404. package/scripts/release/sign-release-index.mjs +85 -0
  405. package/scripts/release/validate-release-assets.mjs +170 -0
  406. package/scripts/release/verify-release.mjs +261 -0
  407. package/scripts/restore-pg.sh +34 -0
  408. package/scripts/scaffold/create-settld-paid-tool.mjs +19 -0
  409. package/scripts/sdk/smoke-python.py +30 -0
  410. package/scripts/sdk/smoke.mjs +16 -0
  411. package/scripts/settlement/x402-batch-worker.mjs +1091 -0
  412. package/scripts/setup/circle-bootstrap.mjs +310 -0
  413. package/scripts/setup/host-config.mjs +617 -0
  414. package/scripts/setup/onboard.mjs +1337 -0
  415. package/scripts/setup/openclaw-onboard.mjs +423 -0
  416. package/scripts/setup/wizard.mjs +986 -0
  417. package/scripts/slo/check.mjs +239 -0
  418. package/scripts/smoke/k8s-smoke.mjs +214 -0
  419. package/scripts/spec/generate-protocol-vectors.mjs +1019 -0
  420. package/scripts/test/check-no-generated-artifacts.sh +12 -0
  421. package/scripts/test/run.sh +59 -0
  422. package/scripts/trust/validate-trust-file.mjs +57 -0
  423. package/scripts/trust-config/rotate-settld-pay.mjs +277 -0
  424. package/scripts/trust-config/wizard.mjs +161 -0
  425. package/scripts/vendor-contract-test-lib.mjs +182 -0
  426. package/scripts/vendor-contract-test.mjs +55 -0
  427. package/scripts/vercel/build-mkdocs.sh +9 -0
  428. package/scripts/vercel/ignore-mkdocs.sh +25 -0
  429. package/scripts/vercel/install-mkdocs.sh +6 -0
  430. package/scripts/verify-pg.js +217 -0
  431. package/scripts/x402/receipt-verify.mjs +289 -0
  432. package/services/finance-sink/src/dedupe-store.js +29 -6
  433. package/services/receiver/src/dedupe-store.js +29 -5
  434. package/services/x402-gateway/Dockerfile +13 -0
  435. package/services/x402-gateway/README.md +58 -0
  436. package/services/x402-gateway/examples/upstream-mock.js +337 -0
  437. package/services/x402-gateway/src/server.js +1058 -0
  438. package/src/api/app.js +34658 -16940
  439. package/src/api/maintenance.js +70 -0
  440. package/src/api/middleware/trust-kernel.js +114 -0
  441. package/src/api/openapi.js +1778 -70
  442. package/src/api/persistence.js +456 -0
  443. package/src/api/server.js +81 -5
  444. package/src/api/store.js +1581 -62
  445. package/src/api/workers/deliveries.js +99 -4
  446. package/src/api/workers/insolvency-sweep.js +159 -0
  447. package/src/core/agent-card.js +69 -0
  448. package/src/core/agent-wallets.js +231 -0
  449. package/src/core/agreement-delegation.js +549 -0
  450. package/src/core/billing-plans.js +40 -6
  451. package/src/core/circle-reserve-adapter.js +845 -0
  452. package/src/core/event-policy.js +21 -2
  453. package/src/core/maintenance-locks.js +1 -0
  454. package/src/core/operator-action.js +303 -0
  455. package/src/core/paid-tool-manifest.js +318 -0
  456. package/src/core/policy-decision.js +322 -0
  457. package/src/core/policy-packs.js +207 -0
  458. package/src/core/profile-fingerprint.js +27 -0
  459. package/src/core/profile-simulation-reasons.js +84 -0
  460. package/src/core/profile-templates.js +242 -0
  461. package/src/core/provider-publish-conformance.js +525 -0
  462. package/src/core/provider-publish-proof.js +396 -0
  463. package/src/core/provider-quote-signature.js +170 -0
  464. package/src/core/settld-keys.js +112 -0
  465. package/src/core/settld-pay-token.js +344 -0
  466. package/src/core/settlement-kernel.js +239 -2
  467. package/src/core/settlement-verifier.js +335 -0
  468. package/src/core/tool-call-agreement.js +112 -0
  469. package/src/core/tool-call-evidence.js +144 -0
  470. package/src/core/tool-provider-signature.js +98 -0
  471. package/src/core/wallet-assignment-resolver.js +129 -0
  472. package/src/core/wallet-provider-bootstrap.js +365 -0
  473. package/src/core/x402-escalation-override.js +258 -0
  474. package/src/core/x402-gate.js +118 -0
  475. package/src/core/x402-provider-refund-decision.js +220 -0
  476. package/src/core/x402-receipt-verifier.js +708 -0
  477. package/src/core/x402-reversal-command.js +251 -0
  478. package/src/core/x402-wallet-issuer-decision.js +252 -0
  479. package/src/core/zk-verifier.js +300 -0
  480. package/src/db/migrations/029_reputation_event_index.sql +54 -0
  481. package/src/db/migrations/030_artifacts_source_event_unique_job_only.sql +15 -0
  482. package/src/db/pg.js +18 -7
  483. package/src/db/store-pg.js +1508 -111
@@ -0,0 +1,1091 @@
1
+ #!/usr/bin/env node
2
+
3
+ import crypto from "node:crypto";
4
+ import fs from "node:fs";
5
+ import path from "node:path";
6
+ import process from "node:process";
7
+
8
+ import { canonicalJsonStringify } from "../../src/core/canonical-json.js";
9
+ import { keyIdFromPublicKeyPem, sha256Hex, signHashHexEd25519 } from "../../src/core/crypto.js";
10
+
11
+ const CIRCLE_TX_OK_STATES = new Set(["INITIATED", "QUEUED", "SENT", "CONFIRMED", "COMPLETE", "CLEARED"]);
12
+ const CIRCLE_TX_FAIL_STATES = new Set(["DENIED", "FAILED", "CANCELLED", "STUCK"]);
13
+
14
+ function usage() {
15
+ return [
16
+ "Usage:",
17
+ " node scripts/settlement/x402-batch-worker.mjs [options]",
18
+ "",
19
+ "Options:",
20
+ " --artifact-root <dir> Source artifact root (default: artifacts/mcp-paid-exa)",
21
+ " --registry <file> Provider payout registry file (required)",
22
+ " --state <file> Worker state file (default: artifacts/settlement/x402-batch-state.json)",
23
+ " --out-dir <dir> Output run directory (default: artifacts/settlement/x402-batches/<timestamp>)",
24
+ " --dry-run Compute without mutating state",
25
+ " --execute-circle Submit pending batches to Circle payout rails",
26
+ " --circle-mode <mode> Circle mode: stub|sandbox|production (default: env X402_BATCH_CIRCLE_MODE or stub)",
27
+ " --max-payout-attempts <n> Max retries for failed payouts (default: 3)",
28
+ " --help Show this help",
29
+ "",
30
+ "Circle env when --execute-circle and mode!=stub:",
31
+ " CIRCLE_API_KEY",
32
+ " CIRCLE_WALLET_ID_SPEND",
33
+ " CIRCLE_TOKEN_ID_USDC",
34
+ " ENTITY_SECRET or CIRCLE_ENTITY_SECRET_HEX (preferred, per-request ciphertext)",
35
+ " CIRCLE_ENTITY_SECRET_CIPHERTEXT_TEMPLATE (recommended) or",
36
+ " CIRCLE_ENTITY_SECRET_CIPHERTEXT + CIRCLE_ALLOW_STATIC_ENTITY_SECRET=1",
37
+ "Optional:",
38
+ " CIRCLE_BASE_URL",
39
+ " CIRCLE_BLOCKCHAIN",
40
+ " CIRCLE_FEE_LEVEL",
41
+ " CIRCLE_TIMEOUT_MS"
42
+ ].join("\n");
43
+ }
44
+
45
+ function parseArgs(argv) {
46
+ const out = {
47
+ artifactRoot: "artifacts/mcp-paid-exa",
48
+ registryPath: null,
49
+ statePath: "artifacts/settlement/x402-batch-state.json",
50
+ outDir: null,
51
+ dryRun: false,
52
+ executeCircle: false,
53
+ circleMode: null,
54
+ maxPayoutAttempts: 3,
55
+ help: false
56
+ };
57
+ for (let i = 0; i < argv.length; i += 1) {
58
+ const arg = String(argv[i] ?? "").trim();
59
+ if (!arg) continue;
60
+ if (arg === "--help" || arg === "-h") {
61
+ out.help = true;
62
+ continue;
63
+ }
64
+ if (arg === "--dry-run") {
65
+ out.dryRun = true;
66
+ continue;
67
+ }
68
+ if (arg === "--execute-circle") {
69
+ out.executeCircle = true;
70
+ continue;
71
+ }
72
+ if (arg === "--artifact-root") {
73
+ const value = String(argv[i + 1] ?? "").trim();
74
+ if (!value) throw new Error("--artifact-root requires a value");
75
+ out.artifactRoot = value;
76
+ i += 1;
77
+ continue;
78
+ }
79
+ if (arg === "--registry") {
80
+ const value = String(argv[i + 1] ?? "").trim();
81
+ if (!value) throw new Error("--registry requires a value");
82
+ out.registryPath = value;
83
+ i += 1;
84
+ continue;
85
+ }
86
+ if (arg === "--state") {
87
+ const value = String(argv[i + 1] ?? "").trim();
88
+ if (!value) throw new Error("--state requires a value");
89
+ out.statePath = value;
90
+ i += 1;
91
+ continue;
92
+ }
93
+ if (arg === "--out-dir") {
94
+ const value = String(argv[i + 1] ?? "").trim();
95
+ if (!value) throw new Error("--out-dir requires a value");
96
+ out.outDir = value;
97
+ i += 1;
98
+ continue;
99
+ }
100
+ if (arg === "--circle-mode") {
101
+ const value = String(argv[i + 1] ?? "").trim();
102
+ if (!value) throw new Error("--circle-mode requires a value");
103
+ out.circleMode = value;
104
+ i += 1;
105
+ continue;
106
+ }
107
+ if (arg === "--max-payout-attempts") {
108
+ const value = Number(argv[i + 1]);
109
+ if (!Number.isSafeInteger(value) || value <= 0) throw new Error("--max-payout-attempts must be a positive integer");
110
+ out.maxPayoutAttempts = value;
111
+ i += 1;
112
+ continue;
113
+ }
114
+ throw new Error(`unknown argument: ${arg}`);
115
+ }
116
+ if (!out.registryPath && !out.help) throw new Error("--registry is required");
117
+ return out;
118
+ }
119
+
120
+ function nowIso() {
121
+ return new Date().toISOString();
122
+ }
123
+
124
+ function readJsonFile(filePath) {
125
+ const raw = fs.readFileSync(filePath, "utf8");
126
+ return JSON.parse(raw);
127
+ }
128
+
129
+ function writeJsonFile(filePath, value) {
130
+ fs.mkdirSync(path.dirname(filePath), { recursive: true });
131
+ fs.writeFileSync(filePath, `${JSON.stringify(value, null, 2)}\n`, "utf8");
132
+ }
133
+
134
+ function normalizeIso(value, fallback = null) {
135
+ if (typeof value !== "string" || value.trim() === "") return fallback;
136
+ const t = Date.parse(value);
137
+ if (!Number.isFinite(t)) return fallback;
138
+ return new Date(t).toISOString();
139
+ }
140
+
141
+ function normalizeNonNegativeInt(value, fallback = 0) {
142
+ const n = Number(value);
143
+ if (!Number.isSafeInteger(n) || n < 0) return fallback;
144
+ return n;
145
+ }
146
+
147
+ function normalizeCurrency(value, fallback = "USD") {
148
+ const raw = typeof value === "string" && value.trim() !== "" ? value.trim().toUpperCase() : fallback;
149
+ if (!/^[A-Z][A-Z0-9_]{2,11}$/.test(raw)) return fallback;
150
+ return raw;
151
+ }
152
+
153
+ function normalizeCircleMode(value) {
154
+ const raw = String(value ?? "").trim().toLowerCase();
155
+ if (!raw || raw === "stub" || raw === "test") return "stub";
156
+ if (raw === "sandbox") return "sandbox";
157
+ if (raw === "production" || raw === "prod") return "production";
158
+ throw new Error("circle mode must be stub|sandbox|production");
159
+ }
160
+
161
+ function readEnv(name, fallback = null) {
162
+ const raw = process.env[name];
163
+ if (raw === undefined || raw === null || String(raw).trim() === "") return fallback;
164
+ return String(raw).trim();
165
+ }
166
+
167
+ function readBoolEnv(name, fallback = false) {
168
+ const raw = readEnv(name, null);
169
+ if (raw === null) return fallback;
170
+ const value = raw.toLowerCase();
171
+ if (value === "1" || value === "true" || value === "yes" || value === "on") return true;
172
+ if (value === "0" || value === "false" || value === "no" || value === "off") return false;
173
+ return fallback;
174
+ }
175
+
176
+ function normalizeCircleState(value) {
177
+ if (value === null || value === undefined || String(value).trim() === "") return null;
178
+ return String(value).trim().toUpperCase();
179
+ }
180
+
181
+ function stableUuidV4FromString(input) {
182
+ const text = String(input ?? "").trim();
183
+ if (!text) throw new Error("uuid input is required");
184
+ const buf = Buffer.from(sha256Hex(text).slice(0, 32), "hex");
185
+ buf[6] = (buf[6] & 0x0f) | 0x40;
186
+ buf[8] = (buf[8] & 0x3f) | 0x80;
187
+ const hex = buf.toString("hex");
188
+ return `${hex.slice(0, 8)}-${hex.slice(8, 12)}-${hex.slice(12, 16)}-${hex.slice(16, 20)}-${hex.slice(20, 32)}`;
189
+ }
190
+
191
+ function centsToAssetAmountString(amountCents) {
192
+ const cents = normalizeNonNegativeInt(amountCents, -1);
193
+ if (cents <= 0) throw new Error("amountCents must be a positive integer");
194
+ const whole = Math.floor(cents / 100);
195
+ const fraction = String(cents % 100).padStart(2, "0");
196
+ return `${whole}.${fraction}`;
197
+ }
198
+
199
+ function loadRegistry(registryPath) {
200
+ const resolved = path.resolve(process.cwd(), registryPath);
201
+ const payload = readJsonFile(resolved);
202
+ if (!payload || typeof payload !== "object" || Array.isArray(payload)) throw new Error("registry must be an object");
203
+ if (payload.schemaVersion !== "X402ProviderPayoutRegistry.v1") {
204
+ throw new Error("registry.schemaVersion must be X402ProviderPayoutRegistry.v1");
205
+ }
206
+ const providers = Array.isArray(payload.providers) ? payload.providers : [];
207
+ const byProvider = new Map();
208
+ for (const row of providers) {
209
+ if (!row || typeof row !== "object" || Array.isArray(row)) continue;
210
+ const providerId = typeof row.providerId === "string" && row.providerId.trim() !== "" ? row.providerId.trim() : null;
211
+ if (!providerId) continue;
212
+ const destination = row.destination && typeof row.destination === "object" && !Array.isArray(row.destination) ? row.destination : null;
213
+ if (!destination) continue;
214
+ byProvider.set(providerId, destination);
215
+ }
216
+ return { resolvedPath: resolved, providerDestinations: byProvider };
217
+ }
218
+
219
+ function loadWorkerState(statePath) {
220
+ const resolved = path.resolve(process.cwd(), statePath);
221
+ if (!fs.existsSync(resolved)) {
222
+ return {
223
+ resolvedPath: resolved,
224
+ state: {
225
+ schemaVersion: "X402BatchWorkerState.v1",
226
+ updatedAt: null,
227
+ processedGateIds: {},
228
+ batches: []
229
+ }
230
+ };
231
+ }
232
+ const payload = readJsonFile(resolved);
233
+ if (!payload || typeof payload !== "object" || Array.isArray(payload)) throw new Error("state must be an object");
234
+ if (payload.schemaVersion !== "X402BatchWorkerState.v1") {
235
+ throw new Error("state.schemaVersion must be X402BatchWorkerState.v1");
236
+ }
237
+ const processedGateIds =
238
+ payload.processedGateIds && typeof payload.processedGateIds === "object" && !Array.isArray(payload.processedGateIds)
239
+ ? payload.processedGateIds
240
+ : {};
241
+ const batches = Array.isArray(payload.batches) ? payload.batches : [];
242
+ return {
243
+ resolvedPath: resolved,
244
+ state: {
245
+ schemaVersion: "X402BatchWorkerState.v1",
246
+ updatedAt: normalizeIso(payload.updatedAt, null),
247
+ processedGateIds,
248
+ batches
249
+ }
250
+ };
251
+ }
252
+
253
+ function collectArtifactRuns(artifactRoot) {
254
+ const resolvedRoot = path.resolve(process.cwd(), artifactRoot);
255
+ if (!fs.existsSync(resolvedRoot)) return [];
256
+ const entries = fs
257
+ .readdirSync(resolvedRoot, { withFileTypes: true })
258
+ .filter((row) => row.isDirectory())
259
+ .map((row) => row.name)
260
+ .sort((a, b) => a.localeCompare(b));
261
+
262
+ const out = [];
263
+ for (const dirName of entries) {
264
+ const dirPath = path.join(resolvedRoot, dirName);
265
+ const summaryPath = path.join(dirPath, "summary.json");
266
+ const gateStatePath = path.join(dirPath, "gate-state.json");
267
+ if (!fs.existsSync(summaryPath) || !fs.existsSync(gateStatePath)) continue;
268
+
269
+ const summary = readJsonFile(summaryPath);
270
+ const gateState = readJsonFile(gateStatePath);
271
+ if (summary?.ok !== true) continue;
272
+ const gate = gateState?.gate;
273
+ const settlement = gateState?.settlement;
274
+ if (!gate || typeof gate !== "object") continue;
275
+
276
+ const gateId = typeof gate.gateId === "string" && gate.gateId.trim() !== "" ? gate.gateId.trim() : null;
277
+ if (!gateId) continue;
278
+ const providerId = typeof gate.payeeAgentId === "string" && gate.payeeAgentId.trim() !== "" ? gate.payeeAgentId.trim() : null;
279
+ if (!providerId) continue;
280
+
281
+ const releasedAmountCents = normalizeNonNegativeInt(gate?.decision?.releasedAmountCents ?? settlement?.releasedAmountCents ?? 0, 0);
282
+ const refundedAmountCents = normalizeNonNegativeInt(gate?.decision?.refundedAmountCents ?? settlement?.refundedAmountCents ?? 0, 0);
283
+ const currency = normalizeCurrency(gate?.terms?.currency ?? settlement?.currency ?? "USD");
284
+ const resolvedAt = normalizeIso(gate?.resolvedAt ?? settlement?.resolvedAt ?? summary?.timestamps?.completedAt, null);
285
+ const settlementStatus =
286
+ typeof settlement?.status === "string" && settlement.status.trim() !== "" ? settlement.status.trim().toLowerCase() : null;
287
+ const reserveId =
288
+ typeof summary?.circleReserveId === "string" && summary.circleReserveId.trim() !== ""
289
+ ? summary.circleReserveId.trim()
290
+ : typeof gate?.authorization?.reserve?.reserveId === "string" && gate.authorization.reserve.reserveId.trim() !== ""
291
+ ? gate.authorization.reserve.reserveId.trim()
292
+ : null;
293
+ const receiptId =
294
+ typeof settlement?.decisionTrace?.settlementReceipt?.receiptId === "string" &&
295
+ settlement.decisionTrace.settlementReceipt.receiptId.trim() !== ""
296
+ ? settlement.decisionTrace.settlementReceipt.receiptId.trim()
297
+ : null;
298
+ const decisionId =
299
+ typeof settlement?.decisionTrace?.settlementReceipt?.decisionRef?.decisionId === "string" &&
300
+ settlement.decisionTrace.settlementReceipt.decisionRef.decisionId.trim() !== ""
301
+ ? settlement.decisionTrace.settlementReceipt.decisionRef.decisionId.trim()
302
+ : null;
303
+
304
+ out.push({
305
+ gateId,
306
+ runId: typeof gate.runId === "string" ? gate.runId : null,
307
+ providerId,
308
+ releasedAmountCents,
309
+ refundedAmountCents,
310
+ currency,
311
+ settlementStatus,
312
+ resolvedAt,
313
+ reserveId,
314
+ receiptId,
315
+ decisionId,
316
+ artifactDir: dirPath
317
+ });
318
+ }
319
+ return out;
320
+ }
321
+
322
+ function groupByProviderAndCurrency(rows) {
323
+ const groups = new Map();
324
+ for (const row of rows) {
325
+ const key = `${row.providerId}\n${row.currency}`;
326
+ const existing = groups.get(key) ?? [];
327
+ existing.push(row);
328
+ groups.set(key, existing);
329
+ }
330
+ return groups;
331
+ }
332
+
333
+ function buildBatchId({ providerId, currency, gateIds }) {
334
+ const material = `${providerId}\n${currency}\n${gateIds.slice().sort((a, b) => a.localeCompare(b)).join("\n")}`;
335
+ return `pbatch_${sha256Hex(material).slice(0, 24)}`;
336
+ }
337
+
338
+ function maybeSignManifest(manifest) {
339
+ const publicKeyPem =
340
+ typeof process.env.SETTLD_BATCH_SIGNER_PUBLIC_KEY_PEM === "string" && process.env.SETTLD_BATCH_SIGNER_PUBLIC_KEY_PEM.trim() !== ""
341
+ ? process.env.SETTLD_BATCH_SIGNER_PUBLIC_KEY_PEM
342
+ : null;
343
+ const privateKeyPem =
344
+ typeof process.env.SETTLD_BATCH_SIGNER_PRIVATE_KEY_PEM === "string" && process.env.SETTLD_BATCH_SIGNER_PRIVATE_KEY_PEM.trim() !== ""
345
+ ? process.env.SETTLD_BATCH_SIGNER_PRIVATE_KEY_PEM
346
+ : null;
347
+ if (!publicKeyPem || !privateKeyPem) return null;
348
+ const canonical = canonicalJsonStringify(manifest);
349
+ const payloadHash = sha256Hex(canonical);
350
+ const signatureBase64 = signHashHexEd25519(payloadHash, privateKeyPem);
351
+ return {
352
+ schemaVersion: "X402BatchManifestSignature.v1",
353
+ algorithm: "ed25519",
354
+ keyId: keyIdFromPublicKeyPem(publicKeyPem),
355
+ payloadHash,
356
+ signatureBase64
357
+ };
358
+ }
359
+
360
+ function normalizeBatchRecord(row, { defaultMaxAttempts }) {
361
+ if (!row || typeof row !== "object" || Array.isArray(row)) return null;
362
+ const batchId = typeof row.batchId === "string" && row.batchId.trim() !== "" ? row.batchId.trim() : null;
363
+ if (!batchId) return null;
364
+ const providerId = typeof row.providerId === "string" && row.providerId.trim() !== "" ? row.providerId.trim() : null;
365
+ if (!providerId) return null;
366
+ const currency = normalizeCurrency(row.currency ?? "USD");
367
+ const gates = Array.isArray(row.gates) ? row.gates.filter((g) => g && typeof g === "object" && !Array.isArray(g) && typeof g.gateId === "string") : [];
368
+ const totalAmountCents =
369
+ Number.isSafeInteger(Number(row.totalAmountCents)) && Number(row.totalAmountCents) > 0
370
+ ? Number(row.totalAmountCents)
371
+ : gates.reduce((sum, gate) => sum + normalizeNonNegativeInt(gate.releasedAmountCents, 0), 0);
372
+ const gateCount = Number.isSafeInteger(Number(row.gateCount)) ? Number(row.gateCount) : gates.length;
373
+ const destination = row.destination && typeof row.destination === "object" && !Array.isArray(row.destination) ? row.destination : null;
374
+ const payout = row.payout && typeof row.payout === "object" && !Array.isArray(row.payout) ? row.payout : {};
375
+ const status =
376
+ typeof payout.status === "string" && payout.status.trim() !== "" ? payout.status.trim().toLowerCase() : "manifest_only_pending";
377
+ return {
378
+ schemaVersion: "X402ProviderPayoutBatch.v1",
379
+ batchId,
380
+ createdAt: normalizeIso(row.createdAt, nowIso()),
381
+ providerId,
382
+ currency,
383
+ totalAmountCents,
384
+ gateCount,
385
+ destination,
386
+ settlementMethod: typeof row.settlementMethod === "string" && row.settlementMethod.trim() !== "" ? row.settlementMethod.trim() : "deferred_batch_manifest_only",
387
+ gates,
388
+ payout: {
389
+ status,
390
+ attempts: normalizeNonNegativeInt(payout.attempts, 0),
391
+ maxAttempts: normalizeNonNegativeInt(payout.maxAttempts, defaultMaxAttempts),
392
+ idempotencyKey:
393
+ typeof payout.idempotencyKey === "string" && payout.idempotencyKey.trim() !== ""
394
+ ? payout.idempotencyKey.trim()
395
+ : stableUuidV4FromString(`x402-batch:${batchId}`),
396
+ transactionId: typeof payout.transactionId === "string" && payout.transactionId.trim() !== "" ? payout.transactionId.trim() : null,
397
+ circleState: typeof payout.circleState === "string" && payout.circleState.trim() !== "" ? payout.circleState.trim() : null,
398
+ lastAttemptAt: normalizeIso(payout.lastAttemptAt, null),
399
+ lastError: payout.lastError ?? null,
400
+ submittedAt: normalizeIso(payout.submittedAt, null),
401
+ providerResponse: payout.providerResponse ?? null
402
+ }
403
+ };
404
+ }
405
+
406
+ function buildNewBatch({ providerId, currency, rows, destination, nowAt, maxAttempts }) {
407
+ const gateIds = rows.map((row) => row.gateId);
408
+ const batchId = buildBatchId({ providerId, currency, gateIds });
409
+ const totalAmountCents = rows.reduce((sum, row) => sum + row.releasedAmountCents, 0);
410
+ return {
411
+ schemaVersion: "X402ProviderPayoutBatch.v1",
412
+ batchId,
413
+ createdAt: nowAt,
414
+ providerId,
415
+ currency,
416
+ totalAmountCents,
417
+ gateCount: rows.length,
418
+ destination,
419
+ settlementMethod: "deferred_batch_manifest_only",
420
+ gates: rows.map((row) => ({
421
+ gateId: row.gateId,
422
+ runId: row.runId,
423
+ releasedAmountCents: row.releasedAmountCents,
424
+ refundedAmountCents: row.refundedAmountCents,
425
+ resolvedAt: row.resolvedAt,
426
+ reserveId: row.reserveId,
427
+ receiptId: row.receiptId ?? null,
428
+ decisionId: row.decisionId ?? null,
429
+ artifactDir: row.artifactDir
430
+ })),
431
+ payout: {
432
+ status: "manifest_only_pending",
433
+ attempts: 0,
434
+ maxAttempts,
435
+ idempotencyKey: stableUuidV4FromString(`x402-batch:${batchId}`),
436
+ transactionId: null,
437
+ circleState: null,
438
+ lastAttemptAt: null,
439
+ lastError: null,
440
+ submittedAt: null,
441
+ providerResponse: null
442
+ }
443
+ };
444
+ }
445
+
446
+ function buildPayoutReconciliation({ batches, generatedAt, artifactRoot, registryPath, statePath }) {
447
+ const safeBatches = Array.isArray(batches) ? batches : [];
448
+ const rows = [];
449
+ let totalDeclaredAmountCents = 0;
450
+ let totalRecomputedAmountCents = 0;
451
+ let totalGateCount = 0;
452
+
453
+ for (const batch of safeBatches) {
454
+ if (!batch || typeof batch !== "object" || Array.isArray(batch)) continue;
455
+ const gates = Array.isArray(batch.gates) ? batch.gates : [];
456
+ const declaredAmountCents = normalizeNonNegativeInt(batch.totalAmountCents, 0);
457
+ const recomputedAmountCents = gates.reduce((sum, gate) => sum + normalizeNonNegativeInt(gate?.releasedAmountCents, 0), 0);
458
+ const driftCents = declaredAmountCents - recomputedAmountCents;
459
+ const receiptIds = [
460
+ ...new Set(
461
+ gates
462
+ .map((gate) => (typeof gate?.receiptId === "string" && gate.receiptId.trim() !== "" ? gate.receiptId.trim() : null))
463
+ .filter(Boolean)
464
+ )
465
+ ].sort((a, b) => a.localeCompare(b));
466
+ const decisionIds = [
467
+ ...new Set(
468
+ gates
469
+ .map((gate) => (typeof gate?.decisionId === "string" && gate.decisionId.trim() !== "" ? gate.decisionId.trim() : null))
470
+ .filter(Boolean)
471
+ )
472
+ ].sort((a, b) => a.localeCompare(b));
473
+ rows.push({
474
+ schemaVersion: "X402PayoutBatchReconciliationRow.v1",
475
+ batchId: batch.batchId ?? null,
476
+ providerId: batch.providerId ?? null,
477
+ currency: batch.currency ?? null,
478
+ gateCount: gates.length,
479
+ declaredAmountCents,
480
+ recomputedAmountCents,
481
+ driftCents,
482
+ receiptIds,
483
+ decisionIds,
484
+ gates: gates.map((gate) => ({
485
+ gateId: gate?.gateId ?? null,
486
+ runId: gate?.runId ?? null,
487
+ releasedAmountCents: normalizeNonNegativeInt(gate?.releasedAmountCents, 0),
488
+ refundedAmountCents: normalizeNonNegativeInt(gate?.refundedAmountCents, 0),
489
+ reserveId: gate?.reserveId ?? null,
490
+ receiptId: gate?.receiptId ?? null,
491
+ decisionId: gate?.decisionId ?? null
492
+ }))
493
+ });
494
+ totalDeclaredAmountCents += declaredAmountCents;
495
+ totalRecomputedAmountCents += recomputedAmountCents;
496
+ totalGateCount += gates.length;
497
+ }
498
+
499
+ return {
500
+ schemaVersion: "X402PayoutReconciliation.v1",
501
+ generatedAt,
502
+ artifactRoot,
503
+ registryPath,
504
+ statePath,
505
+ ok: rows.every((row) => row.driftCents === 0),
506
+ totals: {
507
+ batchCount: rows.length,
508
+ gateCount: totalGateCount,
509
+ declaredAmountCents: totalDeclaredAmountCents,
510
+ recomputedAmountCents: totalRecomputedAmountCents,
511
+ driftCents: totalDeclaredAmountCents - totalRecomputedAmountCents
512
+ },
513
+ batches: rows
514
+ };
515
+ }
516
+
517
+ function normalizeEntitySecretProvider() {
518
+ const template = readEnv("CIRCLE_ENTITY_SECRET_CIPHERTEXT_TEMPLATE", null);
519
+ if (template) {
520
+ return () => template.replaceAll("{{uuid}}", crypto.randomUUID());
521
+ }
522
+ const staticCiphertext = readEnv("CIRCLE_ENTITY_SECRET_CIPHERTEXT", null);
523
+ if (staticCiphertext && readBoolEnv("CIRCLE_ALLOW_STATIC_ENTITY_SECRET", false)) {
524
+ return () => staticCiphertext;
525
+ }
526
+ return null;
527
+ }
528
+
529
+ function normalizeEntitySecretHex(value) {
530
+ const raw = String(value ?? "").trim();
531
+ if (!raw) return null;
532
+ if (!/^[0-9a-fA-F]{64}$/.test(raw)) throw new Error("ENTITY_SECRET must be a 64-character hex string");
533
+ return raw.toLowerCase();
534
+ }
535
+
536
+ function normalizePublicKeyPem(value) {
537
+ const raw = String(value ?? "").trim();
538
+ if (!raw) throw new Error("entity public key is missing");
539
+ if (raw.includes("BEGIN PUBLIC KEY")) return raw.replace(/\\n/g, "\n");
540
+ const chunks = raw.match(/.{1,64}/g) ?? [];
541
+ return `-----BEGIN PUBLIC KEY-----\n${chunks.join("\n")}\n-----END PUBLIC KEY-----\n`;
542
+ }
543
+
544
+ function createDynamicEntitySecretProvider({ apiKey, baseUrl, timeoutMs, entitySecretHex }) {
545
+ const secret = normalizeEntitySecretHex(entitySecretHex);
546
+ if (!secret) return null;
547
+ let cachedPublicKeyPem = null;
548
+ return async () => {
549
+ if (!cachedPublicKeyPem) {
550
+ const payload = await fetchCircleJson({
551
+ runtime: {
552
+ apiKey,
553
+ baseUrl,
554
+ timeoutMs
555
+ },
556
+ method: "GET",
557
+ endpoint: "/v1/w3s/config/entity/publicKey"
558
+ });
559
+ cachedPublicKeyPem = normalizePublicKeyPem(payload?.data?.publicKey);
560
+ }
561
+ return crypto
562
+ .publicEncrypt(
563
+ {
564
+ key: cachedPublicKeyPem,
565
+ padding: crypto.constants.RSA_PKCS1_OAEP_PADDING,
566
+ oaepHash: "sha256"
567
+ },
568
+ Buffer.from(secret, "hex")
569
+ )
570
+ .toString("base64");
571
+ };
572
+ }
573
+
574
+ function createCirclePayoutRuntime({ mode }) {
575
+ const normalizedMode = normalizeCircleMode(mode);
576
+ if (normalizedMode === "stub") {
577
+ return {
578
+ mode: "stub"
579
+ };
580
+ }
581
+
582
+ const apiKey = readEnv("CIRCLE_API_KEY", null);
583
+ const sourceWalletId = readEnv("CIRCLE_WALLET_ID_SPEND", null);
584
+ const tokenId = readEnv("CIRCLE_TOKEN_ID_USDC", null);
585
+ const baseUrl = readEnv("CIRCLE_BASE_URL", normalizedMode === "production" ? "https://api.circle.com" : "https://api-sandbox.circle.com");
586
+ const blockchain = readEnv("CIRCLE_BLOCKCHAIN", normalizedMode === "production" ? "BASE" : "BASE-SEPOLIA");
587
+ const feeLevel = String(readEnv("CIRCLE_FEE_LEVEL", "MEDIUM") ?? "MEDIUM")
588
+ .trim()
589
+ .toUpperCase();
590
+ const timeoutMs = Number(readEnv("CIRCLE_TIMEOUT_MS", "20000"));
591
+ const staticEntitySecretProvider = normalizeEntitySecretProvider();
592
+ const entitySecretHex = normalizeEntitySecretHex(readEnv("CIRCLE_ENTITY_SECRET_HEX", readEnv("ENTITY_SECRET", null)));
593
+ const entitySecretProvider =
594
+ createDynamicEntitySecretProvider({
595
+ apiKey,
596
+ baseUrl,
597
+ timeoutMs,
598
+ entitySecretHex
599
+ }) ?? staticEntitySecretProvider;
600
+
601
+ const missing = [];
602
+ if (!apiKey) missing.push("CIRCLE_API_KEY");
603
+ if (!sourceWalletId) missing.push("CIRCLE_WALLET_ID_SPEND");
604
+ if (!tokenId) missing.push("CIRCLE_TOKEN_ID_USDC");
605
+ if (!entitySecretProvider) {
606
+ missing.push("CIRCLE_ENTITY_SECRET_CIPHERTEXT_TEMPLATE (or CIRCLE_ENTITY_SECRET_CIPHERTEXT + CIRCLE_ALLOW_STATIC_ENTITY_SECRET=1)");
607
+ }
608
+ if (missing.length > 0) {
609
+ throw new Error(`circle payout execution requires env: ${missing.join(", ")}`);
610
+ }
611
+ if (!Number.isFinite(timeoutMs) || timeoutMs <= 0) throw new Error("CIRCLE_TIMEOUT_MS must be a positive number");
612
+
613
+ return {
614
+ mode: normalizedMode,
615
+ apiKey,
616
+ sourceWalletId,
617
+ tokenId,
618
+ baseUrl: String(baseUrl).replace(/\/+$/, ""),
619
+ blockchain,
620
+ feeLevel,
621
+ timeoutMs,
622
+ getEntitySecretCiphertext: entitySecretProvider
623
+ };
624
+ }
625
+
626
+ function extractCircleTransaction(payload) {
627
+ if (!payload || typeof payload !== "object" || Array.isArray(payload)) return { id: null, state: null };
628
+ const candidates = [payload];
629
+ if (payload.data && typeof payload.data === "object" && !Array.isArray(payload.data)) candidates.push(payload.data);
630
+ if (payload.transaction && typeof payload.transaction === "object" && !Array.isArray(payload.transaction)) candidates.push(payload.transaction);
631
+ if (Array.isArray(payload.transactions) && payload.transactions.length > 0 && payload.transactions[0] && typeof payload.transactions[0] === "object") {
632
+ candidates.push(payload.transactions[0]);
633
+ }
634
+ for (const row of candidates) {
635
+ const id =
636
+ (typeof row.id === "string" && row.id.trim() !== "" ? row.id.trim() : null) ??
637
+ (typeof row.transactionId === "string" && row.transactionId.trim() !== "" ? row.transactionId.trim() : null);
638
+ const state = normalizeCircleState(row.state ?? row.status ?? null);
639
+ if (id || state) return { id, state };
640
+ }
641
+ return { id: null, state: null };
642
+ }
643
+
644
+ async function parseResponseBody(response) {
645
+ const text = await response.text();
646
+ if (!text) return { text: "", json: null };
647
+ try {
648
+ return { text, json: JSON.parse(text) };
649
+ } catch {
650
+ return { text, json: null };
651
+ }
652
+ }
653
+
654
+ async function fetchWithTimeout(fetchFn, url, init, timeoutMs) {
655
+ const controller = new AbortController();
656
+ const timer = setTimeout(() => controller.abort(new Error("timeout")), timeoutMs);
657
+ try {
658
+ return await fetchFn(url, { ...init, signal: controller.signal });
659
+ } finally {
660
+ clearTimeout(timer);
661
+ }
662
+ }
663
+
664
+ async function fetchCircleJson({ runtime, method, endpoint, body = null }) {
665
+ const fetchFn = typeof fetch === "function" ? fetch : null;
666
+ if (!fetchFn) throw new Error("global fetch is unavailable");
667
+ const url = new URL(endpoint, runtime.baseUrl).toString();
668
+ const res = await fetchWithTimeout(
669
+ fetchFn,
670
+ url,
671
+ {
672
+ method,
673
+ headers: {
674
+ authorization: `Bearer ${runtime.apiKey}`,
675
+ "content-type": "application/json; charset=utf-8",
676
+ "x-request-id": crypto.randomUUID()
677
+ },
678
+ body: body === null ? undefined : JSON.stringify(body)
679
+ },
680
+ runtime.timeoutMs
681
+ );
682
+ const parsed = await parseResponseBody(res);
683
+ if (!res.ok) {
684
+ const baseDetail = parsed.json?.message ?? parsed.json?.error ?? parsed.text ?? `HTTP ${res.status}`;
685
+ const validationErrors = Array.isArray(parsed.json?.errors) ? parsed.json.errors : null;
686
+ const detail = validationErrors ? `${baseDetail} ${JSON.stringify(validationErrors)}` : baseDetail;
687
+ throw new Error(`Circle ${method} ${endpoint} failed: ${detail}`);
688
+ }
689
+ return parsed.json;
690
+ }
691
+
692
+ async function resolveWalletAddress({ runtime, walletId, cache }) {
693
+ const key = String(walletId ?? "").trim();
694
+ if (!key) throw new Error("walletId is required");
695
+ if (cache.has(key)) return cache.get(key);
696
+ const payload = await fetchCircleJson({
697
+ runtime,
698
+ method: "GET",
699
+ endpoint: `/v1/w3s/wallets/${encodeURIComponent(key)}`
700
+ });
701
+ const candidates = [payload, payload?.wallet, payload?.data, payload?.data?.wallet]
702
+ .filter((row) => row && typeof row === "object" && !Array.isArray(row));
703
+ if (Array.isArray(payload?.data?.wallets)) {
704
+ for (const row of payload.data.wallets) {
705
+ if (row && typeof row === "object" && !Array.isArray(row)) candidates.push(row);
706
+ }
707
+ }
708
+ let address = null;
709
+ for (const row of candidates) {
710
+ if (typeof row.address === "string" && row.address.trim() !== "") {
711
+ address = row.address.trim();
712
+ break;
713
+ }
714
+ if (typeof row.blockchainAddress === "string" && row.blockchainAddress.trim() !== "") {
715
+ address = row.blockchainAddress.trim();
716
+ break;
717
+ }
718
+ if (Array.isArray(row.addresses) && row.addresses.length > 0) {
719
+ const first = row.addresses[0];
720
+ if (first && typeof first === "object" && !Array.isArray(first) && typeof first.address === "string" && first.address.trim() !== "") {
721
+ address = first.address.trim();
722
+ break;
723
+ }
724
+ }
725
+ }
726
+ if (!address) throw new Error(`unable to resolve wallet address for ${walletId}`);
727
+ cache.set(key, address);
728
+ return address;
729
+ }
730
+
731
+ function classifyCircleTransferState(state) {
732
+ if (!state) return "unknown";
733
+ if (CIRCLE_TX_OK_STATES.has(state)) return "submitted";
734
+ if (CIRCLE_TX_FAIL_STATES.has(state)) return "failed";
735
+ return "unknown";
736
+ }
737
+
738
+ async function fetchCircleTransactionById({ runtime, transactionId }) {
739
+ const txId = String(transactionId ?? "").trim();
740
+ if (!txId) throw new Error("transactionId is required");
741
+ const endpoints = [`/v1/w3s/transactions/${encodeURIComponent(txId)}`, `/v1/w3s/developer/transactions/${encodeURIComponent(txId)}`];
742
+ let lastErr = null;
743
+ for (const endpoint of endpoints) {
744
+ try {
745
+ const payload = await fetchCircleJson({ runtime, method: "GET", endpoint });
746
+ return extractCircleTransaction(payload);
747
+ } catch (err) {
748
+ lastErr = err;
749
+ }
750
+ }
751
+ throw lastErr ?? new Error(`unable to fetch Circle transaction ${txId}`);
752
+ }
753
+
754
+ async function executeCirclePayoutBatch({ runtime, batch, walletAddressCache }) {
755
+ if (runtime.mode === "stub") {
756
+ const fakeTxId = `circle_tx_${sha256Hex(`stub:${batch.batchId}`).slice(0, 24)}`;
757
+ return {
758
+ ok: true,
759
+ transactionId: fakeTxId,
760
+ circleState: "COMPLETE",
761
+ response: {
762
+ mode: "stub",
763
+ destinationType: batch.destination?.type ?? null
764
+ }
765
+ };
766
+ }
767
+
768
+ const destination = batch.destination;
769
+ if (!destination || typeof destination !== "object" || Array.isArray(destination)) {
770
+ return { ok: false, error: "batch destination is missing" };
771
+ }
772
+
773
+ let destinationAddress = null;
774
+ let destinationWalletId = null;
775
+ let blockchain = runtime.blockchain;
776
+
777
+ const destinationType = typeof destination.type === "string" ? destination.type.trim().toLowerCase() : "";
778
+ if (destinationType === "circle_wallet") {
779
+ destinationWalletId = typeof destination.walletId === "string" && destination.walletId.trim() !== "" ? destination.walletId.trim() : null;
780
+ if (!destinationWalletId) return { ok: false, error: "circle_wallet destination requires walletId" };
781
+ destinationAddress = await resolveWalletAddress({ runtime, walletId: destinationWalletId, cache: walletAddressCache });
782
+ if (typeof destination.blockchain === "string" && destination.blockchain.trim() !== "") blockchain = destination.blockchain.trim();
783
+ } else if (destinationType === "onchain_address") {
784
+ destinationAddress = typeof destination.address === "string" && destination.address.trim() !== "" ? destination.address.trim() : null;
785
+ if (!destinationAddress) return { ok: false, error: "onchain_address destination requires address" };
786
+ if (typeof destination.blockchain === "string" && destination.blockchain.trim() !== "") blockchain = destination.blockchain.trim();
787
+ } else {
788
+ return { ok: false, error: `unsupported destination.type: ${String(destination.type ?? "unknown")}` };
789
+ }
790
+
791
+ const body = {
792
+ idempotencyKey: batch.payout.idempotencyKey,
793
+ walletId: runtime.sourceWalletId,
794
+ destinationAddress,
795
+ tokenId: runtime.tokenId,
796
+ blockchain,
797
+ feeLevel: runtime.feeLevel,
798
+ entitySecretCiphertext: await runtime.getEntitySecretCiphertext(),
799
+ amounts: [centsToAssetAmountString(batch.totalAmountCents)]
800
+ };
801
+ if (destinationWalletId) body.destinationWalletId = destinationWalletId;
802
+
803
+ const payload = await fetchCircleJson({
804
+ runtime,
805
+ method: "POST",
806
+ endpoint: "/v1/w3s/developer/transactions/transfer",
807
+ body
808
+ });
809
+ const extracted = extractCircleTransaction(payload);
810
+ const transactionId = extracted.id;
811
+ let circleState = normalizeCircleState(extracted.state);
812
+
813
+ if (!transactionId) return { ok: false, error: "circle response missing transaction id" };
814
+ if (!circleState) {
815
+ const fetched = await fetchCircleTransactionById({ runtime, transactionId });
816
+ circleState = normalizeCircleState(fetched.state);
817
+ }
818
+
819
+ const classification = classifyCircleTransferState(circleState);
820
+ if (classification === "submitted") {
821
+ return {
822
+ ok: true,
823
+ transactionId,
824
+ circleState,
825
+ response: payload
826
+ };
827
+ }
828
+ return {
829
+ ok: false,
830
+ transactionId,
831
+ circleState,
832
+ error: `circle transfer not safe to mark submitted (state=${circleState ?? "unknown"})`
833
+ };
834
+ }
835
+
836
+ async function main() {
837
+ let args;
838
+ try {
839
+ args = parseArgs(process.argv.slice(2));
840
+ } catch (err) {
841
+ process.stderr.write(`${err?.message ?? String(err)}\n\n${usage()}\n`);
842
+ process.exitCode = 1;
843
+ return;
844
+ }
845
+ if (args.help) {
846
+ process.stdout.write(`${usage()}\n`);
847
+ return;
848
+ }
849
+
850
+ const circleMode = normalizeCircleMode(args.circleMode ?? readEnv("X402_BATCH_CIRCLE_MODE", "stub"));
851
+ const registry = loadRegistry(args.registryPath);
852
+ const workerState = loadWorkerState(args.statePath);
853
+ const discovered = collectArtifactRuns(args.artifactRoot);
854
+ const nowAt = nowIso();
855
+
856
+ const existingByBatchId = new Map();
857
+ for (const row of workerState.state.batches) {
858
+ const normalized = normalizeBatchRecord(row, { defaultMaxAttempts: args.maxPayoutAttempts });
859
+ if (!normalized) continue;
860
+ existingByBatchId.set(normalized.batchId, normalized);
861
+ }
862
+
863
+ const alreadyProcessed = workerState.state.processedGateIds;
864
+ const eligible = discovered.filter((row) => {
865
+ if (!row.gateId) return false;
866
+ if (alreadyProcessed[row.gateId]) return false;
867
+ if (row.releasedAmountCents <= 0) return false;
868
+ if (row.settlementStatus !== "released") return false;
869
+ return true;
870
+ });
871
+
872
+ const groups = groupByProviderAndCurrency(eligible);
873
+ const skipped = [];
874
+ const newBatches = [];
875
+ for (const groupRows of groups.values()) {
876
+ const sortedRows = groupRows
877
+ .slice()
878
+ .sort((a, b) => `${a.resolvedAt ?? ""}\n${a.gateId}`.localeCompare(`${b.resolvedAt ?? ""}\n${b.gateId}`));
879
+ const providerId = sortedRows[0].providerId;
880
+ const currency = sortedRows[0].currency;
881
+ const destination = registry.providerDestinations.get(providerId) ?? null;
882
+ if (!destination) {
883
+ skipped.push({
884
+ providerId,
885
+ currency,
886
+ reason: "missing_provider_destination",
887
+ gateIds: sortedRows.map((row) => row.gateId)
888
+ });
889
+ continue;
890
+ }
891
+
892
+ const batch = buildNewBatch({
893
+ providerId,
894
+ currency,
895
+ rows: sortedRows,
896
+ destination,
897
+ nowAt,
898
+ maxAttempts: args.maxPayoutAttempts
899
+ });
900
+ const existing = existingByBatchId.get(batch.batchId) ?? null;
901
+ if (existing) continue;
902
+ existingByBatchId.set(batch.batchId, batch);
903
+ newBatches.push(batch);
904
+ for (const gate of batch.gates) {
905
+ alreadyProcessed[gate.gateId] = {
906
+ batchId: batch.batchId,
907
+ providerId: batch.providerId,
908
+ processedAt: nowAt
909
+ };
910
+ }
911
+ }
912
+
913
+ const payoutExecution = {
914
+ enabled: args.executeCircle === true,
915
+ mode: circleMode,
916
+ attempted: 0,
917
+ submitted: 0,
918
+ failed: 0,
919
+ skipped: 0,
920
+ results: []
921
+ };
922
+
923
+ if (args.executeCircle && args.dryRun) {
924
+ const batchesSorted = Array.from(existingByBatchId.values()).sort((a, b) => String(a.batchId).localeCompare(String(b.batchId)));
925
+ payoutExecution.skipped = batchesSorted.length;
926
+ payoutExecution.results = batchesSorted.map((batch) => ({
927
+ batchId: batch.batchId,
928
+ status: "skipped_dry_run"
929
+ }));
930
+ } else if (args.executeCircle) {
931
+ const runtime = createCirclePayoutRuntime({ mode: circleMode });
932
+ const walletAddressCache = new Map();
933
+ const batchesSorted = Array.from(existingByBatchId.values()).sort((a, b) => String(a.batchId).localeCompare(String(b.batchId)));
934
+ for (const batch of batchesSorted) {
935
+ const payout = batch.payout ?? {};
936
+ const status = String(payout.status ?? "").toLowerCase();
937
+ const attempts = normalizeNonNegativeInt(payout.attempts, 0);
938
+ const maxAttempts = normalizeNonNegativeInt(payout.maxAttempts, args.maxPayoutAttempts) || args.maxPayoutAttempts;
939
+ batch.payout.maxAttempts = maxAttempts;
940
+ batch.payout.idempotencyKey =
941
+ typeof payout.idempotencyKey === "string" && payout.idempotencyKey.trim() !== ""
942
+ ? payout.idempotencyKey.trim()
943
+ : stableUuidV4FromString(`x402-batch:${batch.batchId}`);
944
+
945
+ if (status === "submitted" || status === "confirmed") {
946
+ payoutExecution.skipped += 1;
947
+ payoutExecution.results.push({
948
+ batchId: batch.batchId,
949
+ status: "skipped_already_submitted",
950
+ transactionId: batch.payout.transactionId ?? null
951
+ });
952
+ continue;
953
+ }
954
+ if (status === "failed" && attempts >= maxAttempts) {
955
+ payoutExecution.skipped += 1;
956
+ payoutExecution.results.push({
957
+ batchId: batch.batchId,
958
+ status: "skipped_retry_exhausted",
959
+ attempts,
960
+ maxAttempts
961
+ });
962
+ continue;
963
+ }
964
+
965
+ payoutExecution.attempted += 1;
966
+ batch.payout.lastAttemptAt = nowAt;
967
+ batch.payout.attempts = attempts + 1;
968
+ batch.payout.maxAttempts = maxAttempts;
969
+ try {
970
+ const executed = await executeCirclePayoutBatch({ runtime, batch, walletAddressCache });
971
+ if (!executed.ok) {
972
+ batch.payout.status = "failed";
973
+ batch.payout.lastError = {
974
+ message: executed.error ?? "unknown error",
975
+ circleState: executed.circleState ?? null
976
+ };
977
+ batch.payout.circleState = executed.circleState ?? null;
978
+ if (executed.transactionId) batch.payout.transactionId = executed.transactionId;
979
+ payoutExecution.failed += 1;
980
+ payoutExecution.results.push({
981
+ batchId: batch.batchId,
982
+ status: "failed",
983
+ transactionId: executed.transactionId ?? null,
984
+ circleState: executed.circleState ?? null,
985
+ error: executed.error ?? null
986
+ });
987
+ continue;
988
+ }
989
+
990
+ batch.payout.status = "submitted";
991
+ batch.payout.transactionId = executed.transactionId ?? batch.payout.transactionId ?? null;
992
+ batch.payout.circleState = executed.circleState ?? null;
993
+ batch.payout.lastError = null;
994
+ batch.payout.submittedAt = nowAt;
995
+ batch.payout.providerResponse = executed.response ?? null;
996
+ batch.settlementMethod = "circle_transfer";
997
+ payoutExecution.submitted += 1;
998
+ payoutExecution.results.push({
999
+ batchId: batch.batchId,
1000
+ status: "submitted",
1001
+ transactionId: batch.payout.transactionId,
1002
+ circleState: batch.payout.circleState
1003
+ });
1004
+ } catch (err) {
1005
+ batch.payout.status = "failed";
1006
+ batch.payout.lastError = { message: err?.message ?? String(err ?? "") };
1007
+ payoutExecution.failed += 1;
1008
+ payoutExecution.results.push({
1009
+ batchId: batch.batchId,
1010
+ status: "failed",
1011
+ error: err?.message ?? String(err ?? "")
1012
+ });
1013
+ }
1014
+ }
1015
+ }
1016
+
1017
+ const batchesSorted = Array.from(existingByBatchId.values()).sort((a, b) => String(a.batchId).localeCompare(String(b.batchId)));
1018
+
1019
+ const manifest = {
1020
+ schemaVersion: "X402PayoutManifest.v1",
1021
+ createdAt: nowAt,
1022
+ artifactRoot: path.resolve(process.cwd(), args.artifactRoot),
1023
+ registryPath: registry.resolvedPath,
1024
+ statePath: workerState.resolvedPath,
1025
+ dryRun: args.dryRun === true,
1026
+ executeCircle: args.executeCircle === true,
1027
+ circleMode,
1028
+ discoveredGateCount: discovered.length,
1029
+ eligibleGateCount: eligible.length,
1030
+ skipped,
1031
+ newBatches,
1032
+ trackedBatchCount: batchesSorted.length,
1033
+ payoutExecution
1034
+ };
1035
+ const manifestHash = sha256Hex(canonicalJsonStringify(manifest));
1036
+ const signature = maybeSignManifest(manifest);
1037
+ const reconciliation = buildPayoutReconciliation({
1038
+ batches: batchesSorted,
1039
+ generatedAt: nowAt,
1040
+ artifactRoot: path.resolve(process.cwd(), args.artifactRoot),
1041
+ registryPath: registry.resolvedPath,
1042
+ statePath: workerState.resolvedPath
1043
+ });
1044
+
1045
+ const outDir =
1046
+ args.outDir && String(args.outDir).trim() !== ""
1047
+ ? path.resolve(process.cwd(), args.outDir)
1048
+ : path.resolve(process.cwd(), "artifacts", "settlement", "x402-batches", nowAt.replaceAll(":", "").replaceAll(".", ""));
1049
+ fs.mkdirSync(outDir, { recursive: true });
1050
+
1051
+ writeJsonFile(path.join(outDir, "payout-manifest.json"), manifest);
1052
+ writeJsonFile(path.join(outDir, "payout-manifest.meta.json"), {
1053
+ schemaVersion: "X402PayoutManifestMeta.v1",
1054
+ manifestHash,
1055
+ signature
1056
+ });
1057
+ writeJsonFile(path.join(outDir, "payout-reconciliation.json"), reconciliation);
1058
+ for (const batch of newBatches) {
1059
+ writeJsonFile(path.join(outDir, "batches", `${batch.batchId}.json`), batch);
1060
+ }
1061
+
1062
+ if (!args.dryRun) {
1063
+ workerState.state.updatedAt = nowAt;
1064
+ workerState.state.processedGateIds = alreadyProcessed;
1065
+ workerState.state.batches = batchesSorted;
1066
+ writeJsonFile(workerState.resolvedPath, workerState.state);
1067
+ }
1068
+
1069
+ const result = {
1070
+ ok: true,
1071
+ outDir,
1072
+ manifestHash,
1073
+ batchCount: newBatches.length,
1074
+ trackedBatchCount: batchesSorted.length,
1075
+ skippedProviderCount: skipped.length,
1076
+ processedGateCount: newBatches.reduce((sum, batch) => sum + batch.gateCount, 0),
1077
+ dryRun: args.dryRun === true,
1078
+ executeCircle: args.executeCircle === true,
1079
+ payoutExecution,
1080
+ reconciliation: {
1081
+ ok: reconciliation.ok,
1082
+ totals: reconciliation.totals
1083
+ }
1084
+ };
1085
+ process.stdout.write(`${JSON.stringify(result)}\n`);
1086
+ }
1087
+
1088
+ main().catch((err) => {
1089
+ process.stderr.write(`${err?.stack ?? err?.message ?? String(err)}\n`);
1090
+ process.exitCode = 1;
1091
+ });