@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.cjs
CHANGED
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
var async_hooks = require('async_hooks');
|
|
4
4
|
var schemes = require('x402/schemes');
|
|
5
|
+
var viem = require('viem');
|
|
5
6
|
var accounts = require('viem/accounts');
|
|
6
7
|
var ethers = require('ethers');
|
|
7
8
|
var crypto = require('crypto');
|
|
@@ -242,6 +243,22 @@ function signHash(privateKey, hash) {
|
|
|
242
243
|
);
|
|
243
244
|
}
|
|
244
245
|
}
|
|
246
|
+
async function signUserOpTypedDataForDelegation(privateKey, typedData) {
|
|
247
|
+
try {
|
|
248
|
+
const wallet = new ethers.ethers.Wallet(privateKey);
|
|
249
|
+
const types = { ...typedData.types };
|
|
250
|
+
delete types.EIP712Domain;
|
|
251
|
+
return await wallet.signTypedData(
|
|
252
|
+
typedData.domain,
|
|
253
|
+
types,
|
|
254
|
+
typedData.message
|
|
255
|
+
);
|
|
256
|
+
} catch (err) {
|
|
257
|
+
throw new HavenSigningError(
|
|
258
|
+
`Failed to sign delegation UserOperation: ${err instanceof Error ? err.message : String(err)}`
|
|
259
|
+
);
|
|
260
|
+
}
|
|
261
|
+
}
|
|
245
262
|
function addressFromKey(privateKey) {
|
|
246
263
|
try {
|
|
247
264
|
return new ethers.ethers.Wallet(privateKey).address;
|
|
@@ -259,6 +276,24 @@ function verifySignature(hash, signature, expectedAddress) {
|
|
|
259
276
|
return false;
|
|
260
277
|
}
|
|
261
278
|
}
|
|
279
|
+
var RECEIPT_VERSION = "haven-receipt-1";
|
|
280
|
+
function defaultRecover(hash, signature) {
|
|
281
|
+
return ethers.ethers.recoverAddress(hash, signature);
|
|
282
|
+
}
|
|
283
|
+
function verifyPaymentReceipt(receipt, recover = defaultRecover) {
|
|
284
|
+
const { delegate, signHash: signHash2, signature } = receipt.authorization;
|
|
285
|
+
if (!signature) return { verified: false, reason: "missing_signature" };
|
|
286
|
+
let recovered;
|
|
287
|
+
try {
|
|
288
|
+
recovered = recover(signHash2, signature);
|
|
289
|
+
} catch {
|
|
290
|
+
return { verified: false, reason: "bad_signature" };
|
|
291
|
+
}
|
|
292
|
+
if (recovered.toLowerCase() !== delegate.toLowerCase()) {
|
|
293
|
+
return { verified: false, reason: "signer_mismatch", recoveredSigner: recovered };
|
|
294
|
+
}
|
|
295
|
+
return { verified: true, recoveredSigner: recovered };
|
|
296
|
+
}
|
|
262
297
|
|
|
263
298
|
// src/base64.ts
|
|
264
299
|
function normalizeBase64(value) {
|
|
@@ -303,6 +338,8 @@ function decodeBase64Json(value, label) {
|
|
|
303
338
|
|
|
304
339
|
// src/x402.ts
|
|
305
340
|
var BASE_USDC_ADDRESS = "0x833589fcd6edb6e08f4c7c32d4f71b54bda02913";
|
|
341
|
+
var BASE_SEPOLIA_USDC_ADDRESS = "0x036cbd53842c5426634e7929541ec2318f3dcf7e";
|
|
342
|
+
var STANDARD_X402_USDC_ADDRESSES = /* @__PURE__ */ new Set([BASE_USDC_ADDRESS, BASE_SEPOLIA_USDC_ADDRESS]);
|
|
306
343
|
var X402_IDEMPOTENCY_BUCKET_MS = 3e5;
|
|
307
344
|
var DECIMAL_ATOMIC_AMOUNT_RE = /^[0-9]+$/;
|
|
308
345
|
function isPositiveDecimalAtomicAmount(value) {
|
|
@@ -311,6 +348,11 @@ function isPositiveDecimalAtomicAmount(value) {
|
|
|
311
348
|
function optionAuthorizationAmount(option) {
|
|
312
349
|
return option.maxAmountRequired ?? option.amount;
|
|
313
350
|
}
|
|
351
|
+
var X402_MAX_AUTHORIZATION_WINDOW_SECONDS = 600;
|
|
352
|
+
function clampAuthorizationWindow(seconds) {
|
|
353
|
+
const requested = typeof seconds === "number" && Number.isFinite(seconds) ? seconds : 30;
|
|
354
|
+
return Math.min(Math.max(Math.floor(requested), 1), X402_MAX_AUTHORIZATION_WINDOW_SECONDS);
|
|
355
|
+
}
|
|
314
356
|
function normalizePaymentOption(value) {
|
|
315
357
|
const candidate = value;
|
|
316
358
|
if (!candidate || typeof candidate !== "object" || typeof candidate.scheme !== "string" || typeof candidate.network !== "string" || typeof candidate.asset !== "string" || typeof candidate.payTo !== "string") {
|
|
@@ -332,7 +374,7 @@ function normalizePaymentOption(value) {
|
|
|
332
374
|
mimeType: candidate.mimeType,
|
|
333
375
|
asset: candidate.asset,
|
|
334
376
|
payTo: candidate.payTo,
|
|
335
|
-
maxTimeoutSeconds: candidate.maxTimeoutSeconds
|
|
377
|
+
maxTimeoutSeconds: clampAuthorizationWindow(candidate.maxTimeoutSeconds),
|
|
336
378
|
extra: candidate.extra
|
|
337
379
|
};
|
|
338
380
|
}
|
|
@@ -362,11 +404,15 @@ function normalizePaymentRequired(value) {
|
|
|
362
404
|
var SUPPORTED_X402_NETWORKS = {
|
|
363
405
|
"eip155:100": "Gnosis Chain",
|
|
364
406
|
"eip155:8453": "Base",
|
|
365
|
-
"base": "Base"
|
|
407
|
+
"base": "Base",
|
|
408
|
+
"eip155:84532": "Base Sepolia",
|
|
409
|
+
"base-sepolia": "Base Sepolia"
|
|
366
410
|
};
|
|
367
411
|
var STANDARD_X402_NETWORKS = {
|
|
368
412
|
"eip155:8453": "base",
|
|
369
|
-
"base": "base"
|
|
413
|
+
"base": "base",
|
|
414
|
+
"eip155:84532": "base-sepolia",
|
|
415
|
+
"base-sepolia": "base-sepolia"
|
|
370
416
|
};
|
|
371
417
|
var GNOSIS_TOKENS = {
|
|
372
418
|
"0x0000000000000000000000000000000000000000": { symbol: "xDAI", decimals: 18 },
|
|
@@ -377,14 +423,21 @@ var BASE_TOKENS = {
|
|
|
377
423
|
"0x0000000000000000000000000000000000000000": { symbol: "ETH", decimals: 18 },
|
|
378
424
|
"0x833589fcd6edb6e08f4c7c32d4f71b54bda02913": { symbol: "USDC", decimals: 6 }
|
|
379
425
|
};
|
|
426
|
+
var BASE_SEPOLIA_TOKENS = {
|
|
427
|
+
"0x0000000000000000000000000000000000000000": { symbol: "ETH", decimals: 18 },
|
|
428
|
+
"0x036cbd53842c5426634e7929541ec2318f3dcf7e": { symbol: "USDC", decimals: 6 }
|
|
429
|
+
};
|
|
380
430
|
var ALL_TOKENS = {
|
|
381
431
|
...GNOSIS_TOKENS,
|
|
382
|
-
...BASE_TOKENS
|
|
432
|
+
...BASE_TOKENS,
|
|
433
|
+
...BASE_SEPOLIA_TOKENS
|
|
383
434
|
};
|
|
384
435
|
var NETWORK_TOKENS = {
|
|
385
436
|
"eip155:100": GNOSIS_TOKENS,
|
|
386
437
|
"eip155:8453": BASE_TOKENS,
|
|
387
|
-
"base": BASE_TOKENS
|
|
438
|
+
"base": BASE_TOKENS,
|
|
439
|
+
"eip155:84532": BASE_SEPOLIA_TOKENS,
|
|
440
|
+
"base-sepolia": BASE_SEPOLIA_TOKENS
|
|
388
441
|
};
|
|
389
442
|
function parsePaymentRequired(response) {
|
|
390
443
|
const v2Header = response.headers.get("PAYMENT-REQUIRED");
|
|
@@ -438,7 +491,7 @@ function selectPaymentOption(accepts) {
|
|
|
438
491
|
function selectStandardPaymentOption(accepts) {
|
|
439
492
|
if (!accepts || accepts.length === 0) return null;
|
|
440
493
|
for (const opt of accepts) {
|
|
441
|
-
if (opt.scheme === "exact" && opt.network in STANDARD_X402_NETWORKS && opt.asset.toLowerCase()
|
|
494
|
+
if (opt.scheme === "exact" && opt.network in STANDARD_X402_NETWORKS && STANDARD_X402_USDC_ADDRESSES.has(opt.asset.toLowerCase()) && isPositiveDecimalAtomicAmount(optionAuthorizationAmount(opt))) {
|
|
442
495
|
return opt;
|
|
443
496
|
}
|
|
444
497
|
}
|
|
@@ -452,8 +505,9 @@ function x402AuthorizationAmount(option) {
|
|
|
452
505
|
return amount;
|
|
453
506
|
}
|
|
454
507
|
function buildX402ExpectedMessage(context) {
|
|
508
|
+
const version = context.typedDataHash ? 2 : 1;
|
|
455
509
|
const payload = {
|
|
456
|
-
version
|
|
510
|
+
version,
|
|
457
511
|
kind: "haven.x402.expected",
|
|
458
512
|
paymentId: context.paymentId,
|
|
459
513
|
payloadHash: context.payloadHash.toLowerCase(),
|
|
@@ -466,7 +520,10 @@ function buildX402ExpectedMessage(context) {
|
|
|
466
520
|
if (context.expiresAt) {
|
|
467
521
|
payload.expiresAt = context.expiresAt;
|
|
468
522
|
}
|
|
469
|
-
|
|
523
|
+
if (context.typedDataHash) {
|
|
524
|
+
payload.typedDataHash = context.typedDataHash.toLowerCase();
|
|
525
|
+
}
|
|
526
|
+
return `Haven x402 expected context v${version}
|
|
470
527
|
${stableStringify(payload)}`;
|
|
471
528
|
}
|
|
472
529
|
function toStandardPaymentRequirements(paymentRequired, option) {
|
|
@@ -486,7 +543,10 @@ function toStandardPaymentRequirements(paymentRequired, option) {
|
|
|
486
543
|
mimeType: option.mimeType ?? paymentRequired.resource.mimeType ?? "application/octet-stream",
|
|
487
544
|
payTo: option.payTo,
|
|
488
545
|
asset: option.asset,
|
|
489
|
-
|
|
546
|
+
// Second enforcement point (#715): the parse path clamps too, but this is
|
|
547
|
+
// the last stop before the x402 library turns the timeout into
|
|
548
|
+
// `validBefore` — options constructed without parsing are bounded here.
|
|
549
|
+
maxTimeoutSeconds: clampAuthorizationWindow(option.maxTimeoutSeconds),
|
|
490
550
|
extra: option.extra
|
|
491
551
|
};
|
|
492
552
|
}
|
|
@@ -620,7 +680,8 @@ var CHAIN_EXPLORER_TX = {
|
|
|
620
680
|
8453: "https://basescan.org/tx"
|
|
621
681
|
};
|
|
622
682
|
var CHAIN_USDC = {
|
|
623
|
-
8453: "0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913"
|
|
683
|
+
8453: "0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913",
|
|
684
|
+
84532: "0x036CbD53842c5426634e7929541eC2318f3dCF7e"
|
|
624
685
|
};
|
|
625
686
|
function buildExplorerUrl(chainId, txHash) {
|
|
626
687
|
const base = CHAIN_EXPLORER_TX[chainId ?? 8453] ?? CHAIN_EXPLORER_TX[8453];
|
|
@@ -783,6 +844,16 @@ function parseSseJsonRpcMessages(text) {
|
|
|
783
844
|
function selectJsonRpcResult(messages) {
|
|
784
845
|
return messages.find((m) => "result" in m || "error" in m) ?? messages[messages.length - 1];
|
|
785
846
|
}
|
|
847
|
+
function x402TypedDataDigest(typedData) {
|
|
848
|
+
if (!typedData || typeof typedData !== "object") return void 0;
|
|
849
|
+
try {
|
|
850
|
+
return viem.hashTypedData(typedData);
|
|
851
|
+
} catch (err) {
|
|
852
|
+
throw new HavenSigningError(
|
|
853
|
+
`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)}`
|
|
854
|
+
);
|
|
855
|
+
}
|
|
856
|
+
}
|
|
786
857
|
var HavenClient = class {
|
|
787
858
|
apiKey;
|
|
788
859
|
delegateKey;
|
|
@@ -932,6 +1003,11 @@ var HavenClient = class {
|
|
|
932
1003
|
if (!raw.sign_data?.hash) {
|
|
933
1004
|
throw new HavenApiError("No sign_hash returned from x402/authorize", 500, raw);
|
|
934
1005
|
}
|
|
1006
|
+
if (raw.sign_data.signature_scheme !== void 0 && !raw.sign_data.typed_data) {
|
|
1007
|
+
throw new HavenSigningError(
|
|
1008
|
+
`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.`
|
|
1009
|
+
);
|
|
1010
|
+
}
|
|
935
1011
|
if (!raw.x402_expected_auth) {
|
|
936
1012
|
throw new HavenApiError("No x402 expected-context binding returned from x402/authorize", 500, raw);
|
|
937
1013
|
}
|
|
@@ -948,6 +1024,14 @@ var HavenClient = class {
|
|
|
948
1024
|
asset: option.asset,
|
|
949
1025
|
network: option.network,
|
|
950
1026
|
expectedAuth: raw.x402_expected_auth,
|
|
1027
|
+
// #1138: the digest the delegation-rail expected context commits to.
|
|
1028
|
+
// Re-derived locally, exactly like every other context field the edge
|
|
1029
|
+
// signer is handed (amount, merchantTo, …) — none of them are trusted
|
|
1030
|
+
// because they arrived, they are trusted because the reconstructed
|
|
1031
|
+
// message has to match Haven's signature over it. A typed_data altered in
|
|
1032
|
+
// transit therefore fails message equality and is refused, and the signer
|
|
1033
|
+
// re-derives this digest a second time from the payload it actually signs.
|
|
1034
|
+
expectedTypedDataHash: x402TypedDataDigest(raw.sign_data.typed_data),
|
|
951
1035
|
fundingTo
|
|
952
1036
|
};
|
|
953
1037
|
}
|
|
@@ -971,6 +1055,42 @@ var HavenClient = class {
|
|
|
971
1055
|
}
|
|
972
1056
|
return signature;
|
|
973
1057
|
}
|
|
1058
|
+
/**
|
|
1059
|
+
* Sign a payment's `sign_data` with the correct scheme for its rail.
|
|
1060
|
+
*
|
|
1061
|
+
* Dispatching on the server-provided scheme means a caller never has to
|
|
1062
|
+
* know which rail an account is on; an unknown scheme is a hard error,
|
|
1063
|
+
* never a guessed signature. The session rail's 'eip191_userop' is retired
|
|
1064
|
+
* (#834) — the backend refuses those intents with HTTP 410 before any
|
|
1065
|
+
* sign_data reaches a client, so encountering it here is a hard error too.
|
|
1066
|
+
*/
|
|
1067
|
+
async signForData(signData) {
|
|
1068
|
+
if (!this.delegateKey) {
|
|
1069
|
+
throw new HavenSigningError(
|
|
1070
|
+
"Cannot sign without a delegateKey. Pass the private key in HavenClient config, or sign externally."
|
|
1071
|
+
);
|
|
1072
|
+
}
|
|
1073
|
+
const scheme = signData.signature_scheme;
|
|
1074
|
+
if (scheme === "eip191_userop") {
|
|
1075
|
+
throw new HavenSigningError(
|
|
1076
|
+
"The session rail is retired \u2014 'eip191_userop' intents can no longer be signed. Re-onboard the account on the delegation rail."
|
|
1077
|
+
);
|
|
1078
|
+
}
|
|
1079
|
+
if (scheme === "eip712_userop") {
|
|
1080
|
+
if (!signData.typed_data) {
|
|
1081
|
+
throw new HavenSigningError(
|
|
1082
|
+
"sign_data.signature_scheme is eip712_userop but typed_data is missing \u2014 refusing to sign the bare hash (the account would reject it)."
|
|
1083
|
+
);
|
|
1084
|
+
}
|
|
1085
|
+
return signUserOpTypedDataForDelegation(this.delegateKey, signData.typed_data);
|
|
1086
|
+
}
|
|
1087
|
+
if (scheme === void 0) {
|
|
1088
|
+
return signHash(this.delegateKey, signData.hash);
|
|
1089
|
+
}
|
|
1090
|
+
throw new HavenSigningError(
|
|
1091
|
+
`Unknown sign_data.signature_scheme '${scheme}' \u2014 refusing to guess a signing scheme. Update @haven_ai/sdk.`
|
|
1092
|
+
);
|
|
1093
|
+
}
|
|
974
1094
|
/**
|
|
975
1095
|
* Step 3: Submit a signature to execute the payment.
|
|
976
1096
|
*
|
|
@@ -1091,9 +1211,10 @@ var HavenClient = class {
|
|
|
1091
1211
|
}
|
|
1092
1212
|
const ethBalance = await provider.getBalance(delegateAddress);
|
|
1093
1213
|
if (ethBalance > 0n) {
|
|
1094
|
-
const
|
|
1214
|
+
const fee = await provider.getFeeData();
|
|
1215
|
+
const effectiveGasPrice = fee.maxFeePerGas ?? fee.gasPrice ?? 1000000n;
|
|
1095
1216
|
const gasLimit = 21000n;
|
|
1096
|
-
const gasCost =
|
|
1217
|
+
const gasCost = effectiveGasPrice * gasLimit * 2n;
|
|
1097
1218
|
const ethToSend = ethBalance > gasCost ? ethBalance - gasCost : 0n;
|
|
1098
1219
|
if (ethToSend > 0n) {
|
|
1099
1220
|
const tx = await wallet.sendTransaction({ to: safeAddress, value: ethToSend });
|
|
@@ -1206,6 +1327,18 @@ var HavenClient = class {
|
|
|
1206
1327
|
const raw = await this.get(`/machine-payments/receipts${query}`);
|
|
1207
1328
|
return raw.receipts.map((receipt) => this.mapPaymentReceipt(receipt));
|
|
1208
1329
|
}
|
|
1330
|
+
/**
|
|
1331
|
+
* Fetch the verifiable receipt bundle for a settled payment and verify it
|
|
1332
|
+
* locally. The server's own verification is ignored — the receipt is verified
|
|
1333
|
+
* here (independently of Haven) by recovering the signer from the
|
|
1334
|
+
* authorisation, so the result is trustworthy even if the backend lied.
|
|
1335
|
+
*/
|
|
1336
|
+
async getReceipt(paymentId) {
|
|
1337
|
+
const { receipt } = await this.get(
|
|
1338
|
+
`/payments/${paymentId}/receipt`
|
|
1339
|
+
);
|
|
1340
|
+
return { receipt, verification: verifyPaymentReceipt(receipt) };
|
|
1341
|
+
}
|
|
1209
1342
|
/**
|
|
1210
1343
|
* Rehydrate the x402/MPP resume-state bundle for a payment id.
|
|
1211
1344
|
*
|
|
@@ -1348,7 +1481,7 @@ var HavenClient = class {
|
|
|
1348
1481
|
if (!raw.sign_data?.hash) {
|
|
1349
1482
|
throw new HavenApiError("No sign_hash returned from x402/authorize", 500, raw);
|
|
1350
1483
|
}
|
|
1351
|
-
const sig =
|
|
1484
|
+
const sig = await this.signForData(raw.sign_data);
|
|
1352
1485
|
const execResult = await this.post(
|
|
1353
1486
|
`/payments/${raw.payment_id}/sign`,
|
|
1354
1487
|
{ signature: sig }
|
|
@@ -1707,8 +1840,39 @@ var HavenClient = class {
|
|
|
1707
1840
|
protocolReceiptHeaderName: "PAYMENT-RESPONSE",
|
|
1708
1841
|
protocolReceiptHeader: retryResponse.headers.get("PAYMENT-RESPONSE") ?? void 0
|
|
1709
1842
|
});
|
|
1843
|
+
await this.reportMerchantReceipt(receipt.paymentId, retryResponse);
|
|
1710
1844
|
return retryResponse;
|
|
1711
1845
|
}
|
|
1846
|
+
/**
|
|
1847
|
+
* #956: capture the merchant's OWN receipt when the paid response carries
|
|
1848
|
+
* one, and report it to Haven so the reporting feed can attach it next to
|
|
1849
|
+
* the Haven-generated payment evidence (#498). Two supported signals on the
|
|
1850
|
+
* paid response:
|
|
1851
|
+
*
|
|
1852
|
+
* x-receipt-json: base64-encoded JSON receipt document (inline)
|
|
1853
|
+
* x-receipt-url: https URL to the receipt document (reference)
|
|
1854
|
+
*
|
|
1855
|
+
* Strictly best-effort: absence is the normal case, and no failure here may
|
|
1856
|
+
* ever affect the completed payment — the response is already paid for.
|
|
1857
|
+
*/
|
|
1858
|
+
async reportMerchantReceipt(paymentId, response) {
|
|
1859
|
+
try {
|
|
1860
|
+
const inlineB64 = response.headers.get("x-receipt-json");
|
|
1861
|
+
const url = response.headers.get("x-receipt-url");
|
|
1862
|
+
if (!inlineB64 && !url) return;
|
|
1863
|
+
let body = null;
|
|
1864
|
+
if (inlineB64) {
|
|
1865
|
+
if (inlineB64.length > Math.ceil(64 * 1024 * 4 / 3)) return;
|
|
1866
|
+
const decoded = JSON.parse(Buffer.from(inlineB64, "base64").toString("utf8"));
|
|
1867
|
+
if (decoded && typeof decoded === "object") body = { json: decoded };
|
|
1868
|
+
} else if (url && url.startsWith("https://") && url.length <= 2048) {
|
|
1869
|
+
body = { url };
|
|
1870
|
+
}
|
|
1871
|
+
if (!body) return;
|
|
1872
|
+
await this.post(`/machine-payments/${paymentId}/merchant-receipt`, body);
|
|
1873
|
+
} catch {
|
|
1874
|
+
}
|
|
1875
|
+
}
|
|
1712
1876
|
/**
|
|
1713
1877
|
* Deliver an already-signed x402 payment header to the merchant and return
|
|
1714
1878
|
* the merchant's response. Used by the hosted MCP server to complete the
|
|
@@ -1796,6 +1960,7 @@ var HavenClient = class {
|
|
|
1796
1960
|
protocolReceiptHeaderName: protocolReceiptHeader ? "PAYMENT-RESPONSE" : void 0,
|
|
1797
1961
|
protocolReceiptHeader
|
|
1798
1962
|
});
|
|
1963
|
+
await this.reportMerchantReceipt(evidenceContext.paymentId, surfaced);
|
|
1799
1964
|
}
|
|
1800
1965
|
return {
|
|
1801
1966
|
status: surfaced.status,
|
|
@@ -1890,7 +2055,7 @@ var HavenClient = class {
|
|
|
1890
2055
|
if (!raw.sign_data?.hash) {
|
|
1891
2056
|
throw new HavenApiError("No sign_hash returned from machine payment authorization", 500, raw);
|
|
1892
2057
|
}
|
|
1893
|
-
const sig =
|
|
2058
|
+
const sig = await this.signForData(raw.sign_data);
|
|
1894
2059
|
const execResult = await this.post(
|
|
1895
2060
|
`/payments/${raw.payment_id}/sign`,
|
|
1896
2061
|
{ signature: sig }
|
|
@@ -1993,6 +2158,7 @@ var HavenClient = class {
|
|
|
1993
2158
|
protocolReceiptHeaderName: retryResponse.headers.has("Payment-Receipt") ? "Payment-Receipt" : retryResponse.headers.has("MACHINE-PAYMENT-RESPONSE") ? "MACHINE-PAYMENT-RESPONSE" : void 0,
|
|
1994
2159
|
protocolReceiptHeader: retryResponse.headers.get("Payment-Receipt") ?? retryResponse.headers.get("MACHINE-PAYMENT-RESPONSE") ?? void 0
|
|
1995
2160
|
});
|
|
2161
|
+
await this.reportMerchantReceipt(receipt.paymentId, retryResponse);
|
|
1996
2162
|
return retryResponse;
|
|
1997
2163
|
}
|
|
1998
2164
|
assertCanResumeX402(status, paymentRequired, option) {
|
|
@@ -2822,7 +2988,11 @@ var HavenClient = class {
|
|
|
2822
2988
|
});
|
|
2823
2989
|
const data = await res.json();
|
|
2824
2990
|
if (!res.ok) {
|
|
2825
|
-
const
|
|
2991
|
+
const record = data;
|
|
2992
|
+
const errorText = typeof record.error === "string" ? record.error : void 0;
|
|
2993
|
+
const rawDetails = record.details ?? record.detail;
|
|
2994
|
+
const detailsText = typeof rawDetails === "string" ? rawDetails : rawDetails != null ? JSON.stringify(rawDetails) : void 0;
|
|
2995
|
+
const message = errorText && detailsText ? `${errorText}: ${detailsText}` : errorText ?? detailsText ?? `API request failed`;
|
|
2826
2996
|
throw new HavenApiError(message, res.status, data);
|
|
2827
2997
|
}
|
|
2828
2998
|
return data;
|
|
@@ -2850,6 +3020,12 @@ var HavenClient = class {
|
|
|
2850
3020
|
txHash: raw.tx_hash,
|
|
2851
3021
|
errorMessage: raw.error_message,
|
|
2852
3022
|
explorerUrl: raw.explorer_url ?? (raw.tx_hash ? buildExplorerUrl(raw.chain_id, raw.tx_hash) : null),
|
|
3023
|
+
fee: raw.fee ? {
|
|
3024
|
+
amount: raw.fee.amount,
|
|
3025
|
+
token: raw.fee.token,
|
|
3026
|
+
basisPoints: raw.fee.basis_points,
|
|
3027
|
+
applied: raw.fee.applied
|
|
3028
|
+
} : null,
|
|
2853
3029
|
createdAt: raw.created_at,
|
|
2854
3030
|
signedAt: raw.signed_at,
|
|
2855
3031
|
submittedAt: raw.submitted_at,
|
|
@@ -2873,6 +3049,12 @@ var HavenClient = class {
|
|
|
2873
3049
|
expiresAt: raw.expires_at,
|
|
2874
3050
|
chainId: raw.chain_id,
|
|
2875
3051
|
message: raw.message,
|
|
3052
|
+
fee: raw.fee ? {
|
|
3053
|
+
amount: raw.fee.amount,
|
|
3054
|
+
token: raw.fee.token,
|
|
3055
|
+
basisPoints: raw.fee.basis_points,
|
|
3056
|
+
applied: raw.fee.applied
|
|
3057
|
+
} : null,
|
|
2876
3058
|
amountAtomic: raw.amount_atomic ?? raw.x402?.amount_atomic ?? null,
|
|
2877
3059
|
asset: raw.asset ?? raw.x402?.asset ?? null,
|
|
2878
3060
|
network: raw.network ?? raw.x402?.network ?? null,
|
|
@@ -3018,13 +3200,13 @@ var toolDescriptions = {
|
|
|
3018
3200
|
getAgent: {
|
|
3019
3201
|
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.",
|
|
3020
3202
|
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.",
|
|
3021
|
-
behavior: 'Reads identity plus the on-chain AllowanceModule
|
|
3203
|
+
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.',
|
|
3022
3204
|
nextActionGuidance: ""
|
|
3023
3205
|
},
|
|
3024
3206
|
getAllowances: {
|
|
3025
3207
|
summary: "Return configured and on-chain allowance state for the authenticated agent. On-chain allowance is the real spend gate.",
|
|
3026
3208
|
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.",
|
|
3027
|
-
behavior: "
|
|
3209
|
+
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.",
|
|
3028
3210
|
nextActionGuidance: ""
|
|
3029
3211
|
},
|
|
3030
3212
|
listReceipts: {
|
|
@@ -3033,6 +3215,12 @@ var toolDescriptions = {
|
|
|
3033
3215
|
behavior: "Returns the agent's recent machine-payment receipts ordered by recency. Proof header values are not returned.",
|
|
3034
3216
|
nextActionGuidance: ""
|
|
3035
3217
|
},
|
|
3218
|
+
verifyReceipt: {
|
|
3219
|
+
summary: "Verify a payment receipt offline \u2014 confirm the agent authorised the transfer.",
|
|
3220
|
+
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.",
|
|
3221
|
+
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.",
|
|
3222
|
+
nextActionGuidance: ""
|
|
3223
|
+
},
|
|
3036
3224
|
payMcpTool: {
|
|
3037
3225
|
summary: "Call a named tool on an MCP merchant that requires an x402 payment, handling the full initialize \u2192 pay \u2192 retry round trip.",
|
|
3038
3226
|
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.",
|
|
@@ -3422,18 +3610,31 @@ var SKILL_FOLDER_NAME = "haven-pay";
|
|
|
3422
3610
|
|
|
3423
3611
|
// src/sweep.ts
|
|
3424
3612
|
var SWEEP_BASE_CHAIN_ID = 8453;
|
|
3613
|
+
var SWEEP_BASE_SEPOLIA_CHAIN_ID = 84532;
|
|
3425
3614
|
var SWEEP_BASE_USDC_ADDRESS = "0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913";
|
|
3615
|
+
var SWEEP_BASE_SEPOLIA_USDC_ADDRESS = "0x036CbD53842c5426634e7929541eC2318f3dCF7e";
|
|
3426
3616
|
var USDC_EIP712_DOMAIN_BY_CHAIN = {
|
|
3427
3617
|
[SWEEP_BASE_CHAIN_ID]: {
|
|
3428
3618
|
name: "USD Coin",
|
|
3429
3619
|
version: "2",
|
|
3430
3620
|
chainId: SWEEP_BASE_CHAIN_ID,
|
|
3431
3621
|
verifyingContract: SWEEP_BASE_USDC_ADDRESS
|
|
3622
|
+
},
|
|
3623
|
+
[SWEEP_BASE_SEPOLIA_CHAIN_ID]: {
|
|
3624
|
+
name: "USDC",
|
|
3625
|
+
version: "2",
|
|
3626
|
+
chainId: SWEEP_BASE_SEPOLIA_CHAIN_ID,
|
|
3627
|
+
verifyingContract: SWEEP_BASE_SEPOLIA_USDC_ADDRESS
|
|
3432
3628
|
}
|
|
3433
3629
|
};
|
|
3434
3630
|
var USDC_ADDRESS_BY_CHAIN = {
|
|
3435
|
-
[SWEEP_BASE_CHAIN_ID]: SWEEP_BASE_USDC_ADDRESS
|
|
3631
|
+
[SWEEP_BASE_CHAIN_ID]: SWEEP_BASE_USDC_ADDRESS,
|
|
3632
|
+
[SWEEP_BASE_SEPOLIA_CHAIN_ID]: SWEEP_BASE_SEPOLIA_USDC_ADDRESS
|
|
3436
3633
|
};
|
|
3634
|
+
var SWEEPABLE_CHAIN_IDS = Object.keys(USDC_ADDRESS_BY_CHAIN).map(Number);
|
|
3635
|
+
function isSweepableChain(chainId) {
|
|
3636
|
+
return chainId in USDC_ADDRESS_BY_CHAIN;
|
|
3637
|
+
}
|
|
3437
3638
|
var TRANSFER_WITH_AUTHORIZATION_TYPES = {
|
|
3438
3639
|
TransferWithAuthorization: [
|
|
3439
3640
|
{ name: "from", type: "address" },
|
|
@@ -3448,7 +3649,7 @@ function sweepUsdcAddress(chainId) {
|
|
|
3448
3649
|
const address = USDC_ADDRESS_BY_CHAIN[chainId];
|
|
3449
3650
|
if (!address) {
|
|
3450
3651
|
throw new HavenSigningError(
|
|
3451
|
-
`Sweep is
|
|
3652
|
+
`Sweep is not supported on chain ${chainId}. Supported: ${SWEEPABLE_CHAIN_IDS.join(", ")}.`
|
|
3452
3653
|
);
|
|
3453
3654
|
}
|
|
3454
3655
|
return address;
|
|
@@ -3457,7 +3658,7 @@ function sweepUsdcDomain(chainId) {
|
|
|
3457
3658
|
const domain = USDC_EIP712_DOMAIN_BY_CHAIN[chainId];
|
|
3458
3659
|
if (!domain) {
|
|
3459
3660
|
throw new HavenSigningError(
|
|
3460
|
-
`Sweep is
|
|
3661
|
+
`Sweep is not supported on chain ${chainId}. Supported: ${SWEEPABLE_CHAIN_IDS.join(", ")}.`
|
|
3461
3662
|
);
|
|
3462
3663
|
}
|
|
3463
3664
|
return domain;
|
|
@@ -3535,8 +3736,11 @@ exports.HavenError = HavenError;
|
|
|
3535
3736
|
exports.HavenPaymentStateError = HavenPaymentStateError;
|
|
3536
3737
|
exports.HavenSigningError = HavenSigningError;
|
|
3537
3738
|
exports.HavenTimeoutError = HavenTimeoutError;
|
|
3739
|
+
exports.RECEIPT_VERSION = RECEIPT_VERSION;
|
|
3538
3740
|
exports.SKILL_FOLDER_NAME = SKILL_FOLDER_NAME;
|
|
3539
3741
|
exports.SWEEP_BASE_CHAIN_ID = SWEEP_BASE_CHAIN_ID;
|
|
3742
|
+
exports.SWEEP_BASE_SEPOLIA_CHAIN_ID = SWEEP_BASE_SEPOLIA_CHAIN_ID;
|
|
3743
|
+
exports.SWEEP_BASE_SEPOLIA_USDC_ADDRESS = SWEEP_BASE_SEPOLIA_USDC_ADDRESS;
|
|
3540
3744
|
exports.SWEEP_BASE_USDC_ADDRESS = SWEEP_BASE_USDC_ADDRESS;
|
|
3541
3745
|
exports.TRANSFER_WITH_AUTHORIZATION_TYPES = TRANSFER_WITH_AUTHORIZATION_TYPES;
|
|
3542
3746
|
exports.addressFromKey = addressFromKey;
|
|
@@ -3552,6 +3756,7 @@ exports.encodeBase64Utf8 = encodeBase64Utf8;
|
|
|
3552
3756
|
exports.encodeMachinePaymentProof = encodeMachinePaymentProof;
|
|
3553
3757
|
exports.encodePaymentProof = encodePaymentProof;
|
|
3554
3758
|
exports.havenTools = havenTools;
|
|
3759
|
+
exports.isSweepableChain = isSweepableChain;
|
|
3555
3760
|
exports.parseMachinePaymentChallenge = parseMachinePaymentChallenge;
|
|
3556
3761
|
exports.parseMachinePaymentChallengeResponse = parseMachinePaymentChallengeResponse;
|
|
3557
3762
|
exports.parsePaymentRequired = parsePaymentRequired;
|
|
@@ -3559,10 +3764,12 @@ exports.parsePaymentRequiredResponse = parsePaymentRequiredResponse;
|
|
|
3559
3764
|
exports.selectPaymentOption = selectPaymentOption;
|
|
3560
3765
|
exports.selectStandardPaymentOption = selectStandardPaymentOption;
|
|
3561
3766
|
exports.signHash = signHash;
|
|
3767
|
+
exports.signUserOpTypedDataForDelegation = signUserOpTypedDataForDelegation;
|
|
3562
3768
|
exports.sweepUsdcAddress = sweepUsdcAddress;
|
|
3563
3769
|
exports.sweepUsdcDomain = sweepUsdcDomain;
|
|
3564
3770
|
exports.toStandardPaymentRequirements = toStandardPaymentRequirements;
|
|
3565
3771
|
exports.toolDescriptions = toolDescriptions;
|
|
3772
|
+
exports.verifyPaymentReceipt = verifyPaymentReceipt;
|
|
3566
3773
|
exports.verifySignature = verifySignature;
|
|
3567
3774
|
exports.x402AuthorizationAmount = x402AuthorizationAmount;
|
|
3568
3775
|
//# sourceMappingURL=index.cjs.map
|