@haven_ai/sdk 0.2.1-alpha.0 → 0.4.0-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/README.md +1 -0
- package/dist/index.cjs +326 -89
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +397 -142
- package/dist/index.d.ts +397 -142
- package/dist/index.js +315 -84
- package/dist/index.js.map +1 -1
- package/package.json +2 -1
package/dist/index.cjs
CHANGED
|
@@ -114,12 +114,12 @@ var AgentPaymentNextAction = {
|
|
|
114
114
|
* the agent's per-token allowance needs to be raised before the payment
|
|
115
115
|
* can succeed. A user approval will not fix this state on its own.
|
|
116
116
|
*
|
|
117
|
-
* #
|
|
118
|
-
*
|
|
119
|
-
*
|
|
120
|
-
*
|
|
117
|
+
* #2914: the account-vocabulary spelling, and the only one — the
|
|
118
|
+
* pre-#2907 `fund_safe_or_raise_allowance` wire value (and the
|
|
119
|
+
* `AgentPaymentNextActionAccountAlias` seam #2908 added to bridge it) are
|
|
120
|
+
* retired along with the rest of the #2908 compatibility window.
|
|
121
121
|
*/
|
|
122
|
-
|
|
122
|
+
FundAccountOrRaiseAllowance: "fund_account_or_raise_allowance",
|
|
123
123
|
/**
|
|
124
124
|
* The delegate wallet may hold funds that were sent from the Safe but never
|
|
125
125
|
* settled to the merchant. The wallet owner should initiate a sweep to
|
|
@@ -146,19 +146,6 @@ var AgentPaymentNextAction = {
|
|
|
146
146
|
*/
|
|
147
147
|
AwaitingSettlementEvidence: "awaiting_settlement_evidence"
|
|
148
148
|
};
|
|
149
|
-
var AgentPaymentNextActionAccountAlias = {
|
|
150
|
-
/** Account-vocabulary twin of `fund_safe_or_raise_allowance`; same meaning. */
|
|
151
|
-
FundAccountOrRaiseAllowance: "fund_account_or_raise_allowance"
|
|
152
|
-
};
|
|
153
|
-
function canonicalAgentPaymentNextAction(value) {
|
|
154
|
-
if (value === AgentPaymentNextActionAccountAlias.FundAccountOrRaiseAllowance) {
|
|
155
|
-
return AgentPaymentNextAction.FundSafeOrRaiseAllowance;
|
|
156
|
-
}
|
|
157
|
-
return value;
|
|
158
|
-
}
|
|
159
|
-
function isFundAccountOrRaiseAllowance(value) {
|
|
160
|
-
return value === AgentPaymentNextAction.FundSafeOrRaiseAllowance || value === AgentPaymentNextActionAccountAlias.FundAccountOrRaiseAllowance;
|
|
161
|
-
}
|
|
162
149
|
var AgentPaymentFailureCode = {
|
|
163
150
|
/** A merchant-authoritative x402 price exceeds the caller's pre-funding max_amount cap. */
|
|
164
151
|
PriceExceedsMax: "PRICE_EXCEEDS_MAX",
|
|
@@ -253,7 +240,7 @@ var AgentPaymentNextActionDescriptions = {
|
|
|
253
240
|
[AgentPaymentNextAction.StopAndTellUser]: "Stop retrying this payment and tell the user what happened.",
|
|
254
241
|
[AgentPaymentNextAction.RequestAgainIfUserStillWantsIt]: "Ask again only if the user still wants the payment after expiry.",
|
|
255
242
|
[AgentPaymentNextAction.PaymentWindowExpired]: "The x402 funding/quote window expired. Re-quote with the same idempotency key before asking the signer to build a merchant payment header again.",
|
|
256
|
-
[AgentPaymentNextAction.
|
|
243
|
+
[AgentPaymentNextAction.FundAccountOrRaiseAllowance]: "Stop and tell the user that the account needs to be funded or the agent budget raised before the payment can succeed.",
|
|
257
244
|
[AgentPaymentNextAction.RetryWithExplicitContext]: "Retry the same tool call, this time passing merchant_url, tool_name, arguments, and mcp_transport explicitly \u2014 the server had no stored context to rehydrate for this payment id.",
|
|
258
245
|
[AgentPaymentNextAction.SweepStrandedFunds]: "Tell the user that funds may be stranded in the delegate wallet and prompt them to initiate a sweep in Haven to return them to the originating account.",
|
|
259
246
|
[AgentPaymentNextAction.AwaitingSettlementEvidence]: "The settlement window passed with no verified on-chain evidence yet. If you hold the merchant's real settlement transaction hash, report it with haven_report_settlement_evidence. Otherwise, Haven's settlement sweep may still attribute it within about two minutes \u2014 poll getPaymentStatus once more, then tell the user the goods were delivered but unverified if it still shows nothing."
|
|
@@ -721,7 +708,13 @@ function normalizePaymentOption(value) {
|
|
|
721
708
|
mimeType: candidate.mimeType,
|
|
722
709
|
asset: candidate.asset,
|
|
723
710
|
payTo: candidate.payTo,
|
|
724
|
-
|
|
711
|
+
// Keep the merchant offer intact for v2 `accepted` matching. Only the
|
|
712
|
+
// signing conversion may cap its authorization lifetime (#3117).
|
|
713
|
+
// Retain legacy fallback behavior for missing or unusable timeouts.
|
|
714
|
+
// Integer, because the authorize body types it `integer` and the child's
|
|
715
|
+
// timestamp caveat is built from it — the old clamp floored, so keeping a
|
|
716
|
+
// fractional value here would 400 a merchant that used to work (#3117).
|
|
717
|
+
maxTimeoutSeconds: typeof candidate.maxTimeoutSeconds === "number" && Number.isFinite(candidate.maxTimeoutSeconds) && candidate.maxTimeoutSeconds >= 1 ? Math.floor(candidate.maxTimeoutSeconds) : clampAuthorizationWindow(candidate.maxTimeoutSeconds),
|
|
725
718
|
extra: candidate.extra
|
|
726
719
|
};
|
|
727
720
|
}
|
|
@@ -872,6 +865,13 @@ function selectPaymentOption(accepts) {
|
|
|
872
865
|
return null;
|
|
873
866
|
}
|
|
874
867
|
var ERC7710_ASSET_TRANSFER_METHOD = "erc7710";
|
|
868
|
+
function isEip3009ConstructibleOption(option) {
|
|
869
|
+
const rawMethod = option.extra?.assetTransferMethod;
|
|
870
|
+
if (rawMethod !== void 0 && rawMethod !== "eip3009") return false;
|
|
871
|
+
const rawFlow = option.extra?.paymentFlow;
|
|
872
|
+
if (rawFlow !== void 0 && rawFlow !== "authorization") return false;
|
|
873
|
+
return true;
|
|
874
|
+
}
|
|
875
875
|
function x402AssetTransferMethod(option) {
|
|
876
876
|
const raw = option.extra?.assetTransferMethod;
|
|
877
877
|
return typeof raw === "string" ? raw : null;
|
|
@@ -892,7 +892,8 @@ function selectStandardPaymentOption(accepts) {
|
|
|
892
892
|
if (!accepts || accepts.length === 0) return null;
|
|
893
893
|
for (const opt of accepts) {
|
|
894
894
|
if (opt === null || typeof opt !== "object") continue;
|
|
895
|
-
if (!
|
|
895
|
+
if (!isEip3009ConstructibleOption(opt)) continue;
|
|
896
|
+
if (isPayableStandardOption(opt)) return opt;
|
|
896
897
|
}
|
|
897
898
|
return null;
|
|
898
899
|
}
|
|
@@ -901,6 +902,7 @@ function selectErc7710PaymentOption(accepts) {
|
|
|
901
902
|
for (const opt of accepts) {
|
|
902
903
|
if (opt === null || typeof opt !== "object") continue;
|
|
903
904
|
if (isErc7710Option(opt) && isPayableStandardOption(opt)) return opt;
|
|
905
|
+
if (!isEip3009ConstructibleOption(opt)) continue;
|
|
904
906
|
}
|
|
905
907
|
return null;
|
|
906
908
|
}
|
|
@@ -962,6 +964,11 @@ function toStandardPaymentRequirements(paymentRequired, option) {
|
|
|
962
964
|
if (option.scheme !== "exact") {
|
|
963
965
|
throw new Error(`Unsupported x402 scheme: ${option.scheme}`);
|
|
964
966
|
}
|
|
967
|
+
if (!isEip3009ConstructibleOption(option)) {
|
|
968
|
+
throw new Error(
|
|
969
|
+
"Unsupported x402 payment requirements: this SDK constructs EIP-3009 authorization payments only. The merchant advertised a different extra.assetTransferMethod or an unrecognized extra.paymentFlow."
|
|
970
|
+
);
|
|
971
|
+
}
|
|
965
972
|
return {
|
|
966
973
|
scheme: "exact",
|
|
967
974
|
network,
|
|
@@ -971,14 +978,11 @@ function toStandardPaymentRequirements(paymentRequired, option) {
|
|
|
971
978
|
mimeType: option.mimeType ?? paymentRequired.resource.mimeType ?? "application/octet-stream",
|
|
972
979
|
payTo: option.payTo,
|
|
973
980
|
asset: option.asset,
|
|
974
|
-
//
|
|
975
|
-
//
|
|
976
|
-
// `
|
|
977
|
-
//
|
|
978
|
-
//
|
|
979
|
-
// the library's `validBefore = now + this value` then carries enough
|
|
980
|
-
// slack to satisfy the facilitator's `validBefore ≥ now + maxTimeout`
|
|
981
|
-
// verify rule after our funding leg confirms.
|
|
981
|
+
// Signing-only policy (#715/#3117): parsed and directly supplied options
|
|
982
|
+
// share this cap before the library computes validBefore. Keep the offer
|
|
983
|
+
// echoed in `accepted` unchanged. The forward margin (#1256) adds time
|
|
984
|
+
// for funding/retry, but does not guarantee a merchant timeout above the
|
|
985
|
+
// total bounded lifetime can pass facilitator verification.
|
|
982
986
|
maxTimeoutSeconds: clampAuthorizationWindow(option.maxTimeoutSeconds) + X402_SETTLEMENT_FORWARD_MARGIN_SECONDS,
|
|
983
987
|
extra: option.extra
|
|
984
988
|
};
|
|
@@ -1226,10 +1230,7 @@ function mapPaymentStatusResult(raw) {
|
|
|
1226
1230
|
rail: raw.rail,
|
|
1227
1231
|
status: raw.status,
|
|
1228
1232
|
phase: raw.phase,
|
|
1229
|
-
|
|
1230
|
-
// so every `=== AgentPaymentNextAction.X` downstream keeps working when
|
|
1231
|
-
// the server flips its emit at #2914.
|
|
1232
|
-
nextAction: canonicalAgentPaymentNextAction(raw.next_action),
|
|
1233
|
+
nextAction: raw.next_action,
|
|
1233
1234
|
amount: raw.amount,
|
|
1234
1235
|
token: raw.token,
|
|
1235
1236
|
resourceUrl: raw.resource_url,
|
|
@@ -1365,7 +1366,7 @@ function messageForState(label, status, paymentId, nextAction) {
|
|
|
1365
1366
|
function paymentStateFromRaw(label, raw) {
|
|
1366
1367
|
if (!raw.payment_id || !raw.status) return null;
|
|
1367
1368
|
const phase = raw.phase ?? phaseForStatus(raw.status);
|
|
1368
|
-
const nextAction =
|
|
1369
|
+
const nextAction = raw.next_action ?? nextActionForStatus(raw.status);
|
|
1369
1370
|
if (!phase || !nextAction) return null;
|
|
1370
1371
|
const amount = raw.amount ?? raw.requested ?? "";
|
|
1371
1372
|
const token = raw.token ?? "";
|
|
@@ -1436,6 +1437,51 @@ function throwPaymentStateError(label, raw) {
|
|
|
1436
1437
|
throw new HavenApiError(message, statusCode, raw);
|
|
1437
1438
|
}
|
|
1438
1439
|
|
|
1440
|
+
// src/x402-retry-target.ts
|
|
1441
|
+
function resolveX402RetryTarget(input) {
|
|
1442
|
+
const requestUrl = input.requestUrl?.trim() || void 0;
|
|
1443
|
+
if (requestUrl) {
|
|
1444
|
+
return {
|
|
1445
|
+
url: requestUrl,
|
|
1446
|
+
source: "request",
|
|
1447
|
+
resourceUrlDiffersFromRequest: requestUrl !== input.resourceUrl
|
|
1448
|
+
};
|
|
1449
|
+
}
|
|
1450
|
+
return { url: input.resourceUrl, source: "resource" };
|
|
1451
|
+
}
|
|
1452
|
+
var LOOPBACK_HOSTS = /* @__PURE__ */ new Set(["localhost", "::1", "[::1]"]);
|
|
1453
|
+
var RESERVED_SUFFIXES = [".test", ".localhost", ".invalid", ".example"];
|
|
1454
|
+
var IPV4_LOOPBACK = /^127(?:\.(?:25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3}$/;
|
|
1455
|
+
function isSecureX402RetryTarget(url) {
|
|
1456
|
+
let parsed;
|
|
1457
|
+
try {
|
|
1458
|
+
parsed = new URL(url);
|
|
1459
|
+
} catch {
|
|
1460
|
+
return false;
|
|
1461
|
+
}
|
|
1462
|
+
if (parsed.protocol === "https:") return true;
|
|
1463
|
+
if (parsed.protocol !== "http:") return false;
|
|
1464
|
+
const host = parsed.hostname.toLowerCase();
|
|
1465
|
+
if (LOOPBACK_HOSTS.has(host) || IPV4_LOOPBACK.test(host)) return true;
|
|
1466
|
+
return RESERVED_SUFFIXES.some((suffix) => host.endsWith(suffix));
|
|
1467
|
+
}
|
|
1468
|
+
var INSECURE_RETRY_TARGET_CODE = "INSECURE_RETRY_TARGET";
|
|
1469
|
+
var HavenInsecureRetryTargetError = class extends HavenError {
|
|
1470
|
+
constructor(url) {
|
|
1471
|
+
super(
|
|
1472
|
+
`Refusing to send a payment header to ${url}: the paid x402 retry must go to an https URL (or a loopback / reserved test host). The merchant's challenge declared this resource URL; retry with the https URL you quoted (pass it as \`url\`), and report a merchant whose challenge downgrades the scheme.`,
|
|
1473
|
+
INSECURE_RETRY_TARGET_CODE,
|
|
1474
|
+
400
|
|
1475
|
+
);
|
|
1476
|
+
this.url = url;
|
|
1477
|
+
this.name = "HavenInsecureRetryTargetError";
|
|
1478
|
+
}
|
|
1479
|
+
url;
|
|
1480
|
+
};
|
|
1481
|
+
function assertSecureX402RetryTarget(url) {
|
|
1482
|
+
if (!isSecureX402RetryTarget(url)) throw new HavenInsecureRetryTargetError(url);
|
|
1483
|
+
}
|
|
1484
|
+
|
|
1439
1485
|
// src/mcp-merchant-transport.ts
|
|
1440
1486
|
var DEFAULT_MERCHANT_TIMEOUT = 3e5;
|
|
1441
1487
|
var MCP_NOTIFICATION_TIMEOUT = 1e4;
|
|
@@ -1593,6 +1639,7 @@ var McpMerchantTransport = class {
|
|
|
1593
1639
|
* overwrite, which is worse than the duplicate this change removes.
|
|
1594
1640
|
*/
|
|
1595
1641
|
async deliverPayment(url, init, paymentHeader) {
|
|
1642
|
+
assertSecureX402RetryTarget(url);
|
|
1596
1643
|
const headers = new Headers(init?.headers);
|
|
1597
1644
|
const send = x402PaymentHeaderNamesFor(paymentHeader);
|
|
1598
1645
|
for (const name of X402_PAYMENT_HEADER_NAMES) {
|
|
@@ -1688,20 +1735,6 @@ function verifyPaymentReceipt(receipt, recover = defaultRecover) {
|
|
|
1688
1735
|
return { verified: true, recoveredSigner: recovered };
|
|
1689
1736
|
}
|
|
1690
1737
|
|
|
1691
|
-
// src/account-naming.ts
|
|
1692
|
-
function readAccountAddress(raw) {
|
|
1693
|
-
return raw.account_address ?? raw.safe_address ?? void 0;
|
|
1694
|
-
}
|
|
1695
|
-
function readAccountId(raw) {
|
|
1696
|
-
return raw.account_id ?? raw.safe_id ?? void 0;
|
|
1697
|
-
}
|
|
1698
|
-
function accountAddressTwins(address) {
|
|
1699
|
-
return { accountAddress: address, safeAddress: address };
|
|
1700
|
-
}
|
|
1701
|
-
function readX402ReceiptPayer(raw) {
|
|
1702
|
-
return raw.payer ?? raw.account_address ?? raw.sign_data?.components?.payer_account ?? raw.safe_address ?? raw.sign_data?.components?.safe;
|
|
1703
|
-
}
|
|
1704
|
-
|
|
1705
1738
|
// src/account-reads.ts
|
|
1706
1739
|
function safeBigInt(value) {
|
|
1707
1740
|
try {
|
|
@@ -1721,6 +1754,10 @@ function deriveReadiness(status, allowances) {
|
|
|
1721
1754
|
if (status !== "active") return "revoked";
|
|
1722
1755
|
return allowances.some((allowance) => safeBigInt(allowance.remainingAtomic) > 0n) ? "ready" : "needs_approval";
|
|
1723
1756
|
}
|
|
1757
|
+
function formatRemainingDisplay(tokenAddress, tokenSymbol, remainingAtomic) {
|
|
1758
|
+
const token = resolveTokenFromAddress(tokenAddress);
|
|
1759
|
+
return token ? `${formatAtomicAmount(safeBigInt(remainingAtomic), token.decimals)} ${tokenSymbol}` : `${remainingAtomic} ${tokenSymbol} (atomic; unknown decimals)`;
|
|
1760
|
+
}
|
|
1724
1761
|
var AccountReads = class {
|
|
1725
1762
|
transport;
|
|
1726
1763
|
getPaymentStatus;
|
|
@@ -1742,12 +1779,12 @@ var AccountReads = class {
|
|
|
1742
1779
|
async getAgentSummary() {
|
|
1743
1780
|
const [agent, allowanceSummary] = await Promise.all([this.getAgent(), this.getAllowances()]);
|
|
1744
1781
|
const allowances = allowanceSummary.allowances.map((allowance) => {
|
|
1745
|
-
const token = resolveTokenFromAddress(allowance.tokenAddress);
|
|
1746
|
-
const remainingDisplay = token ? `${formatAtomicAmount(safeBigInt(allowance.onchain.remaining), token.decimals)} ${allowance.tokenSymbol}` : `${allowance.onchain.remaining} ${allowance.tokenSymbol} (atomic; unknown decimals)`;
|
|
1747
1782
|
return {
|
|
1783
|
+
id: allowance.id,
|
|
1748
1784
|
tokenSymbol: allowance.tokenSymbol,
|
|
1785
|
+
tokenAddress: allowance.tokenAddress,
|
|
1749
1786
|
remainingAtomic: allowance.onchain.remaining,
|
|
1750
|
-
remainingDisplay,
|
|
1787
|
+
remainingDisplay: allowance.remainingDisplay,
|
|
1751
1788
|
configuredAmount: allowance.configuredAmount,
|
|
1752
1789
|
resetPeriodMin: allowance.resetPeriodMin,
|
|
1753
1790
|
isResetPending: allowance.onchain.isResetPending
|
|
@@ -1760,10 +1797,13 @@ var AccountReads = class {
|
|
|
1760
1797
|
const raw = await this.transport.get("/machine-payments/allowances");
|
|
1761
1798
|
return {
|
|
1762
1799
|
agentId: raw.agent_id,
|
|
1763
|
-
//
|
|
1764
|
-
//
|
|
1765
|
-
//
|
|
1766
|
-
|
|
1800
|
+
// `account_address` is required on the wire contract, so the declared
|
|
1801
|
+
// type stays `string`; a server that omits it is off-contract and the
|
|
1802
|
+
// cast is the one place that case is allowed through as `undefined`
|
|
1803
|
+
// rather than a fabricated `''` (a present-but-blank address downstream
|
|
1804
|
+
// — the hosted MCP output spreads this object, and the sweep uses it as
|
|
1805
|
+
// a destination).
|
|
1806
|
+
accountAddress: raw.account_address,
|
|
1767
1807
|
delegateAddress: raw.delegate_address,
|
|
1768
1808
|
chainId: raw.chain_id,
|
|
1769
1809
|
allowances: raw.allowances.map((allowance) => ({
|
|
@@ -1772,6 +1812,7 @@ var AccountReads = class {
|
|
|
1772
1812
|
tokenSymbol: allowance.token_symbol,
|
|
1773
1813
|
configuredAmount: allowance.configured_amount,
|
|
1774
1814
|
resetPeriodMin: allowance.reset_period_min,
|
|
1815
|
+
remainingDisplay: formatRemainingDisplay(allowance.token_address, allowance.token_symbol, allowance.onchain.remaining),
|
|
1775
1816
|
onchain: {
|
|
1776
1817
|
amount: allowance.onchain.amount,
|
|
1777
1818
|
spent: allowance.onchain.spent,
|
|
@@ -1786,6 +1827,33 @@ var AccountReads = class {
|
|
|
1786
1827
|
}))
|
|
1787
1828
|
};
|
|
1788
1829
|
}
|
|
1830
|
+
/**
|
|
1831
|
+
* #3126 — the sufficiency signal behind {@link HavenBalanceCoverage}.
|
|
1832
|
+
*
|
|
1833
|
+
* Deliberately NOT a balance read: the endpoint answers whether the
|
|
1834
|
+
* account HOLDS at least the checked amount, as `covered
|
|
1835
|
+
* true/false/null`, and never returns the balance itself. The camelCase
|
|
1836
|
+
* mapping is permissive (raw fields flow through; the server owns the
|
|
1837
|
+
* wire shape, pinned by the backend's `expectMatchesSpec` assertion), so
|
|
1838
|
+
* an older server that has not deployed the endpoint surfaces its 404 as
|
|
1839
|
+
* a thrown error rather than a fabricated answer.
|
|
1840
|
+
*/
|
|
1841
|
+
async checkFunds(input) {
|
|
1842
|
+
const query = `token=${encodeURIComponent(input.token)}&amount_atomic=${encodeURIComponent(input.amountAtomic)}`;
|
|
1843
|
+
const raw = await this.transport.get(
|
|
1844
|
+
`/machine-payments/balance-coverage?${query}`
|
|
1845
|
+
);
|
|
1846
|
+
return {
|
|
1847
|
+
covered: raw.covered,
|
|
1848
|
+
...raw.coverage_error !== void 0 ? { coverageError: raw.coverage_error } : {},
|
|
1849
|
+
chainId: raw.chain_id,
|
|
1850
|
+
tokenAddress: raw.token_address,
|
|
1851
|
+
tokenSymbol: raw.token_symbol,
|
|
1852
|
+
checkedAmountAtomic: raw.checked_amount_atomic,
|
|
1853
|
+
budgetRemainingAtomic: raw.budget_remaining_atomic,
|
|
1854
|
+
...raw.budget_remaining_is_from_chain !== void 0 ? { budgetRemainingIsFromChain: raw.budget_remaining_is_from_chain } : {}
|
|
1855
|
+
};
|
|
1856
|
+
}
|
|
1789
1857
|
async getPostPurchaseAllowanceSummary(paymentId) {
|
|
1790
1858
|
const unavailable = (detail, payment2 = null) => ({
|
|
1791
1859
|
payment: payment2,
|
|
@@ -1837,10 +1905,23 @@ var AccountReads = class {
|
|
|
1837
1905
|
return unavailable(error instanceof Error ? error.message : String(error));
|
|
1838
1906
|
}
|
|
1839
1907
|
}
|
|
1908
|
+
/** The first page's receipts as a bare array — the pre-#3128 shape, kept for callers that never page. */
|
|
1840
1909
|
async listReceipts(options = {}) {
|
|
1841
|
-
|
|
1910
|
+
return (await this.listReceiptsPage(options)).receipts;
|
|
1911
|
+
}
|
|
1912
|
+
/** #3128: one page with `total`, `hasMore` and `nextCursor` — see {@link HavenPaymentReceiptsPage}. */
|
|
1913
|
+
async listReceiptsPage(options = {}) {
|
|
1914
|
+
const params = new URLSearchParams();
|
|
1915
|
+
if (options.limit) params.set("limit", String(options.limit));
|
|
1916
|
+
if (options.cursor) params.set("cursor", options.cursor);
|
|
1917
|
+
const query = params.size > 0 ? `?${params.toString()}` : "";
|
|
1842
1918
|
const raw = await this.transport.get(`/machine-payments/receipts${query}`);
|
|
1843
|
-
return
|
|
1919
|
+
return {
|
|
1920
|
+
receipts: raw.receipts.map(mapPaymentReceipt),
|
|
1921
|
+
total: typeof raw.total === "number" ? raw.total : null,
|
|
1922
|
+
hasMore: typeof raw.has_more === "boolean" ? raw.has_more : null,
|
|
1923
|
+
nextCursor: typeof raw.next_cursor === "string" ? raw.next_cursor : null
|
|
1924
|
+
};
|
|
1844
1925
|
}
|
|
1845
1926
|
async getReceipt(paymentId) {
|
|
1846
1927
|
const { receipt } = await this.transport.get(`/payments/${paymentId}/receipt`);
|
|
@@ -1852,10 +1933,10 @@ var AccountReads = class {
|
|
|
1852
1933
|
id: raw.id,
|
|
1853
1934
|
name: raw.name,
|
|
1854
1935
|
status: raw.status,
|
|
1855
|
-
//
|
|
1856
|
-
//
|
|
1857
|
-
//
|
|
1858
|
-
|
|
1936
|
+
// See the comment on `getAllowances` above: `account_address` is
|
|
1937
|
+
// required on the wire contract, so an omission here is off-contract
|
|
1938
|
+
// and comes through as `undefined` rather than a fabricated `''`.
|
|
1939
|
+
accountAddress: raw.account_address,
|
|
1859
1940
|
delegateAddress: raw.delegate_address,
|
|
1860
1941
|
chainId: raw.chain_id,
|
|
1861
1942
|
executionRail: raw.execution_rail === "delegation" ? "delegation" : "legacy"
|
|
@@ -2012,8 +2093,11 @@ function requestInitFromSnapshot(request) {
|
|
|
2012
2093
|
}
|
|
2013
2094
|
function noCompatiblePaymentOptionError(accepts) {
|
|
2014
2095
|
const erc7710Only = selectErc7710PaymentOption(accepts) !== null;
|
|
2096
|
+
const unsupportedOnly = !erc7710Only && accepts.some(
|
|
2097
|
+
(opt) => opt !== null && typeof opt === "object" && !isErc7710Option(opt) && (x402AssetTransferMethod(opt) !== null || opt.extra?.paymentFlow !== void 0)
|
|
2098
|
+
);
|
|
2015
2099
|
return new HavenApiError(
|
|
2016
|
-
"No compatible payment option found in x402 requirements. Haven supports standard x402 exact payments on Base USDC." + (erc7710Only ? " The only Haven-compatible option this merchant advertises is tagged extra.assetTransferMethod: 'erc7710' (direct settlement), which this EIP-3009 payment path cannot settle \u2014 the limitation is the settlement scheme, not the asset. Paying this merchant requires a delegation-rail erc7710 flow (settleX402Erc7710, or the hosted MCP purchase tools)." : ""),
|
|
2100
|
+
"No compatible payment option found in x402 requirements. Haven supports standard x402 exact payments on Base USDC." + (erc7710Only ? " The only Haven-compatible option this merchant advertises is tagged extra.assetTransferMethod: 'erc7710' (direct settlement), which this EIP-3009 payment path cannot settle \u2014 the limitation is the settlement scheme, not the asset. Paying this merchant requires a delegation-rail erc7710 flow (settleX402Erc7710, or the hosted MCP purchase tools)." : unsupportedOnly ? " Every option this merchant advertises asks for a transfer method or payment flow Haven cannot construct (for example extra.assetTransferMethod: 'permit2', or an unrecognized extra.paymentFlow). No payment intent was created and no funds moved." : ""),
|
|
2017
2101
|
400
|
|
2018
2102
|
);
|
|
2019
2103
|
}
|
|
@@ -2033,6 +2117,11 @@ function buildX402Quote(paymentRequired, request, idempotencyKey, mcpTransport)
|
|
|
2033
2117
|
request,
|
|
2034
2118
|
...mcpTransport ? { mcpTransport } : {},
|
|
2035
2119
|
resourceUrl: paymentRequired.resource.url,
|
|
2120
|
+
// #3097: the merchant's declaration vs the URL the caller quoted. The
|
|
2121
|
+
// paid retry goes to the caller's URL; a quote that says the two disagree
|
|
2122
|
+
// is how an agent sees a challenge that downgrades the scheme or moves
|
|
2123
|
+
// the host before it pays.
|
|
2124
|
+
resourceUrlDiffersFromRequest: paymentRequired.resource.url !== request.url,
|
|
2036
2125
|
description: paymentRequired.resource.description ?? option.description ?? null,
|
|
2037
2126
|
mimeType: paymentRequired.resource.mimeType ?? option.mimeType ?? null,
|
|
2038
2127
|
amountAtomic: x402AuthorizationAmount(option),
|
|
@@ -2187,6 +2276,13 @@ function assertCanResumeX402(status, paymentRequired, option) {
|
|
|
2187
2276
|
);
|
|
2188
2277
|
}
|
|
2189
2278
|
}
|
|
2279
|
+
|
|
2280
|
+
// src/account-naming.ts
|
|
2281
|
+
function readX402ReceiptPayer(raw) {
|
|
2282
|
+
return raw.payer ?? raw.account_address ?? raw.sign_data?.components?.payer_account;
|
|
2283
|
+
}
|
|
2284
|
+
|
|
2285
|
+
// src/x402-funding-leg.ts
|
|
2190
2286
|
var X402FundingLeg = class {
|
|
2191
2287
|
delegateKey;
|
|
2192
2288
|
delegateAddress;
|
|
@@ -3099,7 +3195,18 @@ function mapCatalogEntry(entry) {
|
|
|
3099
3195
|
verifiedAt: entry.verified_at,
|
|
3100
3196
|
source: entry.source,
|
|
3101
3197
|
domainVerified: entry.domain_verified,
|
|
3102
|
-
verifiedPayable: entry.verified_payable
|
|
3198
|
+
verifiedPayable: entry.verified_payable,
|
|
3199
|
+
// #3078: absent (older backend) or null (unresolved join) both mean "no
|
|
3200
|
+
// merchant known" — the public field is then absent, never null.
|
|
3201
|
+
...entry.merchant ? {
|
|
3202
|
+
merchant: {
|
|
3203
|
+
id: entry.merchant.id,
|
|
3204
|
+
slug: entry.merchant.slug,
|
|
3205
|
+
name: entry.merchant.name,
|
|
3206
|
+
listingStatus: entry.merchant.listing_status,
|
|
3207
|
+
isTestMerchant: entry.merchant.is_test_merchant
|
|
3208
|
+
}
|
|
3209
|
+
} : {}
|
|
3103
3210
|
};
|
|
3104
3211
|
}
|
|
3105
3212
|
var HavenClient = class {
|
|
@@ -3481,6 +3588,49 @@ var HavenClient = class {
|
|
|
3481
3588
|
async getAllowances() {
|
|
3482
3589
|
return this.accountReads.getAllowances();
|
|
3483
3590
|
}
|
|
3591
|
+
/**
|
|
3592
|
+
* #3126 — is the checked amount of the token actually HELD on the
|
|
3593
|
+
* agent's own account?
|
|
3594
|
+
*
|
|
3595
|
+
* This is the companion to {@link getAllowances}, not a variant of it:
|
|
3596
|
+
* allowances answer what the agent is PERMITTED to spend this period;
|
|
3597
|
+
* this answers whether the account HOLDS funds behind that permission,
|
|
3598
|
+
* as a sufficiency signal — `covered: true | false | null` — never as a
|
|
3599
|
+
* balance. `covered: null` means the chain read failed: treat it as
|
|
3600
|
+
* unverifiable, not as absence (`coverageError` says why). The account's
|
|
3601
|
+
* balance itself is deliberately not returned.
|
|
3602
|
+
*/
|
|
3603
|
+
async checkFunds(input) {
|
|
3604
|
+
return this.accountReads.checkFunds(input);
|
|
3605
|
+
}
|
|
3606
|
+
/**
|
|
3607
|
+
* `POST /machine-payments/budget-precheck` (#3054): ask Haven to decide —
|
|
3608
|
+
* server-side — whether `amountAtomic` of `token` fits the agent's
|
|
3609
|
+
* remaining delegation budget, the same compare the guided prepare used to
|
|
3610
|
+
* run locally over its allowances read.
|
|
3611
|
+
*
|
|
3612
|
+
* On insufficiency Haven refuses (403, `delegation_budget_exceeded`) and
|
|
3613
|
+
* the refusal reaches the `payment_refusals` ledger with
|
|
3614
|
+
* `source: 'hosted_prepare'` — the point of the endpoint. This method
|
|
3615
|
+
* surfaces that decision as a thrown {@link HavenApiError}; it does NOT
|
|
3616
|
+
* swallow it, because swallowing would turn a decided refusal into the
|
|
3617
|
+
* degrade-to-warning path and the ledger row would still land while the
|
|
3618
|
+
* purchase proceeded.
|
|
3619
|
+
*
|
|
3620
|
+
* camelCase body like the route family; the response mirrors the wire
|
|
3621
|
+
* (`sufficient`, `remaining_atomic`). `resourceUrl` is the merchant
|
|
3622
|
+
* resource being bought — the ledger dedupe window's discriminating
|
|
3623
|
+
* column — never this request's own URL.
|
|
3624
|
+
*/
|
|
3625
|
+
async precheckBudget(input) {
|
|
3626
|
+
return this.post("/machine-payments/budget-precheck", {
|
|
3627
|
+
chainId: input.chainId,
|
|
3628
|
+
token: input.token,
|
|
3629
|
+
amountAtomic: input.amountAtomic,
|
|
3630
|
+
...input.merchantTo !== void 0 ? { merchantTo: input.merchantTo } : {},
|
|
3631
|
+
...input.resourceUrl !== void 0 ? { resourceUrl: input.resourceUrl } : {}
|
|
3632
|
+
});
|
|
3633
|
+
}
|
|
3484
3634
|
/**
|
|
3485
3635
|
* Post-purchase allowance/budget summary for a settled payment (#1310).
|
|
3486
3636
|
*
|
|
@@ -3603,6 +3753,10 @@ var HavenClient = class {
|
|
|
3603
3753
|
async listReceipts(options = {}) {
|
|
3604
3754
|
return this.accountReads.listReceipts(options);
|
|
3605
3755
|
}
|
|
3756
|
+
/** #3128: one page of receipts with `total`, `hasMore` and `nextCursor`. */
|
|
3757
|
+
async listReceiptsPage(options = {}) {
|
|
3758
|
+
return this.accountReads.listReceiptsPage(options);
|
|
3759
|
+
}
|
|
3606
3760
|
/**
|
|
3607
3761
|
* Fetch the verifiable receipt bundle for a settled payment and verify it
|
|
3608
3762
|
* locally. The server's own verification is ignored — the receipt is verified
|
|
@@ -4212,13 +4366,13 @@ var toolDescriptions = {
|
|
|
4212
4366
|
summary: "Pay an inspected x402 quote. The delegate key signs locally; Haven only validates and relays signed, on-chain-constrained payment transactions.",
|
|
4213
4367
|
selectionGuidance: "Do not use this for read-only allowance, budget, spend-limit, remaining-amount, reset-period, or what-can-I-spend questions; use the allowance lookup tool instead.",
|
|
4214
4368
|
behavior: "Signs the payment locally and returns the merchant response. Settlement is either direct account-to-merchant with no funding leg, or a bridge that first redeems the agent's budget delegation to fund the delegate wallet for an EIP-3009 authorization. A payment outside the on-chain budget is declined before any money moves; nothing is queued for a human to approve later.",
|
|
4215
|
-
nextActionGuidance: "Preserve the returned resume_state \u2014 it identifies this payment if you need to ask about it later. This tool performs the merchant retry itself, so do not wait on a signal while the call is in flight. If the process crashes after this call and a later haven_get_payment_status reports nextAction=retry_original_x402_request, Haven's funding leg confirmed but no merchant response was ever recorded \u2014 call the resume tool with the preserved resume_state or payment_id instead of paying again. If the response carries phase=insufficient_funds and nextAction=
|
|
4369
|
+
nextActionGuidance: "Preserve the returned resume_state \u2014 it identifies this payment if you need to ask about it later. This tool performs the merchant retry itself, so do not wait on a signal while the call is in flight. If the process crashes after this call and a later haven_get_payment_status reports nextAction=retry_original_x402_request, Haven's funding leg confirmed but no merchant response was ever recorded \u2014 call the resume tool with the preserved resume_state or payment_id instead of paying again. If the response carries phase=insufficient_funds and nextAction=fund_account_or_raise_allowance, the payment cannot be retried until the account is funded or the agent budget raised \u2014 stop and tell the user the shortfall reported on the response."
|
|
4216
4370
|
},
|
|
4217
4371
|
payX402OneShot: {
|
|
4218
4372
|
summary: "Fetch an x402 paid HTTP resource in a single call. Handles the full probe -> pay -> retry round trip and returns the merchant response.",
|
|
4219
4373
|
selectionGuidance: "Prefer this over the quote+pay split when the agent just wants the paid resource and does not need to inspect the price first. If you already have a quote from haven_quote_x402, use haven_pay_x402_quote instead. Do not use for read-only allowance, budget, spend-limit, remaining-amount, reset-period, or what-can-I-spend questions; use the allowance lookup tool instead.",
|
|
4220
4374
|
behavior: "Calls the URL, parses any HTTP 402 x402 challenge, signs the payment locally, then retries the original request with the signed payment header (sent under PAYMENT-SIGNATURE, plus the legacy X-PAYMENT on the EIP-3009 path only) and returns the merchant response. Settlement is either direct account-to-merchant with no funding leg, or a bridge that first redeems the agent's budget delegation to fund the delegate wallet for an EIP-3009 authorization. A payment outside the on-chain budget is declined before any money moves; nothing is queued for a human to approve later. If the resource returns a non-402 status, returns it unchanged without contacting Haven.",
|
|
4221
|
-
nextActionGuidance: "Preserve the returned resume_state or paymentId \u2014 either identifies this payment if you need to ask about it later. This tool performs the merchant retry itself, so do not wait on a signal while the call is in flight. If the process crashes after this call and a later haven_get_payment_status reports nextAction=retry_original_x402_request, Haven's funding leg confirmed but no merchant response was ever recorded \u2014 call the resume tool with the preserved resume_state or payment_id instead of paying again. If the response carries phase=insufficient_funds and nextAction=
|
|
4375
|
+
nextActionGuidance: "Preserve the returned resume_state or paymentId \u2014 either identifies this payment if you need to ask about it later. This tool performs the merchant retry itself, so do not wait on a signal while the call is in flight. If the process crashes after this call and a later haven_get_payment_status reports nextAction=retry_original_x402_request, Haven's funding leg confirmed but no merchant response was ever recorded \u2014 call the resume tool with the preserved resume_state or payment_id instead of paying again. If the response carries phase=insufficient_funds and nextAction=fund_account_or_raise_allowance, the payment cannot be retried until the account is funded or the agent budget raised \u2014 stop and tell the user the shortfall reported on the response."
|
|
4222
4376
|
},
|
|
4223
4377
|
resumeX402: {
|
|
4224
4378
|
summary: "Resume an x402 payment whose Haven-side authorization already succeeded but whose merchant retry did not complete.",
|
|
@@ -4240,21 +4394,29 @@ var toolDescriptions = {
|
|
|
4240
4394
|
nextActionGuidance: ""
|
|
4241
4395
|
},
|
|
4242
4396
|
getAgent: {
|
|
4243
|
-
summary: "Return the authenticated agent identity AND its live spend authority in one call: Haven wallet, delegate, chain, raw status, spend_authority_readiness,
|
|
4244
|
-
selectionGuidance: "Use this as the
|
|
4245
|
-
behavior:
|
|
4397
|
+
summary: "Return the authenticated agent identity AND its live spend authority in one call: Haven wallet, delegate, chain, raw status, spend_authority_readiness, per-token remaining allowance (atomic + human-readable). The recommended first call in a new session.",
|
|
4398
|
+
selectionGuidance: "Use this as the session bootstrap, or to confirm identity together with whether the agent can spend right now. For per-token detail (configured vs spent vs reset window) use haven_get_allowances.",
|
|
4399
|
+
behavior: 'Reads identity plus the live spend-authority snapshot \u2014 the active on-chain budget delegation. spend_authority_readiness (readiness is a deprecated alias, same value) 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. It covers hosted identity + on-chain spend authority ONLY \u2014 the hosted server cannot see the LOCAL signer, so "ready" does not mean the signer can start; verify the signer with a signer tool call or connect --doctor. An over-budget payment is declined before any money moves; there is no approval queue \u2014 ask the owner to grant or raise the budget in Haven. allowances[] carries id, tokenAddress, remainingAtomic, remainingDisplay per token. Identity fields: id, name, status, accountAddress, delegateAddress, chainId.',
|
|
4246
4400
|
nextActionGuidance: ""
|
|
4247
4401
|
},
|
|
4248
4402
|
getAllowances: {
|
|
4249
4403
|
summary: "Return configured and on-chain allowance state for the authenticated agent. On-chain allowance is the real spend gate.",
|
|
4250
|
-
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.",
|
|
4251
|
-
behavior: "Returns the per-token spend authority for the account: the active budget delegation (remaining = the period budget, which re-arms natively at the period boundary). An over-budget payment is declined before any money moves; nothing queues. Configured amounts from Haven are returned alongside.",
|
|
4404
|
+
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. For whether the account actually HOLDS funds behind the budget use haven_check_funds.",
|
|
4405
|
+
behavior: "Returns the per-token spend authority for the account: the active budget delegation (remaining = the period budget, which re-arms natively at the period boundary), each with id, onchain.remaining, remainingDisplay. An over-budget payment is declined before any money moves; nothing queues. Configured amounts from Haven are returned alongside.",
|
|
4252
4406
|
nextActionGuidance: ""
|
|
4253
4407
|
},
|
|
4408
|
+
// #3126 — the sufficiency signal, deliberately NOT a balance tool. The
|
|
4409
|
+
// constrained actor reads a boolean, never the treasury total.
|
|
4410
|
+
checkFunds: {
|
|
4411
|
+
summary: "Check whether the agent's account actually holds at least the given amount of a token \u2014 funds held, not spend permitted.",
|
|
4412
|
+
selectionGuidance: "Use this before attempting a payment when it matters whether the money is really there: allowance answers say what you are PERMITTED to spend, never whether the account HOLDS it. For allowance, budget, spend-limit, remaining-budget, reset-period, or what-can-I-spend questions use the allowance lookup tool instead.",
|
|
4413
|
+
behavior: "Returns covered: true (the account holds at least the checked amount), false (a live chain read reports less \u2014 the budget is backed by an empty account; stop and tell the user funds are missing), or null (the chain read failed \u2014 unverifiable, never treat it as absence; coverageError says why). The account balance itself is deliberately not returned: this is a sufficiency signal, not a balance read. budget_remaining_atomic is the permitted figure from the allowance lookup (the SDK spells it budgetRemainingAtomic), named so it can never be confused with holdings.",
|
|
4414
|
+
nextActionGuidance: "On covered=false, do not attempt the payment \u2014 tell the user the account is short and let them fund it; on covered=null, retry the check shortly or proceed knowing the payment may fail on-chain."
|
|
4415
|
+
},
|
|
4254
4416
|
listReceipts: {
|
|
4255
|
-
summary: "List
|
|
4256
|
-
selectionGuidance: "
|
|
4257
|
-
behavior: "
|
|
4417
|
+
summary: "List machine-payment receipts, newest first, by page.",
|
|
4418
|
+
selectionGuidance: "For transaction history or payment evidence; use the allowance tool instead for remaining allowance or what-can-I-spend questions.",
|
|
4419
|
+
behavior: "Page: { receipts, total, hasMore, nextCursor }; total 0 = none exist (no indexing delay); hasMore = cut at limit, send nextCursor as cursor. parties.treasuryAccount is Haven's authoritative payer. protocolReceiptPayload is the merchant's PAYMENT-RESPONSE, relayed verbatim: merchant-controlled, unverified, not Haven's record; payer may differ from payerAddress. Proof header values are omitted.",
|
|
4258
4420
|
nextActionGuidance: ""
|
|
4259
4421
|
},
|
|
4260
4422
|
verifyReceipt: {
|
|
@@ -4270,10 +4432,10 @@ var toolDescriptions = {
|
|
|
4270
4432
|
nextActionGuidance: "On a decline, report the reason to the user and ask them to raise the budget in Haven \u2014 there is no approval queue to wait on. This tool retries the merchant itself while it runs, so do not wait on a signal mid-call. If the process crashes after payment, a later haven_get_payment_status call may report nextAction=retry_original_x402_request \u2014 resume via haven_resume_x402_payment instead of paying again."
|
|
4271
4433
|
},
|
|
4272
4434
|
discoverTools: {
|
|
4273
|
-
summary: "Step 1 of a purchase: discover payable services from Haven's curated merchant catalog \u2014 names, prices,
|
|
4274
|
-
selectionGuidance: "Use this when the user asks what the agent can buy, pay for, or which paid services exist \u2014 or when you need a resource URL for a service the user described. Use verified=verified for entries Haven watched pass a live quote probe (operator-curated or self-submitted) \u2014 domain_verified is the only ownership claim; never treat these badges as proof of merchant honesty, quality, or reliability. Do NOT use for balance, budget, or spend-limit questions \u2014 use haven_get_allowances. Do NOT use to pay \u2014 each returned entry names the
|
|
4275
|
-
behavior: "Use each entry's suggested_tool
|
|
4276
|
-
nextActionGuidance: `
|
|
4435
|
+
summary: "Step 1 of a purchase: discover payable services from Haven's curated merchant catalog \u2014 names, prices, the next call.",
|
|
4436
|
+
selectionGuidance: "Use this when the user asks what the agent can buy, pay for, or which paid services exist \u2014 or when you need a resource URL for a service the user described. Use verified=verified for entries Haven watched pass a live quote probe (operator-curated or self-submitted) \u2014 domain_verified is the only ownership claim; never treat these badges as proof of merchant honesty, quality, or reliability. Do NOT use for balance, budget, or spend-limit questions \u2014 use haven_get_allowances. Do NOT use to pay \u2014 each returned entry names the next tool to call.",
|
|
4437
|
+
behavior: "Use each entry's suggested_tool + suggested_arguments first; an entry without them says why. Read-only lookup against Haven's curated catalog; entries are periodically re-verified against the live merchant and degraded entries are flagged. Use category for a case-insensitive category filter (for example, VPN or vpn), or search for a product name, category, or description term. Returns name, description, price, rail, resource URL, tool_name, tool_arguments, the hint, and the provenance badges source/domain_verified/verified_payable. The catalog price (price_display/price_atomic, marked price_is_indicative) is a last-verified hint, NOT authoritative \u2014 the real price comes from the merchant's live 402 at pay time. Never creates a payment, signature, or approval.",
|
|
4438
|
+
nextActionGuidance: `Call suggested_tool with suggested_arguments VERBATIM (no hint: read suggested_tool_omitted_reason). Confirm the price from the live quote or pay result, not the catalog; if the next tool takes a cap, pass the user's cap as max_amount_human in whole tokens ("no more than 1 USDC" \u2192 max_amount_human: "1"), never atomic units by hand.`
|
|
4277
4439
|
},
|
|
4278
4440
|
submitCatalogEntry: {
|
|
4279
4441
|
summary: "Submit a merchant's payable (x402/MCP) endpoint to Haven's Verified Payable Directory for verification and listing.",
|
|
@@ -4283,13 +4445,13 @@ var toolDescriptions = {
|
|
|
4283
4445
|
},
|
|
4284
4446
|
sweep_delegate: {
|
|
4285
4447
|
summary: "Sweep stranded USDC and/or ETH from the delegate wallet back to the originating account.",
|
|
4286
|
-
selectionGuidance: "Use this when the user instructs you to recover stranded funds on the delegate wallet, or when a payment status returns nextAction=sweep_stranded_funds. Do NOT use for normal payments \u2014 use haven_pay_x402. Do NOT use to read balances only \u2014 use haven_get_allowances.",
|
|
4448
|
+
selectionGuidance: "Use this when the user instructs you to recover stranded funds on the delegate wallet, or when a payment status returns nextAction=sweep_stranded_funds. Do NOT use for normal payments \u2014 use haven_pay_x402. Do NOT use to read balances only \u2014 use haven_get_allowances, or haven_check_funds for whether the account holds funds.",
|
|
4287
4449
|
behavior: `Reads the delegate EOA's on-chain USDC and ETH balances. For each non-zero balance, signs and submits a transfer from the delegate EOA to the originating account (hardcoded destination). The delegate key signs locally \u2014 Haven never sees it and the backend never constructs signed transactions (CASP/MiCA Red Line #2). Returns tx hashes and recovered amounts. Returns an empty transfers list when nothing is stranded. Each transfer carries confirmation: "confirmed" (a receipt was seen \u2014 the funds are in the account) or "unconfirmed" (broadcast but not confirmed within 90 seconds \u2014 still in the mempool, may still land). The top-level unconfirmed flag is true when any transfer is unconfirmed.`,
|
|
4288
4450
|
nextActionGuidance: 'If transfers is non-empty, confirm the amounts with the user. Report a transfer as recovered ONLY when its confirmation is "confirmed". For an "unconfirmed" transfer, tell the user it was submitted but not yet confirmed, give them its txHash and explorerUrl to check, and do not re-run the sweep immediately \u2014 a re-run after it lands will simply find nothing stranded.'
|
|
4289
4451
|
},
|
|
4290
4452
|
send: {
|
|
4291
4453
|
summary: "Send ETH or USDC directly from the agent's Haven wallet to a recipient address.",
|
|
4292
|
-
selectionGuidance: "Use this for plain transfers \u2014 refunding a user, paying a freelancer, topping up a co-agent's wallet, or moving funds between addresses. Do NOT use for x402 paid endpoints \u2014 use haven_pay_x402 instead. Do NOT use for read-only allowance, budget, or what-can-I-spend questions \u2014 use haven_get_allowances.",
|
|
4454
|
+
selectionGuidance: "Use this for plain transfers \u2014 refunding a user, paying a freelancer, topping up a co-agent's wallet, or moving funds between addresses. Do NOT use for x402 paid endpoints \u2014 use haven_pay_x402 instead. Do NOT use for read-only allowance, budget, or what-can-I-spend questions \u2014 use haven_get_allowances. Do NOT use to check whether funds are held before sending \u2014 use haven_check_funds.",
|
|
4293
4455
|
behavior: "Sends the requested amount by redeeming the agent's on-chain budget delegation, account to recipient with no funding leg. Budget, recipient and expiry are enforced on-chain while the transfer is prepared, so a request outside them is declined before any money moves and before the agent is asked to sign \u2014 it is never queued for a human to approve later. The agent's signing key signs the account's typed data; Haven never receives the key.",
|
|
4294
4456
|
nextActionGuidance: "On a decline, report the reason to the user and ask them to grant or raise the budget in Haven \u2014 there is nothing to poll and no approval will arrive. After a successful send, poll haven_get_payment_status until nextAction=none."
|
|
4295
4457
|
},
|
|
@@ -4668,8 +4830,10 @@ the \`mcp__haven-signer__\` namespace and keep the delegate key on this machine.
|
|
|
4668
4830
|
That namespacing is Claude-family; other runtimes name the servers by their
|
|
4669
4831
|
own config keys (Codex: \`haven\`, \`haven_signer\`). Tool results carry the
|
|
4670
4832
|
exact next step (\`next_action\`, \`next_tool\`, \`next_arguments\`, plus the
|
|
4671
|
-
runtime-neutral \`next_tool_server\` + \`next_tool_name\`
|
|
4672
|
-
on that logical server, whatever your runtime calls it).
|
|
4833
|
+
runtime-neutral \`next_tool_server\` + \`next_tool_name\` + \`next_tool_server_role\`
|
|
4834
|
+
\u2014 the bare tool name on that logical server, whatever your runtime calls it).
|
|
4835
|
+
When no tool follows, \`next_tool\` is absent and \`next_tool_omitted_reason\`
|
|
4836
|
+
says why; that is a complete answer.
|
|
4673
4837
|
Follow those fields first; the prose below is fallback and orientation, not
|
|
4674
4838
|
the source of truth.
|
|
4675
4839
|
|
|
@@ -4756,6 +4920,13 @@ spending:
|
|
|
4756
4920
|
local signer; the signer is verified by calling any signer tool.
|
|
4757
4921
|
- \`mcp__haven__haven_get_allowances\` \u2014 detailed per-token breakdown
|
|
4758
4922
|
(configured, spent, reset window) when you need more than the summary.
|
|
4923
|
+
- \`mcp__haven__haven_check_funds\` \u2014 whether the account actually HOLDS at
|
|
4924
|
+
least a given amount of a token. Allowance answers above say what you are
|
|
4925
|
+
permitted to spend; this one says whether the money is really there,
|
|
4926
|
+
answered as \`covered\` true/false/null \u2014 never as a balance. On
|
|
4927
|
+
\`covered: false\`, stop and tell the user the account is short; on
|
|
4928
|
+
\`covered: null\` (the chain read failed), treat it as unverifiable rather
|
|
4929
|
+
than as absence.
|
|
4759
4930
|
|
|
4760
4931
|
Budgets reset on a period the user chose. If a payment exceeds the remaining
|
|
4761
4932
|
budget it is declined before any money moves \u2014 tell the user; they can raise
|
|
@@ -4884,8 +5055,10 @@ check on in-flight payments. Do not poll in a tight loop.
|
|
|
4884
5055
|
## Failure handling
|
|
4885
5056
|
|
|
4886
5057
|
Haven tool failures are shaped like \`{ success: false, code, message, ... }\`
|
|
4887
|
-
or older \`{ error, status, details? }\` responses.
|
|
4888
|
-
|
|
5058
|
+
or older \`{ error, status, details? }\` responses. A failure carries the same
|
|
5059
|
+
\`next_action\` / \`next_tool\` / \`next_arguments\` / \`next_tool_omitted_reason\`
|
|
5060
|
+
fields a success does; follow them first, then branch on \`code\` and surface
|
|
5061
|
+
\`message\` or \`error\` verbatim. Common cases:
|
|
4889
5062
|
|
|
4890
5063
|
- \`insufficient_funds\`: the Haven wallet doesn't hold enough of that token.
|
|
4891
5064
|
Suggest the user add funds in the Haven dashboard.
|
|
@@ -4946,6 +5119,64 @@ for that credential.
|
|
|
4946
5119
|
var SKILL_FOLDER_NAME = "haven-pay";
|
|
4947
5120
|
var HAVEN_SKILL_BODY_MD = HAVEN_SKILL_MD.replace(/^---\n[\s\S]*?\n---\n+/, "");
|
|
4948
5121
|
|
|
5122
|
+
// src/next-step.ts
|
|
5123
|
+
var NEXT_TOOL_SERVER_NAMES = {
|
|
5124
|
+
hosted: "haven",
|
|
5125
|
+
signer: "haven-signer"
|
|
5126
|
+
};
|
|
5127
|
+
var NEXT_TOOL_SERVER_ROLES = {
|
|
5128
|
+
haven: "hosted",
|
|
5129
|
+
"haven-signer": "signer"
|
|
5130
|
+
};
|
|
5131
|
+
function renderNextTool(role, name) {
|
|
5132
|
+
return `mcp__${NEXT_TOOL_SERVER_NAMES[role]}__${name}`;
|
|
5133
|
+
}
|
|
5134
|
+
function parseNextTool(literal) {
|
|
5135
|
+
const m = /^mcp__([a-z0-9-]+)__([a-z0-9_]+)$/.exec(literal);
|
|
5136
|
+
if (!m) return null;
|
|
5137
|
+
const role = NEXT_TOOL_SERVER_ROLES[m[1]];
|
|
5138
|
+
return { server: m[1], name: m[2], ...role ? { role } : {} };
|
|
5139
|
+
}
|
|
5140
|
+
var DEFAULT_NEXT_TOOL_BY_ACTION = {
|
|
5141
|
+
check_status_later: "haven_get_payment_status",
|
|
5142
|
+
sweep_stranded_funds: "haven_sweep_delegate"
|
|
5143
|
+
// `retry_original_x402_request` is NOT here: its only live emitter
|
|
5144
|
+
// (state-direct-recovery.ts, erc7710) names no tool on purpose — the retry
|
|
5145
|
+
// is the agent's own HTTP call — so a default would contradict the site.
|
|
5146
|
+
};
|
|
5147
|
+
function defaultNextToolFor(action) {
|
|
5148
|
+
return DEFAULT_NEXT_TOOL_BY_ACTION[action];
|
|
5149
|
+
}
|
|
5150
|
+
function createNextStepBuilder(targets) {
|
|
5151
|
+
return function nextStep(input) {
|
|
5152
|
+
const i = input;
|
|
5153
|
+
const base = {
|
|
5154
|
+
next_action: input.nextAction,
|
|
5155
|
+
safe_to_continue: input.safeToContinue,
|
|
5156
|
+
reason: input.reason
|
|
5157
|
+
};
|
|
5158
|
+
if (i.nextTool === null) {
|
|
5159
|
+
return { ...base, next_tool_omitted_reason: i.nextToolOmittedReason ?? "no next tool" };
|
|
5160
|
+
}
|
|
5161
|
+
const target = targets[i.nextTool];
|
|
5162
|
+
if (!target) {
|
|
5163
|
+
return { ...base, next_tool_omitted_reason: `${i.nextTool} is not a registered next-step target` };
|
|
5164
|
+
}
|
|
5165
|
+
const problem = target.validate(i.nextArguments);
|
|
5166
|
+
if (problem) {
|
|
5167
|
+
return { ...base, next_tool_omitted_reason: `next_arguments do not parse under ${i.nextTool}: ${problem}` };
|
|
5168
|
+
}
|
|
5169
|
+
return {
|
|
5170
|
+
...base,
|
|
5171
|
+
next_tool: renderNextTool(target.role, i.nextTool),
|
|
5172
|
+
next_tool_server: NEXT_TOOL_SERVER_NAMES[target.role],
|
|
5173
|
+
next_tool_name: i.nextTool,
|
|
5174
|
+
next_tool_server_role: target.role,
|
|
5175
|
+
next_arguments: i.nextArguments
|
|
5176
|
+
};
|
|
5177
|
+
};
|
|
5178
|
+
}
|
|
5179
|
+
|
|
4949
5180
|
// src/node-version.ts
|
|
4950
5181
|
var HAVEN_MINIMUM_NODE_VERSION = "22.0.0";
|
|
4951
5182
|
function parseNodeVersion(value) {
|
|
@@ -5047,7 +5278,6 @@ exports.AgentPaymentFailureCode = AgentPaymentFailureCode;
|
|
|
5047
5278
|
exports.AgentPaymentFailureCodeDescriptions = AgentPaymentFailureCodeDescriptions;
|
|
5048
5279
|
exports.AgentPaymentFailureCodeSchema = AgentPaymentFailureCodeSchema;
|
|
5049
5280
|
exports.AgentPaymentNextAction = AgentPaymentNextAction;
|
|
5050
|
-
exports.AgentPaymentNextActionAccountAlias = AgentPaymentNextActionAccountAlias;
|
|
5051
5281
|
exports.AgentPaymentNextActionDescriptions = AgentPaymentNextActionDescriptions;
|
|
5052
5282
|
exports.AgentPaymentNextActionSchema = AgentPaymentNextActionSchema;
|
|
5053
5283
|
exports.AgentPaymentPhase = AgentPaymentPhase;
|
|
@@ -5059,6 +5289,7 @@ exports.AgentPaymentRailSchema = AgentPaymentRailSchema;
|
|
|
5059
5289
|
exports.AgentPaymentWarningCode = AgentPaymentWarningCode;
|
|
5060
5290
|
exports.CONNECTOR_PACKAGE_NAME = CONNECTOR_PACKAGE_NAME;
|
|
5061
5291
|
exports.DEFAULT_CONFIRMATION_TIMEOUT_MS = DEFAULT_CONFIRMATION_TIMEOUT_MS;
|
|
5292
|
+
exports.DEFAULT_NEXT_TOOL_BY_ACTION = DEFAULT_NEXT_TOOL_BY_ACTION;
|
|
5062
5293
|
exports.DISCOVERY_MAX_BYTES = DISCOVERY_MAX_BYTES;
|
|
5063
5294
|
exports.ERC7710_ASSET_TRANSFER_METHOD = ERC7710_ASSET_TRANSFER_METHOD;
|
|
5064
5295
|
exports.HAVEN_AGENT_RUNBOOK_MD = HAVEN_AGENT_RUNBOOK_MD;
|
|
@@ -5069,13 +5300,17 @@ exports.HAVEN_SKILL_MD = HAVEN_SKILL_MD;
|
|
|
5069
5300
|
exports.HavenApiError = HavenApiError;
|
|
5070
5301
|
exports.HavenClient = HavenClient;
|
|
5071
5302
|
exports.HavenError = HavenError;
|
|
5303
|
+
exports.HavenInsecureRetryTargetError = HavenInsecureRetryTargetError;
|
|
5072
5304
|
exports.HavenPaymentStateError = HavenPaymentStateError;
|
|
5073
5305
|
exports.HavenSigningError = HavenSigningError;
|
|
5074
5306
|
exports.HavenTimeoutError = HavenTimeoutError;
|
|
5075
5307
|
exports.HavenUnsupportedSignerVersionError = HavenUnsupportedSignerVersionError;
|
|
5076
5308
|
exports.HavenZeroSettlementHashError = HavenZeroSettlementHashError;
|
|
5309
|
+
exports.INSECURE_RETRY_TARGET_CODE = INSECURE_RETRY_TARGET_CODE;
|
|
5077
5310
|
exports.MERCHANT_DISCOVERY_PATHS = MERCHANT_DISCOVERY_PATHS;
|
|
5078
5311
|
exports.MerchantTimeoutError = MerchantTimeoutError;
|
|
5312
|
+
exports.NEXT_TOOL_SERVER_NAMES = NEXT_TOOL_SERVER_NAMES;
|
|
5313
|
+
exports.NEXT_TOOL_SERVER_ROLES = NEXT_TOOL_SERVER_ROLES;
|
|
5079
5314
|
exports.RECEIPT_VERSION = RECEIPT_VERSION;
|
|
5080
5315
|
exports.SIGNER_UPDATE_FALLBACK = SIGNER_UPDATE_FALLBACK;
|
|
5081
5316
|
exports.SKILL_FOLDER_NAME = SKILL_FOLDER_NAME;
|
|
@@ -5095,18 +5330,19 @@ exports.X402_PAYMENT_HEADER_NAMES_SENT = X402_PAYMENT_HEADER_NAMES_SENT;
|
|
|
5095
5330
|
exports.X402_PAYMENT_REQUIRED_HEADER_NAME = X402_PAYMENT_REQUIRED_HEADER_NAME;
|
|
5096
5331
|
exports.X402_PAYMENT_RESPONSE_HEADER_NAME = X402_PAYMENT_RESPONSE_HEADER_NAME;
|
|
5097
5332
|
exports.X402_SETTLEMENT_FORWARD_MARGIN_SECONDS = X402_SETTLEMENT_FORWARD_MARGIN_SECONDS;
|
|
5098
|
-
exports.accountAddressTwins = accountAddressTwins;
|
|
5099
5333
|
exports.addressFromKey = addressFromKey;
|
|
5334
|
+
exports.assertSecureX402RetryTarget = assertSecureX402RetryTarget;
|
|
5100
5335
|
exports.buildSweepAuthorizationMessage = buildSweepAuthorizationMessage;
|
|
5101
5336
|
exports.buildSweepTypedData = buildSweepTypedData;
|
|
5102
5337
|
exports.buildX402ExpectedMessage = buildX402ExpectedMessage;
|
|
5103
|
-
exports.canonicalAgentPaymentNextAction = canonicalAgentPaymentNextAction;
|
|
5104
5338
|
exports.compareNodeVersions = compareNodeVersions;
|
|
5105
5339
|
exports.composeDescription = composeDescription;
|
|
5106
5340
|
exports.connectorRerunCommand = connectorRerunCommand;
|
|
5107
5341
|
exports.connectorSpec = connectorSpec;
|
|
5342
|
+
exports.createNextStepBuilder = createNextStepBuilder;
|
|
5108
5343
|
exports.decodeBase64Json = decodeBase64Json;
|
|
5109
5344
|
exports.decodeBase64Utf8 = decodeBase64Utf8;
|
|
5345
|
+
exports.defaultNextToolFor = defaultNextToolFor;
|
|
5110
5346
|
exports.discoverMerchantMcpUrl = discoverMerchantMcpUrl;
|
|
5111
5347
|
exports.encodeBase64Json = encodeBase64Json;
|
|
5112
5348
|
exports.encodeBase64Utf8 = encodeBase64Utf8;
|
|
@@ -5114,18 +5350,19 @@ exports.encodePaymentProof = encodePaymentProof;
|
|
|
5114
5350
|
exports.havenTools = havenTools;
|
|
5115
5351
|
exports.isConnectorChannel = isConnectorChannel;
|
|
5116
5352
|
exports.isErc7710Option = isErc7710Option;
|
|
5117
|
-
exports.
|
|
5353
|
+
exports.isSecureX402RetryTarget = isSecureX402RetryTarget;
|
|
5118
5354
|
exports.isSupportedNodeVersion = isSupportedNodeVersion;
|
|
5119
5355
|
exports.isSweepableChain = isSweepableChain;
|
|
5120
5356
|
exports.isZeroSettlementTxHash = isZeroSettlementTxHash;
|
|
5121
5357
|
exports.normalizePaymentRequired = normalizePaymentRequired;
|
|
5358
|
+
exports.parseNextTool = parseNextTool;
|
|
5122
5359
|
exports.parsePaymentRequired = parsePaymentRequired;
|
|
5123
5360
|
exports.parsePaymentRequiredResponse = parsePaymentRequiredResponse;
|
|
5124
|
-
exports.readAccountAddress = readAccountAddress;
|
|
5125
|
-
exports.readAccountId = readAccountId;
|
|
5126
5361
|
exports.readX402ReceiptPayer = readX402ReceiptPayer;
|
|
5362
|
+
exports.renderNextTool = renderNextTool;
|
|
5127
5363
|
exports.resolveConnectorChannel = resolveConnectorChannel;
|
|
5128
5364
|
exports.resolveTokenFromAddress = resolveTokenFromAddress;
|
|
5365
|
+
exports.resolveX402RetryTarget = resolveX402RetryTarget;
|
|
5129
5366
|
exports.sameUrl = sameUrl;
|
|
5130
5367
|
exports.selectErc7710PaymentOption = selectErc7710PaymentOption;
|
|
5131
5368
|
exports.selectPaymentOption = selectPaymentOption;
|