@haven_ai/sdk 0.5.0-alpha.1 → 0.7.0-alpha.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +24 -8
- package/dist/{base64-BUMXmruY.d.cts → base64-_seD1uw8.d.cts} +425 -18
- package/dist/{base64-BUMXmruY.d.ts → base64-_seD1uw8.d.ts} +425 -18
- package/dist/{chunk-JKUIE45B.cjs → chunk-3SVPI4LS.cjs} +130 -9
- package/dist/chunk-3SVPI4LS.cjs.map +1 -0
- package/dist/{chunk-QSSVOPWA.cjs → chunk-J6DQEBK2.cjs} +340 -17
- package/dist/chunk-J6DQEBK2.cjs.map +1 -0
- package/dist/{chunk-GLNG2GF7.js → chunk-NNIT7WAI.js} +127 -10
- package/dist/chunk-NNIT7WAI.js.map +1 -0
- package/dist/{chunk-KQ7JXKTV.js → chunk-R3RZJJWL.js} +321 -6
- package/dist/chunk-R3RZJJWL.js.map +1 -0
- package/dist/edge.cjs +296 -57
- package/dist/edge.cjs.map +1 -1
- package/dist/edge.d.cts +122 -5
- package/dist/edge.d.ts +122 -5
- package/dist/edge.js +204 -4
- package/dist/edge.js.map +1 -1
- package/dist/index.cjs +772 -350
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +162 -28
- package/dist/index.d.ts +162 -28
- package/dist/index.js +461 -80
- package/dist/index.js.map +1 -1
- package/dist/test-support.cjs +18 -18
- package/dist/test-support.js +1 -1
- package/package.json +1 -1
- package/dist/chunk-GLNG2GF7.js.map +0 -1
- package/dist/chunk-JKUIE45B.cjs.map +0 -1
- package/dist/chunk-KQ7JXKTV.js.map +0 -1
- package/dist/chunk-QSSVOPWA.cjs.map +0 -1
package/dist/index.js
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
|
-
import { selectStandardPaymentOption, buildX402IdempotencyKey, x402AuthorizationAmount, assertBoundDirectPaymentUserOp, HavenTypedDataRefusedError, assertOwnSettlementChild, parsePaymentRequiredResponse, x402PaymentHeaderNamesSent, SDK_CLIENT_IDENTITY, readClientUpdate, HAVEN_CLIENT_HEADER, resolveTokenFromAddress, isSweepableChain, sweepUsdcAddress, x402PaymentHeaderNamesFor, X402_PAYMENT_HEADER_NAMES, toStandardPaymentRequirements, decodeBase64Json, encodeBase64Json, x402V2PaymentEnvelope, selectX402SettlementScheme, selectErc7710PaymentOption, isErc7710Option, x402AssetTransferMethod, normalizePaymentRequired } from './chunk-
|
|
2
|
-
export { DEFAULT_NEXT_TOOL_BY_ACTION, ERC7710_ASSET_TRANSFER_METHOD, HAVEN_CLIENT_HEADER, HAVEN_MINIMUM_NODE_VERSION, HavenTypedDataRefusedError, 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_REQUIRED_HEADER_NAME, X402_PAYMENT_RESPONSE_HEADER_NAME, X402_SETTLEMENT_FORWARD_MARGIN_SECONDS, assertBoundDirectPaymentUserOp, buildSweepAuthorizationMessage, buildSweepTypedData, buildX402ExpectedMessage, compareNodeVersions, createNextStepBuilder, decodeBase64Json, decodeBase64Utf8, defaultNextToolFor, encodeBase64Json, encodeBase64Utf8, encodePaymentProof, havenClientIdentity, isErc7710Option, isSupportedNodeVersion, isSweepableChain, normalizePaymentRequired, parseNextTool, parsePaymentRequired, parsePaymentRequiredResponse, readClientUpdate, renderNextTool, resolveTokenFromAddress, selectErc7710PaymentOption, selectPaymentOption, selectStandardPaymentOption, selectX402SettlementScheme, sweepUsdcAddress, sweepUsdcDomain, toStandardPaymentRequirements, unsupportedNodeVersionMessage, validateStandardX402PaymentHeader, x402AssetTransferMethod, x402AuthorizationAmount, x402FacilitatorAddresses, x402V2PaymentEnvelope } from './chunk-
|
|
3
|
-
import { HavenSigningError, HavenError, DEFAULT_CONFIRMATION_TIMEOUT_MS, HavenApiError, assertUserOpTypedDataBinding, AgentPaymentRail, AgentPaymentPhase, HavenTimeoutError, X402UnexpectedStatusError, X402AlreadySettledError, AgentPaymentWarningCode, MerchantTimeoutError, AgentPaymentNextAction, HavenPaymentStateError, HavenZeroSettlementHashError, chainIdForNetwork } from './chunk-
|
|
4
|
-
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, X402AlreadySettledError, X402UnexpectedStatusError, assertUserOpTypedDataBinding, connectorRerunCommand, connectorSpec, deriveDelegateAccountAddress, isConnectorChannel, isPackedUserOperationTypedData, packedUserOperationHash, resolveConnectorChannel, signerUpdateFallback, verifySettlementChild } from './chunk-
|
|
5
|
-
import { hashTypedData } from 'viem';
|
|
1
|
+
import { selectStandardPaymentOption, buildX402IdempotencyKey, x402AuthorizationAmount, assertBoundDirectPaymentUserOp, assertFundingLegPaysDelegate, HavenTypedDataRefusedError, assertOwnSettlementChild, parsePaymentRequiredResponse, x402PaymentHeaderNamesSent, SDK_CLIENT_IDENTITY, readClientUpdate, HAVEN_CLIENT_HEADER, resolveTokenFromAddress, isSweepableChain, sweepUsdcAddress, x402PaymentHeaderNamesFor, X402_PAYMENT_HEADER_NAMES, toStandardPaymentRequirements, decodeBase64Json, encodeBase64Json, x402V2PaymentEnvelope, selectX402SettlementScheme, selectErc7710PaymentOption, isErc7710Option, x402AssetTransferMethod, normalizePaymentRequired } from './chunk-R3RZJJWL.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_REQUIRED_HEADER_NAME, X402_PAYMENT_RESPONSE_HEADER_NAME, X402_SETTLEMENT_FORWARD_MARGIN_SECONDS, assertBoundDirectPaymentUserOp, assertOwnTaskBudgetCloseUserOp, assertOwnTaskChild, buildSweepAuthorizationMessage, buildSweepTypedData, buildX402ExpectedMessage, compareNodeVersions, createNextStepBuilder, decodeBase64Json, decodeBase64Utf8, defaultNextToolFor, 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, x402V2PaymentEnvelope } from './chunk-R3RZJJWL.js';
|
|
3
|
+
import { HavenSigningError, HavenError, DEFAULT_CONFIRMATION_TIMEOUT_MS, HavenApiError, assertUserOpTypedDataBinding, AgentPaymentRail, AgentPaymentPhase, HavenTimeoutError, X402TaskBudgetMismatchError, X402UnexpectedStatusError, X402AlreadySettledError, AgentPaymentWarningCode, MerchantTimeoutError, AgentPaymentNextAction, HavenPaymentStateError, HavenZeroSettlementHashError, X402Erc7710AlreadySettledError, DELEGATION_MANAGER, chainIdForNetwork } from './chunk-NNIT7WAI.js';
|
|
4
|
+
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-NNIT7WAI.js';
|
|
5
|
+
import { keccak256, toBytes, concatHex, hashTypedData, encodeAbiParameters } from 'viem';
|
|
6
6
|
import { ethers } from 'ethers';
|
|
7
7
|
import { AsyncLocalStorage } from 'async_hooks';
|
|
8
8
|
import { exact } from 'x402/schemes';
|
|
@@ -161,7 +161,18 @@ function mapParties(raw) {
|
|
|
161
161
|
treasuryAccount: raw.treasury_account,
|
|
162
162
|
delegate: raw.delegate,
|
|
163
163
|
delegateAccount: raw.delegate_account,
|
|
164
|
-
merchant: raw.merchant
|
|
164
|
+
merchant: raw.merchant,
|
|
165
|
+
// #3332: additive — absent unless the wire shape carries it.
|
|
166
|
+
...raw.buyer ? {
|
|
167
|
+
buyer: {
|
|
168
|
+
legalName: raw.buyer.legal_name,
|
|
169
|
+
country: raw.buyer.country,
|
|
170
|
+
orgNumber: raw.buyer.org_number,
|
|
171
|
+
vatNumber: raw.buyer.vat_number,
|
|
172
|
+
viesStatus: raw.buyer.vies_status,
|
|
173
|
+
viesCheckedAt: raw.buyer.vies_checked_at
|
|
174
|
+
}
|
|
175
|
+
} : {}
|
|
165
176
|
};
|
|
166
177
|
}
|
|
167
178
|
function mapPaymentResult(raw, buildExplorerUrl2) {
|
|
@@ -205,6 +216,9 @@ function mapPaymentStatusResult(raw) {
|
|
|
205
216
|
expiresAt: raw.expires_at,
|
|
206
217
|
chainId: raw.chain_id,
|
|
207
218
|
message: raw.message,
|
|
219
|
+
// #3420: the delivered half of the settle vocabulary, additive — absent
|
|
220
|
+
// from the raw payload (older backend) stays absent here.
|
|
221
|
+
...raw.delivered === true ? { delivered: true } : {},
|
|
208
222
|
fee: raw.fee ? {
|
|
209
223
|
amount: raw.fee.amount,
|
|
210
224
|
token: raw.fee.token,
|
|
@@ -232,17 +246,13 @@ function mapPaymentReceipt(raw) {
|
|
|
232
246
|
id: raw.id,
|
|
233
247
|
paymentId: raw.payment_id,
|
|
234
248
|
source: raw.rail,
|
|
235
|
-
rail: raw.rail,
|
|
236
249
|
paymentProofStatus: raw.proof_status,
|
|
237
|
-
proofStatus: raw.proof_status,
|
|
238
250
|
txHash: raw.tx_hash,
|
|
239
251
|
fundingTxHash: raw.funding_tx_hash ?? null,
|
|
240
252
|
settlementTxHash: raw.settlement_tx_hash ?? null,
|
|
241
253
|
chainId: raw.chain_id,
|
|
242
254
|
x402ResourceUrl: raw.resource_url,
|
|
243
|
-
resourceUrl: raw.resource_url,
|
|
244
255
|
x402MerchantAddress: raw.merchant_address,
|
|
245
|
-
merchantAddress: raw.merchant_address,
|
|
246
256
|
payerAddress: raw.payer_address,
|
|
247
257
|
parties: mapParties(raw.parties),
|
|
248
258
|
settlementAddress: raw.settlement_address,
|
|
@@ -842,22 +852,93 @@ function selectJsonRpcResult(messages) {
|
|
|
842
852
|
return messages.find((message) => "result" in message || "error" in message) ?? messages.at(-1);
|
|
843
853
|
}
|
|
844
854
|
var RECEIPT_VERSION = "haven-receipt-1";
|
|
855
|
+
var DELEGATION_MANAGER_CHAIN_IDS = /* @__PURE__ */ new Set([8453, 84532]);
|
|
856
|
+
var EIP712_DOMAIN_TYPEHASH = keccak256(
|
|
857
|
+
toBytes("EIP712Domain(string name,string version,uint256 chainId,address verifyingContract)")
|
|
858
|
+
);
|
|
859
|
+
function delegationDomainSeparator(chainId) {
|
|
860
|
+
return keccak256(
|
|
861
|
+
encodeAbiParameters(
|
|
862
|
+
[{ type: "bytes32" }, { type: "bytes32" }, { type: "bytes32" }, { type: "uint256" }, { type: "address" }],
|
|
863
|
+
[
|
|
864
|
+
EIP712_DOMAIN_TYPEHASH,
|
|
865
|
+
keccak256(toBytes("DelegationManager")),
|
|
866
|
+
keccak256(toBytes("1")),
|
|
867
|
+
BigInt(chainId),
|
|
868
|
+
DELEGATION_MANAGER
|
|
869
|
+
]
|
|
870
|
+
)
|
|
871
|
+
);
|
|
872
|
+
}
|
|
873
|
+
function delegationDigest(structHash, chainId) {
|
|
874
|
+
return keccak256(concatHex(["0x1901", delegationDomainSeparator(chainId), structHash]));
|
|
875
|
+
}
|
|
845
876
|
function defaultRecover(hash, signature) {
|
|
846
877
|
return ethers.recoverAddress(hash, signature);
|
|
847
878
|
}
|
|
848
|
-
|
|
849
|
-
|
|
850
|
-
if (!signature) return { verified: false, reason: "missing_signature" };
|
|
879
|
+
var NOT_A_SIGNED_RECEIPT = { verified: false, reason: "not_a_signed_receipt" };
|
|
880
|
+
function match(delegate, hash, signature, recover) {
|
|
851
881
|
let recovered;
|
|
852
882
|
try {
|
|
853
|
-
recovered = recover(
|
|
883
|
+
recovered = recover(hash, signature);
|
|
854
884
|
} catch {
|
|
855
|
-
return {
|
|
885
|
+
return { ok: false, reason: "bad_signature" };
|
|
856
886
|
}
|
|
857
887
|
if (recovered.toLowerCase() !== delegate.toLowerCase()) {
|
|
858
|
-
return {
|
|
888
|
+
return { ok: false, reason: "signer_mismatch", recoveredSigner: recovered };
|
|
889
|
+
}
|
|
890
|
+
return { ok: true, recoveredSigner: recovered };
|
|
891
|
+
}
|
|
892
|
+
function finish(result, verifiedOver) {
|
|
893
|
+
if (result.ok) {
|
|
894
|
+
return { verified: true, recoveredSigner: result.recoveredSigner, verifiedOver };
|
|
859
895
|
}
|
|
860
|
-
return { verified:
|
|
896
|
+
return result.recoveredSigner !== void 0 ? { verified: false, reason: result.reason, recoveredSigner: result.recoveredSigner } : { verified: false, reason: result.reason };
|
|
897
|
+
}
|
|
898
|
+
function verifyPaymentReceipt(receipt, recover = defaultRecover) {
|
|
899
|
+
if (typeof receipt !== "object" || receipt === null) return NOT_A_SIGNED_RECEIPT;
|
|
900
|
+
const source = receipt;
|
|
901
|
+
if (typeof source.authorization !== "object" || source.authorization === null) {
|
|
902
|
+
return NOT_A_SIGNED_RECEIPT;
|
|
903
|
+
}
|
|
904
|
+
const auth = source.authorization;
|
|
905
|
+
const { delegate, signHash: signHash2, signature, signatureScheme } = auth;
|
|
906
|
+
if (typeof delegate !== "string" || delegate === "" || typeof signHash2 !== "string" || signHash2 === "") {
|
|
907
|
+
return NOT_A_SIGNED_RECEIPT;
|
|
908
|
+
}
|
|
909
|
+
if (typeof signature !== "string" || signature === "") {
|
|
910
|
+
return { verified: false, reason: "missing_signature" };
|
|
911
|
+
}
|
|
912
|
+
const chainId = typeof source.onChain?.chainId === "number" ? source.onChain.chainId : void 0;
|
|
913
|
+
const digestVerifiable = chainId !== void 0 && DELEGATION_MANAGER_CHAIN_IDS.has(chainId);
|
|
914
|
+
if (signatureScheme === "eip712_userop") {
|
|
915
|
+
return { verified: false, reason: "not_verifiable_offline" };
|
|
916
|
+
}
|
|
917
|
+
if (signatureScheme === "eip712_delegation") {
|
|
918
|
+
if (!digestVerifiable) {
|
|
919
|
+
return { verified: false, reason: "not_verifiable_offline" };
|
|
920
|
+
}
|
|
921
|
+
let digest;
|
|
922
|
+
try {
|
|
923
|
+
digest = delegationDigest(signHash2, chainId);
|
|
924
|
+
} catch {
|
|
925
|
+
return { verified: false, reason: "bad_signature" };
|
|
926
|
+
}
|
|
927
|
+
return finish(match(delegate, digest, signature, recover), "delegation_digest");
|
|
928
|
+
}
|
|
929
|
+
const raw = match(delegate, signHash2, signature, recover);
|
|
930
|
+
if (raw.ok) return finish(raw, "sign_hash");
|
|
931
|
+
if (digestVerifiable) {
|
|
932
|
+
let digest;
|
|
933
|
+
try {
|
|
934
|
+
digest = delegationDigest(signHash2, chainId);
|
|
935
|
+
} catch {
|
|
936
|
+
return finish(raw, "sign_hash");
|
|
937
|
+
}
|
|
938
|
+
const delegated = match(delegate, digest, signature, recover);
|
|
939
|
+
if (delegated.ok) return finish(delegated, "delegation_digest");
|
|
940
|
+
}
|
|
941
|
+
return finish(raw, "sign_hash");
|
|
861
942
|
}
|
|
862
943
|
|
|
863
944
|
// src/account-reads.ts
|
|
@@ -883,6 +964,54 @@ function formatRemainingDisplay(tokenAddress, tokenSymbol, remainingAtomic) {
|
|
|
883
964
|
const token = resolveTokenFromAddress(tokenAddress);
|
|
884
965
|
return token ? `${formatAtomicAmount(safeBigInt(remainingAtomic), token.decimals)} ${tokenSymbol}` : `${remainingAtomic} ${tokenSymbol} (atomic; unknown decimals)`;
|
|
885
966
|
}
|
|
967
|
+
function formatTokenAmount(atomic, decimals) {
|
|
968
|
+
const value = safeBigInt(atomic);
|
|
969
|
+
if (value < 0n) return "0";
|
|
970
|
+
const digits = value.toString().padStart(decimals + 1, "0");
|
|
971
|
+
const intPart = digits.slice(0, digits.length - decimals) || "0";
|
|
972
|
+
const fracPart = digits.slice(digits.length - decimals).replace(/0+$/, "");
|
|
973
|
+
return fracPart ? `${intPart}.${fracPart}` : intPart;
|
|
974
|
+
}
|
|
975
|
+
function mapTaskBudget(raw) {
|
|
976
|
+
return {
|
|
977
|
+
id: raw.id,
|
|
978
|
+
agentId: raw.agent_id,
|
|
979
|
+
chainId: raw.chain_id,
|
|
980
|
+
tokenAddress: raw.token_address,
|
|
981
|
+
recipientAddress: raw.recipient_address,
|
|
982
|
+
parentDelegationHash: raw.parent_delegation_hash,
|
|
983
|
+
delegationHash: raw.delegation_hash,
|
|
984
|
+
label: raw.label,
|
|
985
|
+
maxAtomic: raw.max_atomic,
|
|
986
|
+
status: raw.status,
|
|
987
|
+
expiresAt: raw.expires_at,
|
|
988
|
+
isExpired: raw.is_expired,
|
|
989
|
+
createdAt: raw.created_at,
|
|
990
|
+
openedAt: raw.opened_at,
|
|
991
|
+
closedAt: raw.closed_at,
|
|
992
|
+
closeTxHash: raw.close_tx_hash
|
|
993
|
+
};
|
|
994
|
+
}
|
|
995
|
+
function summarizeTaskBudget(taskBudget) {
|
|
996
|
+
const token = resolveTokenFromAddress(taskBudget.tokenAddress);
|
|
997
|
+
const maxDisplay = token ? `${formatAtomicAmount(safeBigInt(taskBudget.maxAtomic), token.decimals)} ${token.symbol}` : `${taskBudget.maxAtomic} (atomic; unknown decimals)`;
|
|
998
|
+
return {
|
|
999
|
+
id: taskBudget.id,
|
|
1000
|
+
label: taskBudget.label,
|
|
1001
|
+
tokenAddress: taskBudget.tokenAddress,
|
|
1002
|
+
maxAtomic: taskBudget.maxAtomic,
|
|
1003
|
+
maxDisplay,
|
|
1004
|
+
recipientAddress: taskBudget.recipientAddress,
|
|
1005
|
+
expiresAt: taskBudget.expiresAt
|
|
1006
|
+
};
|
|
1007
|
+
}
|
|
1008
|
+
function compactReceipt(receipt) {
|
|
1009
|
+
const row = { ...receipt };
|
|
1010
|
+
delete row.challengePayload;
|
|
1011
|
+
delete row.selectedPayment;
|
|
1012
|
+
delete row.protocolReceiptPayload;
|
|
1013
|
+
return row;
|
|
1014
|
+
}
|
|
886
1015
|
var AccountReads = class {
|
|
887
1016
|
transport;
|
|
888
1017
|
getPaymentStatus;
|
|
@@ -902,7 +1031,11 @@ var AccountReads = class {
|
|
|
902
1031
|
return request;
|
|
903
1032
|
}
|
|
904
1033
|
async getAgentSummary() {
|
|
905
|
-
const [agent, allowanceSummary] = await Promise.all([
|
|
1034
|
+
const [agent, allowanceSummary, taskBudgets] = await Promise.all([
|
|
1035
|
+
this.getAgent(),
|
|
1036
|
+
this.getAllowances(),
|
|
1037
|
+
this.listOpenTaskBudgetsSummary()
|
|
1038
|
+
]);
|
|
906
1039
|
const allowances = allowanceSummary.allowances.map((allowance) => {
|
|
907
1040
|
return {
|
|
908
1041
|
id: allowance.id,
|
|
@@ -916,7 +1049,30 @@ var AccountReads = class {
|
|
|
916
1049
|
};
|
|
917
1050
|
});
|
|
918
1051
|
const readiness = deriveReadiness(agent.status, allowances);
|
|
919
|
-
return { ...agent, readiness, spend_authority_readiness: readiness, allowances };
|
|
1052
|
+
return { ...agent, readiness, spend_authority_readiness: readiness, allowances, taskBudgets };
|
|
1053
|
+
}
|
|
1054
|
+
/**
|
|
1055
|
+
* #3329: `GET /task-budgets?status=open`, mapped to the condensed summary
|
|
1056
|
+
* row `getAgentSummary()` carries. Fails SOFT per the #3093 rule (default
|
|
1057
|
+
* every array wire key to `[]` so an absent/malformed key degrades instead
|
|
1058
|
+
* of taking the route down): a 404 (a backend that predates #3329's
|
|
1059
|
+
* route), ANY other transport error, an undefined/null body, or a missing
|
|
1060
|
+
* or non-array `task_budgets` key all return `[]` rather than throwing —
|
|
1061
|
+
* `getAgentSummary`'s identity + readiness + allowances fields are what
|
|
1062
|
+
* callers depend on, and this read must never be why the whole summary
|
|
1063
|
+
* fails.
|
|
1064
|
+
*/
|
|
1065
|
+
async listOpenTaskBudgetsSummary() {
|
|
1066
|
+
try {
|
|
1067
|
+
const raw = await this.transport.get(
|
|
1068
|
+
"/task-budgets?status=open"
|
|
1069
|
+
);
|
|
1070
|
+
const rows = raw?.task_budgets;
|
|
1071
|
+
if (!Array.isArray(rows)) return [];
|
|
1072
|
+
return rows.map((row) => summarizeTaskBudget(mapTaskBudget(row)));
|
|
1073
|
+
} catch {
|
|
1074
|
+
return [];
|
|
1075
|
+
}
|
|
920
1076
|
}
|
|
921
1077
|
async getAllowances() {
|
|
922
1078
|
const raw = await this.transport.get("/machine-payments/allowances");
|
|
@@ -1006,22 +1162,22 @@ var AccountReads = class {
|
|
|
1006
1162
|
try {
|
|
1007
1163
|
const tokenAddress = payment.asset ?? payment.x402?.asset ?? null;
|
|
1008
1164
|
if (!tokenAddress) return unavailable("the settled payment does not carry a resolvable token address", payment);
|
|
1009
|
-
const
|
|
1165
|
+
const match2 = allowanceResult.value.allowances.find(
|
|
1010
1166
|
(allowance) => allowance.tokenAddress.toLowerCase() === tokenAddress.toLowerCase()
|
|
1011
1167
|
);
|
|
1012
|
-
if (!
|
|
1013
|
-
const token = resolveTokenFromAddress(
|
|
1014
|
-
const remainingDisplay = token ? `${formatAtomicAmount(safeBigInt(
|
|
1168
|
+
if (!match2) return unavailable("no allowance/budget row matches the settled token", payment);
|
|
1169
|
+
const token = resolveTokenFromAddress(match2.tokenAddress);
|
|
1170
|
+
const remainingDisplay = token ? `${formatAtomicAmount(safeBigInt(match2.onchain.remaining), token.decimals)} ${match2.tokenSymbol}` : void 0;
|
|
1015
1171
|
const rail = agentResult.value.executionRail;
|
|
1016
1172
|
return {
|
|
1017
1173
|
payment,
|
|
1018
1174
|
allowance: {
|
|
1019
1175
|
rail,
|
|
1020
|
-
remaining_atomic:
|
|
1176
|
+
remaining_atomic: match2.onchain.remaining,
|
|
1021
1177
|
...remainingDisplay ? { remaining_display: remainingDisplay } : {},
|
|
1022
|
-
token_symbol:
|
|
1023
|
-
token_address:
|
|
1024
|
-
reset_period:
|
|
1178
|
+
token_symbol: match2.tokenSymbol,
|
|
1179
|
+
token_address: match2.tokenAddress,
|
|
1180
|
+
reset_period: match2.resetPeriodMin,
|
|
1025
1181
|
source: rail === "delegation" ? "active_delegations" : "allowance_module"
|
|
1026
1182
|
},
|
|
1027
1183
|
warnings: []
|
|
@@ -1041,8 +1197,13 @@ var AccountReads = class {
|
|
|
1041
1197
|
if (options.cursor) params.set("cursor", options.cursor);
|
|
1042
1198
|
const query = params.size > 0 ? `?${params.toString()}` : "";
|
|
1043
1199
|
const raw = await this.transport.get(`/machine-payments/receipts${query}`);
|
|
1200
|
+
const receipts = raw.receipts.map(mapPaymentReceipt);
|
|
1044
1201
|
return {
|
|
1045
|
-
|
|
1202
|
+
// #3423 (owner decision, opt-in): `compact` drops the three bulky
|
|
1203
|
+
// payload echoes (the merchant's full 402 challenge, the selected
|
|
1204
|
+
// option, and the merchant's PAYMENT-RESPONSE) from each row. The keys
|
|
1205
|
+
// are absent, not null, and the default shape is unchanged.
|
|
1206
|
+
receipts: options.compact ? receipts.map(compactReceipt) : receipts,
|
|
1046
1207
|
total: typeof raw.total === "number" ? raw.total : null,
|
|
1047
1208
|
hasMore: typeof raw.has_more === "boolean" ? raw.has_more : null,
|
|
1048
1209
|
nextCursor: typeof raw.next_cursor === "string" ? raw.next_cursor : null
|
|
@@ -1408,6 +1569,10 @@ function readX402ReceiptPayer(raw) {
|
|
|
1408
1569
|
}
|
|
1409
1570
|
|
|
1410
1571
|
// src/x402-funding-leg.ts
|
|
1572
|
+
function sameX402TaskBudget(a, b) {
|
|
1573
|
+
if (a === void 0 || b === void 0) return a === b;
|
|
1574
|
+
return a.toLowerCase() === b.toLowerCase();
|
|
1575
|
+
}
|
|
1411
1576
|
var X402FundingLeg = class {
|
|
1412
1577
|
delegateKey;
|
|
1413
1578
|
delegateAddress;
|
|
@@ -1433,20 +1598,35 @@ var X402FundingLeg = class {
|
|
|
1433
1598
|
this.assertSignableAuthorizationState = options.assertSignableAuthorizationState;
|
|
1434
1599
|
}
|
|
1435
1600
|
// ── Receipt cache ────────────────────────────────────────────────
|
|
1436
|
-
/**
|
|
1437
|
-
|
|
1601
|
+
/**
|
|
1602
|
+
* #3392: each cached receipt records the authorizing budget id(s) it was
|
|
1603
|
+
* created under (absent when none) — `taskBudgetId` since #3329, and since
|
|
1604
|
+
* #3330 `subBudgetId` (agent B's grant). A call naming a DIFFERENT
|
|
1605
|
+
* combination is refused here — before any network call — instead of
|
|
1606
|
+
* silently returning a receipt paid under another budget. Absent-vs-absent
|
|
1607
|
+
* and equal ids (case-insensitive) hit as before.
|
|
1608
|
+
*/
|
|
1609
|
+
cachedReceipt(idempotencyKey, taskBudgetId, subBudgetId) {
|
|
1438
1610
|
const cached = this.receiptCache.get(idempotencyKey);
|
|
1439
|
-
if (cached
|
|
1440
|
-
|
|
1611
|
+
if (!cached || cached.expiresAt <= Date.now()) return void 0;
|
|
1612
|
+
if (!sameX402TaskBudget(cached.taskBudgetId, taskBudgetId) || !sameX402TaskBudget(cached.subBudgetId, subBudgetId)) {
|
|
1613
|
+
throw new X402TaskBudgetMismatchError(
|
|
1614
|
+
`The x402 idempotency key '${idempotencyKey}' was already used under a different authorizing budget (task '${cached.taskBudgetId ?? "none"}' vs '${taskBudgetId ?? "none"}', sub '${cached.subBudgetId ?? "none"}' vs '${subBudgetId ?? "none"}'). Pass a new idempotencyKey to pay under this budget.`,
|
|
1615
|
+
idempotencyKey,
|
|
1616
|
+
cached.taskBudgetId,
|
|
1617
|
+
taskBudgetId
|
|
1618
|
+
);
|
|
1619
|
+
}
|
|
1620
|
+
return cached.receipt;
|
|
1441
1621
|
}
|
|
1442
|
-
cacheReceipt(idempotencyKey, paymentHeader, receipt) {
|
|
1622
|
+
cacheReceipt(idempotencyKey, paymentHeader, receipt, taskBudgetId, subBudgetId) {
|
|
1443
1623
|
const expiresAt = getPaymentHeaderValidBefore(paymentHeader);
|
|
1444
1624
|
if (expiresAt > Date.now()) {
|
|
1445
|
-
this.receiptCache.set(idempotencyKey, { expiresAt, receipt });
|
|
1625
|
+
this.receiptCache.set(idempotencyKey, { expiresAt, taskBudgetId, subBudgetId, receipt });
|
|
1446
1626
|
}
|
|
1447
1627
|
}
|
|
1448
1628
|
// ── Authorization ────────────────────────────────────────────────
|
|
1449
|
-
async authorize(paymentRequired, option, idempotencyKey) {
|
|
1629
|
+
async authorize(paymentRequired, option, idempotencyKey, taskBudgetId, subBudgetId) {
|
|
1450
1630
|
const raw = await this.post("/x402", {
|
|
1451
1631
|
url: paymentRequired.resource.url,
|
|
1452
1632
|
payTo: this.delegateAddress,
|
|
@@ -1459,7 +1639,15 @@ var X402FundingLeg = class {
|
|
|
1459
1639
|
// #1360: same explicit funding-leg declaration as createX402Intent —
|
|
1460
1640
|
// this local-key path derives payTo from the key (never stale), but the
|
|
1461
1641
|
// declaration keeps both writers of the 3009 shape loud-by-default.
|
|
1462
|
-
settlementScheme: "eip3009"
|
|
1642
|
+
settlementScheme: "eip3009",
|
|
1643
|
+
// #3329: `/x402` bodies are camelCase (`X402AuthorizeRequest`) — do not
|
|
1644
|
+
// switch this to `task_budget_id`, which is only the `POST /payments` key.
|
|
1645
|
+
...taskBudgetId ? { taskBudgetId } : {},
|
|
1646
|
+
// #3330: same camelCase channel one level deeper — agent B pays through
|
|
1647
|
+
// the sub-budget agent A granted it (`[funding, B grant, A parent-child,
|
|
1648
|
+
// A budget]`). Mutually exclusive with `taskBudgetId` server-side; the
|
|
1649
|
+
// backend refuses a body naming both.
|
|
1650
|
+
...subBudgetId ? { subBudgetId } : {}
|
|
1463
1651
|
});
|
|
1464
1652
|
const state = paymentStateFromRaw("x402 payment", raw);
|
|
1465
1653
|
const executedReplay = raw.success && raw.tx_hash ? "idempotency-collision" : state?.nextAction === AgentPaymentNextAction.RetryOriginalX402Request ? "approval-resume" : null;
|
|
@@ -1482,19 +1670,22 @@ var X402FundingLeg = class {
|
|
|
1482
1670
|
const paymentHeader = await this.createPaymentHeader(paymentRequired, option);
|
|
1483
1671
|
if (raw.success && raw.tx_hash) {
|
|
1484
1672
|
const receipt2 = this.receiptFromAuthorization(paymentRequired, option, paymentHeader, raw);
|
|
1485
|
-
this.cacheReceipt(idempotencyKey, paymentHeader, receipt2);
|
|
1673
|
+
this.cacheReceipt(idempotencyKey, paymentHeader, receipt2, taskBudgetId, subBudgetId);
|
|
1486
1674
|
return receipt2;
|
|
1487
1675
|
}
|
|
1488
1676
|
if (state?.nextAction === AgentPaymentNextAction.RetryOriginalX402Request) {
|
|
1489
1677
|
const receipt2 = this.receiptFromStatus(paymentRequired, option, paymentHeader, state);
|
|
1490
|
-
this.cacheReceipt(idempotencyKey, paymentHeader, receipt2);
|
|
1678
|
+
this.cacheReceipt(idempotencyKey, paymentHeader, receipt2, taskBudgetId, subBudgetId);
|
|
1491
1679
|
return receipt2;
|
|
1492
1680
|
}
|
|
1493
1681
|
this.assertSignableAuthorizationState("x402 payment", raw);
|
|
1494
1682
|
if (!raw.sign_data?.hash) {
|
|
1495
1683
|
throw new HavenApiError("No sign_hash returned from x402/authorize", 500, raw);
|
|
1496
1684
|
}
|
|
1497
|
-
const sig = await this.signForData(raw.sign_data
|
|
1685
|
+
const sig = await this.signForData(raw.sign_data, {
|
|
1686
|
+
asset: option.asset,
|
|
1687
|
+
amount: x402AuthorizationAmount(option)
|
|
1688
|
+
});
|
|
1498
1689
|
const execResult = await this.post(
|
|
1499
1690
|
`/payments/${raw.payment_id}/sign`,
|
|
1500
1691
|
{ signature: sig }
|
|
@@ -1507,7 +1698,7 @@ var X402FundingLeg = class {
|
|
|
1507
1698
|
execResult.chain_id ?? chainIdFromNetwork(option.network)
|
|
1508
1699
|
);
|
|
1509
1700
|
const receipt = this.receiptFromAuthorization(paymentRequired, option, paymentHeader, raw, execResult);
|
|
1510
|
-
this.cacheReceipt(idempotencyKey, paymentHeader, receipt);
|
|
1701
|
+
this.cacheReceipt(idempotencyKey, paymentHeader, receipt, taskBudgetId, subBudgetId);
|
|
1511
1702
|
return receipt;
|
|
1512
1703
|
}
|
|
1513
1704
|
// ── Header minting ───────────────────────────────────────────────
|
|
@@ -1770,11 +1961,30 @@ var X402Erc7710 = class {
|
|
|
1770
1961
|
...selection.facilitatorAddresses ? { facilitatorAddresses: selection.facilitatorAddresses } : {},
|
|
1771
1962
|
// #1307/#1547: persisted so the settle leg can rehydrate the merchant
|
|
1772
1963
|
// call by payment_id on this scheme too, not only on the 3009 bridge.
|
|
1773
|
-
...options.mcpCallContext ? { mcpCallContext: options.mcpCallContext } : {}
|
|
1964
|
+
...options.mcpCallContext ? { mcpCallContext: options.mcpCallContext } : {},
|
|
1965
|
+
// #3329: `/x402` bodies are camelCase (`X402AuthorizeRequest`) — do not
|
|
1966
|
+
// switch this to `task_budget_id`, which is only the `POST /payments` key.
|
|
1967
|
+
...options.taskBudgetId ? { taskBudgetId: options.taskBudgetId } : {},
|
|
1968
|
+
// #3330: same camelCase channel one level deeper; mutually exclusive
|
|
1969
|
+
// with `taskBudgetId` server-side.
|
|
1970
|
+
...options.subBudgetId ? { subBudgetId: options.subBudgetId } : {}
|
|
1774
1971
|
});
|
|
1775
1972
|
if (!raw.payment_id) {
|
|
1776
1973
|
throw new HavenApiError("No payment_id returned from x402/authorize", 500, raw);
|
|
1777
1974
|
}
|
|
1975
|
+
if (raw.status === "confirmed" && raw.tx_hash) {
|
|
1976
|
+
const requestedUrl = options.resourceUrl ?? paymentRequired.resource?.url;
|
|
1977
|
+
const samePurchase = typeof raw.to === "string" && raw.to.toLowerCase() === merchantPayTo.toLowerCase() && raw.resource_url === requestedUrl;
|
|
1978
|
+
if (!samePurchase) {
|
|
1979
|
+
throw new HavenApiError(
|
|
1980
|
+
`Idempotency key already belongs to confirmed payment ${raw.payment_id}, which is not this erc7710 purchase (different payee or resource). Nothing was signed and nothing was charged. Check that payment's status before buying again: a funded but undelivered EIP-3009 payment is recovered, not paid twice. Otherwise use a new idempotency key for this purchase.`,
|
|
1981
|
+
409,
|
|
1982
|
+
raw,
|
|
1983
|
+
raw.payment_id
|
|
1984
|
+
);
|
|
1985
|
+
}
|
|
1986
|
+
throw new X402Erc7710AlreadySettledError(raw.payment_id, raw.tx_hash, raw);
|
|
1987
|
+
}
|
|
1778
1988
|
const signData = raw.sign_data;
|
|
1779
1989
|
if (signData?.signature_scheme !== "eip712_delegation" || !signData.typed_data) {
|
|
1780
1990
|
throw new HavenApiError(
|
|
@@ -1805,10 +2015,20 @@ var X402Erc7710 = class {
|
|
|
1805
2015
|
* hosted boundary) is irrelevant here.
|
|
1806
2016
|
*/
|
|
1807
2017
|
async submit(paymentId, signature) {
|
|
1808
|
-
|
|
1809
|
-
|
|
1810
|
-
{ signature }
|
|
1811
|
-
)
|
|
2018
|
+
let settled;
|
|
2019
|
+
try {
|
|
2020
|
+
settled = await this.post(`/x402/${paymentId}/settle`, { signature });
|
|
2021
|
+
} catch (err) {
|
|
2022
|
+
const body = err instanceof HavenApiError ? err.body : void 0;
|
|
2023
|
+
if (err instanceof HavenApiError && err.statusCode === 409 && body?.code === "payment_already_settled" && typeof body.tx_hash === "string") {
|
|
2024
|
+
throw new X402Erc7710AlreadySettledError(
|
|
2025
|
+
typeof body.payment_id === "string" ? body.payment_id : paymentId,
|
|
2026
|
+
body.tx_hash,
|
|
2027
|
+
body
|
|
2028
|
+
);
|
|
2029
|
+
}
|
|
2030
|
+
throw err;
|
|
2031
|
+
}
|
|
1812
2032
|
if (!settled.payment_header) {
|
|
1813
2033
|
throw new HavenApiError(
|
|
1814
2034
|
"x402 settle returned no payment_header \u2014 the merchant cannot be retried.",
|
|
@@ -2371,6 +2591,15 @@ var HavenClient = class {
|
|
|
2371
2591
|
pollingInterval;
|
|
2372
2592
|
chainRpcs;
|
|
2373
2593
|
inFlightX402 = /* @__PURE__ */ new Map();
|
|
2594
|
+
/**
|
|
2595
|
+
* #3392: the task budget each in-flight x402 entry was created under
|
|
2596
|
+
* (`undefined` = none). A call joining an in-flight entry under a
|
|
2597
|
+
* different budget is refused before any network call — never silently
|
|
2598
|
+
* charged to another budget's payment.
|
|
2599
|
+
*/
|
|
2600
|
+
inFlightX402TaskBudget = /* @__PURE__ */ new Map();
|
|
2601
|
+
/** #3330: the sub-budget each in-flight entry was created under, same pin as `inFlightX402TaskBudget`. */
|
|
2602
|
+
inFlightX402SubBudget = /* @__PURE__ */ new Map();
|
|
2374
2603
|
/**
|
|
2375
2604
|
* The EIP-3009 funding-leg lifecycle (#1618). The facade holds a reference
|
|
2376
2605
|
* and delegates; it does not reimplement any of it.
|
|
@@ -2416,7 +2645,7 @@ var HavenClient = class {
|
|
|
2416
2645
|
x402Wallet: this.x402Wallet,
|
|
2417
2646
|
chainRpcs: this.chainRpcs,
|
|
2418
2647
|
post: (path, body) => this.post(path, body),
|
|
2419
|
-
signForData: (signData) => this.signForData(signData),
|
|
2648
|
+
signForData: (signData, fundingExpectation) => this.signForData(signData, void 0, fundingExpectation),
|
|
2420
2649
|
assertSignableAuthorizationState: (label, raw) => this.throwIfNonSignableAuthorizationState(label, raw)
|
|
2421
2650
|
});
|
|
2422
2651
|
this.merchantCompletion = new MerchantCompletion({
|
|
@@ -2493,7 +2722,11 @@ var HavenClient = class {
|
|
|
2493
2722
|
token: request.token,
|
|
2494
2723
|
amount: request.amount,
|
|
2495
2724
|
to: request.to,
|
|
2496
|
-
...request.idempotencyKey ? { idempotency_key: request.idempotencyKey } : {}
|
|
2725
|
+
...request.idempotencyKey ? { idempotency_key: request.idempotencyKey } : {},
|
|
2726
|
+
...request.taskBudgetId ? { task_budget_id: request.taskBudgetId } : {},
|
|
2727
|
+
// #3330: `POST /payments` is snake_case — agent B pays through the
|
|
2728
|
+
// sub-budget A granted it; the backend refuses a body naming both ids.
|
|
2729
|
+
...request.subBudgetId ? { sub_budget_id: request.subBudgetId } : {}
|
|
2497
2730
|
});
|
|
2498
2731
|
if (raw.status === "pending_approval") {
|
|
2499
2732
|
throwPaymentStateError("Payment", raw);
|
|
@@ -2553,7 +2786,14 @@ var HavenClient = class {
|
|
|
2553
2786
|
// the agent passes only payment_id. Bounded: the backend rejects >64KB,
|
|
2554
2787
|
// so an oversized blob is omitted here (signer falls back to the
|
|
2555
2788
|
// caller-supplied copy) rather than failing the intent.
|
|
2556
|
-
...new TextEncoder().encode(JSON.stringify(paymentRequired)).length <= 65536 ? { paymentRequired } : {}
|
|
2789
|
+
...new TextEncoder().encode(JSON.stringify(paymentRequired)).length <= 65536 ? { paymentRequired } : {},
|
|
2790
|
+
// #3329: `/x402` bodies are camelCase (`X402AuthorizeRequest`), unlike
|
|
2791
|
+
// `POST /payments`'s snake_case `task_budget_id` above — the two
|
|
2792
|
+
// surfaces use different conventions, so this key is NOT `task_budget_id`.
|
|
2793
|
+
...options.taskBudgetId ? { taskBudgetId: options.taskBudgetId } : {},
|
|
2794
|
+
// #3330: same camelCase channel one level deeper (`X402AuthorizeRequest`);
|
|
2795
|
+
// mutually exclusive with `taskBudgetId` server-side.
|
|
2796
|
+
...options.subBudgetId ? { subBudgetId: options.subBudgetId } : {}
|
|
2557
2797
|
});
|
|
2558
2798
|
if (raw.status !== "pending_signature") {
|
|
2559
2799
|
throwPaymentStateError("x402 payment", raw);
|
|
@@ -2625,7 +2865,7 @@ var HavenClient = class {
|
|
|
2625
2865
|
* (#834) — the backend refuses those intents with HTTP 410 before any
|
|
2626
2866
|
* sign_data reaches a client, so encountering it here is a hard error too.
|
|
2627
2867
|
*/
|
|
2628
|
-
async signForData(signData, settlementExpectation) {
|
|
2868
|
+
async signForData(signData, settlementExpectation, fundingExpectation) {
|
|
2629
2869
|
if (!this.delegateKey) {
|
|
2630
2870
|
throw new HavenSigningError(
|
|
2631
2871
|
"Cannot sign without a delegateKey. Pass the private key in HavenClient config, or sign externally."
|
|
@@ -2648,6 +2888,13 @@ var HavenClient = class {
|
|
|
2648
2888
|
signData.typed_data,
|
|
2649
2889
|
this.delegateAddress
|
|
2650
2890
|
);
|
|
2891
|
+
if (fundingExpectation) {
|
|
2892
|
+
assertFundingLegPaysDelegate(signData.typed_data, {
|
|
2893
|
+
delegateAddress: this.delegateAddress,
|
|
2894
|
+
asset: fundingExpectation.asset,
|
|
2895
|
+
amount: fundingExpectation.amount
|
|
2896
|
+
});
|
|
2897
|
+
}
|
|
2651
2898
|
return signUserOpTypedDataForDelegation(this.delegateKey, signData.typed_data);
|
|
2652
2899
|
}
|
|
2653
2900
|
if (scheme === "eip712_delegation") {
|
|
@@ -2926,7 +3173,12 @@ var HavenClient = class {
|
|
|
2926
3173
|
async listReceipts(options = {}) {
|
|
2927
3174
|
return this.accountReads.listReceipts(options);
|
|
2928
3175
|
}
|
|
2929
|
-
/**
|
|
3176
|
+
/**
|
|
3177
|
+
* One page of receipts with `total`, `hasMore` and `nextCursor` (#3128).
|
|
3178
|
+
* `compact: true` (#3423) drops each row's
|
|
3179
|
+
* `challengePayload`, `selectedPayment` and `protocolReceiptPayload`; the
|
|
3180
|
+
* default shape is unchanged.
|
|
3181
|
+
*/
|
|
2930
3182
|
async listReceiptsPage(options = {}) {
|
|
2931
3183
|
return this.accountReads.listReceiptsPage(options);
|
|
2932
3184
|
}
|
|
@@ -2939,6 +3191,96 @@ var HavenClient = class {
|
|
|
2939
3191
|
async getReceipt(paymentId) {
|
|
2940
3192
|
return this.accountReads.getReceipt(paymentId);
|
|
2941
3193
|
}
|
|
3194
|
+
// ── Task Budgets (#3329) ────────────────────────────────────────────
|
|
3195
|
+
/**
|
|
3196
|
+
* Open a task budget: a self-delegated, time-boxed slice of the agent's own
|
|
3197
|
+
* budget delegation. Returns the pending row plus the child delegation's
|
|
3198
|
+
* `sign_data` — sign it (via `signForData` / `haven_sign` with
|
|
3199
|
+
* `task_budget_id`) and pass the signature to `submitTaskBudget` to make it
|
|
3200
|
+
* OPEN. Nothing is on-chain until then.
|
|
3201
|
+
*/
|
|
3202
|
+
async openTaskBudget(request) {
|
|
3203
|
+
const raw = await this.post("/task-budgets", {
|
|
3204
|
+
...request.tokenAddress ? { token_address: request.tokenAddress } : {},
|
|
3205
|
+
max_amount_atomic: request.maxAmountAtomic,
|
|
3206
|
+
ttl_seconds: request.ttlSeconds,
|
|
3207
|
+
...request.recipientAddress ? { recipient_address: request.recipientAddress } : {},
|
|
3208
|
+
...request.label ? { label: request.label } : {}
|
|
3209
|
+
});
|
|
3210
|
+
return {
|
|
3211
|
+
taskBudget: mapTaskBudget(raw.task_budget),
|
|
3212
|
+
signData: raw.sign_data,
|
|
3213
|
+
nextAction: raw.next_action,
|
|
3214
|
+
instructions: raw.instructions
|
|
3215
|
+
};
|
|
3216
|
+
}
|
|
3217
|
+
/** Read one task budget by id. */
|
|
3218
|
+
async getTaskBudget(id) {
|
|
3219
|
+
const raw = await this.get(`/task-budgets/${encodeURIComponent(id)}`);
|
|
3220
|
+
return mapTaskBudget(raw.task_budget);
|
|
3221
|
+
}
|
|
3222
|
+
/** List this agent's task budgets — `status: 'open'` (default; unexpired only) or `'all'`. */
|
|
3223
|
+
async listTaskBudgets(options = {}) {
|
|
3224
|
+
const query = options.status ? `?status=${encodeURIComponent(options.status)}` : "";
|
|
3225
|
+
const raw = await this.get(`/task-budgets${query}`);
|
|
3226
|
+
return raw.task_budgets.map(mapTaskBudget);
|
|
3227
|
+
}
|
|
3228
|
+
/**
|
|
3229
|
+
* The exact signing payload for a pending (purpose `open`) or closing
|
|
3230
|
+
* (purpose `close`) task budget — the `task_budget_id` counterpart of
|
|
3231
|
+
* `getResumeState` / the payment sign-context reads. `haven_sign` with
|
|
3232
|
+
* `task_budget_id` fetches this itself; call it directly only for manual
|
|
3233
|
+
* signing.
|
|
3234
|
+
*/
|
|
3235
|
+
async getTaskBudgetSignContext(id) {
|
|
3236
|
+
const raw = await this.get(`/task-budgets/${encodeURIComponent(id)}/sign-context`);
|
|
3237
|
+
if (raw.purpose === "close") {
|
|
3238
|
+
return {
|
|
3239
|
+
taskBudgetId: raw.task_budget_id,
|
|
3240
|
+
purpose: "close",
|
|
3241
|
+
taskSignContextVersion: raw.task_sign_context_version,
|
|
3242
|
+
typedData: raw.typed_data,
|
|
3243
|
+
userOperation: raw.user_operation,
|
|
3244
|
+
userOpHash: raw.user_op_hash,
|
|
3245
|
+
expected: raw.expected
|
|
3246
|
+
};
|
|
3247
|
+
}
|
|
3248
|
+
return {
|
|
3249
|
+
taskBudgetId: raw.task_budget_id,
|
|
3250
|
+
purpose: "open",
|
|
3251
|
+
taskSignContextVersion: raw.task_sign_context_version,
|
|
3252
|
+
typedData: raw.typed_data,
|
|
3253
|
+
expected: raw.expected
|
|
3254
|
+
};
|
|
3255
|
+
}
|
|
3256
|
+
/** Submit a signature for a pending (open) or closing task budget. */
|
|
3257
|
+
async submitTaskBudget(id, signature) {
|
|
3258
|
+
const raw = await this.post(
|
|
3259
|
+
`/task-budgets/${encodeURIComponent(id)}/submit`,
|
|
3260
|
+
{ signature }
|
|
3261
|
+
);
|
|
3262
|
+
return {
|
|
3263
|
+
taskBudget: mapTaskBudget(raw.task_budget),
|
|
3264
|
+
status: raw.status,
|
|
3265
|
+
...raw.close_tx_hash ? { closeTxHash: raw.close_tx_hash } : {}
|
|
3266
|
+
};
|
|
3267
|
+
}
|
|
3268
|
+
/**
|
|
3269
|
+
* Close a task budget early. A `pending` or already-expired `open` budget
|
|
3270
|
+
* closes immediately (nothing was ever signed or is on-chain). A live
|
|
3271
|
+
* `open` budget returns `sign_data` for an on-chain `disableDelegation`
|
|
3272
|
+
* UserOp — sign it (`haven_sign` with `task_budget_id`) and call
|
|
3273
|
+
* `submitTaskBudget`.
|
|
3274
|
+
*/
|
|
3275
|
+
async closeTaskBudget(id) {
|
|
3276
|
+
const raw = await this.post(`/task-budgets/${encodeURIComponent(id)}/close`, {});
|
|
3277
|
+
return {
|
|
3278
|
+
taskBudget: mapTaskBudget(raw.task_budget),
|
|
3279
|
+
status: raw.status,
|
|
3280
|
+
...raw.sign_data ? { signData: raw.sign_data } : {},
|
|
3281
|
+
...raw.next_action ? { nextAction: raw.next_action } : {}
|
|
3282
|
+
};
|
|
3283
|
+
}
|
|
2942
3284
|
/**
|
|
2943
3285
|
* Rehydrate the x402 resume-state bundle for a payment id (#1328: the MPP
|
|
2944
3286
|
* resume-state variant retired along with the rest of the mpp_demo surface).
|
|
@@ -2987,12 +3329,30 @@ var HavenClient = class {
|
|
|
2987
3329
|
throw noCompatiblePaymentOptionError(paymentRequired.accepts);
|
|
2988
3330
|
}
|
|
2989
3331
|
const idempotencyKey = options.idempotencyKey ?? buildX402IdempotencyKey(paymentRequired, option);
|
|
2990
|
-
const cached = this.fundingLeg.cachedReceipt(idempotencyKey);
|
|
3332
|
+
const cached = this.fundingLeg.cachedReceipt(idempotencyKey, options.taskBudgetId, options.subBudgetId);
|
|
2991
3333
|
if (cached) return cached;
|
|
2992
3334
|
const inFlight = this.inFlightX402.get(idempotencyKey);
|
|
2993
|
-
if (inFlight)
|
|
2994
|
-
|
|
3335
|
+
if (inFlight) {
|
|
3336
|
+
if (!sameX402TaskBudget(this.inFlightX402TaskBudget.get(idempotencyKey), options.taskBudgetId) || !sameX402TaskBudget(this.inFlightX402SubBudget.get(idempotencyKey), options.subBudgetId)) {
|
|
3337
|
+
throw new X402TaskBudgetMismatchError(
|
|
3338
|
+
`The x402 idempotency key '${idempotencyKey}' has an in-flight payment created under a different authorizing budget (task '${this.inFlightX402TaskBudget.get(idempotencyKey) ?? "none"}' vs '${options.taskBudgetId ?? "none"}', sub '${this.inFlightX402SubBudget.get(idempotencyKey) ?? "none"}' vs '${options.subBudgetId ?? "none"}'). Pass a new idempotencyKey to pay under this budget.`,
|
|
3339
|
+
idempotencyKey,
|
|
3340
|
+
this.inFlightX402TaskBudget.get(idempotencyKey),
|
|
3341
|
+
options.taskBudgetId
|
|
3342
|
+
);
|
|
3343
|
+
}
|
|
3344
|
+
return inFlight;
|
|
3345
|
+
}
|
|
3346
|
+
const promise = this.fundingLeg.authorize(
|
|
3347
|
+
paymentRequired,
|
|
3348
|
+
option,
|
|
3349
|
+
idempotencyKey,
|
|
3350
|
+
options.taskBudgetId,
|
|
3351
|
+
options.subBudgetId
|
|
3352
|
+
);
|
|
2995
3353
|
this.inFlightX402.set(idempotencyKey, promise);
|
|
3354
|
+
this.inFlightX402TaskBudget.set(idempotencyKey, options.taskBudgetId);
|
|
3355
|
+
this.inFlightX402SubBudget.set(idempotencyKey, options.subBudgetId);
|
|
2996
3356
|
try {
|
|
2997
3357
|
return await promise;
|
|
2998
3358
|
} catch (err) {
|
|
@@ -3004,6 +3364,8 @@ var HavenClient = class {
|
|
|
3004
3364
|
throw err;
|
|
3005
3365
|
} finally {
|
|
3006
3366
|
this.inFlightX402.delete(idempotencyKey);
|
|
3367
|
+
this.inFlightX402TaskBudget.delete(idempotencyKey);
|
|
3368
|
+
this.inFlightX402SubBudget.delete(idempotencyKey);
|
|
3007
3369
|
}
|
|
3008
3370
|
}
|
|
3009
3371
|
/**
|
|
@@ -3073,7 +3435,7 @@ var HavenClient = class {
|
|
|
3073
3435
|
async payX402Quote(quote, options = {}) {
|
|
3074
3436
|
const idempotencyKey = options.idempotencyKey ?? quote.idempotencyKey;
|
|
3075
3437
|
try {
|
|
3076
|
-
const receipt = await this.authorizeX402(quote.paymentRequired, { idempotencyKey });
|
|
3438
|
+
const receipt = await this.authorizeX402(quote.paymentRequired, { ...options, idempotencyKey });
|
|
3077
3439
|
return this.merchantCompletion.retryRequest(
|
|
3078
3440
|
quote.request.url,
|
|
3079
3441
|
requestInitFromSnapshot(quote.request),
|
|
@@ -3139,7 +3501,7 @@ var HavenClient = class {
|
|
|
3139
3501
|
throw noCompatiblePaymentOptionError(input.paymentRequired.accepts);
|
|
3140
3502
|
}
|
|
3141
3503
|
const idempotencyKey = input.idempotencyKey ?? buildX402IdempotencyKey(input.paymentRequired, option);
|
|
3142
|
-
const cached = this.fundingLeg.cachedReceipt(idempotencyKey);
|
|
3504
|
+
const cached = this.fundingLeg.cachedReceipt(idempotencyKey, input.taskBudgetId);
|
|
3143
3505
|
if (cached) return cached;
|
|
3144
3506
|
const status = await this.getPaymentStatus(input.paymentId);
|
|
3145
3507
|
assertCanResumeX402(status, input.paymentRequired, option);
|
|
@@ -3157,7 +3519,7 @@ var HavenClient = class {
|
|
|
3157
3519
|
}
|
|
3158
3520
|
const paymentHeader = await this.fundingLeg.createPaymentHeader(input.paymentRequired, option);
|
|
3159
3521
|
const receipt = this.fundingLeg.receiptFromStatus(input.paymentRequired, option, paymentHeader, status);
|
|
3160
|
-
this.fundingLeg.cacheReceipt(idempotencyKey, paymentHeader, receipt);
|
|
3522
|
+
this.fundingLeg.cacheReceipt(idempotencyKey, paymentHeader, receipt, input.taskBudgetId);
|
|
3161
3523
|
return receipt;
|
|
3162
3524
|
}
|
|
3163
3525
|
async resumeX402Payment(input) {
|
|
@@ -3584,7 +3946,7 @@ var toolDescriptions = {
|
|
|
3584
3946
|
// deleted `/demo/mpp/*` route. Use the x402 fragments above instead.
|
|
3585
3947
|
getPaymentStatus: {
|
|
3586
3948
|
summary: "Fetch structured Haven payment status for agent recovery.",
|
|
3587
|
-
behavior: "State: phase, nextAction, rail, amount, merchant, resource, idempotency, message; parties: treasury/delegate/delegateAccount/merchant. awaiting_settlement_evidence: poll once, else unverified.",
|
|
3949
|
+
behavior: "State: phase, nextAction, rail, amount, merchant, resource, idempotency, message; delivered when the merchant answered; parties: treasury/delegate/delegateAccount/merchant. awaiting_settlement_evidence: poll once, else unverified. delivered_unverified: terminal, stop.",
|
|
3588
3950
|
nextActionGuidance: ""
|
|
3589
3951
|
},
|
|
3590
3952
|
getResumeState: {
|
|
@@ -3619,9 +3981,9 @@ var toolDescriptions = {
|
|
|
3619
3981
|
nextActionGuidance: ""
|
|
3620
3982
|
},
|
|
3621
3983
|
verifyReceipt: {
|
|
3622
|
-
summary: "Verify a payment receipt offline \u2014 confirm the agent
|
|
3623
|
-
selectionGuidance: "
|
|
3624
|
-
behavior: "
|
|
3984
|
+
summary: "Verify a signed payment receipt offline \u2014 confirm the agent delegate signed its authorisation.",
|
|
3985
|
+
selectionGuidance: "Takes the signed bundle from GET /payments/{id}/receipt, not a haven_list_receipts row \u2014 a row carries no signature and returns not_a_signed_receipt. No network.",
|
|
3986
|
+
behavior: "Never throws. An erc7710 bundle verifies over the delegation digest the delegate signed; a direct or eip3009 bundle returns not_verifiable_offline. verified true means only that the delegate signed the hash named by verifiedOver \u2014 payment facts are Haven-asserted and settlement is not proven; check settlementTxHash on an explorer.",
|
|
3625
3987
|
nextActionGuidance: ""
|
|
3626
3988
|
},
|
|
3627
3989
|
payMcpTool: {
|
|
@@ -3651,7 +4013,7 @@ var toolDescriptions = {
|
|
|
3651
4013
|
send: {
|
|
3652
4014
|
summary: "Send ETH or USDC directly from the agent's Haven wallet to a recipient address.",
|
|
3653
4015
|
selectionGuidance: "Use this for plain transfers \u2014 refunding a user, paying a freelancer, topping up a co-agent's wallet, or moving funds between addresses. Do NOT use for x402 paid endpoints \u2014 use haven_pay_x402 instead. Do NOT use for read-only allowance, budget, or what-can-I-spend questions \u2014 use haven_get_allowances. Do NOT use to check whether funds are held before sending \u2014 use haven_check_funds.",
|
|
3654
|
-
behavior: "Sends the amount by redeeming the agent's on-chain budget delegation, account to recipient with no funding leg. Budget, recipient and expiry are enforced on-chain while the transfer is prepared, so a request outside them is declined before any money moves or any signing \u2014 never queued for a human to approve. The agent's signing key signs the account's typed data
|
|
4016
|
+
behavior: "Sends the amount by redeeming the agent's on-chain budget delegation, account to recipient with no funding leg. Budget, recipient and expiry are enforced on-chain while the transfer is prepared, so a request outside them is declined before any money moves or any signing \u2014 never queued for a human to approve. The agent's signing key signs the account's typed data; a current signer refuses any that does not match the payment's hash (USEROP_BINDING_MISMATCH), an older signer signs it and the account rejects it on-chain. Haven never receives the key.",
|
|
3655
4017
|
nextActionGuidance: "On a decline, report the reason to the user and ask them to grant or raise the budget in Haven \u2014 there is nothing to poll and no approval will arrive. After a successful send, poll haven_get_payment_status until nextAction=none."
|
|
3656
4018
|
},
|
|
3657
4019
|
reportSettlementEvidence: {
|
|
@@ -3908,7 +4270,7 @@ Your user gives you a **budget on their own account** \u2014 not their wallet, a
|
|
|
3908
4270
|
|
|
3909
4271
|
Four of the six steps are your user's \u2014 each needs a human signature or a human decision. The rest is yours. If they already have a funded account, start at step 3.
|
|
3910
4272
|
|
|
3911
|
-
1. **HUMAN \u2014 create the account.** Name, email, password, then a passkey (Face ID / Touch ID) or a wallet. Never offer to enter any of it: you must not have their password, and the passkey is bound to their device. With a terminal,
|
|
4273
|
+
1. **HUMAN \u2014 create the account.** Name, email, password, then a passkey (Face ID / Touch ID) or a wallet. Never offer to enter any of it: you must not have their password, and the passkey is bound to their device. With a terminal, \`<packages.cli.one_liner> login --api <api-url>\` afterwards gets you a scoped session for steps 3-4 (that flag or \`HAVEN_API_URL\`, on the first command \u2014 the session then remembers the backend; **the CLI's built-in default is Haven's hosted production backend**, so on any other deployment an omitted flag connects you somewhere real and wrong rather than failing) \u2014 they approve a code in the browser, you never hold their password. 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. Do not hold the process open while you wait: under \`--json\`, pass \`--no-wait\` to get the link object back at once, then poll it with \`haven login --poll <device_code>\` \u2014 one round per invocation, exit 3 while it is still pending, 0 once approved. It can set up agents and read the account; it cannot sign, approve a budget, move funds, or rotate any agent's keys.
|
|
3912
4274
|
2. **HUMAN \u2014 fund it.** USDC only, no ETH: Haven sponsors the gas. \`/.well-known/haven.json\` names \`chains.default\` as the deployment's expected chain, but after login \`haven wallets funding\` prints the address, the amount and which chain in one place; confirm that chain before you message your user. Without a CLI session, the dashboard's funding card shows the address and amount and its Receive-funds screen names the chain; never assume one: a testnet deployment and production both call themselves Haven. Before you write that message, read the manifest: \`environment\` says whether this deployment is \`production\`, and each \`chains.supported\` entry says whether that chain is a \`testnet\`. Real money is at stake only on a non-testnet chain of a \`production\` deployment \u2014 tell your user which case theirs is.
|
|
3913
4275
|
3. **HUMAN \u2014 create the agent, set its budget**, and paste you the **setup prompt** it hands back. With a CLI session (step 1) you can do this step yourself: \`haven agents connect --name <n> --budget <amount> --token USDC --period <minutes>\` prints the same connector command and approval link; add \`--run\` to do step 4 too.
|
|
3914
4276
|
4. **YOU \u2014 run the connector command** in that prompt (below). It makes your signing key locally, registering only the public address.
|
|
@@ -3984,6 +4346,10 @@ Call \`haven_get_agent\`, one of the Haven MCP tools the connector wires into yo
|
|
|
3984
4346
|
|
|
3985
4347
|
Nothing here needs you to. Steps 1-3 are links: hand your user the full \`<host>/\u2026\` URL and ask them to say when it is done. Step 5 is a link only when your run reported one in \`approval.url\` \u2014 otherwise it is the tab they already have open, as above. Then poll \`haven_get_agent\` until it reads \`ready\`. Do not route around the sign-in wall \u2014 it makes the account theirs, not yours.
|
|
3986
4348
|
|
|
4349
|
+
## If something breaks
|
|
4350
|
+
|
|
4351
|
+
If a Haven result carries \`client_update\`, that client is out of date: run its \`upgrade_command\` as given, then any repair line it prints, then retry. \`required: true\` means payments are refused until you do. What changed: [/releases](/releases).
|
|
4352
|
+
|
|
3987
4353
|
## Vocabulary
|
|
3988
4354
|
|
|
3989
4355
|
| Term | What it is |
|
|
@@ -4002,12 +4368,12 @@ Start by reading {{HAVEN_ORIGIN}}/for-agents.md \u2014 it is written for you and
|
|
|
4002
4368
|
|
|
4003
4369
|
Then:
|
|
4004
4370
|
|
|
4005
|
-
1. Run
|
|
4371
|
+
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.
|
|
4006
4372
|
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.
|
|
4007
4373
|
3. ${AGENT_APPROVAL_RELAY_JSON_SENTENCE}
|
|
4008
4374
|
4. Once I have approved the budget, verify with the \`haven_get_agent\` tool: \`ready\` means you can pay, \`needs_approval\` means my approval has not landed yet.
|
|
4009
4375
|
|
|
4010
|
-
Two things only I can do: approving that budget with my passkey, and funding the account with USDC
|
|
4376
|
+
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.
|
|
4011
4377
|
|
|
4012
4378
|
${AGENT_SECRET_HYGIENE_SENTENCE}`;
|
|
4013
4379
|
|
|
@@ -4151,28 +4517,43 @@ the budget in the Haven dashboard, or wait for the period reset.
|
|
|
4151
4517
|
4. Then FOLLOW THE RESPONSE'S GUIDANCE FIELDS: \`next_action\`, \`next_tool\`,
|
|
4152
4518
|
and \`next_arguments\` name the exact next call \u2014 act on those first; the
|
|
4153
4519
|
prose in this section is fallback and debugging detail. If the catalog
|
|
4154
|
-
|
|
4155
|
-
\`
|
|
4156
|
-
the
|
|
4520
|
+
row is a plain-HTTP x402 paywall (no MCP tool metadata), the response
|
|
4521
|
+
instead names \`mcp__haven__haven_quote_x402\` with the entry's resource
|
|
4522
|
+
URL as \`url\`. If an MCP row is degraded or has no tool name, the response
|
|
4523
|
+
instead names \`mcp__haven__haven_pay_mcp_tool\` (merchant URL, tool name,
|
|
4524
|
+
arguments) as the manual fallback.
|
|
4157
4525
|
|
|
4158
|
-
**Signing:**
|
|
4526
|
+
**Signing:** which signer tool to call depends on the settlement scheme the
|
|
4527
|
+
prepare/pay response already named as \`next_tool\` \u2014 follow that field, never
|
|
4528
|
+
hard-code a choice. **erc7710** (direct settlement \u2014 chosen per merchant when
|
|
4529
|
+
its 402 advertises \`assetTransferMethod: "erc7710"\` AND your account is on
|
|
4530
|
+
the delegation rail; never a blanket default): \`mcp__haven-signer__haven_sign\`
|
|
4531
|
+
with \`payment_id\` ONLY; the signer fetches the settlement child itself.
|
|
4532
|
+
**EIP-3009** (the bridge \u2014 used otherwise, whenever the merchant offers a
|
|
4533
|
+
standard entry): \`mcp__haven-signer__haven_sign_x402\` with \`payment_id\` ONLY \u2014
|
|
4159
4534
|
the local signer fetches the exact signing bytes AND \`payment_required\`
|
|
4160
|
-
itself, so never relay \`typed_data\` or the 402 blob yourself.
|
|
4161
|
-
|
|
4162
|
-
|
|
4163
|
-
|
|
4164
|
-
|
|
4165
|
-
|
|
4535
|
+
itself, so never relay \`typed_data\` or the 402 blob yourself. A merchant
|
|
4536
|
+
that advertises ONLY erc7710 against an account that is NOT on the
|
|
4537
|
+
delegation rail is refused outright \u2014 it does not fall back to EIP-3009. If
|
|
4538
|
+
the signer reports its fetched context carried no \`payment_required\` (older
|
|
4539
|
+
backend), re-call with \`payment_required\` added verbatim. Fallback for an
|
|
4540
|
+
older signer or backend: re-run the quote/prepare tool with the SAME
|
|
4541
|
+
\`idempotency_key\` plus \`include_signing_payload=true\`, then pass
|
|
4542
|
+
\`payload_hash\`, \`x402_expected\` (the nested \`x402.expected\` object), and
|
|
4166
4543
|
\`typed_data\`/\`typed_data_b64\` through unchanged.
|
|
4167
4544
|
|
|
4168
|
-
**Settle:** \`mcp__haven__haven_settle_mcp_tool\` with \`payment_id
|
|
4169
|
-
\`signature
|
|
4545
|
+
**Settle:** \`mcp__haven__haven_settle_mcp_tool\` with \`payment_id\` and
|
|
4546
|
+
\`signature\` always. On **erc7710**, pass no \`payment_header\` \u2014 Haven
|
|
4547
|
+
assembles it at settle, so there is nothing to build locally and no funding
|
|
4548
|
+
transaction to wait for. On **EIP-3009**, also pass \`payment_header\` (from
|
|
4549
|
+
\`haven_sign_x402\`) ONLY. Either way Haven rehydrates the merchant call
|
|
4170
4550
|
context (\`merchant_url\`, \`tool_name\`, \`arguments\`, \`mcp_transport\`)
|
|
4171
4551
|
server-side from \`payment_id\`. Pass those four fields explicitly only as a
|
|
4172
4552
|
version-skew fallback when Haven has no stored context for the id \u2014 both or
|
|
4173
|
-
none together, never just one.
|
|
4174
|
-
funding has not confirmed
|
|
4175
|
-
|
|
4553
|
+
none together, never just one. On **EIP-3009**, \`settled: false\` means
|
|
4554
|
+
funding has not confirmed; on **erc7710** it means the merchant delivered but
|
|
4555
|
+
settlement is not yet verified. Either way, follow the result's guidance
|
|
4556
|
+
fields and do not re-pay.
|
|
4176
4557
|
|
|
4177
4558
|
Step-by-step alternative (also key-safe; for an older signer or backend, or
|
|
4178
4559
|
when you already have a merchant URL and tool name instead of a
|
|
@@ -4362,6 +4743,6 @@ function sameUrl(a, b) {
|
|
|
4362
4743
|
}
|
|
4363
4744
|
}
|
|
4364
4745
|
|
|
4365
|
-
export { AGENT_APPROVAL_RELAY_JSON_SENTENCE, AGENT_APPROVAL_RELAY_PROSE_SENTENCE, AGENT_COMMAND_MODIFICATION_SENTENCE, AGENT_JSON_MODE_SENTENCE, AGENT_LOCAL_KEY_SENTENCE, AGENT_NETWORK_ACCESS_SENTENCE, AGENT_ONBOARDING_PROMPT, AGENT_README_SECTION_MD, AGENT_SECRET_HYGIENE_SENTENCE, AGENT_WIRING_COLLISION_RELAY_SENTENCE, DISCOVERY_MAX_BYTES, HAVEN_AGENT_RUNBOOK_MD, HAVEN_SKILL_BODY_MD, HAVEN_SKILL_MD, HavenClient, HavenInsecureRetryTargetError, INSECURE_RETRY_TARGET_CODE, MERCHANT_DISCOVERY_PATHS, RECEIPT_VERSION, SKILL_FOLDER_NAME, addressFromKey, assertSecureX402RetryTarget, composeDescription, discoverMerchantMcpUrl, havenTools, isSecureX402RetryTarget, isZeroSettlementTxHash, readX402ReceiptPayer, resolveX402RetryTarget, sameUrl, signHash, signUserOpTypedDataForDelegation, toolDescriptions, verifyPaymentReceipt, verifySignature };
|
|
4746
|
+
export { AGENT_APPROVAL_RELAY_JSON_SENTENCE, AGENT_APPROVAL_RELAY_PROSE_SENTENCE, AGENT_COMMAND_MODIFICATION_SENTENCE, AGENT_JSON_MODE_SENTENCE, AGENT_LOCAL_KEY_SENTENCE, AGENT_NETWORK_ACCESS_SENTENCE, AGENT_ONBOARDING_PROMPT, AGENT_README_SECTION_MD, AGENT_SECRET_HYGIENE_SENTENCE, AGENT_WIRING_COLLISION_RELAY_SENTENCE, DISCOVERY_MAX_BYTES, HAVEN_AGENT_RUNBOOK_MD, HAVEN_SKILL_BODY_MD, HAVEN_SKILL_MD, HavenClient, HavenInsecureRetryTargetError, INSECURE_RETRY_TARGET_CODE, MERCHANT_DISCOVERY_PATHS, RECEIPT_VERSION, SKILL_FOLDER_NAME, addressFromKey, assertSecureX402RetryTarget, composeDescription, discoverMerchantMcpUrl, formatTokenAmount, havenTools, isSecureX402RetryTarget, isZeroSettlementTxHash, readX402ReceiptPayer, resolveX402RetryTarget, sameUrl, signHash, signUserOpTypedDataForDelegation, toolDescriptions, verifyPaymentReceipt, verifySignature };
|
|
4366
4747
|
//# sourceMappingURL=index.js.map
|
|
4367
4748
|
//# sourceMappingURL=index.js.map
|