@haven_ai/sdk 0.11.0-alpha.0 → 0.13.0-alpha.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (42) hide show
  1. package/README.md +37 -4
  2. package/dist/{base64-D4VZhgUg.d.ts → base64-C3mJNLYR.d.cts} +10 -2
  3. package/dist/{base64-DR4Gl8cD.d.cts → base64-CxzIc3KH.d.ts} +10 -2
  4. package/dist/{chunk-IKQIIMM6.cjs → chunk-4Z7PKLFD.cjs} +38 -37
  5. package/dist/chunk-4Z7PKLFD.cjs.map +1 -0
  6. package/dist/{chunk-34YJQFCM.js → chunk-CK6VRIPR.js} +3 -3
  7. package/dist/chunk-CK6VRIPR.js.map +1 -0
  8. package/dist/{chunk-ZDJ55LR7.js → chunk-CXQ42FOA.js} +7 -6
  9. package/dist/chunk-CXQ42FOA.js.map +1 -0
  10. package/dist/{chunk-MZKLAHBK.cjs → chunk-LZHCIK7L.cjs} +56 -4
  11. package/dist/chunk-LZHCIK7L.cjs.map +1 -0
  12. package/dist/{chunk-I7SL4A6X.js → chunk-MI7LVP56.js} +55 -5
  13. package/dist/chunk-MI7LVP56.js.map +1 -0
  14. package/dist/{chunk-DXBAN4SN.cjs → chunk-Z5MTKU6O.cjs} +4 -4
  15. package/dist/chunk-Z5MTKU6O.cjs.map +1 -0
  16. package/dist/edge.cjs +93 -93
  17. package/dist/edge.d.cts +11 -8
  18. package/dist/edge.d.ts +11 -8
  19. package/dist/edge.js +4 -4
  20. package/dist/index.cjs +456 -356
  21. package/dist/index.cjs.map +1 -1
  22. package/dist/index.d.cts +26 -15
  23. package/dist/index.d.ts +26 -15
  24. package/dist/index.js +140 -48
  25. package/dist/index.js.map +1 -1
  26. package/dist/{receipt-CbD8L0WL.d.ts → receipt-CTOPh2fm.d.ts} +5 -4
  27. package/dist/{receipt-_bWCELEH.d.cts → receipt-DwRqGjGb.d.cts} +5 -4
  28. package/dist/test-support.cjs +58 -21
  29. package/dist/test-support.cjs.map +1 -1
  30. package/dist/test-support.d.cts +47 -3
  31. package/dist/test-support.d.ts +47 -3
  32. package/dist/test-support.js +37 -3
  33. package/dist/test-support.js.map +1 -1
  34. package/dist/{types-DKmvjXK6.d.cts → types-3vUBmumz.d.cts} +163 -2
  35. package/dist/{types-DKmvjXK6.d.ts → types-3vUBmumz.d.ts} +163 -2
  36. package/package.json +2 -2
  37. package/dist/chunk-34YJQFCM.js.map +0 -1
  38. package/dist/chunk-DXBAN4SN.cjs.map +0 -1
  39. package/dist/chunk-I7SL4A6X.js.map +0 -1
  40. package/dist/chunk-IKQIIMM6.cjs.map +0 -1
  41. package/dist/chunk-MZKLAHBK.cjs.map +0 -1
  42. package/dist/chunk-ZDJ55LR7.js.map +0 -1
