@haven_ai/sdk 0.1.6 → 0.1.7

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 CHANGED
@@ -32,9 +32,10 @@ console.log(result.txHash) // 0x...
32
32
  console.log(result.explorerUrl) // https://gnosisscan.io/tx/0x... (or basescan.org for Base)
33
33
  ```
34
34
 
35
- ## Try it live zero setup
35
+ ## Pay for an x402 resource
36
36
 
37
- Haven hosts a demo endpoint you can hit immediately after creating an agent:
37
+ Point `haven.fetch` at any HTTP resource gated behind `402 Payment Required`
38
+ the SDK detects the 402, pays through Haven, and retries automatically:
38
39
 
39
40
  ```typescript
40
41
  import { HavenClient } from '@haven_ai/sdk'
@@ -46,20 +47,12 @@ const haven = new HavenClient({
46
47
  })
47
48
 
48
49
  // haven.fetch handles 402 → pay → retry automatically
49
- const response = await haven.fetch(
50
- 'https://havenbackend-production-8a00.up.railway.app/demo/x402/data',
51
- )
50
+ const response = await haven.fetch('https://your-x402-endpoint.example/resource')
52
51
  const data = await response.json()
53
-
54
- console.log(data.message) // "You paid! Here's your demo data."
55
- console.log(data.fact) // a fun fact about the agent economy
56
- console.log(data.explorerUrl) // link to the on-chain payment tx
57
52
  ```
58
53
 
59
- Tell your agent:
60
- > "Use Haven to fetch `https://havenbackend-production-8a00.up.railway.app/demo/x402/data` and show me what came back."
61
-
62
- The agent will pay a tiny amount (~0.01 EURe on Gnosis Chain), receive the demo payload, and you'll see the payment in your Haven dashboard activity feed — no local server or extra config required.
54
+ The payment fits within the agent's on-chain allowance (or is queued for your
55
+ approval if it exceeds it), and shows up in your Haven dashboard activity feed.
63
56
 
64
57
  ## Supported Networks & Tokens
65
58
 
package/dist/index.cjs CHANGED
@@ -29,7 +29,15 @@ var AgentPaymentPhase = {
29
29
  /** The payment or approval request expired before completion. */
30
30
  Expired: "expired",
31
31
  /** Haven could not complete the payment; the agent should stop and surface the failure. */
32
- Failed: "failed"
32
+ Failed: "failed",
33
+ /**
34
+ * Pre-flight check determined the delegate's existing balance plus the
35
+ * remaining on-chain allowance cannot cover the requested amount, so no
36
+ * payment intent was created. Distinct from `UserApprovalRequired`: there
37
+ * is no approval that would fix this — the originating Safe needs more
38
+ * funds or the agent's per-token allowance needs to be raised first.
39
+ */
40
+ InsufficientFunds: "insufficient_funds"
33
41
  };
