@haven_ai/sdk 0.1.16-alpha.0 → 0.1.18-alpha.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/dist/index.cjs +227 -20
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +229 -10
- package/dist/index.d.ts +229 -10
- package/dist/index.js +222 -21
- package/dist/index.js.map +1 -1
- package/package.json +9 -1
package/dist/index.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { AsyncLocalStorage } from 'async_hooks';
|
|
2
2
|
import { exact } from 'x402/schemes';
|
|
3
|
+
import { hashTypedData } from 'viem';
|
|
3
4
|
import { privateKeyToAccount } from 'viem/accounts';
|
|
4
5
|
import { ethers } from 'ethers';
|
|
5
6
|
import { createHash } from 'crypto';
|
|
@@ -240,6 +241,22 @@ function signHash(privateKey, hash) {
|
|
|
240
241
|
);
|
|
241
242
|
}
|
|
242
243
|
}
|
|
244
|
+
async function signUserOpTypedDataForDelegation(privateKey, typedData) {
|
|
245
|
+
try {
|
|
246
|
+
const wallet = new ethers.Wallet(privateKey);
|
|
247
|
+
const types = { ...typedData.types };
|
|
248
|
+
delete types.EIP712Domain;
|
|
249
|
+
return await wallet.signTypedData(
|
|
250
|
+
typedData.domain,
|
|
251
|
+
types,
|
|
252
|
+
typedData.message
|
|
253
|
+
);
|
|
254
|
+
} catch (err) {
|
|
255
|
+
throw new HavenSigningError(
|
|
256
|
+
`Failed to sign delegation UserOperation: ${err instanceof Error ? err.message : String(err)}`
|
|
257
|
+
);
|
|
258
|
+
}
|
|
259
|
+
}
|
|
243
260
|
function addressFromKey(privateKey) {
|
|
244
261
|
try {
|
|
245
262
|
return new ethers.Wallet(privateKey).address;
|
|
@@ -257,6 +274,24 @@ function verifySignature(hash, signature, expectedAddress) {
|
|
|
257
274
|
return false;
|
|
258
275
|
}
|
|
259
276
|
}
|
|
277
|
+
var RECEIPT_VERSION = "haven-receipt-1";
|
|
278
|
+
function defaultRecover(hash, signature) {
|
|
279
|
+
return ethers.recoverAddress(hash, signature);
|
|
280
|
+
}
|
|
281
|
+
function verifyPaymentReceipt(receipt, recover = defaultRecover) {
|
|
282
|
+
const { delegate, signHash: signHash2, signature } = receipt.authorization;
|
|
283
|
+
if (!signature) return { verified: false, reason: "missing_signature" };
|
|
284
|
+
let recovered;
|
|
285
|
+
try {
|
|
286
|
+
recovered = recover(signHash2, signature);
|
|
287
|
+
} catch {
|
|
288
|
+
return { verified: false, reason: "bad_signature" };
|
|
289
|
+
}
|
|
290
|
+
if (recovered.toLowerCase() !== delegate.toLowerCase()) {
|
|
291
|
+
return { verified: false, reason: "signer_mismatch", recoveredSigner: recovered };
|
|
292
|
+
}
|
|
293
|
+
return { verified: true, recoveredSigner: recovered };
|
|
294
|
+
}
|
|
260
295
|
|
|
261
296
|
// src/base64.ts
|
|
262
297
|
function normalizeBase64(value) {
|
|
@@ -301,6 +336,8 @@ function decodeBase64Json(value, label) {
|
|
|
301
336
|
|
|
302
337
|
// src/x402.ts
|
|
303
338
|
var BASE_USDC_ADDRESS = "0x833589fcd6edb6e08f4c7c32d4f71b54bda02913";
|
|
339
|
+
var BASE_SEPOLIA_USDC_ADDRESS = "0x036cbd53842c5426634e7929541ec2318f3dcf7e";
|
|
340
|
+
var STANDARD_X402_USDC_ADDRESSES = /* @__PURE__ */ new Set([BASE_USDC_ADDRESS, BASE_SEPOLIA_USDC_ADDRESS]);
|
|
304
341
|
var X402_IDEMPOTENCY_BUCKET_MS = 3e5;
|
|
305
342
|
var DECIMAL_ATOMIC_AMOUNT_RE = /^[0-9]+$/;
|
|
306
343
|
function isPositiveDecimalAtomicAmount(value) {
|
|
@@ -309,6 +346,11 @@ function isPositiveDecimalAtomicAmount(value) {
|
|
|
309
346
|
function optionAuthorizationAmount(option) {
|
|
310
347
|
return option.maxAmountRequired ?? option.amount;
|
|
311
348
|
}
|
|
349
|
+
var X402_MAX_AUTHORIZATION_WINDOW_SECONDS = 600;
|
|
350
|
+
function clampAuthorizationWindow(seconds) {
|
|
351
|
+
const requested = typeof seconds === "number" && Number.isFinite(seconds) ? seconds : 30;
|
|
352
|
+
return Math.min(Math.max(Math.floor(requested), 1), X402_MAX_AUTHORIZATION_WINDOW_SECONDS);
|
|
353
|
+
}
|
|
312
354
|
function normalizePaymentOption(value) {
|
|
313
355
|
const candidate = value;
|
|
314
356
|
if (!candidate || typeof candidate !== "object" || typeof candidate.scheme !== "string" || typeof candidate.network !== "string" || typeof candidate.asset !== "string" || typeof candidate.payTo !== "string") {
|
|
@@ -330,7 +372,7 @@ function normalizePaymentOption(value) {
|
|
|
330
372
|
mimeType: candidate.mimeType,
|
|
331
373
|
asset: candidate.asset,
|
|
332
374
|
payTo: candidate.payTo,
|
|
333
|
-
maxTimeoutSeconds: candidate.maxTimeoutSeconds
|
|
375
|
+
maxTimeoutSeconds: clampAuthorizationWindow(candidate.maxTimeoutSeconds),
|
|
334
376
|
extra: candidate.extra
|
|
335
377
|
};
|
|
336
378
|
}
|
|
@@ -360,11 +402,15 @@ function normalizePaymentRequired(value) {
|
|
|
360
402
|
var SUPPORTED_X402_NETWORKS = {
|
|
361
403
|
"eip155:100": "Gnosis Chain",
|
|
362
404
|
"eip155:8453": "Base",
|
|
363
|
-
"base": "Base"
|
|
405
|
+
"base": "Base",
|
|
406
|
+
"eip155:84532": "Base Sepolia",
|
|
407
|
+
"base-sepolia": "Base Sepolia"
|
|
364
408
|
};
|
|
365
409
|
var STANDARD_X402_NETWORKS = {
|
|
366
410
|
"eip155:8453": "base",
|
|
367
|
-
"base": "base"
|
|
411
|
+
"base": "base",
|
|
412
|
+
"eip155:84532": "base-sepolia",
|
|
413
|
+
"base-sepolia": "base-sepolia"
|
|
368
414
|
};
|
|
369
415
|
var GNOSIS_TOKENS = {
|
|
370
416
|
"0x0000000000000000000000000000000000000000": { symbol: "xDAI", decimals: 18 },
|
|
@@ -375,14 +421,21 @@ var BASE_TOKENS = {
|
|
|
375
421
|
"0x0000000000000000000000000000000000000000": { symbol: "ETH", decimals: 18 },
|
|
376
422
|
"0x833589fcd6edb6e08f4c7c32d4f71b54bda02913": { symbol: "USDC", decimals: 6 }
|
|
377
423
|
};
|
|
424
|
+
var BASE_SEPOLIA_TOKENS = {
|
|
425
|
+
"0x0000000000000000000000000000000000000000": { symbol: "ETH", decimals: 18 },
|
|
426
|
+
"0x036cbd53842c5426634e7929541ec2318f3dcf7e": { symbol: "USDC", decimals: 6 }
|
|
427
|
+
};
|
|
378
428
|
var ALL_TOKENS = {
|
|
379
429
|
...GNOSIS_TOKENS,
|
|
380
|
-
...BASE_TOKENS
|
|
430
|
+
...BASE_TOKENS,
|
|
431
|
+
...BASE_SEPOLIA_TOKENS
|
|
381
432
|
};
|
|
382
433
|
var NETWORK_TOKENS = {
|
|
383
434
|
"eip155:100": GNOSIS_TOKENS,
|
|
384
435
|
"eip155:8453": BASE_TOKENS,
|
|
385
|
-
"base": BASE_TOKENS
|
|
436
|
+
"base": BASE_TOKENS,
|
|
437
|
+
"eip155:84532": BASE_SEPOLIA_TOKENS,
|
|
438
|
+
"base-sepolia": BASE_SEPOLIA_TOKENS
|
|
386
439
|
};
|
|
387
440
|
function parsePaymentRequired(response) {
|
|
388
441
|
const v2Header = response.headers.get("PAYMENT-REQUIRED");
|
|
@@ -436,7 +489,7 @@ function selectPaymentOption(accepts) {
|
|
|
436
489
|
function selectStandardPaymentOption(accepts) {
|
|
437
490
|
if (!accepts || accepts.length === 0) return null;
|
|
438
491
|
for (const opt of accepts) {
|
|
439
|
-
if (opt.scheme === "exact" && opt.network in STANDARD_X402_NETWORKS && opt.asset.toLowerCase()
|
|
492
|
+
if (opt.scheme === "exact" && opt.network in STANDARD_X402_NETWORKS && STANDARD_X402_USDC_ADDRESSES.has(opt.asset.toLowerCase()) && isPositiveDecimalAtomicAmount(optionAuthorizationAmount(opt))) {
|
|
440
493
|
return opt;
|
|
441
494
|
}
|
|
442
495
|
}
|
|
@@ -450,8 +503,9 @@ function x402AuthorizationAmount(option) {
|
|
|
450
503
|
return amount;
|
|
451
504
|
}
|
|
452
505
|
function buildX402ExpectedMessage(context) {
|
|
506
|
+
const version = context.typedDataHash ? 2 : 1;
|
|
453
507
|
const payload = {
|
|
454
|
-
version
|
|
508
|
+
version,
|
|
455
509
|
kind: "haven.x402.expected",
|
|
456
510
|
paymentId: context.paymentId,
|
|
457
511
|
payloadHash: context.payloadHash.toLowerCase(),
|
|
@@ -464,7 +518,10 @@ function buildX402ExpectedMessage(context) {
|
|
|
464
518
|
if (context.expiresAt) {
|
|
465
519
|
payload.expiresAt = context.expiresAt;
|
|
466
520
|
}
|
|
467
|
-
|
|
521
|
+
if (context.typedDataHash) {
|
|
522
|
+
payload.typedDataHash = context.typedDataHash.toLowerCase();
|
|
523
|
+
}
|
|
524
|
+
return `Haven x402 expected context v${version}
|
|
468
525
|
${stableStringify(payload)}`;
|
|
469
526
|
}
|
|
470
527
|
function toStandardPaymentRequirements(paymentRequired, option) {
|
|
@@ -484,7 +541,10 @@ function toStandardPaymentRequirements(paymentRequired, option) {
|
|
|
484
541
|
mimeType: option.mimeType ?? paymentRequired.resource.mimeType ?? "application/octet-stream",
|
|
485
542
|
payTo: option.payTo,
|
|
486
543
|
asset: option.asset,
|
|
487
|
-
|
|
544
|
+
// Second enforcement point (#715): the parse path clamps too, but this is
|
|
545
|
+
// the last stop before the x402 library turns the timeout into
|
|
546
|
+
// `validBefore` — options constructed without parsing are bounded here.
|
|
547
|
+
maxTimeoutSeconds: clampAuthorizationWindow(option.maxTimeoutSeconds),
|
|
488
548
|
extra: option.extra
|
|
489
549
|
};
|
|
490
550
|
}
|
|
@@ -618,7 +678,8 @@ var CHAIN_EXPLORER_TX = {
|
|
|
618
678
|
8453: "https://basescan.org/tx"
|
|
619
679
|
};
|
|
620
680
|
var CHAIN_USDC = {
|
|
621
|
-
8453: "0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913"
|
|
681
|
+
8453: "0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913",
|
|
682
|
+
84532: "0x036CbD53842c5426634e7929541eC2318f3dCF7e"
|
|
622
683
|
};
|
|
623
684
|
function buildExplorerUrl(chainId, txHash) {
|
|
624
685
|
const base = CHAIN_EXPLORER_TX[chainId ?? 8453] ?? CHAIN_EXPLORER_TX[8453];
|
|
@@ -781,6 +842,16 @@ function parseSseJsonRpcMessages(text) {
|
|
|
781
842
|
function selectJsonRpcResult(messages) {
|
|
782
843
|
return messages.find((m) => "result" in m || "error" in m) ?? messages[messages.length - 1];
|
|
783
844
|
}
|
|
845
|
+
function x402TypedDataDigest(typedData) {
|
|
846
|
+
if (!typedData || typeof typedData !== "object") return void 0;
|
|
847
|
+
try {
|
|
848
|
+
return hashTypedData(typedData);
|
|
849
|
+
} catch (err) {
|
|
850
|
+
throw new HavenSigningError(
|
|
851
|
+
`The x402 funding intent carried a sign_data.typed_data that is not a valid EIP-712 payload (needs domain, types, primaryType, message), so its digest cannot be derived and no signer could accept it. Underlying error: ${err instanceof Error ? err.message : String(err)}`
|
|
852
|
+
);
|
|
853
|
+
}
|
|
854
|
+
}
|
|
784
855
|
var HavenClient = class {
|
|
785
856
|
apiKey;
|
|
786
857
|
delegateKey;
|
|
@@ -930,6 +1001,11 @@ var HavenClient = class {
|
|
|
930
1001
|
if (!raw.sign_data?.hash) {
|
|
931
1002
|
throw new HavenApiError("No sign_hash returned from x402/authorize", 500, raw);
|
|
932
1003
|
}
|
|
1004
|
+
if (raw.sign_data.signature_scheme !== void 0 && !raw.sign_data.typed_data) {
|
|
1005
|
+
throw new HavenSigningError(
|
|
1006
|
+
`This account's x402 funding intent declares signature scheme '${raw.sign_data.signature_scheme}' but carried no typed_data to sign. Refusing to fall back to the bare hash \u2014 the account would reject that signature on-chain. This is a backend contract violation; report it rather than working around it.`
|
|
1007
|
+
);
|
|
1008
|
+
}
|
|
933
1009
|
if (!raw.x402_expected_auth) {
|
|
934
1010
|
throw new HavenApiError("No x402 expected-context binding returned from x402/authorize", 500, raw);
|
|
935
1011
|
}
|
|
@@ -946,6 +1022,14 @@ var HavenClient = class {
|
|
|
946
1022
|
asset: option.asset,
|
|
947
1023
|
network: option.network,
|
|
948
1024
|
expectedAuth: raw.x402_expected_auth,
|
|
1025
|
+
// #1138: the digest the delegation-rail expected context commits to.
|
|
1026
|
+
// Re-derived locally, exactly like every other context field the edge
|
|
1027
|
+
// signer is handed (amount, merchantTo, …) — none of them are trusted
|
|
1028
|
+
// because they arrived, they are trusted because the reconstructed
|
|
1029
|
+
// message has to match Haven's signature over it. A typed_data altered in
|
|
1030
|
+
// transit therefore fails message equality and is refused, and the signer
|
|
1031
|
+
// re-derives this digest a second time from the payload it actually signs.
|
|
1032
|
+
expectedTypedDataHash: x402TypedDataDigest(raw.sign_data.typed_data),
|
|
949
1033
|
fundingTo
|
|
950
1034
|
};
|
|
951
1035
|
}
|
|
@@ -969,6 +1053,42 @@ var HavenClient = class {
|
|
|
969
1053
|
}
|
|
970
1054
|
return signature;
|
|
971
1055
|
}
|
|
1056
|
+
/**
|
|
1057
|
+
* Sign a payment's `sign_data` with the correct scheme for its rail.
|
|
1058
|
+
*
|
|
1059
|
+
* Dispatching on the server-provided scheme means a caller never has to
|
|
1060
|
+
* know which rail an account is on; an unknown scheme is a hard error,
|
|
1061
|
+
* never a guessed signature. The session rail's 'eip191_userop' is retired
|
|
1062
|
+
* (#834) — the backend refuses those intents with HTTP 410 before any
|
|
1063
|
+
* sign_data reaches a client, so encountering it here is a hard error too.
|
|
1064
|
+
*/
|
|
1065
|
+
async signForData(signData) {
|
|
1066
|
+
if (!this.delegateKey) {
|
|
1067
|
+
throw new HavenSigningError(
|
|
1068
|
+
"Cannot sign without a delegateKey. Pass the private key in HavenClient config, or sign externally."
|
|
1069
|
+
);
|
|
1070
|
+
}
|
|
1071
|
+
const scheme = signData.signature_scheme;
|
|
1072
|
+
if (scheme === "eip191_userop") {
|
|
1073
|
+
throw new HavenSigningError(
|
|
1074
|
+
"The session rail is retired \u2014 'eip191_userop' intents can no longer be signed. Re-onboard the account on the delegation rail."
|
|
1075
|
+
);
|
|
1076
|
+
}
|
|
1077
|
+
if (scheme === "eip712_userop") {
|
|
1078
|
+
if (!signData.typed_data) {
|
|
1079
|
+
throw new HavenSigningError(
|
|
1080
|
+
"sign_data.signature_scheme is eip712_userop but typed_data is missing \u2014 refusing to sign the bare hash (the account would reject it)."
|
|
1081
|
+
);
|
|
1082
|
+
}
|
|
1083
|
+
return signUserOpTypedDataForDelegation(this.delegateKey, signData.typed_data);
|
|
1084
|
+
}
|
|
1085
|
+
if (scheme === void 0) {
|
|
1086
|
+
return signHash(this.delegateKey, signData.hash);
|
|
1087
|
+
}
|
|
1088
|
+
throw new HavenSigningError(
|
|
1089
|
+
`Unknown sign_data.signature_scheme '${scheme}' \u2014 refusing to guess a signing scheme. Update @haven_ai/sdk.`
|
|
1090
|
+
);
|
|
1091
|
+
}
|
|
972
1092
|
/**
|
|
973
1093
|
* Step 3: Submit a signature to execute the payment.
|
|
974
1094
|
*
|
|
@@ -1089,9 +1209,10 @@ var HavenClient = class {
|
|
|
1089
1209
|
}
|
|
1090
1210
|
const ethBalance = await provider.getBalance(delegateAddress);
|
|
1091
1211
|
if (ethBalance > 0n) {
|
|
1092
|
-
const
|
|
1212
|
+
const fee = await provider.getFeeData();
|
|
1213
|
+
const effectiveGasPrice = fee.maxFeePerGas ?? fee.gasPrice ?? 1000000n;
|
|
1093
1214
|
const gasLimit = 21000n;
|
|
1094
|
-
const gasCost =
|
|
1215
|
+
const gasCost = effectiveGasPrice * gasLimit * 2n;
|
|
1095
1216
|
const ethToSend = ethBalance > gasCost ? ethBalance - gasCost : 0n;
|
|
1096
1217
|
if (ethToSend > 0n) {
|
|
1097
1218
|
const tx = await wallet.sendTransaction({ to: safeAddress, value: ethToSend });
|
|
@@ -1204,6 +1325,18 @@ var HavenClient = class {
|
|
|
1204
1325
|
const raw = await this.get(`/machine-payments/receipts${query}`);
|
|
1205
1326
|
return raw.receipts.map((receipt) => this.mapPaymentReceipt(receipt));
|
|
1206
1327
|
}
|
|
1328
|
+
/**
|
|
1329
|
+
* Fetch the verifiable receipt bundle for a settled payment and verify it
|
|
1330
|
+
* locally. The server's own verification is ignored — the receipt is verified
|
|
1331
|
+
* here (independently of Haven) by recovering the signer from the
|
|
1332
|
+
* authorisation, so the result is trustworthy even if the backend lied.
|
|
1333
|
+
*/
|
|
1334
|
+
async getReceipt(paymentId) {
|
|
1335
|
+
const { receipt } = await this.get(
|
|
1336
|
+
`/payments/${paymentId}/receipt`
|
|
1337
|
+
);
|
|
1338
|
+
return { receipt, verification: verifyPaymentReceipt(receipt) };
|
|
1339
|
+
}
|
|
1207
1340
|
/**
|
|
1208
1341
|
* Rehydrate the x402/MPP resume-state bundle for a payment id.
|
|
1209
1342
|
*
|
|
@@ -1346,7 +1479,7 @@ var HavenClient = class {
|
|
|
1346
1479
|
if (!raw.sign_data?.hash) {
|
|
1347
1480
|
throw new HavenApiError("No sign_hash returned from x402/authorize", 500, raw);
|
|
1348
1481
|
}
|
|
1349
|
-
const sig =
|
|
1482
|
+
const sig = await this.signForData(raw.sign_data);
|
|
1350
1483
|
const execResult = await this.post(
|
|
1351
1484
|
`/payments/${raw.payment_id}/sign`,
|
|
1352
1485
|
{ signature: sig }
|
|
@@ -1705,8 +1838,39 @@ var HavenClient = class {
|
|
|
1705
1838
|
protocolReceiptHeaderName: "PAYMENT-RESPONSE",
|
|
1706
1839
|
protocolReceiptHeader: retryResponse.headers.get("PAYMENT-RESPONSE") ?? void 0
|
|
1707
1840
|
});
|
|
1841
|
+
await this.reportMerchantReceipt(receipt.paymentId, retryResponse);
|
|
1708
1842
|
return retryResponse;
|
|
1709
1843
|
}
|
|
1844
|
+
/**
|
|
1845
|
+
* #956: capture the merchant's OWN receipt when the paid response carries
|
|
1846
|
+
* one, and report it to Haven so the reporting feed can attach it next to
|
|
1847
|
+
* the Haven-generated payment evidence (#498). Two supported signals on the
|
|
1848
|
+
* paid response:
|
|
1849
|
+
*
|
|
1850
|
+
* x-receipt-json: base64-encoded JSON receipt document (inline)
|
|
1851
|
+
* x-receipt-url: https URL to the receipt document (reference)
|
|
1852
|
+
*
|
|
1853
|
+
* Strictly best-effort: absence is the normal case, and no failure here may
|
|
1854
|
+
* ever affect the completed payment — the response is already paid for.
|
|
1855
|
+
*/
|
|
1856
|
+
async reportMerchantReceipt(paymentId, response) {
|
|
1857
|
+
try {
|
|
1858
|
+
const inlineB64 = response.headers.get("x-receipt-json");
|
|
1859
|
+
const url = response.headers.get("x-receipt-url");
|
|
1860
|
+
if (!inlineB64 && !url) return;
|
|
1861
|
+
let body = null;
|
|
1862
|
+
if (inlineB64) {
|
|
1863
|
+
if (inlineB64.length > Math.ceil(64 * 1024 * 4 / 3)) return;
|
|
1864
|
+
const decoded = JSON.parse(Buffer.from(inlineB64, "base64").toString("utf8"));
|
|
1865
|
+
if (decoded && typeof decoded === "object") body = { json: decoded };
|
|
1866
|
+
} else if (url && url.startsWith("https://") && url.length <= 2048) {
|
|
1867
|
+
body = { url };
|
|
1868
|
+
}
|
|
1869
|
+
if (!body) return;
|
|
1870
|
+
await this.post(`/machine-payments/${paymentId}/merchant-receipt`, body);
|
|
1871
|
+
} catch {
|
|
1872
|
+
}
|
|
1873
|
+
}
|
|
1710
1874
|
/**
|
|
1711
1875
|
* Deliver an already-signed x402 payment header to the merchant and return
|
|
1712
1876
|
* the merchant's response. Used by the hosted MCP server to complete the
|
|
@@ -1794,6 +1958,7 @@ var HavenClient = class {
|
|
|
1794
1958
|
protocolReceiptHeaderName: protocolReceiptHeader ? "PAYMENT-RESPONSE" : void 0,
|
|
1795
1959
|
protocolReceiptHeader
|
|
1796
1960
|
});
|
|
1961
|
+
await this.reportMerchantReceipt(evidenceContext.paymentId, surfaced);
|
|
1797
1962
|
}
|
|
1798
1963
|
return {
|
|
1799
1964
|
status: surfaced.status,
|
|
@@ -1888,7 +2053,7 @@ var HavenClient = class {
|
|
|
1888
2053
|
if (!raw.sign_data?.hash) {
|
|
1889
2054
|
throw new HavenApiError("No sign_hash returned from machine payment authorization", 500, raw);
|
|
1890
2055
|
}
|
|
1891
|
-
const sig =
|
|
2056
|
+
const sig = await this.signForData(raw.sign_data);
|
|
1892
2057
|
const execResult = await this.post(
|
|
1893
2058
|
`/payments/${raw.payment_id}/sign`,
|
|
1894
2059
|
{ signature: sig }
|
|
@@ -1991,6 +2156,7 @@ var HavenClient = class {
|
|
|
1991
2156
|
protocolReceiptHeaderName: retryResponse.headers.has("Payment-Receipt") ? "Payment-Receipt" : retryResponse.headers.has("MACHINE-PAYMENT-RESPONSE") ? "MACHINE-PAYMENT-RESPONSE" : void 0,
|
|
1992
2157
|
protocolReceiptHeader: retryResponse.headers.get("Payment-Receipt") ?? retryResponse.headers.get("MACHINE-PAYMENT-RESPONSE") ?? void 0
|
|
1993
2158
|
});
|
|
2159
|
+
await this.reportMerchantReceipt(receipt.paymentId, retryResponse);
|
|
1994
2160
|
return retryResponse;
|
|
1995
2161
|
}
|
|
1996
2162
|
assertCanResumeX402(status, paymentRequired, option) {
|
|
@@ -2820,7 +2986,11 @@ var HavenClient = class {
|
|
|
2820
2986
|
});
|
|
2821
2987
|
const data = await res.json();
|
|
2822
2988
|
if (!res.ok) {
|
|
2823
|
-
const
|
|
2989
|
+
const record = data;
|
|
2990
|
+
const errorText = typeof record.error === "string" ? record.error : void 0;
|
|
2991
|
+
const rawDetails = record.details ?? record.detail;
|
|
2992
|
+
const detailsText = typeof rawDetails === "string" ? rawDetails : rawDetails != null ? JSON.stringify(rawDetails) : void 0;
|
|
2993
|
+
const message = errorText && detailsText ? `${errorText}: ${detailsText}` : errorText ?? detailsText ?? `API request failed`;
|
|
2824
2994
|
throw new HavenApiError(message, res.status, data);
|
|
2825
2995
|
}
|
|
2826
2996
|
return data;
|
|
@@ -2848,6 +3018,12 @@ var HavenClient = class {
|
|
|
2848
3018
|
txHash: raw.tx_hash,
|
|
2849
3019
|
errorMessage: raw.error_message,
|
|
2850
3020
|
explorerUrl: raw.explorer_url ?? (raw.tx_hash ? buildExplorerUrl(raw.chain_id, raw.tx_hash) : null),
|
|
3021
|
+
fee: raw.fee ? {
|
|
3022
|
+
amount: raw.fee.amount,
|
|
3023
|
+
token: raw.fee.token,
|
|
3024
|
+
basisPoints: raw.fee.basis_points,
|
|
3025
|
+
applied: raw.fee.applied
|
|
3026
|
+
} : null,
|
|
2851
3027
|
createdAt: raw.created_at,
|
|
2852
3028
|
signedAt: raw.signed_at,
|
|
2853
3029
|
submittedAt: raw.submitted_at,
|
|
@@ -2871,6 +3047,12 @@ var HavenClient = class {
|
|
|
2871
3047
|
expiresAt: raw.expires_at,
|
|
2872
3048
|
chainId: raw.chain_id,
|
|
2873
3049
|
message: raw.message,
|
|
3050
|
+
fee: raw.fee ? {
|
|
3051
|
+
amount: raw.fee.amount,
|
|
3052
|
+
token: raw.fee.token,
|
|
3053
|
+
basisPoints: raw.fee.basis_points,
|
|
3054
|
+
applied: raw.fee.applied
|
|
3055
|
+
} : null,
|
|
2874
3056
|
amountAtomic: raw.amount_atomic ?? raw.x402?.amount_atomic ?? null,
|
|
2875
3057
|
asset: raw.asset ?? raw.x402?.asset ?? null,
|
|
2876
3058
|
network: raw.network ?? raw.x402?.network ?? null,
|
|
@@ -3016,13 +3198,13 @@ var toolDescriptions = {
|
|
|
3016
3198
|
getAgent: {
|
|
3017
3199
|
summary: "Return the authenticated agent identity AND its live spend authority in one call: Haven wallet, delegate, chain, raw status, a readiness signal, and per-token remaining allowance (atomic + human-readable). The recommended first call in a new session to confirm who you are and whether you can pay right now.",
|
|
3018
3200
|
selectionGuidance: "Use this as the one-shot orientation/bootstrap at the start of a session, or whenever you need to confirm identity together with whether the agent can spend right now. For a detailed per-token breakdown (configured vs spent vs reset window) use haven_get_allowances.",
|
|
3019
|
-
behavior: 'Reads identity plus the on-chain AllowanceModule
|
|
3201
|
+
behavior: 'Reads identity plus the live spend-authority snapshot in one shot \u2014 the on-chain AllowanceModule on the legacy rail, the active budget delegation on the delegation rail. readiness is "ready" when at least one token has remaining spend authority, "needs_approval" when the agent is active but has none, and "revoked" when the credential is not active. What an over-budget payment does differs by rail: on the legacy AllowanceModule rail it is queued for the wallet owner to approve in Haven; on the delegation rail there is no approval queue \u2014 an over-budget redemption reverts on-chain, so ask the owner to grant or raise the budget in Haven rather than waiting for an approval. allowances[] carries remainingAtomic and remainingDisplay per token. Identity fields (id, name, status, safeAddress, delegateAddress, chainId) are unchanged from before.',
|
|
3020
3202
|
nextActionGuidance: ""
|
|
3021
3203
|
},
|
|
3022
3204
|
getAllowances: {
|
|
3023
3205
|
summary: "Return configured and on-chain allowance state for the authenticated agent. On-chain allowance is the real spend gate.",
|
|
3024
3206
|
selectionGuidance: "Use this when the user asks about allowance, budget, spend limit, remaining amount, remaining allowance, remaining budget, daily limit, reset period, what can I spend, or what the agent can still spend.",
|
|
3025
|
-
behavior: "
|
|
3207
|
+
behavior: "Returns the per-token spend authority for the account's rail: the Safe AllowanceModule snapshot (allowance, spent, remaining, reset window) on the legacy rail, or the active budget delegation (remaining = the period budget; over-budget redemptions revert on-chain, nothing queues) on the delegation rail. Configured amounts from Haven are returned alongside.",
|
|
3026
3208
|
nextActionGuidance: ""
|
|
3027
3209
|
},
|
|
3028
3210
|
listReceipts: {
|
|
@@ -3031,6 +3213,12 @@ var toolDescriptions = {
|
|
|
3031
3213
|
behavior: "Returns the agent's recent machine-payment receipts ordered by recency. Proof header values are not returned.",
|
|
3032
3214
|
nextActionGuidance: ""
|
|
3033
3215
|
},
|
|
3216
|
+
verifyReceipt: {
|
|
3217
|
+
summary: "Verify a payment receipt offline \u2014 confirm the agent authorised the transfer.",
|
|
3218
|
+
selectionGuidance: "Use this to check a receipt you already hold; it needs no network and does not trust Haven. Use the history tool to fetch receipts in the first place.",
|
|
3219
|
+
behavior: "Recovers the signer from the receipt authorisation and confirms it matches the agent delegate. Returns verified true/false with the recovered signer or a reason. Pure and local \u2014 no backend call.",
|
|
3220
|
+
nextActionGuidance: ""
|
|
3221
|
+
},
|
|
3034
3222
|
payMcpTool: {
|
|
3035
3223
|
summary: "Call a named tool on an MCP merchant that requires an x402 payment, handling the full initialize \u2192 pay \u2192 retry round trip.",
|
|
3036
3224
|
selectionGuidance: "Use this when the agent wants to call a specific tool on an MCP merchant (e.g. Soundside, Coinbase Bazaar) and payment is required. Prefer this over haven_pay_x402 when you know the merchant_url and tool_name \u2014 it builds the JSON-RPC envelope internally. Use haven_pay_x402 for arbitrary HTTP resources. Do NOT use for read-only allowance or budget questions \u2014 use haven_get_allowances.",
|
|
@@ -3420,18 +3608,31 @@ var SKILL_FOLDER_NAME = "haven-pay";
|
|
|
3420
3608
|
|
|
3421
3609
|
// src/sweep.ts
|
|
3422
3610
|
var SWEEP_BASE_CHAIN_ID = 8453;
|
|
3611
|
+
var SWEEP_BASE_SEPOLIA_CHAIN_ID = 84532;
|
|
3423
3612
|
var SWEEP_BASE_USDC_ADDRESS = "0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913";
|
|
3613
|
+
var SWEEP_BASE_SEPOLIA_USDC_ADDRESS = "0x036CbD53842c5426634e7929541eC2318f3dCF7e";
|
|
3424
3614
|
var USDC_EIP712_DOMAIN_BY_CHAIN = {
|
|
3425
3615
|
[SWEEP_BASE_CHAIN_ID]: {
|
|
3426
3616
|
name: "USD Coin",
|
|
3427
3617
|
version: "2",
|
|
3428
3618
|
chainId: SWEEP_BASE_CHAIN_ID,
|
|
3429
3619
|
verifyingContract: SWEEP_BASE_USDC_ADDRESS
|
|
3620
|
+
},
|
|
3621
|
+
[SWEEP_BASE_SEPOLIA_CHAIN_ID]: {
|
|
3622
|
+
name: "USDC",
|
|
3623
|
+
version: "2",
|
|
3624
|
+
chainId: SWEEP_BASE_SEPOLIA_CHAIN_ID,
|
|
3625
|
+
verifyingContract: SWEEP_BASE_SEPOLIA_USDC_ADDRESS
|
|
3430
3626
|
}
|
|
3431
3627
|
};
|
|
3432
3628
|
var USDC_ADDRESS_BY_CHAIN = {
|
|
3433
|
-
[SWEEP_BASE_CHAIN_ID]: SWEEP_BASE_USDC_ADDRESS
|
|
3629
|
+
[SWEEP_BASE_CHAIN_ID]: SWEEP_BASE_USDC_ADDRESS,
|
|
3630
|
+
[SWEEP_BASE_SEPOLIA_CHAIN_ID]: SWEEP_BASE_SEPOLIA_USDC_ADDRESS
|
|
3434
3631
|
};
|
|
3632
|
+
var SWEEPABLE_CHAIN_IDS = Object.keys(USDC_ADDRESS_BY_CHAIN).map(Number);
|
|
3633
|
+
function isSweepableChain(chainId) {
|
|
3634
|
+
return chainId in USDC_ADDRESS_BY_CHAIN;
|
|
3635
|
+
}
|
|
3435
3636
|
var TRANSFER_WITH_AUTHORIZATION_TYPES = {
|
|
3436
3637
|
TransferWithAuthorization: [
|
|
3437
3638
|
{ name: "from", type: "address" },
|
|
@@ -3446,7 +3647,7 @@ function sweepUsdcAddress(chainId) {
|
|
|
3446
3647
|
const address = USDC_ADDRESS_BY_CHAIN[chainId];
|
|
3447
3648
|
if (!address) {
|
|
3448
3649
|
throw new HavenSigningError(
|
|
3449
|
-
`Sweep is
|
|
3650
|
+
`Sweep is not supported on chain ${chainId}. Supported: ${SWEEPABLE_CHAIN_IDS.join(", ")}.`
|
|
3450
3651
|
);
|
|
3451
3652
|
}
|
|
3452
3653
|
return address;
|
|
@@ -3455,7 +3656,7 @@ function sweepUsdcDomain(chainId) {
|
|
|
3455
3656
|
const domain = USDC_EIP712_DOMAIN_BY_CHAIN[chainId];
|
|
3456
3657
|
if (!domain) {
|
|
3457
3658
|
throw new HavenSigningError(
|
|
3458
|
-
`Sweep is
|
|
3659
|
+
`Sweep is not supported on chain ${chainId}. Supported: ${SWEEPABLE_CHAIN_IDS.join(", ")}.`
|
|
3459
3660
|
);
|
|
3460
3661
|
}
|
|
3461
3662
|
return domain;
|
|
@@ -3510,6 +3711,6 @@ function stableStringify2(value) {
|
|
|
3510
3711
|
return `{${Object.keys(object).sort().map((key) => `${JSON.stringify(key)}:${stableStringify2(object[key])}`).join(",")}}`;
|
|
3511
3712
|
}
|
|
3512
3713
|
|
|
3513
|
-
export { AGENT_PAYMENT_FAILURE_CODE_VALUES, AGENT_PAYMENT_NEXT_ACTION_VALUES, AGENT_PAYMENT_PHASE_VALUES, AGENT_PAYMENT_RAIL_VALUES, AgentPaymentFailureCode, AgentPaymentFailureCodeDescriptions, AgentPaymentFailureCodeSchema, AgentPaymentNextAction, AgentPaymentNextActionDescriptions, AgentPaymentNextActionSchema, AgentPaymentPhase, AgentPaymentPhaseDescriptions, AgentPaymentPhaseSchema, AgentPaymentRail, AgentPaymentRailDescriptions, AgentPaymentRailSchema, HAVEN_SKILL_MD, HavenApiError, HavenClient, HavenError, HavenPaymentStateError, HavenSigningError, HavenTimeoutError, SKILL_FOLDER_NAME, SWEEP_BASE_CHAIN_ID, SWEEP_BASE_USDC_ADDRESS, TRANSFER_WITH_AUTHORIZATION_TYPES, addressFromKey, buildMachinePaymentIdempotencyKey, buildSweepAuthorizationMessage, buildSweepTypedData, buildX402ExpectedMessage, composeDescription, decodeBase64Json, decodeBase64Utf8, encodeBase64Json, encodeBase64Utf8, encodeMachinePaymentProof, encodePaymentProof, havenTools, parseMachinePaymentChallenge, parseMachinePaymentChallengeResponse, parsePaymentRequired, parsePaymentRequiredResponse, selectPaymentOption, selectStandardPaymentOption, signHash, sweepUsdcAddress, sweepUsdcDomain, toStandardPaymentRequirements, toolDescriptions, verifySignature, x402AuthorizationAmount };
|
|
3714
|
+
export { AGENT_PAYMENT_FAILURE_CODE_VALUES, AGENT_PAYMENT_NEXT_ACTION_VALUES, AGENT_PAYMENT_PHASE_VALUES, AGENT_PAYMENT_RAIL_VALUES, AgentPaymentFailureCode, AgentPaymentFailureCodeDescriptions, AgentPaymentFailureCodeSchema, AgentPaymentNextAction, AgentPaymentNextActionDescriptions, AgentPaymentNextActionSchema, AgentPaymentPhase, AgentPaymentPhaseDescriptions, AgentPaymentPhaseSchema, AgentPaymentRail, AgentPaymentRailDescriptions, AgentPaymentRailSchema, HAVEN_SKILL_MD, HavenApiError, HavenClient, HavenError, HavenPaymentStateError, HavenSigningError, HavenTimeoutError, RECEIPT_VERSION, SKILL_FOLDER_NAME, SWEEP_BASE_CHAIN_ID, SWEEP_BASE_SEPOLIA_CHAIN_ID, SWEEP_BASE_SEPOLIA_USDC_ADDRESS, SWEEP_BASE_USDC_ADDRESS, TRANSFER_WITH_AUTHORIZATION_TYPES, addressFromKey, buildMachinePaymentIdempotencyKey, buildSweepAuthorizationMessage, buildSweepTypedData, buildX402ExpectedMessage, composeDescription, decodeBase64Json, decodeBase64Utf8, encodeBase64Json, encodeBase64Utf8, encodeMachinePaymentProof, encodePaymentProof, havenTools, isSweepableChain, parseMachinePaymentChallenge, parseMachinePaymentChallengeResponse, parsePaymentRequired, parsePaymentRequiredResponse, selectPaymentOption, selectStandardPaymentOption, signHash, signUserOpTypedDataForDelegation, sweepUsdcAddress, sweepUsdcDomain, toStandardPaymentRequirements, toolDescriptions, verifyPaymentReceipt, verifySignature, x402AuthorizationAmount };
|
|
3514
3715
|
//# sourceMappingURL=index.js.map
|
|
3515
3716
|
//# sourceMappingURL=index.js.map
|