package/dist/index.js CHANGED
@@ -1,9 +1,9 @@
1
- import { decodeBase64Json, selectStandardPaymentOption, buildX402IdempotencyKey, x402AuthorizationAmount, assertBoundDirectPaymentUserOp, assertFundingLegPaysDelegate, HavenTypedDataRefusedError, assertOwnSettlementChild, parsePaymentRequiredResponse, x402PaymentHeaderNamesSent, SDK_CLIENT_IDENTITY, readClientUpdate, HAVEN_CLIENT_HEADER, isSweepableChain, sweepUsdcAddress, x402PaymentHeaderNamesFor, X402_PAYMENT_HEADER_NAMES, toStandardPaymentRequirements, deriveX402PaymentNonce, encodeBase64Json, x402V2PaymentEnvelope, isErc7710Option, buildSignedTaxDeclaration, encodeTaxDeclarationHeader, selectX402SettlementScheme, selectErc7710PaymentOption, x402AssetTransferMethod, resolveTokenFromAddress, normalizePaymentRequired, X_TAX_DECLARATION_HEADER } from './chunk-ZDJ55LR7.js';
2
- export { DEFAULT_NEXT_TOOL_BY_ACTION, ERC7710_ASSET_TRANSFER_METHOD, HAVEN_CLIENT_HEADER, HAVEN_MINIMUM_NODE_VERSION, HavenTypedDataRefusedError, MAX_TASK_BUDGET_TTL_SECONDS, NEXT_TOOL_SERVER_NAMES, NEXT_TOOL_SERVER_ROLES, SDK_CLIENT_IDENTITY, SDK_VERSION, SWEEP_BASE_CHAIN_ID, SWEEP_BASE_SEPOLIA_CHAIN_ID, SWEEP_BASE_SEPOLIA_USDC_ADDRESS, SWEEP_BASE_USDC_ADDRESS, TRANSFER_WITH_AUTHORIZATION_TYPES, TYPED_DATA_NOT_ALLOWED, X402PaymentHeaderValidationError, X402_LEGACY_PAYMENT_HEADER_NAME, X402_MAX_AUTHORIZATION_WINDOW_SECONDS, X402_PAYMENT_HEADER_NAME, X402_PAYMENT_HEADER_NAMES_SENT, X402_PAYMENT_NONCE_TAG, X402_PAYMENT_REQUIRED_HEADER_NAME, X402_PAYMENT_RESPONSE_HEADER_NAME, X402_SETTLEMENT_FORWARD_MARGIN_SECONDS, assertBoundDirectPaymentUserOp, assertOwnTaskBudgetCloseUserOp, assertOwnTaskChild, buildSweepAuthorizationMessage, buildSweepTypedData, buildX402ExpectedMessage, compareNodeVersions, createNextStepBuilder, decodeBase64Json, decodeBase64Utf8, defaultNextToolFor, deriveX402PaymentNonce, encodeBase64Json, encodeBase64Utf8, encodePaymentProof, hashDelegation, havenClientIdentity, isErc7710Option, isSupportedNodeVersion, isSweepableChain, isTaskChildTypedData, normalizePaymentRequired, parseNextTool, parsePaymentRequired, parsePaymentRequiredResponse, readClientUpdate, renderNextTool, resolveTokenBySymbol, resolveTokenFromAddress, selectErc7710PaymentOption, selectPaymentOption, selectStandardPaymentOption, selectX402SettlementScheme, sweepUsdcAddress, sweepUsdcDomain, toStandardPaymentRequirements, unsupportedNodeVersionMessage, validateStandardX402PaymentHeader, x402AssetTransferMethod, x402AuthorizationAmount, x402FacilitatorAddresses, x402PaymentHeaderNamesFor, x402RetryHeadersFor, x402V2PaymentEnvelope } from './chunk-ZDJ55LR7.js';
3
- import { verifyPaymentReceipt } from './chunk-34YJQFCM.js';
4
- export { RECEIPT_VERSION, verifyPaymentReceipt } from './chunk-34YJQFCM.js';
5
- import { HavenSigningError, HavenError, DEFAULT_CONFIRMATION_TIMEOUT_MS, HavenApiError, assertUserOpTypedDataBinding, AgentPaymentRail, AgentPaymentPhase, HavenTimeoutError, X402TaskBudgetMismatchError, X402UnexpectedStatusError, X402AlreadySettledError, MerchantTimeoutError, AgentPaymentWarningCode, AgentPaymentNextAction, HavenPaymentStateError, HavenZeroSettlementHashError, X402Erc7710AlreadySettledError, chainIdForNetwork } from './chunk-I7SL4A6X.js';
6
- 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, CONNECTOR_PACKAGE_NAME, DEFAULT_CONFIRMATION_TIMEOUT_MS, DIRECT_SIGN_CONTEXT_VERSION, ENTRY_POINT_V07, HAVEN_CONNECTOR_CHANNEL, HYBRID_DELEGATOR_DOMAIN_NAME, HYBRID_DELEGATOR_DOMAIN_VERSION, HavenApiError, HavenError, HavenPaymentStateError, HavenSigningError, HavenTimeoutError, HavenUnsupportedSignerVersionError, HavenUserOpBindingError, HavenZeroSettlementHashError, MerchantTimeoutError, PACKED_USER_OPERATION_FIELDS, ROOT_AUTHORITY, SIGNER_UPDATE_FALLBACK, SignerRefusalCode, TASK_SIGN_CONTEXT_VERSION, X402AlreadySettledError, X402Erc7710AlreadySettledError, X402TaskBudgetMismatchError, X402UnexpectedStatusError, assertUserOpTypedDataBinding, connectorRerunCommand, connectorSpec, connectorUpgradeCommand, deriveDelegateAccountAddress, isConnectorChannel, isPackedUserOperationTypedData, packedUserOperationHash, resolveConnectorChannel, signerUpdateFallback, verifySettlementChild } from './chunk-I7SL4A6X.js';
1
+ import { decodeBase64Json, selectStandardPaymentOption, buildX402IdempotencyKey, x402AuthorizationAmount, assertBoundDirectPaymentUserOp, assertFundingLegPaysDelegate, HavenTypedDataRefusedError, assertOwnSettlementChild, parsePaymentRequiredResponse, x402PaymentHeaderNamesSent, SDK_CLIENT_IDENTITY, readClientUpdate, HAVEN_CLIENT_HEADER, isSweepableChain, sweepUsdcAddress, x402PaymentHeaderNamesFor, X402_PAYMENT_HEADER_NAMES, toStandardPaymentRequirements, deriveX402PaymentNonce, encodeBase64Json, x402V2PaymentEnvelope, isErc7710Option, buildSignedTaxDeclaration, encodeTaxDeclarationHeader, selectX402SettlementScheme, selectErc7710PaymentOption, x402AssetTransferMethod, resolveTokenFromAddress, normalizePaymentRequired, X_TAX_DECLARATION_HEADER } from './chunk-CXQ42FOA.js';
2
+ export { DEFAULT_NEXT_TOOL_BY_ACTION, ERC7710_ASSET_TRANSFER_METHOD, HAVEN_CLIENT_HEADER, HAVEN_MINIMUM_NODE_VERSION, HavenTypedDataRefusedError, MAX_TASK_BUDGET_TTL_SECONDS, NEXT_TOOL_SERVER_NAMES, NEXT_TOOL_SERVER_ROLES, SDK_CLIENT_IDENTITY, SDK_VERSION, SWEEP_BASE_CHAIN_ID, SWEEP_BASE_SEPOLIA_CHAIN_ID, SWEEP_BASE_SEPOLIA_USDC_ADDRESS, SWEEP_BASE_USDC_ADDRESS, TRANSFER_WITH_AUTHORIZATION_TYPES, TYPED_DATA_NOT_ALLOWED, X402PaymentHeaderValidationError, X402_LEGACY_PAYMENT_HEADER_NAME, X402_MAX_AUTHORIZATION_WINDOW_SECONDS, X402_PAYMENT_HEADER_NAME, X402_PAYMENT_HEADER_NAMES_SENT, X402_PAYMENT_NONCE_TAG, X402_PAYMENT_REQUIRED_HEADER_NAME, X402_PAYMENT_RESPONSE_HEADER_NAME, X402_SETTLEMENT_FORWARD_MARGIN_SECONDS, assertBoundDirectPaymentUserOp, assertOwnTaskBudgetCloseUserOp, assertOwnTaskChild, buildSweepAuthorizationMessage, buildSweepTypedData, buildX402ExpectedMessage, compareNodeVersions, createNextStepBuilder, decodeBase64Json, decodeBase64Utf8, defaultNextToolFor, deriveX402PaymentNonce, encodeBase64Json, encodeBase64Utf8, encodePaymentProof, hashDelegation, havenClientIdentity, isErc7710Option, isSupportedNodeVersion, isSweepableChain, isTaskChildTypedData, normalizePaymentRequired, parseNextTool, parsePaymentRequired, parsePaymentRequiredResponse, readClientUpdate, renderNextTool, resolveTokenBySymbol, resolveTokenFromAddress, selectErc7710PaymentOption, selectPaymentOption, selectStandardPaymentOption, selectX402SettlementScheme, sweepUsdcAddress, sweepUsdcDomain, toStandardPaymentRequirements, unsupportedNodeVersionMessage, validateStandardX402PaymentHeader, x402AssetTransferMethod, x402AuthorizationAmount, x402FacilitatorAddresses, x402PaymentHeaderNamesFor, x402RetryHeadersFor, x402V2PaymentEnvelope } from './chunk-CXQ42FOA.js';
3
+ import { verifyPaymentReceipt } from './chunk-CK6VRIPR.js';
4
+ export { RECEIPT_VERSION, verifyPaymentReceipt } from './chunk-CK6VRIPR.js';
5
+ import { HavenSigningError, HavenError, DEFAULT_CONFIRMATION_TIMEOUT_MS, HavenApiError, assertUserOpTypedDataBinding, AgentPaymentRail, AgentPaymentPhase, HavenTimeoutError, X402TaskBudgetMismatchError, X402UnexpectedStatusError, X402AlreadySettledError, MerchantTimeoutError, AgentPaymentWarningCode, AgentPaymentNextAction, FundingConfirmationUnknownError, FundingRevertedError, HavenPaymentStateError, HavenZeroSettlementHashError, X402Erc7710AlreadySettledError, chainIdForNetwork } from './chunk-MI7LVP56.js';
6
+ 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, CONNECTOR_PACKAGE_NAME, DEFAULT_CONFIRMATION_TIMEOUT_MS, DIRECT_SIGN_CONTEXT_VERSION, ENTRY_POINT_V07, FundingConfirmationUnknownError, FundingRevertedError, HAVEN_CONNECTOR_CHANNEL, HYBRID_DELEGATOR_DOMAIN_NAME, HYBRID_DELEGATOR_DOMAIN_VERSION, HavenApiError, HavenError, HavenPaymentStateError, HavenSigningError, HavenTimeoutError, HavenUnsupportedSignerVersionError, HavenUserOpBindingError, HavenZeroSettlementHashError, MerchantTimeoutError, PACKED_USER_OPERATION_FIELDS, ROOT_AUTHORITY, SIGNER_UPDATE_FALLBACK, SignerRefusalCode, TASK_SIGN_CONTEXT_VERSION, X402AlreadySettledError, X402Erc7710AlreadySettledError, X402TaskBudgetMismatchError, X402UnexpectedStatusError, assertUserOpTypedDataBinding, connectorRerunCommand, connectorSpec, connectorUpgradeCommand, deriveDelegateAccountAddress, isConnectorChannel, isPackedUserOperationTypedData, packedUserOperationHash, resolveConnectorChannel, signerUpdateFallback, verifySettlementChild } from './chunk-MI7LVP56.js';
7
7
  import { hashTypedData } from 'viem';
