x402-trust-layer 5.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +55 -0
- package/DEPLOY.md +53 -0
- package/Dockerfile +30 -0
- package/LICENSE +21 -0
- package/README.md +294 -0
- package/dist/agents/a2a-payment.d.ts +37 -0
- package/dist/agents/a2a-payment.js +105 -0
- package/dist/agents/agent-escrow.d.ts +30 -0
- package/dist/agents/agent-escrow.js +23 -0
- package/dist/agents/agent-verify.d.ts +15 -0
- package/dist/agents/agent-verify.js +112 -0
- package/dist/agents/api-router.d.ts +32 -0
- package/dist/agents/api-router.js +228 -0
- package/dist/agents/attestation-registry.d.ts +35 -0
- package/dist/agents/attestation-registry.js +76 -0
- package/dist/agents/audition-coach.d.ts +45 -0
- package/dist/agents/audition-coach.js +257 -0
- package/dist/agents/bedrock-bridge.d.ts +3 -0
- package/dist/agents/bedrock-bridge.js +60 -0
- package/dist/agents/budget-allocator.d.ts +24 -0
- package/dist/agents/budget-allocator.js +31 -0
- package/dist/agents/compliance-ledger.d.ts +66 -0
- package/dist/agents/compliance-ledger.js +80 -0
- package/dist/agents/dispute-resolver.d.ts +62 -0
- package/dist/agents/dispute-resolver.js +124 -0
- package/dist/agents/evidence-locker.d.ts +30 -0
- package/dist/agents/evidence-locker.js +47 -0
- package/dist/agents/facilitator-failover.d.ts +15 -0
- package/dist/agents/facilitator-failover.js +18 -0
- package/dist/agents/identity-gate.d.ts +20 -0
- package/dist/agents/identity-gate.js +79 -0
- package/dist/agents/mandate-compiler.d.ts +51 -0
- package/dist/agents/mandate-compiler.js +73 -0
- package/dist/agents/mandate-diff.d.ts +41 -0
- package/dist/agents/mandate-diff.js +170 -0
- package/dist/agents/market-buy-advisor.d.ts +65 -0
- package/dist/agents/market-buy-advisor.js +234 -0
- package/dist/agents/merchant-trust.d.ts +38 -0
- package/dist/agents/merchant-trust.js +171 -0
- package/dist/agents/mpp-session-broker.d.ts +27 -0
- package/dist/agents/mpp-session-broker.js +29 -0
- package/dist/agents/mpp-session-v2.d.ts +76 -0
- package/dist/agents/mpp-session-v2.js +269 -0
- package/dist/agents/payment-intent-compiler.d.ts +21 -0
- package/dist/agents/payment-intent-compiler.js +45 -0
- package/dist/agents/pipeline-execute.d.ts +40 -0
- package/dist/agents/pipeline-execute.js +100 -0
- package/dist/agents/pipeline-trust-v2.d.ts +31 -0
- package/dist/agents/pipeline-trust-v2.js +111 -0
- package/dist/agents/pre-x402-guard.d.ts +35 -0
- package/dist/agents/pre-x402-guard.js +84 -0
- package/dist/agents/quality-escrow-semantic.d.ts +88 -0
- package/dist/agents/quality-escrow-semantic.js +137 -0
- package/dist/agents/quality-escrow.d.ts +65 -0
- package/dist/agents/quality-escrow.js +104 -0
- package/dist/agents/quality-monitor.d.ts +32 -0
- package/dist/agents/quality-monitor.js +77 -0
- package/dist/agents/rail-optimizer.d.ts +33 -0
- package/dist/agents/rail-optimizer.js +133 -0
- package/dist/agents/receipt-auditor.d.ts +14 -0
- package/dist/agents/receipt-auditor.js +145 -0
- package/dist/agents/refund-arbiter.d.ts +24 -0
- package/dist/agents/refund-arbiter.js +70 -0
- package/dist/agents/research-brief.d.ts +14 -0
- package/dist/agents/research-brief.js +66 -0
- package/dist/agents/risk-gate.d.ts +11 -0
- package/dist/agents/risk-gate.js +78 -0
- package/dist/agents/settlement-graph.d.ts +16 -0
- package/dist/agents/settlement-graph.js +38 -0
- package/dist/agents/spend-governor.d.ts +2 -0
- package/dist/agents/spend-governor.js +70 -0
- package/dist/agents/trust-network.d.ts +138 -0
- package/dist/agents/trust-network.js +244 -0
- package/dist/agents/x402-proxy.d.ts +32 -0
- package/dist/agents/x402-proxy.js +90 -0
- package/dist/client/demo-alchemy-live.d.ts +1 -0
- package/dist/client/demo-alchemy-live.js +226 -0
- package/dist/client/demo-tail.d.ts +1 -0
- package/dist/client/demo-tail.js +100 -0
- package/dist/client/demo.d.ts +1 -0
- package/dist/client/demo.js +293 -0
- package/dist/config.d.ts +94 -0
- package/dist/config.js +223 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.js +389 -0
- package/dist/lib/agent-response.d.ts +14 -0
- package/dist/lib/agent-response.js +13 -0
- package/dist/lib/agentic-gateways.d.ts +5 -0
- package/dist/lib/agentic-gateways.js +15 -0
- package/dist/lib/agentic-probes.d.ts +10 -0
- package/dist/lib/agentic-probes.js +49 -0
- package/dist/lib/alchemy-x402-fetch.d.ts +16 -0
- package/dist/lib/alchemy-x402-fetch.js +95 -0
- package/dist/lib/apply-verifier-body.d.ts +7 -0
- package/dist/lib/apply-verifier-body.js +179 -0
- package/dist/lib/attestation.d.ts +30 -0
- package/dist/lib/attestation.js +107 -0
- package/dist/lib/bazaar-extension.d.ts +15 -0
- package/dist/lib/bazaar-extension.js +265 -0
- package/dist/lib/bazaar.d.ts +100 -0
- package/dist/lib/bazaar.js +341 -0
- package/dist/lib/certified-sellers.d.ts +41 -0
- package/dist/lib/certified-sellers.js +129 -0
- package/dist/lib/chains.d.ts +20 -0
- package/dist/lib/chains.js +78 -0
- package/dist/lib/db-persistence.d.ts +7 -0
- package/dist/lib/db-persistence.js +65 -0
- package/dist/lib/db.d.ts +5 -0
- package/dist/lib/db.js +113 -0
- package/dist/lib/discovery-page.d.ts +2 -0
- package/dist/lib/discovery-page.js +71 -0
- package/dist/lib/ecosystem-telemetry.d.ts +20 -0
- package/dist/lib/ecosystem-telemetry.js +80 -0
- package/dist/lib/erc8004/agent-card.d.ts +34 -0
- package/dist/lib/erc8004/agent-card.js +151 -0
- package/dist/lib/erc8004/cache.d.ts +3 -0
- package/dist/lib/erc8004/cache.js +17 -0
- package/dist/lib/erc8004/constants.d.ts +22 -0
- package/dist/lib/erc8004/constants.js +35 -0
- package/dist/lib/erc8004/registry.d.ts +19 -0
- package/dist/lib/erc8004/registry.js +171 -0
- package/dist/lib/erc8004/resolve-agent.d.ts +7 -0
- package/dist/lib/erc8004/resolve-agent.js +70 -0
- package/dist/lib/erc8004/trust-score.d.ts +33 -0
- package/dist/lib/erc8004/trust-score.js +136 -0
- package/dist/lib/escrow-ledger.d.ts +14 -0
- package/dist/lib/escrow-ledger.js +54 -0
- package/dist/lib/escrow-unified.d.ts +15 -0
- package/dist/lib/escrow-unified.js +28 -0
- package/dist/lib/facilitator-extra.d.ts +13 -0
- package/dist/lib/facilitator-extra.js +52 -0
- package/dist/lib/facilitators.d.ts +20 -0
- package/dist/lib/facilitators.js +89 -0
- package/dist/lib/host-policy.d.ts +4 -0
- package/dist/lib/host-policy.js +20 -0
- package/dist/lib/idempotency.d.ts +4 -0
- package/dist/lib/idempotency.js +120 -0
- package/dist/lib/ledger.d.ts +2 -0
- package/dist/lib/ledger.js +17 -0
- package/dist/lib/logger.d.ts +6 -0
- package/dist/lib/logger.js +24 -0
- package/dist/lib/mandate-vc.d.ts +20 -0
- package/dist/lib/mandate-vc.js +25 -0
- package/dist/lib/mandate.d.ts +44 -0
- package/dist/lib/mandate.js +190 -0
- package/dist/lib/marketplace.d.ts +7 -0
- package/dist/lib/marketplace.js +127 -0
- package/dist/lib/migrations.d.ts +2 -0
- package/dist/lib/migrations.js +130 -0
- package/dist/lib/nonce-store.d.ts +6 -0
- package/dist/lib/nonce-store.js +109 -0
- package/dist/lib/openapi-agentcash.d.ts +5 -0
- package/dist/lib/openapi-agentcash.js +288 -0
- package/dist/lib/openapi-meta.d.ts +5 -0
- package/dist/lib/openapi-meta.js +235 -0
- package/dist/lib/otel.d.ts +2 -0
- package/dist/lib/otel.js +25 -0
- package/dist/lib/paid-resource-url.d.ts +6 -0
- package/dist/lib/paid-resource-url.js +47 -0
- package/dist/lib/parse-with-verifier-fallback.d.ts +3 -0
- package/dist/lib/parse-with-verifier-fallback.js +13 -0
- package/dist/lib/payment-request-context.d.ts +10 -0
- package/dist/lib/payment-request-context.js +5 -0
- package/dist/lib/payment-response.d.ts +13 -0
- package/dist/lib/payment-response.js +39 -0
- package/dist/lib/payto-guard.d.ts +10 -0
- package/dist/lib/payto-guard.js +20 -0
- package/dist/lib/probe.d.ts +29 -0
- package/dist/lib/probe.js +157 -0
- package/dist/lib/problem-detail.d.ts +10 -0
- package/dist/lib/problem-detail.js +14 -0
- package/dist/lib/rate-limit.d.ts +12 -0
- package/dist/lib/rate-limit.js +126 -0
- package/dist/lib/replay-middleware.d.ts +3 -0
- package/dist/lib/replay-middleware.js +27 -0
- package/dist/lib/response-guard.d.ts +5 -0
- package/dist/lib/response-guard.js +40 -0
- package/dist/lib/safe-fetch.d.ts +5 -0
- package/dist/lib/safe-fetch.js +19 -0
- package/dist/lib/security.d.ts +13 -0
- package/dist/lib/security.js +61 -0
- package/dist/lib/semantic-judge.d.ts +14 -0
- package/dist/lib/semantic-judge.js +107 -0
- package/dist/lib/semantic-judge.test.d.ts +1 -0
- package/dist/lib/semantic-judge.test.js +11 -0
- package/dist/lib/ssrf.d.ts +10 -0
- package/dist/lib/ssrf.js +130 -0
- package/dist/lib/ssrf.test.d.ts +1 -0
- package/dist/lib/ssrf.test.js +16 -0
- package/dist/lib/suite-catalog.d.ts +83 -0
- package/dist/lib/suite-catalog.js +131 -0
- package/dist/lib/telemetry.d.ts +5 -0
- package/dist/lib/telemetry.js +37 -0
- package/dist/lib/verifier-fast-path.d.ts +10 -0
- package/dist/lib/verifier-fast-path.js +44 -0
- package/dist/lib/verifier-probe-protocol.d.ts +7 -0
- package/dist/lib/verifier-probe-protocol.js +115 -0
- package/dist/lib/verify-examples.d.ts +2 -0
- package/dist/lib/verify-examples.js +438 -0
- package/dist/lib/version.d.ts +2 -0
- package/dist/lib/version.js +2 -0
- package/dist/lib/webhook-auth.d.ts +3 -0
- package/dist/lib/webhook-auth.js +34 -0
- package/dist/lib/webhook-routes.d.ts +2 -0
- package/dist/lib/webhook-routes.js +112 -0
- package/dist/lib/webhooks.d.ts +23 -0
- package/dist/lib/webhooks.js +123 -0
- package/dist/lib/webhooks.test.d.ts +1 -0
- package/dist/lib/webhooks.test.js +16 -0
- package/dist/lib/x402-client-options.d.ts +28 -0
- package/dist/lib/x402-client-options.js +138 -0
- package/dist/lib/x402-headers.d.ts +10 -0
- package/dist/lib/x402-headers.js +27 -0
- package/dist/lib/x402-paid.d.ts +5 -0
- package/dist/lib/x402-paid.js +252 -0
- package/dist/lib/x402-payment-replay.d.ts +22 -0
- package/dist/lib/x402-payment-replay.js +57 -0
- package/dist/lib/x402gle-host-verify.d.ts +3 -0
- package/dist/lib/x402gle-host-verify.js +27 -0
- package/dist/protocol/agent-passport.d.ts +34 -0
- package/dist/protocol/agent-passport.js +44 -0
- package/dist/protocol/compliance-v2.d.ts +21 -0
- package/dist/protocol/compliance-v2.js +19 -0
- package/dist/protocol/credit-bureau.d.ts +18 -0
- package/dist/protocol/credit-bureau.js +44 -0
- package/dist/protocol/crypto.d.ts +6 -0
- package/dist/protocol/crypto.js +41 -0
- package/dist/protocol/escrow-fsm.d.ts +33 -0
- package/dist/protocol/escrow-fsm.js +99 -0
- package/dist/protocol/fraud-engine.d.ts +28 -0
- package/dist/protocol/fraud-engine.js +77 -0
- package/dist/protocol/observability.d.ts +14 -0
- package/dist/protocol/observability.js +21 -0
- package/dist/protocol/pipeline-full-trust.d.ts +40 -0
- package/dist/protocol/pipeline-full-trust.js +96 -0
- package/dist/protocol/proof-of-execution.d.ts +36 -0
- package/dist/protocol/proof-of-execution.js +48 -0
- package/dist/protocol/reasoning-audit.d.ts +27 -0
- package/dist/protocol/reasoning-audit.js +51 -0
- package/dist/protocol/replay-guard.d.ts +28 -0
- package/dist/protocol/replay-guard.js +76 -0
- package/dist/protocol/replay-guard.test.d.ts +1 -0
- package/dist/protocol/replay-guard.test.js +10 -0
- package/dist/protocol/security-audit.d.ts +18 -0
- package/dist/protocol/security-audit.js +45 -0
- package/dist/protocol/store.d.ts +5 -0
- package/dist/protocol/store.js +59 -0
- package/dist/protocol/threat-catalog.d.ts +13 -0
- package/dist/protocol/threat-catalog.js +75 -0
- package/dist/protocol/trust-oracle.d.ts +23 -0
- package/dist/protocol/trust-oracle.js +30 -0
- package/dist/protocol/trust-score-v2.d.ts +33 -0
- package/dist/protocol/trust-score-v2.js +78 -0
- package/dist/protocol/zk-proofs.d.ts +24 -0
- package/dist/protocol/zk-proofs.js +32 -0
- package/dist/routes/a2a-agent-card.d.ts +3 -0
- package/dist/routes/a2a-agent-card.js +28 -0
- package/dist/routes/catalog.d.ts +5 -0
- package/dist/routes/catalog.js +47 -0
- package/dist/routes/register-all.d.ts +3 -0
- package/dist/routes/register-all.js +1240 -0
- package/dist/routes/schemas.d.ts +83 -0
- package/dist/routes/schemas.js +38 -0
- package/dist/routes/shared.d.ts +16 -0
- package/dist/routes/shared.js +27 -0
- package/dist/routes-protocol.d.ts +10 -0
- package/dist/routes-protocol.js +322 -0
- package/dist/routes.d.ts +2 -0
- package/dist/routes.js +2 -0
- package/dist/types.d.ts +66 -0
- package/dist/types.js +1 -0
- package/openapi.json +7940 -0
- package/package.json +124 -0
- package/public/.well-known/ai-plugin.json +12 -0
- package/public/assets/aegis-logo-blue.png +0 -0
- package/public/assets/aegis-logo-gold.png +0 -0
- package/public/assets/aegis-logo-green.png +0 -0
- package/public/assets/aegis-logo-purple.png +0 -0
- package/public/assets/aegis-logo-red.png +0 -0
- package/public/assets/aegis-logo-white.png +0 -0
- package/public/assets/aegis-logo.png +0 -0
- package/public/assets/x402-trustlayer-logo.png +0 -0
- package/public/assets/x402-trustlayer-logo.svg +5 -0
- package/public/data/agents.json +1528 -0
- package/public/index.html +198 -0
- package/public/landing.css +342 -0
- package/public/landing.js +405 -0
- package/public/llms-full.txt +582 -0
- package/public/llms.txt +132 -0
- package/public/skill.md +135 -0
- package/railway.toml +9 -0
- package/scripts/docker-entrypoint.sh +7 -0
- package/scripts/patch-facilitator-timeout.mjs +61 -0
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
const FACILITATORS = [
|
|
2
|
+
{ id: "dexter", url: "https://x402.dexter.cash" },
|
|
3
|
+
{ id: "coinbase", url: "https://api.cdp.coinbase.com/platform/v2/x402" },
|
|
4
|
+
];
|
|
5
|
+
export async function checkFacilitatorHealth(facilitator) {
|
|
6
|
+
const start = Date.now();
|
|
7
|
+
try {
|
|
8
|
+
const controller = new AbortController();
|
|
9
|
+
const timer = setTimeout(() => controller.abort(), 8_000);
|
|
10
|
+
const res = await fetch(`${facilitator.url}/supported`, {
|
|
11
|
+
signal: controller.signal,
|
|
12
|
+
headers: { accept: "application/json" },
|
|
13
|
+
});
|
|
14
|
+
clearTimeout(timer);
|
|
15
|
+
if (!res.ok) {
|
|
16
|
+
return {
|
|
17
|
+
id: facilitator.id,
|
|
18
|
+
url: facilitator.url,
|
|
19
|
+
healthy: false,
|
|
20
|
+
latencyMs: Date.now() - start,
|
|
21
|
+
supportedNetworks: [],
|
|
22
|
+
error: `HTTP ${res.status}`,
|
|
23
|
+
};
|
|
24
|
+
}
|
|
25
|
+
const body = (await res.json());
|
|
26
|
+
const networks = (body.kinds ?? [])
|
|
27
|
+
.map((k) => k.network)
|
|
28
|
+
.filter((n) => Boolean(n));
|
|
29
|
+
return {
|
|
30
|
+
id: facilitator.id,
|
|
31
|
+
url: facilitator.url,
|
|
32
|
+
healthy: true,
|
|
33
|
+
latencyMs: Date.now() - start,
|
|
34
|
+
supportedNetworks: networks,
|
|
35
|
+
error: null,
|
|
36
|
+
};
|
|
37
|
+
}
|
|
38
|
+
catch (err) {
|
|
39
|
+
return {
|
|
40
|
+
id: facilitator.id,
|
|
41
|
+
url: facilitator.url,
|
|
42
|
+
healthy: false,
|
|
43
|
+
latencyMs: null,
|
|
44
|
+
supportedNetworks: [],
|
|
45
|
+
error: err instanceof Error ? err.message : String(err),
|
|
46
|
+
};
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
export async function rankFacilitators(preferNetwork, fastCheck = false) {
|
|
50
|
+
if (fastCheck) {
|
|
51
|
+
const note = "Fast-check synthetic data — not measured";
|
|
52
|
+
return [
|
|
53
|
+
{
|
|
54
|
+
id: "dexter",
|
|
55
|
+
url: "https://x402.dexter.cash",
|
|
56
|
+
healthy: true,
|
|
57
|
+
latencyMs: 8,
|
|
58
|
+
supportedNetworks: [
|
|
59
|
+
"solana:5eykt4UsFv8P8NJdTREpY1vzqKqZKvdp",
|
|
60
|
+
"eip155:8453",
|
|
61
|
+
"eip155:137",
|
|
62
|
+
],
|
|
63
|
+
error: null,
|
|
64
|
+
synthetic: true,
|
|
65
|
+
note,
|
|
66
|
+
},
|
|
67
|
+
{
|
|
68
|
+
id: "coinbase",
|
|
69
|
+
url: "https://api.cdp.coinbase.com/platform/v2/x402",
|
|
70
|
+
healthy: true,
|
|
71
|
+
latencyMs: 12,
|
|
72
|
+
supportedNetworks: ["eip155:8453"],
|
|
73
|
+
error: null,
|
|
74
|
+
synthetic: true,
|
|
75
|
+
note,
|
|
76
|
+
},
|
|
77
|
+
];
|
|
78
|
+
}
|
|
79
|
+
const results = await Promise.all(FACILITATORS.map(checkFacilitatorHealth));
|
|
80
|
+
return results.sort((a, b) => {
|
|
81
|
+
if (a.healthy !== b.healthy)
|
|
82
|
+
return a.healthy ? -1 : 1;
|
|
83
|
+
const aNet = preferNetwork && a.supportedNetworks.some((n) => n.includes(preferNetwork)) ? 1 : 0;
|
|
84
|
+
const bNet = preferNetwork && b.supportedNetworks.some((n) => n.includes(preferNetwork)) ? 1 : 0;
|
|
85
|
+
if (aNet !== bNet)
|
|
86
|
+
return bNet - aNet;
|
|
87
|
+
return (a.latencyMs ?? 99_999) - (b.latencyMs ?? 99_999);
|
|
88
|
+
});
|
|
89
|
+
}
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
/** Exact host or proper subdomain match (avoids `evilallowed.com` matching `allowed.com`). */
|
|
2
|
+
export declare function hostMatchesPattern(host: string, pattern: string): boolean;
|
|
3
|
+
export declare function hostBlocked(host: string, blocked?: string[]): boolean;
|
|
4
|
+
export declare function hostAllowed(host: string, allowed?: string[]): boolean;
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
/** Exact host or proper subdomain match (avoids `evilallowed.com` matching `allowed.com`). */
|
|
2
|
+
export function hostMatchesPattern(host, pattern) {
|
|
3
|
+
const h = host.toLowerCase().trim();
|
|
4
|
+
const p = pattern.toLowerCase().trim();
|
|
5
|
+
if (!h || !p)
|
|
6
|
+
return false;
|
|
7
|
+
if (h === p)
|
|
8
|
+
return true;
|
|
9
|
+
return h.endsWith(`.${p}`);
|
|
10
|
+
}
|
|
11
|
+
export function hostBlocked(host, blocked) {
|
|
12
|
+
if (!blocked?.length)
|
|
13
|
+
return false;
|
|
14
|
+
return blocked.some((p) => hostMatchesPattern(host, p));
|
|
15
|
+
}
|
|
16
|
+
export function hostAllowed(host, allowed) {
|
|
17
|
+
if (!allowed?.length)
|
|
18
|
+
return true;
|
|
19
|
+
return allowed.some((p) => hostMatchesPattern(host, p));
|
|
20
|
+
}
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
import type { Request, Response, NextFunction } from "express";
|
|
2
|
+
/** Return cached paid response when Idempotency-Key matches (CDP-style safe retries). */
|
|
3
|
+
export declare function idempotencyPreCheck(req: Request, res: Response, next: NextFunction): void;
|
|
4
|
+
export declare function idempotencyCapture(req: Request, res: Response, next: NextFunction): void;
|
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
import { createHash } from "node:crypto";
|
|
2
|
+
import { hasPaymentSignatureHeader } from "./x402-headers.js";
|
|
3
|
+
import { db } from "./db.js";
|
|
4
|
+
const TTL_SEC = 24 * 60 * 60;
|
|
5
|
+
const getCache = db.prepare("SELECT status, body, body_hash, route, created_at FROM idempotency_cache WHERE cache_key = ?");
|
|
6
|
+
const insertCache = db.prepare(`
|
|
7
|
+
INSERT OR REPLACE INTO idempotency_cache (cache_key, status, body, body_hash, route, created_at)
|
|
8
|
+
VALUES (?, ?, ?, ?, ?, unixepoch())
|
|
9
|
+
`);
|
|
10
|
+
const claimInflight = db.prepare("INSERT OR IGNORE INTO idempotency_inflight (cache_key, created_at) VALUES (?, unixepoch())");
|
|
11
|
+
const releaseInflight = db.prepare("DELETE FROM idempotency_inflight WHERE cache_key = ?");
|
|
12
|
+
const pruneCache = db.prepare("DELETE FROM idempotency_cache WHERE created_at < unixepoch() - ?");
|
|
13
|
+
const pruneInflight = db.prepare("DELETE FROM idempotency_inflight WHERE created_at < unixepoch() - 3600");
|
|
14
|
+
function maybePrune() {
|
|
15
|
+
if (Math.random() < 0.02) {
|
|
16
|
+
pruneCache.run(TTL_SEC);
|
|
17
|
+
pruneInflight.run();
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
function cacheKey(route, idempotencyKey) {
|
|
21
|
+
return `${route}::${idempotencyKey}`;
|
|
22
|
+
}
|
|
23
|
+
function bodyHash(req) {
|
|
24
|
+
const raw = JSON.stringify(req.body ?? {});
|
|
25
|
+
return createHash("sha256").update(raw).digest("hex").slice(0, 16);
|
|
26
|
+
}
|
|
27
|
+
function rowToEntry(row) {
|
|
28
|
+
return {
|
|
29
|
+
status: row.status,
|
|
30
|
+
body: JSON.parse(row.body),
|
|
31
|
+
bodyHash: row.body_hash,
|
|
32
|
+
route: row.route,
|
|
33
|
+
createdAt: row.created_at * 1000,
|
|
34
|
+
};
|
|
35
|
+
}
|
|
36
|
+
function tryClaimInFlight(cacheKeyStr) {
|
|
37
|
+
const r = claimInflight.run(cacheKeyStr);
|
|
38
|
+
return r.changes > 0;
|
|
39
|
+
}
|
|
40
|
+
function releaseClaim(cacheKeyStr) {
|
|
41
|
+
releaseInflight.run(cacheKeyStr);
|
|
42
|
+
}
|
|
43
|
+
function loadHit(keyStr) {
|
|
44
|
+
maybePrune();
|
|
45
|
+
const row = getCache.get(keyStr);
|
|
46
|
+
if (!row)
|
|
47
|
+
return null;
|
|
48
|
+
const entry = rowToEntry(row);
|
|
49
|
+
if (Date.now() - entry.createdAt >= TTL_SEC * 1000)
|
|
50
|
+
return null;
|
|
51
|
+
return entry;
|
|
52
|
+
}
|
|
53
|
+
/** Return cached paid response when Idempotency-Key matches (CDP-style safe retries). */
|
|
54
|
+
export function idempotencyPreCheck(req, res, next) {
|
|
55
|
+
const key = String(req.headers["idempotency-key"] ?? "").trim();
|
|
56
|
+
if (!key || key.length > 128)
|
|
57
|
+
return void next();
|
|
58
|
+
if (!hasPaymentSignatureHeader(req))
|
|
59
|
+
return void next();
|
|
60
|
+
const route = req.path;
|
|
61
|
+
const keyStr = cacheKey(route, key);
|
|
62
|
+
const hit = loadHit(keyStr);
|
|
63
|
+
if (hit) {
|
|
64
|
+
if (hit.bodyHash !== bodyHash(req)) {
|
|
65
|
+
res.status(409).json({
|
|
66
|
+
error: "Idempotency-Key reused with different request body",
|
|
67
|
+
idempotencyKey: key,
|
|
68
|
+
});
|
|
69
|
+
return;
|
|
70
|
+
}
|
|
71
|
+
res.setHeader("Idempotency-Replayed", "true");
|
|
72
|
+
res.status(hit.status).json(hit.body);
|
|
73
|
+
return;
|
|
74
|
+
}
|
|
75
|
+
if (!tryClaimInFlight(keyStr)) {
|
|
76
|
+
const retry = loadHit(keyStr);
|
|
77
|
+
if (retry) {
|
|
78
|
+
if (retry.bodyHash !== bodyHash(req)) {
|
|
79
|
+
res.status(409).json({
|
|
80
|
+
error: "Idempotency-Key reused with different request body",
|
|
81
|
+
idempotencyKey: key,
|
|
82
|
+
});
|
|
83
|
+
return;
|
|
84
|
+
}
|
|
85
|
+
res.setHeader("Idempotency-Replayed", "true");
|
|
86
|
+
res.status(retry.status).json(retry.body);
|
|
87
|
+
return;
|
|
88
|
+
}
|
|
89
|
+
res.status(409).json({
|
|
90
|
+
error: "Idempotency-Key already in progress",
|
|
91
|
+
idempotencyKey: key,
|
|
92
|
+
});
|
|
93
|
+
return;
|
|
94
|
+
}
|
|
95
|
+
req.idempotencyClaimKey = keyStr;
|
|
96
|
+
next();
|
|
97
|
+
}
|
|
98
|
+
export function idempotencyCapture(req, res, next) {
|
|
99
|
+
const key = String(req.headers["idempotency-key"] ?? "").trim();
|
|
100
|
+
if (!key || key.length > 128)
|
|
101
|
+
return void next();
|
|
102
|
+
const claimKeyStr = req.idempotencyClaimKey;
|
|
103
|
+
const routeKey = cacheKey(req.path, key);
|
|
104
|
+
const origJson = res.json.bind(res);
|
|
105
|
+
res.json = ((body) => {
|
|
106
|
+
if (res.statusCode >= 200 && res.statusCode < 300 && hasPaymentSignatureHeader(req)) {
|
|
107
|
+
insertCache.run(routeKey, res.statusCode, JSON.stringify(body ?? null), bodyHash(req), req.path);
|
|
108
|
+
}
|
|
109
|
+
if (claimKeyStr)
|
|
110
|
+
releaseClaim(claimKeyStr);
|
|
111
|
+
return origJson(body);
|
|
112
|
+
});
|
|
113
|
+
const origEnd = res.end.bind(res);
|
|
114
|
+
res.end = ((...args) => {
|
|
115
|
+
if (claimKeyStr)
|
|
116
|
+
releaseClaim(claimKeyStr);
|
|
117
|
+
return origEnd(...args);
|
|
118
|
+
});
|
|
119
|
+
next();
|
|
120
|
+
}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import { db } from "./db.js";
|
|
2
|
+
function dayKey(agentId) {
|
|
3
|
+
const day = new Date().toISOString().slice(0, 10);
|
|
4
|
+
return `${agentId}:${day}`;
|
|
5
|
+
}
|
|
6
|
+
const sumDay = db.prepare(`SELECT COALESCE(SUM(amount_usdc), 0) AS total FROM spend_ledger WHERE agent_id = ? AND day_key = ?`);
|
|
7
|
+
const insertSpend = db.prepare(`INSERT INTO spend_ledger (agent_id, amount_usdc, day_key) VALUES (?, ?, ?)`);
|
|
8
|
+
export async function getSpentToday(agentId) {
|
|
9
|
+
const row = sumDay.get(agentId, dayKey(agentId));
|
|
10
|
+
return row?.total ?? 0;
|
|
11
|
+
}
|
|
12
|
+
export async function recordSpend(agentId, amountUsdc) {
|
|
13
|
+
const dk = dayKey(agentId);
|
|
14
|
+
insertSpend.run(agentId, amountUsdc, dk);
|
|
15
|
+
const row = sumDay.get(agentId, dk);
|
|
16
|
+
return row.total;
|
|
17
|
+
}
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
export declare const logger: {
|
|
2
|
+
info(fields: Record<string, unknown>, msg: string): void;
|
|
3
|
+
warn(fields: Record<string, unknown>, msg: string): void;
|
|
4
|
+
error(fields: Record<string, unknown>, msg: string): void;
|
|
5
|
+
debug(fields: Record<string, unknown>, msg: string): void;
|
|
6
|
+
};
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
function emit(level, fields, msg) {
|
|
2
|
+
const line = JSON.stringify({ level, msg, time: new Date().toISOString(), ...fields });
|
|
3
|
+
if (level === "error")
|
|
4
|
+
console.error(line);
|
|
5
|
+
else if (level === "warn")
|
|
6
|
+
console.warn(line);
|
|
7
|
+
else
|
|
8
|
+
console.log(line);
|
|
9
|
+
}
|
|
10
|
+
export const logger = {
|
|
11
|
+
info(fields, msg) {
|
|
12
|
+
emit("info", fields, msg);
|
|
13
|
+
},
|
|
14
|
+
warn(fields, msg) {
|
|
15
|
+
emit("warn", fields, msg);
|
|
16
|
+
},
|
|
17
|
+
error(fields, msg) {
|
|
18
|
+
emit("error", fields, msg);
|
|
19
|
+
},
|
|
20
|
+
debug(fields, msg) {
|
|
21
|
+
if (process.env.LOG_LEVEL === "debug")
|
|
22
|
+
emit("debug", fields, msg);
|
|
23
|
+
},
|
|
24
|
+
};
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import type { MandateRecord, MandateScope } from "./mandate.js";
|
|
2
|
+
export type MandateVC = {
|
|
3
|
+
"@context": string[];
|
|
4
|
+
type: ["VerifiableCredential", "AgentPaymentMandate"];
|
|
5
|
+
issuer: string;
|
|
6
|
+
validFrom: string;
|
|
7
|
+
validUntil: string;
|
|
8
|
+
credentialSubject: {
|
|
9
|
+
id: string;
|
|
10
|
+
agentId: string;
|
|
11
|
+
scope: MandateScope;
|
|
12
|
+
};
|
|
13
|
+
proof: {
|
|
14
|
+
type: "HmacProof2026";
|
|
15
|
+
created: string;
|
|
16
|
+
proofValue: string;
|
|
17
|
+
verificationMethod: string;
|
|
18
|
+
};
|
|
19
|
+
};
|
|
20
|
+
export declare function mandateToVC(record: MandateRecord, principalDid?: string): MandateVC;
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import { config } from "../config.js";
|
|
2
|
+
export function mandateToVC(record, principalDid) {
|
|
3
|
+
const base = config.publicBaseUrl.replace(/\/$/, "");
|
|
4
|
+
return {
|
|
5
|
+
"@context": [
|
|
6
|
+
"https://www.w3.org/2018/credentials/v1",
|
|
7
|
+
"https://x402trustlayer.xyz/contexts/agent-payment-mandate/v1",
|
|
8
|
+
],
|
|
9
|
+
type: ["VerifiableCredential", "AgentPaymentMandate"],
|
|
10
|
+
issuer: "did:web:x402trustlayer.xyz",
|
|
11
|
+
validFrom: record.issuedAt,
|
|
12
|
+
validUntil: record.scope.expiresAt,
|
|
13
|
+
credentialSubject: {
|
|
14
|
+
id: principalDid ?? `did:web:agent.id#${record.agentId}`,
|
|
15
|
+
agentId: record.agentId,
|
|
16
|
+
scope: record.scope,
|
|
17
|
+
},
|
|
18
|
+
proof: {
|
|
19
|
+
type: "HmacProof2026",
|
|
20
|
+
created: record.issuedAt,
|
|
21
|
+
proofValue: record.signature,
|
|
22
|
+
verificationMethod: `${base}/keys/hmac-signing-key`,
|
|
23
|
+
},
|
|
24
|
+
};
|
|
25
|
+
}
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
export type MandateScope = {
|
|
2
|
+
maxPerTxUsdc: number;
|
|
3
|
+
dailyCapUsdc: number;
|
|
4
|
+
allowedMerchants: string[];
|
|
5
|
+
allowedCategories: string[];
|
|
6
|
+
allowedRails: string[];
|
|
7
|
+
expiresAt: string;
|
|
8
|
+
};
|
|
9
|
+
export type MandateRecord = {
|
|
10
|
+
mandateId: string;
|
|
11
|
+
issuedAt: string;
|
|
12
|
+
principal: string;
|
|
13
|
+
agentId: string;
|
|
14
|
+
intent: string;
|
|
15
|
+
intentHash: string;
|
|
16
|
+
scope: MandateScope;
|
|
17
|
+
suiteVersion: string;
|
|
18
|
+
signature: string;
|
|
19
|
+
};
|
|
20
|
+
/** Stable mandate id used by x402gle / Dexter verifier probes (see verify-examples.ts). */
|
|
21
|
+
export declare const VERIFIER_PROBE_MANDATE_ID = "mdt_verifier_probe_example";
|
|
22
|
+
export declare function hashIntent(intent: string): string;
|
|
23
|
+
export declare function issueMandate(input: {
|
|
24
|
+
principal: string;
|
|
25
|
+
agentId: string;
|
|
26
|
+
intent: string;
|
|
27
|
+
scope: MandateScope;
|
|
28
|
+
}): Promise<MandateRecord>;
|
|
29
|
+
/** Seed a signed probe mandate so /api/mandate/verify passes x402gle audits. */
|
|
30
|
+
export declare function ensureVerifierProbeMandate(): Promise<MandateRecord>;
|
|
31
|
+
export type MandateCheck = {
|
|
32
|
+
amountUsdc: number;
|
|
33
|
+
merchant?: string;
|
|
34
|
+
category?: string;
|
|
35
|
+
rail?: string;
|
|
36
|
+
};
|
|
37
|
+
export type MandateVerifyResult = {
|
|
38
|
+
valid: boolean;
|
|
39
|
+
withinScope: boolean;
|
|
40
|
+
reason: string;
|
|
41
|
+
record: MandateRecord | null;
|
|
42
|
+
violations: string[];
|
|
43
|
+
};
|
|
44
|
+
export declare function verifyMandate(mandateId: string, proposed?: MandateCheck): Promise<MandateVerifyResult>;
|
|
@@ -0,0 +1,190 @@
|
|
|
1
|
+
import { createHash, createHmac, randomBytes, timingSafeEqual } from "node:crypto";
|
|
2
|
+
import { readFile } from "node:fs/promises";
|
|
3
|
+
import path from "node:path";
|
|
4
|
+
import { fileURLToPath } from "node:url";
|
|
5
|
+
import { config } from "../config.js";
|
|
6
|
+
import { db } from "./db.js";
|
|
7
|
+
import { SUITE_VERSION } from "./version.js";
|
|
8
|
+
const root = path.dirname(fileURLToPath(import.meta.url));
|
|
9
|
+
const legacyStorePath = path.join(root, "..", "..", "data", "mandates.json");
|
|
10
|
+
/** Stable mandate id used by x402gle / Dexter verifier probes (see verify-examples.ts). */
|
|
11
|
+
export const VERIFIER_PROBE_MANDATE_ID = "mdt_verifier_probe_example";
|
|
12
|
+
const selectById = db.prepare("SELECT * FROM mandates WHERE mandate_id = ?");
|
|
13
|
+
const selectAll = db.prepare("SELECT * FROM mandates ORDER BY issued_at DESC");
|
|
14
|
+
const upsertMandate = db.prepare(`
|
|
15
|
+
INSERT OR REPLACE INTO mandates (
|
|
16
|
+
mandate_id, principal, agent_id, intent, intent_hash, scope, signature,
|
|
17
|
+
issued_at, expires_at, suite_version
|
|
18
|
+
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
|
19
|
+
`);
|
|
20
|
+
function rowToRecord(row) {
|
|
21
|
+
return {
|
|
22
|
+
mandateId: row.mandate_id,
|
|
23
|
+
issuedAt: row.issued_at,
|
|
24
|
+
principal: row.principal,
|
|
25
|
+
agentId: row.agent_id,
|
|
26
|
+
intent: row.intent,
|
|
27
|
+
intentHash: row.intent_hash,
|
|
28
|
+
scope: JSON.parse(row.scope),
|
|
29
|
+
suiteVersion: row.suite_version,
|
|
30
|
+
signature: row.signature,
|
|
31
|
+
};
|
|
32
|
+
}
|
|
33
|
+
function expiresAtUnix(scope) {
|
|
34
|
+
const t = new Date(scope.expiresAt).getTime();
|
|
35
|
+
return Number.isFinite(t) ? Math.floor(t / 1000) : null;
|
|
36
|
+
}
|
|
37
|
+
async function migrateLegacyJsonOnce() {
|
|
38
|
+
const count = db.prepare("SELECT COUNT(*) AS c FROM mandates").get().c;
|
|
39
|
+
if (count > 0)
|
|
40
|
+
return;
|
|
41
|
+
try {
|
|
42
|
+
const raw = await readFile(legacyStorePath, "utf8");
|
|
43
|
+
const parsed = JSON.parse(raw);
|
|
44
|
+
if (!Array.isArray(parsed))
|
|
45
|
+
return;
|
|
46
|
+
for (const r of parsed) {
|
|
47
|
+
upsertMandate.run(r.mandateId, r.principal, r.agentId, r.intent, r.intentHash, JSON.stringify(r.scope), r.signature, r.issuedAt, expiresAtUnix(r.scope), r.suiteVersion ?? SUITE_VERSION);
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
catch {
|
|
51
|
+
/* no legacy file */
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
async function loadStore() {
|
|
55
|
+
await migrateLegacyJsonOnce();
|
|
56
|
+
return selectAll.all().map(rowToRecord);
|
|
57
|
+
}
|
|
58
|
+
async function saveRecord(record) {
|
|
59
|
+
upsertMandate.run(record.mandateId, record.principal, record.agentId, record.intent, record.intentHash, JSON.stringify(record.scope), record.signature, record.issuedAt, expiresAtUnix(record.scope), record.suiteVersion);
|
|
60
|
+
}
|
|
61
|
+
export function hashIntent(intent) {
|
|
62
|
+
return createHash("sha256").update(intent).digest("hex");
|
|
63
|
+
}
|
|
64
|
+
function canonical(record) {
|
|
65
|
+
return JSON.stringify({
|
|
66
|
+
mandateId: record.mandateId,
|
|
67
|
+
principal: record.principal,
|
|
68
|
+
agentId: record.agentId,
|
|
69
|
+
intentHash: record.intentHash,
|
|
70
|
+
scope: record.scope,
|
|
71
|
+
});
|
|
72
|
+
}
|
|
73
|
+
function sign(payload) {
|
|
74
|
+
return createHmac("sha256", config.attestationHmacSecret).update(payload).digest("hex");
|
|
75
|
+
}
|
|
76
|
+
function signaturesEqual(a, b) {
|
|
77
|
+
try {
|
|
78
|
+
const ba = Buffer.from(a, "hex");
|
|
79
|
+
const bb = Buffer.from(b, "hex");
|
|
80
|
+
if (ba.length !== bb.length)
|
|
81
|
+
return false;
|
|
82
|
+
return timingSafeEqual(ba, bb);
|
|
83
|
+
}
|
|
84
|
+
catch {
|
|
85
|
+
return false;
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
export async function issueMandate(input) {
|
|
89
|
+
const mandateId = `mdt_${randomBytes(8).toString("hex")}`;
|
|
90
|
+
const issuedAt = new Date().toISOString();
|
|
91
|
+
const intentHash = hashIntent(input.intent);
|
|
92
|
+
const base = {
|
|
93
|
+
mandateId,
|
|
94
|
+
issuedAt,
|
|
95
|
+
principal: input.principal,
|
|
96
|
+
agentId: input.agentId,
|
|
97
|
+
intent: input.intent,
|
|
98
|
+
intentHash,
|
|
99
|
+
scope: input.scope,
|
|
100
|
+
};
|
|
101
|
+
const signature = sign(canonical(base));
|
|
102
|
+
const record = { ...base, suiteVersion: SUITE_VERSION, signature };
|
|
103
|
+
await saveRecord(record);
|
|
104
|
+
return record;
|
|
105
|
+
}
|
|
106
|
+
/** Seed a signed probe mandate so /api/mandate/verify passes x402gle audits. */
|
|
107
|
+
export async function ensureVerifierProbeMandate() {
|
|
108
|
+
const existing = selectById.get(VERIFIER_PROBE_MANDATE_ID);
|
|
109
|
+
if (existing)
|
|
110
|
+
return rowToRecord(existing);
|
|
111
|
+
const intent = "Buy ETH/USD oracle data for a trading bot, under $1 per call, daily $10 cap";
|
|
112
|
+
const scope = {
|
|
113
|
+
maxPerTxUsdc: 0.5,
|
|
114
|
+
dailyCapUsdc: 10,
|
|
115
|
+
allowedMerchants: ["myceliasignal.com", "dexter.cash", "api.myceliasignal.com"],
|
|
116
|
+
allowedCategories: ["market-data", "oracle"],
|
|
117
|
+
allowedRails: ["base-x402", "solana-x402", "visa-cli"],
|
|
118
|
+
expiresAt: new Date(Date.now() + 365 * 24 * 60 * 60_000).toISOString(),
|
|
119
|
+
};
|
|
120
|
+
const issuedAt = new Date().toISOString();
|
|
121
|
+
const intentHash = hashIntent(intent);
|
|
122
|
+
const base = {
|
|
123
|
+
mandateId: VERIFIER_PROBE_MANDATE_ID,
|
|
124
|
+
issuedAt,
|
|
125
|
+
principal: "cardholder:dexter-verifier",
|
|
126
|
+
agentId: "dexter-verifier-probe",
|
|
127
|
+
intent,
|
|
128
|
+
intentHash,
|
|
129
|
+
scope,
|
|
130
|
+
};
|
|
131
|
+
const signature = sign(canonical(base));
|
|
132
|
+
const record = { ...base, suiteVersion: SUITE_VERSION, signature };
|
|
133
|
+
await saveRecord(record);
|
|
134
|
+
return record;
|
|
135
|
+
}
|
|
136
|
+
export async function verifyMandate(mandateId, proposed) {
|
|
137
|
+
if (mandateId === VERIFIER_PROBE_MANDATE_ID) {
|
|
138
|
+
await ensureVerifierProbeMandate();
|
|
139
|
+
}
|
|
140
|
+
await migrateLegacyJsonOnce();
|
|
141
|
+
const row = selectById.get(mandateId);
|
|
142
|
+
const record = row ? rowToRecord(row) : null;
|
|
143
|
+
if (!record) {
|
|
144
|
+
return { valid: false, withinScope: false, reason: "Mandate not found", record: null, violations: ["not_found"] };
|
|
145
|
+
}
|
|
146
|
+
const expected = sign(canonical({
|
|
147
|
+
mandateId: record.mandateId,
|
|
148
|
+
issuedAt: record.issuedAt,
|
|
149
|
+
principal: record.principal,
|
|
150
|
+
agentId: record.agentId,
|
|
151
|
+
intent: record.intent,
|
|
152
|
+
intentHash: record.intentHash,
|
|
153
|
+
scope: record.scope,
|
|
154
|
+
}));
|
|
155
|
+
if (!signaturesEqual(expected, record.signature)) {
|
|
156
|
+
return { valid: false, withinScope: false, reason: "Signature mismatch (tampered)", record, violations: ["signature"] };
|
|
157
|
+
}
|
|
158
|
+
if (new Date(record.scope.expiresAt) < new Date()) {
|
|
159
|
+
return { valid: true, withinScope: false, reason: "Mandate expired", record, violations: ["expired"] };
|
|
160
|
+
}
|
|
161
|
+
const violations = [];
|
|
162
|
+
if (proposed) {
|
|
163
|
+
if (proposed.amountUsdc > record.scope.maxPerTxUsdc) {
|
|
164
|
+
violations.push(`amount ${proposed.amountUsdc} exceeds maxPerTxUsdc ${record.scope.maxPerTxUsdc}`);
|
|
165
|
+
}
|
|
166
|
+
if (proposed.merchant &&
|
|
167
|
+
record.scope.allowedMerchants.length > 0 &&
|
|
168
|
+
!record.scope.allowedMerchants.some((m) => proposed.merchant.toLowerCase().includes(m.toLowerCase()))) {
|
|
169
|
+
violations.push(`merchant ${proposed.merchant} not in allowedMerchants`);
|
|
170
|
+
}
|
|
171
|
+
if (proposed.category &&
|
|
172
|
+
record.scope.allowedCategories.length > 0 &&
|
|
173
|
+
!record.scope.allowedCategories.includes(proposed.category)) {
|
|
174
|
+
violations.push(`category ${proposed.category} not in allowedCategories`);
|
|
175
|
+
}
|
|
176
|
+
if (proposed.rail &&
|
|
177
|
+
record.scope.allowedRails.length > 0 &&
|
|
178
|
+
!record.scope.allowedRails.includes(proposed.rail)) {
|
|
179
|
+
violations.push(`rail ${proposed.rail} not in allowedRails`);
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
const withinScope = violations.length === 0;
|
|
183
|
+
return {
|
|
184
|
+
valid: true,
|
|
185
|
+
withinScope,
|
|
186
|
+
reason: withinScope ? "Valid mandate, proposed payment within scope" : "Valid signature but proposed payment violates scope",
|
|
187
|
+
record,
|
|
188
|
+
violations,
|
|
189
|
+
};
|
|
190
|
+
}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import type { MarketplaceResource } from "../types.js";
|
|
2
|
+
export declare function searchMarketplace(query: string, options?: {
|
|
3
|
+
limit?: number;
|
|
4
|
+
maxPriceUsdc?: number;
|
|
5
|
+
verified?: boolean;
|
|
6
|
+
}): Promise<MarketplaceResource[]>;
|
|
7
|
+
export declare function pickBestResource(resources: MarketplaceResource[], preferNetwork?: string, query?: string): MarketplaceResource | null;
|