@haven_ai/sdk 0.4.0-alpha.0 → 0.5.0-alpha.1
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 +53 -3
- package/dist/base64-BUMXmruY.d.cts +2731 -0
- package/dist/base64-BUMXmruY.d.ts +2731 -0
- package/dist/chunk-GLNG2GF7.js +903 -0
- package/dist/chunk-GLNG2GF7.js.map +1 -0
- package/dist/chunk-JKUIE45B.cjs +964 -0
- package/dist/chunk-JKUIE45B.cjs.map +1 -0
- package/dist/chunk-KQ7JXKTV.js +902 -0
- package/dist/chunk-KQ7JXKTV.js.map +1 -0
- package/dist/chunk-QSSVOPWA.cjs +970 -0
- package/dist/chunk-QSSVOPWA.cjs.map +1 -0
- package/dist/edge.cjs +271 -0
- package/dist/edge.cjs.map +1 -0
- package/dist/edge.d.cts +105 -0
- package/dist/edge.d.ts +105 -0
- package/dist/edge.js +56 -0
- package/dist/edge.js.map +1 -0
- package/dist/index.cjs +899 -1453
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +30 -2322
- package/dist/index.d.ts +30 -2322
- package/dist/index.js +319 -1215
- package/dist/index.js.map +1 -1
- package/dist/test-support.cjs +215 -0
- package/dist/test-support.cjs.map +1 -0
- package/dist/test-support.d.cts +204 -0
- package/dist/test-support.d.ts +204 -0
- package/dist/test-support.js +200 -0
- package/dist/test-support.js.map +1 -0
- package/package.json +23 -1
|
@@ -0,0 +1,903 @@
|
|
|
1
|
+
import { pad, keccak256, encodeAbiParameters, getAddress, encodeFunctionData, encodeDeployData, getContractAddress, decodeFunctionData, decodeAbiParameters, isAddress } from 'viem';
|
|
2
|
+
|
|
3
|
+
// src/connector-channel.ts
|
|
4
|
+
var CONNECTOR_PACKAGE_NAME = "@haven_ai/connect";
|
|
5
|
+
var HAVEN_CONNECTOR_CHANNEL = "alpha";
|
|
6
|
+
var CHANNEL_PATTERN = /^[a-z][a-z0-9-]{0,31}$/;
|
|
7
|
+
function isConnectorChannel(value) {
|
|
8
|
+
return CHANNEL_PATTERN.test(value);
|
|
9
|
+
}
|
|
10
|
+
function resolveConnectorChannel(raw, fallback = HAVEN_CONNECTOR_CHANNEL) {
|
|
11
|
+
const trimmed = (raw ?? "").trim();
|
|
12
|
+
if (trimmed === "") return fallback;
|
|
13
|
+
if (!isConnectorChannel(trimmed)) {
|
|
14
|
+
throw new Error(
|
|
15
|
+
`HAVEN_CONNECTOR_CHANNEL is set to ${JSON.stringify(raw)}, which is not a valid npm dist-tag (lowercase letter first, then letters, digits or hyphens). Refusing to start rather than fall back to the default channel, because falling back would hand out the production connector while looking configured.`
|
|
16
|
+
);
|
|
17
|
+
}
|
|
18
|
+
return trimmed;
|
|
19
|
+
}
|
|
20
|
+
function connectorSpec(channel = HAVEN_CONNECTOR_CHANNEL) {
|
|
21
|
+
return `${CONNECTOR_PACKAGE_NAME}@${channel}`;
|
|
22
|
+
}
|
|
23
|
+
function connectorRerunCommand(args, options) {
|
|
24
|
+
const channel = options?.channel ?? HAVEN_CONNECTOR_CHANNEL;
|
|
25
|
+
const flags = options?.npxFlags ? `${options.npxFlags} ` : "";
|
|
26
|
+
const command = `npx ${flags}${connectorSpec(channel)}`;
|
|
27
|
+
return args ? `${command} ${args}` : command;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
// src/types.ts
|
|
31
|
+
var DEFAULT_CONFIRMATION_TIMEOUT_MS = 9e4;
|
|
32
|
+
var AgentPaymentPhase = {
|
|
33
|
+
/** The agent must sign and submit the prepared payment before Haven can relay it. */
|
|
34
|
+
AgentSignatureRequired: "agent_signature_required",
|
|
35
|
+
/** Haven has received the signed payment and the agent should poll for confirmation. */
|
|
36
|
+
PaymentSubmitted: "payment_submitted",
|
|
37
|
+
/** The direct payment is confirmed; the agent does not need to do more for this payment id. */
|
|
38
|
+
PaymentConfirmed: "payment_confirmed",
|
|
39
|
+
/**
|
|
40
|
+
* #2115: RETIRED wire value — no live rail produces it. It described the
|
|
41
|
+
* Safe rail's approval queue, which no longer exists. Kept so a stored value
|
|
42
|
+
* still typechecks; see `AgentPaymentPhaseDescriptions` below for the
|
|
43
|
+
* agent-visible wording, which this comment used to contradict.
|
|
44
|
+
*/
|
|
45
|
+
UserApprovalRequired: "user_approval_required",
|
|
46
|
+
/** #2115: RETIRED wire value — no live rail produces it. Stop and tell the user. */
|
|
47
|
+
UserExecutionRequired: "user_execution_required",
|
|
48
|
+
/** #2115: RETIRED wire value — no live rail produces it. Stop and tell the user. */
|
|
49
|
+
WaitingForAdditionalApprovals: "waiting_for_additional_approvals",
|
|
50
|
+
/** The Haven funding leg was sent; the agent can continue the merchant/protocol leg. */
|
|
51
|
+
FundingSent: "funding_sent",
|
|
52
|
+
/** The payment was rejected and cannot proceed; the agent should stop and tell the user. */
|
|
53
|
+
Rejected: "rejected",
|
|
54
|
+
/** The payment expired before completion. */
|
|
55
|
+
Expired: "expired",
|
|
56
|
+
/** Haven could not complete the payment; the agent should stop and surface the failure. */
|
|
57
|
+
Failed: "failed",
|
|
58
|
+
/**
|
|
59
|
+
* Pre-flight check determined the delegate's existing balance plus the
|
|
60
|
+
* remaining on-chain budget cannot cover the requested amount, so no
|
|
61
|
+
* payment intent was created. The account must be funded or the agent's
|
|
62
|
+
* budget raised before retrying — #2115: the old wording contrasted this
|
|
63
|
+
* with `UserApprovalRequired` as if that were a live alternative, and named
|
|
64
|
+
* the retired rail's Safe and per-token allowance as the fix.
|
|
65
|
+
*/
|
|
66
|
+
InsufficientFunds: "insufficient_funds",
|
|
67
|
+
/**
|
|
68
|
+
* Haven's funding leg (account → delegate, the #946 EIP-3009 bridge)
|
|
69
|
+
* confirmed on-chain, but the merchant rejected the x402 retry. The delegate
|
|
70
|
+
* wallet may hold stranded USDC that was never settled to the merchant. The
|
|
71
|
+
* agent should stop, tell the user, and wait for the sweep flow to reclaim
|
|
72
|
+
* the funds.
|
|
73
|
+
*/
|
|
74
|
+
FundedButUnsettled: "funded_but_unsettled"
|
|
75
|
+
};
|
|
76
|
+
var AgentPaymentNextAction = {
|
|
77
|
+
/** Sign with the delegate key and submit the payment to Haven. */
|
|
78
|
+
SignAndSubmitPayment: "sign_and_submit_payment",
|
|
79
|
+
/** Poll getPaymentStatus later using this payment id. */
|
|
80
|
+
CheckStatusLater: "check_status_later",
|
|
81
|
+
/** No further agent action is required for this payment id. */
|
|
82
|
+
None: "none",
|
|
83
|
+
/**
|
|
84
|
+
* #2115: RETIRED wire value — no live rail produces it and nothing maps to
|
|
85
|
+
* it. Stop and tell the user rather than polling; no approval will arrive.
|
|
86
|
+
*/
|
|
87
|
+
WaitForUserApproval: "wait_for_user_approval",
|
|
88
|
+
/** #2115: RETIRED wire value — no live rail produces it. Stop and tell the user rather than polling. */
|
|
89
|
+
WaitForUserToCompletePayment: "wait_for_user_to_complete_payment",
|
|
90
|
+
/** Resume this payment id and retry the original x402 request with the merchant payment header. */
|
|
91
|
+
RetryOriginalX402Request: "retry_original_x402_request",
|
|
92
|
+
/** Stop retrying this payment and tell the user what happened. */
|
|
93
|
+
StopAndTellUser: "stop_and_tell_user",
|
|
94
|
+
/** Ask again only if the user still wants the payment after expiry. */
|
|
95
|
+
RequestAgainIfUserStillWantsIt: "request_again_if_user_still_wants_it",
|
|
96
|
+
/** #1307: retry the SAME tool call, supplying the explicit context fields the server could not rehydrate. */
|
|
97
|
+
RetryWithExplicitContext: "retry_with_explicit_context",
|
|
98
|
+
/**
|
|
99
|
+
* The x402 funding/quote window expired. Re-quote the same logical merchant
|
|
100
|
+
* operation with the same idempotency key to stay double-charge-safe.
|
|
101
|
+
*/
|
|
102
|
+
PaymentWindowExpired: "payment_window_expired",
|
|
103
|
+
/**
|
|
104
|
+
* Stop and tell the user that the originating account needs to be funded or
|
|
105
|
+
* the agent's per-token allowance needs to be raised before the payment
|
|
106
|
+
* can succeed. A user approval will not fix this state on its own.
|
|
107
|
+
*
|
|
108
|
+
* #2914: the account-vocabulary spelling, and the only one — the
|
|
109
|
+
* pre-#2907 `fund_safe_or_raise_allowance` wire value (and the
|
|
110
|
+
* `AgentPaymentNextActionAccountAlias` seam #2908 added to bridge it) are
|
|
111
|
+
* retired along with the rest of the #2908 compatibility window.
|
|
112
|
+
*/
|
|
113
|
+
FundAccountOrRaiseAllowance: "fund_account_or_raise_allowance",
|
|
114
|
+
/**
|
|
115
|
+
* The delegate wallet may hold funds that were sent from the agent's Haven
|
|
116
|
+
* account but never settled to the merchant. The wallet owner should initiate
|
|
117
|
+
* a sweep to return those funds to that account.
|
|
118
|
+
*/
|
|
119
|
+
SweepStrandedFunds: "sweep_stranded_funds",
|
|
120
|
+
/**
|
|
121
|
+
* #2970: a `submitted` erc7710 x402 intent whose settlement window has
|
|
122
|
+
* passed with no on-chain settlement evidence Haven could verify. Distinct
|
|
123
|
+
* from {@link CheckStatusLater}, which this REPLACES once the window is
|
|
124
|
+
* past — but it is not futile: Haven's settlement sweep (120s tick) scans
|
|
125
|
+
* each candidate over its own window plus a 120s clock-skew allowance, so
|
|
126
|
+
* it can still attribute the settlement for a short while after this value
|
|
127
|
+
* first appears. Poll {@link CheckStatusLater}'s tool
|
|
128
|
+
* (`haven_get_payment_status`) once more, roughly two minutes later; if it
|
|
129
|
+
* still shows no evidence, tell the user the goods were delivered but
|
|
130
|
+
* Haven holds no verified settlement evidence for this payment. If the
|
|
131
|
+
* agent holds the merchant's real settlement transaction hash (from
|
|
132
|
+
* `PAYMENT-RESPONSE`'s `transaction` field, or a prior settle/complete
|
|
133
|
+
* result's `settlement_tx_hash`), report it with the hosted
|
|
134
|
+
* `haven_report_settlement_evidence` tool instead of waiting —
|
|
135
|
+
* `haven_report_x402_outcome` takes no hash and refuses a non-`confirmed`
|
|
136
|
+
* intent.
|
|
137
|
+
*/
|
|
138
|
+
AwaitingSettlementEvidence: "awaiting_settlement_evidence"
|
|
139
|
+
};
|
|
140
|
+
var AgentPaymentFailureCode = {
|
|
141
|
+
/** A merchant-authoritative x402 price exceeds the caller's pre-funding max_amount cap. */
|
|
142
|
+
PriceExceedsMax: "PRICE_EXCEEDS_MAX",
|
|
143
|
+
/** The x402 funding/quote window expired before the signer or hosted settle step could finish. */
|
|
144
|
+
PaymentWindowExpired: "PAYMENT_WINDOW_EXPIRED",
|
|
145
|
+
/** The merchant rejected the paid retry. On eip3009 the funding leg had succeeded (sweep);
|
|
146
|
+
* on erc7710 there is no funding leg — nothing to sweep, follow the message (#2983). */
|
|
147
|
+
MerchantRejectedAfterFunding: "MERCHANT_REJECTED_AFTER_FUNDING",
|
|
148
|
+
/** #1300 review: funding is on-chain but the merchant never ANSWERED the
|
|
149
|
+
* paid retry within the timeout. NOT proof of rejection — the merchant
|
|
150
|
+
* may still settle late, so the guidance is verify-then-act. On eip3009
|
|
151
|
+
* the funding leg had succeeded (verify-then-sweep); on erc7710 there is
|
|
152
|
+
* no funding leg — nothing to sweep, follow the message (#3000). */
|
|
153
|
+
MerchantUnresponsiveAfterFunding: "MERCHANT_UNRESPONSIVE_AFTER_FUNDING",
|
|
154
|
+
/**
|
|
155
|
+
* #1307: the caller omitted merchant_url/tool_name (asking Haven to
|
|
156
|
+
* rehydrate the stored MCP merchant-call context by payment_id), but no
|
|
157
|
+
* usable context was stored for this intent — either it was never an
|
|
158
|
+
* MCP-tool quote, or the stored context is incomplete. The fallback is
|
|
159
|
+
* mechanical: re-send merchant_url, tool_name, arguments, and
|
|
160
|
+
* mcp_transport explicitly (the version-skew path).
|
|
161
|
+
*/
|
|
162
|
+
MerchantCallContextUnavailable: "MERCHANT_CALL_CONTEXT_UNAVAILABLE",
|
|
163
|
+
/**
|
|
164
|
+
* #1351: the caller supplied BOTH the atomic `max_amount` and the
|
|
165
|
+
* human-denominated `max_amount_human` cap for one purchase. Haven refuses
|
|
166
|
+
* to guess which the user meant — the two differ by a factor of 10^decimals,
|
|
167
|
+
* so picking wrong is exactly the silent-overspend this cap exists to
|
|
168
|
+
* prevent. Rejected before any merchant probe, funding intent, or signature.
|
|
169
|
+
*/
|
|
170
|
+
AmbiguousMaxAmount: "AMBIGUOUS_MAX_AMOUNT",
|
|
171
|
+
/**
|
|
172
|
+
* #1351: a human-denominated cap was supplied, but it cannot be converted to
|
|
173
|
+
* atomic units against THIS quote — either the quote's asset has no known
|
|
174
|
+
* decimals on its network, or the cap carries more fraction digits than the
|
|
175
|
+
* asset can represent (truncating it would silently change the user's cap).
|
|
176
|
+
* The fallback is the exact atomic `max_amount`.
|
|
177
|
+
*/
|
|
178
|
+
MaxAmountUnconvertible: "MAX_AMOUNT_UNCONVERTIBLE",
|
|
179
|
+
/**
|
|
180
|
+
* #2979: the merchant answered a `tools/call` probe with its own
|
|
181
|
+
* machine-readable "cannot settle right now" refusal (HTTP 503,
|
|
182
|
+
* `{ error: 'merchant_not_ready', reason_code, ... }`) instead of a 402
|
|
183
|
+
* challenge — e.g. its settlement wallet is out of gas. No 402 was ever
|
|
184
|
+
* issued and no payment was created; this is honest and (per
|
|
185
|
+
* `retry_after_s`, when present) usually transient, unlike a permanent
|
|
186
|
+
* endpoint miss.
|
|
187
|
+
*/
|
|
188
|
+
MerchantNotReady: "MERCHANT_NOT_READY"
|
|
189
|
+
};
|
|
190
|
+
var AgentPaymentRail = {
|
|
191
|
+
/** Standard Haven payment from the user's account, redeemed through the agent's budget delegation. */
|
|
192
|
+
Direct: "direct",
|
|
193
|
+
/** x402 HTTP 402 payment flow with a Haven funding leg and merchant retry leg. */
|
|
194
|
+
X402: "x402",
|
|
195
|
+
/** Machine Payment Protocol family — categorical value used as a resume-state discriminator. */
|
|
196
|
+
Mpp: "mpp",
|
|
197
|
+
/** Haven internal MPP demo rail. Not for production traffic. */
|
|
198
|
+
MppDemo: "mpp_demo",
|
|
199
|
+
/** Crypto-settled MPP rail. */
|
|
200
|
+
MppCrypto: "mpp_crypto",
|
|
201
|
+
/** Stripe-deposit-backed MPP rail. */
|
|
202
|
+
StripeDeposit: "stripe_deposit",
|
|
203
|
+
/** Stripe Payment Token MPP rail. */
|
|
204
|
+
Spt: "spt"
|
|
205
|
+
};
|
|
206
|
+
var AGENT_PAYMENT_PHASE_VALUES = Object.values(AgentPaymentPhase);
|
|
207
|
+
var AGENT_PAYMENT_NEXT_ACTION_VALUES = Object.values(AgentPaymentNextAction);
|
|
208
|
+
var AGENT_PAYMENT_FAILURE_CODE_VALUES = Object.values(AgentPaymentFailureCode);
|
|
209
|
+
var AGENT_PAYMENT_RAIL_VALUES = Object.values(AgentPaymentRail);
|
|
210
|
+
var AgentPaymentPhaseDescriptions = {
|
|
211
|
+
[AgentPaymentPhase.AgentSignatureRequired]: "The agent must sign and submit the prepared payment before Haven can relay it.",
|
|
212
|
+
[AgentPaymentPhase.PaymentSubmitted]: "Haven has received the signed payment and the agent should poll for confirmation.",
|
|
213
|
+
[AgentPaymentPhase.PaymentConfirmed]: "The direct payment is confirmed; the agent does not need to do more for this payment id.",
|
|
214
|
+
[AgentPaymentPhase.UserApprovalRequired]: "Retired wire value: no live rail produces it. It described the Safe rail's approval queue, which no longer exists \u2014 an out-of-policy payment is declined before any money moves. If it is ever seen, stop and tell the user; no approval is pending.",
|
|
215
|
+
[AgentPaymentPhase.UserExecutionRequired]: "Retired wire value: no live rail produces it. Stop and tell the user.",
|
|
216
|
+
[AgentPaymentPhase.WaitingForAdditionalApprovals]: "Retired wire value: no live rail produces it. Stop and tell the user.",
|
|
217
|
+
[AgentPaymentPhase.FundingSent]: "The Haven funding leg was sent; the agent can continue the merchant/protocol leg.",
|
|
218
|
+
[AgentPaymentPhase.Rejected]: "The payment was rejected and cannot proceed; the agent should stop and tell the user.",
|
|
219
|
+
[AgentPaymentPhase.Expired]: "The payment expired before completion.",
|
|
220
|
+
[AgentPaymentPhase.Failed]: "Haven could not complete the payment; the agent should stop and surface the failure.",
|
|
221
|
+
[AgentPaymentPhase.InsufficientFunds]: "Pre-flight check determined the delegate balance plus the remaining on-chain budget cannot cover the requested amount, so no payment was created. The account must be funded or the agent budget raised before retrying.",
|
|
222
|
+
[AgentPaymentPhase.FundedButUnsettled]: "Haven's funding leg confirmed on-chain but the merchant rejected the x402 retry. The delegate wallet may hold stranded funds. The agent should stop and wait for the wallet owner to sweep the stranded funds back to the account."
|
|
223
|
+
};
|
|
224
|
+
var AgentPaymentNextActionDescriptions = {
|
|
225
|
+
[AgentPaymentNextAction.SignAndSubmitPayment]: "Sign with the delegate key and submit the payment to Haven.",
|
|
226
|
+
[AgentPaymentNextAction.CheckStatusLater]: "Poll getPaymentStatus later using this payment id.",
|
|
227
|
+
[AgentPaymentNextAction.None]: "No further agent action is required for this payment id.",
|
|
228
|
+
[AgentPaymentNextAction.WaitForUserApproval]: "Retired wire value: no live rail produces it, and nothing maps to it. It described a per-payment approval queue that no longer exists. If it is ever seen, stop and tell the user rather than polling \u2014 no approval will arrive.",
|
|
229
|
+
[AgentPaymentNextAction.WaitForUserToCompletePayment]: "Retired wire value: no live rail produces it. Stop and tell the user rather than polling.",
|
|
230
|
+
[AgentPaymentNextAction.RetryOriginalX402Request]: "Resume this payment id and retry the original x402 request with the merchant payment header.",
|
|
231
|
+
[AgentPaymentNextAction.StopAndTellUser]: "Stop retrying this payment and tell the user what happened.",
|
|
232
|
+
[AgentPaymentNextAction.RequestAgainIfUserStillWantsIt]: "Ask again only if the user still wants the payment after expiry.",
|
|
233
|
+
[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.",
|
|
234
|
+
[AgentPaymentNextAction.FundAccountOrRaiseAllowance]: "Stop and tell the user that the account needs to be funded or the agent budget raised before the payment can succeed.",
|
|
235
|
+
[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.",
|
|
236
|
+
[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.",
|
|
237
|
+
[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."
|
|
238
|
+
};
|
|
239
|
+
var AgentPaymentFailureCodeDescriptions = {
|
|
240
|
+
[AgentPaymentFailureCode.PriceExceedsMax]: "The merchant-authoritative x402 amount exceeds the caller's max_amount cap. No funding transfer was created; ask the user before retrying with a larger cap.",
|
|
241
|
+
[AgentPaymentFailureCode.PaymentWindowExpired]: "The x402 funding/quote window expired before the signer or hosted settle step could finish. Re-quote via haven_pay_mcp_tool with the same idempotency key to avoid duplicate funding.",
|
|
242
|
+
[AgentPaymentFailureCode.MerchantRejectedAfterFunding]: "The merchant rejected the paid retry. eip3009: the funding leg succeeded \u2014 stop retrying the merchant and reconcile stranded delegate funds with haven_sweep_delegate. erc7710: no funding leg, nothing to sweep \u2014 follow the message (re-quote later, or check haven_get_payment_status after the window first).",
|
|
243
|
+
[AgentPaymentFailureCode.MerchantUnresponsiveAfterFunding]: "The merchant did not answer the paid retry before the timeout. The merchant may still settle late. eip3009: the funding leg succeeded \u2014 check haven_get_payment_status, retry haven_complete_mcp_tool once, sweep only if no settlement appears. erc7710: no funding leg, nothing to sweep, and haven_complete_mcp_tool has no erc7710 branch \u2014 do not retry it; check haven_get_payment_status after the payment window and re-quote only if it shows no settlement.",
|
|
244
|
+
[AgentPaymentFailureCode.MerchantCallContextUnavailable]: "merchant_url/tool_name were omitted and no stored merchant call context is available for this payment_id. Re-send merchant_url, tool_name, arguments, and mcp_transport explicitly.",
|
|
245
|
+
[AgentPaymentFailureCode.AmbiguousMaxAmount]: "Both max_amount (atomic units) and max_amount_human (whole tokens) were supplied for one purchase. Nothing was contacted and nothing was spent. Re-send with exactly ONE: max_amount_human for a cap the user stated in tokens, max_amount for an exact atomic figure.",
|
|
246
|
+
[AgentPaymentFailureCode.MaxAmountUnconvertible]: "max_amount_human could not be converted to atomic units against this quote's asset \u2014 either its decimals are unknown to Haven or the cap has more decimal places than the asset supports. Nothing was spent. Round the cap, or re-send it as an exact atomic max_amount.",
|
|
247
|
+
[AgentPaymentFailureCode.MerchantNotReady]: 'The merchant refused the probe with its own "cannot settle right now" signal instead of a 402 challenge. No payment was created. Often transient \u2014 retry later (see retry_after_s in the message, if given) rather than treating this as a broken or wrong endpoint.'
|
|
248
|
+
};
|
|
249
|
+
var AgentPaymentWarningCode = {
|
|
250
|
+
/** No max_amount cap was supplied — the live quoted price was accepted as-is. */
|
|
251
|
+
MissingMaxAmount: "MISSING_MAX_AMOUNT",
|
|
252
|
+
/** The signing window closes soon; sign promptly or re-quote with the same idempotency key. */
|
|
253
|
+
QuoteExpiresSoon: "QUOTE_EXPIRES_SOON",
|
|
254
|
+
/** The merchant URL was resolved via discovery — pass the RESOLVED url forward. */
|
|
255
|
+
MerchantUrlDiscovered: "MERCHANT_URL_DISCOVERED",
|
|
256
|
+
/**
|
|
257
|
+
* #1306: the catalog's last-verified price_atomic differs from the LIVE
|
|
258
|
+
* merchant quote for a guided catalog purchase. The catalog price is only
|
|
259
|
+
* ever indicative; the live quote in the same response is authoritative.
|
|
260
|
+
*/
|
|
261
|
+
CatalogPriceDiffers: "CATALOG_PRICE_DIFFERS",
|
|
262
|
+
/**
|
|
263
|
+
* #1306: the rail-aware allowance/budget pre-check could not be read (RPC
|
|
264
|
+
* failure, etc). `sufficient` is reported as null rather than a fabricated
|
|
265
|
+
* true/false — the on-chain policy remains the actual gate either way.
|
|
266
|
+
*/
|
|
267
|
+
AllowanceCheckUnavailable: "ALLOWANCE_CHECK_UNAVAILABLE",
|
|
268
|
+
/**
|
|
269
|
+
* #1319: the delegation-rail read itself SUCCEEDED, but the remaining
|
|
270
|
+
* figure it returned is the #1145 fallback (the full configured budget)
|
|
271
|
+
* rather than a live ERC20PeriodTransferEnforcer read — `sufficient` is a
|
|
272
|
+
* real true/false, just computed from an optimistic number. Distinct from
|
|
273
|
+
* {@link AgentPaymentWarningCode.AllowanceCheckUnavailable}, which fires
|
|
274
|
+
* when the read failed outright and `sufficient` degrades to null. The
|
|
275
|
+
* on-chain policy re-checks at redemption either way; this only says the
|
|
276
|
+
* guidance shown here may be optimistic.
|
|
277
|
+
*/
|
|
278
|
+
AllowanceReadOptimistic: "ALLOWANCE_READ_OPTIMISTIC",
|
|
279
|
+
/**
|
|
280
|
+
* #2991: the quote tools' `expected_settlement_scheme` prediction of what
|
|
281
|
+
* `haven_prepare_catalog_purchase` / `haven_pay_mcp_tool` will actually
|
|
282
|
+
* select could not be computed — the agent's execution rail could not be
|
|
283
|
+
* read from Haven, so `expected_settlement_scheme` is `null` rather than a
|
|
284
|
+
* guess. `accepted_scheme` (the merchant's offer) is unaffected.
|
|
285
|
+
*/
|
|
286
|
+
X402SchemeUnknown: "X402_SCHEME_UNKNOWN",
|
|
287
|
+
/**
|
|
288
|
+
* #2968: the merchant answered 200 and handed over goods, but Haven holds NO
|
|
289
|
+
* on-chain evidence that the payment moved. `settled: false` beside this code
|
|
290
|
+
* is not a failure — it is the absence of proof, and the two must travel
|
|
291
|
+
* together so an agent can tell "the user has the goods" apart from "the
|
|
292
|
+
* money moved". Carries the intent's `expires_at`: after that instant the
|
|
293
|
+
* settlement can no longer land at all.
|
|
294
|
+
*/
|
|
295
|
+
SettlementUnconfirmed: "SETTLEMENT_UNCONFIRMED"
|
|
296
|
+
};
|
|
297
|
+
var AgentPaymentRailDescriptions = {
|
|
298
|
+
[AgentPaymentRail.Direct]: "Standard Haven payment from the user-controlled account, redeeming the agent's on-chain budget delegation.",
|
|
299
|
+
[AgentPaymentRail.X402]: "x402 HTTP 402 payment flow with a Haven funding leg and merchant retry leg.",
|
|
300
|
+
[AgentPaymentRail.Mpp]: "Categorical MPP rail value used as a resume-state discriminator. Response bodies carry a granular mpp_* value instead.",
|
|
301
|
+
[AgentPaymentRail.MppDemo]: "Haven internal MPP demo rail. Not for production traffic.",
|
|
302
|
+
[AgentPaymentRail.MppCrypto]: "Crypto-settled MPP rail.",
|
|
303
|
+
[AgentPaymentRail.StripeDeposit]: "Stripe-deposit-backed MPP rail.",
|
|
304
|
+
[AgentPaymentRail.Spt]: "Stripe Payment Token MPP rail."
|
|
305
|
+
};
|
|
306
|
+
var AgentPaymentPhaseSchema = {
|
|
307
|
+
type: "string",
|
|
308
|
+
enum: AGENT_PAYMENT_PHASE_VALUES,
|
|
309
|
+
description: "Stable Haven agent payment state phase.",
|
|
310
|
+
"x-enumDescriptions": AgentPaymentPhaseDescriptions
|
|
311
|
+
};
|
|
312
|
+
var AgentPaymentNextActionSchema = {
|
|
313
|
+
type: "string",
|
|
314
|
+
enum: AGENT_PAYMENT_NEXT_ACTION_VALUES,
|
|
315
|
+
description: "Stable next action an agent should take for a Haven payment state.",
|
|
316
|
+
"x-enumDescriptions": AgentPaymentNextActionDescriptions
|
|
317
|
+
};
|
|
318
|
+
var AgentPaymentFailureCodeSchema = {
|
|
319
|
+
type: "string",
|
|
320
|
+
enum: AGENT_PAYMENT_FAILURE_CODE_VALUES,
|
|
321
|
+
description: "Stable machine-readable failure codes for Haven agent payment recovery paths.",
|
|
322
|
+
"x-enumDescriptions": AgentPaymentFailureCodeDescriptions
|
|
323
|
+
};
|
|
324
|
+
var AgentPaymentRailSchema = {
|
|
325
|
+
type: "string",
|
|
326
|
+
enum: AGENT_PAYMENT_RAIL_VALUES,
|
|
327
|
+
description: "Stable rail identifier for Haven agent payment states.",
|
|
328
|
+
"x-enumDescriptions": AgentPaymentRailDescriptions
|
|
329
|
+
};
|
|
330
|
+
var HavenError = class extends Error {
|
|
331
|
+
constructor(message, code, statusCode, paymentId) {
|
|
332
|
+
super(message);
|
|
333
|
+
this.code = code;
|
|
334
|
+
this.statusCode = statusCode;
|
|
335
|
+
this.paymentId = paymentId;
|
|
336
|
+
this.name = "HavenError";
|
|
337
|
+
}
|
|
338
|
+
code;
|
|
339
|
+
statusCode;
|
|
340
|
+
paymentId;
|
|
341
|
+
};
|
|
342
|
+
var HavenApiError = class extends HavenError {
|
|
343
|
+
constructor(message, statusCode, body, paymentId) {
|
|
344
|
+
super(message, "API_ERROR", statusCode, paymentId);
|
|
345
|
+
this.body = body;
|
|
346
|
+
this.name = "HavenApiError";
|
|
347
|
+
}
|
|
348
|
+
body;
|
|
349
|
+
};
|
|
350
|
+
var MerchantTimeoutError = class extends HavenApiError {
|
|
351
|
+
merchantErrorCode = "merchant_timeout";
|
|
352
|
+
constructor(message) {
|
|
353
|
+
super(message, 504);
|
|
354
|
+
this.name = "MerchantTimeoutError";
|
|
355
|
+
}
|
|
356
|
+
};
|
|
357
|
+
var X402UnexpectedStatusError = class extends HavenApiError {
|
|
358
|
+
x402ErrorCode = "unexpected_non_402_status";
|
|
359
|
+
/**
|
|
360
|
+
* #2979: `body` is the merchant's own JSON, when the non-402 response
|
|
361
|
+
* carried one — e.g. the demo merchant's `/mcp` readiness gate answers
|
|
362
|
+
* `503 { error: 'merchant_not_ready', reason_code, ... }`. Optional and
|
|
363
|
+
* best-effort: a non-JSON or unreadable body leaves this `undefined`, same
|
|
364
|
+
* as before this field existed. Consumers key on it (not on the message
|
|
365
|
+
* string) to distinguish an honest, machine-readable merchant refusal from
|
|
366
|
+
* a genuine "this is not the x402 endpoint" miss, which otherwise look
|
|
367
|
+
* identical — both are just "some non-402 status".
|
|
368
|
+
*/
|
|
369
|
+
constructor(message, statusCode, body) {
|
|
370
|
+
super(message, statusCode, body);
|
|
371
|
+
this.name = "X402UnexpectedStatusError";
|
|
372
|
+
}
|
|
373
|
+
};
|
|
374
|
+
var X402AlreadySettledError = class extends HavenApiError {
|
|
375
|
+
constructor(message, receipt, basis) {
|
|
376
|
+
super(message, 409, void 0, receipt.paymentId);
|
|
377
|
+
this.receipt = receipt;
|
|
378
|
+
this.basis = basis;
|
|
379
|
+
this.name = "X402AlreadySettledError";
|
|
380
|
+
}
|
|
381
|
+
receipt;
|
|
382
|
+
basis;
|
|
383
|
+
x402ErrorCode = "already_settled";
|
|
384
|
+
};
|
|
385
|
+
var HavenPaymentStateError = class extends HavenApiError {
|
|
386
|
+
constructor(message, statusCode, state, body) {
|
|
387
|
+
super(message, statusCode, body, state.paymentId);
|
|
388
|
+
this.state = state;
|
|
389
|
+
this.name = "HavenPaymentStateError";
|
|
390
|
+
}
|
|
391
|
+
state;
|
|
392
|
+
resumeState;
|
|
393
|
+
get status() {
|
|
394
|
+
return this.state.status;
|
|
395
|
+
}
|
|
396
|
+
get phase() {
|
|
397
|
+
return this.state.phase;
|
|
398
|
+
}
|
|
399
|
+
get nextAction() {
|
|
400
|
+
return this.state.nextAction;
|
|
401
|
+
}
|
|
402
|
+
};
|
|
403
|
+
var HavenSigningError = class extends HavenError {
|
|
404
|
+
constructor(message) {
|
|
405
|
+
super(message, "SIGNING_ERROR");
|
|
406
|
+
this.name = "HavenSigningError";
|
|
407
|
+
}
|
|
408
|
+
};
|
|
409
|
+
var HavenZeroSettlementHashError = class extends HavenError {
|
|
410
|
+
constructor(paymentId) {
|
|
411
|
+
super(
|
|
412
|
+
"settlement_tx_hash is the zero hash (0x00\u202600), which is never a real settlement transaction \u2014 refused before any report was sent.",
|
|
413
|
+
"ZERO_SETTLEMENT_HASH",
|
|
414
|
+
400,
|
|
415
|
+
paymentId
|
|
416
|
+
);
|
|
417
|
+
this.name = "HavenZeroSettlementHashError";
|
|
418
|
+
}
|
|
419
|
+
};
|
|
420
|
+
var SignerRefusalCode = {
|
|
421
|
+
/** `SUPPORTED_X402_EXPECTED_VERSIONS` in `@haven_ai/signer` does not include the received version. */
|
|
422
|
+
UnsupportedExpectedContextVersion: "UNSUPPORTED_EXPECTED_CONTEXT_VERSION",
|
|
423
|
+
/** `SUPPORTED_SWEEP_BINDING_VERSIONS` in `@haven_ai/signer` does not include the received version. */
|
|
424
|
+
UnsupportedSweepBindingVersion: "UNSUPPORTED_SWEEP_BINDING_VERSION"
|
|
425
|
+
};
|
|
426
|
+
function signerUpdateFallback(channel = HAVEN_CONNECTOR_CHANNEL) {
|
|
427
|
+
return `Update @haven_ai/signer by rerunning \`${connectorRerunCommand(void 0, { channel })}\`, which reinstalls the pinned MCP runtime, then retry the same signing call. Nothing was signed or spent \u2014 the quote or payment this version came from is unaffected and does not need to be re-quoted.`;
|
|
428
|
+
}
|
|
429
|
+
var SIGNER_UPDATE_FALLBACK = signerUpdateFallback();
|
|
430
|
+
var HavenUnsupportedSignerVersionError = class extends HavenError {
|
|
431
|
+
constructor(message, code, supportedVersions, receivedVersion, fallback) {
|
|
432
|
+
super(message, code);
|
|
433
|
+
this.supportedVersions = supportedVersions;
|
|
434
|
+
this.receivedVersion = receivedVersion;
|
|
435
|
+
this.fallback = fallback;
|
|
436
|
+
this.name = "HavenUnsupportedSignerVersionError";
|
|
437
|
+
}
|
|
438
|
+
supportedVersions;
|
|
439
|
+
receivedVersion;
|
|
440
|
+
fallback;
|
|
441
|
+
};
|
|
442
|
+
var HavenTimeoutError = class extends HavenError {
|
|
443
|
+
constructor(paymentId) {
|
|
444
|
+
super(
|
|
445
|
+
`Timed out waiting for payment ${paymentId} to confirm`,
|
|
446
|
+
"TIMEOUT",
|
|
447
|
+
void 0,
|
|
448
|
+
paymentId
|
|
449
|
+
);
|
|
450
|
+
this.name = "HavenTimeoutError";
|
|
451
|
+
}
|
|
452
|
+
};
|
|
453
|
+
var DIRECT_SIGN_CONTEXT_VERSION = 1;
|
|
454
|
+
var EIP712_DOMAIN_FIELDS = [
|
|
455
|
+
{ name: "name", type: "string" },
|
|
456
|
+
{ name: "version", type: "string" },
|
|
457
|
+
{ name: "chainId", type: "uint256" },
|
|
458
|
+
{ name: "verifyingContract", type: "address" }
|
|
459
|
+
];
|
|
460
|
+
var ENTRY_POINT_V07 = "0x0000000071727De22E5E9d8BAf0edAc6f37da032";
|
|
461
|
+
var HYBRID_DELEGATOR_DOMAIN_NAME = "HybridDeleGator";
|
|
462
|
+
var HYBRID_DELEGATOR_DOMAIN_VERSION = "1";
|
|
463
|
+
var PACKED_USER_OPERATION_FIELDS = [
|
|
464
|
+
{ name: "sender", type: "address" },
|
|
465
|
+
{ name: "nonce", type: "uint256" },
|
|
466
|
+
{ name: "initCode", type: "bytes" },
|
|
467
|
+
{ name: "callData", type: "bytes" },
|
|
468
|
+
{ name: "accountGasLimits", type: "bytes32" },
|
|
469
|
+
{ name: "preVerificationGas", type: "uint256" },
|
|
470
|
+
{ name: "gasFees", type: "bytes32" },
|
|
471
|
+
{ name: "paymasterAndData", type: "bytes" },
|
|
472
|
+
{ name: "entryPoint", type: "address" }
|
|
473
|
+
];
|
|
474
|
+
var HavenUserOpBindingError = class extends HavenError {
|
|
475
|
+
constructor(message) {
|
|
476
|
+
super(message, "USEROP_BINDING_MISMATCH");
|
|
477
|
+
this.name = "HavenUserOpBindingError";
|
|
478
|
+
}
|
|
479
|
+
};
|
|
480
|
+
function isPackedUserOperationTypedData(value) {
|
|
481
|
+
return typeof value === "object" && value !== null && value.primaryType === "PackedUserOperation";
|
|
482
|
+
}
|
|
483
|
+
var HEX = /^0x[0-9a-fA-F]*$/;
|
|
484
|
+
var BYTES32 = /^0x[0-9a-fA-F]{64}$/;
|
|
485
|
+
function refuse(detail) {
|
|
486
|
+
throw new HavenUserOpBindingError(
|
|
487
|
+
`Refusing to sign: this UserOperation typed data does not match its payload_hash (${detail}). The payload was altered between the Haven result and this call \u2014 sign by payment_id so the signer fetches the exact bytes, or pass typed_data_b64 from the result unchanged.`
|
|
488
|
+
);
|
|
489
|
+
}
|
|
490
|
+
function hexField(message, key, pattern) {
|
|
491
|
+
const value = message[key];
|
|
492
|
+
if (typeof value !== "string" || !pattern.test(value)) refuse(`message.${key} is not well-formed`);
|
|
493
|
+
return value;
|
|
494
|
+
}
|
|
495
|
+
function uintField(message, key) {
|
|
496
|
+
const value = message[key];
|
|
497
|
+
try {
|
|
498
|
+
if (typeof value === "bigint") return value;
|
|
499
|
+
if (typeof value === "string" && /^(0x[0-9a-fA-F]+|[0-9]+)$/.test(value) || typeof value === "number") {
|
|
500
|
+
const n = BigInt(value);
|
|
501
|
+
if (n >= 0n) return n;
|
|
502
|
+
}
|
|
503
|
+
} catch {
|
|
504
|
+
}
|
|
505
|
+
return refuse(`message.${key} is not an unsigned integer`);
|
|
506
|
+
}
|
|
507
|
+
function addressField(value, where) {
|
|
508
|
+
if (typeof value !== "string" || !isAddress(value, { strict: false })) refuse(`${where} is not an address`);
|
|
509
|
+
return getAddress(value);
|
|
510
|
+
}
|
|
511
|
+
function packedUserOperationHash(typedData) {
|
|
512
|
+
if (!isPackedUserOperationTypedData(typedData)) refuse("primaryType is not PackedUserOperation");
|
|
513
|
+
const td = typedData;
|
|
514
|
+
const message = td.message;
|
|
515
|
+
if (!message || typeof message !== "object") refuse("message is missing");
|
|
516
|
+
const chainId = uintField({ chainId: td.domain?.chainId }, "chainId");
|
|
517
|
+
const inner = keccak256(
|
|
518
|
+
encodeAbiParameters(
|
|
519
|
+
[
|
|
520
|
+
{ type: "address" },
|
|
521
|
+
{ type: "uint256" },
|
|
522
|
+
{ type: "bytes32" },
|
|
523
|
+
{ type: "bytes32" },
|
|
524
|
+
{ type: "bytes32" },
|
|
525
|
+
{ type: "uint256" },
|
|
526
|
+
{ type: "bytes32" },
|
|
527
|
+
{ type: "bytes32" }
|
|
528
|
+
],
|
|
529
|
+
[
|
|
530
|
+
addressField(message.sender, "message.sender"),
|
|
531
|
+
uintField(message, "nonce"),
|
|
532
|
+
keccak256(hexField(message, "initCode", HEX)),
|
|
533
|
+
keccak256(hexField(message, "callData", HEX)),
|
|
534
|
+
hexField(message, "accountGasLimits", BYTES32),
|
|
535
|
+
uintField(message, "preVerificationGas"),
|
|
536
|
+
hexField(message, "gasFees", BYTES32),
|
|
537
|
+
keccak256(hexField(message, "paymasterAndData", HEX))
|
|
538
|
+
]
|
|
539
|
+
)
|
|
540
|
+
);
|
|
541
|
+
return keccak256(
|
|
542
|
+
encodeAbiParameters(
|
|
543
|
+
[{ type: "bytes32" }, { type: "address" }, { type: "uint256" }],
|
|
544
|
+
[inner, addressField(message.entryPoint, "message.entryPoint"), chainId]
|
|
545
|
+
)
|
|
546
|
+
);
|
|
547
|
+
}
|
|
548
|
+
function assertUserOpTypedDataBinding(typedData, payloadHash) {
|
|
549
|
+
if (typeof payloadHash !== "string" || !BYTES32.test(payloadHash)) refuse("payload_hash is not a 32-byte hex string");
|
|
550
|
+
if (!isPackedUserOperationTypedData(typedData)) refuse("primaryType is not PackedUserOperation");
|
|
551
|
+
const td = typedData;
|
|
552
|
+
const domain = td.domain ?? {};
|
|
553
|
+
const message = td.message ?? {};
|
|
554
|
+
if (domain.name !== HYBRID_DELEGATOR_DOMAIN_NAME) refuse("domain.name is not HybridDeleGator");
|
|
555
|
+
if (domain.version !== HYBRID_DELEGATOR_DOMAIN_VERSION) refuse("domain.version is not 1");
|
|
556
|
+
if (addressField(domain.verifyingContract, "domain.verifyingContract") !== addressField(message.sender, "message.sender")) {
|
|
557
|
+
refuse("domain.verifyingContract is not message.sender");
|
|
558
|
+
}
|
|
559
|
+
if (addressField(message.entryPoint, "message.entryPoint") !== getAddress(ENTRY_POINT_V07)) {
|
|
560
|
+
refuse("message.entryPoint is not the v0.7 EntryPoint");
|
|
561
|
+
}
|
|
562
|
+
const fields = (td.types ?? {}).PackedUserOperation;
|
|
563
|
+
if (!Array.isArray(fields) || fields.length !== PACKED_USER_OPERATION_FIELDS.length || fields.some(
|
|
564
|
+
(f, i) => !f || f.name !== PACKED_USER_OPERATION_FIELDS[i].name || f.type !== PACKED_USER_OPERATION_FIELDS[i].type
|
|
565
|
+
)) {
|
|
566
|
+
refuse("types.PackedUserOperation is not the HybridDeleGator field list");
|
|
567
|
+
}
|
|
568
|
+
const extraTypes = Object.keys(td.types ?? {}).filter((k) => k !== "PackedUserOperation" && k !== "EIP712Domain");
|
|
569
|
+
if (extraTypes.length > 0) refuse(`unexpected types: ${extraTypes.join(", ")}`);
|
|
570
|
+
const domainKeys = Object.keys(domain).sort();
|
|
571
|
+
if (domainKeys.join(",") !== [...EIP712_DOMAIN_FIELDS.map((f) => f.name)].sort().join(",")) {
|
|
572
|
+
refuse(`domain keys are not exactly ${EIP712_DOMAIN_FIELDS.map((f) => f.name).join(", ")}`);
|
|
573
|
+
}
|
|
574
|
+
const declaredDomain = (td.types ?? {}).EIP712Domain;
|
|
575
|
+
if (declaredDomain !== void 0 && (!Array.isArray(declaredDomain) || declaredDomain.length !== EIP712_DOMAIN_FIELDS.length || declaredDomain.some(
|
|
576
|
+
(f, i) => !f || f.name !== EIP712_DOMAIN_FIELDS[i].name || f.type !== EIP712_DOMAIN_FIELDS[i].type
|
|
577
|
+
))) {
|
|
578
|
+
refuse("types.EIP712Domain is not the canonical name/version/chainId/verifyingContract list");
|
|
579
|
+
}
|
|
580
|
+
const recomputed = packedUserOperationHash(typedData);
|
|
581
|
+
if (recomputed.toLowerCase() !== payloadHash.toLowerCase()) refuse("recomputed UserOperation hash differs");
|
|
582
|
+
return recomputed;
|
|
583
|
+
}
|
|
584
|
+
|
|
585
|
+
// src/settlement-child.ts
|
|
586
|
+
var DELEGATION_MANAGER = "0xdb9B1e94B5b69Df7e401DDbedE43491141047dB3";
|
|
587
|
+
var CAVEAT_ENFORCERS = {
|
|
588
|
+
erc20TransferAmount: "0xf100b0819427117EcF76Ed94B358B1A5b5C6D2Fc",
|
|
589
|
+
allowedCalldata: "0xc2b0d624c1c4319760C96503BA27C347F3260f55",
|
|
590
|
+
timestamp: "0x1046bb45C8d673d4ea75321280DB34899413c069",
|
|
591
|
+
redeemer: "0xE144b0b2618071B4E56f746313528a669c7E65c5"
|
|
592
|
+
};
|
|
593
|
+
var MAX_SETTLEMENT_WINDOW_SECONDS = 600;
|
|
594
|
+
function isSettlementChildTypedData(value) {
|
|
595
|
+
const td = value;
|
|
596
|
+
return !!td && td.primaryType === "Delegation" && typeof td.domain?.verifyingContract === "string" && Array.isArray(td.message?.caveats);
|
|
597
|
+
}
|
|
598
|
+
function same(a, b) {
|
|
599
|
+
return !!a && !!b && a.toLowerCase() === b.toLowerCase();
|
|
600
|
+
}
|
|
601
|
+
function hex(value) {
|
|
602
|
+
return value.startsWith("0x") ? value.slice(2).toLowerCase() : value.toLowerCase();
|
|
603
|
+
}
|
|
604
|
+
function slice(terms, start, end) {
|
|
605
|
+
const body = hex(terms);
|
|
606
|
+
return end === void 0 ? body.slice(start * 2) : body.slice(start * 2, end * 2);
|
|
607
|
+
}
|
|
608
|
+
function findCaveat(caveats, enforcer) {
|
|
609
|
+
return caveats.find((c) => same(c.enforcer, enforcer));
|
|
610
|
+
}
|
|
611
|
+
function refuse2(what, detail) {
|
|
612
|
+
throw new HavenSigningError(
|
|
613
|
+
`Refusing to sign the x402 settlement child: ${what}. ${detail} The signature on this child is what lets a merchant pull from the treasury, so it is verified locally against an expectation Haven cannot rewrite, rather than trusted.`
|
|
614
|
+
);
|
|
615
|
+
}
|
|
616
|
+
function chainIdForNetwork(network) {
|
|
617
|
+
if (!network) return void 0;
|
|
618
|
+
const caip = /^eip155:(\d+)$/.exec(network);
|
|
619
|
+
if (caip) return Number(caip[1]);
|
|
620
|
+
if (network === "base") return 8453;
|
|
621
|
+
if (network === "base-sepolia") return 84532;
|
|
622
|
+
return void 0;
|
|
623
|
+
}
|
|
624
|
+
var ROOT_AUTHORITY = `0x${"ff".repeat(32)}`;
|
|
625
|
+
function verifySettlementChild(typedData, expected, now = Date.now()) {
|
|
626
|
+
if (typedData.primaryType !== "Delegation") {
|
|
627
|
+
refuse2("it is not a Delegation payload", `primaryType was '${typedData.primaryType}'.`);
|
|
628
|
+
}
|
|
629
|
+
if (!same(typedData.domain?.verifyingContract, DELEGATION_MANAGER)) {
|
|
630
|
+
refuse2(
|
|
631
|
+
"the EIP-712 domain names an unknown DelegationManager",
|
|
632
|
+
`Expected ${DELEGATION_MANAGER}, got ${typedData.domain?.verifyingContract}.`
|
|
633
|
+
);
|
|
634
|
+
}
|
|
635
|
+
const domainChain = Number(typedData.domain?.chainId);
|
|
636
|
+
if (!Number.isFinite(domainChain) || domainChain !== expected.chainId) {
|
|
637
|
+
refuse2(
|
|
638
|
+
"it is scoped to the wrong chain",
|
|
639
|
+
`The expected context says chain ${expected.chainId}; the child says ${typedData.domain?.chainId}.`
|
|
640
|
+
);
|
|
641
|
+
}
|
|
642
|
+
const authority = typedData.message?.authority;
|
|
643
|
+
if (typeof authority !== "string" || !/^0x[0-9a-fA-F]{64}$/.test(authority)) {
|
|
644
|
+
refuse2("its authority is not a 32-byte delegation hash", `Got ${String(authority)}.`);
|
|
645
|
+
}
|
|
646
|
+
if (authority.toLowerCase() === ROOT_AUTHORITY) {
|
|
647
|
+
refuse2(
|
|
648
|
+
"it is a ROOT delegation, not a re-delegation of the agent budget",
|
|
649
|
+
"A root child would grant its redeemer authority over the agent's own account instead of spending through the budget delegation's enforcers."
|
|
650
|
+
);
|
|
651
|
+
}
|
|
652
|
+
if (expected.delegatorAccount !== void 0) {
|
|
653
|
+
const delegator = typedData.message?.delegator;
|
|
654
|
+
if (typeof delegator !== "string" || !same(delegator, expected.delegatorAccount)) {
|
|
655
|
+
refuse2(
|
|
656
|
+
"it is delegated by an account other than this agent's own",
|
|
657
|
+
`Expected delegator ${expected.delegatorAccount}; the child names ${String(delegator)}.`
|
|
658
|
+
);
|
|
659
|
+
}
|
|
660
|
+
}
|
|
661
|
+
const caveats = typedData.message?.caveats ?? [];
|
|
662
|
+
if (caveats.length === 0) {
|
|
663
|
+
refuse2("it carries no caveats at all", "An unconstrained delegation is not a payment.");
|
|
664
|
+
}
|
|
665
|
+
const transfer = findCaveat(caveats, CAVEAT_ENFORCERS.erc20TransferAmount);
|
|
666
|
+
if (!transfer) {
|
|
667
|
+
refuse2(
|
|
668
|
+
"it has no ERC-20 transfer-amount caveat",
|
|
669
|
+
"Without it the delegation is not bounded to an amount."
|
|
670
|
+
);
|
|
671
|
+
}
|
|
672
|
+
const token = `0x${slice(transfer.terms, 0, 20)}`;
|
|
673
|
+
if (!same(token, expected.asset)) {
|
|
674
|
+
refuse2("it spends a different token", `Expected ${expected.asset}, child pins ${token}.`);
|
|
675
|
+
}
|
|
676
|
+
const amount = BigInt(`0x${slice(transfer.terms, 20, 52)}`);
|
|
677
|
+
if (amount !== BigInt(expected.amount)) {
|
|
678
|
+
refuse2(
|
|
679
|
+
"the amount does not match",
|
|
680
|
+
`The expected context says ${expected.amount}; the child allows ${amount.toString()}.`
|
|
681
|
+
);
|
|
682
|
+
}
|
|
683
|
+
const calldata = findCaveat(caveats, CAVEAT_ENFORCERS.allowedCalldata);
|
|
684
|
+
if (!calldata) {
|
|
685
|
+
refuse2(
|
|
686
|
+
"it has no payee pin",
|
|
687
|
+
"Without an allowed-calldata caveat the merchant could redeem it to any address."
|
|
688
|
+
);
|
|
689
|
+
}
|
|
690
|
+
const startIndex = BigInt(`0x${slice(calldata.terms, 0, 32)}`);
|
|
691
|
+
if (startIndex !== 4n) {
|
|
692
|
+
refuse2(
|
|
693
|
+
"the payee pin points at the wrong calldata offset",
|
|
694
|
+
`Expected offset 4 (the transfer's \`to\` word), got ${startIndex.toString()}.`
|
|
695
|
+
);
|
|
696
|
+
}
|
|
697
|
+
const paddedPayee = slice(calldata.terms, 32, 64);
|
|
698
|
+
if (paddedPayee.slice(0, 24) !== "0".repeat(24)) {
|
|
699
|
+
refuse2("the pinned payee is not a plain address", "Its 32-byte word is not a padded address.");
|
|
700
|
+
}
|
|
701
|
+
const payee = `0x${paddedPayee.slice(24)}`;
|
|
702
|
+
if (!same(payee, expected.merchantTo)) {
|
|
703
|
+
refuse2(
|
|
704
|
+
"it pays a different address than Haven declared",
|
|
705
|
+
`The expected context says ${expected.merchantTo}; the child pins ${payee}.`
|
|
706
|
+
);
|
|
707
|
+
}
|
|
708
|
+
const timestamp = findCaveat(caveats, CAVEAT_ENFORCERS.timestamp);
|
|
709
|
+
if (!timestamp) {
|
|
710
|
+
refuse2("it never expires", "A settlement child without a timestamp caveat is open-ended.");
|
|
711
|
+
}
|
|
712
|
+
const beforeThreshold = Number(BigInt(`0x${slice(timestamp.terms, 16, 32)}`));
|
|
713
|
+
if (beforeThreshold <= 0) {
|
|
714
|
+
refuse2("its expiry is unset", "The timestamp caveat has no upper bound.");
|
|
715
|
+
}
|
|
716
|
+
const nowSec = Math.floor(now / 1e3);
|
|
717
|
+
if (beforeThreshold <= nowSec) {
|
|
718
|
+
refuse2("it has already expired", `Expiry ${beforeThreshold} is not in the future.`);
|
|
719
|
+
}
|
|
720
|
+
if (beforeThreshold > nowSec + MAX_SETTLEMENT_WINDOW_SECONDS) {
|
|
721
|
+
refuse2(
|
|
722
|
+
"its window is longer than a settlement may live",
|
|
723
|
+
`Expiry is ${beforeThreshold - nowSec}s out; the ceiling is ${MAX_SETTLEMENT_WINDOW_SECONDS}s.`
|
|
724
|
+
);
|
|
725
|
+
}
|
|
726
|
+
if (expected.redeemers && expected.redeemers.length > 0) {
|
|
727
|
+
const redeemer = findCaveat(caveats, CAVEAT_ENFORCERS.redeemer);
|
|
728
|
+
if (!redeemer) {
|
|
729
|
+
refuse2(
|
|
730
|
+
"it has no facilitator pin",
|
|
731
|
+
"The merchant advertised facilitators, so the child must be redeemable only by them."
|
|
732
|
+
);
|
|
733
|
+
}
|
|
734
|
+
const body = hex(redeemer.terms);
|
|
735
|
+
if (body.length === 0 || body.length % 40 !== 0) {
|
|
736
|
+
refuse2("its facilitator pin is malformed", "The redeemer terms are not a list of addresses.");
|
|
737
|
+
}
|
|
738
|
+
const pinned = /* @__PURE__ */ new Set();
|
|
739
|
+
for (let i = 0; i < body.length; i += 40) pinned.add(body.slice(i, i + 40));
|
|
740
|
+
const wanted = new Set(expected.redeemers.map((a) => hex(a)));
|
|
741
|
+
if (pinned.size !== wanted.size || [...wanted].some((a) => !pinned.has(a))) {
|
|
742
|
+
refuse2(
|
|
743
|
+
"it is redeemable by different facilitators than the merchant advertised",
|
|
744
|
+
`Expected ${[...wanted].map((a) => `0x${a}`).join(", ")}; the child pins ${[...pinned].map((a) => `0x${a}`).join(", ")}.`
|
|
745
|
+
);
|
|
746
|
+
}
|
|
747
|
+
}
|
|
748
|
+
if (expected.expiresAt) {
|
|
749
|
+
const declared = Math.floor(Date.parse(expected.expiresAt) / 1e3);
|
|
750
|
+
if (Number.isFinite(declared) && beforeThreshold > declared) {
|
|
751
|
+
refuse2(
|
|
752
|
+
"it outlives the payment window Haven declared",
|
|
753
|
+
`The expected context expires at ${expected.expiresAt}; the child lives to ${beforeThreshold}.`
|
|
754
|
+
);
|
|
755
|
+
}
|
|
756
|
+
}
|
|
757
|
+
}
|
|
758
|
+
var SIMPLE_FACTORY_ADDRESS = "0x69Aa2f9fe1572F1B640E1bbc512f5c3a734fc77c";
|
|
759
|
+
var HYBRID_DELEGATOR_IMPLEMENTATION = "0x48dBe696A4D990079e039489bA2053B36E8FFEC4";
|
|
760
|
+
var ERC1967_PROXY_BYTECODE = "0x60806040526040516103f03803806103f08339810160408190526100229161025e565b61002c8282610033565b5050610341565b61003c82610091565b6040516001600160a01b038316907fbc7cd75a20ee27fd9adebab32041f755214dbc6bffa90cc0225b39da2e5c2d3b905f90a280511561008557610080828261010c565b505050565b61008d61017f565b5050565b806001600160a01b03163b5f036100cb57604051634c9c8ce360e01b81526001600160a01b03821660048201526024015b60405180910390fd5b7f360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc80546001600160a01b0319166001600160a01b0392909216919091179055565b60605f80846001600160a01b0316846040516101289190610326565b5f60405180830381855af49150503d805f8114610160576040519150601f19603f3d011682016040523d82523d5f602084013e610165565b606091505b5090925090506101768583836101a0565b95945050505050565b341561019e5760405163b398979f60e01b815260040160405180910390fd5b565b6060826101b5576101b0826101ff565b6101f8565b81511580156101cc57506001600160a01b0384163b155b156101f557604051639996b31560e01b81526001600160a01b03851660048201526024016100c2565b50805b9392505050565b80511561020f5780518082602001fd5b604051630a12f52160e11b815260040160405180910390fd5b634e487b7160e01b5f52604160045260245ffd5b5f5b8381101561025657818101518382015260200161023e565b50505f910152565b5f806040838503121561026f575f80fd5b82516001600160a01b0381168114610285575f80fd5b60208401519092506001600160401b03808211156102a1575f80fd5b818501915085601f8301126102b4575f80fd5b8151818111156102c6576102c6610228565b604051601f8201601f19908116603f011681019083821181831017156102ee576102ee610228565b81604052828152886020848701011115610306575f80fd5b61031783602083016020880161023c565b80955050505050509250929050565b5f825161033781846020870161023c565b9190910192915050565b60a38061034d5f395ff3fe6080604052600a600c565b005b60186014601a565b6050565b565b5f604b7f360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc546001600160a01b031690565b905090565b365f80375f80365f845af43d5f803e8080156069573d5ff35b3d5ffdfea2646970667358221220fd2cc92935c943d341edacaf5318a0b9ab0185ce62ef72e95ab393ef358730c464736f6c63430008170033";
|
|
761
|
+
var ERC1967_PROXY_ABI = [
|
|
762
|
+
{
|
|
763
|
+
type: "constructor",
|
|
764
|
+
inputs: [
|
|
765
|
+
{ name: "implementation", type: "address" },
|
|
766
|
+
{ name: "_data", type: "bytes" }
|
|
767
|
+
],
|
|
768
|
+
stateMutability: "payable"
|
|
769
|
+
}
|
|
770
|
+
];
|
|
771
|
+
var HYBRID_DELEGATOR_INITIALIZE_ABI = [
|
|
772
|
+
{
|
|
773
|
+
type: "function",
|
|
774
|
+
name: "initialize",
|
|
775
|
+
inputs: [
|
|
776
|
+
{ name: "_owner", type: "address" },
|
|
777
|
+
{ name: "_keyIds", type: "string[]" },
|
|
778
|
+
{ name: "_xValues", type: "uint256[]" },
|
|
779
|
+
{ name: "_yValues", type: "uint256[]" }
|
|
780
|
+
],
|
|
781
|
+
outputs: [],
|
|
782
|
+
stateMutability: "nonpayable"
|
|
783
|
+
}
|
|
784
|
+
];
|
|
785
|
+
var DEPLOY_SALT = pad("0x", { size: 32 });
|
|
786
|
+
function deriveDelegateAccountAddress(owner) {
|
|
787
|
+
const initData = encodeFunctionData({
|
|
788
|
+
abi: HYBRID_DELEGATOR_INITIALIZE_ABI,
|
|
789
|
+
functionName: "initialize",
|
|
790
|
+
args: [owner, [], [], []]
|
|
791
|
+
});
|
|
792
|
+
const proxyCreationCode = encodeDeployData({
|
|
793
|
+
abi: ERC1967_PROXY_ABI,
|
|
794
|
+
args: [HYBRID_DELEGATOR_IMPLEMENTATION, initData],
|
|
795
|
+
bytecode: ERC1967_PROXY_BYTECODE
|
|
796
|
+
});
|
|
797
|
+
return getContractAddress({
|
|
798
|
+
bytecode: proxyCreationCode,
|
|
799
|
+
from: SIMPLE_FACTORY_ADDRESS,
|
|
800
|
+
opcode: "CREATE2",
|
|
801
|
+
salt: DEPLOY_SALT
|
|
802
|
+
});
|
|
803
|
+
}
|
|
804
|
+
var CAVEAT_COMPONENTS = [
|
|
805
|
+
{ name: "enforcer", type: "address" },
|
|
806
|
+
{ name: "terms", type: "bytes" },
|
|
807
|
+
{ name: "args", type: "bytes" }
|
|
808
|
+
];
|
|
809
|
+
var DELEGATION_TUPLE_COMPONENTS = [
|
|
810
|
+
{ name: "delegate", type: "address" },
|
|
811
|
+
{ name: "delegator", type: "address" },
|
|
812
|
+
{ name: "authority", type: "bytes32" },
|
|
813
|
+
{ name: "caveats", type: "tuple[]", components: CAVEAT_COMPONENTS },
|
|
814
|
+
{ name: "salt", type: "uint256" },
|
|
815
|
+
{ name: "signature", type: "bytes" }
|
|
816
|
+
];
|
|
817
|
+
var DELEGATION_ARRAY_PARAM = [
|
|
818
|
+
{ type: "tuple[]", components: DELEGATION_TUPLE_COMPONENTS }
|
|
819
|
+
];
|
|
820
|
+
var REDEEM_DELEGATIONS_ABI = [
|
|
821
|
+
{
|
|
822
|
+
type: "function",
|
|
823
|
+
name: "redeemDelegations",
|
|
824
|
+
inputs: [
|
|
825
|
+
{ name: "_permissionContexts", type: "bytes[]" },
|
|
826
|
+
{ name: "_modes", type: "bytes32[]" },
|
|
827
|
+
{ name: "_executionCallDatas", type: "bytes[]" }
|
|
828
|
+
],
|
|
829
|
+
outputs: [],
|
|
830
|
+
stateMutability: "nonpayable"
|
|
831
|
+
}
|
|
832
|
+
];
|
|
833
|
+
var SINGLE_DEFAULT_MODE = `0x${"00".repeat(32)}`;
|
|
834
|
+
function refuse3(detail) {
|
|
835
|
+
throw new HavenSigningError(
|
|
836
|
+
`Refusing to sign: this UserOperation's redeemDelegations call ${detail}. This signer only signs a redemption of a single delegation granted to the agent's own account.`
|
|
837
|
+
);
|
|
838
|
+
}
|
|
839
|
+
function assertRedeemsOwnBudgetDelegation(redeemCalldata, ownAccount) {
|
|
840
|
+
let permissionContexts;
|
|
841
|
+
let modes;
|
|
842
|
+
let executionCallDatas;
|
|
843
|
+
try {
|
|
844
|
+
const { args } = decodeFunctionData({ abi: REDEEM_DELEGATIONS_ABI, data: redeemCalldata });
|
|
845
|
+
[permissionContexts, modes, executionCallDatas] = args;
|
|
846
|
+
} catch {
|
|
847
|
+
refuse3("is not a well-formed redeemDelegations(bytes[],bytes32[],bytes[]) call");
|
|
848
|
+
}
|
|
849
|
+
const reEncoded = encodeFunctionData({
|
|
850
|
+
abi: REDEEM_DELEGATIONS_ABI,
|
|
851
|
+
functionName: "redeemDelegations",
|
|
852
|
+
args: [permissionContexts, modes, executionCallDatas]
|
|
853
|
+
});
|
|
854
|
+
if (reEncoded.toLowerCase() !== redeemCalldata.toLowerCase()) {
|
|
855
|
+
refuse3("does not re-encode to the exact bytes signed (trailing or non-canonical bytes)");
|
|
856
|
+
}
|
|
857
|
+
if (permissionContexts.length === 0 || permissionContexts.length !== modes.length || permissionContexts.length !== executionCallDatas.length) {
|
|
858
|
+
refuse3(
|
|
859
|
+
`has mismatched or empty argument arrays (permissionContexts=${permissionContexts.length}, modes=${modes.length}, executionCallDatas=${executionCallDatas.length})`
|
|
860
|
+
);
|
|
861
|
+
}
|
|
862
|
+
if (permissionContexts.length !== 1) {
|
|
863
|
+
refuse3(`redeems ${permissionContexts.length} delegation chains at once, not exactly one`);
|
|
864
|
+
}
|
|
865
|
+
if (modes[0].toLowerCase() !== SINGLE_DEFAULT_MODE.toLowerCase()) {
|
|
866
|
+
refuse3(`uses execution mode ${modes[0]}, not ExecutionMode.SingleDefault`);
|
|
867
|
+
}
|
|
868
|
+
let delegations;
|
|
869
|
+
try {
|
|
870
|
+
const [decoded] = decodeAbiParameters(DELEGATION_ARRAY_PARAM, permissionContexts[0]);
|
|
871
|
+
delegations = decoded;
|
|
872
|
+
} catch {
|
|
873
|
+
refuse3("carries a permission context that does not decode as an ABI-encoded Delegation[]");
|
|
874
|
+
}
|
|
875
|
+
if (delegations.length === 0) {
|
|
876
|
+
refuse3(
|
|
877
|
+
"carries an EMPTY delegation chain \u2014 DelegationManager treats this as self-authorised and runs the paired execution as the account, with no caveat, budget or recipient pin"
|
|
878
|
+
);
|
|
879
|
+
}
|
|
880
|
+
if (delegations.length !== 1) {
|
|
881
|
+
refuse3(`carries a ${delegations.length}-link delegation chain, not the single budget grant Haven emits`);
|
|
882
|
+
}
|
|
883
|
+
const reEncodedContext = encodeAbiParameters(DELEGATION_ARRAY_PARAM, [delegations]);
|
|
884
|
+
if (reEncodedContext.toLowerCase() !== permissionContexts[0].toLowerCase()) {
|
|
885
|
+
refuse3("carries a permission context with trailing or non-canonical bytes");
|
|
886
|
+
}
|
|
887
|
+
const leaf = delegations[0];
|
|
888
|
+
if (leaf.delegate.toLowerCase() !== ownAccount.toLowerCase()) {
|
|
889
|
+
refuse3(
|
|
890
|
+
`is redeemed for delegate ${leaf.delegate}, not this signer's own account (${ownAccount})`
|
|
891
|
+
);
|
|
892
|
+
}
|
|
893
|
+
const root = delegations[delegations.length - 1];
|
|
894
|
+
if (root.delegator.toLowerCase() === ownAccount.toLowerCase()) {
|
|
895
|
+
refuse3(
|
|
896
|
+
"carries a root delegation granted by this signer's OWN account \u2014 a real budget delegation always comes from elsewhere"
|
|
897
|
+
);
|
|
898
|
+
}
|
|
899
|
+
}
|
|
900
|
+
|
|
901
|
+
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, AgentPaymentWarningCode, CAVEAT_ENFORCERS, CONNECTOR_PACKAGE_NAME, DEFAULT_CONFIRMATION_TIMEOUT_MS, DELEGATION_MANAGER, DELEGATION_TUPLE_COMPONENTS, DIRECT_SIGN_CONTEXT_VERSION, ENTRY_POINT_V07, HAVEN_CONNECTOR_CHANNEL, HYBRID_DELEGATOR_DOMAIN_NAME, HYBRID_DELEGATOR_DOMAIN_VERSION, HYBRID_DELEGATOR_IMPLEMENTATION, HavenApiError, HavenError, HavenPaymentStateError, HavenSigningError, HavenTimeoutError, HavenUnsupportedSignerVersionError, HavenUserOpBindingError, HavenZeroSettlementHashError, MAX_SETTLEMENT_WINDOW_SECONDS, MerchantTimeoutError, PACKED_USER_OPERATION_FIELDS, REDEEM_DELEGATIONS_ABI, ROOT_AUTHORITY, SIGNER_UPDATE_FALLBACK, SIMPLE_FACTORY_ADDRESS, SINGLE_DEFAULT_MODE, SignerRefusalCode, X402AlreadySettledError, X402UnexpectedStatusError, assertRedeemsOwnBudgetDelegation, assertUserOpTypedDataBinding, chainIdForNetwork, connectorRerunCommand, connectorSpec, deriveDelegateAccountAddress, isConnectorChannel, isPackedUserOperationTypedData, isSettlementChildTypedData, packedUserOperationHash, resolveConnectorChannel, signerUpdateFallback, verifySettlementChild };
|
|
902
|
+
//# sourceMappingURL=chunk-GLNG2GF7.js.map
|
|
903
|
+
//# sourceMappingURL=chunk-GLNG2GF7.js.map
|