8
8
  import { ethers } from 'ethers';
9
9
  import { AsyncLocalStorage } from 'async_hooks';
@@ -143,12 +143,16 @@ var HavenApiTransport = class {
143
143
  return data;
144
144
  } catch (err) {
145
145
  if (err instanceof HavenApiError) throw err;
146
+ const failureCause = { kind: "no_answer", method };
146
147
  if (err instanceof Error && err.name === "AbortError") {
147
- throw new HavenApiError(`Request to ${path} timed out`, 408);
148
+ throw new HavenApiError(`Request to ${path} timed out`, 408, void 0, void 0, failureCause);
148
149
  }
149
150
  throw new HavenApiError(
150
151
  `Request to ${path} failed: ${err instanceof Error ? err.message : String(err)}`,
151
- 0
152
+ 0,
153
+ void 0,
154
+ void 0,
155
+ failureCause
152
156
  );
153
157
  } finally {
154
158
  clearTimeout(timeout);
@@ -282,6 +286,10 @@ function mapPaymentStatusResult(raw) {
282
286
  // carried through even when `null` (a real, known answer).
283
287
  ...raw.settlement_scheme !== void 0 ? { settlementScheme: raw.settlement_scheme } : {},
284
288
  ...raw.merchant_settlement_recorded === true ? { merchantSettlementRecorded: true } : {},
289
+ // #4530: the recorded transaction itself — absent from the raw payload
290
+ // (older backend, or nothing recorded) stays absent here, so the field
291
+ // never claims a hash the backend did not record.
292
+ ...typeof raw.merchant_settlement_tx_hash === "string" ? { merchantSettlementTxHash: raw.merchant_settlement_tx_hash } : {},
285
293
  // #3494: additive, mirrors the backend's own `status === 'failed'` gate —
286
294
  // absent from the raw payload (older backend, or any non-failed status)
287
295
  // stays absent here.
@@ -354,6 +362,30 @@ function mapPaymentReceipt(raw) {
354
362
  if ("approval_request_id" in raw) {
355
363
  receipt.approvalRequestId = raw.approval_request_id ?? null;
356
364
  }
365
+ if ("settlement_scheme" in raw) {
366
+ receipt.settlementScheme = raw.settlement_scheme ?? null;
367
+ }
368
+ if ("budget_delegation_hash" in raw) {
369
+ receipt.budgetDelegationHash = raw.budget_delegation_hash ?? null;
370
+ }
371
+ if ("delivery_quality" in raw) {
372
+ receipt.deliveryQuality = raw.delivery_quality ?? null;
373
+ }
374
+ if ("delivery_note" in raw) {
375
+ receipt.deliveryNote = raw.delivery_note ?? null;
376
+ }
377
+ if ("delivery_reported_at" in raw) {
378
+ receipt.deliveryReportedAt = raw.delivery_reported_at ?? null;
379
+ }
380
+ if ("delivery_reference" in raw) {
381
+ receipt.deliveryReference = raw.delivery_reference ?? null;
382
+ }
383
+ if ("purchase_group_id" in raw) {
384
+ receipt.purchaseGroupId = raw.purchase_group_id ?? null;
385
+ }
386
+ if ("parent_payment_id" in raw) {
387
+ receipt.parentPaymentId = raw.parent_payment_id ?? null;
388
+ }
357
389
  return receipt;
358
390
  }
359
391
 
@@ -473,7 +505,7 @@ function throwPaymentStateError(label, raw) {
473
505
  throw new HavenPaymentStateError(state.message, statusCode, state, raw);
474
506
  }
475
507
  if (raw.status === "pending_approval") {
476
- throw new HavenApiError(
508
+ throw HavenApiError.deterministic(
477
509
  `${label} exceeds the agent's on-chain budget and was declined; no approval is pending and none will arrive (payment_id: ${raw.payment_id}).`,
478
510
  statusCode,
479
511
  raw
@@ -488,6 +520,7 @@ function throwPaymentStateError(label, raw) {
488
520
  }
489
521
  const paymentId = raw.payment_id ? ` (payment_id: ${raw.payment_id})` : "";
490
522
  const message = raw.error ?? `${label} ${raw.status}${paymentId}`;
523
+ if (statusCode >= 500) throw HavenApiError.deterministic(message, statusCode, raw);
491
524
  throw new HavenApiError(message, statusCode, raw);
492
525
  }
493
526
 
@@ -1346,6 +1379,17 @@ function deriveReadiness(status, allowances) {
1346
1379
  if (status !== "active") return "revoked";
1347
1380
  return allowances.some((allowance) => safeBigInt(allowance.remainingAtomic) > 0n) ? "ready" : "needs_approval";
1348
1381
  }
1382
+ function deriveBudgetRefillsAt(readiness, allowances) {
1383
+ if (readiness !== "needs_approval" || allowances.length === 0) return null;
1384
+ const times = allowances.map((allowance) => allowance.nextRefillAt).filter((at) => at !== null && !Number.isNaN(Date.parse(at)));
1385
+ if (times.length === 0) return null;
1386
+ return times.reduce((earliest, at) => Date.parse(at) < Date.parse(earliest) ? at : earliest);
1387
+ }
1388
+ function deriveBudgetEndedAt(readiness, allowances, budgetEndedAt) {
1389
+ if (readiness !== "needs_approval" || allowances.length > 0) return null;
1390
+ if (!budgetEndedAt || Number.isNaN(Date.parse(budgetEndedAt))) return null;
1391
+ return budgetEndedAt;
1392
+ }
1349
1393
  function formatRemainingDisplay(tokenAddress, tokenSymbol, remainingAtomic) {
1350
1394
  const token = resolveTokenFromAddress(tokenAddress);
1351
1395
  return token ? `${formatAtomicAmount(safeBigInt(remainingAtomic), token.decimals)} ${tokenSymbol}` : `${remainingAtomic} ${tokenSymbol} (atomic; unknown decimals)`;
@@ -1514,11 +1558,23 @@ var AccountReads = class {
1514
1558
  isResetPending: allowance.onchain.isResetPending,
1515
1559
  // #3731: same provenance as the detailed read's field — the compact
1516
1560
  // row is a projection of the same allowance, never a second source.
1517
- fundsCoverRemaining: allowance.fundsCoverRemaining
1561
+ fundsCoverRemaining: allowance.fundsCoverRemaining,
1562
+ nextRefillAt: allowance.nextRefillAt
1518
1563
  };
1519
1564
  });
1520
1565
  const readiness = deriveReadiness(agent.status, allowances);
1521
- return { ...agent, readiness, spend_authority_readiness: readiness, allowances, taskBudgets, pendingSubBudgetSignatures };
1566
+ const budgetRefillsAt = deriveBudgetRefillsAt(readiness, allowances);
1567
+ const budgetEndedAt = deriveBudgetEndedAt(readiness, allowances, allowanceSummary.budgetEndedAt);
1568
+ return {
1569
+ ...agent,
1570
+ readiness,
1571
+ spend_authority_readiness: readiness,
1572
+ budgetRefillsAt,
1573
+ budgetEndedAt,
1574
+ allowances,
1575
+ taskBudgets,
1576
+ pendingSubBudgetSignatures
1577
+ };
1522
1578
  }
1523
1579
  /**
1524
1580
  * #3329: `GET /task-budgets?status=open`, mapped to the condensed summary
@@ -1614,6 +1670,9 @@ var AccountReads = class {
1614
1670
  accountAddress: raw.account_address,
1615
1671
  delegateAddress: raw.delegate_address,
1616
1672
  chainId: raw.chain_id,
1673
+ // #4352: a non-empty parseable date → itself; anything else (absent on
1674
+ // an older backend, null when no budget has ended) → null.
1675
+ budgetEndedAt: typeof raw.budget_ended_at === "string" && !Number.isNaN(Date.parse(raw.budget_ended_at)) ? raw.budget_ended_at : null,
1617
1676
  supersededBy: mapSupersession(raw.superseded_by),
1618
1677
  allowances: raw.allowances.map((allowance) => ({
1619
1678
  id: allowance.id,
@@ -1632,6 +1691,9 @@ var AccountReads = class {
1632
1691
  // "unverifiable", not a missing key. The key is absent on the wire
1633
1692
  // when the remaining is 0; that maps to null here too.
1634
1693
  fundsCoverRemaining: allowance.funds_cover_remaining === true || allowance.funds_cover_remaining === false ? allowance.funds_cover_remaining : null,
1694
+ // #4355: a non-empty string → itself; anything else (absent on an
1695
+ // older backend, null when the budget never refills) → null.
1696
+ nextRefillAt: typeof allowance.next_refill_at === "string" && allowance.next_refill_at !== "" ? allowance.next_refill_at : null,
1635
1697
  remainingDisplay: formatRemainingDisplay(allowance.token_address, allowance.token_symbol, allowance.onchain.remaining),
1636
1698
  onchain: {
1637
1699
  amount: allowance.onchain.amount,
@@ -2257,7 +2319,7 @@ var X402FundingLeg = class {
2257
2319
  }
2258
2320
  this.assertSignableAuthorizationState("x402 payment", raw);
2259
2321
  if (!raw.sign_data?.hash) {
2260
- throw new HavenApiError("No sign_hash returned from x402/authorize", 500, raw);
2322
+ throw HavenApiError.deterministic("No sign_hash returned from x402/authorize", 500, raw);
2261
2323
  }
2262
2324
  const sig = await this.signForData(raw.sign_data, {
2263
2325
  asset: option.asset,
@@ -2379,20 +2441,27 @@ var X402FundingLeg = class {
2379
2441
  * Skipped when `chainRpcs` does not include the chain; in that case Haven's
2380
2442
  * backend has already confirmed on-chain submission and callers accept the
2381
2443
  * small propagation window as a trade-off for not configuring an RPC URL.
2444
+ *
2445
+ * #4470: the two outcomes are typed apart. ethers 6 `waitForTransaction`
2446
+ * REJECTS on its timeout (and on an RPC failure) rather than resolving
2447
+ * null, so that path is `FundingConfirmationUnknownError` — funding may
2448
+ * have moved. A mined receipt with `status !== 1` is `FundingRevertedError`.
2382
2449
  */
2383
2450
  async waitForFundingTx(txHash, chainId, timeoutMs = 3e4) {
2384
2451
  if (!txHash || !chainId) return;
2385
2452
  const rpcUrl = this.chainRpcs[chainId];
2386
2453
  if (!rpcUrl) return;
2387
2454
  const provider = createJsonRpcProvider(rpcUrl);
2388
- const onChainReceipt = await provider.waitForTransaction(txHash, 1, timeoutMs);
2389
- if (!onChainReceipt || onChainReceipt.status !== 1) {
2390
- throw new HavenApiError(
2391
- "Funding tx did not confirm on-chain within the timeout window.",
2392
- 500,
2393
- { txHash, chainId }
2394
- );
2455
+ let onChainReceipt;
2456
+ try {
2457
+ onChainReceipt = await provider.waitForTransaction(txHash, 1, timeoutMs);
2458
+ } catch (err) {
2459
+ throw new FundingConfirmationUnknownError(txHash, chainId, err instanceof Error ? err.message : String(err));
2460
+ }
2461
+ if (!onChainReceipt) {
2462
+ throw new FundingConfirmationUnknownError(txHash, chainId, "no receipt within the timeout window");
2395
2463
  }
2464
+ if (onChainReceipt.status !== 1) throw new FundingRevertedError(txHash, chainId);
2396
2465
  }
2397
2466
  /**
2398
2467
  * Can the delegate EOA still fund an authorization for `amountAtomic`?
@@ -2574,7 +2643,7 @@ var X402Erc7710 = class {
2574
2643
  ...options.subBudgetId ? { subBudgetId: options.subBudgetId } : {}
2575
2644
  });
2576
2645
  if (!raw.payment_id) {
2577
- throw new HavenApiError("No payment_id returned from x402/authorize", 500, raw);
2646
+ throw HavenApiError.deterministic("No payment_id returned from x402/authorize", 500, raw);
2578
2647
  }
2579
2648
  if (raw.status === "confirmed" && raw.tx_hash) {
2580
2649
  const requestedUrl = options.resourceUrl ?? paymentRequired.resource?.url;
@@ -2591,7 +2660,7 @@ var X402Erc7710 = class {
2591
2660
  }
2592
2661
  const signData = raw.sign_data;
2593
2662
  if (signData?.signature_scheme !== "eip712_delegation" || !signData.typed_data) {
2594
- throw new HavenApiError(
2663
+ throw HavenApiError.deterministic(
2595
2664
  `x402/authorize did not return an erc7710 settlement child (signature_scheme was ${JSON.stringify(signData?.signature_scheme)}). Refusing to sign a payload this path did not ask for.`,
2596
2665
  500,
2597
2666
  raw
@@ -2634,7 +2703,7 @@ var X402Erc7710 = class {
2634
2703
  throw err;
2635
2704
  }
2636
2705
  if (!settled.payment_header) {
2637
- throw new HavenApiError(
2706
+ throw HavenApiError.deterministic(
2638
2707
  "x402 settle returned no payment_header \u2014 the merchant cannot be retried.",
2639
2708
  500,
2640
2709
  settled
@@ -2805,10 +2874,10 @@ var MerchantCompletion = class {
2805
2874
  }
2806
2875
  async retryRequest(url, initialInit, paymentRequired, receipt) {
2807
2876
  if (!receipt.accepted) {
2808
- throw new HavenApiError("No accepted x402 option was recorded for payment retry", 500);
2877
+ throw HavenApiError.deterministic("No accepted x402 option was recorded for payment retry", 500);
2809
2878
  }
2810
2879
  if (!receipt.paymentHeader) {
2811
- throw new HavenApiError("No x402 payment header was returned for payment retry", 500);
2880
+ throw HavenApiError.deterministic("No x402 payment header was returned for payment retry", 500);
2812
2881
  }
2813
2882
  let retryInit = initialInit;
2814
2883
  if (this.getTaxDeclarationHeader && this.delegateKey) {
@@ -3172,7 +3241,11 @@ var MerchantCompletion = class {
3172
3241
  merchantStatus: input.merchantStatus,
3173
3242
  // #3778: accepted outcomes only — a rejection delivered nothing, so
3174
3243
  // there is no deliverable to point at.
3175
- ...input.deliveryReference ? { deliveryReference: input.deliveryReference } : {}
3244
+ ...input.deliveryReference ? { deliveryReference: input.deliveryReference } : {},
3245
+ // #4534: the purchase correlation key — accepted outcomes only, for the
3246
+ // same reason: a rejected retry bought nothing to group.
3247
+ ...input.purchaseGroupId ? { purchaseGroupId: input.purchaseGroupId } : {},
3248
+ ...input.merchantResponse ? { merchantResponse: input.merchantResponse } : {}
3176
3249
  });
3177
3250
  return { paymentId: status.paymentId, outcome: "accepted", txHash, resourceUrl, recorded: "evidence" };
3178
3251
  }
@@ -3194,15 +3267,16 @@ var MerchantCompletion = class {
3194
3267
  };
3195
3268
  for (let attempt = 0; ; attempt += 1) {
3196
3269
  const result = await this.postEvidenceOnce(body);
3197
- if (result.outcome !== "retryable" || attempt >= EVIDENCE_RETRY_DELAYS_MS.length) {
3270
+ if (result.outcome !== "retryable" || result.statusCode !== EVIDENCE_RETRYABLE_STATUS || attempt >= EVIDENCE_RETRY_DELAYS_MS.length) {
3198
3271
  return result;
3199
3272
  }
3200
3273
  await this.sleep(EVIDENCE_RETRY_DELAYS_MS[attempt]);
3201
3274
  }
3202
3275
  }
3203
3276
  /**
3204
- * ONE evidence attempt — the body of `reportEvidence`'s loop above. A 503
3205
- * answers `retryable` immediately; the caller decides whether to sleep and
3277
+ * ONE evidence attempt — the body of `reportEvidence`'s loop above. A 503,
3278
+ * or no verdict at all (a non-answer or a 408, #4470), answers `retryable`
3279
+ * immediately; the caller decides whether to sleep and
3206
3280
  * re-attempt (`reportEvidence`, and — #4215 — the local settlement report
3207
3281
  * in `retryRequest`) or hand the outcome to its consumer (#3764 D2, the
3208
3282
  * hosted settlement report).
@@ -3213,7 +3287,8 @@ var MerchantCompletion = class {
3213
3287
  return { outcome: "confirmed" };
3214
3288
  } catch (err) {
3215
3289
  const statusCode = err instanceof HavenApiError ? err.statusCode : void 0;
3216
- if (statusCode !== EVIDENCE_RETRYABLE_STATUS) {
3290
+ const noVerdict = statusCode === 408 || err instanceof HavenApiError && err.failureCause?.kind === "no_answer";
3291
+ if (statusCode !== EVIDENCE_RETRYABLE_STATUS && !noVerdict) {
3217
3292
  const reason = refusalReasonFromBody(err);
3218
3293
  const errorCode = refusalErrorCodeFromBody(err);
3219
3294
  return {
@@ -3634,7 +3709,7 @@ var HavenClient = class {
3634
3709
  }
3635
3710
  const fundingTo = options.delegateAddress ?? (await this.getAgent()).delegateAddress;
3636
3711
  if (!fundingTo) {
3637
- throw new HavenApiError("Authenticated agent has no delegate address registered.", 502);
3712
+ throw HavenApiError.deterministic("Authenticated agent has no delegate address registered.", 502);
3638
3713
  }
3639
3714
  const idempotencyKey = options.idempotencyKey ?? buildX402IdempotencyKey(paymentRequired, option);
3640
3715
  const raw = await this.post("/x402", {
@@ -3672,7 +3747,7 @@ var HavenClient = class {
3672
3747
  throwPaymentStateError("x402 payment", raw);
3673
3748
  }
3674
3749
  if (!raw.sign_data?.hash) {
3675
- throw new HavenApiError("No sign_hash returned from x402/authorize", 500, raw);
3750
+ throw HavenApiError.deterministic("No sign_hash returned from x402/authorize", 500, raw);
3676
3751
  }
3677
3752
  if (raw.sign_data.signature_scheme !== void 0 && !raw.sign_data.typed_data) {
3678
3753
  throw new HavenSigningError(
@@ -3680,7 +3755,7 @@ var HavenClient = class {
3680
3755
  );
3681
3756
  }
3682
3757
  if (!raw.x402_expected_auth) {
3683
- throw new HavenApiError("No x402 expected-context binding returned from x402/authorize", 500, raw);
3758
+ throw HavenApiError.deterministic("No x402 expected-context binding returned from x402/authorize", 500, raw);
3684
3759
  }
3685
3760
  return {
3686
3761
  paymentId: raw.payment_id,
@@ -3910,6 +3985,12 @@ var HavenClient = class {
3910
3985
  * degrade-to-warning path and the ledger row would still land while the
3911
3986
  * purchase proceeded.
3912
3987
  *
3988
+ * #4458: an agent whose only budget for the token has ENDED is refused the
3989
+ * same way with `error_code: 'delegation_expired'` and `budget_ended_at`
3990
+ * (ISO 8601) — also thrown, also recorded (`source: 'hosted_prepare'`).
3991
+ * The remedy differs: the owner sets a NEW budget, waiting for a period to
3992
+ * reset cannot help.
3993
+ *
3913
3994
  * #3518: without `merchantTo`, an agent whose budgets for the token are all
3914
3995
  * merchant-locked gets 409 `budget_requires_recipient` (thrown, nothing
3915
3996
  * recorded) naming the pins in `budget_recipient_addresses` — repeat the
@@ -3934,12 +4015,13 @@ var HavenClient = class {
3934
4015
  /**
3935
4016
  * Post-purchase allowance/budget summary for a settled payment (#1310).
3936
4017
  *
3937
- * Reuses the EXACT rail-aware read path {@link getAllowances} / #1306's
3938
- * catalog-purchase preflight `allowance` block use — `GET
3939
- * /machine-payments/allowances`, with delegation-rail values coming from
3940
- * the #1090 `deriveDelegationBudgets`-backed enforcer read, never
4018
+ * Reuses the EXACT rail-aware read path {@link getAllowances} uses —
4019
+ * `GET /machine-payments/allowances`, with delegation-rail values coming
4020
+ * from the #1090 `deriveDelegationBudgets`-backed enforcer read, never
3941
4021
  * `agent_allowances` — so this can never disagree with
3942
- * {@link getAllowances} for the same fixture. The settled token is
4022
+ * {@link getAllowances} for the same fixture. (#1306's catalog-purchase
4023
+ * preflight read the same path until #3054; it now calls
4024
+ * `POST /machine-payments/budget-precheck` instead.) The settled token is
3943
4025
  * resolved from {@link getPaymentStatus} so callers pass only
3944
4026
  * `paymentId`, never a second haven_get_agent-style round trip.
3945
4027
  *
@@ -4964,7 +5046,7 @@ var toolDescriptions = {
4964
5046
  getAgent: {
4965
5047
  summary: "Return the authenticated agent identity AND its live spend authority in one call: Haven wallet, delegate, chain, raw status, spend_authority_readiness, per-token remaining allowance (atomic + human-readable). The recommended first call in a new session.",
4966
5048
  selectionGuidance: "Use this as the session bootstrap, or to confirm identity together with whether the agent has spend authority right now. For per-token detail (configured vs spent vs reset window) use haven_get_allowances.",
4967
- behavior: `Reads identity plus the live spend-authority snapshot \u2014 the active on-chain budget delegation. spend_authority_readiness (readiness is a deprecated alias, same value) is "ready" when at least one token has remaining spend authority, "needs_approval" when the agent is active but has none, and "revoked" when the credential is not active. Readiness is AUTHORITY: it says a budget is live, not that the account holds funds. Each allowances[] row carries fundsCoverRemaining: false when the account's balance cannot back that row's whole remaining budget \u2014 a heads-up to mention to the user, not a refusal (budgets above the balance are a normal setup; an owner may top up weekly) \u2014 and null when the coverage read failed or the remaining figure was not read live; the key is absent when the remaining is 0, and rows for one token are compared alone. It covers hosted identity + on-chain spend authority ONLY \u2014 the hosted server cannot see the LOCAL signer, so "ready" does not mean the signer can start; verify the signer with a signer tool call or connect --doctor. An over-budget payment is declined before any money moves; there is no approval queue \u2014 ask the owner to grant or raise the budget in Haven. allowances[] carries id, tokenAddress, remainingAtomic, remainingDisplay per token. taskBudgets[] rows carry maxAtomic/maxDisplay plus spentAtomic/remainingAtomic/remainingDisplay \u2014 what the chain still allows that budget \u2014 and remainingIsFromChain: false marks a failed read (figures null, never the full cap); only live rows are listed (closed and expired omitted), each with its status (pending | open | closing) and isExpired, so a closing budget stays visible. pendingSubBudgetSignatures[] lists sub-budget rows awaiting YOUR signature, each with its haven_sign next step. Identity fields: id, name, status, accountAddress, delegateAddress, chainId.`,
5049
+ behavior: `Reads identity plus the live spend-authority snapshot \u2014 the active on-chain budget delegation. spend_authority_readiness (readiness is a deprecated alias, same value) is "ready" when at least one token has remaining spend authority, "needs_approval" when the agent is active but has none, and "revoked" when the credential is not active. needs_approval with no allowances[] rows and budgetEndedAt set: the budget ENDED on that date \u2014 ask the owner to set a new one, waiting will not help; with no rows and no budgetEndedAt: nothing approved yet; with rows: spent for this period, refilling at budgetRefillsAt (null: it ends first). Readiness is AUTHORITY \u2014 a budget is live, not that funds are held. Each allowances[] row carries fundsCoverRemaining: false when the account's balance cannot back that row's whole remaining budget \u2014 a heads-up to mention, not a refusal (budgets above the balance are normal; an owner may top up weekly) \u2014 and null when the coverage read failed or the remaining was not read live; the key is absent at remaining 0, and rows for one token are compared alone. 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 it with a signer tool call or connect --doctor. An over-budget payment is declined before any money moves; there is no approval queue \u2014 ask the owner to grant or raise the budget. allowances[] carries id, tokenAddress, remainingAtomic, remainingDisplay, nextRefillAt per token. taskBudgets[] rows carry maxAtomic/maxDisplay plus spentAtomic/remainingAtomic/remainingDisplay \u2014 what the chain still allows that budget \u2014 and remainingIsFromChain: false marks a failed read (figures null, never the cap); only live rows are listed, each with its status (pending | open | closing) and isExpired. pendingSubBudgetSignatures[] lists sub-budget rows awaiting YOUR signature, with the haven_sign step. Identity fields: id, name, status, accountAddress, delegateAddress, chainId.`,
4968
5050
  nextActionGuidance: ""
4969
5051
  },
4970
5052
  getAllowances: {
@@ -5286,7 +5368,7 @@ You are an AI agent whose user has no Haven account yet, or has one but has not
5286
5368
 
5287
5369
  ## What Haven is, in three sentences
5288
5370
 
5289
- Your user gives you a **budget on their own account** \u2014 not their wallet, and not a key to their funds. The budget is a delegation they sign, enforced on-chain: a payment over it, to the wrong recipient, or past its expiry is refused at execution time, not by a dashboard promise. Haven constructs and relays the payments; you get an agent credential and a signing key made on your machine, and your user can revoke the budget without you and without Haven.
5371
+ Your user gives you a **budget on their own account** \u2014 not their wallet, and not a key to their funds. The budget is a delegation they sign, enforced on-chain: a payment over it, to the wrong recipient, or past its expiry is refused at execution time, not by a dashboard promise. The budget also ENDS on a date your user chooses \u2014 30, 90 (default), 180 or 365 days out \u2014 and that day is enforced on-chain too. When the budget ends, your authority to pay ends with it; ask your user to renew it (a new approval on the agent's page, or a new \`budget grant\`). Haven constructs and relays the payments; you get an agent credential and a signing key made on your machine, and your user can revoke the budget without you and without Haven.
5290
5372
 
5291
5373
  ## The sequence
5292
5374
 
@@ -5359,7 +5441,7 @@ If the approval wait times out, nothing is lost \u2014 your agent is registered
5359
5441
  Call \`haven_get_agent\`, one of the Haven MCP tools the connector wires into your runtime in step 4. It returns identity plus \`spend_authority_readiness\`:
5360
5442
 
5361
5443
  - \`ready\` \u2014 a budget is live: you have the authority to pay. It does not say the account holds funds \u2014 the \`allowances[]\` rows carry \`funds_cover_remaining\`, and \`false\` there is a heads-up to mention to your user, not a refusal.
5362
- - \`needs_approval\` \u2014 the connector finished, nobody approved yet. Ask your user again, in their Haven tab; there is no queue to wait in.
5444
+ - \`needs_approval\` \u2014 no budget can pay. \`allowances[]\` empty with \`budgetEndedAt\` set: your budget ENDED on that date; waiting will not help \u2014 ask your user to set a new one in their Haven tab. \`allowances[]\` empty with \`budgetEndedAt\` null: nobody approved one yet; ask your user again \u2014 there is no queue. Rows, all spent: it refills at \`budgetRefillsAt\`; tell your user, then wait or ask them to raise it (\`null\`: it ends first; ask them to renew it).
5363
5445
  - \`revoked\` \u2014 the credential is not active; ask your user to create a new agent.
5364
5446
 
5365
5447
  \`ready\` covers hosted identity and the budget only, not your local signer. Check that with \`npx -y @haven_ai/connect@<channel> --doctor\`, the same tag your prompt named \u2014 a separate command, so the command-modification rule does not bind it.
@@ -5395,7 +5477,7 @@ Then:
5395
5477
  1. Run \`<packages.cli.one_liner> login\`. That command is the manifest's \`packages.cli.one_liner\` (\`/.well-known/haven.json\`) \u2014 run it as given: \`packages.cli.channel\` serves the full spec. It prints a link and a code and does not need my password \u2014 it must never ask for it. Give me the link straight away and wait for me to approve it in my browser.
5396
5478
  2. Once I have approved, run \`haven agents connect --name <a name you choose> --budget <amount> --token USDC --period <minutes>\` with the budget I tell you. If I have not given you one, ask me before running it. Add \`--run\` to complete the connection in the same step.
5397
5479
  3. ${AGENT_APPROVAL_RELAY_JSON_SENTENCE}
5398
- 4. Once I have approved the budget, verify with the \`haven_get_agent\` tool: \`ready\` means I have given you the authority to pay \u2014 it does not confirm the account holds funds, so if its allowances rows carry \`funds_cover_remaining: false\`, tell me and still try the payment; \`needs_approval\` means my approval has not landed yet.
5480
+ 4. Once I have approved the budget, verify with the \`haven_get_agent\` tool: \`ready\` means I have given you the authority to pay \u2014 it does not confirm the account holds funds, so if its allowances rows carry \`funds_cover_remaining: false\`, tell me and still try the payment; \`needs_approval\` with no allowances rows and \`budgetEndedAt\` set means my budget ended on that date \u2014 tell me, and I will set a new one; with no rows and no \`budgetEndedAt\`, my approval has not landed yet; with rows, the budget is spent \u2014 it refills at \`budgetRefillsAt\` (\`null\`: it ends first), so tell me, and I can raise or renew it.
5399
5481
 
5400
5482
  Two things only I can do: approving that budget with my passkey, and funding the account with USDC \u2014 no ETH, Haven sponsors the gas. Tell me if either is missing rather than working around it.
5401
5483
 
@@ -5515,8 +5597,10 @@ Do not guess the wallet address, network, or budget.
5515
5597
 
5516
5598
  For instant orientation at the start of a session, read the non-secret
5517
5599
  \`agent.json\` the connector wrote to your Haven credential directory (typically
5518
- \`~/.haven/agents/<agent-id>/agent.json\` \u2014 if you don't know the agent id, list
5519
- \`~/.haven/agents/\` to find the folder). It
5600
+ \`~/.haven/agents/<slug>/agent.json\`, where \`<slug>\` is the wiring name of the
5601
+ \`haven-<slug>\` pair \u2014 only a \`--bare\` run, or a \`--replace\` without \`--name\`,
5602
+ names the directory by the agent id instead; if you don't know the folder, list
5603
+ \`~/.haven/agents/\` to find it). It
5520
5604
  holds your agent id, Haven wallet address, network, and *configured* per-token
5521
5605
  budget, and contains no keys \u2014 the fastest way to answer "who am I and what may
5522
5606
  I spend" with no round trip. If that file is absent (some setups don't write
@@ -5537,7 +5621,13 @@ spending:
5537
5621
  the user, not a refusal \u2014 a budget above the balance is a normal setup, so
5538
5622
  still try the payment; \`null\` means the coverage read failed or the remaining
5539
5623
  figure was not read live; the key is absent when the remaining is 0. Rows for
5540
- one token are compared alone.
5624
+ one token are compared alone. \`needs_approval\` with no \`allowances[]\`
5625
+ rows and \`budgetEndedAt\` set means the budget ENDED on that date \u2014 waiting
5626
+ will not help, ask the owner to set a new one; with no rows and no
5627
+ \`budgetEndedAt\`, nobody approved a budget yet; with rows (all at remaining 0) the
5628
+ budget is spent for this period, and \`budgetRefillsAt\` says when it refills
5629
+ (\`null\`: it ends before it refills) \u2014 tell the user, then wait or ask them
5630
+ to raise it.
5541
5631
  - \`mcp__haven__haven_get_allowances\` \u2014 detailed per-token breakdown
5542
5632
  (configured, spent, reset window) when you need more than the summary.
5543
5633
  - \`mcp__haven__haven_check_funds\` \u2014 whether the account actually HOLDS at
@@ -5751,8 +5841,10 @@ for example, an invoice carrying an \`x402_payment_url\` to POST with its
5751
5841
  report it, the merchant's settlement evidence. (A deposit transfer is bound
5752
5842
  to its payee and amount on-chain too; the difference is the record, not
5753
5843
  the binding.)
5754
- - **If the quote says stop, stop.** The quote checks the agent's remaining
5755
- budget at quote time, and it can check a price you state (\`expected_amount\`).
5844
+ - **If the quote says stop, stop.** The hosted quote checks the agent's
5845
+ remaining budget at quote time, and it can check a price you state
5846
+ (\`expected_amount\`). On the local runtime (\`@haven_ai/mcp\`) the quote
5847
+ checks only a price you state; the budget is checked when you pay.
5756
5848
  When it returns \`safe_to_continue: false\` \u2014 the amount exceeds the budget, or
5757
5849
  the price does not match what you expected \u2014 it prefills no cap and names no
5758
5850
  next tool: stop and tell the user. If the x402 challenge itself is wrong (a
@@ -5897,8 +5989,8 @@ only part of it was usable), and an optional \`note\` (max 2000 characters)
5897
5989
  saying what was wrong. It is evidence only: it moves no money, never changes
5898
5990
  \`settled\` or any money field, and works only on your own settled
5899
5991
  payments \u2014 another agent's payment is refused. A re-report replaces your
5900
- earlier verdict; the receipt then carries it beside the payment, so the
5901
- owner does not read a junk delivery as a success.
5992
+ earlier verdict; the receipt then carries it beside the payment, so a junk
5993
+ delivery does not read as a success.
5902
5994
  ### Relay a delivered code or credential to the owner \u2014 immediately, verbatim
5903
5995
 
5904
5996
  If a merchant response carries what the user PAID FOR \u2014 a redemption code, gift