@haven_ai/sdk 0.1.25-alpha.0 → 0.1.27-alpha.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -275,6 +275,17 @@ var X402UnexpectedStatusError = class extends HavenApiError {
275
275
  this.name = "X402UnexpectedStatusError";
276
276
  }
277
277
  };
278
+ var X402AlreadySettledError = class extends HavenApiError {
279
+ constructor(message, receipt, basis) {
280
+ super(message, 409, void 0, receipt.paymentId);
281
+ this.receipt = receipt;
282
+ this.basis = basis;
283
+ this.name = "X402AlreadySettledError";
284
+ }
285
+ receipt;
286
+ basis;
287
+ x402ErrorCode = "already_settled";
288
+ };
278
289
  var HavenPaymentStateError = class extends HavenApiError {
279
290
  constructor(message, statusCode, state, body) {
280
291
  super(message, statusCode, body, state.paymentId);
@@ -951,8 +962,8 @@ function createJsonRpcProvider(url) {
951
962
  function createWallet(privateKey, provider) {
952
963
  return new ethers.Wallet(privateKey, provider);
953
964
  }
954
- function createErc20Contract(address, abi, signer) {
955
- return new ethers.Contract(address, abi, signer);
965
+ function createErc20Contract(address, abi, runner) {
966
+ return new ethers.Contract(address, abi, runner);
956
967
  }
957
968
 
958
969
  // src/client.ts
@@ -1501,7 +1512,8 @@ var HavenClient = class {
1501
1512
  isResetPending: a.onchain.isResetPending
1502
1513
  };
1503
1514
  });
1504
- return { ...agent, readiness: deriveReadiness(agent.status, allowances), allowances };
1515
+ const readiness = deriveReadiness(agent.status, allowances);
1516
+ return { ...agent, readiness, spend_authority_readiness: readiness, allowances };
1505
1517
  }
1506
1518
  /**
1507
1519
  * Sweep stranded USDC and ETH from the delegate EOA back to the originating Safe.
@@ -1932,7 +1944,6 @@ var HavenClient = class {
1932
1944
  }
1933
1945
  }
1934
1946
  async authorizeStandardX402(paymentRequired, option, idempotencyKey) {
1935
- const paymentHeader = await this.createStandardX402Header(paymentRequired, option);
1936
1947
  const raw = await this.post("/x402", {
1937
1948
  url: paymentRequired.resource.url,
1938
1949
  payTo: this.delegateAddress,
@@ -1947,12 +1958,30 @@ var HavenClient = class {
1947
1958
  // declaration keeps both writers of the 3009 shape loud-by-default.
1948
1959
  settlementScheme: "eip3009"
1949
1960
  });
1961
+ const state = this.paymentStateFromRaw("x402 payment", raw);
1962
+ const executedReplay = raw.success && raw.tx_hash ? "idempotency-collision" : state?.nextAction === AgentPaymentNextAction.RetryOriginalX402Request ? "approval-resume" : null;
1963
+ if (executedReplay) {
1964
+ const canFund = await this.delegateCanFund(
1965
+ raw.chain_id ?? state?.chainId ?? chainIdFromNetwork(option.network),
1966
+ option.asset,
1967
+ x402AuthorizationAmount(option)
1968
+ );
1969
+ const refuse = executedReplay === "idempotency-collision" ? canFund !== true : canFund === false;
1970
+ if (refuse) {
1971
+ const settledReceipt = state && executedReplay === "approval-resume" ? this.mapX402ReceiptFromStatus(paymentRequired, option, void 0, state) : this.mapX402ReceiptFromAuthorization(paymentRequired, option, void 0, raw);
1972
+ throw new X402AlreadySettledError(
1973
+ canFund === false ? "This x402 payment already settled \u2014 the delegate no longer holds the funds to authorize it again. To buy the same item a second time, pass a distinct `idempotencyKey`; the synthesised key intentionally collapses repeat calls for the same product within a 5-minute window so a retried request cannot pay twice." : "This x402 payment already settled, and whether the delegate can still fund a new authorization could not be verified (no `chainRpcs` entry for this chain). Refusing rather than issue an authorization that may be unfundable. To buy the same item a second time, pass a distinct `idempotencyKey`; to finish an interrupted payment, resume it by `paymentId`.",
1974
+ settledReceipt,
1975
+ canFund === false ? "settled" : "unverifiable"
1976
+ );
1977
+ }
1978
+ }
1979
+ const paymentHeader = await this.createStandardX402Header(paymentRequired, option);
1950
1980
  if (raw.success && raw.tx_hash) {
1951
1981
  const receipt2 = this.mapX402ReceiptFromAuthorization(paymentRequired, option, paymentHeader, raw);
1952
1982
  this.cacheX402Receipt(idempotencyKey, paymentHeader, receipt2);
1953
1983
  return receipt2;
1954
1984
  }
1955
- const state = this.paymentStateFromRaw("x402 payment", raw);
1956
1985
  if (state?.nextAction === AgentPaymentNextAction.RetryOriginalX402Request) {
1957
1986
  const receipt2 = this.mapX402ReceiptFromStatus(paymentRequired, option, paymentHeader, state);
1958
1987
  this.cacheX402Receipt(idempotencyKey, paymentHeader, receipt2);
@@ -2064,7 +2093,10 @@ var HavenClient = class {
2064
2093
  // redeemable ONLY by them. `null` here means the merchant advertised none
2065
2094
  // (or an empty array, which the backend 400s on), so the field is OMITTED
2066
2095
  // rather than sent empty. See x402FacilitatorAddresses.
2067
- ...selection.facilitatorAddresses ? { facilitatorAddresses: selection.facilitatorAddresses } : {}
2096
+ ...selection.facilitatorAddresses ? { facilitatorAddresses: selection.facilitatorAddresses } : {},
2097
+ // #1307/#1547: persisted so the settle leg can rehydrate the merchant
2098
+ // call by payment_id on this scheme too, not only on the 3009 bridge.
2099
+ ...options.mcpCallContext ? { mcpCallContext: options.mcpCallContext } : {}
2068
2100
  });
2069
2101
  if (!raw.payment_id) {
2070
2102
  throw new HavenApiError("No payment_id returned from x402/authorize", 500, raw);
@@ -2133,6 +2165,18 @@ var HavenClient = class {
2133
2165
  if (cached && cached.expiresAt > Date.now()) return cached.receipt;
2134
2166
  const status = await this.getPaymentStatus(input.paymentId);
2135
2167
  this.assertCanResumeX402(status, input.paymentRequired, option);
2168
+ const canFund = await this.delegateCanFund(
2169
+ status.chainId ?? chainIdFromNetwork(option.network),
2170
+ option.asset,
2171
+ x402AuthorizationAmount(option)
2172
+ );
2173
+ if (canFund === false) {
2174
+ throw new X402AlreadySettledError(
2175
+ `x402 payment ${status.paymentId} has already settled \u2014 the delegate no longer holds the funds to authorize it again, so there is nothing left to resume.`,
2176
+ this.mapX402ReceiptFromStatus(input.paymentRequired, option, void 0, status),
2177
+ "settled"
2178
+ );
2179
+ }
2136
2180
  const paymentHeader = await this.createStandardX402Header(input.paymentRequired, option);
2137
2181
  const receipt = this.mapX402ReceiptFromStatus(input.paymentRequired, option, paymentHeader, status);
2138
2182
  this.cacheX402Receipt(idempotencyKey, paymentHeader, receipt);
@@ -2860,6 +2904,45 @@ var HavenClient = class {
2860
2904
  );
2861
2905
  }
2862
2906
  }
2907
+ /**
2908
+ * Can the delegate EOA still fund an authorization for `amountAtomic`?
2909
+ *
2910
+ * #1521: the only question that separates a legitimate resume (funding
2911
+ * confirmed, merchant never paid — the delegate still holds the money) from
2912
+ * a replayed settled payment (funding confirmed, merchant paid, delegate
2913
+ * spent). The intent's own `status: 'confirmed'` is identical in both.
2914
+ *
2915
+ * The balance is asked of the CHAIN rather than of Haven's bookkeeping on
2916
+ * purpose: the merchant-settlement evidence record is written by this SDK
2917
+ * *after* the merchant call, so a client that dies between the two leaves
2918
+ * the backend believing the merchant was never paid — the exact case the
2919
+ * discriminator has to get right. The chain cannot be behind in that way.
2920
+ *
2921
+ * Returns `null` — never a guess — when `chainRpcs` has no entry for the
2922
+ * chain or the read fails. Callers must treat that as "unverifiable", not
2923
+ * as "funded".
2924
+ */
2925
+ async delegateCanFund(chainId, tokenAddress, amountAtomic, timeoutMs = 1e4) {
2926
+ if (!chainId || !this.delegateAddress) return null;
2927
+ const rpcUrl = this.chainRpcs[chainId];
2928
+ if (!rpcUrl) return null;
2929
+ try {
2930
+ const provider = createJsonRpcProvider(rpcUrl);
2931
+ const token = createErc20Contract(
2932
+ tokenAddress,
2933
+ ["function balanceOf(address) view returns (uint256)"],
2934
+ provider
2935
+ );
2936
+ const balance = await Promise.race([
2937
+ token.balanceOf(this.delegateAddress),
2938
+ new Promise((resolve) => setTimeout(() => resolve(null), timeoutMs).unref?.())
2939
+ ]);
2940
+ if (balance === null) return null;
2941
+ return balance >= BigInt(amountAtomic);
2942
+ } catch {
2943
+ return null;
2944
+ }
2945
+ }
2863
2946
  throwIfNonSignableAuthorizationState(label, raw) {
2864
2947
  if (raw.status === "pending_signature") return;
2865
2948
  this.throwPaymentStateError(label, raw);
@@ -3495,9 +3578,9 @@ var toolDescriptions = {
3495
3578
  nextActionGuidance: ""
3496
3579
  },
3497
3580
  getAgent: {
3498
- summary: "Return the authenticated agent identity AND its live spend authority in one call: Haven wallet, delegate, chain, raw status, a readiness signal, and per-token remaining allowance (atomic + human-readable). The recommended first call in a new session to confirm who you are and whether you can pay right now.",
3581
+ summary: "Return the authenticated agent identity AND its live spend authority in one call: Haven wallet, delegate, chain, raw status, spend_authority_readiness, and per-token remaining allowance (atomic + human-readable). The recommended first call in a new session to confirm who you are and whether Haven will let you spend right now.",
3499
3582
  selectionGuidance: "Use this as the one-shot orientation/bootstrap at the start of a session, or whenever you need to confirm identity together with whether the agent can spend right now. For a detailed per-token breakdown (configured vs spent vs reset window) use haven_get_allowances.",
3500
- behavior: 'Reads identity plus the live spend-authority snapshot in one shot \u2014 the on-chain AllowanceModule on the legacy rail, the active budget delegation on the delegation rail. readiness is "ready" when at least one token has remaining spend authority, "needs_approval" when the agent is active but has none, and "revoked" when the credential is not active. What an over-budget payment does differs by rail: on the legacy AllowanceModule rail it is queued for the wallet owner to approve in Haven; on the delegation rail there is no approval queue \u2014 an over-budget redemption reverts on-chain, so ask the owner to grant or raise the budget in Haven rather than waiting for an approval. allowances[] carries remainingAtomic and remainingDisplay per token. Identity fields (id, name, status, safeAddress, delegateAddress, chainId) are unchanged from before.',
3583
+ behavior: 'Reads identity plus the live spend-authority snapshot in one shot \u2014 the on-chain AllowanceModule on the legacy rail, the active budget delegation on the delegation rail. 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. What an over-budget payment does differs by rail: on the legacy AllowanceModule rail it is queued for the wallet owner to approve in Haven; on the delegation rail there is no approval queue \u2014 an over-budget redemption reverts on-chain, so ask the owner to grant or raise the budget in Haven rather than waiting for an approval. allowances[] carries remainingAtomic and remainingDisplay per token. Identity fields (id, name, status, safeAddress, delegateAddress, chainId) are unchanged from before.',
3501
3584
  nextActionGuidance: ""
3502
3585
  },
3503
3586
  getAllowances: {
@@ -3528,7 +3611,7 @@ var toolDescriptions = {
3528
3611
  summary: "Step 1 of a purchase: discover payable services from Haven's curated merchant catalog \u2014 names, prices, and which pay tool to use next.",
3529
3612
  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. 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 pay tool to use next.",
3530
3613
  behavior: "Use each entry's suggested_tool field first \u2014 it names the exact next call. 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, and suggested_tool. 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.",
3531
- nextActionGuidance: `Pick an entry and pay it with the tool named in suggested_tool, passing the entry's resource_url, tool_name, and tool_arguments for MCP merchants. Confirm the price from the live pay-tool result (not the catalog), and pass the user's cap as max_amount_human in whole tokens ("no more than 1 USDC" \u2192 max_amount_human: "1") \u2014 never convert it to atomic units by hand (#1351).`
3614
+ nextActionGuidance: `Pick an entry and pay it with the tool named in suggested_tool, passing the entry's resource_url, tool_name, and tool_arguments for MCP merchants. Confirm the price from the live pay-tool result (not the catalog), and pass the user's cap as max_amount_human in whole tokens ("no more than 1 USDC" \u2192 max_amount_human: "1") \u2014 never convert it to atomic units by hand.`
3532
3615
  },
3533
3616
  sweep_delegate: {
3534
3617
  summary: "Sweep stranded USDC and/or ETH from the delegate wallet back to the originating Safe.",
@@ -3773,9 +3856,13 @@ user's approval in Haven.
3773
3856
 
3774
3857
  Hosted tools run in the \`mcp__haven__\` namespace. Local signing tools run in
3775
3858
  the \`mcp__haven-signer__\` namespace and keep the delegate key on this machine.
3776
- Tool results carry the exact next step (\`next_action\`, \`next_tool\`,
3777
- \`next_arguments\`) \u2014 follow those fields first; the prose below is fallback
3778
- and orientation, not the source of truth.
3859
+ That namespacing is Claude-family; other runtimes name the servers by their
3860
+ own config keys (Codex: \`haven\`, \`haven_signer\`). Tool results carry the
3861
+ exact next step (\`next_action\`, \`next_tool\`, \`next_arguments\`, plus the
3862
+ runtime-neutral \`next_tool_server\` + \`next_tool_name\` \u2014 the bare tool name
3863
+ on that logical server, whatever your runtime calls it).
3864
+ Follow those fields first; the prose below is fallback and orientation, not
3865
+ the source of truth.
3779
3866
 
3780
3867
  ## When to use this skill
3781
3868
 
@@ -3801,8 +3888,10 @@ Before any payment, confirm the *live remaining* budget with the tools \u2014
3801
3888
  spending:
3802
3889
 
3803
3890
  - \`mcp__haven__haven_get_agent\` \u2014 the recommended first call: identity
3804
- (wallet, network) plus a readiness signal (\`ready\` / \`needs_approval\` /
3805
- \`revoked\`) and live remaining per-token allowance, in one shot.
3891
+ (wallet, network) plus \`spend_authority_readiness\` (\`ready\` / \`needs_approval\` /
3892
+ \`revoked\`) and live remaining per-token allowance, in one shot. That signal
3893
+ covers hosted identity and on-chain spend authority only \u2014 it cannot see the
3894
+ local signer; the signer is verified by calling any signer tool.
3806
3895
  - \`mcp__haven__haven_get_allowances\` \u2014 detailed per-token breakdown
3807
3896
  (configured, spent, reset window) when you need more than the summary.
3808
3897
 
@@ -4040,6 +4129,6 @@ function sameUrl(a, b) {
4040
4129
  }
4041
4130
  }
4042
4131
 
4043
- 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, DISCOVERY_MAX_BYTES, ERC7710_ASSET_TRANSFER_METHOD, HAVEN_MINIMUM_NODE_VERSION, HAVEN_SKILL_BODY_MD, HAVEN_SKILL_MD, HavenApiError, HavenClient, HavenError, HavenPaymentStateError, HavenSigningError, HavenTimeoutError, HavenUnsupportedSignerVersionError, MERCHANT_DISCOVERY_PATHS, MerchantTimeoutError, RECEIPT_VERSION, SIGNER_UPDATE_FALLBACK, SKILL_FOLDER_NAME, SWEEP_BASE_CHAIN_ID, SWEEP_BASE_SEPOLIA_CHAIN_ID, SWEEP_BASE_SEPOLIA_USDC_ADDRESS, SWEEP_BASE_USDC_ADDRESS, SignerRefusalCode, TRANSFER_WITH_AUTHORIZATION_TYPES, X402PaymentHeaderValidationError, X402UnexpectedStatusError, X402_MAX_AUTHORIZATION_WINDOW_SECONDS, X402_SETTLEMENT_FORWARD_MARGIN_SECONDS, addressFromKey, buildSweepAuthorizationMessage, buildSweepTypedData, buildX402ExpectedMessage, compareNodeVersions, composeDescription, decodeBase64Json, decodeBase64Utf8, discoverMerchantMcpUrl, encodeBase64Json, encodeBase64Utf8, encodePaymentProof, havenTools, isErc7710Option, isSupportedNodeVersion, isSweepableChain, normalizePaymentRequired, parsePaymentRequired, parsePaymentRequiredResponse, resolveTokenFromAddress, sameUrl, selectErc7710PaymentOption, selectPaymentOption, selectStandardPaymentOption, selectX402SettlementScheme, signHash, signUserOpTypedDataForDelegation, sweepUsdcAddress, sweepUsdcDomain, toStandardPaymentRequirements, toolDescriptions, unsupportedNodeVersionMessage, validateStandardX402PaymentHeader, verifyPaymentReceipt, verifySignature, x402AssetTransferMethod, x402AuthorizationAmount, x402FacilitatorAddresses };
4132
+ 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, DISCOVERY_MAX_BYTES, ERC7710_ASSET_TRANSFER_METHOD, HAVEN_MINIMUM_NODE_VERSION, HAVEN_SKILL_BODY_MD, HAVEN_SKILL_MD, HavenApiError, HavenClient, HavenError, HavenPaymentStateError, HavenSigningError, HavenTimeoutError, HavenUnsupportedSignerVersionError, MERCHANT_DISCOVERY_PATHS, MerchantTimeoutError, RECEIPT_VERSION, SIGNER_UPDATE_FALLBACK, SKILL_FOLDER_NAME, SWEEP_BASE_CHAIN_ID, SWEEP_BASE_SEPOLIA_CHAIN_ID, SWEEP_BASE_SEPOLIA_USDC_ADDRESS, SWEEP_BASE_USDC_ADDRESS, SignerRefusalCode, TRANSFER_WITH_AUTHORIZATION_TYPES, X402AlreadySettledError, X402PaymentHeaderValidationError, X402UnexpectedStatusError, X402_MAX_AUTHORIZATION_WINDOW_SECONDS, X402_SETTLEMENT_FORWARD_MARGIN_SECONDS, addressFromKey, buildSweepAuthorizationMessage, buildSweepTypedData, buildX402ExpectedMessage, compareNodeVersions, composeDescription, decodeBase64Json, decodeBase64Utf8, discoverMerchantMcpUrl, encodeBase64Json, encodeBase64Utf8, encodePaymentProof, havenTools, isErc7710Option, isSupportedNodeVersion, isSweepableChain, normalizePaymentRequired, parsePaymentRequired, parsePaymentRequiredResponse, resolveTokenFromAddress, sameUrl, selectErc7710PaymentOption, selectPaymentOption, selectStandardPaymentOption, selectX402SettlementScheme, signHash, signUserOpTypedDataForDelegation, sweepUsdcAddress, sweepUsdcDomain, toStandardPaymentRequirements, toolDescriptions, unsupportedNodeVersionMessage, validateStandardX402PaymentHeader, verifyPaymentReceipt, verifySignature, x402AssetTransferMethod, x402AuthorizationAmount, x402FacilitatorAddresses };
4044
4133
  //# sourceMappingURL=index.js.map
4045
4134
  //# sourceMappingURL=index.js.map