34
42
  var AgentPaymentNextAction = {
35
43
  /** Sign with the delegate key and submit the payment to Haven. */
@@ -47,7 +55,13 @@ var AgentPaymentNextAction = {
47
55
  /** Stop retrying this payment and tell the user what happened. */
48
56
  StopAndTellUser: "stop_and_tell_user",
49
57
  /** Ask again only if the user still wants the payment after expiry. */
50
- RequestAgainIfUserStillWantsIt: "request_again_if_user_still_wants_it"
58
+ RequestAgainIfUserStillWantsIt: "request_again_if_user_still_wants_it",
59
+ /**
60
+ * Stop and tell the user that the originating Safe needs to be funded or
61
+ * the agent's per-token allowance needs to be raised before the payment
62
+ * can succeed. A user approval will not fix this state on its own.
63
+ */
64
+ FundSafeOrRaiseAllowance: "fund_safe_or_raise_allowance"
51
65
  };
52
66
  var AgentPaymentRail = {
53
67
  /** Standard Haven payment from the user's Safe through an approved delegate allowance. */
@@ -78,7 +92,8 @@ var AgentPaymentPhaseDescriptions = {
78
92
  [AgentPaymentPhase.FundingSent]: "The Haven funding leg was sent; the agent can continue the merchant/protocol leg.",
79
93
  [AgentPaymentPhase.Rejected]: "The wallet owner rejected the request; the agent should stop and tell the user.",
80
94
  [AgentPaymentPhase.Expired]: "The payment or approval request expired before completion.",
81
- [AgentPaymentPhase.Failed]: "Haven could not complete the payment; the agent should stop and surface the failure."
95
+ [AgentPaymentPhase.Failed]: "Haven could not complete the payment; the agent should stop and surface the failure.",
96
+ [AgentPaymentPhase.InsufficientFunds]: "Pre-flight check determined the delegate balance plus the remaining on-chain allowance cannot cover the requested amount, so no payment was created. The originating Safe must be funded or the agent allowance raised before retrying."
82
97
  };
83
98
  var AgentPaymentNextActionDescriptions = {
84
99
  [AgentPaymentNextAction.SignAndSubmitPayment]: "Sign with the delegate key and submit the payment to Haven.",
@@ -88,7 +103,8 @@ var AgentPaymentNextActionDescriptions = {
88
103
  [AgentPaymentNextAction.WaitForUserToCompletePayment]: "Wait for the wallet owner to finish sending the approved funding payment.",
89
104
  [AgentPaymentNextAction.RetryOriginalX402Request]: "Resume this payment id and retry the original x402 request with the merchant payment header.",
90
105
  [AgentPaymentNextAction.StopAndTellUser]: "Stop retrying this payment and tell the user what happened.",
91
- [AgentPaymentNextAction.RequestAgainIfUserStillWantsIt]: "Ask again only if the user still wants the payment after expiry."
106
+ [AgentPaymentNextAction.RequestAgainIfUserStillWantsIt]: "Ask again only if the user still wants the payment after expiry.",
107
+ [AgentPaymentNextAction.FundSafeOrRaiseAllowance]: "Stop and tell the user that the originating Safe needs to be funded or the agent allowance raised before the payment can succeed."
92
108
  };
93
109
  var AgentPaymentRailDescriptions = {
94
110
  [AgentPaymentRail.Direct]: "Standard Haven payment from the user-controlled Safe through an approved delegate allowance.",
@@ -204,6 +220,7 @@ function verifySignature(hash, signature, expectedAddress) {
204
220
  }
205
221
  var BASE_USDC_ADDRESS = "0x833589fcd6edb6e08f4c7c32d4f71b54bda02913";
206
222
  var X402_IDEMPOTENCY_BUCKET_MS = 3e5;
223
+ var DECIMAL_ATOMIC_AMOUNT_RE = /^[0-9]+$/;
207
224
  function decodeBase64Json(value, label) {
208
225
  try {
209
226
  return JSON.parse(atob(value));
@@ -211,6 +228,12 @@ function decodeBase64Json(value, label) {
211
228
  throw new Error(`Failed to decode ${label}`);
212
229
  }
213
230
  }
231
+ function isPositiveDecimalAtomicAmount(value) {
232
+ return DECIMAL_ATOMIC_AMOUNT_RE.test(value) && BigInt(value) > 0n;
233
+ }
234
+ function optionAuthorizationAmount(option) {
235
+ return option.maxAmountRequired ?? option.amount;
236
+ }
214
237
  function normalizePaymentOption(value) {
215
238
  const candidate = value;
216
239
  if (!candidate || typeof candidate !== "object" || typeof candidate.scheme !== "string" || typeof candidate.network !== "string" || typeof candidate.asset !== "string" || typeof candidate.payTo !== "string") {
@@ -218,6 +241,10 @@ function normalizePaymentOption(value) {
218
241
  }
219
242
  const amount = typeof candidate.amount === "string" ? candidate.amount : typeof candidate.maxAmountRequired === "string" ? candidate.maxAmountRequired : null;
220
243
  if (!amount) return null;
244
+ if (!isPositiveDecimalAtomicAmount(amount)) return null;
245
+ if (candidate.maxAmountRequired !== void 0 && (typeof candidate.maxAmountRequired !== "string" || !isPositiveDecimalAtomicAmount(candidate.maxAmountRequired))) {
246
+ return null;
247
+ }
221
248
  return {
222
249
  scheme: candidate.scheme,
223
250
  network: candidate.network,
@@ -318,11 +345,13 @@ function selectPaymentOption(accepts) {
318
345
  for (const opt of accepts) {
319
346
  if (opt.network in SUPPORTED_X402_NETWORKS) {
320
347
  const networkTokens = NETWORK_TOKENS[opt.network];
321
- if (networkTokens?.[opt.asset.toLowerCase()]) return opt;
348
+ if (networkTokens?.[opt.asset.toLowerCase()] && isPositiveDecimalAtomicAmount(optionAuthorizationAmount(opt))) {
349
+ return opt;
350
+ }
322
351
  }
323
352
  }
324
353
  for (const opt of accepts) {
325
- if (opt.network in SUPPORTED_X402_NETWORKS) {
354
+ if (opt.network in SUPPORTED_X402_NETWORKS && isPositiveDecimalAtomicAmount(optionAuthorizationAmount(opt))) {
326
355
  return opt;
327
356
  }
328
357
  }
@@ -331,14 +360,18 @@ function selectPaymentOption(accepts) {
331
360
  function selectStandardPaymentOption(accepts) {
332
361
  if (!accepts || accepts.length === 0) return null;
333
362
  for (const opt of accepts) {
334
- if (opt.scheme === "exact" && opt.network in STANDARD_X402_NETWORKS && opt.asset.toLowerCase() === BASE_USDC_ADDRESS) {
363
+ if (opt.scheme === "exact" && opt.network in STANDARD_X402_NETWORKS && opt.asset.toLowerCase() === BASE_USDC_ADDRESS && isPositiveDecimalAtomicAmount(optionAuthorizationAmount(opt))) {
335
364
  return opt;
336
365
  }
337
366
  }
338
367
  return null;
339
368
  }
340
369
  function x402AuthorizationAmount(option) {
341
- return option.maxAmountRequired ?? option.amount;
370
+ const amount = optionAuthorizationAmount(option);
371
+ if (!isPositiveDecimalAtomicAmount(amount)) {
372
+ throw new Error("Invalid x402 amount: must be a positive decimal atomic amount");
373
+ }
374
+ return amount;
342
375
  }
343
376
  function buildX402ExpectedMessage(context) {
344
377
  return `Haven x402 expected context v1
@@ -2309,13 +2342,19 @@ var toolDescriptions = {
2309
2342
  quoteX402: {
2310
2343
  summary: "Inspect an HTTP 402 x402 paid resource without creating a Haven payment, signature, approval, or on-chain transaction.",
2311
2344
  behavior: "Probes the merchant directly and parses the 402 response. Pure read-only client behavior \u2014 Haven is not contacted.",
2312
- nextActionGuidance: ""
2345
+ nextActionGuidance: "On success the returned quote is the input to haven_pay_x402_quote. Do not call the merchant again \u2014 Haven re-uses the captured request when paying."
2313
2346
  },
2314
2347
  payX402: {
2315
2348
  summary: "Pay an inspected x402 quote. The delegate key signs locally; Haven only validates and relays signed, on-chain-constrained payment transactions.",
2316
2349
  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.",
2317
2350
  behavior: "Signs the EIP-3009 payment from the delegate wallet, asks Haven for a Safe AllowanceModule top-up if needed, and returns the merchant response or a pending-approval state.",
2318
- nextActionGuidance: "If approval is needed, preserve the returned resume_state and wait for nextAction=retry_original_x402_request before resuming."
2351
+ nextActionGuidance: "If approval is needed, preserve the returned resume_state and wait for nextAction=retry_original_x402_request before resuming. If the response carries phase=insufficient_funds and nextAction=fund_safe_or_raise_allowance, the payment cannot be retried until the originating Safe is funded or the agent allowance raised \u2014 stop and tell the user the shortfall reported on the response."
2352
+ },
2353
+ payX402OneShot: {
2354
+ summary: "Fetch an x402 paid HTTP resource in a single call. Handles the full probe -> pay -> retry round trip and returns the merchant response.",
2355
+ 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.",
2356
+ behavior: "Calls the URL, parses any HTTP 402 x402 challenge, signs the EIP-3009 payment from the delegate wallet, asks Haven for a Safe AllowanceModule top-up if needed, then retries the original request with the X-PAYMENT header and returns the merchant response. If the resource returns an MPP machine-payment challenge instead of standard x402, the MPP payment path is used automatically. If the resource returns a non-402 status, returns it unchanged without contacting Haven.",
2357
+ nextActionGuidance: "If approval is needed, preserve the returned resume_state or paymentId and call the resume tool once nextAction=retry_original_x402_request. If the response carries phase=insufficient_funds and nextAction=fund_safe_or_raise_allowance, the payment cannot be retried until the originating Safe is funded or the agent allowance raised \u2014 stop and tell the user the shortfall reported on the response."
2319
2358
  },
2320
2359
  resumeX402: {
2321
2360
  summary: "Resume an x402 payment after the Haven wallet owner approved the funding step.",
@@ -2325,7 +2364,7 @@ var toolDescriptions = {
2325
2364
  quoteMpp: {
2326
2365
  summary: "Inspect a Haven MPP challenge or paid MPP URL without creating a Haven payment, signature, approval, or on-chain transaction.",
2327
2366
  behavior: "Parses an MPP challenge envelope and returns a typed quote with rail tag, amount, asset, and merchant context. Pure read-only \u2014 Haven is not contacted.",
2328
- nextActionGuidance: ""
2367
+ nextActionGuidance: "On success the returned quote is the input to haven_pay_mpp_challenge. Do not call the merchant again \u2014 Haven re-uses the captured request when paying."
2329
2368
  },
2330
2369
  payMpp: {
2331
2370
  summary: "Pay an inspected MPP challenge. The delegate key signs locally; Haven only validates and relays signed, on-chain-constrained payment transactions.",