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.
- package/README.md +93 -3
- package/SETTLD_VERSION +1 -1
- package/bin/settld-mcp +2 -0
- package/bin/settld.js +71 -0
- package/conformance/kernel-v0/README.md +7 -0
- package/conformance/kernel-v0/run.mjs +292 -4
- package/docs/ACCESS.md +57 -0
- package/docs/ADOPTION_CHECKLIST.md +44 -0
- package/docs/ALERTS.md +198 -0
- package/docs/ARCHITECTURE.md +69 -0
- package/docs/ARCHITECTURE_FOUNDER_GUIDE.md +284 -0
- package/docs/ARTIFACTS.md +60 -0
- package/docs/CERTIFICATION_CHECKLIST.md +33 -0
- package/docs/CIRCLE_SANDBOX_E2E.md +152 -0
- package/docs/CONFIG.md +297 -0
- package/docs/CONTRACTS_APIS.md +23 -0
- package/docs/DEPRECATION.md +31 -0
- package/docs/DOMAIN_MODEL.md +92 -0
- package/docs/EVENT_ENVELOPE.md +53 -0
- package/docs/FINANCE_PACK_FORMAT.md +53 -0
- package/docs/INCIDENT_TAXONOMY.md +30 -0
- package/docs/JOB_STATE_MACHINE.md +66 -0
- package/docs/KERNEL_COMPATIBLE.md +60 -0
- package/docs/KERNEL_V0.md +40 -0
- package/docs/KEY_ROTATION.md +80 -0
- package/docs/LEDGER.md +82 -0
- package/docs/LIVENESS.md +76 -0
- package/docs/MVP_BUILD_ORDER.md +36 -0
- package/docs/ONCALL_PLAYBOOK.md +39 -0
- package/docs/OPERATIONS_SIGNING.md +20 -0
- package/docs/OVERVIEW.md +190 -0
- package/docs/PERF_BASELINE.md +85 -0
- package/docs/PRD.md +77 -0
- package/docs/QUICKSTART_KERNEL_V0.md +96 -0
- package/docs/QUICKSTART_MCP.md +377 -0
- package/docs/QUICKSTART_MCP_HOSTS.md +210 -0
- package/docs/QUICKSTART_POLICY_PACKS.md +65 -0
- package/docs/QUICKSTART_PRODUCE.md +61 -0
- package/docs/QUICKSTART_PROFILES.md +198 -0
- package/docs/QUICKSTART_RELEASE_VERIFY.md +39 -0
- package/docs/QUICKSTART_SDK.md +125 -0
- package/docs/QUICKSTART_SDK_PYTHON.md +111 -0
- package/docs/QUICKSTART_VERIFY.md +54 -0
- package/docs/QUICKSTART_X402_GATEWAY.md +317 -0
- package/docs/README.md +33 -0
- package/docs/RELEASE_CHECKLIST.md +182 -0
- package/docs/RELEASING.md +82 -0
- package/docs/REPO_SETTINGS.md +37 -0
- package/docs/RUNBOOK.md +86 -0
- package/docs/SKILLS.md +42 -0
- package/docs/SKILL_BUNDLE_FORMAT.md +48 -0
- package/docs/SLO.md +131 -0
- package/docs/SUMMARY.md +17 -0
- package/docs/SUPPORT.md +31 -0
- package/docs/THREAT_MODEL.md +36 -0
- package/docs/TRUST.md +59 -0
- package/docs/WORKFLOW.md +35 -0
- package/docs/X402_BATCH_SETTLEMENT.md +126 -0
- package/docs/blog/2026-02-14-your-ai-agent-just-spent-500-where-is-the-receipt.md +73 -0
- package/docs/examples/x402-provider-payout-registry.example.json +14 -0
- package/docs/gitbook/README.md +64 -0
- package/docs/gitbook/SETUP.md +25 -0
- package/docs/gitbook/SUMMARY.md +15 -0
- package/docs/gitbook/api-reference.md +73 -0
- package/docs/gitbook/closepacks.md +55 -0
- package/docs/gitbook/conformance.md +59 -0
- package/docs/gitbook/core-primitives.md +85 -0
- package/docs/gitbook/dispute-lifecycle.md +33 -0
- package/docs/gitbook/faq.md +21 -0
- package/docs/gitbook/guides.md +49 -0
- package/docs/gitbook/operations-runbook.md +36 -0
- package/docs/gitbook/quickstart.md +103 -0
- package/docs/gitbook/replay-and-audit.md +30 -0
- package/docs/gitbook/sdk-reference.md +35 -0
- package/docs/gitbook/security-model.md +58 -0
- package/docs/integrations/README.md +15 -0
- package/docs/integrations/github-actions-verify.yml +31 -0
- package/docs/integrations/github-actions.md +34 -0
- package/docs/integrations/openclaw/CLAWHUB_PUBLISH_CHECKLIST.md +65 -0
- package/docs/integrations/openclaw/PUBLIC_QUICKSTART.md +95 -0
- package/docs/integrations/openclaw/settld-mcp-skill/SKILL.md +69 -0
- package/docs/integrations/openclaw/settld-mcp-skill/mcp-server.example.json +12 -0
- package/docs/kernel-compatible/capabilities.json +36 -0
- package/docs/marketing/agent-commerce-substrate.md +78 -0
- package/docs/marketing/hn-repost-2026-02-17.md +102 -0
- package/docs/marketing/show-hn-post.md +45 -0
- package/docs/ops/ARTIFACT_VERIFICATION_STATUS.md +43 -0
- package/docs/ops/BILLING_WEBHOOK_REPLAY.md +105 -0
- package/docs/ops/CI_FLAKE_BUDGET.md +31 -0
- package/docs/ops/DISPUTE_FINANCE_RECONCILIATION_PACKET.md +56 -0
- package/docs/ops/GO_LIVE_GATE_S13.md +27 -0
- package/docs/ops/HOSTED_BASELINE_R2.md +129 -0
- package/docs/ops/KERNEL_V0_SHIP_GATE.md +69 -0
- package/docs/ops/LIGHTHOUSE_PRODUCTION_CLOSE.md +51 -0
- package/docs/ops/MCP_COMPATIBILITY_MATRIX.md +30 -0
- package/docs/ops/MINIMUM_PRODUCTION_TOPOLOGY.md +89 -0
- package/docs/ops/P0_BACKEND_PROGRESS.md +150 -0
- package/docs/ops/PAYMENTS_ALPHA_R5.md +105 -0
- package/docs/ops/PILOT_ONBOARDING_RUNBOOK.md +112 -0
- package/docs/ops/PRODUCTION_DEPLOYMENT_CHECKLIST.md +140 -0
- package/docs/ops/R1_SLOS.md +66 -0
- package/docs/ops/RELEASE_SIGNING_INCIDENT.md +58 -0
- package/docs/ops/SELF_SERVE_LAUNCH_AUTOMATION.md +89 -0
- package/docs/ops/THROUGHPUT_DRILL_10X.md +48 -0
- package/docs/ops/TRUST_CONFIG_WIZARD.md +60 -0
- package/docs/ops/X402_PILOT_WEEKLY_METRICS.md +76 -0
- package/docs/ops/tool-call-disputes-holdback.md +52 -0
- package/docs/pilot-kit/PILOT_PACKAGE_SCORECARD_X402.md +46 -0
- package/docs/pilot-kit/README.md +29 -0
- package/docs/pilot-kit/architecture-one-pager.md +48 -0
- package/docs/pilot-kit/buyer-email.txt +19 -0
- package/docs/pilot-kit/buyer-one-pager.md +31 -0
- package/docs/pilot-kit/gtm-pilot-playbook.md +182 -0
- package/docs/pilot-kit/offline-verify.md +33 -0
- package/docs/pilot-kit/procurement-one-pager.md +50 -0
- package/docs/pilot-kit/rfp-clause.md +46 -0
- package/docs/pilot-kit/roi-calculator-template.csv +2 -0
- package/docs/pilot-kit/security-qa.md +153 -0
- package/docs/pilot-kit/security-summary.md +35 -0
- package/docs/plans/2026-02-13-mcp-spike-design.md +113 -0
- package/docs/plans/2026-02-20-trust-os-v1-jira-backlog.md +348 -0
- package/docs/plans/2026-02-21-agent-economic-actor-operating-model.md +169 -0
- package/docs/plans/2026-02-21-trust-os-v1-strategy.md +241 -0
- package/docs/research/2026-02-21-agent-spend-host-landscape.md +57 -0
- package/docs/spec/AcceptanceCriteria.v1.md +17 -0
- package/docs/spec/AcceptanceEvaluation.v1.md +10 -0
- package/docs/spec/AgentEvent.v1.md +47 -0
- package/docs/spec/AgentIdentity.v1.md +62 -0
- package/docs/spec/AgentPassport.v1.md +95 -0
- package/docs/spec/AgentReputation.v1.md +59 -0
- package/docs/spec/AgentReputation.v2.md +52 -0
- package/docs/spec/AgentRun.v1.md +47 -0
- package/docs/spec/AgentRunSettlement.v1.md +52 -0
- package/docs/spec/AgentWallet.v1.md +43 -0
- package/docs/spec/AgreementDelegation.v1.md +109 -0
- package/docs/spec/ArbitrationCase.v1.md +67 -0
- package/docs/spec/ArbitrationOutcomeMapping.v1.md +62 -0
- package/docs/spec/ArbitrationVerdict.v1.md +60 -0
- package/docs/spec/BundleHeadAttestation.v1.md +32 -0
- package/docs/spec/CANONICAL_JSON.md +31 -0
- package/docs/spec/CRYPTOGRAPHY.md +61 -0
- package/docs/spec/ClosePack.v1.md +49 -0
- package/docs/spec/ClosePackManifest.v1.md +24 -0
- package/docs/spec/DelegationGrant.v1.md +90 -0
- package/docs/spec/DisputeCaseLifecycle.v1.md +51 -0
- package/docs/spec/DisputeOpenEnvelope.v1.md +43 -0
- package/docs/spec/ERRORS.md +76 -0
- package/docs/spec/ESCROW_NETTING_INVARIANTS.md +71 -0
- package/docs/spec/EvidenceIndex.v1.md +20 -0
- package/docs/spec/ExecutionIntent.v1.md +90 -0
- package/docs/spec/FinancePackBundleManifest.v1.md +24 -0
- package/docs/spec/FundingHold.v1.md +60 -0
- package/docs/spec/GovernancePolicy.v1.md +34 -0
- package/docs/spec/GovernancePolicy.v2.md +30 -0
- package/docs/spec/INVARIANTS.md +389 -0
- package/docs/spec/InteractionDirectionMatrix.v1.md +30 -0
- package/docs/spec/InvoiceBundleManifest.v1.md +24 -0
- package/docs/spec/InvoiceClaim.v1.md +11 -0
- package/docs/spec/MONEY_RAIL_STATE_MACHINE.md +58 -0
- package/docs/spec/MarketplaceAcceptance.v2.md +46 -0
- package/docs/spec/MarketplaceOffer.v2.md +54 -0
- package/docs/spec/MeteringReport.v1.md +18 -0
- package/docs/spec/OperatorAction.v1.md +90 -0
- package/docs/spec/PRODUCER_ERRORS.md +42 -0
- package/docs/spec/PolicyDecision.v1.md +83 -0
- package/docs/spec/PricingMatrix.v1.md +20 -0
- package/docs/spec/PricingMatrixSignatures.v1.md +30 -0
- package/docs/spec/PricingMatrixSignatures.v2.md +29 -0
- package/docs/spec/ProduceCliOutput.v1.md +46 -0
- package/docs/spec/ProofBundleManifest.v1.md +24 -0
- package/docs/spec/README.md +109 -0
- package/docs/spec/REFERENCE_IMPLEMENTATIONS.md +29 -0
- package/docs/spec/REFERENCE_VERIFIER_BEHAVIOR.md +68 -0
- package/docs/spec/REMOTE_SIGNER.md +66 -0
- package/docs/spec/ReleaseIndex.v1.md +32 -0
- package/docs/spec/ReleaseIndexSignatures.v1.md +17 -0
- package/docs/spec/ReleaseTrust.v1.md +13 -0
- package/docs/spec/ReleaseTrust.v2.md +26 -0
- package/docs/spec/RemoteSignerRequest.v1.md +21 -0
- package/docs/spec/RemoteSignerResponse.v1.md +16 -0
- package/docs/spec/ReputationEvent.v1.md +63 -0
- package/docs/spec/RevocationList.v1.md +28 -0
- package/docs/spec/SIGNER_PROVIDER_PLUGIN.md +32 -0
- package/docs/spec/STRICTNESS.md +68 -0
- package/docs/spec/SUPPLY_CHAIN.md +33 -0
- package/docs/spec/SettlementAdjustment.v1.md +45 -0
- package/docs/spec/SettlementDecisionRecord.v1.md +48 -0
- package/docs/spec/SettlementDecisionRecord.v2.md +53 -0
- package/docs/spec/SettlementDecisionReport.v1.md +44 -0
- package/docs/spec/SettlementKernel.v1.md +59 -0
- package/docs/spec/SettlementReceipt.v1.md +63 -0
- package/docs/spec/SlaDefinition.v1.md +24 -0
- package/docs/spec/SlaEvaluation.v1.md +12 -0
- package/docs/spec/THREAT_MODEL.md +113 -0
- package/docs/spec/TOOL_PROVENANCE.md +30 -0
- package/docs/spec/TRUST_ANCHORS.md +84 -0
- package/docs/spec/TenantSettings.v1.md +90 -0
- package/docs/spec/TenantSettings.v2.md +99 -0
- package/docs/spec/TimestampProof.v1.md +25 -0
- package/docs/spec/ToolCallAgreement.v1.md +34 -0
- package/docs/spec/ToolCallEvidence.v1.md +47 -0
- package/docs/spec/ToolManifest.v1.md +47 -0
- package/docs/spec/VERIFIER_ENVIRONMENT.md +38 -0
- package/docs/spec/VERSIONING.md +107 -0
- package/docs/spec/VerificationReport.v1.md +50 -0
- package/docs/spec/VerifyAboutOutput.v1.md +10 -0
- package/docs/spec/VerifyCliOutput.v1.md +28 -0
- package/docs/spec/WARNINGS.md +83 -0
- package/docs/spec/error-codes.v1.txt +285 -0
- package/docs/spec/examples/agreement_delegation_v1.example.json +21 -0
- package/docs/spec/examples/arbitration_case_v1.example.json +26 -0
- package/docs/spec/examples/arbitration_verdict_v1.example.json +32 -0
- package/docs/spec/examples/dispute_open_envelope_v1.example.json +18 -0
- package/docs/spec/examples/produce_cli_output_v1.example.json +32 -0
- package/docs/spec/examples/release_index_signature_v1.example.json +9 -0
- package/docs/spec/examples/release_index_signatures_v1.example.json +14 -0
- package/docs/spec/examples/release_index_v1.example.json +15 -0
- package/docs/spec/examples/release_trust_v1.example.json +7 -0
- package/docs/spec/examples/release_trust_v2.example.json +22 -0
- package/docs/spec/examples/remote_signer_request_v1.example.json +18 -0
- package/docs/spec/examples/remote_signer_response_v1.example.json +8 -0
- package/docs/spec/examples/reputation_event_v1.example.json +29 -0
- package/docs/spec/examples/verification_report_v1.example.json +24 -0
- package/docs/spec/examples/verify_about_output_v1.example.json +29 -0
- package/docs/spec/examples/verify_cli_output_v1.example.json +13 -0
- package/docs/spec/legacy/MarketplaceAcceptance.v1.md +48 -0
- package/docs/spec/legacy/MarketplaceOffer.v1.md +56 -0
- package/docs/spec/legacy/schemas/MarketplaceAcceptance.v1.schema.json +53 -0
- package/docs/spec/legacy/schemas/MarketplaceOffer.v1.schema.json +61 -0
- package/docs/spec/producer-error-codes.v1.txt +14 -0
- package/docs/spec/schemas/AcceptanceCriteria.v1.schema.json +24 -0
- package/docs/spec/schemas/AcceptanceEvaluation.v1.schema.json +26 -0
- package/docs/spec/schemas/AgentEvent.v1.schema.json +49 -0
- package/docs/spec/schemas/AgentIdentity.v1.schema.json +129 -0
- package/docs/spec/schemas/AgentPassport.v1.schema.json +112 -0
- package/docs/spec/schemas/AgentReputation.v1.schema.json +151 -0
- package/docs/spec/schemas/AgentReputation.v2.schema.json +120 -0
- package/docs/spec/schemas/AgentRun.v1.schema.json +71 -0
- package/docs/spec/schemas/AgentRunSettlement.v1.schema.json +75 -0
- package/docs/spec/schemas/AgentWallet.v1.schema.json +54 -0
- package/docs/spec/schemas/AgreementDelegation.v1.schema.json +50 -0
- package/docs/spec/schemas/ArbitrationCase.v1.schema.json +133 -0
- package/docs/spec/schemas/ArbitrationVerdict.v1.schema.json +149 -0
- package/docs/spec/schemas/BundleHeadAttestation.v1.schema.json +21 -0
- package/docs/spec/schemas/ClosePackManifest.v1.schema.json +38 -0
- package/docs/spec/schemas/DelegationGrant.v1.schema.json +102 -0
- package/docs/spec/schemas/DisputeOpenEnvelope.v1.schema.json +78 -0
- package/docs/spec/schemas/EvidenceIndex.v1.schema.json +41 -0
- package/docs/spec/schemas/ExecutionIntent.v1.schema.json +85 -0
- package/docs/spec/schemas/FinancePackBundleManifest.v1.schema.json +38 -0
- package/docs/spec/schemas/FundingHold.v1.schema.json +46 -0
- package/docs/spec/schemas/GovernancePolicy.v1.schema.json +45 -0
- package/docs/spec/schemas/GovernancePolicy.v2.schema.json +70 -0
- package/docs/spec/schemas/InteractionDirectionMatrix.v1.schema.json +43 -0
- package/docs/spec/schemas/InvoiceBundleManifest.v1.schema.json +38 -0
- package/docs/spec/schemas/InvoiceClaim.v1.schema.json +39 -0
- package/docs/spec/schemas/MarketplaceAcceptance.v2.schema.json +53 -0
- package/docs/spec/schemas/MarketplaceOffer.v2.schema.json +61 -0
- package/docs/spec/schemas/MeteringReport.v1.schema.json +45 -0
- package/docs/spec/schemas/OperatorAction.v1.schema.json +113 -0
- package/docs/spec/schemas/PolicyDecision.v1.schema.json +74 -0
- package/docs/spec/schemas/PricingMatrix.v1.schema.json +24 -0
- package/docs/spec/schemas/PricingMatrixSignatures.v1.schema.json +24 -0
- package/docs/spec/schemas/PricingMatrixSignatures.v2.schema.json +24 -0
- package/docs/spec/schemas/ProduceCliOutput.v1.schema.json +107 -0
- package/docs/spec/schemas/ProofBundleManifest.v1.schema.json +37 -0
- package/docs/spec/schemas/PublicKeys.v1.schema.json +33 -0
- package/docs/spec/schemas/ReleaseIndex.v1.schema.json +45 -0
- package/docs/spec/schemas/ReleaseIndexSignature.v1.schema.json +16 -0
- package/docs/spec/schemas/ReleaseIndexSignatures.v1.schema.json +16 -0
- package/docs/spec/schemas/ReleaseTrust.v1.schema.json +15 -0
- package/docs/spec/schemas/ReleaseTrust.v2.schema.json +37 -0
- package/docs/spec/schemas/RemoteSignerPublicKeyResponse.v1.schema.json +14 -0
- package/docs/spec/schemas/RemoteSignerRequest.v1.schema.json +24 -0
- package/docs/spec/schemas/RemoteSignerResponse.v1.schema.json +10 -0
- package/docs/spec/schemas/RemoteSignerSignRequest.v1.schema.json +27 -0
- package/docs/spec/schemas/RemoteSignerSignResponse.v1.schema.json +16 -0
- package/docs/spec/schemas/ReputationEvent.v1.schema.json +164 -0
- package/docs/spec/schemas/RevocationList.v1.schema.json +51 -0
- package/docs/spec/schemas/SettlementAdjustment.v1.schema.json +44 -0
- package/docs/spec/schemas/SettlementDecisionRecord.v1.schema.json +66 -0
- package/docs/spec/schemas/SettlementDecisionRecord.v2.schema.json +149 -0
- package/docs/spec/schemas/SettlementDecisionReport.v1.schema.json +61 -0
- package/docs/spec/schemas/SettlementReceipt.v1.schema.json +135 -0
- package/docs/spec/schemas/SlaDefinition.v1.schema.json +33 -0
- package/docs/spec/schemas/SlaEvaluation.v1.schema.json +26 -0
- package/docs/spec/schemas/TenantSettings.v1.schema.json +90 -0
- package/docs/spec/schemas/TenantSettings.v2.schema.json +161 -0
- package/docs/spec/schemas/TimestampProof.v1.schema.json +17 -0
- package/docs/spec/schemas/ToolCallAgreement.v1.schema.json +34 -0
- package/docs/spec/schemas/ToolCallEvidence.v1.schema.json +45 -0
- package/docs/spec/schemas/ToolManifest.v1.schema.json +54 -0
- package/docs/spec/schemas/VerificationReport.v1.schema.json +83 -0
- package/docs/spec/schemas/VerifyAboutOutput.v1.schema.json +54 -0
- package/docs/spec/schemas/VerifyCliOutput.v1.schema.json +75 -0
- package/docs/spec/schemas/VerifyReleaseOutput.v1.schema.json +47 -0
- package/docs/spec/x402-error-codes.v1.txt +35 -0
- package/docs/templates/buyer-email.txt +18 -0
- package/docs/templates/buyer-one-pager.md +24 -0
- package/package.json +53 -6
- package/scripts/acceptance/full-stack.mjs +734 -0
- package/scripts/acceptance/full-stack.sh +99 -0
- package/scripts/audit/build-audit-packet.mjs +242 -0
- package/scripts/backup-pg.sh +45 -0
- package/scripts/backup-restore/README.md +18 -0
- package/scripts/backup-restore/capture-state.mjs +130 -0
- package/scripts/backup-restore/client.mjs +97 -0
- package/scripts/backup-restore/seed-workload.mjs +235 -0
- package/scripts/backup-restore/verify-state.mjs +139 -0
- package/scripts/backup-restore-test.sh +217 -0
- package/scripts/chaos.js +221 -0
- package/scripts/ci/build-launch-cutover-packet.mjs +304 -0
- package/scripts/ci/build-self-serve-benchmark-report.mjs +122 -0
- package/scripts/ci/changelog-guard.mjs +145 -0
- package/scripts/ci/check-kernel-v0-launch-gate.mjs +233 -0
- package/scripts/ci/check-secret-hygiene.mjs +78 -0
- package/scripts/ci/check-version-consistency.mjs +42 -0
- package/scripts/ci/cli-pack-smoke.mjs +160 -0
- package/scripts/ci/flake-budget-guard.mjs +68 -0
- package/scripts/ci/generate-error-codes.mjs +54 -0
- package/scripts/ci/lib/lighthouse-tracker.mjs +90 -0
- package/scripts/ci/lib/self-serve-launch-gate.mjs +89 -0
- package/scripts/ci/npm-pack-smoke.mjs +454 -0
- package/scripts/ci/run-10x-throughput-drill.mjs +318 -0
- package/scripts/ci/run-10x-throughput-incident-rehearsal.mjs +368 -0
- package/scripts/ci/run-arbitration-workspace-browser-e2e.sh +22 -0
- package/scripts/ci/run-circle-sandbox-smoke.mjs +237 -0
- package/scripts/ci/run-go-live-gate.mjs +150 -0
- package/scripts/ci/run-kernel-v0-ship-gate.mjs +97 -0
- package/scripts/ci/run-mcp-host-cert-matrix.mjs +201 -0
- package/scripts/ci/run-mcp-host-smoke.mjs +473 -0
- package/scripts/ci/run-offline-verification-parity-gate.mjs +762 -0
- package/scripts/ci/run-onboarding-host-success-gate.mjs +516 -0
- package/scripts/ci/run-onboarding-policy-slo-gate.mjs +537 -0
- package/scripts/ci/run-production-cutover-gate.mjs +540 -0
- package/scripts/ci/run-public-openclaw-npx-smoke.mjs +148 -0
- package/scripts/ci/run-release-promotion-guard.mjs +756 -0
- package/scripts/ci/run-self-serve-launch-gate.mjs +56 -0
- package/scripts/ci/runtime-import-smoke.mjs +58 -0
- package/scripts/ci/update-lighthouse-tracker.mjs +112 -0
- package/scripts/closepack/lib.mjs +286 -0
- package/scripts/collect-debug.sh +263 -0
- package/scripts/demo/compositional-settlement-3hop.mjs +237 -0
- package/scripts/demo/delivery-robot/export-ui-fixture.mjs +188 -0
- package/scripts/demo/delivery-robot/generate.mjs +377 -0
- package/scripts/demo/kernel-agent-goes-shopping.mjs +202 -0
- package/scripts/demo/magic-link-first-green.mjs +118 -0
- package/scripts/demo/magic-link-kind-smoke.mjs +577 -0
- package/scripts/demo/mcp-paid-exa.mjs +1110 -0
- package/scripts/dev/billing-doctor.sh +145 -0
- package/scripts/dev/billing-smoke-prod.sh +219 -0
- package/scripts/dev/billing-webhook-replay.sh +161 -0
- package/scripts/dev/env.dev.example +29 -0
- package/scripts/dev/env.sh +37 -0
- package/scripts/dev/new-sdk-key.sh +81 -0
- package/scripts/dev/sdk-first-run.sh +21 -0
- package/scripts/dev/smoke-x402-gateway.sh +115 -0
- package/scripts/dev/start-api.sh +24 -0
- package/scripts/doctor/mcp-host.mjs +120 -0
- package/scripts/examples/produce-and-verify-jobproof.mjs +191 -0
- package/scripts/examples/sdk-first-paid-rfq.py +105 -0
- package/scripts/examples/sdk-first-verified-run.mjs +85 -0
- package/scripts/examples/sdk-first-verified-run.py +99 -0
- package/scripts/examples/sdk-tenant-analytics.mjs +103 -0
- package/scripts/examples/sdk-tenant-analytics.py +118 -0
- package/scripts/finance-pack/bundle.mjs +284 -0
- package/scripts/fixtures/generate-bundle-fixtures.mjs +877 -0
- package/scripts/governance/export.mjs +169 -0
- package/scripts/load/delivery-stress.k6.js +183 -0
- package/scripts/load/ingest-burst.k6.js +236 -0
- package/scripts/load/run-delivery-load.js +66 -0
- package/scripts/load/webhook-receiver.js +131 -0
- package/scripts/magic-link/migrate-run-records-to-db.mjs +35 -0
- package/scripts/mcp/probe.mjs +238 -0
- package/scripts/mcp/settld-mcp-http-gateway.mjs +178 -0
- package/scripts/mcp/settld-mcp-server.mjs +1511 -0
- package/scripts/openapi/write.mjs +13 -0
- package/scripts/ops/bootstrap-tenant-conformance.mjs +185 -0
- package/scripts/ops/build-x402-pilot-reliability-report.mjs +489 -0
- package/scripts/ops/check-x402-receipt-sample.mjs +181 -0
- package/scripts/ops/design-partner-run-packet.mjs +466 -0
- package/scripts/ops/dispute-finance-reconciliation-packet.mjs +313 -0
- package/scripts/ops/hosted-baseline-evidence.mjs +890 -0
- package/scripts/ops/money-rails-chargeback-evidence.mjs +509 -0
- package/scripts/ops/money-rails-reconcile-evidence.mjs +180 -0
- package/scripts/ops/p0-seed-money-rail-operation.mjs +432 -0
- package/scripts/ops/run-x402-hitl-smoke.mjs +607 -0
- package/scripts/pilot/finance-pack.mjs +495 -0
- package/scripts/pilot/fixtures/robot-keypair.json +4 -0
- package/scripts/pilot/fixtures/server-signer.json +4 -0
- package/scripts/policy/cli.mjs +600 -0
- package/scripts/profile/cli.mjs +1324 -0
- package/scripts/proof-bundle/job.mjs +109 -0
- package/scripts/proof-bundle/lib.mjs +92 -0
- package/scripts/proof-bundle/month.mjs +103 -0
- package/scripts/provider/conformance-run.mjs +159 -0
- package/scripts/provider/keys-generate.mjs +135 -0
- package/scripts/provider/publish.mjs +420 -0
- package/scripts/quickstart/x402.mjs +334 -0
- package/scripts/register-entity-secret.mjs +102 -0
- package/scripts/release/build-artifacts.mjs +181 -0
- package/scripts/release/generate-release-index.mjs +112 -0
- package/scripts/release/release-index-lib.mjs +232 -0
- package/scripts/release/sign-release-index.mjs +85 -0
- package/scripts/release/validate-release-assets.mjs +170 -0
- package/scripts/release/verify-release.mjs +261 -0
- package/scripts/restore-pg.sh +34 -0
- package/scripts/scaffold/create-settld-paid-tool.mjs +19 -0
- package/scripts/sdk/smoke-python.py +30 -0
- package/scripts/sdk/smoke.mjs +16 -0
- package/scripts/settlement/x402-batch-worker.mjs +1091 -0
- package/scripts/setup/circle-bootstrap.mjs +310 -0
- package/scripts/setup/host-config.mjs +617 -0
- package/scripts/setup/onboard.mjs +1337 -0
- package/scripts/setup/openclaw-onboard.mjs +423 -0
- package/scripts/setup/wizard.mjs +986 -0
- package/scripts/slo/check.mjs +239 -0
- package/scripts/smoke/k8s-smoke.mjs +214 -0
- package/scripts/spec/generate-protocol-vectors.mjs +1019 -0
- package/scripts/test/check-no-generated-artifacts.sh +12 -0
- package/scripts/test/run.sh +59 -0
- package/scripts/trust/validate-trust-file.mjs +57 -0
- package/scripts/trust-config/rotate-settld-pay.mjs +277 -0
- package/scripts/trust-config/wizard.mjs +161 -0
- package/scripts/vendor-contract-test-lib.mjs +182 -0
- package/scripts/vendor-contract-test.mjs +55 -0
- package/scripts/vercel/build-mkdocs.sh +9 -0
- package/scripts/vercel/ignore-mkdocs.sh +25 -0
- package/scripts/vercel/install-mkdocs.sh +6 -0
- package/scripts/verify-pg.js +217 -0
- package/scripts/x402/receipt-verify.mjs +289 -0
- package/services/finance-sink/src/dedupe-store.js +29 -6
- package/services/receiver/src/dedupe-store.js +29 -5
- package/services/x402-gateway/Dockerfile +13 -0
- package/services/x402-gateway/README.md +58 -0
- package/services/x402-gateway/examples/upstream-mock.js +337 -0
- package/services/x402-gateway/src/server.js +1058 -0
- package/src/api/app.js +34658 -16940
- package/src/api/maintenance.js +70 -0
- package/src/api/middleware/trust-kernel.js +114 -0
- package/src/api/openapi.js +1778 -70
- package/src/api/persistence.js +456 -0
- package/src/api/server.js +81 -5
- package/src/api/store.js +1581 -62
- package/src/api/workers/deliveries.js +99 -4
- package/src/api/workers/insolvency-sweep.js +159 -0
- package/src/core/agent-card.js +69 -0
- package/src/core/agent-wallets.js +231 -0
- package/src/core/agreement-delegation.js +549 -0
- package/src/core/billing-plans.js +40 -6
- package/src/core/circle-reserve-adapter.js +845 -0
- package/src/core/event-policy.js +21 -2
- package/src/core/maintenance-locks.js +1 -0
- package/src/core/operator-action.js +303 -0
- package/src/core/paid-tool-manifest.js +318 -0
- package/src/core/policy-decision.js +322 -0
- package/src/core/policy-packs.js +207 -0
- package/src/core/profile-fingerprint.js +27 -0
- package/src/core/profile-simulation-reasons.js +84 -0
- package/src/core/profile-templates.js +242 -0
- package/src/core/provider-publish-conformance.js +525 -0
- package/src/core/provider-publish-proof.js +396 -0
- package/src/core/provider-quote-signature.js +170 -0
- package/src/core/settld-keys.js +112 -0
- package/src/core/settld-pay-token.js +344 -0
- package/src/core/settlement-kernel.js +239 -2
- package/src/core/settlement-verifier.js +335 -0
- package/src/core/tool-call-agreement.js +112 -0
- package/src/core/tool-call-evidence.js +144 -0
- package/src/core/tool-provider-signature.js +98 -0
- package/src/core/wallet-assignment-resolver.js +129 -0
- package/src/core/wallet-provider-bootstrap.js +365 -0
- package/src/core/x402-escalation-override.js +258 -0
- package/src/core/x402-gate.js +118 -0
- package/src/core/x402-provider-refund-decision.js +220 -0
- package/src/core/x402-receipt-verifier.js +708 -0
- package/src/core/x402-reversal-command.js +251 -0
- package/src/core/x402-wallet-issuer-decision.js +252 -0
- package/src/core/zk-verifier.js +300 -0
- package/src/db/migrations/029_reputation_event_index.sql +54 -0
- package/src/db/migrations/030_artifacts_source_event_unique_job_only.sql +15 -0
- package/src/db/pg.js +18 -7
- package/src/db/store-pg.js +1508 -111
|
@@ -0,0 +1,845 @@
|
|
|
1
|
+
import crypto from "node:crypto";
|
|
2
|
+
|
|
3
|
+
import { sha256Hex } from "./crypto.js";
|
|
4
|
+
|
|
5
|
+
export const CIRCLE_RESERVE_STATUS = Object.freeze({
|
|
6
|
+
RESERVED: "reserved",
|
|
7
|
+
VOIDED: "voided"
|
|
8
|
+
});
|
|
9
|
+
|
|
10
|
+
export const CIRCLE_TRANSACTION_STATE = Object.freeze({
|
|
11
|
+
CANCELLED: "CANCELLED",
|
|
12
|
+
CONFIRMED: "CONFIRMED",
|
|
13
|
+
COMPLETE: "COMPLETE",
|
|
14
|
+
DENIED: "DENIED",
|
|
15
|
+
FAILED: "FAILED",
|
|
16
|
+
INITIATED: "INITIATED",
|
|
17
|
+
CLEARED: "CLEARED",
|
|
18
|
+
QUEUED: "QUEUED",
|
|
19
|
+
SENT: "SENT",
|
|
20
|
+
STUCK: "STUCK"
|
|
21
|
+
});
|
|
22
|
+
|
|
23
|
+
const RESERVE_OK_STATES = new Set([
|
|
24
|
+
CIRCLE_TRANSACTION_STATE.INITIATED,
|
|
25
|
+
CIRCLE_TRANSACTION_STATE.QUEUED,
|
|
26
|
+
CIRCLE_TRANSACTION_STATE.SENT,
|
|
27
|
+
CIRCLE_TRANSACTION_STATE.CONFIRMED,
|
|
28
|
+
CIRCLE_TRANSACTION_STATE.COMPLETE,
|
|
29
|
+
CIRCLE_TRANSACTION_STATE.CLEARED
|
|
30
|
+
]);
|
|
31
|
+
|
|
32
|
+
const RESERVE_FAIL_STATES = new Set([
|
|
33
|
+
CIRCLE_TRANSACTION_STATE.DENIED,
|
|
34
|
+
CIRCLE_TRANSACTION_STATE.FAILED,
|
|
35
|
+
CIRCLE_TRANSACTION_STATE.CANCELLED
|
|
36
|
+
]);
|
|
37
|
+
|
|
38
|
+
const CANCELLABLE_STATES = new Set([
|
|
39
|
+
CIRCLE_TRANSACTION_STATE.INITIATED,
|
|
40
|
+
CIRCLE_TRANSACTION_STATE.QUEUED,
|
|
41
|
+
CIRCLE_TRANSACTION_STATE.SENT
|
|
42
|
+
]);
|
|
43
|
+
|
|
44
|
+
function assertNonEmptyString(value, name) {
|
|
45
|
+
if (typeof value !== "string" || value.trim() === "") throw new TypeError(`${name} must be a non-empty string`);
|
|
46
|
+
return String(value).trim();
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
function normalizePositiveSafeInt(value, name) {
|
|
50
|
+
const n = Number(value);
|
|
51
|
+
if (!Number.isSafeInteger(n) || n <= 0) throw new TypeError(`${name} must be a positive safe integer`);
|
|
52
|
+
return n;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
function normalizeCurrency(value, name) {
|
|
56
|
+
const out = assertNonEmptyString(String(value ?? "USD"), name).toUpperCase();
|
|
57
|
+
if (!/^[A-Z][A-Z0-9_]{2,11}$/.test(out)) throw new TypeError(`${name} must match ^[A-Z][A-Z0-9_]{2,11}$`);
|
|
58
|
+
return out;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
function parseBooleanLike(value, fallback = false) {
|
|
62
|
+
if (value === null || value === undefined || String(value).trim() === "") return fallback;
|
|
63
|
+
const normalized = String(value).trim().toLowerCase();
|
|
64
|
+
if (normalized === "1" || normalized === "true" || normalized === "yes" || normalized === "on") return true;
|
|
65
|
+
if (normalized === "0" || normalized === "false" || normalized === "no" || normalized === "off") return false;
|
|
66
|
+
return fallback;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
function normalizeMode(value) {
|
|
70
|
+
const normalized = String(value ?? "stub").trim().toLowerCase();
|
|
71
|
+
if (normalized === "stub" || normalized === "test") return "stub";
|
|
72
|
+
if (normalized === "fail") return "fail";
|
|
73
|
+
if (normalized === "sandbox") return "sandbox";
|
|
74
|
+
if (normalized === "production" || normalized === "prod") return "production";
|
|
75
|
+
throw new TypeError("mode must be stub|fail|sandbox|production");
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
function normalizeIsoDate(value, name) {
|
|
79
|
+
const out = assertNonEmptyString(value, name);
|
|
80
|
+
if (!Number.isFinite(Date.parse(out))) throw new TypeError(`${name} must be an ISO date-time`);
|
|
81
|
+
return out;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
function normalizeCircleState(value) {
|
|
85
|
+
if (value === null || value === undefined || String(value).trim() === "") return null;
|
|
86
|
+
const normalized = String(value).trim().toUpperCase();
|
|
87
|
+
if (Object.values(CIRCLE_TRANSACTION_STATE).includes(normalized)) return normalized;
|
|
88
|
+
return normalized;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
function classifyCircleReserveState(state) {
|
|
92
|
+
if (!state) return "unknown";
|
|
93
|
+
if (RESERVE_OK_STATES.has(state)) return "reserved";
|
|
94
|
+
if (RESERVE_FAIL_STATES.has(state)) return "failed";
|
|
95
|
+
if (state === CIRCLE_TRANSACTION_STATE.STUCK) return "uncertain";
|
|
96
|
+
return "unknown";
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
function centsToAssetAmountString(amountCents) {
|
|
100
|
+
const cents = normalizePositiveSafeInt(amountCents, "amountCents");
|
|
101
|
+
const whole = Math.floor(cents / 100);
|
|
102
|
+
const fraction = String(cents % 100).padStart(2, "0");
|
|
103
|
+
return `${whole}.${fraction}`;
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
function isUuidV4(value) {
|
|
107
|
+
if (typeof value !== "string") return false;
|
|
108
|
+
return /^[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i.test(value.trim());
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
function stableUuidV4FromString(input) {
|
|
112
|
+
const text = assertNonEmptyString(input, "idempotencyKeySource");
|
|
113
|
+
const buf = Buffer.from(sha256Hex(text).slice(0, 32), "hex");
|
|
114
|
+
buf[6] = (buf[6] & 0x0f) | 0x40;
|
|
115
|
+
buf[8] = (buf[8] & 0x3f) | 0x80;
|
|
116
|
+
const hex = buf.toString("hex");
|
|
117
|
+
return `${hex.slice(0, 8)}-${hex.slice(8, 12)}-${hex.slice(12, 16)}-${hex.slice(16, 20)}-${hex.slice(20, 32)}`;
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
function normalizeCircleIdempotencyKey(value) {
|
|
121
|
+
const source = assertNonEmptyString(value, "idempotencyKey");
|
|
122
|
+
if (isUuidV4(source)) return source.toLowerCase();
|
|
123
|
+
return stableUuidV4FromString(source);
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
function makeAdapterError(code, message, details = null) {
|
|
127
|
+
const err = new Error(message);
|
|
128
|
+
err.code = code;
|
|
129
|
+
if (details && typeof details === "object") err.details = details;
|
|
130
|
+
return err;
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
function pickFirstObject(value) {
|
|
134
|
+
if (value && typeof value === "object" && !Array.isArray(value)) return value;
|
|
135
|
+
if (Array.isArray(value) && value.length > 0) {
|
|
136
|
+
const first = value[0];
|
|
137
|
+
if (first && typeof first === "object" && !Array.isArray(first)) return first;
|
|
138
|
+
}
|
|
139
|
+
return null;
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
function extractCircleTransaction(payload) {
|
|
143
|
+
const root = pickFirstObject(payload);
|
|
144
|
+
if (!root) return { id: null, state: null, raw: null };
|
|
145
|
+
|
|
146
|
+
const candidates = [];
|
|
147
|
+
candidates.push(root);
|
|
148
|
+
if (root.data) {
|
|
149
|
+
const data = pickFirstObject(root.data);
|
|
150
|
+
if (data) candidates.push(data);
|
|
151
|
+
}
|
|
152
|
+
if (root.transaction) {
|
|
153
|
+
const tx = pickFirstObject(root.transaction);
|
|
154
|
+
if (tx) candidates.push(tx);
|
|
155
|
+
}
|
|
156
|
+
if (root.transactions) {
|
|
157
|
+
const tx = pickFirstObject(root.transactions);
|
|
158
|
+
if (tx) candidates.push(tx);
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
for (const row of candidates) {
|
|
162
|
+
const id =
|
|
163
|
+
(typeof row.id === "string" && row.id.trim() !== "" ? row.id.trim() : null) ??
|
|
164
|
+
(typeof row.transactionId === "string" && row.transactionId.trim() !== "" ? row.transactionId.trim() : null);
|
|
165
|
+
const state = normalizeCircleState(row.state ?? row.status ?? null);
|
|
166
|
+
if (id || state) return { id, state, raw: row };
|
|
167
|
+
}
|
|
168
|
+
return { id: null, state: null, raw: null };
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
async function parseErrorBody(response) {
|
|
172
|
+
const text = await response.text();
|
|
173
|
+
if (!text) return { text: "", json: null };
|
|
174
|
+
try {
|
|
175
|
+
return { text, json: JSON.parse(text) };
|
|
176
|
+
} catch {
|
|
177
|
+
return { text, json: null };
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
function errorIncludes(codeOrMessage, needle) {
|
|
182
|
+
const hay = String(codeOrMessage ?? "").toLowerCase();
|
|
183
|
+
return hay.includes(String(needle ?? "").toLowerCase());
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
async function fetchWithTimeout(fetchFn, url, init, timeoutMs) {
|
|
187
|
+
const ms = Number(timeoutMs);
|
|
188
|
+
if (!Number.isFinite(ms) || ms <= 0) throw new TypeError("timeoutMs must be a positive number");
|
|
189
|
+
const controller = new AbortController();
|
|
190
|
+
const timer = setTimeout(() => controller.abort(new Error("timeout")), ms);
|
|
191
|
+
try {
|
|
192
|
+
return await fetchFn(url, { ...init, signal: controller.signal });
|
|
193
|
+
} finally {
|
|
194
|
+
clearTimeout(timer);
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
function normalizeEntitySecretProvider({
|
|
199
|
+
entitySecretCiphertextProvider = null,
|
|
200
|
+
entitySecretCiphertext = null,
|
|
201
|
+
entitySecretTemplate = null,
|
|
202
|
+
allowStatic = false
|
|
203
|
+
} = {}) {
|
|
204
|
+
if (typeof entitySecretCiphertextProvider === "function") {
|
|
205
|
+
return { mode: "function", get: entitySecretCiphertextProvider };
|
|
206
|
+
}
|
|
207
|
+
if (typeof entitySecretTemplate === "string" && entitySecretTemplate.trim() !== "") {
|
|
208
|
+
const template = entitySecretTemplate.trim();
|
|
209
|
+
return {
|
|
210
|
+
mode: "template",
|
|
211
|
+
get: () => template.replaceAll("{{uuid}}", crypto.randomUUID())
|
|
212
|
+
};
|
|
213
|
+
}
|
|
214
|
+
if (typeof entitySecretCiphertext === "string" && entitySecretCiphertext.trim() !== "") {
|
|
215
|
+
const value = entitySecretCiphertext.trim();
|
|
216
|
+
if (!allowStatic) {
|
|
217
|
+
throw makeAdapterError(
|
|
218
|
+
"CIRCLE_CONFIG_INVALID",
|
|
219
|
+
"entitySecretCiphertext must be unique per request; provide CIRCLE_ENTITY_SECRET_CIPHERTEXT_TEMPLATE with {{uuid}} or a provider function"
|
|
220
|
+
);
|
|
221
|
+
}
|
|
222
|
+
return { mode: "static", get: () => value };
|
|
223
|
+
}
|
|
224
|
+
return { mode: "missing", get: null };
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
function normalizeEntitySecretHex(value) {
|
|
228
|
+
const raw = String(value ?? "").trim();
|
|
229
|
+
if (!raw) return null;
|
|
230
|
+
if (!/^[0-9a-fA-F]{64}$/.test(raw)) {
|
|
231
|
+
throw makeAdapterError("CIRCLE_CONFIG_INVALID", "ENTITY_SECRET must be a 64-character hex string");
|
|
232
|
+
}
|
|
233
|
+
return raw.toLowerCase();
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
function normalizePublicKeyPem(raw) {
|
|
237
|
+
const text = assertNonEmptyString(raw, "entityPublicKey");
|
|
238
|
+
if (text.includes("BEGIN PUBLIC KEY")) return text.replace(/\\n/g, "\n");
|
|
239
|
+
const chunks = text.match(/.{1,64}/g) ?? [];
|
|
240
|
+
return `-----BEGIN PUBLIC KEY-----\n${chunks.join("\n")}\n-----END PUBLIC KEY-----\n`;
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
function createEntitySecretCiphertextProvider({
|
|
244
|
+
apiKey,
|
|
245
|
+
baseUrl,
|
|
246
|
+
fetchFn,
|
|
247
|
+
timeoutMs,
|
|
248
|
+
requestId,
|
|
249
|
+
entitySecretHex
|
|
250
|
+
} = {}) {
|
|
251
|
+
let cachedPublicKeyPem = null;
|
|
252
|
+
const secret = normalizeEntitySecretHex(entitySecretHex);
|
|
253
|
+
if (!secret) return null;
|
|
254
|
+
return async () => {
|
|
255
|
+
if (!cachedPublicKeyPem) {
|
|
256
|
+
const requestUrl = new URL("/v1/w3s/config/entity/publicKey", baseUrl).toString();
|
|
257
|
+
const response = await fetchWithTimeout(
|
|
258
|
+
fetchFn,
|
|
259
|
+
requestUrl,
|
|
260
|
+
{
|
|
261
|
+
method: "GET",
|
|
262
|
+
headers: {
|
|
263
|
+
authorization: `Bearer ${apiKey}`,
|
|
264
|
+
accept: "application/json",
|
|
265
|
+
"x-request-id": requestId()
|
|
266
|
+
}
|
|
267
|
+
},
|
|
268
|
+
timeoutMs
|
|
269
|
+
);
|
|
270
|
+
const { json, text } = await parseErrorBody(response);
|
|
271
|
+
if (response.status < 200 || response.status >= 300) {
|
|
272
|
+
const detail = json?.message ?? json?.error ?? text ?? `HTTP ${response.status}`;
|
|
273
|
+
throw makeAdapterError("CIRCLE_HTTP_ERROR", `GET /v1/w3s/config/entity/publicKey failed: ${detail}`, {
|
|
274
|
+
status: response.status,
|
|
275
|
+
body: json ?? text
|
|
276
|
+
});
|
|
277
|
+
}
|
|
278
|
+
cachedPublicKeyPem = normalizePublicKeyPem(json?.data?.publicKey);
|
|
279
|
+
}
|
|
280
|
+
return crypto
|
|
281
|
+
.publicEncrypt(
|
|
282
|
+
{
|
|
283
|
+
key: cachedPublicKeyPem,
|
|
284
|
+
padding: crypto.constants.RSA_PKCS1_OAEP_PADDING,
|
|
285
|
+
oaepHash: "sha256"
|
|
286
|
+
},
|
|
287
|
+
Buffer.from(secret, "hex")
|
|
288
|
+
)
|
|
289
|
+
.toString("base64");
|
|
290
|
+
};
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
function normalizeTransferAmountField(value) {
|
|
294
|
+
const normalized = String(value ?? "amounts").trim().toLowerCase();
|
|
295
|
+
if (normalized === "amounts" || normalized === "amount") return normalized;
|
|
296
|
+
throw new TypeError("transferAmountField must be amounts|amount");
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
function normalizeFeeLevel(value) {
|
|
300
|
+
const normalized = String(value ?? "MEDIUM").trim().toUpperCase();
|
|
301
|
+
if (!normalized) return "MEDIUM";
|
|
302
|
+
return normalized;
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
function buildTransferBody({
|
|
306
|
+
sourceWalletId,
|
|
307
|
+
destinationAddress,
|
|
308
|
+
destinationWalletId = null,
|
|
309
|
+
amountString,
|
|
310
|
+
tokenId,
|
|
311
|
+
blockchain,
|
|
312
|
+
entitySecretCiphertext,
|
|
313
|
+
idempotencyKey,
|
|
314
|
+
transferAmountField = "amounts",
|
|
315
|
+
feeLevel = null
|
|
316
|
+
} = {}) {
|
|
317
|
+
const body = {
|
|
318
|
+
idempotencyKey,
|
|
319
|
+
walletId: sourceWalletId,
|
|
320
|
+
destinationAddress,
|
|
321
|
+
tokenId,
|
|
322
|
+
blockchain,
|
|
323
|
+
entitySecretCiphertext
|
|
324
|
+
};
|
|
325
|
+
if (destinationWalletId) body.destinationWalletId = destinationWalletId;
|
|
326
|
+
if (typeof feeLevel === "string" && feeLevel.trim() !== "") body.feeLevel = feeLevel.trim().toUpperCase();
|
|
327
|
+
if (transferAmountField === "amounts") body.amounts = [amountString];
|
|
328
|
+
else body.amount = amountString;
|
|
329
|
+
return body;
|
|
330
|
+
}
|
|
331
|
+
|
|
332
|
+
function normalizeBaseUrl(baseUrl, mode) {
|
|
333
|
+
const fallback = mode === "production" ? "https://api.circle.com" : "https://api-sandbox.circle.com";
|
|
334
|
+
const out = String(baseUrl ?? fallback).trim();
|
|
335
|
+
try {
|
|
336
|
+
const u = new URL(out);
|
|
337
|
+
return u.toString().replace(/\/+$/, "");
|
|
338
|
+
} catch (err) {
|
|
339
|
+
throw new TypeError(`invalid Circle baseUrl: ${err?.message ?? String(err ?? "")}`);
|
|
340
|
+
}
|
|
341
|
+
}
|
|
342
|
+
|
|
343
|
+
function readCircleRuntimeConfig({
|
|
344
|
+
mode,
|
|
345
|
+
config = null,
|
|
346
|
+
fetchFn = null,
|
|
347
|
+
now = () => new Date().toISOString(),
|
|
348
|
+
entitySecretCiphertextProvider = null
|
|
349
|
+
} = {}) {
|
|
350
|
+
const cfg = config && typeof config === "object" && !Array.isArray(config) ? config : {};
|
|
351
|
+
const env = typeof process !== "undefined" && process?.env ? process.env : {};
|
|
352
|
+
|
|
353
|
+
const normalizedMode = normalizeMode(mode);
|
|
354
|
+
const nowIso = () => normalizeIsoDate(typeof now === "function" ? now() : new Date().toISOString(), "now()");
|
|
355
|
+
const effectiveFetch = fetchFn ?? (typeof fetch === "function" ? fetch : null);
|
|
356
|
+
|
|
357
|
+
if (normalizedMode === "stub" || normalizedMode === "fail") {
|
|
358
|
+
return {
|
|
359
|
+
mode: normalizedMode,
|
|
360
|
+
nowIso,
|
|
361
|
+
fetchFn: effectiveFetch,
|
|
362
|
+
baseUrl: null,
|
|
363
|
+
apiKey: null,
|
|
364
|
+
timeoutMs: 0,
|
|
365
|
+
blockchain: null,
|
|
366
|
+
spendWalletId: null,
|
|
367
|
+
escrowWalletId: null,
|
|
368
|
+
spendAddress: null,
|
|
369
|
+
escrowAddress: null,
|
|
370
|
+
tokenId: null,
|
|
371
|
+
transferAmountField: "amounts",
|
|
372
|
+
entitySecret: { mode: "missing", get: null },
|
|
373
|
+
requestId: () => crypto.randomUUID()
|
|
374
|
+
};
|
|
375
|
+
}
|
|
376
|
+
|
|
377
|
+
if (!effectiveFetch) throw makeAdapterError("CIRCLE_CONFIG_INVALID", "fetchFn is required in sandbox/production mode");
|
|
378
|
+
|
|
379
|
+
const apiKey = assertNonEmptyString(cfg.apiKey ?? env.CIRCLE_API_KEY ?? "", "CIRCLE_API_KEY");
|
|
380
|
+
const baseUrl = normalizeBaseUrl(cfg.baseUrl ?? env.CIRCLE_BASE_URL ?? null, normalizedMode);
|
|
381
|
+
const timeoutMsRaw = cfg.timeoutMs ?? env.CIRCLE_TIMEOUT_MS ?? 20_000;
|
|
382
|
+
const timeoutMs = Number(timeoutMsRaw);
|
|
383
|
+
if (!Number.isFinite(timeoutMs) || timeoutMs <= 0) throw new TypeError("CIRCLE_TIMEOUT_MS must be a positive number");
|
|
384
|
+
|
|
385
|
+
const blockchain = assertNonEmptyString(cfg.blockchain ?? env.CIRCLE_BLOCKCHAIN ?? (normalizedMode === "production" ? "BASE" : "BASE-SEPOLIA"), "CIRCLE_BLOCKCHAIN");
|
|
386
|
+
const spendWalletId = assertNonEmptyString(cfg.spendWalletId ?? env.CIRCLE_WALLET_ID_SPEND ?? "", "CIRCLE_WALLET_ID_SPEND");
|
|
387
|
+
const escrowWalletId = assertNonEmptyString(cfg.escrowWalletId ?? env.CIRCLE_WALLET_ID_ESCROW ?? "", "CIRCLE_WALLET_ID_ESCROW");
|
|
388
|
+
const spendAddress = cfg.spendAddress ?? env.CIRCLE_SPEND_ADDRESS ?? null;
|
|
389
|
+
const escrowAddress = cfg.escrowAddress ?? env.CIRCLE_ESCROW_ADDRESS ?? null;
|
|
390
|
+
const tokenId = assertNonEmptyString(cfg.tokenId ?? env.CIRCLE_TOKEN_ID_USDC ?? "", "CIRCLE_TOKEN_ID_USDC");
|
|
391
|
+
const transferAmountField = normalizeTransferAmountField(cfg.transferAmountField ?? env.CIRCLE_TRANSFER_AMOUNT_FIELD ?? "amounts");
|
|
392
|
+
const feeLevel = normalizeFeeLevel(cfg.feeLevel ?? env.CIRCLE_FEE_LEVEL ?? "MEDIUM");
|
|
393
|
+
|
|
394
|
+
const requestId =
|
|
395
|
+
typeof cfg.requestId === "function"
|
|
396
|
+
? cfg.requestId
|
|
397
|
+
: () => {
|
|
398
|
+
return crypto.randomUUID();
|
|
399
|
+
};
|
|
400
|
+
|
|
401
|
+
const entitySecretHex = normalizeEntitySecretHex(cfg.entitySecretHex ?? env.CIRCLE_ENTITY_SECRET_HEX ?? env.ENTITY_SECRET ?? null);
|
|
402
|
+
const dynamicEntitySecretProvider =
|
|
403
|
+
typeof cfg.entitySecretCiphertextProvider === "function"
|
|
404
|
+
? null
|
|
405
|
+
: createEntitySecretCiphertextProvider({
|
|
406
|
+
apiKey,
|
|
407
|
+
baseUrl,
|
|
408
|
+
fetchFn: effectiveFetch,
|
|
409
|
+
timeoutMs,
|
|
410
|
+
requestId,
|
|
411
|
+
entitySecretHex
|
|
412
|
+
});
|
|
413
|
+
const entitySecret = normalizeEntitySecretProvider({
|
|
414
|
+
entitySecretCiphertextProvider: cfg.entitySecretCiphertextProvider ?? entitySecretCiphertextProvider ?? dynamicEntitySecretProvider,
|
|
415
|
+
entitySecretCiphertext: cfg.entitySecretCiphertext ?? env.CIRCLE_ENTITY_SECRET_CIPHERTEXT ?? null,
|
|
416
|
+
entitySecretTemplate: cfg.entitySecretTemplate ?? env.CIRCLE_ENTITY_SECRET_CIPHERTEXT_TEMPLATE ?? null,
|
|
417
|
+
allowStatic: parseBooleanLike(cfg.allowStaticEntitySecretCiphertext ?? env.CIRCLE_ALLOW_STATIC_ENTITY_SECRET, false)
|
|
418
|
+
});
|
|
419
|
+
if (typeof entitySecret.get !== "function") {
|
|
420
|
+
throw makeAdapterError(
|
|
421
|
+
"CIRCLE_CONFIG_INVALID",
|
|
422
|
+
"entitySecretCiphertext provider is required in sandbox/production mode (set CIRCLE_ENTITY_SECRET_CIPHERTEXT_TEMPLATE='...{{uuid}}...' or CIRCLE_ENTITY_SECRET_HEX)"
|
|
423
|
+
);
|
|
424
|
+
}
|
|
425
|
+
|
|
426
|
+
return {
|
|
427
|
+
mode: normalizedMode,
|
|
428
|
+
nowIso,
|
|
429
|
+
fetchFn: effectiveFetch,
|
|
430
|
+
baseUrl,
|
|
431
|
+
apiKey,
|
|
432
|
+
timeoutMs,
|
|
433
|
+
blockchain,
|
|
434
|
+
spendWalletId,
|
|
435
|
+
escrowWalletId,
|
|
436
|
+
spendAddress: typeof spendAddress === "string" && spendAddress.trim() !== "" ? spendAddress.trim() : null,
|
|
437
|
+
escrowAddress: typeof escrowAddress === "string" && escrowAddress.trim() !== "" ? escrowAddress.trim() : null,
|
|
438
|
+
tokenId,
|
|
439
|
+
feeLevel,
|
|
440
|
+
transferAmountField,
|
|
441
|
+
entitySecret:
|
|
442
|
+
dynamicEntitySecretProvider === null ? entitySecret : { mode: "derived", get: dynamicEntitySecretProvider },
|
|
443
|
+
requestId
|
|
444
|
+
};
|
|
445
|
+
}
|
|
446
|
+
|
|
447
|
+
function resolveResponseJsonOrThrow({ status, json, text, request }) {
|
|
448
|
+
if (status >= 200 && status < 300) return json;
|
|
449
|
+
const baseDetail = json?.message ?? json?.error ?? text ?? `HTTP ${status}`;
|
|
450
|
+
const validationErrors = Array.isArray(json?.errors) ? json.errors : null;
|
|
451
|
+
const detail = validationErrors ? `${baseDetail} ${JSON.stringify(validationErrors)}` : baseDetail;
|
|
452
|
+
throw makeAdapterError("CIRCLE_HTTP_ERROR", `${request} failed: ${detail}`, { status, body: json ?? text });
|
|
453
|
+
}
|
|
454
|
+
|
|
455
|
+
async function fetchCircleJson({ runtime, method, path, body = null } = {}) {
|
|
456
|
+
const requestUrl = new URL(path, runtime.baseUrl).toString();
|
|
457
|
+
const headers = {
|
|
458
|
+
authorization: `Bearer ${runtime.apiKey}`,
|
|
459
|
+
"content-type": "application/json; charset=utf-8",
|
|
460
|
+
"x-request-id": runtime.requestId()
|
|
461
|
+
};
|
|
462
|
+
const response = await fetchWithTimeout(
|
|
463
|
+
runtime.fetchFn,
|
|
464
|
+
requestUrl,
|
|
465
|
+
{
|
|
466
|
+
method,
|
|
467
|
+
headers,
|
|
468
|
+
body: body === null ? undefined : JSON.stringify(body)
|
|
469
|
+
},
|
|
470
|
+
runtime.timeoutMs
|
|
471
|
+
);
|
|
472
|
+
const { json, text } = await parseErrorBody(response);
|
|
473
|
+
return resolveResponseJsonOrThrow({
|
|
474
|
+
status: response.status,
|
|
475
|
+
json,
|
|
476
|
+
text,
|
|
477
|
+
request: `${method} ${path}`
|
|
478
|
+
});
|
|
479
|
+
}
|
|
480
|
+
|
|
481
|
+
function normalizeTransferError(error, { operation, details } = {}) {
|
|
482
|
+
const raw = error?.details?.body ?? error?.details ?? null;
|
|
483
|
+
const rawText = raw && typeof raw === "object" ? JSON.stringify(raw) : String(raw ?? error?.message ?? "");
|
|
484
|
+
if (errorIncludes(error?.code, "CIRCLE_HTTP_ERROR") && errorIncludes(rawText, "amounts")) {
|
|
485
|
+
return makeAdapterError("CIRCLE_RESERVE_FAILED", `${operation} failed: invalid transfer amount field`, details ?? null);
|
|
486
|
+
}
|
|
487
|
+
const code = error?.code ?? "CIRCLE_RESERVE_FAILED";
|
|
488
|
+
return makeAdapterError(code, `${operation} failed: ${error?.message ?? String(error ?? "")}`, details ?? null);
|
|
489
|
+
}
|
|
490
|
+
|
|
491
|
+
async function fetchCircleTransactionById({ runtime, transactionId } = {}) {
|
|
492
|
+
const txId = assertNonEmptyString(transactionId, "transactionId");
|
|
493
|
+
const candidatePaths = [`/v1/w3s/transactions/${encodeURIComponent(txId)}`, `/v1/w3s/developer/transactions/${encodeURIComponent(txId)}`];
|
|
494
|
+
let lastError = null;
|
|
495
|
+
for (const path of candidatePaths) {
|
|
496
|
+
try {
|
|
497
|
+
const payload = await fetchCircleJson({ runtime, method: "GET", path });
|
|
498
|
+
const extracted = extractCircleTransaction(payload);
|
|
499
|
+
const state = normalizeCircleState(extracted.state);
|
|
500
|
+
return {
|
|
501
|
+
transactionId: extracted.id ?? txId,
|
|
502
|
+
state,
|
|
503
|
+
raw: extracted.raw ?? payload
|
|
504
|
+
};
|
|
505
|
+
} catch (err) {
|
|
506
|
+
lastError = err;
|
|
507
|
+
if (String(err?.code ?? "") !== "CIRCLE_HTTP_ERROR") throw err;
|
|
508
|
+
}
|
|
509
|
+
}
|
|
510
|
+
throw lastError ?? makeAdapterError("CIRCLE_HTTP_ERROR", `unable to fetch Circle transaction ${txId}`);
|
|
511
|
+
}
|
|
512
|
+
|
|
513
|
+
async function transferWithShape({
|
|
514
|
+
runtime,
|
|
515
|
+
sourceWalletId,
|
|
516
|
+
destinationAddress,
|
|
517
|
+
destinationWalletId = null,
|
|
518
|
+
amountCents,
|
|
519
|
+
idempotencyKey,
|
|
520
|
+
transferAmountField
|
|
521
|
+
} = {}) {
|
|
522
|
+
const amountString = centsToAssetAmountString(amountCents);
|
|
523
|
+
const entitySecretCiphertext = assertNonEmptyString(await runtime.entitySecret.get(), "entitySecretCiphertext");
|
|
524
|
+
const body = buildTransferBody({
|
|
525
|
+
sourceWalletId,
|
|
526
|
+
destinationAddress,
|
|
527
|
+
destinationWalletId,
|
|
528
|
+
amountString,
|
|
529
|
+
tokenId: runtime.tokenId,
|
|
530
|
+
blockchain: runtime.blockchain,
|
|
531
|
+
entitySecretCiphertext,
|
|
532
|
+
idempotencyKey,
|
|
533
|
+
transferAmountField,
|
|
534
|
+
feeLevel: runtime.feeLevel
|
|
535
|
+
});
|
|
536
|
+
|
|
537
|
+
const payload = await fetchCircleJson({
|
|
538
|
+
runtime,
|
|
539
|
+
method: "POST",
|
|
540
|
+
path: "/v1/w3s/developer/transactions/transfer",
|
|
541
|
+
body
|
|
542
|
+
});
|
|
543
|
+
|
|
544
|
+
const extracted = extractCircleTransaction(payload);
|
|
545
|
+
const transactionId = extracted.id;
|
|
546
|
+
const initialState = normalizeCircleState(extracted.state);
|
|
547
|
+
if (!transactionId) {
|
|
548
|
+
throw makeAdapterError("CIRCLE_RESERVE_FAILED", "Circle transfer response missing transaction id");
|
|
549
|
+
}
|
|
550
|
+
|
|
551
|
+
const state = initialState ?? (await fetchCircleTransactionById({ runtime, transactionId })).state;
|
|
552
|
+
return {
|
|
553
|
+
transactionId,
|
|
554
|
+
state
|
|
555
|
+
};
|
|
556
|
+
}
|
|
557
|
+
|
|
558
|
+
async function tryCircleCancel({ runtime, reserveId } = {}) {
|
|
559
|
+
const txId = assertNonEmptyString(reserveId, "reserveId");
|
|
560
|
+
try {
|
|
561
|
+
const payload = await fetchCircleJson({
|
|
562
|
+
runtime,
|
|
563
|
+
method: "POST",
|
|
564
|
+
path: `/v1/w3s/developer/transactions/${encodeURIComponent(txId)}/cancel`,
|
|
565
|
+
body: {}
|
|
566
|
+
});
|
|
567
|
+
const extracted = extractCircleTransaction(payload);
|
|
568
|
+
const state = normalizeCircleState(extracted.state);
|
|
569
|
+
return {
|
|
570
|
+
cancelled: state === CIRCLE_TRANSACTION_STATE.CANCELLED,
|
|
571
|
+
state,
|
|
572
|
+
transactionId: extracted.id ?? txId
|
|
573
|
+
};
|
|
574
|
+
} catch (err) {
|
|
575
|
+
if (String(err?.code ?? "") === "CIRCLE_HTTP_ERROR") {
|
|
576
|
+
const msg = JSON.stringify(err?.details?.body ?? {});
|
|
577
|
+
if (errorIncludes(msg, "cannot cancel") || errorIncludes(msg, "not cancellable") || errorIncludes(msg, "already")) {
|
|
578
|
+
return { cancelled: false, state: null, transactionId: txId };
|
|
579
|
+
}
|
|
580
|
+
}
|
|
581
|
+
throw err;
|
|
582
|
+
}
|
|
583
|
+
}
|
|
584
|
+
|
|
585
|
+
export function createCircleReserveAdapter({
|
|
586
|
+
mode = "stub",
|
|
587
|
+
now = () => new Date().toISOString(),
|
|
588
|
+
fetchFn = null,
|
|
589
|
+
config = null,
|
|
590
|
+
entitySecretCiphertextProvider = null
|
|
591
|
+
} = {}) {
|
|
592
|
+
const normalizedMode = normalizeMode(mode);
|
|
593
|
+
let runtime = null;
|
|
594
|
+
function getRuntime() {
|
|
595
|
+
if (runtime) return runtime;
|
|
596
|
+
runtime = readCircleRuntimeConfig({ mode: normalizedMode, config, fetchFn, now, entitySecretCiphertextProvider });
|
|
597
|
+
return runtime;
|
|
598
|
+
}
|
|
599
|
+
|
|
600
|
+
const walletAddressCache = new Map();
|
|
601
|
+
async function resolveWalletAddress(runtimeValue, walletId, { fallbackAddress = null } = {}) {
|
|
602
|
+
const normalizedWalletId = assertNonEmptyString(walletId, "walletId");
|
|
603
|
+
if (typeof fallbackAddress === "string" && fallbackAddress.trim() !== "") return fallbackAddress.trim();
|
|
604
|
+
if (walletAddressCache.has(normalizedWalletId)) return walletAddressCache.get(normalizedWalletId);
|
|
605
|
+
const payload = await fetchCircleJson({
|
|
606
|
+
runtime: runtimeValue,
|
|
607
|
+
method: "GET",
|
|
608
|
+
path: `/v1/w3s/wallets/${encodeURIComponent(normalizedWalletId)}`
|
|
609
|
+
});
|
|
610
|
+
const root = pickFirstObject(payload);
|
|
611
|
+
const candidates = [];
|
|
612
|
+
if (root) candidates.push(root);
|
|
613
|
+
if (root?.wallet) candidates.push(root.wallet);
|
|
614
|
+
if (root?.data) candidates.push(root.data);
|
|
615
|
+
if (root?.data?.wallet) candidates.push(root.data.wallet);
|
|
616
|
+
if (Array.isArray(root?.data?.wallets)) {
|
|
617
|
+
for (const row of root.data.wallets) {
|
|
618
|
+
if (row && typeof row === "object" && !Array.isArray(row)) candidates.push(row);
|
|
619
|
+
}
|
|
620
|
+
}
|
|
621
|
+
let address = null;
|
|
622
|
+
for (const candidate of candidates) {
|
|
623
|
+
const row = pickFirstObject(candidate);
|
|
624
|
+
if (!row) continue;
|
|
625
|
+
if (typeof row.address === "string" && row.address.trim() !== "") {
|
|
626
|
+
address = row.address.trim();
|
|
627
|
+
break;
|
|
628
|
+
}
|
|
629
|
+
if (row.blockchainAddress && typeof row.blockchainAddress === "string" && row.blockchainAddress.trim() !== "") {
|
|
630
|
+
address = row.blockchainAddress.trim();
|
|
631
|
+
break;
|
|
632
|
+
}
|
|
633
|
+
if (Array.isArray(row.addresses) && row.addresses.length > 0) {
|
|
634
|
+
const first = pickFirstObject(row.addresses[0]);
|
|
635
|
+
if (first && typeof first.address === "string" && first.address.trim() !== "") {
|
|
636
|
+
address = first.address.trim();
|
|
637
|
+
break;
|
|
638
|
+
}
|
|
639
|
+
}
|
|
640
|
+
}
|
|
641
|
+
if (!address) throw makeAdapterError("CIRCLE_CONFIG_INVALID", `unable to resolve wallet address for ${normalizedWalletId}`);
|
|
642
|
+
walletAddressCache.set(normalizedWalletId, address);
|
|
643
|
+
return address;
|
|
644
|
+
}
|
|
645
|
+
|
|
646
|
+
function nowIso() {
|
|
647
|
+
if (normalizedMode === "stub" || normalizedMode === "fail") {
|
|
648
|
+
return normalizeIsoDate(typeof now === "function" ? now() : new Date().toISOString(), "now()");
|
|
649
|
+
}
|
|
650
|
+
return getRuntime().nowIso();
|
|
651
|
+
}
|
|
652
|
+
|
|
653
|
+
async function reserve({
|
|
654
|
+
tenantId,
|
|
655
|
+
gateId,
|
|
656
|
+
amountCents,
|
|
657
|
+
currency = "USD",
|
|
658
|
+
idempotencyKey = null,
|
|
659
|
+
payerAgentId = null,
|
|
660
|
+
payeeAgentId = null
|
|
661
|
+
} = {}) {
|
|
662
|
+
const normalizedTenantId = assertNonEmptyString(tenantId, "tenantId");
|
|
663
|
+
const normalizedGateId = assertNonEmptyString(gateId, "gateId");
|
|
664
|
+
const normalizedAmountCents = normalizePositiveSafeInt(amountCents, "amountCents");
|
|
665
|
+
const normalizedCurrency = normalizeCurrency(currency, "currency");
|
|
666
|
+
const normalizedIdempotencyKey =
|
|
667
|
+
idempotencyKey === null || idempotencyKey === undefined || String(idempotencyKey).trim() === ""
|
|
668
|
+
? normalizedGateId
|
|
669
|
+
: String(idempotencyKey).trim();
|
|
670
|
+
|
|
671
|
+
if (normalizedMode === "fail") {
|
|
672
|
+
throw makeAdapterError("CIRCLE_RESERVE_UNAVAILABLE", "circle reserve unavailable");
|
|
673
|
+
}
|
|
674
|
+
|
|
675
|
+
if (normalizedMode === "stub") {
|
|
676
|
+
const reserveId = `circle_transfer_${sha256Hex(
|
|
677
|
+
`${normalizedTenantId}\n${normalizedGateId}\n${normalizedAmountCents}\n${normalizedCurrency}\n${normalizedIdempotencyKey}\n${String(
|
|
678
|
+
payerAgentId ?? ""
|
|
679
|
+
)}\n${String(payeeAgentId ?? "")}`
|
|
680
|
+
).slice(0, 32)}`;
|
|
681
|
+
return {
|
|
682
|
+
reserveId,
|
|
683
|
+
status: CIRCLE_RESERVE_STATUS.RESERVED,
|
|
684
|
+
adapter: "circle",
|
|
685
|
+
mode: "transfer",
|
|
686
|
+
amountCents: normalizedAmountCents,
|
|
687
|
+
currency: normalizedCurrency,
|
|
688
|
+
createdAt: nowIso(),
|
|
689
|
+
metadata: {
|
|
690
|
+
idempotencyKey: normalizedIdempotencyKey
|
|
691
|
+
}
|
|
692
|
+
};
|
|
693
|
+
}
|
|
694
|
+
|
|
695
|
+
const runtimeValue = getRuntime();
|
|
696
|
+
const circleIdempotencyKey = normalizeCircleIdempotencyKey(normalizedIdempotencyKey);
|
|
697
|
+
const destinationAddress = await resolveWalletAddress(runtimeValue, runtimeValue.escrowWalletId, {
|
|
698
|
+
fallbackAddress: runtimeValue.escrowAddress
|
|
699
|
+
});
|
|
700
|
+
let transferred = null;
|
|
701
|
+
try {
|
|
702
|
+
transferred = await transferWithShape({
|
|
703
|
+
runtime: runtimeValue,
|
|
704
|
+
sourceWalletId: runtimeValue.spendWalletId,
|
|
705
|
+
destinationAddress,
|
|
706
|
+
destinationWalletId: runtimeValue.escrowWalletId,
|
|
707
|
+
amountCents: normalizedAmountCents,
|
|
708
|
+
idempotencyKey: circleIdempotencyKey,
|
|
709
|
+
transferAmountField: runtimeValue.transferAmountField
|
|
710
|
+
});
|
|
711
|
+
} catch (err) {
|
|
712
|
+
throw normalizeTransferError(err, {
|
|
713
|
+
operation: "reserve transfer",
|
|
714
|
+
details: { gateId: normalizedGateId, idempotencyKey: normalizedIdempotencyKey }
|
|
715
|
+
});
|
|
716
|
+
}
|
|
717
|
+
|
|
718
|
+
const circleState = normalizeCircleState(transferred.state);
|
|
719
|
+
const classification = classifyCircleReserveState(circleState);
|
|
720
|
+
if (classification !== "reserved") {
|
|
721
|
+
const errCode = classification === "uncertain" ? "CIRCLE_RESERVE_UNCERTAIN" : "CIRCLE_RESERVE_FAILED";
|
|
722
|
+
throw makeAdapterError(errCode, `Circle reserve not safe to authorize (state=${circleState ?? "unknown"})`, {
|
|
723
|
+
circleTransactionId: transferred.transactionId,
|
|
724
|
+
circleState
|
|
725
|
+
});
|
|
726
|
+
}
|
|
727
|
+
|
|
728
|
+
return {
|
|
729
|
+
reserveId: transferred.transactionId,
|
|
730
|
+
status: CIRCLE_RESERVE_STATUS.RESERVED,
|
|
731
|
+
adapter: "circle",
|
|
732
|
+
mode: "transfer",
|
|
733
|
+
amountCents: normalizedAmountCents,
|
|
734
|
+
currency: normalizedCurrency,
|
|
735
|
+
createdAt: nowIso(),
|
|
736
|
+
circleState,
|
|
737
|
+
metadata: {
|
|
738
|
+
idempotencyKey: normalizedIdempotencyKey,
|
|
739
|
+
circleIdempotencyKey
|
|
740
|
+
}
|
|
741
|
+
};
|
|
742
|
+
}
|
|
743
|
+
|
|
744
|
+
async function voidReserve({
|
|
745
|
+
reserveId,
|
|
746
|
+
idempotencyKey = null,
|
|
747
|
+
amountCents = null,
|
|
748
|
+
currency = "USD"
|
|
749
|
+
} = {}) {
|
|
750
|
+
const normalizedReserveId = assertNonEmptyString(reserveId, "reserveId");
|
|
751
|
+
const normalizedCurrency = normalizeCurrency(currency, "currency");
|
|
752
|
+
const nowAt = nowIso();
|
|
753
|
+
|
|
754
|
+
if (normalizedMode === "stub") {
|
|
755
|
+
return {
|
|
756
|
+
reserveId: normalizedReserveId,
|
|
757
|
+
status: CIRCLE_RESERVE_STATUS.VOIDED,
|
|
758
|
+
voidedAt: nowAt,
|
|
759
|
+
method: "stub"
|
|
760
|
+
};
|
|
761
|
+
}
|
|
762
|
+
if (normalizedMode === "fail") {
|
|
763
|
+
throw makeAdapterError("CIRCLE_RESERVE_UNAVAILABLE", "circle reserve unavailable");
|
|
764
|
+
}
|
|
765
|
+
|
|
766
|
+
const runtimeValue = getRuntime();
|
|
767
|
+
const tx = await fetchCircleTransactionById({ runtime: runtimeValue, transactionId: normalizedReserveId });
|
|
768
|
+
const state = normalizeCircleState(tx.state);
|
|
769
|
+
if (state === CIRCLE_TRANSACTION_STATE.CANCELLED || state === CIRCLE_TRANSACTION_STATE.DENIED || state === CIRCLE_TRANSACTION_STATE.FAILED) {
|
|
770
|
+
return {
|
|
771
|
+
reserveId: normalizedReserveId,
|
|
772
|
+
status: CIRCLE_RESERVE_STATUS.VOIDED,
|
|
773
|
+
voidedAt: nowAt,
|
|
774
|
+
method: "already_terminal",
|
|
775
|
+
circleState: state
|
|
776
|
+
};
|
|
777
|
+
}
|
|
778
|
+
|
|
779
|
+
if (CANCELLABLE_STATES.has(state)) {
|
|
780
|
+
const cancelled = await tryCircleCancel({ runtime: runtimeValue, reserveId: normalizedReserveId });
|
|
781
|
+
if (cancelled.cancelled) {
|
|
782
|
+
return {
|
|
783
|
+
reserveId: normalizedReserveId,
|
|
784
|
+
status: CIRCLE_RESERVE_STATUS.VOIDED,
|
|
785
|
+
voidedAt: nowAt,
|
|
786
|
+
method: "cancel",
|
|
787
|
+
circleState: cancelled.state ?? CIRCLE_TRANSACTION_STATE.CANCELLED
|
|
788
|
+
};
|
|
789
|
+
}
|
|
790
|
+
}
|
|
791
|
+
|
|
792
|
+
const normalizedAmountCents = normalizePositiveSafeInt(amountCents, "amountCents");
|
|
793
|
+
const compensateSourceWalletId = runtimeValue.escrowWalletId;
|
|
794
|
+
const spendAddress = await resolveWalletAddress(runtimeValue, runtimeValue.spendWalletId, {
|
|
795
|
+
fallbackAddress: runtimeValue.spendAddress
|
|
796
|
+
});
|
|
797
|
+
const compensationIdempotencySource =
|
|
798
|
+
idempotencyKey === null || idempotencyKey === undefined || String(idempotencyKey).trim() === ""
|
|
799
|
+
? `${normalizedReserveId}:void`
|
|
800
|
+
: String(idempotencyKey).trim();
|
|
801
|
+
const compensationIdempotencyKey = normalizeCircleIdempotencyKey(compensationIdempotencySource);
|
|
802
|
+
|
|
803
|
+
let compensation = null;
|
|
804
|
+
try {
|
|
805
|
+
compensation = await transferWithShape({
|
|
806
|
+
runtime: runtimeValue,
|
|
807
|
+
sourceWalletId: compensateSourceWalletId,
|
|
808
|
+
destinationAddress: spendAddress,
|
|
809
|
+
destinationWalletId: runtimeValue.spendWalletId,
|
|
810
|
+
amountCents: normalizedAmountCents,
|
|
811
|
+
idempotencyKey: compensationIdempotencyKey,
|
|
812
|
+
transferAmountField: runtimeValue.transferAmountField
|
|
813
|
+
});
|
|
814
|
+
} catch (err) {
|
|
815
|
+
throw normalizeTransferError(err, { operation: "compensating transfer", details: { reserveId: normalizedReserveId } });
|
|
816
|
+
}
|
|
817
|
+
|
|
818
|
+
const compensationState = normalizeCircleState(compensation.state);
|
|
819
|
+
const compensationClass = classifyCircleReserveState(compensationState);
|
|
820
|
+
if (compensationClass !== "reserved") {
|
|
821
|
+
throw makeAdapterError("CIRCLE_RESERVE_VOID_FAILED", `compensating transfer failed (state=${compensationState ?? "unknown"})`, {
|
|
822
|
+
reserveId: normalizedReserveId,
|
|
823
|
+
compensationReserveId: compensation.transactionId
|
|
824
|
+
});
|
|
825
|
+
}
|
|
826
|
+
|
|
827
|
+
return {
|
|
828
|
+
reserveId: normalizedReserveId,
|
|
829
|
+
status: CIRCLE_RESERVE_STATUS.VOIDED,
|
|
830
|
+
voidedAt: nowAt,
|
|
831
|
+
method: "compensate",
|
|
832
|
+
circleState: compensationState,
|
|
833
|
+
compensationReserveId: compensation.transactionId,
|
|
834
|
+
amountCents: normalizedAmountCents,
|
|
835
|
+
currency: normalizedCurrency
|
|
836
|
+
};
|
|
837
|
+
}
|
|
838
|
+
|
|
839
|
+
return {
|
|
840
|
+
providerId: "circle",
|
|
841
|
+
mode: normalizedMode,
|
|
842
|
+
reserve,
|
|
843
|
+
void: voidReserve
|
|
844
|
+
};
|
|
845
|
+
}
|