@agent-score/commerce 1.0.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/LICENSE +21 -0
- package/README.md +306 -0
- package/dist/_response-DmziuJz6.d.mts +137 -0
- package/dist/_response-rbK0zM7y.d.ts +137 -0
- package/dist/api/index.d.mts +1 -0
- package/dist/api/index.d.ts +1 -0
- package/dist/api/index.js +37 -0
- package/dist/api/index.js.map +1 -0
- package/dist/api/index.mjs +14 -0
- package/dist/api/index.mjs.map +1 -0
- package/dist/challenge/index.d.mts +523 -0
- package/dist/challenge/index.d.ts +523 -0
- package/dist/challenge/index.js +354 -0
- package/dist/challenge/index.js.map +1 -0
- package/dist/challenge/index.mjs +318 -0
- package/dist/challenge/index.mjs.map +1 -0
- package/dist/core.d.mts +252 -0
- package/dist/core.d.ts +252 -0
- package/dist/core.js +500 -0
- package/dist/core.js.map +1 -0
- package/dist/core.mjs +472 -0
- package/dist/core.mjs.map +1 -0
- package/dist/discovery/index.d.mts +382 -0
- package/dist/discovery/index.d.ts +382 -0
- package/dist/discovery/index.js +675 -0
- package/dist/discovery/index.js.map +1 -0
- package/dist/discovery/index.mjs +630 -0
- package/dist/discovery/index.mjs.map +1 -0
- package/dist/identity/express.d.mts +44 -0
- package/dist/identity/express.d.ts +44 -0
- package/dist/identity/express.js +777 -0
- package/dist/identity/express.js.map +1 -0
- package/dist/identity/express.mjs +738 -0
- package/dist/identity/express.mjs.map +1 -0
- package/dist/identity/fastify.d.mts +63 -0
- package/dist/identity/fastify.d.ts +63 -0
- package/dist/identity/fastify.js +780 -0
- package/dist/identity/fastify.js.map +1 -0
- package/dist/identity/fastify.mjs +741 -0
- package/dist/identity/fastify.mjs.map +1 -0
- package/dist/identity/hono.d.mts +83 -0
- package/dist/identity/hono.d.ts +83 -0
- package/dist/identity/hono.js +779 -0
- package/dist/identity/hono.js.map +1 -0
- package/dist/identity/hono.mjs +740 -0
- package/dist/identity/hono.mjs.map +1 -0
- package/dist/identity/nextjs.d.mts +62 -0
- package/dist/identity/nextjs.d.ts +62 -0
- package/dist/identity/nextjs.js +784 -0
- package/dist/identity/nextjs.js.map +1 -0
- package/dist/identity/nextjs.mjs +747 -0
- package/dist/identity/nextjs.mjs.map +1 -0
- package/dist/identity/policy.d.mts +115 -0
- package/dist/identity/policy.d.ts +115 -0
- package/dist/identity/policy.js +81 -0
- package/dist/identity/policy.js.map +1 -0
- package/dist/identity/policy.mjs +53 -0
- package/dist/identity/policy.mjs.map +1 -0
- package/dist/identity/web.d.mts +82 -0
- package/dist/identity/web.d.ts +82 -0
- package/dist/identity/web.js +775 -0
- package/dist/identity/web.js.map +1 -0
- package/dist/identity/web.mjs +738 -0
- package/dist/identity/web.mjs.map +1 -0
- package/dist/index.d.mts +252 -0
- package/dist/index.d.ts +252 -0
- package/dist/index.js +432 -0
- package/dist/index.js.map +1 -0
- package/dist/index.mjs +388 -0
- package/dist/index.mjs.map +1 -0
- package/dist/payment/index.d.mts +716 -0
- package/dist/payment/index.d.ts +716 -0
- package/dist/payment/index.js +691 -0
- package/dist/payment/index.js.map +1 -0
- package/dist/payment/index.mjs +639 -0
- package/dist/payment/index.mjs.map +1 -0
- package/dist/signer-Cvdwn6Cs.d.mts +48 -0
- package/dist/signer-Cvdwn6Cs.d.ts +48 -0
- package/dist/stripe-multichain/index.d.mts +221 -0
- package/dist/stripe-multichain/index.d.ts +221 -0
- package/dist/stripe-multichain/index.js +243 -0
- package/dist/stripe-multichain/index.js.map +1 -0
- package/dist/stripe-multichain/index.mjs +199 -0
- package/dist/stripe-multichain/index.mjs.map +1 -0
- package/dist/wwwauthenticate-CU1eNvMQ.d.mts +37 -0
- package/dist/wwwauthenticate-CU1eNvMQ.d.ts +37 -0
- package/package.json +172 -0
|
@@ -0,0 +1,738 @@
|
|
|
1
|
+
// src/_denial.ts
|
|
2
|
+
var FIXABLE_DENIAL_REASONS = /* @__PURE__ */ new Set([
|
|
3
|
+
"kyc_required",
|
|
4
|
+
"kyc_pending",
|
|
5
|
+
"kyc_failed",
|
|
6
|
+
"jurisdiction_restricted"
|
|
7
|
+
]);
|
|
8
|
+
function isFixableDenial(reasons) {
|
|
9
|
+
if (!reasons || reasons.length === 0) return true;
|
|
10
|
+
return reasons.every((r) => FIXABLE_DENIAL_REASONS.has(r));
|
|
11
|
+
}
|
|
12
|
+
function denialReasonStatus(reason) {
|
|
13
|
+
if (reason.code === "token_expired" || reason.code === "invalid_credential") return 401;
|
|
14
|
+
if (reason.code === "api_error") return 503;
|
|
15
|
+
return 403;
|
|
16
|
+
}
|
|
17
|
+
function buildSignerMismatchBody(input) {
|
|
18
|
+
const { result } = input;
|
|
19
|
+
if (result.kind === "pass" || result.kind === "api_error") return null;
|
|
20
|
+
const learnMoreUrl = input.learnMoreUrl ?? "https://docs.agentscore.sh/guides/agent-identity";
|
|
21
|
+
if (result.kind === "wallet_signer_mismatch") {
|
|
22
|
+
const linkedWallets = result.linkedWallets ?? [];
|
|
23
|
+
const userMessage = input.userMessage ?? (linkedWallets.length > 0 ? `Sign the payment with one of the wallets linked to this operator: ${linkedWallets.join(", ")}. Then retry.` : "Sign the payment with the same wallet you claimed via X-Wallet-Address, or switch to X-Operator-Token for rail-independent identity.");
|
|
24
|
+
return {
|
|
25
|
+
error: {
|
|
26
|
+
code: "wallet_signer_mismatch",
|
|
27
|
+
message: "Payment signer does not match the wallet claimed via X-Wallet-Address. The signer and the claimed wallet must both resolve to the same AgentScore operator."
|
|
28
|
+
},
|
|
29
|
+
claimed_operator: result.claimedOperator,
|
|
30
|
+
actual_signer_operator: result.actualSignerOperator ?? null,
|
|
31
|
+
expected_signer: result.expectedSigner,
|
|
32
|
+
actual_signer: result.actualSigner,
|
|
33
|
+
linked_wallets: linkedWallets,
|
|
34
|
+
next_steps: {
|
|
35
|
+
action: "regenerate_payment_from_linked_wallet",
|
|
36
|
+
user_message: userMessage,
|
|
37
|
+
learn_more_url: learnMoreUrl
|
|
38
|
+
}
|
|
39
|
+
};
|
|
40
|
+
}
|
|
41
|
+
return {
|
|
42
|
+
error: {
|
|
43
|
+
code: "wallet_auth_requires_wallet_signing",
|
|
44
|
+
message: "Wallet-auth requires a payment rail that carries a wallet signature (Tempo MPP, x402). Stripe SPT and card rails have no wallet signer; switch to X-Operator-Token to use those."
|
|
45
|
+
},
|
|
46
|
+
next_steps: {
|
|
47
|
+
action: "switch_to_operator_token",
|
|
48
|
+
user_message: input.userMessage ?? "Drop the X-Wallet-Address header and retry with X-Operator-Token (works on every payment rail).",
|
|
49
|
+
learn_more_url: learnMoreUrl
|
|
50
|
+
}
|
|
51
|
+
};
|
|
52
|
+
}
|
|
53
|
+
function buildContactSupportNextSteps(supportEmail, message) {
|
|
54
|
+
return {
|
|
55
|
+
action: "contact_support",
|
|
56
|
+
support_email: supportEmail,
|
|
57
|
+
user_message: message ?? `If you believe this denial is in error, contact support at ${supportEmail} with your order details.`
|
|
58
|
+
};
|
|
59
|
+
}
|
|
60
|
+
function verificationAgentInstructions(input = {}) {
|
|
61
|
+
const baseSteps = [
|
|
62
|
+
"Present the verify_url directly to the user \u2014 it is a complete, ready-to-open URL with the session token already embedded (e.g. https://agentscore.sh/verify?session=sess_...). Do NOT modify or construct the URL yourself.",
|
|
63
|
+
`Immediately begin polling poll_url every ${input.pollIntervalSeconds ?? 5} seconds with header X-Poll-Secret set to poll_secret. The user will complete verification in their browser while you poll in the background.`,
|
|
64
|
+
"The user visits the URL, signs in, completes identity verification (photo ID + selfie via Stripe Identity), and closes the tab. They do NOT need to copy or paste anything back to you.",
|
|
65
|
+
'When your poll returns status "verified", extract operator_token from the response. This is a one-time value \u2014 save it immediately. Subsequent polls return status "consumed" without the token.',
|
|
66
|
+
input.retryStep ?? "Retry the original merchant request with header X-Operator-Token set to the operator_token value."
|
|
67
|
+
];
|
|
68
|
+
return {
|
|
69
|
+
action: "poll_for_credential",
|
|
70
|
+
user_action: input.userAction ?? "The user must visit verify_url to complete identity verification before this request can proceed",
|
|
71
|
+
steps: input.extraSteps ? [...baseSteps, ...input.extraSteps] : baseSteps,
|
|
72
|
+
poll_interval_seconds: input.pollIntervalSeconds ?? 5,
|
|
73
|
+
poll_secret_header: "X-Poll-Secret",
|
|
74
|
+
retry_token_header: "X-Operator-Token",
|
|
75
|
+
timeout_seconds: input.timeoutSeconds ?? 3600,
|
|
76
|
+
...input.orderTtl ? { order_ttl: input.orderTtl } : {},
|
|
77
|
+
...input.extra ?? {}
|
|
78
|
+
};
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
// src/_response.ts
|
|
82
|
+
var DEFAULT_MESSAGES = {
|
|
83
|
+
missing_identity: "No identity provided. Send X-Wallet-Address (wallet) or X-Operator-Token (credential).",
|
|
84
|
+
identity_verification_required: "Identity verification is required to access this resource. Visit verify_url to complete KYC.",
|
|
85
|
+
wallet_not_trusted: "The wallet does not meet the merchant compliance policy.",
|
|
86
|
+
api_error: "AgentScore is unreachable. This is transient \u2014 retry in a few seconds.",
|
|
87
|
+
payment_required: "AgentScore tier does not support assess. Contact support.",
|
|
88
|
+
wallet_signer_mismatch: "Payment signer does not match the wallet claimed via X-Wallet-Address. The signer and the claimed wallet must both resolve to the same AgentScore operator.",
|
|
89
|
+
wallet_auth_requires_wallet_signing: "X-Wallet-Address was sent with a rail that has no wallet signature (Stripe SPT / card). Switch to X-Operator-Token, or use a wallet-signing rail (Tempo MPP, x402).",
|
|
90
|
+
token_expired: "The operator token is expired or revoked. A fresh verification session has been minted \u2014 visit verify_url to mint a new token.",
|
|
91
|
+
invalid_credential: "The operator token is not recognized. Switch to a different stored token, or drop the header to bootstrap a fresh session."
|
|
92
|
+
};
|
|
93
|
+
var RESERVED_FIELDS = /* @__PURE__ */ new Set([
|
|
94
|
+
"error",
|
|
95
|
+
"decision",
|
|
96
|
+
"reasons",
|
|
97
|
+
"verify_url",
|
|
98
|
+
"session_id",
|
|
99
|
+
"poll_secret",
|
|
100
|
+
"poll_url",
|
|
101
|
+
"agent_instructions",
|
|
102
|
+
"agent_memory",
|
|
103
|
+
"claimed_operator",
|
|
104
|
+
"actual_signer_operator",
|
|
105
|
+
"expected_signer",
|
|
106
|
+
"actual_signer",
|
|
107
|
+
"linked_wallets"
|
|
108
|
+
]);
|
|
109
|
+
function denialReasonToBody(reason) {
|
|
110
|
+
const message = reason.message ?? DEFAULT_MESSAGES[reason.code];
|
|
111
|
+
const body = { error: { code: reason.code, message } };
|
|
112
|
+
if (reason.decision) body.decision = reason.decision;
|
|
113
|
+
if (reason.reasons) body.reasons = reason.reasons;
|
|
114
|
+
if (reason.verify_url) body.verify_url = reason.verify_url;
|
|
115
|
+
if (reason.session_id) body.session_id = reason.session_id;
|
|
116
|
+
if (reason.poll_secret) body.poll_secret = reason.poll_secret;
|
|
117
|
+
if (reason.poll_url) body.poll_url = reason.poll_url;
|
|
118
|
+
if (reason.agent_instructions) body.agent_instructions = reason.agent_instructions;
|
|
119
|
+
if (reason.agent_memory) body.agent_memory = reason.agent_memory;
|
|
120
|
+
if (reason.claimed_operator) body.claimed_operator = reason.claimed_operator;
|
|
121
|
+
if (reason.code === "wallet_signer_mismatch") body.actual_signer_operator = reason.actual_signer_operator ?? null;
|
|
122
|
+
if (reason.expected_signer) body.expected_signer = reason.expected_signer;
|
|
123
|
+
if (reason.actual_signer) body.actual_signer = reason.actual_signer;
|
|
124
|
+
if (reason.linked_wallets && reason.linked_wallets.length > 0) body.linked_wallets = reason.linked_wallets;
|
|
125
|
+
if (reason.code === "api_error" && !(reason.extra && reason.extra.next_steps)) {
|
|
126
|
+
body.next_steps = { action: "retry", retry_after_seconds: 5 };
|
|
127
|
+
}
|
|
128
|
+
if (reason.extra) {
|
|
129
|
+
for (const [key, value] of Object.entries(reason.extra)) {
|
|
130
|
+
if (RESERVED_FIELDS.has(key)) {
|
|
131
|
+
console.warn(`[gate] onBeforeSession returned reserved field "${key}" \u2014 ignoring to preserve gate authority`);
|
|
132
|
+
continue;
|
|
133
|
+
}
|
|
134
|
+
body[key] = value;
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
return body;
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
// src/address.ts
|
|
141
|
+
var SOLANA_BASE58_RE = /^[1-9A-HJ-NP-Za-km-z]{32,44}$/;
|
|
142
|
+
var isSolanaAddress = (address) => SOLANA_BASE58_RE.test(address) && !address.startsWith("0x");
|
|
143
|
+
var normalizeAddress = (address) => {
|
|
144
|
+
if (isSolanaAddress(address)) {
|
|
145
|
+
return address;
|
|
146
|
+
}
|
|
147
|
+
return address.toLowerCase();
|
|
148
|
+
};
|
|
149
|
+
|
|
150
|
+
// src/cache.ts
|
|
151
|
+
var TTLCache = class {
|
|
152
|
+
constructor(defaultTtlMs, maxSize = 1e4) {
|
|
153
|
+
this.defaultTtlMs = defaultTtlMs;
|
|
154
|
+
this.maxSize = maxSize;
|
|
155
|
+
}
|
|
156
|
+
defaultTtlMs;
|
|
157
|
+
store = /* @__PURE__ */ new Map();
|
|
158
|
+
maxSize;
|
|
159
|
+
get(key) {
|
|
160
|
+
const entry = this.store.get(key);
|
|
161
|
+
if (!entry) return void 0;
|
|
162
|
+
if (Date.now() > entry.expiresAt) {
|
|
163
|
+
this.store.delete(key);
|
|
164
|
+
return void 0;
|
|
165
|
+
}
|
|
166
|
+
return entry.value;
|
|
167
|
+
}
|
|
168
|
+
set(key, value, ttlMs) {
|
|
169
|
+
if (this.store.size >= this.maxSize) {
|
|
170
|
+
this.sweep();
|
|
171
|
+
}
|
|
172
|
+
if (this.store.size >= this.maxSize) {
|
|
173
|
+
this.evictOldest(this.store.size - this.maxSize + 1);
|
|
174
|
+
}
|
|
175
|
+
this.store.set(key, {
|
|
176
|
+
value,
|
|
177
|
+
expiresAt: Date.now() + (ttlMs ?? this.defaultTtlMs)
|
|
178
|
+
});
|
|
179
|
+
}
|
|
180
|
+
/** Remove all expired entries. */
|
|
181
|
+
sweep() {
|
|
182
|
+
const now = Date.now();
|
|
183
|
+
for (const [k, v] of this.store) {
|
|
184
|
+
if (now > v.expiresAt) {
|
|
185
|
+
this.store.delete(k);
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
/** Evict the oldest `count` entries by insertion order. */
|
|
190
|
+
evictOldest(count) {
|
|
191
|
+
let removed = 0;
|
|
192
|
+
for (const key of this.store.keys()) {
|
|
193
|
+
if (removed >= count) break;
|
|
194
|
+
this.store.delete(key);
|
|
195
|
+
removed++;
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
};
|
|
199
|
+
|
|
200
|
+
// src/core.ts
|
|
201
|
+
function stripTrailingSlashes(s) {
|
|
202
|
+
let end = s.length;
|
|
203
|
+
while (end > 0 && s.charCodeAt(end - 1) === 47) end--;
|
|
204
|
+
return end === s.length ? s : s.slice(0, end);
|
|
205
|
+
}
|
|
206
|
+
var CANONICAL_AGENTSCORE_API = "https://api.agentscore.sh";
|
|
207
|
+
var WALLET_SIGNER_MISMATCH_INSTRUCTIONS = JSON.stringify({
|
|
208
|
+
action: "resign_or_switch_to_operator_token",
|
|
209
|
+
steps: [
|
|
210
|
+
"Preferred: re-submit the payment signed by expected_signer (or any entry in linked_wallets \u2014 same-operator wallets are fungible) and retry with the same X-Wallet-Address.",
|
|
211
|
+
"Alternative: drop X-Wallet-Address and retry with X-Operator-Token. Use a stored opc_... if you have one; otherwise retry this request with NO identity header \u2014 the merchant will mint a verification session in the 403 body (verify_url + poll_secret). Share verify_url with the user, poll, receive a fresh opc_..."
|
|
212
|
+
],
|
|
213
|
+
user_message: "The payment signer resolves to a different operator than X-Wallet-Address. Re-sign from expected_signer or any linked_wallets entry, or switch to X-Operator-Token."
|
|
214
|
+
});
|
|
215
|
+
var WALLET_AUTH_REQUIRES_WALLET_SIGNING_INSTRUCTIONS = JSON.stringify({
|
|
216
|
+
action: "switch_to_operator_token",
|
|
217
|
+
steps: [
|
|
218
|
+
"This payment rail (Stripe SPT, card) carries no wallet signature \u2014 X-Wallet-Address cannot be verified against the payment.",
|
|
219
|
+
"Drop X-Wallet-Address and retry with X-Operator-Token. If you do not have a stored opc_..., retry with no identity header to receive a verification session."
|
|
220
|
+
],
|
|
221
|
+
user_message: "Wallet-address identity is only supported on wallet-signing rails (Tempo MPP, x402). On Stripe or card, use X-Operator-Token instead."
|
|
222
|
+
});
|
|
223
|
+
var INVALID_CREDENTIAL_INSTRUCTIONS = JSON.stringify({
|
|
224
|
+
action: "switch_token_or_restart_session",
|
|
225
|
+
steps: [
|
|
226
|
+
"The X-Operator-Token you sent does not match any credential. This is a permanent state \u2014 retrying with the same token will keep failing.",
|
|
227
|
+
"If you have other stored opc_... credentials, retry with one of them.",
|
|
228
|
+
"Otherwise drop X-Operator-Token and retry with no identity header \u2014 the merchant will mint a fresh verification session in the 403 body (verify_url + poll_secret) so the user can re-verify and you can poll for a new operator_token."
|
|
229
|
+
],
|
|
230
|
+
user_message: "The operator_token is not recognized. Use a different stored token, or restart the verification session flow to mint a new one."
|
|
231
|
+
});
|
|
232
|
+
function buildAgentMemoryHint() {
|
|
233
|
+
const api = CANONICAL_AGENTSCORE_API;
|
|
234
|
+
return {
|
|
235
|
+
save_for_future_agentscore_gates: true,
|
|
236
|
+
pattern_summary: "AgentScore is a cross-merchant identity substrate for agent commerce. Any endpoint that returns 403 missing_identity / identity_verification_required or 402 challenges referencing AgentScore uses the same identity system. One KYC works across all AgentScore merchants \u2014 no re-verification per site. Save this pattern (and the endpoints below) to persistent memory so future AgentScore encounters skip the cold-start session flow.",
|
|
237
|
+
quickstart: "https://docs.agentscore.sh/guides/agent-identity",
|
|
238
|
+
identity_check_endpoint: `${api}/v1/credentials`,
|
|
239
|
+
identity_paths: {
|
|
240
|
+
wallet: "If paying from a crypto wallet you have used before on any AgentScore merchant, send `X-Wallet-Address: 0x...`. Shortest path; no token lifecycle to manage.",
|
|
241
|
+
operator_token: "For any rail (Stripe SPT, card, or a wallet you have not linked yet), send `X-Operator-Token: opc_...`. Reusable across merchants until the token expires."
|
|
242
|
+
},
|
|
243
|
+
bootstrap: "If you have neither a linked wallet nor a valid operator_token, follow the session/verify flow in the per-request `agent_instructions` block. This happens at most once per agent identity \u2014 after first verification, the operator_token and any subsequently used wallet are reusable everywhere.",
|
|
244
|
+
do_not_persist_in_memory: ["operator_token", "poll_secret"],
|
|
245
|
+
persist_in_credential_store: ["operator_token"]
|
|
246
|
+
};
|
|
247
|
+
}
|
|
248
|
+
function createAgentScoreCore(options) {
|
|
249
|
+
if (!options.apiKey) {
|
|
250
|
+
throw new Error("AgentScore API key is required. Get one at https://agentscore.sh/sign-up");
|
|
251
|
+
}
|
|
252
|
+
const {
|
|
253
|
+
apiKey,
|
|
254
|
+
requireKyc,
|
|
255
|
+
requireSanctionsClear,
|
|
256
|
+
minAge,
|
|
257
|
+
blockedJurisdictions,
|
|
258
|
+
allowedJurisdictions,
|
|
259
|
+
failOpen = false,
|
|
260
|
+
cacheSeconds = 300,
|
|
261
|
+
baseUrl: rawBaseUrl = "https://api.agentscore.sh",
|
|
262
|
+
chain: gateChain,
|
|
263
|
+
userAgent,
|
|
264
|
+
createSessionOnMissing
|
|
265
|
+
} = options;
|
|
266
|
+
const baseUrl = stripTrailingSlashes(rawBaseUrl);
|
|
267
|
+
const agentMemoryHint = buildAgentMemoryHint();
|
|
268
|
+
const defaultUa = `@agent-score/commerce@${"1.0.0"}`;
|
|
269
|
+
const userAgentHeader = userAgent ? `${userAgent} (${defaultUa})` : defaultUa;
|
|
270
|
+
const API_TIMEOUT_MS = 1e4;
|
|
271
|
+
const cache = new TTLCache(cacheSeconds * 1e3);
|
|
272
|
+
async function evaluate(identity, ctx) {
|
|
273
|
+
if (!identity || !identity.address && !identity.operatorToken) {
|
|
274
|
+
if (failOpen) return { kind: "allow" };
|
|
275
|
+
if (createSessionOnMissing) {
|
|
276
|
+
try {
|
|
277
|
+
const sessionBody = {};
|
|
278
|
+
if (createSessionOnMissing.context != null) sessionBody.context = createSessionOnMissing.context;
|
|
279
|
+
if (createSessionOnMissing.productName != null) sessionBody.product_name = createSessionOnMissing.productName;
|
|
280
|
+
if (createSessionOnMissing.getSessionOptions && ctx !== void 0) {
|
|
281
|
+
try {
|
|
282
|
+
const dynamic = await createSessionOnMissing.getSessionOptions(ctx);
|
|
283
|
+
if (dynamic?.context != null) sessionBody.context = dynamic.context;
|
|
284
|
+
if (dynamic?.productName != null) sessionBody.product_name = dynamic.productName;
|
|
285
|
+
} catch (err) {
|
|
286
|
+
console.warn("[gate] createSessionOnMissing.getSessionOptions hook failed:", err instanceof Error ? err.message : err);
|
|
287
|
+
}
|
|
288
|
+
}
|
|
289
|
+
const sessionBaseUrl = stripTrailingSlashes(createSessionOnMissing.baseUrl ?? "https://api.agentscore.sh");
|
|
290
|
+
const sessionRes = await fetch(`${sessionBaseUrl}/v1/sessions`, {
|
|
291
|
+
method: "POST",
|
|
292
|
+
headers: {
|
|
293
|
+
"X-API-Key": createSessionOnMissing.apiKey,
|
|
294
|
+
"Content-Type": "application/json",
|
|
295
|
+
Accept: "application/json",
|
|
296
|
+
"User-Agent": userAgentHeader
|
|
297
|
+
},
|
|
298
|
+
body: JSON.stringify(sessionBody),
|
|
299
|
+
signal: AbortSignal.timeout(API_TIMEOUT_MS)
|
|
300
|
+
});
|
|
301
|
+
if (sessionRes.ok) {
|
|
302
|
+
const data = await sessionRes.json();
|
|
303
|
+
if (typeof data.session_id !== "string" || typeof data.poll_secret !== "string" || typeof data.verify_url !== "string") {
|
|
304
|
+
console.warn("[gate] /v1/sessions returned 200 without required fields \u2014 falling back to bare missing_identity");
|
|
305
|
+
} else {
|
|
306
|
+
let extra;
|
|
307
|
+
if (createSessionOnMissing.onBeforeSession && ctx !== void 0) {
|
|
308
|
+
try {
|
|
309
|
+
const sessionMeta = {
|
|
310
|
+
session_id: data.session_id,
|
|
311
|
+
verify_url: data.verify_url,
|
|
312
|
+
poll_secret: data.poll_secret,
|
|
313
|
+
poll_url: data.poll_url,
|
|
314
|
+
expires_at: data.expires_at
|
|
315
|
+
};
|
|
316
|
+
const result = await createSessionOnMissing.onBeforeSession(ctx, sessionMeta);
|
|
317
|
+
if (result && typeof result === "object") extra = result;
|
|
318
|
+
} catch (err) {
|
|
319
|
+
console.warn("[gate] createSessionOnMissing.onBeforeSession hook failed:", err instanceof Error ? err.message : err);
|
|
320
|
+
}
|
|
321
|
+
}
|
|
322
|
+
const apiNextSteps = data.next_steps;
|
|
323
|
+
return {
|
|
324
|
+
kind: "deny",
|
|
325
|
+
reason: {
|
|
326
|
+
code: "identity_verification_required",
|
|
327
|
+
verify_url: data.verify_url,
|
|
328
|
+
session_id: data.session_id,
|
|
329
|
+
poll_secret: data.poll_secret,
|
|
330
|
+
poll_url: data.poll_url,
|
|
331
|
+
agent_instructions: apiNextSteps ? JSON.stringify(apiNextSteps) : void 0,
|
|
332
|
+
agent_memory: agentMemoryHint,
|
|
333
|
+
...extra && { extra }
|
|
334
|
+
}
|
|
335
|
+
};
|
|
336
|
+
}
|
|
337
|
+
}
|
|
338
|
+
} catch (err) {
|
|
339
|
+
console.warn("[gate] createSessionOnMissing path failed \u2014 falling back to bare missing_identity:", err instanceof Error ? err.message : err);
|
|
340
|
+
}
|
|
341
|
+
}
|
|
342
|
+
const missingIdentityInstructions = JSON.stringify({
|
|
343
|
+
action: "probe_identity_then_session",
|
|
344
|
+
steps: [
|
|
345
|
+
"If you have a wallet AND this merchant accepts a wallet-signing rail (Tempo MPP or x402 \u2014 see openapi.json x-payment-info): send X-Wallet-Address and retry. If the wallet is already linked to an AgentScore operator (via prior SIWE claim or prior captureWallet), this passes in one round trip. If the wallet is unlinked or the account has no KYC, the 403 will include a verify_url \u2014 share it with the user to claim the wallet + complete KYC, then retry.",
|
|
346
|
+
"If step 1 is denied or you already have a stored operator_token (valid + not expired): send X-Operator-Token: opc_... and retry.",
|
|
347
|
+
"If neither applies: retry with NO identity header. Merchants that auto-create verification sessions (most AgentScore merchants do) return verify_url + session_id + poll_secret in the 403 body \u2014 share verify_url with the user, then poll poll_url every 5s with the X-Poll-Secret header until status=verified (the poll returns a one-time operator_token). If the retry returns the same bare 403, this merchant does not support self-service session bootstrapping \u2014 direct the user to https://agentscore.sh/sign-up to create an AgentScore identity and mint an operator_token from their dashboard (https://agentscore.sh/dashboard/verify). The user hands the opc_... to you, and you retry with X-Operator-Token."
|
|
348
|
+
],
|
|
349
|
+
user_message: "Try X-Wallet-Address first if you have a wallet and the merchant accepts Tempo/x402; fall back to a stored X-Operator-Token, then to the session/verify flow described in agent_memory.bootstrap."
|
|
350
|
+
});
|
|
351
|
+
return {
|
|
352
|
+
kind: "deny",
|
|
353
|
+
reason: {
|
|
354
|
+
code: "missing_identity",
|
|
355
|
+
agent_instructions: missingIdentityInstructions,
|
|
356
|
+
agent_memory: agentMemoryHint
|
|
357
|
+
}
|
|
358
|
+
};
|
|
359
|
+
}
|
|
360
|
+
const cacheKey = identity.operatorToken?.toLowerCase() ?? (identity.address ? normalizeAddress(identity.address) : "");
|
|
361
|
+
const cached = cache.get(cacheKey);
|
|
362
|
+
if (cached) {
|
|
363
|
+
if (cached.allow) {
|
|
364
|
+
return { kind: "allow", data: cached.raw };
|
|
365
|
+
}
|
|
366
|
+
return {
|
|
367
|
+
kind: "deny",
|
|
368
|
+
reason: {
|
|
369
|
+
code: "wallet_not_trusted",
|
|
370
|
+
decision: cached.decision,
|
|
371
|
+
reasons: cached.reasons,
|
|
372
|
+
verify_url: cached.raw?.verify_url,
|
|
373
|
+
data: cached.raw
|
|
374
|
+
}
|
|
375
|
+
};
|
|
376
|
+
}
|
|
377
|
+
try {
|
|
378
|
+
const body = {};
|
|
379
|
+
if (identity.address) body.address = identity.address;
|
|
380
|
+
if (identity.operatorToken) body.operator_token = identity.operatorToken;
|
|
381
|
+
if (gateChain) body.chain = gateChain;
|
|
382
|
+
const policy = {};
|
|
383
|
+
if (requireKyc != null) policy.require_kyc = requireKyc;
|
|
384
|
+
if (requireSanctionsClear != null) policy.require_sanctions_clear = requireSanctionsClear;
|
|
385
|
+
if (minAge != null) policy.min_age = minAge;
|
|
386
|
+
if (blockedJurisdictions != null) policy.blocked_jurisdictions = blockedJurisdictions;
|
|
387
|
+
if (allowedJurisdictions != null) policy.allowed_jurisdictions = allowedJurisdictions;
|
|
388
|
+
if (Object.keys(policy).length > 0) body.policy = policy;
|
|
389
|
+
const response = await fetch(`${baseUrl}/v1/assess`, {
|
|
390
|
+
method: "POST",
|
|
391
|
+
headers: {
|
|
392
|
+
"X-API-Key": apiKey,
|
|
393
|
+
"Content-Type": "application/json",
|
|
394
|
+
Accept: "application/json",
|
|
395
|
+
"User-Agent": userAgentHeader
|
|
396
|
+
},
|
|
397
|
+
body: JSON.stringify(body),
|
|
398
|
+
signal: AbortSignal.timeout(API_TIMEOUT_MS)
|
|
399
|
+
});
|
|
400
|
+
if (response.status === 402) {
|
|
401
|
+
if (failOpen) return { kind: "allow" };
|
|
402
|
+
return { kind: "deny", reason: { code: "payment_required" } };
|
|
403
|
+
}
|
|
404
|
+
if (response.status === 401) {
|
|
405
|
+
try {
|
|
406
|
+
const errData = await response.clone().json();
|
|
407
|
+
const code = errData?.error?.code;
|
|
408
|
+
if (code === "token_expired") {
|
|
409
|
+
return {
|
|
410
|
+
kind: "deny",
|
|
411
|
+
reason: {
|
|
412
|
+
code,
|
|
413
|
+
data: errData,
|
|
414
|
+
...typeof errData.verify_url === "string" ? { verify_url: errData.verify_url } : {},
|
|
415
|
+
...typeof errData.session_id === "string" ? { session_id: errData.session_id } : {},
|
|
416
|
+
...typeof errData.poll_secret === "string" ? { poll_secret: errData.poll_secret } : {},
|
|
417
|
+
...typeof errData.poll_url === "string" ? { poll_url: errData.poll_url } : {},
|
|
418
|
+
...errData.next_steps ? { agent_instructions: JSON.stringify(errData.next_steps) } : {},
|
|
419
|
+
...errData.agent_memory ? { agent_memory: errData.agent_memory } : {}
|
|
420
|
+
}
|
|
421
|
+
};
|
|
422
|
+
}
|
|
423
|
+
if (code === "invalid_credential") {
|
|
424
|
+
return {
|
|
425
|
+
kind: "deny",
|
|
426
|
+
reason: {
|
|
427
|
+
code: "invalid_credential",
|
|
428
|
+
agent_instructions: INVALID_CREDENTIAL_INSTRUCTIONS,
|
|
429
|
+
agent_memory: agentMemoryHint
|
|
430
|
+
}
|
|
431
|
+
};
|
|
432
|
+
}
|
|
433
|
+
if (code) {
|
|
434
|
+
console.warn(`[gate] /v1/assess returned 401 ${code} \u2014 no specific handler, surfacing as api_error.`);
|
|
435
|
+
}
|
|
436
|
+
} catch (err) {
|
|
437
|
+
console.warn("[gate] /v1/assess 401 body parse failed:", err instanceof Error ? err.message : err);
|
|
438
|
+
}
|
|
439
|
+
}
|
|
440
|
+
if (response.status >= 400 && response.status < 500 && response.status !== 402) {
|
|
441
|
+
try {
|
|
442
|
+
const errData = await response.clone().json();
|
|
443
|
+
const code = errData?.error?.code;
|
|
444
|
+
if (code && code !== "token_expired" && code !== "invalid_credential") {
|
|
445
|
+
console.warn(
|
|
446
|
+
`[gate] /v1/assess returned ${response.status} ${code} \u2014 surfacing as api_error. Validate input shape before invoking the gate to avoid this.`
|
|
447
|
+
);
|
|
448
|
+
}
|
|
449
|
+
} catch {
|
|
450
|
+
}
|
|
451
|
+
}
|
|
452
|
+
if (!response.ok) {
|
|
453
|
+
throw new Error(`AgentScore API returned ${response.status}`);
|
|
454
|
+
}
|
|
455
|
+
const data = await response.json();
|
|
456
|
+
const decision = data.decision;
|
|
457
|
+
const decisionReasons = data.decision_reasons ?? [];
|
|
458
|
+
const allow = decision === "allow" || decision == null;
|
|
459
|
+
cache.set(cacheKey, { allow, decision: decision ?? void 0, reasons: decisionReasons, raw: data });
|
|
460
|
+
if (allow) {
|
|
461
|
+
return { kind: "allow", data };
|
|
462
|
+
}
|
|
463
|
+
return {
|
|
464
|
+
kind: "deny",
|
|
465
|
+
reason: {
|
|
466
|
+
code: "wallet_not_trusted",
|
|
467
|
+
decision: decision ?? void 0,
|
|
468
|
+
reasons: decisionReasons,
|
|
469
|
+
verify_url: data.verify_url,
|
|
470
|
+
data
|
|
471
|
+
}
|
|
472
|
+
};
|
|
473
|
+
} catch (err) {
|
|
474
|
+
console.warn("[gate] /v1/assess call failed \u2014 surfacing as api_error:", err instanceof Error ? err.message : err);
|
|
475
|
+
if (failOpen) return { kind: "allow" };
|
|
476
|
+
return { kind: "deny", reason: { code: "api_error" } };
|
|
477
|
+
}
|
|
478
|
+
}
|
|
479
|
+
async function captureWallet2(options2) {
|
|
480
|
+
try {
|
|
481
|
+
const body = {
|
|
482
|
+
operator_token: options2.operatorToken,
|
|
483
|
+
wallet_address: options2.walletAddress,
|
|
484
|
+
network: options2.network
|
|
485
|
+
};
|
|
486
|
+
if (options2.idempotencyKey) body.idempotency_key = options2.idempotencyKey;
|
|
487
|
+
await fetch(`${baseUrl}/v1/credentials/wallets`, {
|
|
488
|
+
method: "POST",
|
|
489
|
+
headers: {
|
|
490
|
+
"X-API-Key": apiKey,
|
|
491
|
+
"Content-Type": "application/json",
|
|
492
|
+
Accept: "application/json",
|
|
493
|
+
"User-Agent": userAgentHeader
|
|
494
|
+
},
|
|
495
|
+
body: JSON.stringify(body),
|
|
496
|
+
signal: AbortSignal.timeout(API_TIMEOUT_MS)
|
|
497
|
+
});
|
|
498
|
+
} catch (err) {
|
|
499
|
+
console.warn("[agentscore-commerce] captureWallet failed:", err instanceof Error ? err.message : err);
|
|
500
|
+
}
|
|
501
|
+
}
|
|
502
|
+
async function resolveWalletToOperator(walletAddress) {
|
|
503
|
+
const wallet = normalizeAddress(walletAddress);
|
|
504
|
+
const extractFromCached = (raw) => {
|
|
505
|
+
const op = raw.resolved_operator;
|
|
506
|
+
const links = raw.linked_wallets;
|
|
507
|
+
return {
|
|
508
|
+
operator: typeof op === "string" ? op : null,
|
|
509
|
+
linkedWallets: Array.isArray(links) ? links.filter((w) => typeof w === "string") : []
|
|
510
|
+
};
|
|
511
|
+
};
|
|
512
|
+
const plainCached = cache.get(wallet);
|
|
513
|
+
if (plainCached?.raw) {
|
|
514
|
+
return { ok: true, ...extractFromCached(plainCached.raw) };
|
|
515
|
+
}
|
|
516
|
+
const resolveCached = cache.get(`resolve:${wallet}`);
|
|
517
|
+
if (resolveCached?.raw) {
|
|
518
|
+
return { ok: true, ...extractFromCached(resolveCached.raw) };
|
|
519
|
+
}
|
|
520
|
+
try {
|
|
521
|
+
const response = await fetch(`${baseUrl}/v1/assess`, {
|
|
522
|
+
method: "POST",
|
|
523
|
+
headers: {
|
|
524
|
+
"X-API-Key": apiKey,
|
|
525
|
+
"Content-Type": "application/json",
|
|
526
|
+
Accept: "application/json",
|
|
527
|
+
"User-Agent": userAgentHeader
|
|
528
|
+
},
|
|
529
|
+
body: JSON.stringify({ address: walletAddress }),
|
|
530
|
+
signal: AbortSignal.timeout(API_TIMEOUT_MS)
|
|
531
|
+
});
|
|
532
|
+
if (!response.ok) return { ok: false };
|
|
533
|
+
const data = await response.json();
|
|
534
|
+
cache.set(`resolve:${wallet}`, { allow: true, raw: data });
|
|
535
|
+
return { ok: true, ...extractFromCached(data) };
|
|
536
|
+
} catch (err) {
|
|
537
|
+
console.warn("[gate] resolveWalletToOperator failed \u2014 returning { ok:false }:", err instanceof Error ? err.message : err);
|
|
538
|
+
return { ok: false };
|
|
539
|
+
}
|
|
540
|
+
}
|
|
541
|
+
function reportSignerEvent(kind) {
|
|
542
|
+
try {
|
|
543
|
+
const pending = fetch(`${baseUrl}/v1/telemetry/signer-match`, {
|
|
544
|
+
method: "POST",
|
|
545
|
+
headers: {
|
|
546
|
+
"X-API-Key": apiKey,
|
|
547
|
+
"Content-Type": "application/json",
|
|
548
|
+
Accept: "application/json",
|
|
549
|
+
"User-Agent": userAgentHeader
|
|
550
|
+
},
|
|
551
|
+
body: JSON.stringify({ kind }),
|
|
552
|
+
signal: AbortSignal.timeout(API_TIMEOUT_MS)
|
|
553
|
+
});
|
|
554
|
+
if (pending && typeof pending.catch === "function") {
|
|
555
|
+
pending.catch((err) => {
|
|
556
|
+
console.warn("[agentscore-commerce] signer-match telemetry failed:", err instanceof Error ? err.message : err);
|
|
557
|
+
});
|
|
558
|
+
}
|
|
559
|
+
} catch {
|
|
560
|
+
}
|
|
561
|
+
}
|
|
562
|
+
async function verifyWalletSignerMatch2(options2) {
|
|
563
|
+
const { claimedWallet, signer } = options2;
|
|
564
|
+
if (!signer) {
|
|
565
|
+
reportSignerEvent("wallet_auth_requires_wallet_signing");
|
|
566
|
+
return {
|
|
567
|
+
kind: "wallet_auth_requires_wallet_signing",
|
|
568
|
+
claimedWallet,
|
|
569
|
+
agentInstructions: WALLET_AUTH_REQUIRES_WALLET_SIGNING_INSTRUCTIONS
|
|
570
|
+
};
|
|
571
|
+
}
|
|
572
|
+
const claimedNorm = normalizeAddress(claimedWallet);
|
|
573
|
+
const signerNorm = normalizeAddress(signer);
|
|
574
|
+
if (claimedNorm === signerNorm) {
|
|
575
|
+
reportSignerEvent("pass");
|
|
576
|
+
return { kind: "pass", claimedOperator: null, signerOperator: null };
|
|
577
|
+
}
|
|
578
|
+
const [claimedResolve, signerResolve] = await Promise.all([
|
|
579
|
+
resolveWalletToOperator(claimedNorm),
|
|
580
|
+
resolveWalletToOperator(signerNorm)
|
|
581
|
+
]);
|
|
582
|
+
if (!claimedResolve.ok || !signerResolve.ok) {
|
|
583
|
+
reportSignerEvent("api_error");
|
|
584
|
+
return { kind: "api_error", claimedWallet: claimedNorm };
|
|
585
|
+
}
|
|
586
|
+
const claimedOperator = claimedResolve.operator;
|
|
587
|
+
const signerOperator = signerResolve.operator;
|
|
588
|
+
if (claimedOperator && signerOperator && claimedOperator === signerOperator) {
|
|
589
|
+
reportSignerEvent("pass");
|
|
590
|
+
return { kind: "pass", claimedOperator, signerOperator };
|
|
591
|
+
}
|
|
592
|
+
reportSignerEvent("wallet_signer_mismatch");
|
|
593
|
+
return {
|
|
594
|
+
kind: "wallet_signer_mismatch",
|
|
595
|
+
claimedOperator,
|
|
596
|
+
actualSignerOperator: signerOperator,
|
|
597
|
+
expectedSigner: claimedNorm,
|
|
598
|
+
actualSigner: signerNorm,
|
|
599
|
+
// Populated from /v1/assess.linked_wallets on the claimed wallet — the full set of
|
|
600
|
+
// wallets the agent CAN sign with to satisfy the claim (same-operator rule).
|
|
601
|
+
linkedWallets: claimedResolve.linkedWallets,
|
|
602
|
+
agentInstructions: WALLET_SIGNER_MISMATCH_INSTRUCTIONS
|
|
603
|
+
};
|
|
604
|
+
}
|
|
605
|
+
return { evaluate, captureWallet: captureWallet2, verifyWalletSignerMatch: verifyWalletSignerMatch2 };
|
|
606
|
+
}
|
|
607
|
+
|
|
608
|
+
// src/signer.ts
|
|
609
|
+
async function extractPaymentSigner(request, x402PaymentHeader) {
|
|
610
|
+
const authHeader = request.headers.get("authorization");
|
|
611
|
+
if (authHeader) {
|
|
612
|
+
try {
|
|
613
|
+
const moduleName = "mppx";
|
|
614
|
+
const mppx = await import(moduleName).catch(() => null);
|
|
615
|
+
if (mppx?.Credential?.extractPaymentScheme(authHeader)) {
|
|
616
|
+
const credential = mppx.Credential.fromRequest(request);
|
|
617
|
+
const source = credential.source;
|
|
618
|
+
const match = source?.match(/^did:pkh:eip155:\d+:(0x[0-9a-fA-F]{40})$/);
|
|
619
|
+
if (match) return { address: match[1].toLowerCase(), network: "evm" };
|
|
620
|
+
}
|
|
621
|
+
} catch (err) {
|
|
622
|
+
console.warn("[gate] MPP signer extraction failed:", err instanceof Error ? err.message : err);
|
|
623
|
+
}
|
|
624
|
+
}
|
|
625
|
+
if (x402PaymentHeader) {
|
|
626
|
+
try {
|
|
627
|
+
const decoded = atob(x402PaymentHeader);
|
|
628
|
+
const parsed = JSON.parse(decoded);
|
|
629
|
+
const network = parsed?.accepted?.network ?? "";
|
|
630
|
+
if (network.startsWith("eip155:")) {
|
|
631
|
+
const from = parsed?.payload?.authorization?.from;
|
|
632
|
+
if (typeof from === "string" && /^0x[0-9a-fA-F]{40}$/.test(from)) {
|
|
633
|
+
return { address: from.toLowerCase(), network: "evm" };
|
|
634
|
+
}
|
|
635
|
+
} else if (network.startsWith("solana:")) {
|
|
636
|
+
const transaction = parsed?.payload?.transaction;
|
|
637
|
+
if (typeof transaction === "string") {
|
|
638
|
+
const moduleName = "@x402/svm";
|
|
639
|
+
const svm = await import(moduleName).catch(() => null);
|
|
640
|
+
if (svm?.decodeTransactionFromPayload && svm.getTokenPayerFromTransaction) {
|
|
641
|
+
const tx = svm.decodeTransactionFromPayload({ transaction });
|
|
642
|
+
const payer = svm.getTokenPayerFromTransaction(tx);
|
|
643
|
+
if (typeof payer === "string" && payer.length > 0) return { address: payer, network: "solana" };
|
|
644
|
+
}
|
|
645
|
+
}
|
|
646
|
+
} else {
|
|
647
|
+
const from = parsed?.payload?.authorization?.from;
|
|
648
|
+
if (typeof from === "string" && /^0x[0-9a-fA-F]{40}$/.test(from)) {
|
|
649
|
+
return { address: from.toLowerCase(), network: "evm" };
|
|
650
|
+
}
|
|
651
|
+
}
|
|
652
|
+
} catch (err) {
|
|
653
|
+
console.warn("[gate] x402 signer extraction failed:", err instanceof Error ? err.message : err);
|
|
654
|
+
}
|
|
655
|
+
}
|
|
656
|
+
return null;
|
|
657
|
+
}
|
|
658
|
+
async function extractPaymentSignerAddress(request, x402PaymentHeader) {
|
|
659
|
+
const result = await extractPaymentSigner(request, x402PaymentHeader);
|
|
660
|
+
return result?.address ?? null;
|
|
661
|
+
}
|
|
662
|
+
function readX402PaymentHeader(request) {
|
|
663
|
+
return request.headers.get("payment-signature") ?? request.headers.get("x-payment") ?? void 0;
|
|
664
|
+
}
|
|
665
|
+
|
|
666
|
+
// src/identity/express.ts
|
|
667
|
+
var GATE_STATE_KEY = "__agentscoreGate";
|
|
668
|
+
function defaultExtractIdentity(req) {
|
|
669
|
+
const token = req.headers["x-operator-token"];
|
|
670
|
+
const addr = req.headers["x-wallet-address"];
|
|
671
|
+
const identity = {};
|
|
672
|
+
if (typeof token === "string" && token.length > 0) identity.operatorToken = token;
|
|
673
|
+
if (typeof addr === "string" && addr.length > 0) identity.address = addr;
|
|
674
|
+
if (identity.operatorToken || identity.address) return identity;
|
|
675
|
+
return void 0;
|
|
676
|
+
}
|
|
677
|
+
function defaultOnDenied(_req, res, reason) {
|
|
678
|
+
res.status(denialReasonStatus(reason)).json(denialReasonToBody(reason));
|
|
679
|
+
}
|
|
680
|
+
function agentscoreGate(options) {
|
|
681
|
+
const { extractIdentity = defaultExtractIdentity, onDenied = defaultOnDenied, ...coreOptions } = options;
|
|
682
|
+
const core = createAgentScoreCore(coreOptions);
|
|
683
|
+
return async function agentscoreMiddleware(req, res, next) {
|
|
684
|
+
const identity = extractIdentity(req);
|
|
685
|
+
req[GATE_STATE_KEY] = {
|
|
686
|
+
core,
|
|
687
|
+
operatorToken: identity?.operatorToken,
|
|
688
|
+
walletAddress: identity?.address
|
|
689
|
+
};
|
|
690
|
+
const outcome = await core.evaluate(identity, req);
|
|
691
|
+
if (outcome.kind === "allow") {
|
|
692
|
+
if (outcome.data) req.agentscore = outcome.data;
|
|
693
|
+
next();
|
|
694
|
+
return;
|
|
695
|
+
}
|
|
696
|
+
onDenied(req, res, outcome.reason);
|
|
697
|
+
};
|
|
698
|
+
}
|
|
699
|
+
function getAgentScoreData(req) {
|
|
700
|
+
return req.agentscore;
|
|
701
|
+
}
|
|
702
|
+
async function captureWallet(req, options) {
|
|
703
|
+
const state = req[GATE_STATE_KEY];
|
|
704
|
+
if (!state?.operatorToken) return;
|
|
705
|
+
await state.core.captureWallet({
|
|
706
|
+
operatorToken: state.operatorToken,
|
|
707
|
+
walletAddress: options.walletAddress,
|
|
708
|
+
network: options.network,
|
|
709
|
+
idempotencyKey: options.idempotencyKey
|
|
710
|
+
});
|
|
711
|
+
}
|
|
712
|
+
async function verifyWalletSignerMatch(req, options) {
|
|
713
|
+
const state = req[GATE_STATE_KEY];
|
|
714
|
+
if (!state?.walletAddress || state.operatorToken) {
|
|
715
|
+
return { kind: "pass", claimedOperator: null, signerOperator: null };
|
|
716
|
+
}
|
|
717
|
+
return state.core.verifyWalletSignerMatch({
|
|
718
|
+
claimedWallet: state.walletAddress,
|
|
719
|
+
signer: options.signer,
|
|
720
|
+
network: options.network
|
|
721
|
+
});
|
|
722
|
+
}
|
|
723
|
+
export {
|
|
724
|
+
FIXABLE_DENIAL_REASONS,
|
|
725
|
+
agentscoreGate,
|
|
726
|
+
buildContactSupportNextSteps,
|
|
727
|
+
buildSignerMismatchBody,
|
|
728
|
+
captureWallet,
|
|
729
|
+
denialReasonStatus,
|
|
730
|
+
denialReasonToBody,
|
|
731
|
+
extractPaymentSignerAddress,
|
|
732
|
+
getAgentScoreData,
|
|
733
|
+
isFixableDenial,
|
|
734
|
+
readX402PaymentHeader,
|
|
735
|
+
verificationAgentInstructions,
|
|
736
|
+
verifyWalletSignerMatch
|
|
737
|
+
};
|
|
738
|
+
//# sourceMappingURL=express.mjs.map
|