@haven_ai/sdk 0.1.2 → 0.1.3
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 +46 -9
- package/dist/index.cjs +345 -63
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +49 -3
- package/dist/index.d.ts +49 -3
- package/dist/index.js +345 -63
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -356,6 +356,9 @@ function buildExplorerUrl(chainId, txHash) {
|
|
|
356
356
|
const base = CHAIN_EXPLORER_TX[chainId ?? 8453] ?? CHAIN_EXPLORER_TX[8453];
|
|
357
357
|
return `${base}/${txHash}`;
|
|
358
358
|
}
|
|
359
|
+
function explorerUrlOrEmpty(chainId, txHash) {
|
|
360
|
+
return txHash ? buildExplorerUrl(chainId, txHash) : "";
|
|
361
|
+
}
|
|
359
362
|
var DEFAULT_REQUEST_TIMEOUT = 3e4;
|
|
360
363
|
var DEFAULT_CONFIRMATION_TIMEOUT = 9e4;
|
|
361
364
|
var DEFAULT_POLLING_INTERVAL = 3e3;
|
|
@@ -418,6 +421,28 @@ function messageForState(label, status, paymentId, nextAction) {
|
|
|
418
421
|
}
|
|
419
422
|
return `${label} is ${status}; next_action=${nextAction} (payment_id: ${paymentId}).`;
|
|
420
423
|
}
|
|
424
|
+
function sameAddress(a, b) {
|
|
425
|
+
return Boolean(a && b && a.toLowerCase() === b.toLowerCase());
|
|
426
|
+
}
|
|
427
|
+
function decimalFromUsdcAtomic(value) {
|
|
428
|
+
const amount = BigInt(value);
|
|
429
|
+
const whole = amount / 1000000n;
|
|
430
|
+
const fraction = (amount % 1000000n).toString().padStart(6, "0").replace(/0+$/, "");
|
|
431
|
+
return fraction ? `${whole}.${fraction}` : whole.toString();
|
|
432
|
+
}
|
|
433
|
+
function normalizeDecimal(value) {
|
|
434
|
+
if (!value.includes(".")) return value.replace(/^0+(?=\d)/, "") || "0";
|
|
435
|
+
const [whole, fraction = ""] = value.split(".");
|
|
436
|
+
const normalizedWhole = whole.replace(/^0+(?=\d)/, "") || "0";
|
|
437
|
+
const normalizedFraction = fraction.replace(/0+$/, "");
|
|
438
|
+
return normalizedFraction ? `${normalizedWhole}.${normalizedFraction}` : normalizedWhole;
|
|
439
|
+
}
|
|
440
|
+
function parseMerchantSettlement(header) {
|
|
441
|
+
if (!header) return {};
|
|
442
|
+
const parsed = parseProtocolReceiptHeader(header);
|
|
443
|
+
const tx = typeof parsed?.transaction === "string" ? parsed.transaction : typeof parsed?.txHash === "string" ? parsed.txHash : typeof parsed?.tx_hash === "string" ? parsed.tx_hash : null;
|
|
444
|
+
return { settlementTxHash: tx };
|
|
445
|
+
}
|
|
421
446
|
var HavenClient = class {
|
|
422
447
|
apiKey;
|
|
423
448
|
delegateKey;
|
|
@@ -561,7 +586,7 @@ var HavenClient = class {
|
|
|
561
586
|
*
|
|
562
587
|
* Requires `delegateKey` to be set in the client config.
|
|
563
588
|
*/
|
|
564
|
-
async authorizeX402(paymentRequired) {
|
|
589
|
+
async authorizeX402(paymentRequired, options = {}) {
|
|
565
590
|
if (!this.delegateKey) {
|
|
566
591
|
throw new HavenSigningError(
|
|
567
592
|
"delegateKey is required for x402 payments. Pass it in the HavenClient config."
|
|
@@ -577,7 +602,7 @@ var HavenClient = class {
|
|
|
577
602
|
400
|
|
578
603
|
);
|
|
579
604
|
}
|
|
580
|
-
const idempotencyKey = buildX402IdempotencyKey(paymentRequired, option);
|
|
605
|
+
const idempotencyKey = options.idempotencyKey ?? buildX402IdempotencyKey(paymentRequired, option);
|
|
581
606
|
const cached = this.x402ReceiptCache.get(idempotencyKey);
|
|
582
607
|
if (cached && cached.expiresAt > Date.now()) return cached.receipt;
|
|
583
608
|
const inFlight = this.inFlightX402.get(idempotencyKey);
|
|
@@ -603,21 +628,13 @@ var HavenClient = class {
|
|
|
603
628
|
idempotencyKey
|
|
604
629
|
});
|
|
605
630
|
if (raw.success && raw.tx_hash) {
|
|
606
|
-
const receipt2 =
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
resourceUrl: paymentRequired.resource.url,
|
|
614
|
-
explorerUrl: raw.explorer_url ?? (raw.tx_hash ? buildExplorerUrl(raw.chain_id, raw.tx_hash) : ""),
|
|
615
|
-
accepted: option,
|
|
616
|
-
paymentHeader,
|
|
617
|
-
merchantTo: raw.merchant_to ?? option.payTo,
|
|
618
|
-
payer: raw.payer ?? raw.safe_address,
|
|
619
|
-
chainId: raw.chain_id ?? chainIdFromNetwork(option.network)
|
|
620
|
-
};
|
|
631
|
+
const receipt2 = this.mapX402ReceiptFromAuthorization(paymentRequired, option, paymentHeader, raw);
|
|
632
|
+
this.cacheX402Receipt(idempotencyKey, paymentHeader, receipt2);
|
|
633
|
+
return receipt2;
|
|
634
|
+
}
|
|
635
|
+
const state = this.paymentStateFromRaw("x402 payment", raw);
|
|
636
|
+
if (state?.nextAction === "retry_original_x402_request") {
|
|
637
|
+
const receipt2 = this.mapX402ReceiptFromStatus(paymentRequired, option, paymentHeader, state);
|
|
621
638
|
this.cacheX402Receipt(idempotencyKey, paymentHeader, receipt2);
|
|
622
639
|
return receipt2;
|
|
623
640
|
}
|
|
@@ -633,24 +650,53 @@ var HavenClient = class {
|
|
|
633
650
|
if (execResult.status !== "confirmed") {
|
|
634
651
|
this.throwPaymentStateError("x402 payment", execResult);
|
|
635
652
|
}
|
|
636
|
-
const receipt =
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
|
|
653
|
+
const receipt = this.mapX402ReceiptFromAuthorization(paymentRequired, option, paymentHeader, raw, execResult);
|
|
654
|
+
this.cacheX402Receipt(idempotencyKey, paymentHeader, receipt);
|
|
655
|
+
return receipt;
|
|
656
|
+
}
|
|
657
|
+
async resumeAuthorizedX402(input) {
|
|
658
|
+
if (!this.delegateKey) {
|
|
659
|
+
throw new HavenSigningError(
|
|
660
|
+
"delegateKey is required for x402 payments. Pass it in the HavenClient config."
|
|
661
|
+
);
|
|
662
|
+
}
|
|
663
|
+
if (!this.delegateAddress) {
|
|
664
|
+
throw new HavenSigningError("delegateAddress could not be derived from delegateKey.");
|
|
665
|
+
}
|
|
666
|
+
const option = selectStandardPaymentOption(input.paymentRequired.accepts);
|
|
667
|
+
if (!option) {
|
|
668
|
+
throw new HavenApiError(
|
|
669
|
+
"No compatible payment option found in x402 requirements. Haven supports standard x402 exact payments on Base USDC.",
|
|
670
|
+
400
|
|
671
|
+
);
|
|
672
|
+
}
|
|
673
|
+
const idempotencyKey = input.idempotencyKey ?? buildX402IdempotencyKey(input.paymentRequired, option);
|
|
674
|
+
const cached = this.x402ReceiptCache.get(idempotencyKey);
|
|
675
|
+
if (cached && cached.expiresAt > Date.now()) return cached.receipt;
|
|
676
|
+
const status = await this.getPaymentStatus(input.paymentId);
|
|
677
|
+
this.assertCanResumeX402(status, input.paymentRequired, option);
|
|
678
|
+
const paymentHeader = await this.createStandardX402Header(input.paymentRequired, option);
|
|
679
|
+
const receipt = this.mapX402ReceiptFromStatus(input.paymentRequired, option, paymentHeader, status);
|
|
651
680
|
this.cacheX402Receipt(idempotencyKey, paymentHeader, receipt);
|
|
652
681
|
return receipt;
|
|
653
682
|
}
|
|
683
|
+
async resumeX402Payment(input) {
|
|
684
|
+
const initialInit = this.withX402Wallet(input.init, this.x402PayerAddress());
|
|
685
|
+
let paymentRequired = input.paymentRequired;
|
|
686
|
+
if (!paymentRequired) {
|
|
687
|
+
const response = await globalThis.fetch(input.url, initialInit);
|
|
688
|
+
if (response.status !== 402) {
|
|
689
|
+
throw new HavenApiError("Expected the original x402 request to return HTTP 402 before resuming.", 400);
|
|
690
|
+
}
|
|
691
|
+
paymentRequired = await parsePaymentRequiredResponse(response);
|
|
692
|
+
}
|
|
693
|
+
const receipt = await this.resumeAuthorizedX402({
|
|
694
|
+
paymentId: input.paymentId,
|
|
695
|
+
paymentRequired,
|
|
696
|
+
idempotencyKey: input.idempotencyKey
|
|
697
|
+
});
|
|
698
|
+
return this.retryX402Request(input.url, initialInit, paymentRequired, receipt);
|
|
699
|
+
}
|
|
654
700
|
/**
|
|
655
701
|
* Fetch wrapper that automatically handles HTTP 402 responses.
|
|
656
702
|
*
|
|
@@ -664,7 +710,7 @@ var HavenClient = class {
|
|
|
664
710
|
*
|
|
665
711
|
* Requires `delegateKey` to be set in the client config.
|
|
666
712
|
*/
|
|
667
|
-
async fetch(url, init) {
|
|
713
|
+
async fetch(url, init, options = {}) {
|
|
668
714
|
const initialInit = this.withX402Wallet(init, this.x402PayerAddress());
|
|
669
715
|
const response = await globalThis.fetch(url, initialInit);
|
|
670
716
|
if (response.status !== 402) return response;
|
|
@@ -685,7 +731,10 @@ var HavenClient = class {
|
|
|
685
731
|
}
|
|
686
732
|
return this.fetchWithMachinePayment(url, initialInit, challenge);
|
|
687
733
|
}
|
|
688
|
-
const receipt = await this.authorizeX402(paymentRequired);
|
|
734
|
+
const receipt = await this.authorizeX402(paymentRequired, options);
|
|
735
|
+
return this.retryX402Request(url, initialInit, paymentRequired, receipt);
|
|
736
|
+
}
|
|
737
|
+
async retryX402Request(url, initialInit, paymentRequired, receipt) {
|
|
689
738
|
if (!receipt.accepted) {
|
|
690
739
|
throw new HavenApiError("No accepted x402 option was recorded for payment retry", 500);
|
|
691
740
|
}
|
|
@@ -723,6 +772,14 @@ var HavenClient = class {
|
|
|
723
772
|
}
|
|
724
773
|
);
|
|
725
774
|
}
|
|
775
|
+
const merchantSettlement = parseMerchantSettlement(retryResponse.headers.get("PAYMENT-RESPONSE"));
|
|
776
|
+
if (receipt.merchant && merchantSettlement.settlementTxHash) {
|
|
777
|
+
receipt.merchant.settlementTxHash = merchantSettlement.settlementTxHash;
|
|
778
|
+
receipt.merchant.settlementExplorerUrl = buildExplorerUrl(
|
|
779
|
+
receipt.chainId,
|
|
780
|
+
merchantSettlement.settlementTxHash
|
|
781
|
+
);
|
|
782
|
+
}
|
|
726
783
|
await this.reportMachinePaymentEvidence({
|
|
727
784
|
paymentId: receipt.paymentId,
|
|
728
785
|
rail: "x402",
|
|
@@ -825,6 +882,150 @@ var HavenClient = class {
|
|
|
825
882
|
});
|
|
826
883
|
return retryResponse;
|
|
827
884
|
}
|
|
885
|
+
assertCanResumeX402(status, paymentRequired, option) {
|
|
886
|
+
if (status.rail !== "x402") {
|
|
887
|
+
throw new HavenPaymentStateError(
|
|
888
|
+
`Payment ${status.paymentId} is ${status.rail}, not x402.`,
|
|
889
|
+
409,
|
|
890
|
+
status
|
|
891
|
+
);
|
|
892
|
+
}
|
|
893
|
+
if (status.nextAction !== "retry_original_x402_request") {
|
|
894
|
+
throw new HavenPaymentStateError(status.message, PAYMENT_STATE_STATUS_CODES[status.status] ?? 409, status);
|
|
895
|
+
}
|
|
896
|
+
if (!status.txHash) {
|
|
897
|
+
throw new HavenApiError(
|
|
898
|
+
`x402 payment ${status.paymentId} is ready to retry but has no Haven transaction hash.`,
|
|
899
|
+
502,
|
|
900
|
+
status,
|
|
901
|
+
status.paymentId
|
|
902
|
+
);
|
|
903
|
+
}
|
|
904
|
+
if (status.resourceUrl && status.resourceUrl !== paymentRequired.resource.url) {
|
|
905
|
+
throw new HavenApiError(
|
|
906
|
+
"x402 resume request does not match the approved resource URL.",
|
|
907
|
+
409,
|
|
908
|
+
{ status, paymentRequired },
|
|
909
|
+
status.paymentId
|
|
910
|
+
);
|
|
911
|
+
}
|
|
912
|
+
if (status.merchantAddress && !sameAddress(status.merchantAddress, option.payTo)) {
|
|
913
|
+
throw new HavenApiError(
|
|
914
|
+
"x402 resume request does not match the approved merchant.",
|
|
915
|
+
409,
|
|
916
|
+
{ status, selectedPayment: option },
|
|
917
|
+
status.paymentId
|
|
918
|
+
);
|
|
919
|
+
}
|
|
920
|
+
const optionChainId = chainIdFromNetwork(option.network);
|
|
921
|
+
if (status.chainId && optionChainId && status.chainId !== optionChainId) {
|
|
922
|
+
throw new HavenApiError(
|
|
923
|
+
"x402 resume request does not match the approved network.",
|
|
924
|
+
409,
|
|
925
|
+
{ status, selectedPayment: option },
|
|
926
|
+
status.paymentId
|
|
927
|
+
);
|
|
928
|
+
}
|
|
929
|
+
if (status.token && status.token !== "USDC") {
|
|
930
|
+
throw new HavenApiError(
|
|
931
|
+
"x402 resume request does not match the approved token.",
|
|
932
|
+
409,
|
|
933
|
+
{ status, selectedPayment: option },
|
|
934
|
+
status.paymentId
|
|
935
|
+
);
|
|
936
|
+
}
|
|
937
|
+
const approvedAmount = status.amount ? normalizeDecimal(status.amount) : "";
|
|
938
|
+
const requestedAmount = normalizeDecimal(decimalFromUsdcAtomic(option.amount));
|
|
939
|
+
if (approvedAmount && approvedAmount !== requestedAmount) {
|
|
940
|
+
throw new HavenApiError(
|
|
941
|
+
"x402 resume request does not match the approved amount.",
|
|
942
|
+
409,
|
|
943
|
+
{ status, selectedPayment: option },
|
|
944
|
+
status.paymentId
|
|
945
|
+
);
|
|
946
|
+
}
|
|
947
|
+
}
|
|
948
|
+
mapX402ReceiptFromAuthorization(paymentRequired, option, paymentHeader, raw, execResult) {
|
|
949
|
+
const txHash = execResult?.tx_hash ?? raw.tx_hash ?? "";
|
|
950
|
+
const chainId = execResult?.chain_id ?? raw.chain_id ?? chainIdFromNetwork(option.network);
|
|
951
|
+
const token = execResult?.token ?? raw.token ?? "USDC";
|
|
952
|
+
const amount = execResult?.amount ?? raw.amount ?? decimalFromUsdcAtomic(option.amount);
|
|
953
|
+
const to = execResult?.to ?? raw.to ?? this.delegateAddress ?? "";
|
|
954
|
+
const explorerUrl = execResult?.explorer_url ?? raw.explorer_url ?? explorerUrlOrEmpty(chainId, txHash);
|
|
955
|
+
const merchantTo = execResult?.merchant_to ?? raw.merchant_to ?? option.payTo;
|
|
956
|
+
const payer = raw.payer ?? raw.safe_address ?? raw.sign_data?.components.safe;
|
|
957
|
+
return this.buildX402Receipt({
|
|
958
|
+
paymentId: raw.payment_id,
|
|
959
|
+
txHash,
|
|
960
|
+
token,
|
|
961
|
+
amount,
|
|
962
|
+
to,
|
|
963
|
+
resourceUrl: paymentRequired.resource.url,
|
|
964
|
+
explorerUrl,
|
|
965
|
+
accepted: option,
|
|
966
|
+
paymentHeader,
|
|
967
|
+
merchantTo,
|
|
968
|
+
payer,
|
|
969
|
+
chainId
|
|
970
|
+
});
|
|
971
|
+
}
|
|
972
|
+
mapX402ReceiptFromStatus(paymentRequired, option, paymentHeader, status) {
|
|
973
|
+
if (!status.txHash) {
|
|
974
|
+
throw new HavenApiError(
|
|
975
|
+
`x402 payment ${status.paymentId} is ready to retry but has no Haven transaction hash.`,
|
|
976
|
+
502,
|
|
977
|
+
status,
|
|
978
|
+
status.paymentId
|
|
979
|
+
);
|
|
980
|
+
}
|
|
981
|
+
return this.buildX402Receipt({
|
|
982
|
+
paymentId: status.paymentId,
|
|
983
|
+
txHash: status.txHash,
|
|
984
|
+
token: status.token || "USDC",
|
|
985
|
+
amount: status.amount || decimalFromUsdcAtomic(option.amount),
|
|
986
|
+
to: this.delegateAddress ?? "",
|
|
987
|
+
resourceUrl: paymentRequired.resource.url,
|
|
988
|
+
explorerUrl: explorerUrlOrEmpty(status.chainId, status.txHash),
|
|
989
|
+
accepted: option,
|
|
990
|
+
paymentHeader,
|
|
991
|
+
merchantTo: status.merchantAddress ?? option.payTo,
|
|
992
|
+
payer: this.x402Wallet,
|
|
993
|
+
chainId: status.chainId || chainIdFromNetwork(option.network)
|
|
994
|
+
});
|
|
995
|
+
}
|
|
996
|
+
buildX402Receipt(input) {
|
|
997
|
+
const fundingExplorerUrl = input.explorerUrl || explorerUrlOrEmpty(input.chainId, input.txHash);
|
|
998
|
+
return {
|
|
999
|
+
success: true,
|
|
1000
|
+
paymentId: input.paymentId,
|
|
1001
|
+
txHash: input.txHash,
|
|
1002
|
+
token: input.token,
|
|
1003
|
+
amount: input.amount,
|
|
1004
|
+
to: input.to,
|
|
1005
|
+
resourceUrl: input.resourceUrl,
|
|
1006
|
+
explorerUrl: input.explorerUrl,
|
|
1007
|
+
accepted: input.accepted,
|
|
1008
|
+
paymentHeader: input.paymentHeader,
|
|
1009
|
+
merchantTo: input.merchantTo ?? input.accepted.payTo,
|
|
1010
|
+
payer: input.payer,
|
|
1011
|
+
chainId: input.chainId,
|
|
1012
|
+
haven: {
|
|
1013
|
+
paymentId: input.paymentId,
|
|
1014
|
+
fundingTxHash: input.txHash,
|
|
1015
|
+
fundingExplorerUrl
|
|
1016
|
+
},
|
|
1017
|
+
merchant: {
|
|
1018
|
+
payTo: input.merchantTo ?? input.accepted.payTo
|
|
1019
|
+
},
|
|
1020
|
+
x402: {
|
|
1021
|
+
amount: input.accepted.amount,
|
|
1022
|
+
token: input.token,
|
|
1023
|
+
network: input.accepted.network,
|
|
1024
|
+
asset: input.accepted.asset,
|
|
1025
|
+
resource: input.accepted.resource ?? input.resourceUrl
|
|
1026
|
+
}
|
|
1027
|
+
};
|
|
1028
|
+
}
|
|
828
1029
|
async createStandardX402Header(paymentRequired, option) {
|
|
829
1030
|
if (!this.delegateKey) {
|
|
830
1031
|
throw new HavenSigningError("delegateKey is required to sign x402 payment headers.");
|
|
@@ -1008,36 +1209,26 @@ var HavenClient = class {
|
|
|
1008
1209
|
}
|
|
1009
1210
|
}
|
|
1010
1211
|
if (toolName === "authorize_x402_payment") {
|
|
1011
|
-
const { url, payTo, amount, asset, network, description } = input;
|
|
1212
|
+
const { url, payTo, amount, asset, network, description, idempotencyKey } = input;
|
|
1012
1213
|
try {
|
|
1013
|
-
const receipt = await this.authorizeX402(
|
|
1014
|
-
|
|
1015
|
-
|
|
1016
|
-
|
|
1017
|
-
|
|
1018
|
-
|
|
1019
|
-
|
|
1020
|
-
|
|
1021
|
-
|
|
1022
|
-
|
|
1023
|
-
|
|
1024
|
-
|
|
1025
|
-
|
|
1214
|
+
const receipt = await this.authorizeX402(
|
|
1215
|
+
this.toolX402PaymentRequired({ url, payTo, amount, asset, network, description }),
|
|
1216
|
+
{ idempotencyKey }
|
|
1217
|
+
);
|
|
1218
|
+
return this.x402ToolReceipt(receipt);
|
|
1219
|
+
} catch (err) {
|
|
1220
|
+
return this.toolError(err);
|
|
1221
|
+
}
|
|
1222
|
+
}
|
|
1223
|
+
if (toolName === "resume_x402_payment") {
|
|
1224
|
+
const { payment_id, url, payTo, amount, asset, network, description, idempotencyKey } = input;
|
|
1225
|
+
try {
|
|
1226
|
+
const receipt = await this.resumeAuthorizedX402({
|
|
1227
|
+
paymentId: payment_id,
|
|
1228
|
+
paymentRequired: this.toolX402PaymentRequired({ url, payTo, amount, asset, network, description }),
|
|
1229
|
+
idempotencyKey
|
|
1026
1230
|
});
|
|
1027
|
-
return
|
|
1028
|
-
success: true,
|
|
1029
|
-
payment_id: receipt.paymentId,
|
|
1030
|
-
tx_hash: receipt.txHash,
|
|
1031
|
-
token: receipt.token,
|
|
1032
|
-
amount: receipt.amount,
|
|
1033
|
-
to: receipt.to,
|
|
1034
|
-
resource_url: receipt.resourceUrl,
|
|
1035
|
-
explorer_url: receipt.explorerUrl,
|
|
1036
|
-
payment_header: receipt.paymentHeader,
|
|
1037
|
-
merchant_to: receipt.merchantTo,
|
|
1038
|
-
payer: receipt.payer,
|
|
1039
|
-
chain_id: receipt.chainId
|
|
1040
|
-
};
|
|
1231
|
+
return this.x402ToolReceipt(receipt);
|
|
1041
1232
|
} catch (err) {
|
|
1042
1233
|
return this.toolError(err);
|
|
1043
1234
|
}
|
|
@@ -1087,6 +1278,41 @@ var HavenClient = class {
|
|
|
1087
1278
|
}
|
|
1088
1279
|
throw new Error(`Unknown tool: ${toolName}`);
|
|
1089
1280
|
}
|
|
1281
|
+
toolX402PaymentRequired(input) {
|
|
1282
|
+
return {
|
|
1283
|
+
x402Version: 2,
|
|
1284
|
+
resource: { url: input.url, description: input.description },
|
|
1285
|
+
accepts: [
|
|
1286
|
+
{
|
|
1287
|
+
scheme: "exact",
|
|
1288
|
+
network: input.network,
|
|
1289
|
+
amount: input.amount,
|
|
1290
|
+
asset: input.asset,
|
|
1291
|
+
payTo: input.payTo,
|
|
1292
|
+
maxTimeoutSeconds: 30
|
|
1293
|
+
}
|
|
1294
|
+
]
|
|
1295
|
+
};
|
|
1296
|
+
}
|
|
1297
|
+
x402ToolReceipt(receipt) {
|
|
1298
|
+
return {
|
|
1299
|
+
success: true,
|
|
1300
|
+
payment_id: receipt.paymentId,
|
|
1301
|
+
tx_hash: receipt.txHash,
|
|
1302
|
+
token: receipt.token,
|
|
1303
|
+
amount: receipt.amount,
|
|
1304
|
+
to: receipt.to,
|
|
1305
|
+
resource_url: receipt.resourceUrl,
|
|
1306
|
+
explorer_url: receipt.explorerUrl,
|
|
1307
|
+
payment_header: receipt.paymentHeader,
|
|
1308
|
+
merchant_to: receipt.merchantTo,
|
|
1309
|
+
payer: receipt.payer,
|
|
1310
|
+
chain_id: receipt.chainId,
|
|
1311
|
+
haven: receipt.haven,
|
|
1312
|
+
merchant: receipt.merchant,
|
|
1313
|
+
x402: receipt.x402
|
|
1314
|
+
};
|
|
1315
|
+
}
|
|
1090
1316
|
toolError(err) {
|
|
1091
1317
|
if (err instanceof HavenPaymentStateError) {
|
|
1092
1318
|
return {
|
|
@@ -1295,10 +1521,52 @@ var authorizeX402Schema = {
|
|
|
1295
1521
|
description: {
|
|
1296
1522
|
type: "string",
|
|
1297
1523
|
description: "Description of the resource being paid for"
|
|
1524
|
+
},
|
|
1525
|
+
idempotencyKey: {
|
|
1526
|
+
type: "string",
|
|
1527
|
+
description: "Stable caller-supplied key for this user intent. Reuse it when resuming after user approval."
|
|
1298
1528
|
}
|
|
1299
1529
|
},
|
|
1300
1530
|
required: ["url", "payTo", "amount", "asset", "network"]
|
|
1301
1531
|
};
|
|
1532
|
+
var resumeX402Schema = {
|
|
1533
|
+
type: "object",
|
|
1534
|
+
properties: {
|
|
1535
|
+
payment_id: {
|
|
1536
|
+
type: "string",
|
|
1537
|
+
description: "The payment or approval request ID returned by authorize_x402_payment."
|
|
1538
|
+
},
|
|
1539
|
+
url: {
|
|
1540
|
+
type: "string",
|
|
1541
|
+
description: "The original URL that returned HTTP 402."
|
|
1542
|
+
},
|
|
1543
|
+
payTo: {
|
|
1544
|
+
type: "string",
|
|
1545
|
+
description: "Payment recipient address from the original 402 response."
|
|
1546
|
+
},
|
|
1547
|
+
amount: {
|
|
1548
|
+
type: "string",
|
|
1549
|
+
description: "Payment amount in atomic units from the original 402 response."
|
|
1550
|
+
},
|
|
1551
|
+
asset: {
|
|
1552
|
+
type: "string",
|
|
1553
|
+
description: "Token contract address from the original 402 response."
|
|
1554
|
+
},
|
|
1555
|
+
network: {
|
|
1556
|
+
type: "string",
|
|
1557
|
+
description: "CAIP-2 chain ID or x402 network from the original 402 response."
|
|
1558
|
+
},
|
|
1559
|
+
description: {
|
|
1560
|
+
type: "string",
|
|
1561
|
+
description: "Description of the resource being paid for."
|
|
1562
|
+
},
|
|
1563
|
+
idempotencyKey: {
|
|
1564
|
+
type: "string",
|
|
1565
|
+
description: "Stable caller-supplied key used for the original authorization."
|
|
1566
|
+
}
|
|
1567
|
+
},
|
|
1568
|
+
required: ["payment_id", "url", "payTo", "amount", "asset", "network"]
|
|
1569
|
+
};
|
|
1302
1570
|
var authorizeMachinePaymentSchema = {
|
|
1303
1571
|
type: "object",
|
|
1304
1572
|
properties: {
|
|
@@ -1311,7 +1579,8 @@ var authorizeMachinePaymentSchema = {
|
|
|
1311
1579
|
};
|
|
1312
1580
|
var MAKE_PAYMENT_DESCRIPTION = "Request and sign a payment from the user-controlled Safe within approved on-chain limits. Haven authenticates the agent, validates the signed intent, and relays the Safe AllowanceModule transaction; it does not hold keys or control funds. Gnosis Chain tokens: EURe, USDC.e, xDAI. Base tokens: USDC, ETH.";
|
|
1313
1581
|
var GET_STATUS_DESCRIPTION = "Check the status of a previously initiated payment. Accepts payment intent IDs and approval request IDs. Returns the current status, phase, next_action, transaction hash if available, and payment details.";
|
|
1314
|
-
var AUTHORIZE_X402_DESCRIPTION = "Authorize payment for an HTTP 402 (Payment Required) response. When a paid API returns x402 payment requirements, use this tool to sign with the agent-owned delegate key and request a policy-limited Safe AllowanceModule top-up when needed. Haven relays signed transactions only; the agent key authorizes payment and on-chain limits enforce spend. If this returns pending_approval, tell the user it is waiting in Haven, call get_payment_status later, and
|
|
1582
|
+
var AUTHORIZE_X402_DESCRIPTION = "Authorize payment for an HTTP 402 (Payment Required) response. When a paid API returns x402 payment requirements, use this tool to sign with the agent-owned delegate key and request a policy-limited Safe AllowanceModule top-up when needed. Haven relays signed transactions only; the agent key authorizes payment and on-chain limits enforce spend. If this returns pending_approval, tell the user it is waiting in Haven, call get_payment_status later, and use resume_x402_payment only when next_action is retry_original_x402_request. Do not loop retries while approval is pending. Use the returned payment_header as the X-PAYMENT header on the retry request when doing a manual HTTP retry.";
|
|
1583
|
+
var RESUME_X402_DESCRIPTION = "Resume an x402 payment after the user approved it in Haven. Use this only after get_payment_status returns next_action=retry_original_x402_request. It checks the approved payment, validates the original x402 details, and returns a merchant X-PAYMENT header without creating a new approval request.";
|
|
1315
1584
|
var AUTHORIZE_MACHINE_PAYMENT_DESCRIPTION = "Authorize a Haven machine-payment challenge, currently for the internal MPP demo rail. The agent signs the payment, Haven relays it within the on-chain allowance, and the tool returns a proof header for the retry request.";
|
|
1316
1585
|
function claudeTools() {
|
|
1317
1586
|
return [
|
|
@@ -1330,6 +1599,11 @@ function claudeTools() {
|
|
|
1330
1599
|
description: AUTHORIZE_X402_DESCRIPTION,
|
|
1331
1600
|
input_schema: authorizeX402Schema
|
|
1332
1601
|
},
|
|
1602
|
+
{
|
|
1603
|
+
name: "resume_x402_payment",
|
|
1604
|
+
description: RESUME_X402_DESCRIPTION,
|
|
1605
|
+
input_schema: resumeX402Schema
|
|
1606
|
+
},
|
|
1333
1607
|
{
|
|
1334
1608
|
name: "authorize_machine_payment",
|
|
1335
1609
|
description: AUTHORIZE_MACHINE_PAYMENT_DESCRIPTION,
|
|
@@ -1363,6 +1637,14 @@ function openaiTools() {
|
|
|
1363
1637
|
parameters: authorizeX402Schema
|
|
1364
1638
|
}
|
|
1365
1639
|
},
|
|
1640
|
+
{
|
|
1641
|
+
type: "function",
|
|
1642
|
+
function: {
|
|
1643
|
+
name: "resume_x402_payment",
|
|
1644
|
+
description: RESUME_X402_DESCRIPTION,
|
|
1645
|
+
parameters: resumeX402Schema
|
|
1646
|
+
}
|
|
1647
|
+
},
|
|
1366
1648
|
{
|
|
1367
1649
|
type: "function",
|
|
1368
1650
|
function: {
|