@cmdoss/suipay-mcp 0.2.2-dev.2 → 0.2.2-dev.20
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 +33 -11
- package/dist/bin/suipay.js +2 -2
- package/dist/{chunk-62LFIYB7.js → chunk-6VFKI2BY.js} +2141 -326
- package/dist/{chunk-AFEXT5X4.js → chunk-IBRMIXCG.js} +2 -2
- package/dist/{http-IivHoDeb.d.ts → http-RP-Xlg60.d.ts} +251 -6
- package/dist/http.d.ts +4 -1
- package/dist/http.js +1 -1
- package/dist/index.d.ts +12 -215
- package/dist/index.js +2 -2
- package/package.json +15 -14
|
@@ -103,7 +103,7 @@ function wrapFailover(clients) {
|
|
|
103
103
|
|
|
104
104
|
// ../sdk/dist/buyer-control/chain.js
|
|
105
105
|
import { Transaction as Transaction2, coinWithBalance } from "@mysten/sui/transactions";
|
|
106
|
-
import { normalizeStructTag as normalizeStructTag3, normalizeSuiAddress as normalizeSuiAddress2, toBase64 } from "@mysten/sui/utils";
|
|
106
|
+
import { fromBase64 as fromBase642, normalizeStructTag as normalizeStructTag3, normalizeSuiAddress as normalizeSuiAddress2, toBase64 } from "@mysten/sui/utils";
|
|
107
107
|
|
|
108
108
|
// ../core/dist/chain/address.js
|
|
109
109
|
function hexOf(address) {
|
|
@@ -538,6 +538,32 @@ function createSpendAccountChain(options) {
|
|
|
538
538
|
kindBytes: toBase64(await tx.build({ client, onlyTransactionKind: true }))
|
|
539
539
|
};
|
|
540
540
|
}
|
|
541
|
+
function appendCreatePolicyGrant(tx, input) {
|
|
542
|
+
if (!input.onChainPolicyIds.length) {
|
|
543
|
+
throw new Error("create_grant requires at least one on-chain policy id");
|
|
544
|
+
}
|
|
545
|
+
if (!input.maxPerPayment || BigInt(input.maxPerPayment) <= 0n) {
|
|
546
|
+
throw new Error("create_grant requires a non-zero max_per_payment");
|
|
547
|
+
}
|
|
548
|
+
if (!input.sessionCap || BigInt(input.sessionCap) <= 0n) {
|
|
549
|
+
throw new Error("create_grant requires a non-zero session_cap");
|
|
550
|
+
}
|
|
551
|
+
const expires = input.expiresAtMs === void 0 || input.expiresAtMs === null ? 0n : BigInt(input.expiresAtMs);
|
|
552
|
+
tx.moveCall({
|
|
553
|
+
target: target("create_policy_grant"),
|
|
554
|
+
typeArguments: [input.coinType],
|
|
555
|
+
arguments: [
|
|
556
|
+
tx.object(input.poolObjectId),
|
|
557
|
+
tx.pure.address(input.delegate),
|
|
558
|
+
// The unified Grant<T> takes Option<u64> caps. This builder rejects
|
|
559
|
+
// absent or zero caps above, so both are always Some here.
|
|
560
|
+
tx.pure.option("u64", BigInt(input.sessionCap)),
|
|
561
|
+
tx.pure.option("u64", BigInt(input.maxPerPayment)),
|
|
562
|
+
tx.pure.u64(expires),
|
|
563
|
+
tx.pure.vector("u64", input.onChainPolicyIds.map((id) => BigInt(id)))
|
|
564
|
+
]
|
|
565
|
+
});
|
|
566
|
+
}
|
|
541
567
|
async function fundingCoin(tx, owner, coinType, amount) {
|
|
542
568
|
if (!isSuiCoinType(coinType)) {
|
|
543
569
|
return coinWithBalance({ type: coinType, balance: amount });
|
|
@@ -636,7 +662,7 @@ function createSpendAccountChain(options) {
|
|
|
636
662
|
const tx = new Transaction2();
|
|
637
663
|
tx.setSender(input.owner);
|
|
638
664
|
tx.moveCall({
|
|
639
|
-
target: target("
|
|
665
|
+
target: target("withdraw_to_owner"),
|
|
640
666
|
typeArguments: [input.coinType],
|
|
641
667
|
arguments: [
|
|
642
668
|
tx.object(input.poolObjectId),
|
|
@@ -737,32 +763,23 @@ function createSpendAccountChain(options) {
|
|
|
737
763
|
return kindBytes(tx);
|
|
738
764
|
},
|
|
739
765
|
async buildCreateGrantKind(input) {
|
|
740
|
-
|
|
741
|
-
|
|
742
|
-
|
|
743
|
-
|
|
744
|
-
|
|
766
|
+
const tx = new Transaction2();
|
|
767
|
+
tx.setSender(input.owner);
|
|
768
|
+
appendCreatePolicyGrant(tx, input);
|
|
769
|
+
return kindBytes(tx);
|
|
770
|
+
},
|
|
771
|
+
async buildCreateGrantBatchKind(inputs) {
|
|
772
|
+
if (!inputs.length) {
|
|
773
|
+
throw new Error("create_grant batch requires at least one grant");
|
|
745
774
|
}
|
|
746
|
-
|
|
747
|
-
|
|
775
|
+
const owner = inputs[0].owner;
|
|
776
|
+
if (inputs.some((input) => input.owner !== owner)) {
|
|
777
|
+
throw new Error("create_grant batch requires one owner");
|
|
748
778
|
}
|
|
749
779
|
const tx = new Transaction2();
|
|
750
|
-
tx.setSender(
|
|
751
|
-
const
|
|
752
|
-
|
|
753
|
-
target: target("create_policy_grant"),
|
|
754
|
-
typeArguments: [input.coinType],
|
|
755
|
-
arguments: [
|
|
756
|
-
tx.object(input.poolObjectId),
|
|
757
|
-
tx.pure.address(input.delegate),
|
|
758
|
-
// The unified Grant<T> takes Option<u64> caps. This builder rejects
|
|
759
|
-
// absent or zero caps above, so both are always Some here.
|
|
760
|
-
tx.pure.option("u64", BigInt(input.sessionCap)),
|
|
761
|
-
tx.pure.option("u64", BigInt(input.maxPerPayment)),
|
|
762
|
-
tx.pure.u64(expires),
|
|
763
|
-
tx.pure.vector("u64", input.onChainPolicyIds.map((id) => BigInt(id)))
|
|
764
|
-
]
|
|
765
|
-
});
|
|
780
|
+
tx.setSender(owner);
|
|
781
|
+
for (const input of inputs)
|
|
782
|
+
appendCreatePolicyGrant(tx, input);
|
|
766
783
|
return kindBytes(tx);
|
|
767
784
|
},
|
|
768
785
|
async buildCreateSpendGrantKind(input) {
|
|
@@ -823,24 +840,27 @@ function createSpendAccountChain(options) {
|
|
|
823
840
|
},
|
|
824
841
|
async buildSettleOpenRecipientKind(input) {
|
|
825
842
|
if (!input.termsHash || input.termsHash.length === 0) {
|
|
826
|
-
throw new Error("termsHash must be non-empty for spend_account::
|
|
843
|
+
throw new Error("termsHash must be non-empty for spend_account::settle_any_recipient");
|
|
827
844
|
}
|
|
828
845
|
if (!input.paymentIdHash || input.paymentIdHash.length === 0) {
|
|
829
|
-
throw new Error("paymentIdHash must be non-empty for spend_account::
|
|
846
|
+
throw new Error("paymentIdHash must be non-empty for spend_account::settle_any_recipient");
|
|
830
847
|
}
|
|
831
848
|
if (!input.recipient || isZeroAddress(input.recipient)) {
|
|
832
|
-
throw new Error("
|
|
849
|
+
throw new Error("settle_any_recipient recipient must be a non-zero address");
|
|
833
850
|
}
|
|
834
851
|
if (!input.amount || BigInt(input.amount) <= 0n) {
|
|
835
|
-
throw new Error("
|
|
852
|
+
throw new Error("settle_any_recipient amount must be > 0");
|
|
836
853
|
}
|
|
854
|
+
const guarded = resolveGuardedInputs(input, "settle_any_recipient");
|
|
837
855
|
const hash = targetHashBytes(input.targetHash);
|
|
838
856
|
const tx = new Transaction2();
|
|
839
857
|
tx.setSender(input.delegate);
|
|
840
858
|
tx.moveCall({
|
|
841
|
-
target: target("
|
|
859
|
+
target: target("settle_any_recipient"),
|
|
842
860
|
typeArguments: [input.coinType],
|
|
843
861
|
arguments: [
|
|
862
|
+
tx.object(guarded.exclusionDirectoryId),
|
|
863
|
+
tx.object(guarded.recipientRegistryId),
|
|
844
864
|
tx.object(input.poolObjectId),
|
|
845
865
|
tx.object(input.grantObjectId),
|
|
846
866
|
tx.pure.address(input.recipient),
|
|
@@ -848,6 +868,7 @@ function createSpendAccountChain(options) {
|
|
|
848
868
|
tx.pure.vector("u8", Array.from(input.paymentIdHash)),
|
|
849
869
|
tx.pure.vector("u8", Array.from(input.termsHash)),
|
|
850
870
|
tx.pure.vector("u8", Array.from(hash)),
|
|
871
|
+
tx.pure.u64(guarded.expiresAtMs),
|
|
851
872
|
tx.object(input.clockId ?? SUI_CLOCK)
|
|
852
873
|
]
|
|
853
874
|
});
|
|
@@ -858,6 +879,7 @@ function createSpendAccountChain(options) {
|
|
|
858
879
|
tx.setSender(input.owner);
|
|
859
880
|
tx.moveCall({
|
|
860
881
|
target: target("pause_grant"),
|
|
882
|
+
typeArguments: [input.coinType],
|
|
861
883
|
arguments: [tx.object(input.grantObjectId)]
|
|
862
884
|
});
|
|
863
885
|
return kindBytes(tx);
|
|
@@ -867,6 +889,7 @@ function createSpendAccountChain(options) {
|
|
|
867
889
|
tx.setSender(input.owner);
|
|
868
890
|
tx.moveCall({
|
|
869
891
|
target: target("resume_grant"),
|
|
892
|
+
typeArguments: [input.coinType],
|
|
870
893
|
arguments: [tx.object(input.grantObjectId)]
|
|
871
894
|
});
|
|
872
895
|
return kindBytes(tx);
|
|
@@ -876,6 +899,7 @@ function createSpendAccountChain(options) {
|
|
|
876
899
|
tx.setSender(input.owner);
|
|
877
900
|
tx.moveCall({
|
|
878
901
|
target: target("revoke_grant"),
|
|
902
|
+
typeArguments: [input.coinType],
|
|
879
903
|
arguments: [tx.object(input.grantObjectId)]
|
|
880
904
|
});
|
|
881
905
|
return kindBytes(tx);
|
|
@@ -944,30 +968,57 @@ function createSpendAccountChain(options) {
|
|
|
944
968
|
const tx = new Transaction2();
|
|
945
969
|
tx.setSender(input.owner);
|
|
946
970
|
const expires = input.newExpiresAtMs === void 0 || input.newExpiresAtMs === null ? 0n : BigInt(input.newExpiresAtMs);
|
|
971
|
+
const totalBudget = input.newTotalBudget === null || input.newTotalBudget === void 0 ? null : BigInt(input.newTotalBudget);
|
|
947
972
|
tx.moveCall({
|
|
948
973
|
target: target("tighten_grant_limits"),
|
|
974
|
+
// Grant<T> is generic; every sibling grant builder passes the coin type.
|
|
975
|
+
typeArguments: [input.coinType],
|
|
949
976
|
arguments: [
|
|
950
977
|
tx.object(input.grantObjectId),
|
|
951
|
-
tx.pure.u64
|
|
978
|
+
tx.pure.option("u64", totalBudget),
|
|
952
979
|
tx.pure.u64(expires)
|
|
953
980
|
]
|
|
954
981
|
});
|
|
955
982
|
return kindBytes(tx);
|
|
956
983
|
},
|
|
984
|
+
async readGrantTotalSpent(grantObjectId) {
|
|
985
|
+
try {
|
|
986
|
+
const { object } = await client.core.getObject({
|
|
987
|
+
objectId: grantObjectId,
|
|
988
|
+
include: { json: true }
|
|
989
|
+
});
|
|
990
|
+
const json = object?.json;
|
|
991
|
+
if (!json || typeof json !== "object")
|
|
992
|
+
return null;
|
|
993
|
+
const spent = json.total_spent;
|
|
994
|
+
if (typeof spent === "string" && /^\d+$/.test(spent))
|
|
995
|
+
return BigInt(spent);
|
|
996
|
+
if (typeof spent === "number" && Number.isFinite(spent))
|
|
997
|
+
return BigInt(Math.trunc(spent));
|
|
998
|
+
if (typeof spent === "bigint")
|
|
999
|
+
return spent;
|
|
1000
|
+
return null;
|
|
1001
|
+
} catch {
|
|
1002
|
+
return null;
|
|
1003
|
+
}
|
|
1004
|
+
},
|
|
957
1005
|
async buildPayKind(input) {
|
|
958
1006
|
if (!input.termsHash || input.termsHash.length === 0) {
|
|
959
|
-
throw new Error("termsHash must be non-empty for spend_account::
|
|
1007
|
+
throw new Error("termsHash must be non-empty for spend_account::settle_policy");
|
|
960
1008
|
}
|
|
961
1009
|
if (!input.paymentIdHash || input.paymentIdHash.length === 0) {
|
|
962
|
-
throw new Error("paymentIdHash must be non-empty for spend_account::
|
|
1010
|
+
throw new Error("paymentIdHash must be non-empty for spend_account::settle_policy");
|
|
963
1011
|
}
|
|
1012
|
+
const guarded = resolveGuardedInputs(input, "settle_policy");
|
|
964
1013
|
const hash = targetHashBytes(input.targetHash);
|
|
965
1014
|
const tx = new Transaction2();
|
|
966
1015
|
tx.setSender(input.delegate);
|
|
967
1016
|
tx.moveCall({
|
|
968
|
-
target: target("
|
|
1017
|
+
target: target("settle_policy"),
|
|
969
1018
|
typeArguments: [input.coinType],
|
|
970
1019
|
arguments: [
|
|
1020
|
+
tx.object(guarded.exclusionDirectoryId),
|
|
1021
|
+
tx.object(guarded.recipientRegistryId),
|
|
971
1022
|
tx.object(input.poolObjectId),
|
|
972
1023
|
tx.object(input.grantObjectId),
|
|
973
1024
|
tx.pure.u64(BigInt(input.policyId)),
|
|
@@ -975,6 +1026,7 @@ function createSpendAccountChain(options) {
|
|
|
975
1026
|
tx.pure.u64(BigInt(input.amount)),
|
|
976
1027
|
tx.pure.vector("u8", Array.from(input.paymentIdHash)),
|
|
977
1028
|
tx.pure.vector("u8", Array.from(input.termsHash)),
|
|
1029
|
+
tx.pure.u64(guarded.expiresAtMs),
|
|
978
1030
|
tx.object(input.clockId ?? SUI_CLOCK)
|
|
979
1031
|
]
|
|
980
1032
|
});
|
|
@@ -982,6 +1034,28 @@ function createSpendAccountChain(options) {
|
|
|
982
1034
|
}
|
|
983
1035
|
};
|
|
984
1036
|
}
|
|
1037
|
+
function resolveGuardedInputs(input, fn) {
|
|
1038
|
+
const present = [
|
|
1039
|
+
input.exclusionDirectoryId,
|
|
1040
|
+
input.recipientRegistryId,
|
|
1041
|
+
input.expiresAtMs
|
|
1042
|
+
].filter((v) => v !== void 0).length;
|
|
1043
|
+
if (present !== 3) {
|
|
1044
|
+
throw new Error(`${fn} on the fresh lineage requires exclusionDirectoryId, recipientRegistryId and expiresAtMs (mandatory guarded settlement \u2014 there is no unguarded twin)`);
|
|
1045
|
+
}
|
|
1046
|
+
const expires = BigInt(input.expiresAtMs);
|
|
1047
|
+
if (expires <= 0n) {
|
|
1048
|
+
throw new Error(`guarded ${fn} expiresAtMs must be a positive epoch-ms value`);
|
|
1049
|
+
}
|
|
1050
|
+
if (isZeroAddress(input.exclusionDirectoryId) || isZeroAddress(input.recipientRegistryId)) {
|
|
1051
|
+
throw new Error(`guarded ${fn} directory and registry ids must be non-zero`);
|
|
1052
|
+
}
|
|
1053
|
+
return {
|
|
1054
|
+
exclusionDirectoryId: input.exclusionDirectoryId,
|
|
1055
|
+
recipientRegistryId: input.recipientRegistryId,
|
|
1056
|
+
expiresAtMs: expires
|
|
1057
|
+
};
|
|
1058
|
+
}
|
|
985
1059
|
var SUI_NETWORKS = ["testnet", "devnet", "localnet", "mainnet"];
|
|
986
1060
|
function toGrpcNetwork(network) {
|
|
987
1061
|
const short = (network ?? "testnet").replace(/^sui:/, "");
|
|
@@ -990,18 +1064,132 @@ function toGrpcNetwork(network) {
|
|
|
990
1064
|
}
|
|
991
1065
|
return short;
|
|
992
1066
|
}
|
|
993
|
-
|
|
1067
|
+
var GUARDED_DELEGATE_ARG_LAYOUT = {
|
|
1068
|
+
settle_policy: {
|
|
1069
|
+
recipient: null,
|
|
1070
|
+
amount: 6,
|
|
1071
|
+
paymentIdHash: 7,
|
|
1072
|
+
expiresAtMs: 9
|
|
1073
|
+
},
|
|
1074
|
+
settle_any_recipient: {
|
|
1075
|
+
recipient: 4,
|
|
1076
|
+
amount: 5,
|
|
1077
|
+
paymentIdHash: 6,
|
|
1078
|
+
expiresAtMs: 9
|
|
1079
|
+
},
|
|
1080
|
+
settle_policy_to_refund_vault: {
|
|
1081
|
+
recipient: null,
|
|
1082
|
+
amount: 6,
|
|
1083
|
+
paymentIdHash: 7,
|
|
1084
|
+
expiresAtMs: 9
|
|
1085
|
+
},
|
|
1086
|
+
settle_any_recipient_to_refund_vault: {
|
|
1087
|
+
recipient: 4,
|
|
1088
|
+
amount: 5,
|
|
1089
|
+
paymentIdHash: 6,
|
|
1090
|
+
expiresAtMs: 9
|
|
1091
|
+
}
|
|
1092
|
+
};
|
|
1093
|
+
function pureBytesOf(data, arg) {
|
|
1094
|
+
if (!arg || arg.$kind !== "Input" || arg.Input === void 0)
|
|
1095
|
+
return null;
|
|
1096
|
+
const input = data.inputs?.[arg.Input];
|
|
1097
|
+
if (!input || input.$kind !== "Pure" || !input.Pure)
|
|
1098
|
+
return null;
|
|
1099
|
+
return input.Pure.bytes;
|
|
1100
|
+
}
|
|
1101
|
+
function readPureU64(data, arg) {
|
|
1102
|
+
const raw = pureBytesOf(data, arg);
|
|
1103
|
+
if (raw === null)
|
|
1104
|
+
return null;
|
|
1105
|
+
try {
|
|
1106
|
+
return bcs.u64().parse(fromBase642(raw));
|
|
1107
|
+
} catch {
|
|
1108
|
+
return null;
|
|
1109
|
+
}
|
|
1110
|
+
}
|
|
1111
|
+
function readPureVectorU8Hex(data, arg) {
|
|
1112
|
+
const raw = pureBytesOf(data, arg);
|
|
1113
|
+
if (raw === null)
|
|
1114
|
+
return null;
|
|
1115
|
+
try {
|
|
1116
|
+
const bytes = bcs.vector(bcs.u8()).parse(fromBase642(raw));
|
|
1117
|
+
return Buffer.from(bytes).toString("hex");
|
|
1118
|
+
} catch {
|
|
1119
|
+
return null;
|
|
1120
|
+
}
|
|
1121
|
+
}
|
|
1122
|
+
function readPureAddress(data, arg) {
|
|
1123
|
+
const raw = pureBytesOf(data, arg);
|
|
1124
|
+
if (raw === null)
|
|
1125
|
+
return null;
|
|
1126
|
+
try {
|
|
1127
|
+
return normalizeSuiAddress2(bcs.Address.parse(fromBase642(raw)));
|
|
1128
|
+
} catch {
|
|
1129
|
+
return null;
|
|
1130
|
+
}
|
|
1131
|
+
}
|
|
1132
|
+
function recoverGuardedDelegateCalls(finalized) {
|
|
1133
|
+
const raw = finalized?.bcs;
|
|
1134
|
+
let bytes;
|
|
1135
|
+
if (raw instanceof Uint8Array) {
|
|
1136
|
+
bytes = raw;
|
|
1137
|
+
} else if (typeof raw === "string") {
|
|
1138
|
+
try {
|
|
1139
|
+
bytes = fromBase642(raw);
|
|
1140
|
+
} catch {
|
|
1141
|
+
return [];
|
|
1142
|
+
}
|
|
1143
|
+
} else {
|
|
1144
|
+
return [];
|
|
1145
|
+
}
|
|
1146
|
+
let data;
|
|
1147
|
+
try {
|
|
1148
|
+
data = Transaction2.from(bytes).getData();
|
|
1149
|
+
} catch {
|
|
1150
|
+
return [];
|
|
1151
|
+
}
|
|
1152
|
+
const calls = [];
|
|
1153
|
+
for (const command of data.commands) {
|
|
1154
|
+
if (command.$kind !== "MoveCall")
|
|
1155
|
+
continue;
|
|
1156
|
+
const mc = command.MoveCall;
|
|
1157
|
+
if (!mc || mc.module !== "spend_account")
|
|
1158
|
+
continue;
|
|
1159
|
+
if (!(mc.function in GUARDED_DELEGATE_ARG_LAYOUT))
|
|
1160
|
+
continue;
|
|
1161
|
+
const fn = mc.function;
|
|
1162
|
+
const layout = GUARDED_DELEGATE_ARG_LAYOUT[fn];
|
|
1163
|
+
const args = mc.arguments ?? [];
|
|
1164
|
+
calls.push({
|
|
1165
|
+
packageId: normalizeSuiAddress2(mc.package),
|
|
1166
|
+
function: fn,
|
|
1167
|
+
paymentIdHash: readPureVectorU8Hex(data, args[layout.paymentIdHash]),
|
|
1168
|
+
amount: readPureU64(data, args[layout.amount]),
|
|
1169
|
+
recipient: layout.recipient === null ? null : readPureAddress(data, args[layout.recipient]),
|
|
1170
|
+
expiresAtMs: readPureU64(data, args[layout.expiresAtMs])
|
|
1171
|
+
});
|
|
1172
|
+
}
|
|
1173
|
+
return calls;
|
|
1174
|
+
}
|
|
1175
|
+
function createSpendAccountSettledTxReader(options) {
|
|
994
1176
|
const network = toGrpcNetwork(options.network);
|
|
995
1177
|
const client = options.client ?? createFailoverGrpcClient({
|
|
996
1178
|
network,
|
|
997
1179
|
url: options.url ?? `https://fullnode.${network}.sui.io:443`
|
|
998
1180
|
});
|
|
999
1181
|
return {
|
|
1000
|
-
async
|
|
1182
|
+
async getSettledTx(digest) {
|
|
1001
1183
|
const result = await fetchGrpcTransaction(client, digest, {
|
|
1002
1184
|
events: true,
|
|
1003
1185
|
effects: true,
|
|
1004
|
-
transaction: true
|
|
1186
|
+
transaction: true,
|
|
1187
|
+
// The guarded delegate recovery below decodes the authoritative
|
|
1188
|
+
// serialized transaction. Those bytes are a separate read-mask path
|
|
1189
|
+
// from the parsed `transaction` projection — without asking for them
|
|
1190
|
+
// the response carries none, recovery finds no guarded call, and every
|
|
1191
|
+
// delegate payment is refused after the funds already moved.
|
|
1192
|
+
bcs: true
|
|
1005
1193
|
});
|
|
1006
1194
|
if (!result)
|
|
1007
1195
|
return null;
|
|
@@ -1037,26 +1225,28 @@ function createSpendAccountFinalityReader(options) {
|
|
|
1037
1225
|
type: e.eventType,
|
|
1038
1226
|
kind: e.eventType.split("::").pop() ?? e.eventType
|
|
1039
1227
|
}));
|
|
1228
|
+
const guardedDelegateCalls = recoverGuardedDelegateCalls(finalized);
|
|
1040
1229
|
return {
|
|
1041
1230
|
status: "success",
|
|
1042
1231
|
txDigest: digest,
|
|
1043
1232
|
sender,
|
|
1044
1233
|
moveCalls,
|
|
1045
1234
|
objectIds,
|
|
1046
|
-
events
|
|
1235
|
+
events,
|
|
1236
|
+
guardedDelegateCalls
|
|
1047
1237
|
};
|
|
1048
1238
|
}
|
|
1049
1239
|
};
|
|
1050
1240
|
}
|
|
1051
1241
|
|
|
1052
1242
|
// ../sdk/dist/delegated-signer/payer.js
|
|
1053
|
-
import { bcs as
|
|
1243
|
+
import { bcs as bcs5 } from "@mysten/sui/bcs";
|
|
1054
1244
|
import { Transaction as Transaction4 } from "@mysten/sui/transactions";
|
|
1055
|
-
import { fromBase64 as
|
|
1245
|
+
import { fromBase64 as fromBase646, normalizeStructTag as normalizeStructTag6, normalizeSuiAddress as normalizeSuiAddress6, toBase64 as toBase645 } from "@mysten/sui/utils";
|
|
1056
1246
|
import { Ed25519Keypair } from "@mysten/sui/keypairs/ed25519";
|
|
1057
1247
|
|
|
1058
1248
|
// ../sdk/dist/pay-operation/operation.js
|
|
1059
|
-
import { normalizeStructTag as
|
|
1249
|
+
import { normalizeStructTag as normalizeStructTag5, normalizeSuiAddress as normalizeSuiAddress3 } from "@mysten/sui/utils";
|
|
1060
1250
|
|
|
1061
1251
|
// ../core/dist/policy/grant-target.js
|
|
1062
1252
|
function normalizeHash(hex) {
|
|
@@ -1139,6 +1329,9 @@ function resolveGrantTarget(snapshot, offerTargetHash) {
|
|
|
1139
1329
|
};
|
|
1140
1330
|
}
|
|
1141
1331
|
|
|
1332
|
+
// ../sdk/dist/protocol/carrier.js
|
|
1333
|
+
import { fromBase64 as fromBase644, toBase64 as toBase643 } from "@mysten/sui/utils";
|
|
1334
|
+
|
|
1142
1335
|
// ../sdk/dist/protocol/auth-params.js
|
|
1143
1336
|
var TOKEN = /^[A-Za-z0-9!#$%&'*+\-.^_`|~]+$/;
|
|
1144
1337
|
function formatAuthParams(params) {
|
|
@@ -1230,8 +1423,11 @@ function bytesToBase64Url(bytes) {
|
|
|
1230
1423
|
var SETTLEMENT_RAILS = [
|
|
1231
1424
|
"direct",
|
|
1232
1425
|
"delegate",
|
|
1233
|
-
"
|
|
1426
|
+
"recurring"
|
|
1234
1427
|
];
|
|
1428
|
+
function mintLiveRail(rail) {
|
|
1429
|
+
return rail;
|
|
1430
|
+
}
|
|
1235
1431
|
function isDelegateRail(rail) {
|
|
1236
1432
|
return rail === "delegate";
|
|
1237
1433
|
}
|
|
@@ -1239,6 +1435,29 @@ var DIALECT_ORDER = ["mpp", "x402"];
|
|
|
1239
1435
|
function isSettlementRail(value) {
|
|
1240
1436
|
return typeof value === "string" && SETTLEMENT_RAILS.includes(value);
|
|
1241
1437
|
}
|
|
1438
|
+
function decodeWireRail(value) {
|
|
1439
|
+
if (isSettlementRail(value))
|
|
1440
|
+
return value;
|
|
1441
|
+
if (value === "subscription")
|
|
1442
|
+
return "recurring";
|
|
1443
|
+
return null;
|
|
1444
|
+
}
|
|
1445
|
+
function decodeAcceptedRails(rails) {
|
|
1446
|
+
if (rails.length === 0)
|
|
1447
|
+
return null;
|
|
1448
|
+
const out = [];
|
|
1449
|
+
const seen = /* @__PURE__ */ new Set();
|
|
1450
|
+
for (const r of rails) {
|
|
1451
|
+
const decoded = decodeWireRail(r);
|
|
1452
|
+
if (!decoded)
|
|
1453
|
+
return null;
|
|
1454
|
+
if (seen.has(decoded))
|
|
1455
|
+
continue;
|
|
1456
|
+
seen.add(decoded);
|
|
1457
|
+
out.push(decoded);
|
|
1458
|
+
}
|
|
1459
|
+
return out;
|
|
1460
|
+
}
|
|
1242
1461
|
function assertSettlementRail(value) {
|
|
1243
1462
|
if (!isSettlementRail(value)) {
|
|
1244
1463
|
throw new Error(`invalid settlement rail: ${String(value)} (expected ${SETTLEMENT_RAILS.join(" | ")})`);
|
|
@@ -1249,7 +1468,7 @@ function isDialect(value) {
|
|
|
1249
1468
|
return value === "mpp" || value === "x402";
|
|
1250
1469
|
}
|
|
1251
1470
|
function isSettlementMode(value) {
|
|
1252
|
-
return value === "
|
|
1471
|
+
return value === "gateway-settled" || value === "self-settled";
|
|
1253
1472
|
}
|
|
1254
1473
|
function dialectsFromModes(modes) {
|
|
1255
1474
|
return DIALECT_ORDER.filter((d) => modes[d] != null);
|
|
@@ -1287,7 +1506,7 @@ function createSettlementRailAuthority(input) {
|
|
|
1287
1506
|
}
|
|
1288
1507
|
const dialectModes = Object.freeze({ ...input.dialectModes });
|
|
1289
1508
|
const supportedDialects = Object.freeze(dialectsFromModes(dialectModes));
|
|
1290
|
-
const proofMode = dialectModes.mpp ?? dialectModes.x402 ?? "
|
|
1509
|
+
const proofMode = dialectModes.mpp ?? dialectModes.x402 ?? "gateway-settled";
|
|
1291
1510
|
return Object.freeze({
|
|
1292
1511
|
rail: input.rail,
|
|
1293
1512
|
packageId: input.packageId.trim(),
|
|
@@ -1302,12 +1521,12 @@ function spendAccountDebitFunction(input) {
|
|
|
1302
1521
|
const open2 = input?.accessMode === "open";
|
|
1303
1522
|
const refundable = input?.refundable === true;
|
|
1304
1523
|
if (open2 && refundable)
|
|
1305
|
-
return "
|
|
1524
|
+
return "settle_any_recipient_to_refund_vault";
|
|
1306
1525
|
if (open2)
|
|
1307
|
-
return "
|
|
1526
|
+
return "settle_any_recipient";
|
|
1308
1527
|
if (refundable)
|
|
1309
|
-
return "
|
|
1310
|
-
return "
|
|
1528
|
+
return "settle_policy_to_refund_vault";
|
|
1529
|
+
return "settle_policy";
|
|
1311
1530
|
}
|
|
1312
1531
|
function defaultAuthorityForRail(rail, packageId, opts) {
|
|
1313
1532
|
switch (rail) {
|
|
@@ -1318,20 +1537,22 @@ function defaultAuthorityForRail(rail, packageId, opts) {
|
|
|
1318
1537
|
packageId,
|
|
1319
1538
|
module: "spend_account",
|
|
1320
1539
|
payFunction: spendAccountDebitFunction(),
|
|
1321
|
-
dialectModes: dual ? { mpp: "
|
|
1540
|
+
dialectModes: dual ? { mpp: "self-settled", x402: "gateway-settled" } : { mpp: "self-settled" }
|
|
1322
1541
|
});
|
|
1323
1542
|
}
|
|
1324
|
-
case "
|
|
1543
|
+
case "recurring":
|
|
1325
1544
|
return createSettlementRailAuthority({
|
|
1326
|
-
rail: "
|
|
1545
|
+
rail: "recurring",
|
|
1327
1546
|
packageId,
|
|
1328
1547
|
module: "subscription",
|
|
1329
1548
|
// Real Move entry function is `subscription::settle_period`; `charge`
|
|
1330
|
-
// never existed on-chain (a Move call to it would abort).
|
|
1549
|
+
// never existed on-chain (a Move call to it would abort). HTTP
|
|
1550
|
+
// `recurring` is the Move `RAIL_TAG`; the module name stays
|
|
1551
|
+
// `subscription`.
|
|
1331
1552
|
payFunction: "settle_period",
|
|
1332
1553
|
dialectModes: {
|
|
1333
|
-
mpp: "
|
|
1334
|
-
x402: "
|
|
1554
|
+
mpp: "gateway-settled",
|
|
1555
|
+
x402: "gateway-settled"
|
|
1335
1556
|
}
|
|
1336
1557
|
});
|
|
1337
1558
|
case "direct":
|
|
@@ -1339,10 +1560,12 @@ function defaultAuthorityForRail(rail, packageId, opts) {
|
|
|
1339
1560
|
rail: "direct",
|
|
1340
1561
|
packageId,
|
|
1341
1562
|
module: "payment",
|
|
1342
|
-
|
|
1563
|
+
// Fresh lineage (ADR-0048 Option B): the canonical guarded direct settle
|
|
1564
|
+
// is `payment::settle` (guard folded in; no `pay`/`settle_guarded` twin).
|
|
1565
|
+
payFunction: "settle",
|
|
1343
1566
|
dialectModes: {
|
|
1344
|
-
mpp: "
|
|
1345
|
-
x402: "
|
|
1567
|
+
mpp: "gateway-settled",
|
|
1568
|
+
x402: "gateway-settled"
|
|
1346
1569
|
}
|
|
1347
1570
|
});
|
|
1348
1571
|
default: {
|
|
@@ -1351,9 +1574,24 @@ function defaultAuthorityForRail(rail, packageId, opts) {
|
|
|
1351
1574
|
}
|
|
1352
1575
|
}
|
|
1353
1576
|
}
|
|
1577
|
+
function railSelfSettles(rail, opts) {
|
|
1578
|
+
const authority = defaultAuthorityForRail(rail, "0x0", opts);
|
|
1579
|
+
return Object.values(authority.dialectModes).some((mode) => mode === "self-settled");
|
|
1580
|
+
}
|
|
1354
1581
|
|
|
1355
1582
|
// ../sdk/dist/protocol/challenge.js
|
|
1356
1583
|
import { createHmac, randomBytes, timingSafeEqual } from "crypto";
|
|
1584
|
+
|
|
1585
|
+
// ../sdk/dist/protocol/refund-approval.js
|
|
1586
|
+
import { createHash as createHash3 } from "crypto";
|
|
1587
|
+
import { bcs as bcs2 } from "@mysten/sui/bcs";
|
|
1588
|
+
import { fromHex } from "@mysten/sui/utils";
|
|
1589
|
+
|
|
1590
|
+
// ../sdk/dist/protocol/coin-type-hash.js
|
|
1591
|
+
import { createHash as createHash2 } from "crypto";
|
|
1592
|
+
import { normalizeStructTag as normalizeStructTag4 } from "@mysten/sui/utils";
|
|
1593
|
+
|
|
1594
|
+
// ../sdk/dist/protocol/challenge.js
|
|
1357
1595
|
var SIGNED_FIELDS_V1 = [
|
|
1358
1596
|
"challengeId",
|
|
1359
1597
|
"resource",
|
|
@@ -1402,6 +1640,12 @@ var SIGNED_FIELDS_V3 = [
|
|
|
1402
1640
|
"refundTo",
|
|
1403
1641
|
"settlementRef"
|
|
1404
1642
|
];
|
|
1643
|
+
var SIGNED_FIELDS_V4 = [
|
|
1644
|
+
...SIGNED_FIELDS_V3,
|
|
1645
|
+
"acceptedRails",
|
|
1646
|
+
"exclusionProtocolVersion",
|
|
1647
|
+
"registryIdentity"
|
|
1648
|
+
];
|
|
1405
1649
|
var V3_OMIT_WHEN_ABSENT = /* @__PURE__ */ new Set([
|
|
1406
1650
|
"intent",
|
|
1407
1651
|
"periodMs",
|
|
@@ -1419,6 +1663,15 @@ var V3_OMIT_WHEN_ABSENT = /* @__PURE__ */ new Set([
|
|
|
1419
1663
|
"sponsorGasPrice",
|
|
1420
1664
|
"sponsorGasPayment"
|
|
1421
1665
|
]);
|
|
1666
|
+
var V4_OMIT_WHEN_ABSENT = /* @__PURE__ */ new Set([
|
|
1667
|
+
"acceptedRails",
|
|
1668
|
+
"exclusionProtocolVersion",
|
|
1669
|
+
"registryIdentity"
|
|
1670
|
+
]);
|
|
1671
|
+
var OMIT_WHEN_ABSENT = /* @__PURE__ */ new Set([
|
|
1672
|
+
...V3_OMIT_WHEN_ABSENT,
|
|
1673
|
+
...V4_OMIT_WHEN_ABSENT
|
|
1674
|
+
]);
|
|
1422
1675
|
var BODY_HASH_RE = /^[A-Za-z0-9_-]{43}$/;
|
|
1423
1676
|
var CONTENT_TYPE_RE = /^[a-z0-9!#$%&'*+.^_`|~-]+\/[a-z0-9!#$%&'*+.^_`|~-]+(?:; ?[a-z0-9!#$%&'*+.^_`|~-]+=(?:[a-zA-Z0-9!#$%&'*+.^_`|~-]+|"[^"\n\r]*"))*$/;
|
|
1424
1677
|
function isBodyHash(value) {
|
|
@@ -1446,10 +1699,43 @@ function presentV3OnlyFields(bearer) {
|
|
|
1446
1699
|
function carriesV3OnlyFields(bearer) {
|
|
1447
1700
|
return presentV3OnlyFields(bearer).length > 0;
|
|
1448
1701
|
}
|
|
1702
|
+
function presentV4OnlyFields(bearer) {
|
|
1703
|
+
return [
|
|
1704
|
+
bearer.acceptedRails !== void 0 ? "acceptedRails" : void 0,
|
|
1705
|
+
bearer.exclusionProtocolVersion !== void 0 ? "exclusionProtocolVersion" : void 0,
|
|
1706
|
+
bearer.registryIdentity !== void 0 ? "registryIdentity" : void 0
|
|
1707
|
+
].filter((f) => f !== void 0);
|
|
1708
|
+
}
|
|
1709
|
+
function carriesV4OnlyFields(bearer) {
|
|
1710
|
+
return presentV4OnlyFields(bearer).length > 0;
|
|
1711
|
+
}
|
|
1712
|
+
function offerRequiresGuardedSettle(offer) {
|
|
1713
|
+
const rails = [
|
|
1714
|
+
...offer.acceptedRails ?? [],
|
|
1715
|
+
...offer.settlementRail ? [offer.settlementRail] : []
|
|
1716
|
+
];
|
|
1717
|
+
if (!rails.length)
|
|
1718
|
+
return false;
|
|
1719
|
+
return [...new Set(rails)].some((r) => railSelfSettles(r, { x402DialectEnabled: false }));
|
|
1720
|
+
}
|
|
1721
|
+
function parseRegistryIdentity(registryIdentity) {
|
|
1722
|
+
if (!registryIdentity)
|
|
1723
|
+
return null;
|
|
1724
|
+
const idx = registryIdentity.indexOf("::");
|
|
1725
|
+
if (idx <= 0)
|
|
1726
|
+
return null;
|
|
1727
|
+
const packageId = registryIdentity.slice(0, idx);
|
|
1728
|
+
const directoryId = registryIdentity.slice(idx + 2);
|
|
1729
|
+
if (!packageId || !directoryId || directoryId.includes("::"))
|
|
1730
|
+
return null;
|
|
1731
|
+
return { packageId, directoryId };
|
|
1732
|
+
}
|
|
1449
1733
|
function resolveDigestVersion(offer) {
|
|
1734
|
+
if (carriesV4OnlyFields(offer))
|
|
1735
|
+
return 4;
|
|
1450
1736
|
if (carriesV3OnlyFields(offer))
|
|
1451
1737
|
return 3;
|
|
1452
|
-
if (offer.digestVersion === 3 || offer.digestVersion === 2 || offer.digestVersion === 1) {
|
|
1738
|
+
if (offer.digestVersion === 4 || offer.digestVersion === 3 || offer.digestVersion === 2 || offer.digestVersion === 1) {
|
|
1453
1739
|
return offer.digestVersion;
|
|
1454
1740
|
}
|
|
1455
1741
|
if (offer.settlementRail)
|
|
@@ -1457,6 +1743,8 @@ function resolveDigestVersion(offer) {
|
|
|
1457
1743
|
return 1;
|
|
1458
1744
|
}
|
|
1459
1745
|
function signedFieldsFor(version) {
|
|
1746
|
+
if (version === 4)
|
|
1747
|
+
return SIGNED_FIELDS_V4;
|
|
1460
1748
|
if (version === 3)
|
|
1461
1749
|
return SIGNED_FIELDS_V3;
|
|
1462
1750
|
if (version === 2)
|
|
@@ -1467,12 +1755,14 @@ function fieldValue(offer, key) {
|
|
|
1467
1755
|
const v = offer[key];
|
|
1468
1756
|
if (v === void 0 || v === null)
|
|
1469
1757
|
return "";
|
|
1758
|
+
if (Array.isArray(v))
|
|
1759
|
+
return v.join(",");
|
|
1470
1760
|
return String(v);
|
|
1471
1761
|
}
|
|
1472
1762
|
function canonicalOffer(offer, version = resolveDigestVersion(offer)) {
|
|
1473
1763
|
const lines = [];
|
|
1474
1764
|
for (const f of signedFieldsFor(version)) {
|
|
1475
|
-
if (
|
|
1765
|
+
if (OMIT_WHEN_ABSENT.has(f) && (offer[f] === void 0 || offer[f] === null))
|
|
1476
1766
|
continue;
|
|
1477
1767
|
lines.push(`${f}=${fieldValue(offer, f)}`);
|
|
1478
1768
|
}
|
|
@@ -1482,15 +1772,73 @@ function signOffer(offer, secret, version) {
|
|
|
1482
1772
|
const v = version ?? resolveDigestVersion(offer);
|
|
1483
1773
|
return createHmac("sha256", secret).update(canonicalOffer(offer, v)).digest("base64url");
|
|
1484
1774
|
}
|
|
1485
|
-
function
|
|
1775
|
+
function digestMatches(offer, secret) {
|
|
1486
1776
|
const version = resolveDigestVersion(offer);
|
|
1487
1777
|
const expected = Buffer.from(signOffer(offer, secret, version));
|
|
1488
1778
|
const actual = Buffer.from(offer.digest ?? "");
|
|
1489
1779
|
return expected.length === actual.length && timingSafeEqual(expected, actual);
|
|
1490
1780
|
}
|
|
1781
|
+
function withLegacySubscriptionSpelling(offer) {
|
|
1782
|
+
const railIsRecurring = offer.settlementRail === "recurring";
|
|
1783
|
+
const railsHaveRecurring = offer.acceptedRails?.includes("recurring") === true;
|
|
1784
|
+
if (!railIsRecurring && !railsHaveRecurring)
|
|
1785
|
+
return null;
|
|
1786
|
+
return {
|
|
1787
|
+
...offer,
|
|
1788
|
+
...railIsRecurring ? { settlementRail: "subscription" } : {},
|
|
1789
|
+
...railsHaveRecurring ? {
|
|
1790
|
+
acceptedRails: offer.acceptedRails.map((r) => r === "recurring" ? "subscription" : r)
|
|
1791
|
+
} : {}
|
|
1792
|
+
};
|
|
1793
|
+
}
|
|
1794
|
+
function verifyOfferDigest(offer, secret) {
|
|
1795
|
+
if (digestMatches(offer, secret))
|
|
1796
|
+
return true;
|
|
1797
|
+
const legacy = withLegacySubscriptionSpelling(offer);
|
|
1798
|
+
return legacy !== null && digestMatches(legacy, secret);
|
|
1799
|
+
}
|
|
1491
1800
|
function isExpired(offer, now = Date.now()) {
|
|
1492
1801
|
return now >= offer.expiresAt;
|
|
1493
1802
|
}
|
|
1803
|
+
function decodeSponsorGasPayment(encoded) {
|
|
1804
|
+
if (typeof encoded !== "string" || !encoded.trim()) {
|
|
1805
|
+
throw new Error("sponsorGasPayment is required");
|
|
1806
|
+
}
|
|
1807
|
+
return encoded.split(",").map((part) => {
|
|
1808
|
+
const bits = part.split("@");
|
|
1809
|
+
if (bits.length !== 3 || !bits[0] || !bits[1] || !bits[2]) {
|
|
1810
|
+
throw new Error(`malformed sponsorGasPayment segment: ${part}`);
|
|
1811
|
+
}
|
|
1812
|
+
return { objectId: bits[0], version: bits[1], digest: bits[2] };
|
|
1813
|
+
});
|
|
1814
|
+
}
|
|
1815
|
+
var SPONSOR_GAS_ADDRESS_BALANCE = "address-balance";
|
|
1816
|
+
function decodeSponsorGasPin(encoded) {
|
|
1817
|
+
if (encoded === SPONSOR_GAS_ADDRESS_BALANCE)
|
|
1818
|
+
return [];
|
|
1819
|
+
return decodeSponsorGasPayment(encoded);
|
|
1820
|
+
}
|
|
1821
|
+
var ADDRESS_BALANCE_VALIDITY_EPOCHS = 1n;
|
|
1822
|
+
function addressBalanceGasExpiration(input) {
|
|
1823
|
+
const chain = input.chainIdentifier.trim();
|
|
1824
|
+
if (!chain)
|
|
1825
|
+
throw new Error("address-balance gas needs a chain identifier");
|
|
1826
|
+
if (input.epoch < 0n)
|
|
1827
|
+
throw new Error("address-balance gas needs a non-negative epoch");
|
|
1828
|
+
if (!Number.isInteger(input.nonce) || input.nonce < 0 || input.nonce > 4294967295) {
|
|
1829
|
+
throw new Error("address-balance gas nonce must be a u32");
|
|
1830
|
+
}
|
|
1831
|
+
return {
|
|
1832
|
+
ValidDuring: {
|
|
1833
|
+
minEpoch: String(input.epoch),
|
|
1834
|
+
maxEpoch: String(input.epoch + ADDRESS_BALANCE_VALIDITY_EPOCHS),
|
|
1835
|
+
minTimestamp: null,
|
|
1836
|
+
maxTimestamp: null,
|
|
1837
|
+
chain,
|
|
1838
|
+
nonce: input.nonce
|
|
1839
|
+
}
|
|
1840
|
+
};
|
|
1841
|
+
}
|
|
1494
1842
|
|
|
1495
1843
|
// ../sdk/dist/protocol/mpp.js
|
|
1496
1844
|
function offerToBase64Url(offer) {
|
|
@@ -1501,7 +1849,66 @@ var MPP_METHOD = "sui.charge";
|
|
|
1501
1849
|
var MPP_CHALLENGE_HEADER = "WWW-Authenticate";
|
|
1502
1850
|
var MPP_CREDENTIAL_HEADER = "Authorization";
|
|
1503
1851
|
var MPP_RECEIPT_HEADER = "Payment-Receipt";
|
|
1504
|
-
function
|
|
1852
|
+
function splitChallenges(header) {
|
|
1853
|
+
const scheme = `${MPP_SCHEME} `;
|
|
1854
|
+
const value = header.trim();
|
|
1855
|
+
const starts = [];
|
|
1856
|
+
let inQuote = false;
|
|
1857
|
+
for (let i = 0; i < value.length; i++) {
|
|
1858
|
+
const ch = value[i];
|
|
1859
|
+
if (inQuote) {
|
|
1860
|
+
if (ch === "\\") {
|
|
1861
|
+
i++;
|
|
1862
|
+
continue;
|
|
1863
|
+
}
|
|
1864
|
+
if (ch === '"')
|
|
1865
|
+
inQuote = false;
|
|
1866
|
+
continue;
|
|
1867
|
+
}
|
|
1868
|
+
if (ch === '"') {
|
|
1869
|
+
inQuote = true;
|
|
1870
|
+
continue;
|
|
1871
|
+
}
|
|
1872
|
+
if (value.startsWith(scheme, i)) {
|
|
1873
|
+
let j = i - 1;
|
|
1874
|
+
while (j >= 0 && (value[j] === " " || value[j] === " "))
|
|
1875
|
+
j--;
|
|
1876
|
+
if (j < 0 || value[j] === ",")
|
|
1877
|
+
starts.push(i);
|
|
1878
|
+
}
|
|
1879
|
+
}
|
|
1880
|
+
if (starts.length === 0)
|
|
1881
|
+
return [];
|
|
1882
|
+
const parts = [];
|
|
1883
|
+
for (let k = 0; k < starts.length; k++) {
|
|
1884
|
+
const from = starts[k];
|
|
1885
|
+
const to = k + 1 < starts.length ? starts[k + 1] : value.length;
|
|
1886
|
+
parts.push(value.slice(from, to).replace(/,\s*$/, "").trim());
|
|
1887
|
+
}
|
|
1888
|
+
return parts;
|
|
1889
|
+
}
|
|
1890
|
+
function decodeChallenge(header, opts) {
|
|
1891
|
+
const parts = splitChallenges(header);
|
|
1892
|
+
if (parts.length <= 1) {
|
|
1893
|
+
return decodeSingleChallenge(header);
|
|
1894
|
+
}
|
|
1895
|
+
if (opts?.rail !== void 0) {
|
|
1896
|
+
for (const part of parts) {
|
|
1897
|
+
const offer = decodeSingleChallenge(part);
|
|
1898
|
+
if (offer && mintLiveRail(offer.settlementRail ?? "direct") === mintLiveRail(opts.rail)) {
|
|
1899
|
+
return offer;
|
|
1900
|
+
}
|
|
1901
|
+
}
|
|
1902
|
+
return null;
|
|
1903
|
+
}
|
|
1904
|
+
for (const part of parts) {
|
|
1905
|
+
const offer = decodeSingleChallenge(part);
|
|
1906
|
+
if (offer)
|
|
1907
|
+
return offer;
|
|
1908
|
+
}
|
|
1909
|
+
return null;
|
|
1910
|
+
}
|
|
1911
|
+
function decodeSingleChallenge(header) {
|
|
1505
1912
|
const parsed = parseAuthHeader(header);
|
|
1506
1913
|
if (!parsed || parsed.scheme !== MPP_SCHEME)
|
|
1507
1914
|
return null;
|
|
@@ -1549,13 +1956,14 @@ function decodeChallenge(header) {
|
|
|
1549
1956
|
offer.platformFee = p.platform_fee;
|
|
1550
1957
|
if (p.digest_version !== void 0) {
|
|
1551
1958
|
const v = Number(p.digest_version);
|
|
1552
|
-
if (v === 1 || v === 2 || v === 3)
|
|
1959
|
+
if (v === 1 || v === 2 || v === 3 || v === 4)
|
|
1553
1960
|
offer.digestVersion = v;
|
|
1554
1961
|
}
|
|
1555
1962
|
if (p.settlement_rail !== void 0) {
|
|
1556
|
-
|
|
1963
|
+
const rail = decodeWireRail(p.settlement_rail);
|
|
1964
|
+
if (!rail)
|
|
1557
1965
|
return null;
|
|
1558
|
-
offer.settlementRail =
|
|
1966
|
+
offer.settlementRail = rail;
|
|
1559
1967
|
}
|
|
1560
1968
|
if (p.service_id !== void 0)
|
|
1561
1969
|
offer.serviceId = p.service_id;
|
|
@@ -1632,6 +2040,21 @@ function decodeChallenge(header) {
|
|
|
1632
2040
|
if (p.sponsor_gas_payment !== void 0) {
|
|
1633
2041
|
offer.sponsorGasPayment = p.sponsor_gas_payment;
|
|
1634
2042
|
}
|
|
2043
|
+
if (p.accepted_rails !== void 0) {
|
|
2044
|
+
const rails = decodeAcceptedRails(p.accepted_rails.split(","));
|
|
2045
|
+
if (!rails)
|
|
2046
|
+
return null;
|
|
2047
|
+
offer.acceptedRails = rails;
|
|
2048
|
+
}
|
|
2049
|
+
if (p.exclusion_protocol_version !== void 0) {
|
|
2050
|
+
const raw = p.exclusion_protocol_version;
|
|
2051
|
+
const v = Number(raw);
|
|
2052
|
+
if (raw.trim() === "" || !Number.isFinite(v))
|
|
2053
|
+
return null;
|
|
2054
|
+
offer.exclusionProtocolVersion = v;
|
|
2055
|
+
}
|
|
2056
|
+
if (p.registry_identity !== void 0)
|
|
2057
|
+
offer.registryIdentity = p.registry_identity;
|
|
1635
2058
|
return offer;
|
|
1636
2059
|
}
|
|
1637
2060
|
function encodeProof(proof) {
|
|
@@ -1675,12 +2098,128 @@ function decodeReceipt(header) {
|
|
|
1675
2098
|
}
|
|
1676
2099
|
|
|
1677
2100
|
// ../sdk/dist/protocol/x402.js
|
|
2101
|
+
import { fromBase64 as fromBase643, toBase64 as toBase642 } from "@mysten/sui/utils";
|
|
2102
|
+
function utf8ToBase64(text2) {
|
|
2103
|
+
return toBase642(new TextEncoder().encode(text2));
|
|
2104
|
+
}
|
|
2105
|
+
function base64ToUtf8(value) {
|
|
2106
|
+
return new TextDecoder().decode(fromBase643(value));
|
|
2107
|
+
}
|
|
2108
|
+
var X402_VERSION = 2;
|
|
1678
2109
|
var X402_SCHEME = "exact";
|
|
2110
|
+
var X402_PAYMENT_FLOW = "upfront";
|
|
2111
|
+
var X402_ERROR_REASON = {
|
|
2112
|
+
MALFORMED_CREDENTIAL: "invalid_payload",
|
|
2113
|
+
UNKNOWN_CHALLENGE: "invalid_payment_requirements",
|
|
2114
|
+
CHALLENGE_TAMPERED: "invalid_payment_requirements",
|
|
2115
|
+
CHALLENGE_EXPIRED: "invalid_payment_requirements",
|
|
2116
|
+
CHALLENGE_ALREADY_USED: "invalid_transaction_state",
|
|
2117
|
+
CHALLENGE_IN_FLIGHT: "invalid_transaction_state",
|
|
2118
|
+
TRANSACTION_INVALID: "invalid_payload",
|
|
2119
|
+
NETWORK_MISMATCH: "invalid_network",
|
|
2120
|
+
SIGNATURE_INVALID: "invalid_payload",
|
|
2121
|
+
INSUFFICIENT_FUNDS: "insufficient_funds",
|
|
2122
|
+
PAYER_MISMATCH: "invalid_payload",
|
|
2123
|
+
SETTLEMENT_REJECTED: "invalid_transaction_state",
|
|
2124
|
+
SETTLEMENT_MISMATCH: "unexpected_settle_error",
|
|
2125
|
+
AMBIGUOUS: "settlement_pending"
|
|
2126
|
+
};
|
|
1679
2127
|
var X402_CHALLENGE_HEADER = "PAYMENT-REQUIRED";
|
|
2128
|
+
var X402_PAYMENT_HEADER = "PAYMENT-SIGNATURE";
|
|
1680
2129
|
var X402_RESPONSE_HEADER = "PAYMENT-RESPONSE";
|
|
1681
2130
|
var X402_RECEIPT_HEADER = "X-PAYMENT-RESPONSE";
|
|
2131
|
+
function toAccept(offer, authority, opts) {
|
|
2132
|
+
const auth = authority ?? defaultAuthorityForRail(offer.settlementRail ?? "direct", offer.packageId);
|
|
2133
|
+
const payFunction = auth.payFunction;
|
|
2134
|
+
const suipay = {
|
|
2135
|
+
challengeId: offer.challengeId,
|
|
2136
|
+
packageId: offer.packageId,
|
|
2137
|
+
module: auth.module,
|
|
2138
|
+
function: payFunction,
|
|
2139
|
+
decimals: offer.decimals,
|
|
2140
|
+
issuedAt: offer.issuedAt,
|
|
2141
|
+
expiresAt: offer.expiresAt,
|
|
2142
|
+
digest: offer.digest
|
|
2143
|
+
};
|
|
2144
|
+
if (offer.netAmount !== void 0)
|
|
2145
|
+
suipay.netAmount = offer.netAmount;
|
|
2146
|
+
if (offer.platformFee !== void 0)
|
|
2147
|
+
suipay.platformFee = offer.platformFee;
|
|
2148
|
+
if (offer.digestVersion !== void 0)
|
|
2149
|
+
suipay.digestVersion = offer.digestVersion;
|
|
2150
|
+
if (offer.settlementRail !== void 0)
|
|
2151
|
+
suipay.settlementRail = offer.settlementRail;
|
|
2152
|
+
if (offer.serviceId !== void 0)
|
|
2153
|
+
suipay.serviceId = offer.serviceId;
|
|
2154
|
+
if (offer.serviceRevision !== void 0)
|
|
2155
|
+
suipay.serviceRevision = offer.serviceRevision;
|
|
2156
|
+
if (offer.targetHash !== void 0)
|
|
2157
|
+
suipay.targetHash = offer.targetHash;
|
|
2158
|
+
if (offer.authorizationEpoch !== void 0) {
|
|
2159
|
+
suipay.authorizationEpoch = offer.authorizationEpoch;
|
|
2160
|
+
}
|
|
2161
|
+
if (offer.method !== void 0)
|
|
2162
|
+
suipay.method = offer.method;
|
|
2163
|
+
if (offer.path !== void 0)
|
|
2164
|
+
suipay.path = offer.path;
|
|
2165
|
+
if (offer.bodyHash !== void 0)
|
|
2166
|
+
suipay.bodyHash = offer.bodyHash;
|
|
2167
|
+
if (offer.contentType !== void 0)
|
|
2168
|
+
suipay.contentType = offer.contentType;
|
|
2169
|
+
if (offer.intent !== void 0)
|
|
2170
|
+
suipay.intent = offer.intent;
|
|
2171
|
+
if (offer.periodMs !== void 0)
|
|
2172
|
+
suipay.periodMs = offer.periodMs;
|
|
2173
|
+
if (offer.periodCount !== void 0)
|
|
2174
|
+
suipay.periodCount = offer.periodCount;
|
|
2175
|
+
if (offer.refundable !== void 0)
|
|
2176
|
+
suipay.refundable = offer.refundable;
|
|
2177
|
+
if (offer.refundWindowMs !== void 0)
|
|
2178
|
+
suipay.refundWindowMs = offer.refundWindowMs;
|
|
2179
|
+
if (offer.decideWindowMs !== void 0)
|
|
2180
|
+
suipay.decideWindowMs = offer.decideWindowMs;
|
|
2181
|
+
if (offer.chainId !== void 0)
|
|
2182
|
+
suipay.chainId = offer.chainId;
|
|
2183
|
+
if (offer.paymentSalt !== void 0)
|
|
2184
|
+
suipay.paymentSalt = offer.paymentSalt;
|
|
2185
|
+
if (offer.refundTo !== void 0)
|
|
2186
|
+
suipay.refundTo = offer.refundTo;
|
|
2187
|
+
if (offer.sponsorGasOwner !== void 0) {
|
|
2188
|
+
suipay.sponsorGasOwner = offer.sponsorGasOwner;
|
|
2189
|
+
}
|
|
2190
|
+
if (offer.sponsorGasBudget !== void 0) {
|
|
2191
|
+
suipay.sponsorGasBudget = offer.sponsorGasBudget;
|
|
2192
|
+
}
|
|
2193
|
+
if (offer.sponsorGasPrice !== void 0) {
|
|
2194
|
+
suipay.sponsorGasPrice = offer.sponsorGasPrice;
|
|
2195
|
+
}
|
|
2196
|
+
if (offer.sponsorGasPayment !== void 0) {
|
|
2197
|
+
suipay.sponsorGasPayment = offer.sponsorGasPayment;
|
|
2198
|
+
}
|
|
2199
|
+
if (offer.acceptedRails !== void 0)
|
|
2200
|
+
suipay.acceptedRails = offer.acceptedRails;
|
|
2201
|
+
if (offer.exclusionProtocolVersion !== void 0) {
|
|
2202
|
+
suipay.exclusionProtocolVersion = offer.exclusionProtocolVersion;
|
|
2203
|
+
}
|
|
2204
|
+
if (offer.registryIdentity !== void 0)
|
|
2205
|
+
suipay.registryIdentity = offer.registryIdentity;
|
|
2206
|
+
return {
|
|
2207
|
+
scheme: X402_SCHEME,
|
|
2208
|
+
network: offer.network,
|
|
2209
|
+
amount: offer.amount,
|
|
2210
|
+
asset: offer.asset,
|
|
2211
|
+
payTo: offer.payTo,
|
|
2212
|
+
maxTimeoutSeconds: Math.max(1, Math.round((offer.expiresAt - offer.issuedAt) / 1e3)),
|
|
2213
|
+
extra: {
|
|
2214
|
+
paymentFlow: X402_PAYMENT_FLOW,
|
|
2215
|
+
...opts?.gasStation ? { gasStation: opts.gasStation } : {},
|
|
2216
|
+
suipay
|
|
2217
|
+
}
|
|
2218
|
+
};
|
|
2219
|
+
}
|
|
1682
2220
|
function railOf(offer) {
|
|
1683
|
-
|
|
2221
|
+
const rail = offer.settlementRail ?? "direct";
|
|
2222
|
+
return mintLiveRail(rail);
|
|
1684
2223
|
}
|
|
1685
2224
|
function decodeChallenge2(body, opts) {
|
|
1686
2225
|
const b = body;
|
|
@@ -1691,7 +2230,7 @@ function decodeChallenge2(body, opts) {
|
|
|
1691
2230
|
if (a?.scheme !== X402_SCHEME || !a?.extra?.suipay)
|
|
1692
2231
|
continue;
|
|
1693
2232
|
const offer = fromAccept(a, b.resource);
|
|
1694
|
-
if (offer && railOf(offer) === opts.rail)
|
|
2233
|
+
if (offer && railOf(offer) === mintLiveRail(opts.rail))
|
|
1695
2234
|
return offer;
|
|
1696
2235
|
}
|
|
1697
2236
|
return null;
|
|
@@ -1726,13 +2265,14 @@ function fromAccept(accept, resource) {
|
|
|
1726
2265
|
offer.netAmount = s.netAmount;
|
|
1727
2266
|
if (s.platformFee !== void 0)
|
|
1728
2267
|
offer.platformFee = s.platformFee;
|
|
1729
|
-
if (s.digestVersion === 1 || s.digestVersion === 2 || s.digestVersion === 3) {
|
|
2268
|
+
if (s.digestVersion === 1 || s.digestVersion === 2 || s.digestVersion === 3 || s.digestVersion === 4) {
|
|
1730
2269
|
offer.digestVersion = s.digestVersion;
|
|
1731
2270
|
}
|
|
1732
2271
|
if (s.settlementRail !== void 0) {
|
|
1733
|
-
|
|
2272
|
+
const rail = decodeWireRail(s.settlementRail);
|
|
2273
|
+
if (!rail)
|
|
1734
2274
|
return null;
|
|
1735
|
-
offer.settlementRail =
|
|
2275
|
+
offer.settlementRail = rail;
|
|
1736
2276
|
}
|
|
1737
2277
|
if (s.serviceId !== void 0)
|
|
1738
2278
|
offer.serviceId = s.serviceId;
|
|
@@ -1784,11 +2324,71 @@ function fromAccept(accept, resource) {
|
|
|
1784
2324
|
if (s.sponsorGasPayment !== void 0) {
|
|
1785
2325
|
offer.sponsorGasPayment = s.sponsorGasPayment;
|
|
1786
2326
|
}
|
|
2327
|
+
if (s.acceptedRails !== void 0) {
|
|
2328
|
+
if (!Array.isArray(s.acceptedRails))
|
|
2329
|
+
return null;
|
|
2330
|
+
const rails = decodeAcceptedRails(s.acceptedRails);
|
|
2331
|
+
if (!rails)
|
|
2332
|
+
return null;
|
|
2333
|
+
offer.acceptedRails = rails;
|
|
2334
|
+
}
|
|
2335
|
+
if (s.exclusionProtocolVersion !== void 0) {
|
|
2336
|
+
const v = s.exclusionProtocolVersion;
|
|
2337
|
+
if (typeof v !== "number" || !Number.isFinite(v))
|
|
2338
|
+
return null;
|
|
2339
|
+
offer.exclusionProtocolVersion = v;
|
|
2340
|
+
}
|
|
2341
|
+
if (s.registryIdentity !== void 0)
|
|
2342
|
+
offer.registryIdentity = s.registryIdentity;
|
|
1787
2343
|
return offer;
|
|
1788
2344
|
}
|
|
2345
|
+
function encodeCredential(cred) {
|
|
2346
|
+
const payload = {
|
|
2347
|
+
x402Version: X402_VERSION,
|
|
2348
|
+
resource: {
|
|
2349
|
+
url: cred.offer.resource,
|
|
2350
|
+
description: cred.offer.description,
|
|
2351
|
+
mimeType: cred.offer.mimeType
|
|
2352
|
+
},
|
|
2353
|
+
accepted: toAccept(cred.offer),
|
|
2354
|
+
payload: {
|
|
2355
|
+
challengeId: cred.challengeId,
|
|
2356
|
+
payer: cred.payer ?? void 0,
|
|
2357
|
+
transaction: cred.txBytes,
|
|
2358
|
+
signature: cred.signature
|
|
2359
|
+
}
|
|
2360
|
+
};
|
|
2361
|
+
return utf8ToBase64(JSON.stringify(payload));
|
|
2362
|
+
}
|
|
2363
|
+
var X402_ERROR_REASONS = new Set(Object.values(X402_ERROR_REASON));
|
|
2364
|
+
function decodeSettlementResponse(header) {
|
|
2365
|
+
const receipt = decodeReceipt2(header);
|
|
2366
|
+
if (receipt)
|
|
2367
|
+
return receipt;
|
|
2368
|
+
try {
|
|
2369
|
+
const r = JSON.parse(base64ToUtf8(header));
|
|
2370
|
+
if (!r || typeof r !== "object" || r.success !== false)
|
|
2371
|
+
return null;
|
|
2372
|
+
if (typeof r.errorReason !== "string" || !X402_ERROR_REASONS.has(r.errorReason))
|
|
2373
|
+
return null;
|
|
2374
|
+
if (typeof r.network !== "string" || typeof r.transaction !== "string")
|
|
2375
|
+
return null;
|
|
2376
|
+
const failure = {
|
|
2377
|
+
success: false,
|
|
2378
|
+
errorReason: r.errorReason,
|
|
2379
|
+
transaction: r.transaction,
|
|
2380
|
+
network: r.network
|
|
2381
|
+
};
|
|
2382
|
+
if (typeof r.payer === "string")
|
|
2383
|
+
failure.payer = r.payer;
|
|
2384
|
+
return failure;
|
|
2385
|
+
} catch {
|
|
2386
|
+
return null;
|
|
2387
|
+
}
|
|
2388
|
+
}
|
|
1789
2389
|
function decodeReceipt2(header) {
|
|
1790
2390
|
try {
|
|
1791
|
-
const r = JSON.parse(
|
|
2391
|
+
const r = JSON.parse(base64ToUtf8(header));
|
|
1792
2392
|
if (!r?.success || !r.transaction)
|
|
1793
2393
|
return null;
|
|
1794
2394
|
const receipt = {
|
|
@@ -1813,22 +2413,22 @@ function decodeReceipt2(header) {
|
|
|
1813
2413
|
}
|
|
1814
2414
|
|
|
1815
2415
|
// ../sdk/dist/protocol/carrier.js
|
|
1816
|
-
async function readChallenge(res, dialect) {
|
|
2416
|
+
async function readChallenge(res, dialect, opts) {
|
|
1817
2417
|
if (dialect === "mpp") {
|
|
1818
2418
|
const header = res.headers.get(MPP_CHALLENGE_HEADER);
|
|
1819
|
-
return header ? decodeChallenge(header) : null;
|
|
2419
|
+
return header ? decodeChallenge(header, opts) : null;
|
|
1820
2420
|
}
|
|
1821
2421
|
const headerVal = res.headers.get(X402_CHALLENGE_HEADER);
|
|
1822
2422
|
if (headerVal) {
|
|
1823
2423
|
try {
|
|
1824
|
-
const parsed = JSON.parse(
|
|
1825
|
-
const offer = decodeChallenge2(parsed);
|
|
2424
|
+
const parsed = JSON.parse(new TextDecoder().decode(fromBase644(headerVal)));
|
|
2425
|
+
const offer = decodeChallenge2(parsed, opts);
|
|
1826
2426
|
if (offer)
|
|
1827
2427
|
return offer;
|
|
1828
2428
|
} catch {
|
|
1829
2429
|
}
|
|
1830
2430
|
}
|
|
1831
|
-
return decodeChallenge2(await readBody(res));
|
|
2431
|
+
return decodeChallenge2(await readBody(res), opts);
|
|
1832
2432
|
}
|
|
1833
2433
|
function readReceipt(res, dialect) {
|
|
1834
2434
|
const header = dialect === "mpp" ? res.headers.get(MPP_RECEIPT_HEADER) : res.headers.get(X402_RESPONSE_HEADER) ?? res.headers.get(X402_RECEIPT_HEADER);
|
|
@@ -1871,7 +2471,7 @@ async function readBody(res) {
|
|
|
1871
2471
|
configurable: false
|
|
1872
2472
|
});
|
|
1873
2473
|
if (bytes.byteLength <= MAX_INLINE_BINARY_BYTES) {
|
|
1874
|
-
binary.base64 =
|
|
2474
|
+
binary.base64 = toBase643(bytes);
|
|
1875
2475
|
}
|
|
1876
2476
|
return binary;
|
|
1877
2477
|
}
|
|
@@ -1923,10 +2523,10 @@ function requestBindingViolation(offer, request) {
|
|
|
1923
2523
|
if (offer.intent === "subscription" && offer.bodyHash === void 0 && offer.contentType === void 0) {
|
|
1924
2524
|
return null;
|
|
1925
2525
|
}
|
|
1926
|
-
if (offer.digestVersion
|
|
2526
|
+
if ((offer.digestVersion ?? 1) < 3) {
|
|
1927
2527
|
return {
|
|
1928
2528
|
code: "OFFER_NOT_BODY_BOUND",
|
|
1929
|
-
reason: "a paid POST requires an offer digest
|
|
2529
|
+
reason: "a paid POST requires an offer digest that signs the request body"
|
|
1930
2530
|
};
|
|
1931
2531
|
}
|
|
1932
2532
|
if (offer.bodyHash !== request.bodyHash) {
|
|
@@ -2157,24 +2757,74 @@ var PAY_OPERATION_ERROR_CODES = {
|
|
|
2157
2757
|
/** The payment ledger reserve CAS lost (another attempt owns this id). */
|
|
2158
2758
|
PAYMENT_ATTEMPT_IN_FLIGHT: "PAYMENT_ATTEMPT_IN_FLIGHT",
|
|
2159
2759
|
/** The payment ledger could not persist the reservation. */
|
|
2160
|
-
PAYMENT_LEDGER_FAILED: "PAYMENT_LEDGER_FAILED"
|
|
2760
|
+
PAYMENT_LEDGER_FAILED: "PAYMENT_LEDGER_FAILED",
|
|
2761
|
+
/** The offer's HMAC digest failed verification — tampered or wrong secret. */
|
|
2762
|
+
DIGEST_INVALID: "DIGEST_INVALID",
|
|
2763
|
+
/** The offer's expiry is in the past (or issuedAt/expiresAt are incoherent). */
|
|
2764
|
+
OFFER_EXPIRED: "OFFER_EXPIRED",
|
|
2765
|
+
/**
|
|
2766
|
+
* The offer's pinned sponsor gas fields are incomplete or inconsistent
|
|
2767
|
+
* (partial pin, non-numeric budget/price, empty payment) — reject before
|
|
2768
|
+
* building a tx against a gas pin that cannot be honored.
|
|
2769
|
+
*/
|
|
2770
|
+
SPONSOR_GAS_INVALID: "SPONSOR_GAS_INVALID",
|
|
2771
|
+
/**
|
|
2772
|
+
* The recipient / amount / payment_id embedded in the built transaction does
|
|
2773
|
+
* not match the offer — the pre-sign verify-by-rebuild backstop. Nothing may
|
|
2774
|
+
* be signed once this fires.
|
|
2775
|
+
*/
|
|
2776
|
+
TX_OFFER_MISMATCH: "TX_OFFER_MISMATCH",
|
|
2777
|
+
/**
|
|
2778
|
+
* Open-grant x402 is refused. This is PERMANENT and architectural: an open
|
|
2779
|
+
* grant does not pre-bind a recipient, so a buyer cannot verify the recipient
|
|
2780
|
+
* before signing (ADR-0002 / support matrix `open × x402` = unsupported). Do
|
|
2781
|
+
* not conflate with a rail that is merely not built yet.
|
|
2782
|
+
*/
|
|
2783
|
+
UNSUPPORTED_SCOPE: "UNSUPPORTED_SCOPE",
|
|
2784
|
+
/**
|
|
2785
|
+
* A rail/dialect combination that is SUPPORTED by design but not yet
|
|
2786
|
+
* implemented in this build — currently scoped-grant spend_account x402
|
|
2787
|
+
* (the buyer credential-submit flow). Distinct from UNSUPPORTED_SCOPE so an
|
|
2788
|
+
* integrator reads "coming, not yet wired" rather than "permanently refused".
|
|
2789
|
+
* See docs/decisions/ADR-0047-buyer-side-x402-two-paths-one-entrypoint.md.
|
|
2790
|
+
*/
|
|
2791
|
+
RAIL_NOT_YET_IMPLEMENTED: "RAIL_NOT_YET_IMPLEMENTED",
|
|
2792
|
+
/**
|
|
2793
|
+
* Grant rail is configured but required verify-kind RPC context (getObjectJson)
|
|
2794
|
+
* is missing — distinct from RAIL_NOT_YET_IMPLEMENTED, which means the rail
|
|
2795
|
+
* itself is not wired.
|
|
2796
|
+
*/
|
|
2797
|
+
GRANT_VERIFY_MISCONFIGURED: "GRANT_VERIFY_MISCONFIGURED",
|
|
2798
|
+
/** The request URL or method is invalid and cannot be normalized. */
|
|
2799
|
+
REQUEST_INVALID: "REQUEST_INVALID",
|
|
2800
|
+
/** The 402 response body stream failed while reading the challenge. */
|
|
2801
|
+
CHALLENGE_READ_ERROR: "CHALLENGE_READ_ERROR",
|
|
2802
|
+
/**
|
|
2803
|
+
* Pre-sign transaction verification could not reach a verdict due to an RPC
|
|
2804
|
+
* or chain read outage (retryable, not tampering).
|
|
2805
|
+
*/
|
|
2806
|
+
VERIFICATION_UNAVAILABLE: "VERIFICATION_UNAVAILABLE"
|
|
2161
2807
|
};
|
|
2162
2808
|
var CODE_SET = new Set(Object.values(PAY_OPERATION_ERROR_CODES));
|
|
2163
2809
|
|
|
2164
2810
|
// ../sdk/dist/pay-operation/operation.js
|
|
2165
2811
|
async function resolvePayment(input, deps) {
|
|
2166
2812
|
const { url, gatewayUrl, packageId, network, authority, assertLive } = input;
|
|
2167
|
-
const { fetchImpl = fetch, preferredDialect = "mpp", validateOffer, buildPayKind } = deps;
|
|
2813
|
+
const { fetchImpl = fetch, preferredDialect = "mpp", validateOffer, buildPayKind, resolveExclusionRegistry: resolveExclusionRegistry2 } = deps;
|
|
2168
2814
|
const request = input.request ?? normalizePaidHttpRequest({ url, method: "GET" });
|
|
2169
|
-
const
|
|
2170
|
-
if (
|
|
2171
|
-
return failed(PAY_OPERATION_ERROR_CODES.ORIGIN_MISMATCH,
|
|
2815
|
+
const originViolation2 = validateOrigin(url, gatewayUrl);
|
|
2816
|
+
if (originViolation2) {
|
|
2817
|
+
return failed(PAY_OPERATION_ERROR_CODES.ORIGIN_MISMATCH, originViolation2);
|
|
2172
2818
|
}
|
|
2173
2819
|
let response;
|
|
2174
|
-
|
|
2175
|
-
response =
|
|
2176
|
-
}
|
|
2177
|
-
|
|
2820
|
+
if (deps.prefetchedResponse) {
|
|
2821
|
+
response = deps.prefetchedResponse;
|
|
2822
|
+
} else {
|
|
2823
|
+
try {
|
|
2824
|
+
response = await fetchImpl(url, paidRequestInit(request));
|
|
2825
|
+
} catch (e) {
|
|
2826
|
+
return failed(PAY_OPERATION_ERROR_CODES.RESOURCE_UNAVAILABLE, `fetch failed: ${e instanceof Error ? e.message : String(e)}`);
|
|
2827
|
+
}
|
|
2178
2828
|
}
|
|
2179
2829
|
if (response.status !== 402) {
|
|
2180
2830
|
if (response.status >= 200 && response.status < 300) {
|
|
@@ -2203,7 +2853,7 @@ async function resolvePayment(input, deps) {
|
|
|
2203
2853
|
return failed(PAY_OPERATION_ERROR_CODES.REQUEST_BINDING_VIOLATED, bindingViolation.reason, challengeId);
|
|
2204
2854
|
}
|
|
2205
2855
|
if (!isDelegateRail(offer.settlementRail)) {
|
|
2206
|
-
return failed(PAY_OPERATION_ERROR_CODES.UNSUPPORTED_RAIL, `rail ${offer.settlementRail ?? "none"} is not a
|
|
2856
|
+
return failed(PAY_OPERATION_ERROR_CODES.UNSUPPORTED_RAIL, `rail ${offer.settlementRail ?? "none"} is not a delegate rail`, challengeId);
|
|
2207
2857
|
}
|
|
2208
2858
|
let packageMatches;
|
|
2209
2859
|
try {
|
|
@@ -2248,6 +2898,41 @@ async function resolvePayment(input, deps) {
|
|
|
2248
2898
|
recipient: resolved.authority.recipient,
|
|
2249
2899
|
accessMode: resolved.authority.accessMode
|
|
2250
2900
|
};
|
|
2901
|
+
let guarded;
|
|
2902
|
+
if (offerRequiresGuardedSettle(offer)) {
|
|
2903
|
+
const identity = parseRegistryIdentity(offer.registryIdentity);
|
|
2904
|
+
if (!identity) {
|
|
2905
|
+
return failed(PAY_OPERATION_ERROR_CODES.BUILD_FAILED, "guarded offer carries no valid registry identity", challengeId);
|
|
2906
|
+
}
|
|
2907
|
+
if (normalizeSuiAddress3(identity.packageId) !== normalizeSuiAddress3(offer.packageId)) {
|
|
2908
|
+
return failed(PAY_OPERATION_ERROR_CODES.BUILD_FAILED, `guarded registry identity package ${identity.packageId} does not match offer package ${offer.packageId}`, challengeId);
|
|
2909
|
+
}
|
|
2910
|
+
if (!resolveExclusionRegistry2) {
|
|
2911
|
+
return failed(PAY_OPERATION_ERROR_CODES.BUILD_FAILED, "a guarded delegate offer requires an exclusion registry resolver", challengeId);
|
|
2912
|
+
}
|
|
2913
|
+
let registryResolution;
|
|
2914
|
+
try {
|
|
2915
|
+
registryResolution = await resolveExclusionRegistry2({
|
|
2916
|
+
directoryId: identity.directoryId,
|
|
2917
|
+
packageId: offer.packageId,
|
|
2918
|
+
recipient: resolved.authority.recipient
|
|
2919
|
+
});
|
|
2920
|
+
} catch (e) {
|
|
2921
|
+
return failed(PAY_OPERATION_ERROR_CODES.BUILD_FAILED, e instanceof Error ? e.message : String(e), challengeId);
|
|
2922
|
+
}
|
|
2923
|
+
kindInput.exclusionDirectoryId = identity.directoryId;
|
|
2924
|
+
kindInput.recipientRegistryId = registryResolution.recipientRegistryId;
|
|
2925
|
+
kindInput.expiresAtMs = offer.expiresAt;
|
|
2926
|
+
kindInput.exclusionDirectoryInitialSharedVersion = registryResolution.directoryInitialSharedVersion;
|
|
2927
|
+
kindInput.recipientRegistryInitialSharedVersion = registryResolution.registryInitialSharedVersion;
|
|
2928
|
+
guarded = {
|
|
2929
|
+
exclusionDirectoryId: identity.directoryId,
|
|
2930
|
+
recipientRegistryId: registryResolution.recipientRegistryId,
|
|
2931
|
+
expiresAtMs: offer.expiresAt,
|
|
2932
|
+
exclusionDirectoryInitialSharedVersion: registryResolution.directoryInitialSharedVersion,
|
|
2933
|
+
recipientRegistryInitialSharedVersion: registryResolution.registryInitialSharedVersion
|
|
2934
|
+
};
|
|
2935
|
+
}
|
|
2251
2936
|
let kindBytes;
|
|
2252
2937
|
try {
|
|
2253
2938
|
const result = await buildPayKind(kindInput);
|
|
@@ -2264,7 +2949,8 @@ async function resolvePayment(input, deps) {
|
|
|
2264
2949
|
termsHash: tHash,
|
|
2265
2950
|
kindBytes,
|
|
2266
2951
|
paymentId: challengeId,
|
|
2267
|
-
request
|
|
2952
|
+
request,
|
|
2953
|
+
...guarded ? { guarded } : {}
|
|
2268
2954
|
};
|
|
2269
2955
|
}
|
|
2270
2956
|
function failed(code, detail, paymentId) {
|
|
@@ -2314,7 +3000,11 @@ function resolveAuthority(authority, offer) {
|
|
|
2314
3000
|
};
|
|
2315
3001
|
}
|
|
2316
3002
|
if (!offer.targetHash) {
|
|
2317
|
-
return {
|
|
3003
|
+
return {
|
|
3004
|
+
ok: false,
|
|
3005
|
+
code: PAY_OPERATION_ERROR_CODES.POLICY_REJECTED,
|
|
3006
|
+
reason: "offer has no targetHash for scoped resolution"
|
|
3007
|
+
};
|
|
2318
3008
|
}
|
|
2319
3009
|
const result = resolveGrantTarget(authority, offer.targetHash);
|
|
2320
3010
|
if (!result.ok) {
|
|
@@ -2337,8 +3027,8 @@ function resolveAuthority(authority, offer) {
|
|
|
2337
3027
|
};
|
|
2338
3028
|
}
|
|
2339
3029
|
function validateAmount(offer, resolved) {
|
|
2340
|
-
const offerCoinType =
|
|
2341
|
-
const grantCoinType =
|
|
3030
|
+
const offerCoinType = normalizeStructTag5(offer.asset);
|
|
3031
|
+
const grantCoinType = normalizeStructTag5(resolved.coinType);
|
|
2342
3032
|
if (offerCoinType !== grantCoinType) {
|
|
2343
3033
|
return {
|
|
2344
3034
|
code: PAY_OPERATION_ERROR_CODES.COIN_TYPE_MISMATCH,
|
|
@@ -2383,9 +3073,9 @@ function validateAmount(offer, resolved) {
|
|
|
2383
3073
|
// ../sdk/dist/sponsor/personal-message.js
|
|
2384
3074
|
import { randomBytes as randomBytes2 } from "crypto";
|
|
2385
3075
|
import { sha256 as sha2563 } from "@noble/hashes/sha2.js";
|
|
2386
|
-
import { bcs as
|
|
3076
|
+
import { bcs as bcs3 } from "@mysten/sui/bcs";
|
|
2387
3077
|
import { isValidPersonalMessageSignature, verifyPersonalMessageSignature } from "@mysten/sui/verify";
|
|
2388
|
-
import { fromBase64 as
|
|
3078
|
+
import { fromBase64 as fromBase645, normalizeSuiAddress as normalizeSuiAddress4, toBase64 as toBase644 } from "@mysten/sui/utils";
|
|
2389
3079
|
import { Transaction as Transaction3 } from "@mysten/sui/transactions";
|
|
2390
3080
|
var SPONSOR_PERSONAL_MESSAGE_DOMAIN = "suipay:sponsor-personal-message:v1";
|
|
2391
3081
|
function encodeSponsorPersonalMessagePayload(payload) {
|
|
@@ -2406,20 +3096,20 @@ function encodeSponsorPersonalMessagePayload(payload) {
|
|
|
2406
3096
|
throw new Error("network is required");
|
|
2407
3097
|
const parts = [];
|
|
2408
3098
|
const push = (u) => parts.push(u);
|
|
2409
|
-
push(
|
|
2410
|
-
push(
|
|
2411
|
-
push(
|
|
2412
|
-
push(
|
|
2413
|
-
push(
|
|
2414
|
-
push(
|
|
2415
|
-
push(
|
|
2416
|
-
push(
|
|
2417
|
-
push(
|
|
2418
|
-
push(
|
|
3099
|
+
push(bcs3.vector(bcs3.u8()).serialize([...Buffer.from(payload.domain, "utf8")]).toBytes());
|
|
3100
|
+
push(bcs3.vector(bcs3.u8()).serialize([...Buffer.from(payload.nonce, "utf8")]).toBytes());
|
|
3101
|
+
push(bcs3.u64().serialize(BigInt(payload.expiryMs)).toBytes());
|
|
3102
|
+
push(bcs3.Address.serialize(normalizeSuiAddress4(payload.delegate)).toBytes());
|
|
3103
|
+
push(bcs3.Address.serialize(normalizeSuiAddress4(payload.poolId)).toBytes());
|
|
3104
|
+
push(bcs3.Address.serialize(normalizeSuiAddress4(payload.grantId)).toBytes());
|
|
3105
|
+
push(bcs3.vector(bcs3.u8()).serialize([...Buffer.from(payload.network, "utf8")]).toBytes());
|
|
3106
|
+
push(bcs3.Address.serialize(normalizeSuiAddress4(payload.packageId)).toBytes());
|
|
3107
|
+
push(bcs3.vector(bcs3.u8()).serialize([...Buffer.from(payload.requestId, "utf8")]).toBytes());
|
|
3108
|
+
push(bcs3.vector(bcs3.u8()).serialize([...payload.txKindHash]).toBytes());
|
|
2419
3109
|
return Buffer.concat(parts.map((p) => Buffer.from(p)));
|
|
2420
3110
|
}
|
|
2421
3111
|
function hashTransactionKindBytes(kindBytes) {
|
|
2422
|
-
const raw = typeof kindBytes === "string" ?
|
|
3112
|
+
const raw = typeof kindBytes === "string" ? fromBase645(kindBytes) : kindBytes;
|
|
2423
3113
|
return sha2563(raw);
|
|
2424
3114
|
}
|
|
2425
3115
|
function buildSponsorPersonalMessagePayload(input) {
|
|
@@ -2444,7 +3134,7 @@ async function signSponsorPersonalMessagePayload(input) {
|
|
|
2444
3134
|
// ../sdk/dist/protocol/signed-request.js
|
|
2445
3135
|
import { sha256 as sha2564 } from "@noble/hashes/sha2.js";
|
|
2446
3136
|
import { bytesToHex as bytesToHex2 } from "@noble/hashes/utils.js";
|
|
2447
|
-
import { bcs as
|
|
3137
|
+
import { bcs as bcs4 } from "@mysten/sui/bcs";
|
|
2448
3138
|
import { messageWithIntent } from "@mysten/sui/cryptography";
|
|
2449
3139
|
import { isValidPersonalMessageSignature as isValidPersonalMessageSignature2, verifyPersonalMessageSignature as verifyPersonalMessageSignature2 } from "@mysten/sui/verify";
|
|
2450
3140
|
import { normalizeSuiAddress as normalizeSuiAddress5 } from "@mysten/sui/utils";
|
|
@@ -2478,8 +3168,8 @@ function encodeBootstrapRequest(payload) {
|
|
|
2478
3168
|
if (payload.domain !== BOOTSTRAP_REQUEST_DOMAIN) {
|
|
2479
3169
|
throw new Error(`domain must be ${BOOTSTRAP_REQUEST_DOMAIN}`);
|
|
2480
3170
|
}
|
|
2481
|
-
const utf8Vec = (s) =>
|
|
2482
|
-
const addr = (a) =>
|
|
3171
|
+
const utf8Vec = (s) => bcs4.vector(bcs4.u8()).serialize([...Buffer.from(s, "utf8")]).toBytes();
|
|
3172
|
+
const addr = (a) => bcs4.Address.serialize(normalizeSuiAddress5(a)).toBytes();
|
|
2483
3173
|
const parts = [
|
|
2484
3174
|
utf8Vec(payload.domain),
|
|
2485
3175
|
utf8Vec(payload.network),
|
|
@@ -2488,8 +3178,8 @@ function encodeBootstrapRequest(payload) {
|
|
|
2488
3178
|
utf8Vec(payload.method),
|
|
2489
3179
|
utf8Vec(payload.path),
|
|
2490
3180
|
utf8Vec(payload.query),
|
|
2491
|
-
|
|
2492
|
-
|
|
3181
|
+
bcs4.vector(bcs4.u8()).serialize([...hexToBytes32(payload.bodyHash)]).toBytes(),
|
|
3182
|
+
bcs4.u64().serialize(BigInt(payload.timestampMs)).toBytes(),
|
|
2493
3183
|
utf8Vec(payload.nonce)
|
|
2494
3184
|
];
|
|
2495
3185
|
return Buffer.concat(parts.map((p) => Buffer.from(p)));
|
|
@@ -2498,8 +3188,8 @@ function encodeSignedRequest(payload) {
|
|
|
2498
3188
|
if (payload.domain !== SIGNED_REQUEST_DOMAIN) {
|
|
2499
3189
|
throw new Error(`domain must be ${SIGNED_REQUEST_DOMAIN}`);
|
|
2500
3190
|
}
|
|
2501
|
-
const utf8Vec = (s) =>
|
|
2502
|
-
const addr = (a) =>
|
|
3191
|
+
const utf8Vec = (s) => bcs4.vector(bcs4.u8()).serialize([...Buffer.from(s, "utf8")]).toBytes();
|
|
3192
|
+
const addr = (a) => bcs4.Address.serialize(normalizeSuiAddress5(a)).toBytes();
|
|
2503
3193
|
const parts = [
|
|
2504
3194
|
utf8Vec(payload.domain),
|
|
2505
3195
|
utf8Vec(payload.network),
|
|
@@ -2509,8 +3199,8 @@ function encodeSignedRequest(payload) {
|
|
|
2509
3199
|
utf8Vec(payload.method),
|
|
2510
3200
|
utf8Vec(payload.path),
|
|
2511
3201
|
utf8Vec(payload.query),
|
|
2512
|
-
|
|
2513
|
-
|
|
3202
|
+
bcs4.vector(bcs4.u8()).serialize([...hexToBytes32(payload.bodyHash)]).toBytes(),
|
|
3203
|
+
bcs4.u64().serialize(BigInt(payload.timestampMs)).toBytes(),
|
|
2514
3204
|
utf8Vec(payload.nonce)
|
|
2515
3205
|
];
|
|
2516
3206
|
return Buffer.concat(parts.map((p) => Buffer.from(p)));
|
|
@@ -2869,7 +3559,7 @@ async function readPublicSessionState(getObjectJson, grantObjectId) {
|
|
|
2869
3559
|
const g = json;
|
|
2870
3560
|
return {
|
|
2871
3561
|
grantObjectId,
|
|
2872
|
-
// Field names are the Move struct's
|
|
3562
|
+
// Field names are the Move struct's; three were renamed:
|
|
2873
3563
|
// pool_id -> account_id, session_cap -> total_budget, session_spent ->
|
|
2874
3564
|
// total_spent. Reading the old names yielded undefined, which the grant
|
|
2875
3565
|
// verifier then read as "this grant names no pool" and refused every
|
|
@@ -2929,6 +3619,9 @@ var bootstrapPayerGrants = bootstrapPayerSessions;
|
|
|
2929
3619
|
// ../sdk/dist/delegated-signer/payer.js
|
|
2930
3620
|
var FINALIZATION_ATTEMPTS = 20;
|
|
2931
3621
|
var FINALIZATION_DELAY_MS = 250;
|
|
3622
|
+
var X402_EXPIRATION_EPOCHS = 32n;
|
|
3623
|
+
var X402_PRESENT_ATTEMPTS = 10;
|
|
3624
|
+
var X402_PRESENT_DELAY_MS = 1e3;
|
|
2932
3625
|
function delay(ms) {
|
|
2933
3626
|
return new Promise((resolve) => {
|
|
2934
3627
|
setTimeout(resolve, ms);
|
|
@@ -2996,7 +3689,16 @@ var DELEGATED_PAY_ERROR_CODES = {
|
|
|
2996
3689
|
* AGENT's own ceiling, and it is the check that does not need the gateway's
|
|
2997
3690
|
* secret to be meaningful.
|
|
2998
3691
|
*/
|
|
2999
|
-
SPEND_INTENT_VIOLATED: "SPEND_INTENT_VIOLATED"
|
|
3692
|
+
SPEND_INTENT_VIOLATED: "SPEND_INTENT_VIOLATED",
|
|
3693
|
+
/**
|
|
3694
|
+
* x402 was asked for and cannot be paid from the spend account: the 402
|
|
3695
|
+
* carried no x402 offer (the gateway strips it when it has no gas lease), a
|
|
3696
|
+
* sponsor gas pin is missing or unreadable, or no current epoch could be read
|
|
3697
|
+
* to bound the transaction. Nothing was reserved or signed.
|
|
3698
|
+
*/
|
|
3699
|
+
X402_UNAVAILABLE: "X402_UNAVAILABLE",
|
|
3700
|
+
/** The grant's scope cannot pay in the requested dialect (open-recipient over x402). */
|
|
3701
|
+
UNSUPPORTED_SCOPE: "UNSUPPORTED_SCOPE"
|
|
3000
3702
|
};
|
|
3001
3703
|
var DELEGATED_PAY_ERROR_CODE_SET = new Set(Object.values(DELEGATED_PAY_ERROR_CODES));
|
|
3002
3704
|
function isDelegatedPayErrorCode(value) {
|
|
@@ -3033,7 +3735,9 @@ var PUBLIC_DETAIL = {
|
|
|
3033
3735
|
FINALITY_UNAVAILABLE: "no finality reader configured; refusing to sign a pay whose settlement cannot be confirmed",
|
|
3034
3736
|
PREPARED_OFFER_DIVERGED: "settlement offer does not match the terms verified before signing",
|
|
3035
3737
|
PREPARED_VERIFY_UNAVAILABLE: "the pre-sign verification could not be completed; no signature was produced",
|
|
3036
|
-
SPEND_INTENT_VIOLATED: "offer does not match what this agent said it was buying"
|
|
3738
|
+
SPEND_INTENT_VIOLATED: "offer does not match what this agent said it was buying",
|
|
3739
|
+
X402_UNAVAILABLE: "offer cannot be paid over x402 from the spend account (no x402 offer, sponsor gas pin, or current epoch)",
|
|
3740
|
+
UNSUPPORTED_SCOPE: "open-recipient grants cannot pay over x402"
|
|
3037
3741
|
};
|
|
3038
3742
|
function classifyNon402Status(status) {
|
|
3039
3743
|
if (status >= 200 && status < 300)
|
|
@@ -3138,6 +3842,13 @@ function mapPayOpErrorToDelegated(code, detail) {
|
|
|
3138
3842
|
return { code: "RESOURCE_REJECTED", detail: PUBLIC_DETAIL.RESOURCE_REJECTED };
|
|
3139
3843
|
case PAY_OPERATION_ERROR_CODES.RESOURCE_UNAVAILABLE:
|
|
3140
3844
|
return { code: "RESOURCE_UNAVAILABLE", detail: PUBLIC_DETAIL.RESOURCE_UNAVAILABLE };
|
|
3845
|
+
case PAY_OPERATION_ERROR_CODES.DIGEST_INVALID:
|
|
3846
|
+
return { code: "CHALLENGE_TAMPERED", detail: PUBLIC_DETAIL.CHALLENGE_TAMPERED };
|
|
3847
|
+
case PAY_OPERATION_ERROR_CODES.OFFER_EXPIRED:
|
|
3848
|
+
return { code: "CHALLENGE_EXPIRED", detail: PUBLIC_DETAIL.CHALLENGE_EXPIRED };
|
|
3849
|
+
// SPONSOR_GAS_INVALID and TX_OFFER_MISMATCH fall through to POLICY_REJECTED
|
|
3850
|
+
// for now; the caller that surfaces them (the tx-build/sign path) picks
|
|
3851
|
+
// their final outward code when it lands.
|
|
3141
3852
|
// REQUEST_BINDING_VIOLATED, UNSUPPORTED_RAIL, PACKAGE_MISMATCH,
|
|
3142
3853
|
// NETWORK_MISMATCH, COIN_TYPE_MISMATCH, OVERPAY_REJECTED, UNDERPAY_REJECTED,
|
|
3143
3854
|
// ORIGIN_MISMATCH, and any unknown code all fall through to POLICY_REJECTED.
|
|
@@ -3145,7 +3856,7 @@ function mapPayOpErrorToDelegated(code, detail) {
|
|
|
3145
3856
|
return { code: "POLICY_REJECTED", detail: PUBLIC_DETAIL.POLICY_REJECTED };
|
|
3146
3857
|
}
|
|
3147
3858
|
}
|
|
3148
|
-
function
|
|
3859
|
+
function sameAddress2(a, b) {
|
|
3149
3860
|
try {
|
|
3150
3861
|
return normalizeSuiAddress6(a) === normalizeSuiAddress6(b);
|
|
3151
3862
|
} catch {
|
|
@@ -3161,7 +3872,7 @@ function atomicAmount(value) {
|
|
|
3161
3872
|
}
|
|
3162
3873
|
function sameStructTag(a, b) {
|
|
3163
3874
|
try {
|
|
3164
|
-
return
|
|
3875
|
+
return normalizeStructTag6(a) === normalizeStructTag6(b);
|
|
3165
3876
|
} catch {
|
|
3166
3877
|
return false;
|
|
3167
3878
|
}
|
|
@@ -3220,7 +3931,7 @@ async function resolveSigner(input) {
|
|
|
3220
3931
|
return {
|
|
3221
3932
|
address: kp.getPublicKey().toSuiAddress(),
|
|
3222
3933
|
async signTransaction(txBytes) {
|
|
3223
|
-
const { signature } = await kp.signTransaction(
|
|
3934
|
+
const { signature } = await kp.signTransaction(fromBase646(txBytes));
|
|
3224
3935
|
return signature;
|
|
3225
3936
|
},
|
|
3226
3937
|
async signPersonalMessage(message) {
|
|
@@ -3287,6 +3998,7 @@ async function buildSpendAccountPayKind(input) {
|
|
|
3287
3998
|
throw new Error("grantInitialSharedVersion is required");
|
|
3288
3999
|
}
|
|
3289
4000
|
const hash = targetHashBytes(input.targetHash);
|
|
4001
|
+
const guarded = resolveGuardedSettleOfflineInputs(input);
|
|
3290
4002
|
const target = payMoveCallTarget(input.packageId, "scoped");
|
|
3291
4003
|
const clockVersion = input.clockInitialSharedVersion?.trim() || SUI_CLOCK_INITIAL_SHARED_VERSION;
|
|
3292
4004
|
const tx = new Transaction4();
|
|
@@ -3295,6 +4007,16 @@ async function buildSpendAccountPayKind(input) {
|
|
|
3295
4007
|
target,
|
|
3296
4008
|
typeArguments: [input.coinType],
|
|
3297
4009
|
arguments: [
|
|
4010
|
+
tx.sharedObjectRef({
|
|
4011
|
+
objectId: guarded.exclusionDirectoryId,
|
|
4012
|
+
initialSharedVersion: guarded.exclusionDirectoryInitialSharedVersion,
|
|
4013
|
+
mutable: false
|
|
4014
|
+
}),
|
|
4015
|
+
tx.sharedObjectRef({
|
|
4016
|
+
objectId: guarded.recipientRegistryId,
|
|
4017
|
+
initialSharedVersion: guarded.recipientRegistryInitialSharedVersion,
|
|
4018
|
+
mutable: true
|
|
4019
|
+
}),
|
|
3298
4020
|
tx.sharedObjectRef({
|
|
3299
4021
|
objectId: input.poolObjectId,
|
|
3300
4022
|
initialSharedVersion: String(input.poolInitialSharedVersion),
|
|
@@ -3310,6 +4032,7 @@ async function buildSpendAccountPayKind(input) {
|
|
|
3310
4032
|
tx.pure.u64(BigInt(input.amount)),
|
|
3311
4033
|
tx.pure.vector("u8", Array.from(input.paymentIdHash)),
|
|
3312
4034
|
tx.pure.vector("u8", Array.from(input.termsHash)),
|
|
4035
|
+
tx.pure.u64(BigInt(guarded.expiresAtMs)),
|
|
3313
4036
|
tx.sharedObjectRef({
|
|
3314
4037
|
objectId: SUI_CLOCK2,
|
|
3315
4038
|
initialSharedVersion: clockVersion,
|
|
@@ -3318,7 +4041,7 @@ async function buildSpendAccountPayKind(input) {
|
|
|
3318
4041
|
]
|
|
3319
4042
|
});
|
|
3320
4043
|
const bytes = await tx.build({ onlyTransactionKind: true });
|
|
3321
|
-
return { kindBytes:
|
|
4044
|
+
return { kindBytes: toBase645(bytes) };
|
|
3322
4045
|
}
|
|
3323
4046
|
async function buildSpendAccountSettleOpenRecipientKind(input) {
|
|
3324
4047
|
if (!input.termsHash.length)
|
|
@@ -3333,9 +4056,10 @@ async function buildSpendAccountSettleOpenRecipientKind(input) {
|
|
|
3333
4056
|
throw new Error("grantInitialSharedVersion is required");
|
|
3334
4057
|
}
|
|
3335
4058
|
if (!input.recipient || /^0x0+$/i.test(input.recipient)) {
|
|
3336
|
-
throw new Error("
|
|
4059
|
+
throw new Error("settle_any_recipient recipient must be a non-zero address");
|
|
3337
4060
|
}
|
|
3338
4061
|
const hash = targetHashBytes(input.targetHash);
|
|
4062
|
+
const guarded = resolveGuardedSettleOfflineInputs(input);
|
|
3339
4063
|
const target = payMoveCallTarget(input.packageId, "open");
|
|
3340
4064
|
const clockVersion = input.clockInitialSharedVersion?.trim() || SUI_CLOCK_INITIAL_SHARED_VERSION;
|
|
3341
4065
|
const tx = new Transaction4();
|
|
@@ -3344,6 +4068,16 @@ async function buildSpendAccountSettleOpenRecipientKind(input) {
|
|
|
3344
4068
|
target,
|
|
3345
4069
|
typeArguments: [input.coinType],
|
|
3346
4070
|
arguments: [
|
|
4071
|
+
tx.sharedObjectRef({
|
|
4072
|
+
objectId: guarded.exclusionDirectoryId,
|
|
4073
|
+
initialSharedVersion: guarded.exclusionDirectoryInitialSharedVersion,
|
|
4074
|
+
mutable: false
|
|
4075
|
+
}),
|
|
4076
|
+
tx.sharedObjectRef({
|
|
4077
|
+
objectId: guarded.recipientRegistryId,
|
|
4078
|
+
initialSharedVersion: guarded.recipientRegistryInitialSharedVersion,
|
|
4079
|
+
mutable: true
|
|
4080
|
+
}),
|
|
3347
4081
|
tx.sharedObjectRef({
|
|
3348
4082
|
objectId: input.poolObjectId,
|
|
3349
4083
|
initialSharedVersion: String(input.poolInitialSharedVersion),
|
|
@@ -3359,6 +4093,7 @@ async function buildSpendAccountSettleOpenRecipientKind(input) {
|
|
|
3359
4093
|
tx.pure.vector("u8", Array.from(input.paymentIdHash)),
|
|
3360
4094
|
tx.pure.vector("u8", Array.from(input.termsHash)),
|
|
3361
4095
|
tx.pure.vector("u8", Array.from(hash)),
|
|
4096
|
+
tx.pure.u64(BigInt(guarded.expiresAtMs)),
|
|
3362
4097
|
tx.sharedObjectRef({
|
|
3363
4098
|
objectId: SUI_CLOCK2,
|
|
3364
4099
|
initialSharedVersion: clockVersion,
|
|
@@ -3367,14 +4102,14 @@ async function buildSpendAccountSettleOpenRecipientKind(input) {
|
|
|
3367
4102
|
]
|
|
3368
4103
|
});
|
|
3369
4104
|
const bytes = await tx.build({ onlyTransactionKind: true });
|
|
3370
|
-
return { kindBytes:
|
|
4105
|
+
return { kindBytes: toBase645(bytes) };
|
|
3371
4106
|
}
|
|
3372
4107
|
async function buildSpendAccountPayTx(input) {
|
|
3373
4108
|
if (!input.gas?.payment?.length) {
|
|
3374
4109
|
throw new Error("agent gas payment objects are required for executable pay");
|
|
3375
4110
|
}
|
|
3376
4111
|
const { kindBytes } = await buildSpendAccountPayKind(input);
|
|
3377
|
-
const tx = Transaction4.fromKind(
|
|
4112
|
+
const tx = Transaction4.fromKind(fromBase646(kindBytes));
|
|
3378
4113
|
tx.setSender(input.delegate);
|
|
3379
4114
|
tx.setGasOwner(input.gas.owner ?? input.delegate);
|
|
3380
4115
|
tx.setGasBudget(BigInt(input.gas.budget ?? DEFAULT_GAS_BUDGET));
|
|
@@ -3384,7 +4119,7 @@ async function buildSpendAccountPayTx(input) {
|
|
|
3384
4119
|
version: String(c.version),
|
|
3385
4120
|
digest: c.digest
|
|
3386
4121
|
})));
|
|
3387
|
-
const txBytes =
|
|
4122
|
+
const txBytes = toBase645(await tx.build());
|
|
3388
4123
|
return { txBytes, kindBytes };
|
|
3389
4124
|
}
|
|
3390
4125
|
async function buildSpendAccountSettleOpenRecipientTx(input) {
|
|
@@ -3392,7 +4127,7 @@ async function buildSpendAccountSettleOpenRecipientTx(input) {
|
|
|
3392
4127
|
throw new Error("agent gas payment objects are required for executable pay");
|
|
3393
4128
|
}
|
|
3394
4129
|
const { kindBytes } = await buildSpendAccountSettleOpenRecipientKind(input);
|
|
3395
|
-
const tx = Transaction4.fromKind(
|
|
4130
|
+
const tx = Transaction4.fromKind(fromBase646(kindBytes));
|
|
3396
4131
|
tx.setSender(input.delegate);
|
|
3397
4132
|
tx.setGasOwner(input.gas.owner ?? input.delegate);
|
|
3398
4133
|
tx.setGasBudget(BigInt(input.gas.budget ?? DEFAULT_GAS_BUDGET));
|
|
@@ -3402,23 +4137,76 @@ async function buildSpendAccountSettleOpenRecipientTx(input) {
|
|
|
3402
4137
|
version: String(c.version),
|
|
3403
4138
|
digest: c.digest
|
|
3404
4139
|
})));
|
|
3405
|
-
const txBytes =
|
|
4140
|
+
const txBytes = toBase645(await tx.build());
|
|
3406
4141
|
return { txBytes, kindBytes };
|
|
3407
4142
|
}
|
|
4143
|
+
function readX402GasPin(offer) {
|
|
4144
|
+
const owner = offer.sponsorGasOwner?.trim();
|
|
4145
|
+
const budget = atomicAmount(offer.sponsorGasBudget ?? "");
|
|
4146
|
+
const price = atomicAmount(offer.sponsorGasPrice ?? "");
|
|
4147
|
+
if (!owner || budget === null || price === null || budget <= 0n || price <= 0n) {
|
|
4148
|
+
return null;
|
|
4149
|
+
}
|
|
4150
|
+
try {
|
|
4151
|
+
return {
|
|
4152
|
+
owner,
|
|
4153
|
+
budget,
|
|
4154
|
+
price,
|
|
4155
|
+
payment: decodeSponsorGasPin(offer.sponsorGasPayment ?? "")
|
|
4156
|
+
};
|
|
4157
|
+
} catch {
|
|
4158
|
+
return null;
|
|
4159
|
+
}
|
|
4160
|
+
}
|
|
4161
|
+
async function buildSpendAccountX402PayTx(input) {
|
|
4162
|
+
const { kindBytes } = await buildSpendAccountPayKind(input);
|
|
4163
|
+
const tx = Transaction4.fromKind(fromBase646(kindBytes));
|
|
4164
|
+
tx.setSender(input.delegate);
|
|
4165
|
+
tx.setGasOwner(input.gas.owner);
|
|
4166
|
+
tx.setGasBudget(input.gas.budget);
|
|
4167
|
+
tx.setGasPrice(input.gas.price);
|
|
4168
|
+
tx.setGasPayment(input.gas.payment);
|
|
4169
|
+
if (input.gas.payment.length === 0) {
|
|
4170
|
+
if (!input.chainIdentifier) {
|
|
4171
|
+
throw new Error("address-balance sponsor gas needs a chain identifier");
|
|
4172
|
+
}
|
|
4173
|
+
tx.setExpiration(addressBalanceGasExpiration({
|
|
4174
|
+
epoch: input.currentEpoch,
|
|
4175
|
+
chainIdentifier: input.chainIdentifier,
|
|
4176
|
+
nonce: input.nonce ?? randomU32()
|
|
4177
|
+
}));
|
|
4178
|
+
} else {
|
|
4179
|
+
tx.setExpiration({ Epoch: String(input.currentEpoch + X402_EXPIRATION_EPOCHS) });
|
|
4180
|
+
}
|
|
4181
|
+
const txBytes = toBase645(await tx.build());
|
|
4182
|
+
return { txBytes, kindBytes };
|
|
4183
|
+
}
|
|
4184
|
+
function randomU32() {
|
|
4185
|
+
return globalThis.crypto.getRandomValues(new Uint32Array(1))[0];
|
|
4186
|
+
}
|
|
4187
|
+
function currentEpochReader(client) {
|
|
4188
|
+
return async () => {
|
|
4189
|
+
const { systemState } = await client.core.getCurrentSystemState();
|
|
4190
|
+
return BigInt(systemState.epoch);
|
|
4191
|
+
};
|
|
4192
|
+
}
|
|
4193
|
+
function chainIdentifierReader(client) {
|
|
4194
|
+
return async () => (await client.core.getChainIdentifier()).chainIdentifier;
|
|
4195
|
+
}
|
|
3408
4196
|
function assertPayTransaction(txBytes, expected) {
|
|
3409
4197
|
let data;
|
|
3410
4198
|
try {
|
|
3411
|
-
data = Transaction4.from(
|
|
4199
|
+
data = Transaction4.from(fromBase646(txBytes)).getData();
|
|
3412
4200
|
} catch {
|
|
3413
4201
|
throw new Error("transaction is not valid Sui transaction bytes");
|
|
3414
4202
|
}
|
|
3415
|
-
if (!data.sender || !
|
|
4203
|
+
if (!data.sender || !sameAddress2(data.sender, expected.delegate)) {
|
|
3416
4204
|
throw new Error("transaction sender does not match delegate");
|
|
3417
4205
|
}
|
|
3418
4206
|
if (!data.commands || data.commands.length !== 1) {
|
|
3419
4207
|
throw new Error("transaction must contain exactly one command");
|
|
3420
4208
|
}
|
|
3421
|
-
const wanted = payMoveCallTarget(expected.packageId, expected.accessMode);
|
|
4209
|
+
const wanted = payMoveCallTarget(expected.packageId, expected.accessMode, expected.guarded === true);
|
|
3422
4210
|
const cmd = data.commands[0];
|
|
3423
4211
|
if (cmd.$kind !== "MoveCall" || !cmd.MoveCall) {
|
|
3424
4212
|
throw new Error("transaction is not a MoveCall");
|
|
@@ -3429,7 +4217,7 @@ function assertPayTransaction(txBytes, expected) {
|
|
|
3429
4217
|
}
|
|
3430
4218
|
let expectedKind;
|
|
3431
4219
|
try {
|
|
3432
|
-
expectedKind = Transaction4.fromKind(
|
|
4220
|
+
expectedKind = Transaction4.fromKind(fromBase646(expected.kindBytes)).getData();
|
|
3433
4221
|
} catch {
|
|
3434
4222
|
throw new Error("intended pay kind baseline is invalid");
|
|
3435
4223
|
}
|
|
@@ -3450,9 +4238,10 @@ function assertPayTransaction(txBytes, expected) {
|
|
|
3450
4238
|
if (!expected.paymentIdHash.length || !expected.termsHash.length) {
|
|
3451
4239
|
throw new Error("expected paymentIdHash and termsHash must be non-empty");
|
|
3452
4240
|
}
|
|
4241
|
+
const expectedArgc = 11;
|
|
3453
4242
|
const args = cmd.MoveCall.arguments;
|
|
3454
|
-
if (args && args.length !==
|
|
3455
|
-
throw new Error(`pay expects
|
|
4243
|
+
if (args && args.length !== expectedArgc) {
|
|
4244
|
+
throw new Error(`pay expects ${expectedArgc} arguments; got ${args.length}`);
|
|
3456
4245
|
}
|
|
3457
4246
|
}
|
|
3458
4247
|
function asValidatedReceipt(value) {
|
|
@@ -3635,13 +4424,13 @@ function spendIntentViolation(offer, intent) {
|
|
|
3635
4424
|
const ceiling = atomicAmount(intent.maxAmount);
|
|
3636
4425
|
if (offered === null || ceiling === null || offered > ceiling)
|
|
3637
4426
|
return "amount";
|
|
3638
|
-
if (intent.recipient !== void 0 && !
|
|
4427
|
+
if (intent.recipient !== void 0 && !sameAddress2(offer.payTo, intent.recipient)) {
|
|
3639
4428
|
return "recipient";
|
|
3640
4429
|
}
|
|
3641
4430
|
return null;
|
|
3642
4431
|
}
|
|
3643
4432
|
function preparedTermsDivergence(offer, terms) {
|
|
3644
|
-
if (!
|
|
4433
|
+
if (!sameAddress2(offer.payTo, terms.recipient))
|
|
3645
4434
|
return "recipient";
|
|
3646
4435
|
const offered = atomicAmount(offer.amount);
|
|
3647
4436
|
const verified = atomicAmount(terms.amount);
|
|
@@ -3724,6 +4513,10 @@ async function payDelegatedSpendAccount(args) {
|
|
|
3724
4513
|
logInternal(logError, "resolve authority failed", err);
|
|
3725
4514
|
return failed2("GRANT_DENIED", PUBLIC_DETAIL.GRANT_DENIED);
|
|
3726
4515
|
}
|
|
4516
|
+
const x402Requested = args.dialect === "x402";
|
|
4517
|
+
if (x402Requested && snapshot.accessMode === "open") {
|
|
4518
|
+
return failed2("UNSUPPORTED_SCOPE", PUBLIC_DETAIL.UNSUPPORTED_SCOPE);
|
|
4519
|
+
}
|
|
3727
4520
|
let poolInitialSharedVersion;
|
|
3728
4521
|
let grantInitialSharedVersion;
|
|
3729
4522
|
let clockInitialSharedVersion;
|
|
@@ -3797,7 +4590,7 @@ async function payDelegatedSpendAccount(args) {
|
|
|
3797
4590
|
throw new Error("POLICY:no_target");
|
|
3798
4591
|
}
|
|
3799
4592
|
try {
|
|
3800
|
-
|
|
4593
|
+
normalizeStructTag6(offer2.asset);
|
|
3801
4594
|
} catch {
|
|
3802
4595
|
throw new Error("POLICY:bad_asset");
|
|
3803
4596
|
}
|
|
@@ -3817,7 +4610,15 @@ async function payDelegatedSpendAccount(args) {
|
|
|
3817
4610
|
amount: input.amount,
|
|
3818
4611
|
paymentIdHash: input.paymentIdHash,
|
|
3819
4612
|
termsHash: input.termsHash,
|
|
3820
|
-
recipient: input.recipient
|
|
4613
|
+
recipient: input.recipient,
|
|
4614
|
+
// Guarded settle inputs (undefined for an unguarded offer, which then
|
|
4615
|
+
// builds without the exclusion objects). resolvePayment set
|
|
4616
|
+
// these from the resolved exclusion registry.
|
|
4617
|
+
exclusionDirectoryId: input.exclusionDirectoryId,
|
|
4618
|
+
recipientRegistryId: input.recipientRegistryId,
|
|
4619
|
+
exclusionDirectoryInitialSharedVersion: input.exclusionDirectoryInitialSharedVersion,
|
|
4620
|
+
recipientRegistryInitialSharedVersion: input.recipientRegistryInitialSharedVersion,
|
|
4621
|
+
expiresAtMs: input.expiresAtMs
|
|
3821
4622
|
};
|
|
3822
4623
|
if (input.accessMode === "open") {
|
|
3823
4624
|
return buildSpendAccountSettleOpenRecipientKind(kindInput);
|
|
@@ -3825,7 +4626,7 @@ async function payDelegatedSpendAccount(args) {
|
|
|
3825
4626
|
return buildSpendAccountPayKind(kindInput);
|
|
3826
4627
|
};
|
|
3827
4628
|
try {
|
|
3828
|
-
|
|
4629
|
+
normalizeStructTag6(snapshot.coinType);
|
|
3829
4630
|
} catch {
|
|
3830
4631
|
return failed2("POLICY_REJECTED", PUBLIC_DETAIL.POLICY_REJECTED);
|
|
3831
4632
|
}
|
|
@@ -3839,22 +4640,37 @@ async function payDelegatedSpendAccount(args) {
|
|
|
3839
4640
|
};
|
|
3840
4641
|
const resolveDeps = {
|
|
3841
4642
|
fetchImpl: memoizedFetch,
|
|
3842
|
-
preferredDialect: "mpp",
|
|
4643
|
+
preferredDialect: x402Requested ? "x402" : "mpp",
|
|
3843
4644
|
validateOffer: validateOfferCallback,
|
|
3844
|
-
buildPayKind
|
|
4645
|
+
buildPayKind,
|
|
4646
|
+
// Guarded offers resolve the per-recipient exclusion registry
|
|
4647
|
+
// here; absent, resolvePayment fails closed (BUILD_FAILED) for such an offer
|
|
4648
|
+
// and never builds an unguarded settle. Unguarded offers never call it.
|
|
4649
|
+
resolveExclusionRegistry: deps.resolveExclusionRegistry
|
|
3845
4650
|
};
|
|
3846
4651
|
const resolution = await resolvePayment(resolveInput, resolveDeps);
|
|
3847
4652
|
if (resolution.status === "unpaid_ok") {
|
|
3848
4653
|
return { status: "ok", paid: false, body: resolution.body };
|
|
3849
4654
|
}
|
|
3850
4655
|
if (resolution.status === "failed") {
|
|
4656
|
+
logInternal(logError, "resolve payment failed", {
|
|
4657
|
+
code: resolution.code,
|
|
4658
|
+
detail: resolution.detail
|
|
4659
|
+
});
|
|
3851
4660
|
const mapped = mapPayOpErrorToDelegated(resolution.code, resolution.detail);
|
|
3852
4661
|
return failed2(mapped.code, mapped.detail, {
|
|
3853
4662
|
paymentId: resolution.paymentId
|
|
3854
4663
|
});
|
|
3855
4664
|
}
|
|
3856
|
-
const { offer, resolved: resolvedAuthority, kindBytes, paymentIdHash: payIdHash, termsHash: terms } = resolution;
|
|
4665
|
+
const { offer, resolved: resolvedAuthority, kindBytes, paymentIdHash: payIdHash, termsHash: terms, guarded: guardedResolution } = resolution;
|
|
3857
4666
|
const paymentId = resolution.paymentId;
|
|
4667
|
+
const guardedBuildFields = guardedResolution ? {
|
|
4668
|
+
exclusionDirectoryId: guardedResolution.exclusionDirectoryId,
|
|
4669
|
+
recipientRegistryId: guardedResolution.recipientRegistryId,
|
|
4670
|
+
exclusionDirectoryInitialSharedVersion: guardedResolution.exclusionDirectoryInitialSharedVersion,
|
|
4671
|
+
recipientRegistryInitialSharedVersion: guardedResolution.recipientRegistryInitialSharedVersion,
|
|
4672
|
+
expiresAtMs: guardedResolution.expiresAtMs
|
|
4673
|
+
} : {};
|
|
3858
4674
|
const isOpen = snapshot.accessMode === "open";
|
|
3859
4675
|
const resolved = {
|
|
3860
4676
|
poolObjectId: resolvedAuthority.poolObjectId,
|
|
@@ -3866,6 +4682,13 @@ async function payDelegatedSpendAccount(args) {
|
|
|
3866
4682
|
recipient: resolvedAuthority.recipient
|
|
3867
4683
|
};
|
|
3868
4684
|
const offerDigest = offer.digest.trim();
|
|
4685
|
+
let x402Terms = null;
|
|
4686
|
+
if (x402Requested) {
|
|
4687
|
+
x402Terms = await x402Preflight(resolution.dialect, offer, deps, logError);
|
|
4688
|
+
if (!x402Terms) {
|
|
4689
|
+
return failed2("X402_UNAVAILABLE", PUBLIC_DETAIL.X402_UNAVAILABLE, { paymentId });
|
|
4690
|
+
}
|
|
4691
|
+
}
|
|
3869
4692
|
const ledger = deps.paymentLedger;
|
|
3870
4693
|
if (!ledger || typeof ledger.reserve !== "function" || typeof ledger.complete !== "function") {
|
|
3871
4694
|
return failed2("PAYMENT_LEDGER_REQUIRED", PUBLIC_DETAIL.PAYMENT_LEDGER_REQUIRED, { paymentId });
|
|
@@ -3882,7 +4705,9 @@ async function payDelegatedSpendAccount(args) {
|
|
|
3882
4705
|
if (reserveOutcome.state === "done") {
|
|
3883
4706
|
const prior = asValidatedLedgerResult(reserveOutcome.prior, paymentId);
|
|
3884
4707
|
if (!prior) {
|
|
3885
|
-
return ambiguous("PAYMENT_ATTEMPT_IN_FLIGHT", PUBLIC_DETAIL.PAYMENT_ATTEMPT_IN_FLIGHT, {
|
|
4708
|
+
return ambiguous("PAYMENT_ATTEMPT_IN_FLIGHT", PUBLIC_DETAIL.PAYMENT_ATTEMPT_IN_FLIGHT, {
|
|
4709
|
+
paymentId
|
|
4710
|
+
});
|
|
3886
4711
|
}
|
|
3887
4712
|
return {
|
|
3888
4713
|
...prior,
|
|
@@ -3892,10 +4717,14 @@ async function payDelegatedSpendAccount(args) {
|
|
|
3892
4717
|
};
|
|
3893
4718
|
}
|
|
3894
4719
|
if (reserveOutcome.state === "in_flight") {
|
|
3895
|
-
return ambiguous("PAYMENT_ATTEMPT_IN_FLIGHT", PUBLIC_DETAIL.PAYMENT_ATTEMPT_IN_FLIGHT, {
|
|
4720
|
+
return ambiguous("PAYMENT_ATTEMPT_IN_FLIGHT", PUBLIC_DETAIL.PAYMENT_ATTEMPT_IN_FLIGHT, {
|
|
4721
|
+
paymentId
|
|
4722
|
+
});
|
|
3896
4723
|
}
|
|
3897
4724
|
if (reserveOutcome.state !== "reserved") {
|
|
3898
|
-
logInternal(logError, "payment attempt ledger reserve returned unexpected state", {
|
|
4725
|
+
logInternal(logError, "payment attempt ledger reserve returned unexpected state", {
|
|
4726
|
+
state: reserveOutcome.state
|
|
4727
|
+
});
|
|
3899
4728
|
return failed2("PAYMENT_LEDGER_FAILED", PUBLIC_DETAIL.PAYMENT_LEDGER_FAILED, {
|
|
3900
4729
|
paymentId
|
|
3901
4730
|
});
|
|
@@ -3905,27 +4734,6 @@ async function payDelegatedSpendAccount(args) {
|
|
|
3905
4734
|
await releaseReservation(ledger, ownedPaymentId, logError);
|
|
3906
4735
|
return failed2(code, detail, { paymentId });
|
|
3907
4736
|
};
|
|
3908
|
-
const gasMode = resolveGasMode(deps);
|
|
3909
|
-
const execute = deps.executeTx;
|
|
3910
|
-
if (gasMode === "none") {
|
|
3911
|
-
return abortBeforeSign("EXECUTE_UNAVAILABLE", PUBLIC_DETAIL.EXECUTE_UNAVAILABLE);
|
|
3912
|
-
}
|
|
3913
|
-
if (gasMode === "agent" && !execute) {
|
|
3914
|
-
return abortBeforeSign("EXECUTE_UNAVAILABLE", PUBLIC_DETAIL.EXECUTE_UNAVAILABLE);
|
|
3915
|
-
}
|
|
3916
|
-
if (typeof deps.getFinalizedTx !== "function") {
|
|
3917
|
-
return abortBeforeSign("FINALITY_UNAVAILABLE", PUBLIC_DETAIL.FINALITY_UNAVAILABLE);
|
|
3918
|
-
}
|
|
3919
|
-
let signer;
|
|
3920
|
-
try {
|
|
3921
|
-
signer = await resolveSigner(session.signer);
|
|
3922
|
-
} catch (err) {
|
|
3923
|
-
logInternal(logError, "resolve signer failed", err);
|
|
3924
|
-
return abortBeforeSign("SIGN_FAILED", PUBLIC_DETAIL.SIGN_FAILED);
|
|
3925
|
-
}
|
|
3926
|
-
if (!sameAddress(signer.address, resolved.delegateAddress)) {
|
|
3927
|
-
return abortBeforeSign("POLICY_REJECTED", PUBLIC_DETAIL.POLICY_REJECTED);
|
|
3928
|
-
}
|
|
3929
4737
|
const kindBuildInput = {
|
|
3930
4738
|
packageId: session.packageId,
|
|
3931
4739
|
delegate: resolved.delegateAddress,
|
|
@@ -3940,8 +4748,70 @@ async function payDelegatedSpendAccount(args) {
|
|
|
3940
4748
|
amount: offer.amount,
|
|
3941
4749
|
paymentIdHash: payIdHash,
|
|
3942
4750
|
termsHash: terms,
|
|
3943
|
-
recipient: resolved.recipient
|
|
4751
|
+
recipient: resolved.recipient,
|
|
4752
|
+
...guardedBuildFields
|
|
4753
|
+
};
|
|
4754
|
+
const expectedPay = {
|
|
4755
|
+
packageId: session.packageId,
|
|
4756
|
+
delegate: resolved.delegateAddress,
|
|
4757
|
+
poolObjectId: resolved.poolObjectId,
|
|
4758
|
+
grantObjectId: resolved.grantObjectId,
|
|
4759
|
+
policyId: resolved.onChainPolicyId,
|
|
4760
|
+
targetHash: resolved.targetHash,
|
|
4761
|
+
amount: offer.amount,
|
|
4762
|
+
paymentIdHash: payIdHash,
|
|
4763
|
+
termsHash: terms,
|
|
4764
|
+
coinType: resolved.coinType,
|
|
4765
|
+
kindBytes,
|
|
4766
|
+
accessMode: snapshot.accessMode,
|
|
4767
|
+
recipient: resolved.recipient,
|
|
4768
|
+
guarded: guardedResolution !== void 0
|
|
3944
4769
|
};
|
|
4770
|
+
if (x402Terms) {
|
|
4771
|
+
return payX402Reserved({
|
|
4772
|
+
session,
|
|
4773
|
+
deps,
|
|
4774
|
+
fetchImpl,
|
|
4775
|
+
resourceUrl,
|
|
4776
|
+
request,
|
|
4777
|
+
ledger,
|
|
4778
|
+
paymentId,
|
|
4779
|
+
offer,
|
|
4780
|
+
expected: expectedPay,
|
|
4781
|
+
buildInput: {
|
|
4782
|
+
...kindBuildInput,
|
|
4783
|
+
gas: x402Terms.gas,
|
|
4784
|
+
currentEpoch: x402Terms.currentEpoch,
|
|
4785
|
+
...x402Terms.chainIdentifier !== void 0 ? { chainIdentifier: x402Terms.chainIdentifier } : {}
|
|
4786
|
+
},
|
|
4787
|
+
settlement: {
|
|
4788
|
+
status: "success",
|
|
4789
|
+
amount: offer.amount,
|
|
4790
|
+
recipient: resolved.recipient,
|
|
4791
|
+
coinType: resolved.coinType,
|
|
4792
|
+
asset: offer.asset,
|
|
4793
|
+
grantObjectId: resolved.grantObjectId,
|
|
4794
|
+
poolObjectId: resolved.poolObjectId
|
|
4795
|
+
},
|
|
4796
|
+
onProgress: args.onX402Progress,
|
|
4797
|
+
abort: abortBeforeSign
|
|
4798
|
+
});
|
|
4799
|
+
}
|
|
4800
|
+
const gasMode = resolveGasMode(deps);
|
|
4801
|
+
const execute = deps.executeTx;
|
|
4802
|
+
if (gasMode === "none") {
|
|
4803
|
+
return abortBeforeSign("EXECUTE_UNAVAILABLE", PUBLIC_DETAIL.EXECUTE_UNAVAILABLE);
|
|
4804
|
+
}
|
|
4805
|
+
if (gasMode === "agent" && !execute) {
|
|
4806
|
+
return abortBeforeSign("EXECUTE_UNAVAILABLE", PUBLIC_DETAIL.EXECUTE_UNAVAILABLE);
|
|
4807
|
+
}
|
|
4808
|
+
if (typeof deps.getSettledTx !== "function") {
|
|
4809
|
+
return abortBeforeSign("FINALITY_UNAVAILABLE", PUBLIC_DETAIL.FINALITY_UNAVAILABLE);
|
|
4810
|
+
}
|
|
4811
|
+
const signer = await resolveDelegateSigner(session.signer, resolved.delegateAddress, logError);
|
|
4812
|
+
if (typeof signer === "string") {
|
|
4813
|
+
return abortBeforeSign(signer, PUBLIC_DETAIL[signer]);
|
|
4814
|
+
}
|
|
3945
4815
|
let txBytes;
|
|
3946
4816
|
let sponsoredExecuteToken;
|
|
3947
4817
|
let sponsoredCreateDigest;
|
|
@@ -4015,7 +4885,7 @@ async function payDelegatedSpendAccount(args) {
|
|
|
4015
4885
|
return abortBeforeSign("GRANT_DENIED", grantDeniedDetail(err));
|
|
4016
4886
|
}
|
|
4017
4887
|
}
|
|
4018
|
-
const allowedPayTarget = payMoveCallTarget(session.packageId, snapshot.accessMode);
|
|
4888
|
+
const allowedPayTarget = payMoveCallTarget(session.packageId, snapshot.accessMode, guardedResolution !== void 0);
|
|
4019
4889
|
try {
|
|
4020
4890
|
const createRes = await fetchSponsor(`${base}/api/sponsor`, {
|
|
4021
4891
|
method: "POST",
|
|
@@ -4080,21 +4950,7 @@ async function payDelegatedSpendAccount(args) {
|
|
|
4080
4950
|
}
|
|
4081
4951
|
}
|
|
4082
4952
|
try {
|
|
4083
|
-
assertPayTransaction(txBytes,
|
|
4084
|
-
packageId: session.packageId,
|
|
4085
|
-
delegate: resolved.delegateAddress,
|
|
4086
|
-
poolObjectId: resolved.poolObjectId,
|
|
4087
|
-
grantObjectId: resolved.grantObjectId,
|
|
4088
|
-
policyId: resolved.onChainPolicyId,
|
|
4089
|
-
targetHash: resolved.targetHash,
|
|
4090
|
-
amount: offer.amount,
|
|
4091
|
-
paymentIdHash: payIdHash,
|
|
4092
|
-
termsHash: terms,
|
|
4093
|
-
coinType: resolved.coinType,
|
|
4094
|
-
kindBytes,
|
|
4095
|
-
accessMode: snapshot.accessMode,
|
|
4096
|
-
recipient: resolved.recipient
|
|
4097
|
-
});
|
|
4953
|
+
assertPayTransaction(txBytes, expectedPay);
|
|
4098
4954
|
} catch (err) {
|
|
4099
4955
|
logInternal(logError, "pre-sign pay validation failed", err);
|
|
4100
4956
|
return abortBeforeSign(gasMode === "sponsored" ? "SPONSORED_TX_MISMATCH" : "TX_MISMATCH", gasMode === "sponsored" ? PUBLIC_DETAIL.SPONSORED_TX_MISMATCH : PUBLIC_DETAIL.TX_MISMATCH);
|
|
@@ -4166,7 +5022,7 @@ async function payDelegatedSpendAccount(args) {
|
|
|
4166
5022
|
txDigest: errDigest ?? finalDigest
|
|
4167
5023
|
}), logError);
|
|
4168
5024
|
}
|
|
4169
|
-
const getFinalized = deps.
|
|
5025
|
+
const getFinalized = deps.getSettledTx;
|
|
4170
5026
|
let finalized = null;
|
|
4171
5027
|
try {
|
|
4172
5028
|
for (let attempt = 0; attempt < FINALIZATION_ATTEMPTS; attempt += 1) {
|
|
@@ -4177,7 +5033,7 @@ async function payDelegatedSpendAccount(args) {
|
|
|
4177
5033
|
await delay(FINALIZATION_DELAY_MS);
|
|
4178
5034
|
}
|
|
4179
5035
|
} catch (err) {
|
|
4180
|
-
logInternal(logError, "
|
|
5036
|
+
logInternal(logError, "getSettledTx threw after broadcast", err);
|
|
4181
5037
|
return completeAttempt(ledger, paymentId, ambiguous("FINALITY_AMBIGUOUS", PUBLIC_DETAIL.FINALITY_AMBIGUOUS, {
|
|
4182
5038
|
paymentId,
|
|
4183
5039
|
txDigest: finalDigest
|
|
@@ -4232,10 +5088,18 @@ async function payDelegatedSpendAccount(args) {
|
|
|
4232
5088
|
second = await fetchImpl(resourceUrl, paidRequestInit(request, { [MPP_CREDENTIAL_HEADER]: proof }));
|
|
4233
5089
|
} catch (err) {
|
|
4234
5090
|
logInternal(logError, "proof presentation failed", err);
|
|
4235
|
-
return completeAttempt(ledger, paymentId, ambiguous("PROOF_PRESENTATION_FAILED", PUBLIC_DETAIL.PROOF_PRESENTATION_FAILED, {
|
|
5091
|
+
return completeAttempt(ledger, paymentId, ambiguous("PROOF_PRESENTATION_FAILED", PUBLIC_DETAIL.PROOF_PRESENTATION_FAILED, {
|
|
5092
|
+
paymentId,
|
|
5093
|
+
txDigest: finalDigest,
|
|
5094
|
+
settlement
|
|
5095
|
+
}), logError);
|
|
4236
5096
|
}
|
|
4237
5097
|
if (second.status === 503) {
|
|
4238
|
-
return completeAttempt(ledger, paymentId, ambiguous(gatewayRefusedCode(second, logError), PUBLIC_DETAIL.PROOF_PRESENTATION_FAILED, {
|
|
5098
|
+
return completeAttempt(ledger, paymentId, ambiguous(gatewayRefusedCode(second, logError), PUBLIC_DETAIL.PROOF_PRESENTATION_FAILED, {
|
|
5099
|
+
paymentId,
|
|
5100
|
+
txDigest: finalDigest,
|
|
5101
|
+
settlement
|
|
5102
|
+
}), logError);
|
|
4239
5103
|
}
|
|
4240
5104
|
let receipt = null;
|
|
4241
5105
|
try {
|
|
@@ -4244,14 +5108,22 @@ async function payDelegatedSpendAccount(args) {
|
|
|
4244
5108
|
logInternal(logError, "read receipt failed", err);
|
|
4245
5109
|
}
|
|
4246
5110
|
if (second.status !== 200) {
|
|
4247
|
-
return completeAttempt(ledger, paymentId, ambiguous(gatewayRefusedCode(second, logError), PUBLIC_DETAIL.GATEWAY_REFUSED, {
|
|
5111
|
+
return completeAttempt(ledger, paymentId, ambiguous(gatewayRefusedCode(second, logError), PUBLIC_DETAIL.GATEWAY_REFUSED, {
|
|
5112
|
+
paymentId,
|
|
5113
|
+
txDigest: finalDigest,
|
|
5114
|
+
settlement
|
|
5115
|
+
}), logError);
|
|
4248
5116
|
}
|
|
4249
5117
|
let body;
|
|
4250
5118
|
try {
|
|
4251
5119
|
body = await readBody(second);
|
|
4252
5120
|
} catch (err) {
|
|
4253
5121
|
logInternal(logError, "read delivery body failed", err);
|
|
4254
|
-
return completeAttempt(ledger, paymentId, ambiguous("PROOF_PRESENTATION_FAILED", PUBLIC_DETAIL.PROOF_PRESENTATION_FAILED, {
|
|
5122
|
+
return completeAttempt(ledger, paymentId, ambiguous("PROOF_PRESENTATION_FAILED", PUBLIC_DETAIL.PROOF_PRESENTATION_FAILED, {
|
|
5123
|
+
paymentId,
|
|
5124
|
+
txDigest: finalDigest,
|
|
5125
|
+
settlement
|
|
5126
|
+
}), logError);
|
|
4255
5127
|
}
|
|
4256
5128
|
return completeAttempt(ledger, paymentId, {
|
|
4257
5129
|
status: "ok",
|
|
@@ -4263,6 +5135,195 @@ async function payDelegatedSpendAccount(args) {
|
|
|
4263
5135
|
settlement
|
|
4264
5136
|
}, logError);
|
|
4265
5137
|
}
|
|
5138
|
+
async function resolveDelegateSigner(input, delegateAddress, logError) {
|
|
5139
|
+
let signer;
|
|
5140
|
+
try {
|
|
5141
|
+
signer = await resolveSigner(input);
|
|
5142
|
+
} catch (err) {
|
|
5143
|
+
logInternal(logError, "resolve signer failed", err);
|
|
5144
|
+
return "SIGN_FAILED";
|
|
5145
|
+
}
|
|
5146
|
+
return sameAddress2(signer.address, delegateAddress) ? signer : "POLICY_REJECTED";
|
|
5147
|
+
}
|
|
5148
|
+
async function x402Preflight(dialect, offer, deps, logError) {
|
|
5149
|
+
if (dialect !== "x402") {
|
|
5150
|
+
logInternal(logError, "x402 requested but the 402 carried no x402 offer");
|
|
5151
|
+
return null;
|
|
5152
|
+
}
|
|
5153
|
+
const gas = readX402GasPin(offer);
|
|
5154
|
+
if (!gas) {
|
|
5155
|
+
logInternal(logError, "x402 offer lacks readable sponsor gas pins");
|
|
5156
|
+
return null;
|
|
5157
|
+
}
|
|
5158
|
+
if (typeof deps.getCurrentEpoch !== "function") {
|
|
5159
|
+
logInternal(logError, "x402 needs a current-epoch reader");
|
|
5160
|
+
return null;
|
|
5161
|
+
}
|
|
5162
|
+
let currentEpoch;
|
|
5163
|
+
try {
|
|
5164
|
+
currentEpoch = BigInt(await deps.getCurrentEpoch());
|
|
5165
|
+
} catch (err) {
|
|
5166
|
+
logInternal(logError, "read current epoch failed", err);
|
|
5167
|
+
return null;
|
|
5168
|
+
}
|
|
5169
|
+
if (gas.payment.length > 0)
|
|
5170
|
+
return { gas, currentEpoch };
|
|
5171
|
+
if (typeof deps.getChainIdentifier !== "function") {
|
|
5172
|
+
logInternal(logError, "address-balance x402 gas needs a chain-identifier reader");
|
|
5173
|
+
return null;
|
|
5174
|
+
}
|
|
5175
|
+
try {
|
|
5176
|
+
const chainIdentifier = await deps.getChainIdentifier();
|
|
5177
|
+
if (!chainIdentifier.trim())
|
|
5178
|
+
throw new Error("chain identifier is blank");
|
|
5179
|
+
return { gas, currentEpoch, chainIdentifier };
|
|
5180
|
+
} catch (err) {
|
|
5181
|
+
logInternal(logError, "read chain identifier failed", err);
|
|
5182
|
+
return null;
|
|
5183
|
+
}
|
|
5184
|
+
}
|
|
5185
|
+
function emitX402Progress(ctx, event) {
|
|
5186
|
+
if (!ctx.onProgress)
|
|
5187
|
+
return;
|
|
5188
|
+
try {
|
|
5189
|
+
ctx.onProgress(event);
|
|
5190
|
+
} catch (err) {
|
|
5191
|
+
logInternal(ctx.deps.logError, "x402 progress callback threw", err);
|
|
5192
|
+
}
|
|
5193
|
+
}
|
|
5194
|
+
async function payX402Reserved(ctx) {
|
|
5195
|
+
const logError = ctx.deps.logError;
|
|
5196
|
+
const signer = await resolveDelegateSigner(ctx.session.signer, ctx.expected.delegate, logError);
|
|
5197
|
+
if (typeof signer === "string")
|
|
5198
|
+
return ctx.abort(signer, PUBLIC_DETAIL[signer]);
|
|
5199
|
+
let txBytes;
|
|
5200
|
+
try {
|
|
5201
|
+
const build = ctx.deps.buildX402PayTx ?? buildSpendAccountX402PayTx;
|
|
5202
|
+
txBytes = (await build(ctx.buildInput)).txBytes;
|
|
5203
|
+
} catch (err) {
|
|
5204
|
+
logInternal(logError, "x402 pay build failed", err);
|
|
5205
|
+
return ctx.abort("BUILD_FAILED", PUBLIC_DETAIL.BUILD_FAILED);
|
|
5206
|
+
}
|
|
5207
|
+
try {
|
|
5208
|
+
assertPayTransaction(txBytes, ctx.expected);
|
|
5209
|
+
} catch (err) {
|
|
5210
|
+
logInternal(logError, "pre-sign x402 pay validation failed", err);
|
|
5211
|
+
return ctx.abort("TX_MISMATCH", PUBLIC_DETAIL.TX_MISMATCH);
|
|
5212
|
+
}
|
|
5213
|
+
emitX402Progress(ctx, { stage: "built", txBytes });
|
|
5214
|
+
if (ctx.session.assertLive) {
|
|
5215
|
+
try {
|
|
5216
|
+
await ctx.session.assertLive();
|
|
5217
|
+
} catch (err) {
|
|
5218
|
+
logInternal(logError, "assertLive failed at sign", err);
|
|
5219
|
+
return ctx.abort("GRANT_DENIED", grantDeniedDetail(err));
|
|
5220
|
+
}
|
|
5221
|
+
}
|
|
5222
|
+
let signature;
|
|
5223
|
+
try {
|
|
5224
|
+
signature = await signer.signTransaction(txBytes);
|
|
5225
|
+
} catch (err) {
|
|
5226
|
+
logInternal(logError, "delegate sign failed", err);
|
|
5227
|
+
return ctx.abort("SIGN_FAILED", PUBLIC_DETAIL.SIGN_FAILED);
|
|
5228
|
+
}
|
|
5229
|
+
emitX402Progress(ctx, { stage: "signed", signature });
|
|
5230
|
+
let header;
|
|
5231
|
+
try {
|
|
5232
|
+
header = encodeCredential({
|
|
5233
|
+
challengeId: ctx.offer.challengeId,
|
|
5234
|
+
offer: ctx.offer,
|
|
5235
|
+
txBytes,
|
|
5236
|
+
signature,
|
|
5237
|
+
payer: ctx.expected.delegate
|
|
5238
|
+
});
|
|
5239
|
+
} catch (err) {
|
|
5240
|
+
logInternal(logError, "encode x402 credential failed", err);
|
|
5241
|
+
return ctx.abort("BUILD_FAILED", PUBLIC_DETAIL.BUILD_FAILED);
|
|
5242
|
+
}
|
|
5243
|
+
return recordX402Answer(ctx, await presentX402Credential(ctx, header));
|
|
5244
|
+
}
|
|
5245
|
+
function isX402Failure(value) {
|
|
5246
|
+
return value.success === false;
|
|
5247
|
+
}
|
|
5248
|
+
async function presentX402Credential(ctx, header) {
|
|
5249
|
+
const attempts = ctx.deps.x402PresentAttempts ?? X402_PRESENT_ATTEMPTS;
|
|
5250
|
+
const sleep = ctx.deps.sleep ?? delay;
|
|
5251
|
+
let txDigest;
|
|
5252
|
+
for (let attempt = 1; attempt <= attempts; attempt += 1) {
|
|
5253
|
+
emitX402Progress(ctx, { stage: "presented", header, attempt });
|
|
5254
|
+
let response;
|
|
5255
|
+
try {
|
|
5256
|
+
response = await ctx.fetchImpl(ctx.resourceUrl, paidRequestInit(ctx.request, { [X402_PAYMENT_HEADER]: header }));
|
|
5257
|
+
} catch (err) {
|
|
5258
|
+
logInternal(ctx.deps.logError, "x402 credential presentation failed", err);
|
|
5259
|
+
return { kind: "unresolved", txDigest };
|
|
5260
|
+
}
|
|
5261
|
+
const paymentResponse = response.headers.get(X402_RESPONSE_HEADER);
|
|
5262
|
+
emitX402Progress(ctx, {
|
|
5263
|
+
stage: "answered",
|
|
5264
|
+
status: response.status,
|
|
5265
|
+
paymentResponse,
|
|
5266
|
+
attempt
|
|
5267
|
+
});
|
|
5268
|
+
const decoded = paymentResponse ? decodeSettlementResponse(paymentResponse) : null;
|
|
5269
|
+
if (decoded && isX402Failure(decoded) && decoded.transaction.trim()) {
|
|
5270
|
+
txDigest = decoded.transaction.trim();
|
|
5271
|
+
}
|
|
5272
|
+
if (response.status !== 503) {
|
|
5273
|
+
return classifyX402Answer(response, decoded, {
|
|
5274
|
+
challengeId: ctx.offer.challengeId,
|
|
5275
|
+
txDigest,
|
|
5276
|
+
firstPresentation: attempt === 1
|
|
5277
|
+
});
|
|
5278
|
+
}
|
|
5279
|
+
if (attempt < attempts)
|
|
5280
|
+
await sleep(X402_PRESENT_DELAY_MS);
|
|
5281
|
+
}
|
|
5282
|
+
return { kind: "unresolved", txDigest };
|
|
5283
|
+
}
|
|
5284
|
+
function classifyX402Answer(response, decoded, seen) {
|
|
5285
|
+
const receipt = asValidatedReceipt(decoded);
|
|
5286
|
+
if (response.ok && receipt && receipt.challengeId === seen.challengeId) {
|
|
5287
|
+
return { kind: "settled", receipt, response };
|
|
5288
|
+
}
|
|
5289
|
+
if (seen.firstPresentation && response.status === 402 && decoded && isX402Failure(decoded) && !decoded.transaction.trim()) {
|
|
5290
|
+
return { kind: "refused", response };
|
|
5291
|
+
}
|
|
5292
|
+
return { kind: "unresolved", txDigest: seen.txDigest };
|
|
5293
|
+
}
|
|
5294
|
+
async function recordX402Answer(ctx, answer) {
|
|
5295
|
+
const { ledger, paymentId } = ctx;
|
|
5296
|
+
const logError = ctx.deps.logError;
|
|
5297
|
+
if (answer.kind === "unresolved") {
|
|
5298
|
+
return completeAttempt(ledger, paymentId, ambiguous("EXECUTE_AMBIGUOUS", PUBLIC_DETAIL.EXECUTE_AMBIGUOUS, {
|
|
5299
|
+
paymentId,
|
|
5300
|
+
txDigest: answer.txDigest
|
|
5301
|
+
}), logError);
|
|
5302
|
+
}
|
|
5303
|
+
if (answer.kind === "refused") {
|
|
5304
|
+
return ctx.abort(gatewayRefusedCode(answer.response, logError), PUBLIC_DETAIL.GATEWAY_REFUSED);
|
|
5305
|
+
}
|
|
5306
|
+
const settlement = {
|
|
5307
|
+
...ctx.settlement,
|
|
5308
|
+
txDigest: answer.receipt.txDigest
|
|
5309
|
+
};
|
|
5310
|
+
let body;
|
|
5311
|
+
try {
|
|
5312
|
+
body = await readBody(answer.response);
|
|
5313
|
+
} catch (err) {
|
|
5314
|
+
logInternal(logError, "read delivery body failed", err);
|
|
5315
|
+
return completeAttempt(ledger, paymentId, ambiguous("PROOF_PRESENTATION_FAILED", PUBLIC_DETAIL.PROOF_PRESENTATION_FAILED, { paymentId, txDigest: settlement.txDigest, settlement }), logError);
|
|
5316
|
+
}
|
|
5317
|
+
return completeAttempt(ledger, paymentId, {
|
|
5318
|
+
status: "ok",
|
|
5319
|
+
paid: true,
|
|
5320
|
+
body,
|
|
5321
|
+
receipt: answer.receipt,
|
|
5322
|
+
txDigest: settlement.txDigest,
|
|
5323
|
+
paymentId,
|
|
5324
|
+
settlement
|
|
5325
|
+
}, logError);
|
|
5326
|
+
}
|
|
4266
5327
|
function extractDigestFromError(err) {
|
|
4267
5328
|
if (!err || typeof err !== "object")
|
|
4268
5329
|
return void 0;
|
|
@@ -4279,9 +5340,35 @@ function settleOpenRecipientTarget(packageId) {
|
|
|
4279
5340
|
const fn = spendAccountDebitFunction({ accessMode: "open" });
|
|
4280
5341
|
return `${normalizeSuiAddress6(packageId)}::spend_account::${fn}`;
|
|
4281
5342
|
}
|
|
4282
|
-
function payMoveCallTarget(packageId, accessMode = "scoped") {
|
|
5343
|
+
function payMoveCallTarget(packageId, accessMode = "scoped", _guarded = false) {
|
|
4283
5344
|
return accessMode === "open" ? settleOpenRecipientTarget(packageId) : payTarget(packageId);
|
|
4284
5345
|
}
|
|
5346
|
+
function resolveGuardedSettleOfflineInputs(input) {
|
|
5347
|
+
const core = [input.exclusionDirectoryId, input.recipientRegistryId, input.expiresAtMs];
|
|
5348
|
+
const present = core.filter((v) => v !== void 0).length;
|
|
5349
|
+
if (present !== 3) {
|
|
5350
|
+
throw new Error("guarded settle requires exclusionDirectoryId, recipientRegistryId and expiresAtMs together (all or none)");
|
|
5351
|
+
}
|
|
5352
|
+
const expires = BigInt(input.expiresAtMs);
|
|
5353
|
+
if (expires <= 0n) {
|
|
5354
|
+
throw new Error("guarded settle expiresAtMs must be a positive epoch-ms value");
|
|
5355
|
+
}
|
|
5356
|
+
if (/^0x0+$/i.test(input.exclusionDirectoryId) || /^0x0+$/i.test(input.recipientRegistryId)) {
|
|
5357
|
+
throw new Error("guarded settle directory and registry ids must be non-zero");
|
|
5358
|
+
}
|
|
5359
|
+
const dirVer = input.exclusionDirectoryInitialSharedVersion;
|
|
5360
|
+
const regVer = input.recipientRegistryInitialSharedVersion;
|
|
5361
|
+
if (!dirVer || !String(dirVer).trim() || !regVer || !String(regVer).trim()) {
|
|
5362
|
+
throw new Error("guarded offline settle requires directory and registry initial shared versions");
|
|
5363
|
+
}
|
|
5364
|
+
return {
|
|
5365
|
+
exclusionDirectoryId: input.exclusionDirectoryId,
|
|
5366
|
+
recipientRegistryId: input.recipientRegistryId,
|
|
5367
|
+
exclusionDirectoryInitialSharedVersion: String(dirVer),
|
|
5368
|
+
recipientRegistryInitialSharedVersion: String(regVer),
|
|
5369
|
+
expiresAtMs: input.expiresAtMs
|
|
5370
|
+
};
|
|
5371
|
+
}
|
|
4285
5372
|
var PreparedVerificationUnavailableError = class extends Error {
|
|
4286
5373
|
code = DELEGATED_PAY_ERROR_CODES.PREPARED_VERIFY_UNAVAILABLE;
|
|
4287
5374
|
constructor(message, options) {
|
|
@@ -4299,17 +5386,42 @@ function preparedPureBytes(input) {
|
|
|
4299
5386
|
if (!input || input.$kind !== "Pure" || !input.Pure?.bytes)
|
|
4300
5387
|
return null;
|
|
4301
5388
|
try {
|
|
4302
|
-
return
|
|
5389
|
+
return fromBase646(input.Pure.bytes);
|
|
4303
5390
|
} catch {
|
|
4304
5391
|
return null;
|
|
4305
5392
|
}
|
|
4306
5393
|
}
|
|
5394
|
+
function preparedPureVectorU8(input) {
|
|
5395
|
+
const raw = preparedPureBytes(input);
|
|
5396
|
+
try {
|
|
5397
|
+
return Uint8Array.from(bcs5.vector(bcs5.u8()).parse(raw));
|
|
5398
|
+
} catch {
|
|
5399
|
+
}
|
|
5400
|
+
return null;
|
|
5401
|
+
}
|
|
5402
|
+
function sameBytes(a, b) {
|
|
5403
|
+
return Buffer.from(a).equals(Buffer.from(b));
|
|
5404
|
+
}
|
|
5405
|
+
function toHashBytes(val) {
|
|
5406
|
+
if (typeof val === "string") {
|
|
5407
|
+
const clean = val.startsWith("0x") ? val.slice(2) : val;
|
|
5408
|
+
return Uint8Array.from(Buffer.from(clean, "hex"));
|
|
5409
|
+
}
|
|
5410
|
+
return val;
|
|
5411
|
+
}
|
|
5412
|
+
function toTargetHashBytes(val) {
|
|
5413
|
+
if (typeof val === "string") {
|
|
5414
|
+
const clean = val.startsWith("0x") ? val.slice(2) : val;
|
|
5415
|
+
return targetHashBytes(clean);
|
|
5416
|
+
}
|
|
5417
|
+
return val;
|
|
5418
|
+
}
|
|
4307
5419
|
function preparedPureAddress(input) {
|
|
4308
5420
|
const raw = preparedPureBytes(input);
|
|
4309
5421
|
if (!raw)
|
|
4310
5422
|
return null;
|
|
4311
5423
|
try {
|
|
4312
|
-
return normalizeSuiAddress6(
|
|
5424
|
+
return normalizeSuiAddress6(bcs5.Address.parse(raw));
|
|
4313
5425
|
} catch {
|
|
4314
5426
|
return null;
|
|
4315
5427
|
}
|
|
@@ -4319,7 +5431,7 @@ function preparedPureU64(input) {
|
|
|
4319
5431
|
if (!raw)
|
|
4320
5432
|
return null;
|
|
4321
5433
|
try {
|
|
4322
|
-
return BigInt(
|
|
5434
|
+
return BigInt(bcs5.u64().parse(raw));
|
|
4323
5435
|
} catch {
|
|
4324
5436
|
return null;
|
|
4325
5437
|
}
|
|
@@ -4339,9 +5451,12 @@ function preparedSharedObjectId(input) {
|
|
|
4339
5451
|
}
|
|
4340
5452
|
async function verifyPreparedSettlementKind(input) {
|
|
4341
5453
|
const accessMode = input.expected.accessMode ?? "open";
|
|
5454
|
+
if (accessMode !== "scoped" && accessMode !== "open") {
|
|
5455
|
+
throw new Error(`unsupported accessMode: ${String(accessMode)}`);
|
|
5456
|
+
}
|
|
4342
5457
|
let data;
|
|
4343
5458
|
try {
|
|
4344
|
-
data = Transaction4.fromKind(
|
|
5459
|
+
data = Transaction4.fromKind(fromBase646(input.kindBytes)).getData();
|
|
4345
5460
|
} catch {
|
|
4346
5461
|
throw new Error("prepared transaction is not a valid settlement kind");
|
|
4347
5462
|
}
|
|
@@ -4365,16 +5480,25 @@ async function verifyPreparedSettlementKind(input) {
|
|
|
4365
5480
|
return void 0;
|
|
4366
5481
|
return inputs[idx];
|
|
4367
5482
|
};
|
|
4368
|
-
const
|
|
5483
|
+
const poolObjectId = preparedSharedObjectId(resolve(2));
|
|
5484
|
+
if (input.expected.poolObjectId) {
|
|
5485
|
+
if (!poolObjectId || !sameAddress2(poolObjectId, input.expected.poolObjectId)) {
|
|
5486
|
+
throw new Error(`prepared pool ${poolObjectId ?? "(missing)"} does not match expected pool ${input.expected.poolObjectId}`);
|
|
5487
|
+
}
|
|
5488
|
+
}
|
|
5489
|
+
const grantObjectId = preparedSharedObjectId(resolve(3));
|
|
4369
5490
|
if (!grantObjectId) {
|
|
4370
5491
|
throw new Error("prepared transaction does not name a grant object");
|
|
4371
5492
|
}
|
|
5493
|
+
if (input.expected.grantObjectId && !sameAddress2(grantObjectId, input.expected.grantObjectId)) {
|
|
5494
|
+
throw new Error(`prepared grant ${grantObjectId} does not match expected grant ${input.expected.grantObjectId}`);
|
|
5495
|
+
}
|
|
4372
5496
|
if (accessMode === "open") {
|
|
4373
|
-
const recipient = preparedPureAddress(resolve(
|
|
4374
|
-
if (!recipient || !
|
|
5497
|
+
const recipient = preparedPureAddress(resolve(4));
|
|
5498
|
+
if (!recipient || !sameAddress2(recipient, input.expected.recipient)) {
|
|
4375
5499
|
throw new Error(`prepared recipient ${recipient ?? "(missing)"} does not match the requested recipient`);
|
|
4376
5500
|
}
|
|
4377
|
-
const amount = preparedPureU64(resolve(
|
|
5501
|
+
const amount = preparedPureU64(resolve(5));
|
|
4378
5502
|
let expectedAmount;
|
|
4379
5503
|
try {
|
|
4380
5504
|
expectedAmount = BigInt(input.expected.amount);
|
|
@@ -4385,7 +5509,21 @@ async function verifyPreparedSettlementKind(input) {
|
|
|
4385
5509
|
throw new Error(`prepared amount ${amount ?? "(missing)"} does not match the requested amount`);
|
|
4386
5510
|
}
|
|
4387
5511
|
} else {
|
|
4388
|
-
|
|
5512
|
+
if (input.expected.onChainPolicyId !== void 0) {
|
|
5513
|
+
const policyId = preparedPureU64(resolve(4));
|
|
5514
|
+
const expectedPolicyId = BigInt(input.expected.onChainPolicyId);
|
|
5515
|
+
if (policyId !== expectedPolicyId) {
|
|
5516
|
+
throw new Error(`prepared policy_id ${policyId ?? "(missing)"} does not match expected onChainPolicyId`);
|
|
5517
|
+
}
|
|
5518
|
+
}
|
|
5519
|
+
if (input.expected.targetHash) {
|
|
5520
|
+
const targetHash = preparedPureVectorU8(resolve(5));
|
|
5521
|
+
const expectedBytes = toTargetHashBytes(input.expected.targetHash);
|
|
5522
|
+
if (!targetHash || !sameBytes(targetHash, expectedBytes)) {
|
|
5523
|
+
throw new Error("prepared target_hash does not match expected targetHash");
|
|
5524
|
+
}
|
|
5525
|
+
}
|
|
5526
|
+
const amount = preparedPureU64(resolve(6));
|
|
4389
5527
|
let expectedAmount;
|
|
4390
5528
|
try {
|
|
4391
5529
|
expectedAmount = BigInt(input.expected.amount);
|
|
@@ -4395,6 +5533,20 @@ async function verifyPreparedSettlementKind(input) {
|
|
|
4395
5533
|
if (amount === null || amount !== expectedAmount) {
|
|
4396
5534
|
throw new Error(`prepared amount ${amount ?? "(missing)"} does not match the requested amount`);
|
|
4397
5535
|
}
|
|
5536
|
+
if (input.expected.paymentIdHash) {
|
|
5537
|
+
const paymentIdHash2 = preparedPureVectorU8(resolve(7));
|
|
5538
|
+
const expectedBytes = toHashBytes(input.expected.paymentIdHash);
|
|
5539
|
+
if (!paymentIdHash2 || !sameBytes(paymentIdHash2, expectedBytes)) {
|
|
5540
|
+
throw new Error("prepared payment_id_hash does not match expected paymentIdHash");
|
|
5541
|
+
}
|
|
5542
|
+
}
|
|
5543
|
+
if (input.expected.termsHash) {
|
|
5544
|
+
const termsHash2 = preparedPureVectorU8(resolve(8));
|
|
5545
|
+
const expectedBytes = toHashBytes(input.expected.termsHash);
|
|
5546
|
+
if (!termsHash2 || !sameBytes(termsHash2, expectedBytes)) {
|
|
5547
|
+
throw new Error("prepared terms_hash does not match expected termsHash");
|
|
5548
|
+
}
|
|
5549
|
+
}
|
|
4398
5550
|
}
|
|
4399
5551
|
let grant;
|
|
4400
5552
|
try {
|
|
@@ -4405,9 +5557,12 @@ async function verifyPreparedSettlementKind(input) {
|
|
|
4405
5557
|
if (!grant) {
|
|
4406
5558
|
throw new Error("unsigned getObject could not read the named grant");
|
|
4407
5559
|
}
|
|
4408
|
-
if (!grant.delegateAddress || !
|
|
5560
|
+
if (!grant.delegateAddress || !sameAddress2(grant.delegateAddress, input.expected.delegateAddress)) {
|
|
4409
5561
|
throw new Error(`grant delegate ${grant.delegateAddress ?? "(missing)"} does not match the agent address`);
|
|
4410
5562
|
}
|
|
5563
|
+
if (input.expected.poolObjectId && grant.poolObjectId && !sameAddress2(grant.poolObjectId, input.expected.poolObjectId)) {
|
|
5564
|
+
throw new Error(`grant pool ${grant.poolObjectId} does not match expected pool ${input.expected.poolObjectId}`);
|
|
5565
|
+
}
|
|
4411
5566
|
}
|
|
4412
5567
|
function createMemoryLedger() {
|
|
4413
5568
|
const map = /* @__PURE__ */ new Map();
|
|
@@ -4457,10 +5612,10 @@ function createMemoryLedger() {
|
|
|
4457
5612
|
}
|
|
4458
5613
|
|
|
4459
5614
|
// src/auth.ts
|
|
4460
|
-
import { dirname, join as
|
|
5615
|
+
import { dirname as dirname2, join as join3 } from "path";
|
|
4461
5616
|
import {
|
|
4462
5617
|
mkdirSync,
|
|
4463
|
-
readFileSync,
|
|
5618
|
+
readFileSync as readFileSync2,
|
|
4464
5619
|
renameSync,
|
|
4465
5620
|
chmodSync,
|
|
4466
5621
|
unlinkSync,
|
|
@@ -4472,17 +5627,75 @@ import {
|
|
|
4472
5627
|
} from "fs";
|
|
4473
5628
|
import { decodeSuiPrivateKey } from "@mysten/sui/cryptography";
|
|
4474
5629
|
import { Ed25519Keypair as Ed25519Keypair2 } from "@mysten/sui/keypairs/ed25519";
|
|
4475
|
-
import { fromBase64 as
|
|
5630
|
+
import { fromBase64 as fromBase647 } from "@mysten/sui/utils";
|
|
4476
5631
|
import { bytesToHex as bytesToHex4 } from "@noble/hashes/utils.js";
|
|
4477
5632
|
|
|
4478
5633
|
// src/config.ts
|
|
4479
5634
|
import { homedir } from "os";
|
|
4480
|
-
import { join } from "path";
|
|
5635
|
+
import { join as join2 } from "path";
|
|
4481
5636
|
import { SuiGrpcClient as SuiGrpcClient2 } from "@mysten/sui/grpc";
|
|
4482
5637
|
import { normalizeSuiAddress as normalizeSuiAddress7 } from "@mysten/sui/utils";
|
|
5638
|
+
|
|
5639
|
+
// src/hosted-defaults.ts
|
|
5640
|
+
import { existsSync, readFileSync } from "fs";
|
|
5641
|
+
import { dirname, join } from "path";
|
|
5642
|
+
import { fileURLToPath } from "url";
|
|
5643
|
+
var MCP_LOCAL_GATEWAY_URL = "https://gateway-dev-ba84.up.railway.app";
|
|
5644
|
+
var MCP_LOCAL_CONSOLE_URL = "https://suipay-dev.up.railway.app";
|
|
5645
|
+
var MCP_DEV_GATEWAY_URL = "https://gateway-dev-ba84.up.railway.app";
|
|
5646
|
+
var MCP_DEV_CONSOLE_URL = "https://suipay-dev.up.railway.app";
|
|
5647
|
+
var MCP_PROD_GATEWAY_URL = "https://gateway.suipay.dev";
|
|
5648
|
+
var MCP_PROD_CONSOLE_URL = "https://www.sui.io/agentpayments";
|
|
5649
|
+
var MCP_CHANNEL_DEFAULTS = {
|
|
5650
|
+
local: { gatewayUrl: MCP_LOCAL_GATEWAY_URL, consoleUrl: MCP_LOCAL_CONSOLE_URL },
|
|
5651
|
+
dev: { gatewayUrl: MCP_DEV_GATEWAY_URL, consoleUrl: MCP_DEV_CONSOLE_URL },
|
|
5652
|
+
prod: { gatewayUrl: MCP_PROD_GATEWAY_URL, consoleUrl: MCP_PROD_CONSOLE_URL }
|
|
5653
|
+
};
|
|
5654
|
+
function isNodeModulesInstall(moduleUrl) {
|
|
5655
|
+
const text2 = moduleUrl.replace(/\\/g, "/");
|
|
5656
|
+
if (text2.includes("/packages/mcp/")) return false;
|
|
5657
|
+
return text2.includes("/node_modules/");
|
|
5658
|
+
}
|
|
5659
|
+
function detectMcpDefaultChannel(opts) {
|
|
5660
|
+
if (!isNodeModulesInstall(opts.moduleUrl)) return "local";
|
|
5661
|
+
if (typeof opts.version === "string" && opts.version.includes("-dev.")) return "dev";
|
|
5662
|
+
if (!opts.version) return "local";
|
|
5663
|
+
return "prod";
|
|
5664
|
+
}
|
|
5665
|
+
function readOwnPackageVersion(moduleUrl = import.meta.url) {
|
|
5666
|
+
let dir = dirname(fileURLToPath(moduleUrl));
|
|
5667
|
+
for (let i = 0; i < 6; i += 1) {
|
|
5668
|
+
const candidate = join(dir, "package.json");
|
|
5669
|
+
if (existsSync(candidate)) {
|
|
5670
|
+
try {
|
|
5671
|
+
const pkg = JSON.parse(readFileSync(candidate, "utf8"));
|
|
5672
|
+
if (pkg.name === "@cmdoss/suipay-mcp" && typeof pkg.version === "string") {
|
|
5673
|
+
return pkg.version;
|
|
5674
|
+
}
|
|
5675
|
+
} catch {
|
|
5676
|
+
}
|
|
5677
|
+
}
|
|
5678
|
+
const parent = dirname(dir);
|
|
5679
|
+
if (parent === dir) break;
|
|
5680
|
+
dir = parent;
|
|
5681
|
+
}
|
|
5682
|
+
return "";
|
|
5683
|
+
}
|
|
5684
|
+
function resolveMcpHostedDefaults(opts = {}) {
|
|
5685
|
+
const env = opts.env ?? process.env;
|
|
5686
|
+
const moduleUrl = opts.moduleUrl ?? import.meta.url;
|
|
5687
|
+
const version = opts.version ?? readOwnPackageVersion(moduleUrl);
|
|
5688
|
+
const defaults = MCP_CHANNEL_DEFAULTS[detectMcpDefaultChannel({ moduleUrl, version })];
|
|
5689
|
+
return {
|
|
5690
|
+
gatewayUrl: env.SUIPAY_GATEWAY_URL ?? defaults.gatewayUrl,
|
|
5691
|
+
consoleUrl: env.SUIPAY_CONSOLE_URL ?? defaults.consoleUrl
|
|
5692
|
+
};
|
|
5693
|
+
}
|
|
5694
|
+
|
|
5695
|
+
// src/config.ts
|
|
4483
5696
|
function mcpCredentialsPath() {
|
|
4484
5697
|
const home = process.env.HOME ?? process.env.USERPROFILE ?? homedir();
|
|
4485
|
-
return
|
|
5698
|
+
return join2(home, ".suipay", "credentials.json");
|
|
4486
5699
|
}
|
|
4487
5700
|
var MCP_TARGET_PRESETS = {
|
|
4488
5701
|
dev: {
|
|
@@ -4490,7 +5703,7 @@ var MCP_TARGET_PRESETS = {
|
|
|
4490
5703
|
gatewayUrl: "https://gateway-dev-ba84.up.railway.app"
|
|
4491
5704
|
},
|
|
4492
5705
|
local: {
|
|
4493
|
-
consoleUrl: "
|
|
5706
|
+
consoleUrl: "https://suipay-dev.up.railway.app",
|
|
4494
5707
|
gatewayUrl: "http://127.0.0.1:4340"
|
|
4495
5708
|
}
|
|
4496
5709
|
};
|
|
@@ -4580,12 +5793,15 @@ function sameMcpGateway(a, b) {
|
|
|
4580
5793
|
}
|
|
4581
5794
|
function loadMcpConfig(env = process.env, argv = []) {
|
|
4582
5795
|
const network = env.SUIPAY_NETWORK ?? "testnet";
|
|
4583
|
-
const
|
|
4584
|
-
const
|
|
4585
|
-
const
|
|
4586
|
-
const
|
|
5796
|
+
const explicit = parseMcpTargetFlag(argv) ?? parseMcpTargetName(env.SUIPAY_MCP_TARGET);
|
|
5797
|
+
const hosted = resolveMcpHostedDefaults({ env });
|
|
5798
|
+
const preset = explicit ? MCP_TARGET_PRESETS[explicit] : null;
|
|
5799
|
+
const gatewayUrl = preset ? env.SUIPAY_GATEWAY_URL?.trim() || preset.gatewayUrl : hosted.gatewayUrl;
|
|
5800
|
+
const consoleUrl = preset ? env.SUIPAY_CONSOLE_URL?.trim() || preset.consoleUrl : hosted.consoleUrl;
|
|
5801
|
+
const rawDir = env.SUIPAY_EXCLUSION_DIRECTORY_OBJECT_ID?.trim();
|
|
5802
|
+
if (rawDir) assertValidPackageId("SUIPAY_EXCLUSION_DIRECTORY_OBJECT_ID", rawDir);
|
|
4587
5803
|
return {
|
|
4588
|
-
target,
|
|
5804
|
+
...explicit ? { target: explicit } : {},
|
|
4589
5805
|
consoleUrl,
|
|
4590
5806
|
gatewayUrl,
|
|
4591
5807
|
rpcUrl: env.SUIPAY_RPC_URL ?? GRPC_URLS[network],
|
|
@@ -4596,9 +5812,42 @@ function loadMcpConfig(env = process.env, argv = []) {
|
|
|
4596
5812
|
sponsorUrl: env.SUIPAY_SPONSOR_URL ?? `${gatewayUrl.replace(/\/$/, "")}/api/sponsor`,
|
|
4597
5813
|
minFunded: BigInt(env.SUIPAY_MIN_FUNDED ?? "0"),
|
|
4598
5814
|
minMaxPerPayment: BigInt(env.SUIPAY_MIN_MAX_PER_PAYMENT ?? "0"),
|
|
4599
|
-
label: env.SUIPAY_LABEL ?? "SuiPay MCP"
|
|
5815
|
+
label: env.SUIPAY_LABEL ?? "SuiPay MCP",
|
|
5816
|
+
exclusionDirectoryObjectId: rawDir || null
|
|
4600
5817
|
};
|
|
4601
5818
|
}
|
|
5819
|
+
var OBJECT_ID = /^0x(?:[0-9a-fA-F]{1,64})$/;
|
|
5820
|
+
async function publishedExclusionDirectoryId(gatewayUrl, fetchImpl = fetch) {
|
|
5821
|
+
let url;
|
|
5822
|
+
try {
|
|
5823
|
+
url = new URL("/v1/delegate/deployment", gatewayUrl).toString();
|
|
5824
|
+
} catch {
|
|
5825
|
+
return null;
|
|
5826
|
+
}
|
|
5827
|
+
let response;
|
|
5828
|
+
try {
|
|
5829
|
+
response = await fetchImpl(url, {
|
|
5830
|
+
method: "GET",
|
|
5831
|
+
redirect: "manual",
|
|
5832
|
+
signal: AbortSignal.timeout(1500)
|
|
5833
|
+
});
|
|
5834
|
+
} catch {
|
|
5835
|
+
return null;
|
|
5836
|
+
}
|
|
5837
|
+
if (response.status !== 200) return null;
|
|
5838
|
+
let body;
|
|
5839
|
+
try {
|
|
5840
|
+
body = await response.json();
|
|
5841
|
+
} catch {
|
|
5842
|
+
return null;
|
|
5843
|
+
}
|
|
5844
|
+
const id = body && typeof body === "object" && "exclusionDirectoryObjectId" in body ? body.exclusionDirectoryObjectId : void 0;
|
|
5845
|
+
if (typeof id !== "string" || !OBJECT_ID.test(id.trim())) return null;
|
|
5846
|
+
return id.trim();
|
|
5847
|
+
}
|
|
5848
|
+
function rpcClient(cfg) {
|
|
5849
|
+
return new SuiGrpcClient2({ network: cfg.network, baseUrl: cfg.rpcUrl });
|
|
5850
|
+
}
|
|
4602
5851
|
|
|
4603
5852
|
// src/crypto.ts
|
|
4604
5853
|
import { getPublicKeyAsync, utils } from "@noble/ed25519";
|
|
@@ -4626,7 +5875,7 @@ function credsPath() {
|
|
|
4626
5875
|
return mcpCredentialsPath();
|
|
4627
5876
|
}
|
|
4628
5877
|
function credsDir() {
|
|
4629
|
-
return
|
|
5878
|
+
return dirname2(credsPath());
|
|
4630
5879
|
}
|
|
4631
5880
|
var HEX64 = /^[0-9a-fA-F]{64}$/;
|
|
4632
5881
|
var ADDR = /^0x[0-9a-fA-F]{64}$/;
|
|
@@ -4657,7 +5906,7 @@ function readCredentialsJson() {
|
|
|
4657
5906
|
const path = credsPath();
|
|
4658
5907
|
let raw;
|
|
4659
5908
|
try {
|
|
4660
|
-
raw =
|
|
5909
|
+
raw = readFileSync2(path, "utf8");
|
|
4661
5910
|
} catch (error2) {
|
|
4662
5911
|
const code = error2.code;
|
|
4663
5912
|
if (code === "ENOENT" || code === "ENOTDIR") return null;
|
|
@@ -4699,7 +5948,7 @@ function fsyncDir(dir) {
|
|
|
4699
5948
|
}
|
|
4700
5949
|
}
|
|
4701
5950
|
function writeTmpCredentials(dir, payload) {
|
|
4702
|
-
const tmp =
|
|
5951
|
+
const tmp = join3(dir, `.credentials.${process.pid}.${randomSuffix()}.tmp`);
|
|
4703
5952
|
const fd = openSync(tmp, "w", 384);
|
|
4704
5953
|
try {
|
|
4705
5954
|
writeSync(fd, JSON.stringify(payload, null, 2));
|
|
@@ -4774,7 +6023,7 @@ function walletFromSeedHex(seedHex) {
|
|
|
4774
6023
|
return {
|
|
4775
6024
|
address: kp.getPublicKey().toSuiAddress(),
|
|
4776
6025
|
async signTransaction(txBytes) {
|
|
4777
|
-
const { signature } = await kp.signTransaction(
|
|
6026
|
+
const { signature } = await kp.signTransaction(fromBase647(txBytes));
|
|
4778
6027
|
return signature;
|
|
4779
6028
|
}
|
|
4780
6029
|
};
|
|
@@ -4791,7 +6040,7 @@ function walletFromEnvSecret(raw) {
|
|
|
4791
6040
|
return {
|
|
4792
6041
|
address: kp.getPublicKey().toSuiAddress(),
|
|
4793
6042
|
async signTransaction(txBytes) {
|
|
4794
|
-
const { signature } = await kp.signTransaction(
|
|
6043
|
+
const { signature } = await kp.signTransaction(fromBase647(txBytes));
|
|
4795
6044
|
return signature;
|
|
4796
6045
|
}
|
|
4797
6046
|
};
|
|
@@ -4888,9 +6137,29 @@ function recordFromKeypair(kp, opts = {}, existing, operatorSecret) {
|
|
|
4888
6137
|
};
|
|
4889
6138
|
}
|
|
4890
6139
|
var ENV_KEY_MISMATCH = "SUIPAY_MCP_KEY does not match the stored delegate key - refusing to overwrite";
|
|
6140
|
+
function signingMaterial(parsed) {
|
|
6141
|
+
if (!parsed || typeof parsed !== "object") return null;
|
|
6142
|
+
if (isLocalSigningKeyRecord(parsed)) return parsed;
|
|
6143
|
+
const c = parsed;
|
|
6144
|
+
if (typeof c.delegatePrivateKey !== "string" || !HEX64.test(c.delegatePrivateKey) || typeof c.delegatePublicKeyHex !== "string" || !HEX64.test(c.delegatePublicKeyHex) || typeof c.delegateAddress !== "string" || !ADDR.test(c.delegateAddress)) {
|
|
6145
|
+
return null;
|
|
6146
|
+
}
|
|
6147
|
+
const label = typeof c.label === "string" ? c.label.trim() : "";
|
|
6148
|
+
return {
|
|
6149
|
+
version: 2,
|
|
6150
|
+
delegatePrivateKey: c.delegatePrivateKey,
|
|
6151
|
+
delegatePublicKeyHex: c.delegatePublicKeyHex,
|
|
6152
|
+
delegateAddress: c.delegateAddress,
|
|
6153
|
+
createdAt: typeof c.createdAt === "string" && c.createdAt ? c.createdAt : (/* @__PURE__ */ new Date(0)).toISOString(),
|
|
6154
|
+
...isHttpUrl(c.gatewayUrl) ? { gatewayUrl: c.gatewayUrl } : {},
|
|
6155
|
+
...label ? { label } : {}
|
|
6156
|
+
};
|
|
6157
|
+
}
|
|
4891
6158
|
function existingRecordForEnv(parsed) {
|
|
4892
6159
|
if (parsed === null) return null;
|
|
4893
|
-
if (
|
|
6160
|
+
if (isValid(parsed)) return parsed;
|
|
6161
|
+
const material = signingMaterial(parsed);
|
|
6162
|
+
if (material) return material;
|
|
4894
6163
|
throw new Error(
|
|
4895
6164
|
`credentials file at ${credsPath()} failed schema validation - refusing to continue`
|
|
4896
6165
|
);
|
|
@@ -4928,16 +6197,25 @@ async function ensureLocalSigningKey(opts = {}) {
|
|
|
4928
6197
|
}
|
|
4929
6198
|
const parsed = readCredentialsJson();
|
|
4930
6199
|
if (parsed !== null) {
|
|
4931
|
-
if (isLocalSigningKeyRecord(parsed)) {
|
|
4932
|
-
return identityFromStored(
|
|
4933
|
-
parsed.delegatePrivateKey,
|
|
4934
|
-
parsed.delegatePublicKeyHex,
|
|
4935
|
-
parsed.delegateAddress
|
|
4936
|
-
);
|
|
4937
|
-
}
|
|
4938
|
-
if (isValid(parsed)) {
|
|
6200
|
+
if (isValid(parsed) && !isLocalSigningKeyRecord(parsed)) {
|
|
4939
6201
|
return rewriteV1ToLocalSigningKey(parsed, opts);
|
|
4940
6202
|
}
|
|
6203
|
+
const material = signingMaterial(parsed);
|
|
6204
|
+
if (material) {
|
|
6205
|
+
const identity = identityFromStored(
|
|
6206
|
+
material.delegatePrivateKey,
|
|
6207
|
+
material.delegatePublicKeyHex,
|
|
6208
|
+
material.delegateAddress
|
|
6209
|
+
);
|
|
6210
|
+
if (!isLocalSigningKeyRecord(parsed)) {
|
|
6211
|
+
const label = opts.label?.trim() || material.label;
|
|
6212
|
+
writeCredentialsFile({
|
|
6213
|
+
...material,
|
|
6214
|
+
...label ? { label } : {}
|
|
6215
|
+
});
|
|
6216
|
+
}
|
|
6217
|
+
return identity;
|
|
6218
|
+
}
|
|
4941
6219
|
throw new Error(`credentials file at ${credsPath()} failed schema validation - refusing to continue`);
|
|
4942
6220
|
}
|
|
4943
6221
|
const kp = await generateKeypair();
|
|
@@ -4986,7 +6264,7 @@ function loadLocalSigner() {
|
|
|
4986
6264
|
return {
|
|
4987
6265
|
address: parsed.delegateAddress,
|
|
4988
6266
|
async signTransaction(txBytes) {
|
|
4989
|
-
const { signature } = await kp.signTransaction(
|
|
6267
|
+
const { signature } = await kp.signTransaction(fromBase647(txBytes));
|
|
4990
6268
|
return signature;
|
|
4991
6269
|
},
|
|
4992
6270
|
async signPersonalMessage(message) {
|
|
@@ -5028,6 +6306,32 @@ function loadLocalKeypair() {
|
|
|
5028
6306
|
Uint8Array.from(Buffer.from(parsed.delegatePrivateKey, "hex"))
|
|
5029
6307
|
);
|
|
5030
6308
|
}
|
|
6309
|
+
function replaceLocalSigningKey(input) {
|
|
6310
|
+
identityFromStored(
|
|
6311
|
+
input.delegatePrivateKey,
|
|
6312
|
+
input.delegatePublicKeyHex,
|
|
6313
|
+
input.delegateAddress
|
|
6314
|
+
);
|
|
6315
|
+
const parsed = readCredentialsJson();
|
|
6316
|
+
let gatewayUrl;
|
|
6317
|
+
let label;
|
|
6318
|
+
if (parsed && typeof parsed === "object") {
|
|
6319
|
+
const record = parsed;
|
|
6320
|
+
if (isHttpUrl(record.gatewayUrl)) gatewayUrl = record.gatewayUrl;
|
|
6321
|
+
if (typeof record.label === "string" && record.label.trim()) {
|
|
6322
|
+
label = record.label.trim();
|
|
6323
|
+
}
|
|
6324
|
+
}
|
|
6325
|
+
writeCredentialsFile({
|
|
6326
|
+
version: 2,
|
|
6327
|
+
delegatePrivateKey: input.delegatePrivateKey,
|
|
6328
|
+
delegatePublicKeyHex: input.delegatePublicKeyHex,
|
|
6329
|
+
delegateAddress: input.delegateAddress,
|
|
6330
|
+
createdAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
6331
|
+
...gatewayUrl ? { gatewayUrl } : {},
|
|
6332
|
+
...label ? { label } : {}
|
|
6333
|
+
});
|
|
6334
|
+
}
|
|
5031
6335
|
function persistGatewayUrl(gatewayUrl) {
|
|
5032
6336
|
if (!isHttpUrl(gatewayUrl)) {
|
|
5033
6337
|
throw new Error("gatewayUrl must be an http(s) origin");
|
|
@@ -5072,7 +6376,7 @@ function objectWithOwner(object) {
|
|
|
5072
6376
|
}
|
|
5073
6377
|
return { ...object.json, owner: object.owner };
|
|
5074
6378
|
}
|
|
5075
|
-
function
|
|
6379
|
+
function sameAddress3(a, b) {
|
|
5076
6380
|
try {
|
|
5077
6381
|
return normalizeSuiAddress8(a) === normalizeSuiAddress8(b);
|
|
5078
6382
|
} catch {
|
|
@@ -5088,7 +6392,7 @@ function resolveAgentHeldSettlement(cfg, spendAccountPay, env = process.env) {
|
|
|
5088
6392
|
if (!secret) return null;
|
|
5089
6393
|
const local = loadLocalSigner();
|
|
5090
6394
|
if (!local) return null;
|
|
5091
|
-
if (!
|
|
6395
|
+
if (!sameAddress3(local.address, spendAccountPay.snapshot.delegateAddress)) {
|
|
5092
6396
|
return null;
|
|
5093
6397
|
}
|
|
5094
6398
|
const chain = createSpendAccountChain({
|
|
@@ -5096,7 +6400,7 @@ function resolveAgentHeldSettlement(cfg, spendAccountPay, env = process.env) {
|
|
|
5096
6400
|
network: cfg.network,
|
|
5097
6401
|
url: cfg.rpcUrl
|
|
5098
6402
|
});
|
|
5099
|
-
const finality =
|
|
6403
|
+
const finality = createSpendAccountSettledTxReader({ client: chain.client });
|
|
5100
6404
|
const getObjectJson = async (objectId) => {
|
|
5101
6405
|
const { object } = await chain.client.core.getObject({
|
|
5102
6406
|
objectId,
|
|
@@ -5119,7 +6423,7 @@ function resolveAgentHeldSettlement(cfg, spendAccountPay, env = process.env) {
|
|
|
5119
6423
|
sponsor: { sponsorUrl: cfg.gatewayUrl },
|
|
5120
6424
|
gasMode: "sponsored",
|
|
5121
6425
|
resolveSharedObjectVersions: (ids) => resolveSharedObjectVersionsFromGetObject(getObjectJson, ids),
|
|
5122
|
-
|
|
6426
|
+
getSettledTx: (digest) => finality.getSettledTx(digest)
|
|
5123
6427
|
}
|
|
5124
6428
|
};
|
|
5125
6429
|
}
|
|
@@ -5176,9 +6480,16 @@ function toolAllowedByScope(toolName, grantedScope) {
|
|
|
5176
6480
|
}
|
|
5177
6481
|
|
|
5178
6482
|
// src/tools.ts
|
|
5179
|
-
import { isValidSuiAddress, normalizeSuiAddress as
|
|
6483
|
+
import { isValidSuiAddress, normalizeSuiAddress as normalizeSuiAddress15 } from "@mysten/sui/utils";
|
|
5180
6484
|
|
|
5181
6485
|
// src/settle.ts
|
|
6486
|
+
function accessModeFromMoveTarget(target) {
|
|
6487
|
+
const fn = target.split("::").pop() ?? "";
|
|
6488
|
+
if (fn === spendAccountDebitFunction({ accessMode: "open" }) || fn === spendAccountDebitFunction({ accessMode: "open", refundable: true })) {
|
|
6489
|
+
return "open";
|
|
6490
|
+
}
|
|
6491
|
+
return "scoped";
|
|
6492
|
+
}
|
|
5182
6493
|
async function settlePreparedMcpPayment(args) {
|
|
5183
6494
|
try {
|
|
5184
6495
|
await verifyPreparedSettlementKind({
|
|
@@ -5188,9 +6499,9 @@ async function settlePreparedMcpPayment(args) {
|
|
|
5188
6499
|
delegateAddress: args.expected.delegateAddress,
|
|
5189
6500
|
amount: args.expected.amount,
|
|
5190
6501
|
recipient: args.expected.recipient,
|
|
5191
|
-
accessMode:
|
|
5192
|
-
|
|
5193
|
-
)
|
|
6502
|
+
accessMode: accessModeFromMoveTarget(
|
|
6503
|
+
args.signingRequest.submit.moveCallTarget
|
|
6504
|
+
)
|
|
5194
6505
|
},
|
|
5195
6506
|
getObjectJson: args.getObjectJson
|
|
5196
6507
|
});
|
|
@@ -5236,12 +6547,104 @@ async function settlePreparedMcpPayment(args) {
|
|
|
5236
6547
|
}
|
|
5237
6548
|
|
|
5238
6549
|
// ../sdk/dist/agent-sdk/delegate-agent.js
|
|
5239
|
-
import { normalizeSuiAddress as
|
|
6550
|
+
import { normalizeSuiAddress as normalizeSuiAddress11 } from "@mysten/sui/utils";
|
|
5240
6551
|
|
|
5241
|
-
// ../sdk/dist/
|
|
6552
|
+
// ../sdk/dist/buyer-control/exclusion-registry.js
|
|
6553
|
+
import { bcs as bcs6 } from "@mysten/sui/bcs";
|
|
5242
6554
|
import { normalizeSuiAddress as normalizeSuiAddress9 } from "@mysten/sui/utils";
|
|
6555
|
+
var ExclusionRegistryResolutionError = class extends Error {
|
|
6556
|
+
code;
|
|
6557
|
+
constructor(code, message) {
|
|
6558
|
+
super(message);
|
|
6559
|
+
this.code = code;
|
|
6560
|
+
this.name = "ExclusionRegistryResolutionError";
|
|
6561
|
+
}
|
|
6562
|
+
};
|
|
6563
|
+
async function resolveExclusionRegistry(input) {
|
|
6564
|
+
const directoryObjectId = typeof input.directoryObjectId === "string" ? input.directoryObjectId.trim() : "";
|
|
6565
|
+
if (!directoryObjectId) {
|
|
6566
|
+
throw new ExclusionRegistryResolutionError("NO_DIRECTORY", "no exclusion directory object id configured for a guarded offer");
|
|
6567
|
+
}
|
|
6568
|
+
let registryId;
|
|
6569
|
+
try {
|
|
6570
|
+
registryId = await input.reader.boundRegistryId(directoryObjectId, input.recipient);
|
|
6571
|
+
} catch (err) {
|
|
6572
|
+
if (err instanceof ExclusionRegistryResolutionError)
|
|
6573
|
+
throw err;
|
|
6574
|
+
throw new ExclusionRegistryResolutionError("REGISTRY_UNREADABLE", "could not read the exclusion directory binding for this recipient");
|
|
6575
|
+
}
|
|
6576
|
+
if (!registryId || !registryId.trim()) {
|
|
6577
|
+
throw new ExclusionRegistryResolutionError("REGISTRY_NOT_PROVISIONED", "no RecipientRegistry is bound for this recipient in the exclusion directory");
|
|
6578
|
+
}
|
|
6579
|
+
const directoryInitialSharedVersion = await input.reader.initialSharedVersion(directoryObjectId);
|
|
6580
|
+
if (!directoryInitialSharedVersion || !directoryInitialSharedVersion.trim()) {
|
|
6581
|
+
throw new ExclusionRegistryResolutionError("DIRECTORY_VERSION_UNRESOLVED", "could not resolve the exclusion directory initial shared version");
|
|
6582
|
+
}
|
|
6583
|
+
const registryInitialSharedVersion = await input.reader.initialSharedVersion(registryId);
|
|
6584
|
+
if (!registryInitialSharedVersion || !registryInitialSharedVersion.trim()) {
|
|
6585
|
+
throw new ExclusionRegistryResolutionError("REGISTRY_VERSION_UNRESOLVED", "could not resolve the recipient registry initial shared version");
|
|
6586
|
+
}
|
|
6587
|
+
return {
|
|
6588
|
+
recipientRegistryId: normalizeSuiAddress9(registryId),
|
|
6589
|
+
directoryInitialSharedVersion,
|
|
6590
|
+
registryInitialSharedVersion
|
|
6591
|
+
};
|
|
6592
|
+
}
|
|
6593
|
+
function initialSharedVersionFromOwner2(owner) {
|
|
6594
|
+
if (!owner || typeof owner !== "object")
|
|
6595
|
+
return null;
|
|
6596
|
+
const shared = owner.Shared;
|
|
6597
|
+
if (shared?.initialSharedVersion != null) {
|
|
6598
|
+
return String(shared.initialSharedVersion);
|
|
6599
|
+
}
|
|
6600
|
+
return null;
|
|
6601
|
+
}
|
|
6602
|
+
function isAbsentDynamicFieldError(err) {
|
|
6603
|
+
const text2 = (err instanceof Error ? err.message : String(err)).toLowerCase();
|
|
6604
|
+
if (/timeout|timed out|unavailable|econnreset|econnrefused|network|fetch failed|socket| 502| 503| 429/.test(text2)) {
|
|
6605
|
+
return false;
|
|
6606
|
+
}
|
|
6607
|
+
return /\bnot[_ ]found\b|dynamic field not found|no such.*field/.test(text2);
|
|
6608
|
+
}
|
|
6609
|
+
function createGrpcExclusionRegistryReader(client) {
|
|
6610
|
+
return {
|
|
6611
|
+
async boundRegistryId(directoryObjectId, recipient) {
|
|
6612
|
+
let response;
|
|
6613
|
+
try {
|
|
6614
|
+
response = await client.core.getDynamicField({
|
|
6615
|
+
parentId: directoryObjectId,
|
|
6616
|
+
name: { type: "address", bcs: bcs6.Address.serialize(recipient).toBytes() }
|
|
6617
|
+
});
|
|
6618
|
+
} catch (err) {
|
|
6619
|
+
if (isAbsentDynamicFieldError(err))
|
|
6620
|
+
return null;
|
|
6621
|
+
throw new ExclusionRegistryResolutionError("REGISTRY_UNREADABLE", "could not read the exclusion directory binding for this recipient");
|
|
6622
|
+
}
|
|
6623
|
+
const raw = response?.dynamicField?.value?.bcs;
|
|
6624
|
+
if (!raw)
|
|
6625
|
+
return null;
|
|
6626
|
+
try {
|
|
6627
|
+
return normalizeSuiAddress9(bcs6.Address.parse(raw));
|
|
6628
|
+
} catch {
|
|
6629
|
+
return null;
|
|
6630
|
+
}
|
|
6631
|
+
},
|
|
6632
|
+
async initialSharedVersion(objectId) {
|
|
6633
|
+
let response;
|
|
6634
|
+
try {
|
|
6635
|
+
response = await client.core.getObject({ objectId });
|
|
6636
|
+
} catch {
|
|
6637
|
+
return null;
|
|
6638
|
+
}
|
|
6639
|
+
return initialSharedVersionFromOwner2(response?.object?.owner);
|
|
6640
|
+
}
|
|
6641
|
+
};
|
|
6642
|
+
}
|
|
6643
|
+
|
|
6644
|
+
// ../sdk/dist/agent-sdk/verify-prepared-settlement.js
|
|
6645
|
+
import { normalizeSuiAddress as normalizeSuiAddress10 } from "@mysten/sui/utils";
|
|
5243
6646
|
import { Transaction as Transaction5 } from "@mysten/sui/transactions";
|
|
5244
|
-
import { fromBase64 as
|
|
6647
|
+
import { fromBase64 as fromBase648 } from "@mysten/sui/utils";
|
|
5245
6648
|
|
|
5246
6649
|
// ../sdk/dist/protocol/prepared-settlement.js
|
|
5247
6650
|
var PREPARED_SETTLEMENT_VERSION = 1;
|
|
@@ -5269,6 +6672,11 @@ function asPreparedSettlement(value) {
|
|
|
5269
6672
|
"coinType",
|
|
5270
6673
|
"amount",
|
|
5271
6674
|
"recipient",
|
|
6675
|
+
"exclusionDirectoryId",
|
|
6676
|
+
"recipientRegistryId",
|
|
6677
|
+
"exclusionDirectoryInitialSharedVersion",
|
|
6678
|
+
"recipientRegistryInitialSharedVersion",
|
|
6679
|
+
"expiresAtMs",
|
|
5272
6680
|
"payUrl"
|
|
5273
6681
|
];
|
|
5274
6682
|
for (const key of strings) {
|
|
@@ -5293,6 +6701,11 @@ function asPreparedSettlement(value) {
|
|
|
5293
6701
|
coinType: v.coinType,
|
|
5294
6702
|
amount: v.amount,
|
|
5295
6703
|
recipient: v.recipient,
|
|
6704
|
+
exclusionDirectoryId: v.exclusionDirectoryId,
|
|
6705
|
+
recipientRegistryId: v.recipientRegistryId,
|
|
6706
|
+
exclusionDirectoryInitialSharedVersion: v.exclusionDirectoryInitialSharedVersion,
|
|
6707
|
+
recipientRegistryInitialSharedVersion: v.recipientRegistryInitialSharedVersion,
|
|
6708
|
+
expiresAtMs: v.expiresAtMs,
|
|
5296
6709
|
payUrl: v.payUrl
|
|
5297
6710
|
};
|
|
5298
6711
|
}
|
|
@@ -5311,9 +6724,9 @@ var PreparedSettlementRejected = class extends Error {
|
|
|
5311
6724
|
function reject(field, message) {
|
|
5312
6725
|
throw new PreparedSettlementRejected(field, message);
|
|
5313
6726
|
}
|
|
5314
|
-
function
|
|
6727
|
+
function sameAddress4(a, b) {
|
|
5315
6728
|
try {
|
|
5316
|
-
return
|
|
6729
|
+
return normalizeSuiAddress10(a) === normalizeSuiAddress10(b);
|
|
5317
6730
|
} catch {
|
|
5318
6731
|
return false;
|
|
5319
6732
|
}
|
|
@@ -5324,7 +6737,7 @@ function sameCoinType(a, b) {
|
|
|
5324
6737
|
if (pkg === void 0 || rest.length !== 2)
|
|
5325
6738
|
return value.trim();
|
|
5326
6739
|
try {
|
|
5327
|
-
return `${
|
|
6740
|
+
return `${normalizeSuiAddress10(pkg)}::${rest.join("::")}`;
|
|
5328
6741
|
} catch {
|
|
5329
6742
|
return value.trim();
|
|
5330
6743
|
}
|
|
@@ -5344,26 +6757,26 @@ async function verifyPreparedPayment(input) {
|
|
|
5344
6757
|
reject("shape", "the 402 carried no usable prepared settlement");
|
|
5345
6758
|
}
|
|
5346
6759
|
const { offer, delegateAddress } = input;
|
|
5347
|
-
if (!
|
|
6760
|
+
if (!sameAddress4(prepared.sender, delegateAddress)) {
|
|
5348
6761
|
reject("sender", `prepared sender ${prepared.sender} is not this agent's address`);
|
|
5349
6762
|
}
|
|
5350
6763
|
let data;
|
|
5351
6764
|
try {
|
|
5352
|
-
data = Transaction5.from(
|
|
6765
|
+
data = Transaction5.from(fromBase648(prepared.transactionBytes)).getData();
|
|
5353
6766
|
} catch {
|
|
5354
6767
|
reject("transactionBytes", "prepared transaction bytes are not a Sui transaction");
|
|
5355
6768
|
}
|
|
5356
6769
|
const gasOwner = data.gasData?.owner;
|
|
5357
|
-
if (!gasOwner ||
|
|
6770
|
+
if (!gasOwner || sameAddress4(gasOwner, delegateAddress)) {
|
|
5358
6771
|
reject("gasOwner", "prepared transaction is not sponsored: gas would be paid by this agent");
|
|
5359
6772
|
}
|
|
5360
|
-
if (input.expectedGrantObjectId && !
|
|
6773
|
+
if (input.expectedGrantObjectId && !sameAddress4(prepared.grantObjectId, input.expectedGrantObjectId)) {
|
|
5361
6774
|
reject("grantObjectId", `prepared settlement draws on grant ${prepared.grantObjectId}, not the one this agent resolved`);
|
|
5362
6775
|
}
|
|
5363
|
-
if (input.expectedPoolObjectId && !
|
|
6776
|
+
if (input.expectedPoolObjectId && !sameAddress4(prepared.poolObjectId, input.expectedPoolObjectId)) {
|
|
5364
6777
|
reject("poolObjectId", `prepared settlement draws on pool ${prepared.poolObjectId}, not the one this agent resolved`);
|
|
5365
6778
|
}
|
|
5366
|
-
if (!offer.packageId || !
|
|
6779
|
+
if (!offer.packageId || !sameAddress4(prepared.packageId, offer.packageId)) {
|
|
5367
6780
|
reject("packageId", "prepared package id does not match the offer");
|
|
5368
6781
|
}
|
|
5369
6782
|
const wantedTarget = payMoveCallTarget(offer.packageId, prepared.accessMode);
|
|
@@ -5376,9 +6789,19 @@ async function verifyPreparedPayment(input) {
|
|
|
5376
6789
|
if (!sameAmount(prepared.amount, offer.amount)) {
|
|
5377
6790
|
reject("amount", "prepared amount does not match the offer amount");
|
|
5378
6791
|
}
|
|
5379
|
-
if (!
|
|
6792
|
+
if (!sameAddress4(prepared.recipient, offer.payTo)) {
|
|
5380
6793
|
reject("recipient", "prepared recipient does not match the offer payTo");
|
|
5381
6794
|
}
|
|
6795
|
+
const identity = parseRegistryIdentity(offer.registryIdentity);
|
|
6796
|
+
if (!identity) {
|
|
6797
|
+
reject("registryIdentity", "offer carries no valid registry identity for a guarded settle");
|
|
6798
|
+
}
|
|
6799
|
+
if (!sameAddress4(prepared.exclusionDirectoryId, identity.directoryId)) {
|
|
6800
|
+
reject("exclusionDirectoryId", "prepared exclusion directory is not the one the offer committed to");
|
|
6801
|
+
}
|
|
6802
|
+
if (prepared.expiresAtMs !== String(offer.expiresAt)) {
|
|
6803
|
+
reject("expiresAtMs", "prepared guarded expiry does not match the offer expiry");
|
|
6804
|
+
}
|
|
5382
6805
|
const targetHash = offer.targetHash?.trim();
|
|
5383
6806
|
if (!targetHash) {
|
|
5384
6807
|
reject("targetHash", "offer carries no targetHash to bind the settlement to");
|
|
@@ -5398,7 +6821,14 @@ async function verifyPreparedPayment(input) {
|
|
|
5398
6821
|
targetHash,
|
|
5399
6822
|
amount: offer.amount,
|
|
5400
6823
|
paymentIdHash: paymentIdHash(offer.challengeId),
|
|
5401
|
-
termsHash: termsHash(offer)
|
|
6824
|
+
termsHash: termsHash(offer),
|
|
6825
|
+
// Guarded-settle inputs, rebuilt from the prepared bytes and validated
|
|
6826
|
+
// against the offer above.
|
|
6827
|
+
exclusionDirectoryId: prepared.exclusionDirectoryId,
|
|
6828
|
+
recipientRegistryId: prepared.recipientRegistryId,
|
|
6829
|
+
exclusionDirectoryInitialSharedVersion: prepared.exclusionDirectoryInitialSharedVersion,
|
|
6830
|
+
recipientRegistryInitialSharedVersion: prepared.recipientRegistryInitialSharedVersion,
|
|
6831
|
+
expiresAtMs: prepared.expiresAtMs
|
|
5402
6832
|
};
|
|
5403
6833
|
let rebuilt;
|
|
5404
6834
|
try {
|
|
@@ -5733,7 +7163,23 @@ async function fetchGatewayDeployment(options) {
|
|
|
5733
7163
|
}
|
|
5734
7164
|
};
|
|
5735
7165
|
}
|
|
5736
|
-
|
|
7166
|
+
if (d.exclusionDirectoryObjectId !== void 0 && !isNonEmptyString2(d.exclusionDirectoryObjectId)) {
|
|
7167
|
+
return {
|
|
7168
|
+
ok: false,
|
|
7169
|
+
error: {
|
|
7170
|
+
code: MANIFEST_ERROR_CODES.MALFORMED_MANIFEST,
|
|
7171
|
+
detail: "gateway deployment identity carries an unusable exclusionDirectoryObjectId"
|
|
7172
|
+
}
|
|
7173
|
+
};
|
|
7174
|
+
}
|
|
7175
|
+
return {
|
|
7176
|
+
ok: true,
|
|
7177
|
+
value: {
|
|
7178
|
+
network: d.network,
|
|
7179
|
+
packageId: d.packageId,
|
|
7180
|
+
...isNonEmptyString2(d.exclusionDirectoryObjectId) ? { exclusionDirectoryObjectId: d.exclusionDirectoryObjectId } : {}
|
|
7181
|
+
}
|
|
7182
|
+
};
|
|
5737
7183
|
}
|
|
5738
7184
|
function explainBlockedGrant(raw, error2) {
|
|
5739
7185
|
if (error2.code !== MANIFEST_ERROR_CODES.MISSING_POLICY_BINDING)
|
|
@@ -5825,7 +7271,7 @@ async function resolvePayerSession(options) {
|
|
|
5825
7271
|
...options.select ? { select: options.select } : {}
|
|
5826
7272
|
});
|
|
5827
7273
|
if (whole.ok)
|
|
5828
|
-
return whole;
|
|
7274
|
+
return { ok: true, value: { ...whole.value, deployment } };
|
|
5829
7275
|
const data = bootstrap.data;
|
|
5830
7276
|
const rawGrants = Array.isArray(data.grants) ? data.grants : [];
|
|
5831
7277
|
const usable = [];
|
|
@@ -5860,11 +7306,12 @@ async function resolvePayerSession(options) {
|
|
|
5860
7306
|
const pick = scoped ?? blocked[0];
|
|
5861
7307
|
return pick ? { ok: false, error: explainBlockedGrant(pick.raw, pick.error) } : whole;
|
|
5862
7308
|
}
|
|
5863
|
-
|
|
7309
|
+
const resolved = manifestToSession({
|
|
5864
7310
|
manifest: { ...data, grants: usable },
|
|
5865
7311
|
signer: options.signer,
|
|
5866
7312
|
...options.select ? { select: options.select } : {}
|
|
5867
7313
|
});
|
|
7314
|
+
return resolved.ok ? { ok: true, value: { ...resolved.value, deployment } } : resolved;
|
|
5868
7315
|
}
|
|
5869
7316
|
|
|
5870
7317
|
// ../sdk/dist/buyer/transport.js
|
|
@@ -5894,6 +7341,15 @@ function mergeObjectEnvelope(object) {
|
|
|
5894
7341
|
var SECRET_KEY_SET = new Set(FORBIDDEN_SECRET_KEYS.map((k) => k.toLowerCase()));
|
|
5895
7342
|
|
|
5896
7343
|
// ../sdk/dist/agent-sdk/delegate-agent.js
|
|
7344
|
+
function resolveExclusionDirectoryId(explicit, discovered) {
|
|
7345
|
+
return explicit?.trim() ? explicit : discovered;
|
|
7346
|
+
}
|
|
7347
|
+
function createExclusionRegistryResolver(client, exclusionDirectoryObjectId) {
|
|
7348
|
+
if (!exclusionDirectoryObjectId || !exclusionDirectoryObjectId.trim())
|
|
7349
|
+
return void 0;
|
|
7350
|
+
const reader = createGrpcExclusionRegistryReader(client);
|
|
7351
|
+
return async ({ directoryId, recipient }) => resolveExclusionRegistry({ directoryObjectId: directoryId, recipient, reader });
|
|
7352
|
+
}
|
|
5897
7353
|
async function verifyGrantOnChain(input) {
|
|
5898
7354
|
let state;
|
|
5899
7355
|
try {
|
|
@@ -5920,10 +7376,10 @@ async function verifyGrantOnChain(input) {
|
|
|
5920
7376
|
ok: false,
|
|
5921
7377
|
error: { code: MANIFEST_ERROR_CODES.GRANT_NOT_VERIFIED, detail }
|
|
5922
7378
|
});
|
|
5923
|
-
if (!state.delegateAddress ||
|
|
7379
|
+
if (!state.delegateAddress || normalizeSuiAddress11(state.delegateAddress) !== normalizeSuiAddress11(input.delegateAddress)) {
|
|
5924
7380
|
return notVerified(`grant ${input.grantObjectId} does not name this delegate on chain`);
|
|
5925
7381
|
}
|
|
5926
|
-
if (!state.poolObjectId ||
|
|
7382
|
+
if (!state.poolObjectId || normalizeSuiAddress11(state.poolObjectId) !== normalizeSuiAddress11(input.poolObjectId)) {
|
|
5927
7383
|
return notVerified(`grant ${input.grantObjectId} draws on a different pool than the one resolved`);
|
|
5928
7384
|
}
|
|
5929
7385
|
if (state.revoked === true) {
|
|
@@ -5956,8 +7412,17 @@ async function createPayer(options) {
|
|
|
5956
7412
|
const { session, binding } = resolved.value;
|
|
5957
7413
|
const deployment = {
|
|
5958
7414
|
network: session.network,
|
|
5959
|
-
packageId: session.packageId
|
|
7415
|
+
packageId: session.packageId,
|
|
7416
|
+
// What the GATEWAY published, not what this payer settled on: a caller
|
|
7417
|
+
// override belongs to the payer's configuration, not to the deployment it
|
|
7418
|
+
// is describing. Dropping this left the field permanently undefined on the
|
|
7419
|
+
// one instance a consumer can reach, so anything asking the payer whether
|
|
7420
|
+
// guarded settles are available got "no" while it was doing them.
|
|
7421
|
+
...resolved.value.deployment.exclusionDirectoryObjectId ? {
|
|
7422
|
+
exclusionDirectoryObjectId: resolved.value.deployment.exclusionDirectoryObjectId
|
|
7423
|
+
} : {}
|
|
5960
7424
|
};
|
|
7425
|
+
const exclusionDirectoryObjectId = resolveExclusionDirectoryId(options.exclusionDirectoryObjectId, resolved.value.deployment.exclusionDirectoryObjectId);
|
|
5961
7426
|
let network;
|
|
5962
7427
|
try {
|
|
5963
7428
|
network = toGrpcNetwork(deployment.network);
|
|
@@ -5987,7 +7452,7 @@ async function createPayer(options) {
|
|
|
5987
7452
|
}
|
|
5988
7453
|
};
|
|
5989
7454
|
}
|
|
5990
|
-
const finality =
|
|
7455
|
+
const finality = createSpendAccountSettledTxReader({ client: chain.client });
|
|
5991
7456
|
const getObjectJson = async (objectId) => {
|
|
5992
7457
|
const { object } = await chain.client.core.getObject({
|
|
5993
7458
|
objectId,
|
|
@@ -6010,7 +7475,20 @@ async function createPayer(options) {
|
|
|
6010
7475
|
sponsor: { sponsorUrl: options.gatewayUrl },
|
|
6011
7476
|
gasMode: "sponsored",
|
|
6012
7477
|
resolveSharedObjectVersions: (ids) => resolveSharedObjectVersionsFromGetObject(getObjectJson, ids),
|
|
6013
|
-
|
|
7478
|
+
getSettledTx: finality.getSettledTx,
|
|
7479
|
+
// A guarded offer resolves its per-recipient exclusion registry
|
|
7480
|
+
// through this dep, bound to the agent's own gRPC reader. Undefined when no
|
|
7481
|
+
// directory id was configured, so such an offer fails closed (BUILD_FAILED)
|
|
7482
|
+
// rather than building an unguarded settle. Unguarded offers never call it.
|
|
7483
|
+
...(() => {
|
|
7484
|
+
const resolve = createExclusionRegistryResolver(chain.client, exclusionDirectoryObjectId);
|
|
7485
|
+
return resolve ? { resolveExclusionRegistry: resolve } : {};
|
|
7486
|
+
})(),
|
|
7487
|
+
// x402 bounds the transaction's expiration by the current epoch, read from
|
|
7488
|
+
// the same client as every other chain read here.
|
|
7489
|
+
getCurrentEpoch: currentEpochReader(chain.client),
|
|
7490
|
+
// Address-balance gas (ADR-0055) binds the expiration to the chain.
|
|
7491
|
+
getChainIdentifier: chainIdentifierReader(chain.client),
|
|
6014
7492
|
...options.offerSecret ? { offerSecret: options.offerSecret } : {},
|
|
6015
7493
|
...options.fetchImpl ? { fetchImpl: options.fetchImpl } : {},
|
|
6016
7494
|
...options.logError ? { logError: options.logError } : {}
|
|
@@ -6040,7 +7518,9 @@ async function createPayer(options) {
|
|
|
6040
7518
|
cfg: { gatewayUrl: options.gatewayUrl },
|
|
6041
7519
|
deps,
|
|
6042
7520
|
intent: input.intent,
|
|
6043
|
-
...input.preparedTerms ? { preparedTerms: input.preparedTerms } : {}
|
|
7521
|
+
...input.preparedTerms ? { preparedTerms: input.preparedTerms } : {},
|
|
7522
|
+
dialect: input.dialect,
|
|
7523
|
+
onX402Progress: input.onX402Progress
|
|
6044
7524
|
});
|
|
6045
7525
|
}
|
|
6046
7526
|
async function verifySettlement(prepared, offer) {
|
|
@@ -6062,6 +7542,7 @@ async function createPayer(options) {
|
|
|
6062
7542
|
import { createServer } from "http";
|
|
6063
7543
|
import { randomBytes as randomBytes3, timingSafeEqual as timingSafeEqual2 } from "crypto";
|
|
6064
7544
|
import open from "open";
|
|
7545
|
+
import { Ed25519Keypair as Ed25519Keypair3 } from "@mysten/sui/keypairs/ed25519";
|
|
6065
7546
|
var HEX642 = /^[0-9a-f]{64}$/;
|
|
6066
7547
|
var PUBHEX = /^[0-9a-fA-F]{64}$/;
|
|
6067
7548
|
function isPreflight(obj) {
|
|
@@ -6069,6 +7550,11 @@ function isPreflight(obj) {
|
|
|
6069
7550
|
const o = obj;
|
|
6070
7551
|
return typeof o.state === "string" && HEX642.test(o.state) && typeof o.publicKey === "string" && PUBHEX.test(o.publicKey) && typeof o.delegateAddress === "string" && /^0x[0-9a-fA-F]{64}$/.test(o.delegateAddress) && typeof o.gateway === "string" && o.gateway.length > 0;
|
|
6071
7552
|
}
|
|
7553
|
+
function isAdoptKey(obj) {
|
|
7554
|
+
if (!obj || typeof obj !== "object") return false;
|
|
7555
|
+
const o = obj;
|
|
7556
|
+
return typeof o.state === "string" && HEX642.test(o.state) && typeof o.delegatePrivateKey === "string" && HEX642.test(o.delegatePrivateKey) && typeof o.delegatePublicKeyHex === "string" && PUBHEX.test(o.delegatePublicKeyHex) && typeof o.delegateAddress === "string" && /^0x[0-9a-fA-F]{64}$/.test(o.delegateAddress);
|
|
7557
|
+
}
|
|
6072
7558
|
function isCallback(obj) {
|
|
6073
7559
|
if (!obj || typeof obj !== "object") return false;
|
|
6074
7560
|
const o = obj;
|
|
@@ -6193,7 +7679,7 @@ async function startStdioLogin(opts) {
|
|
|
6193
7679
|
res.writeHead(code, { "content-type": type, ...SECURITY_HEADERS, ...extra });
|
|
6194
7680
|
res.end(body);
|
|
6195
7681
|
};
|
|
6196
|
-
const okPath = url.pathname === "/preflight" || url.pathname === "/callback";
|
|
7682
|
+
const okPath = url.pathname === "/preflight" || url.pathname === "/callback" || url.pathname === "/key";
|
|
6197
7683
|
if (req.method === "OPTIONS") {
|
|
6198
7684
|
const extra = { ...cors };
|
|
6199
7685
|
if (okHost && okOrigin && okPath && req.headers["access-control-request-private-network"] === "true") {
|
|
@@ -6264,6 +7750,55 @@ async function startStdioLogin(opts) {
|
|
|
6264
7750
|
);
|
|
6265
7751
|
return;
|
|
6266
7752
|
}
|
|
7753
|
+
if (url.pathname === "/key") {
|
|
7754
|
+
if (!preflightVerified) {
|
|
7755
|
+
res.writeHead(428, {
|
|
7756
|
+
"content-type": "application/json",
|
|
7757
|
+
...cors,
|
|
7758
|
+
...SECURITY_HEADERS
|
|
7759
|
+
});
|
|
7760
|
+
res.end(JSON.stringify({ ok: false, error: "preflight required" }));
|
|
7761
|
+
return;
|
|
7762
|
+
}
|
|
7763
|
+
if (!isAdoptKey(parsed) || !stateEquals(parsed.state, connectState)) {
|
|
7764
|
+
res.writeHead(400, {
|
|
7765
|
+
"content-type": "application/json",
|
|
7766
|
+
...cors,
|
|
7767
|
+
...SECURITY_HEADERS
|
|
7768
|
+
});
|
|
7769
|
+
res.end(JSON.stringify({ ok: false, error: "invalid key payload" }));
|
|
7770
|
+
return;
|
|
7771
|
+
}
|
|
7772
|
+
try {
|
|
7773
|
+
const kp = Ed25519Keypair3.fromSecretKey(
|
|
7774
|
+
Uint8Array.from(Buffer.from(parsed.delegatePrivateKey, "hex"))
|
|
7775
|
+
);
|
|
7776
|
+
const derived = kp.getPublicKey().toSuiAddress();
|
|
7777
|
+
if (derived.toLowerCase() !== parsed.delegateAddress.toLowerCase()) {
|
|
7778
|
+
throw new Error("address mismatch");
|
|
7779
|
+
}
|
|
7780
|
+
replaceLocalSigningKey({
|
|
7781
|
+
delegatePrivateKey: parsed.delegatePrivateKey,
|
|
7782
|
+
delegatePublicKeyHex: parsed.delegatePublicKeyHex,
|
|
7783
|
+
delegateAddress: derived
|
|
7784
|
+
});
|
|
7785
|
+
} catch {
|
|
7786
|
+
res.writeHead(400, {
|
|
7787
|
+
"content-type": "application/json",
|
|
7788
|
+
...cors,
|
|
7789
|
+
...SECURITY_HEADERS
|
|
7790
|
+
});
|
|
7791
|
+
res.end(JSON.stringify({ ok: false, error: "key rejected" }));
|
|
7792
|
+
return;
|
|
7793
|
+
}
|
|
7794
|
+
res.writeHead(200, {
|
|
7795
|
+
"content-type": "application/json",
|
|
7796
|
+
...cors,
|
|
7797
|
+
...SECURITY_HEADERS
|
|
7798
|
+
});
|
|
7799
|
+
res.end(JSON.stringify({ ok: true }));
|
|
7800
|
+
return;
|
|
7801
|
+
}
|
|
6267
7802
|
if (!preflightVerified) {
|
|
6268
7803
|
res.writeHead(428, {
|
|
6269
7804
|
"content-type": "text/html",
|
|
@@ -6346,13 +7881,43 @@ async function startStdioLogin(opts) {
|
|
|
6346
7881
|
}
|
|
6347
7882
|
|
|
6348
7883
|
// src/spend-account-pay.ts
|
|
6349
|
-
import { Transaction as
|
|
7884
|
+
import { Transaction as Transaction8 } from "@mysten/sui/transactions";
|
|
6350
7885
|
import {
|
|
6351
|
-
fromBase64 as
|
|
6352
|
-
normalizeSuiAddress as
|
|
6353
|
-
toBase64 as
|
|
7886
|
+
fromBase64 as fromBase6411,
|
|
7887
|
+
normalizeSuiAddress as normalizeSuiAddress14,
|
|
7888
|
+
toBase64 as toBase647
|
|
6354
7889
|
} from "@mysten/sui/utils";
|
|
7890
|
+
|
|
7891
|
+
// ../sdk/dist/pay-operation/verify-x402-offer.js
|
|
7892
|
+
import { normalizeStructTag as normalizeStructTag7, normalizeSuiAddress as normalizeSuiAddress12 } from "@mysten/sui/utils";
|
|
7893
|
+
|
|
7894
|
+
// ../sdk/dist/pay-operation/verify-direct-tx.js
|
|
7895
|
+
import { Transaction as Transaction6 } from "@mysten/sui/transactions";
|
|
7896
|
+
import { bcs as bcs7 } from "@mysten/sui/bcs";
|
|
7897
|
+
import { fromBase64 as fromBase649, normalizeStructTag as normalizeStructTag8, normalizeSuiAddress as normalizeSuiAddress13 } from "@mysten/sui/utils";
|
|
7898
|
+
|
|
7899
|
+
// ../sdk/dist/pay-operation/resolve-grant-x402.js
|
|
7900
|
+
import { Transaction as Transaction7 } from "@mysten/sui/transactions";
|
|
7901
|
+
import { fromBase64 as fromBase6410, toBase64 as toBase646 } from "@mysten/sui/utils";
|
|
7902
|
+
|
|
7903
|
+
// src/spend-account-pay.ts
|
|
6355
7904
|
var SUI_CLOCK3 = "0x6";
|
|
7905
|
+
var sharedObjectReaderForTests = null;
|
|
7906
|
+
function chainSharedObjectReader(cfg, packageId) {
|
|
7907
|
+
const chain = createSpendAccountChain({
|
|
7908
|
+
packageId,
|
|
7909
|
+
network: cfg.network,
|
|
7910
|
+
url: cfg.rpcUrl
|
|
7911
|
+
});
|
|
7912
|
+
return async (objectId) => {
|
|
7913
|
+
const { object } = await chain.client.core.getObject({
|
|
7914
|
+
objectId,
|
|
7915
|
+
include: { json: true }
|
|
7916
|
+
});
|
|
7917
|
+
if (!object?.owner) return null;
|
|
7918
|
+
return { owner: object.owner };
|
|
7919
|
+
};
|
|
7920
|
+
}
|
|
6356
7921
|
function traceEmitter(trace) {
|
|
6357
7922
|
if (!trace) return () => void 0;
|
|
6358
7923
|
const now = trace.now ?? Date.now;
|
|
@@ -6386,25 +7951,38 @@ function toHex(bytes) {
|
|
|
6386
7951
|
async function buildSpendAccountPayKind2(input) {
|
|
6387
7952
|
if (!input.termsHash.length) throw new Error("termsHash must be non-empty");
|
|
6388
7953
|
if (!input.paymentIdHash.length) throw new Error("paymentIdHash must be non-empty");
|
|
6389
|
-
const auth = defaultAuthorityForRail("delegate", input.packageId);
|
|
6390
|
-
const target = `${normalizeSuiAddress11(auth.packageId)}::${auth.module}::${auth.payFunction}`;
|
|
6391
7954
|
const hashHex = input.targetHash.replace(/^0x/i, "").toLowerCase();
|
|
6392
7955
|
if (!/^[0-9a-f]{64}$/.test(hashHex)) throw new Error("targetHash must be 32-byte hex");
|
|
6393
7956
|
const hash = Uint8Array.from(Buffer.from(hashHex, "hex"));
|
|
6394
|
-
const
|
|
7957
|
+
const guarded = resolveGuardedSettleOfflineInputs(input);
|
|
7958
|
+
const target = payMoveCallTarget(
|
|
7959
|
+
input.packageId,
|
|
7960
|
+
"scoped"
|
|
7961
|
+
);
|
|
7962
|
+
const tx = new Transaction8();
|
|
6395
7963
|
tx.setSender(input.delegate);
|
|
6396
7964
|
tx.moveCall({
|
|
6397
7965
|
target,
|
|
6398
7966
|
typeArguments: [input.coinType],
|
|
6399
7967
|
arguments: [
|
|
7968
|
+
tx.sharedObjectRef({
|
|
7969
|
+
objectId: guarded.exclusionDirectoryId,
|
|
7970
|
+
initialSharedVersion: guarded.exclusionDirectoryInitialSharedVersion,
|
|
7971
|
+
mutable: false
|
|
7972
|
+
}),
|
|
7973
|
+
tx.sharedObjectRef({
|
|
7974
|
+
objectId: guarded.recipientRegistryId,
|
|
7975
|
+
initialSharedVersion: guarded.recipientRegistryInitialSharedVersion,
|
|
7976
|
+
mutable: true
|
|
7977
|
+
}),
|
|
6400
7978
|
tx.sharedObjectRef({
|
|
6401
7979
|
objectId: input.poolObjectId,
|
|
6402
|
-
initialSharedVersion: "1",
|
|
7980
|
+
initialSharedVersion: input.poolInitialSharedVersion?.trim() || "1",
|
|
6403
7981
|
mutable: true
|
|
6404
7982
|
}),
|
|
6405
7983
|
tx.sharedObjectRef({
|
|
6406
7984
|
objectId: input.grantObjectId,
|
|
6407
|
-
initialSharedVersion: "1",
|
|
7985
|
+
initialSharedVersion: input.grantInitialSharedVersion?.trim() || "1",
|
|
6408
7986
|
mutable: true
|
|
6409
7987
|
}),
|
|
6410
7988
|
tx.pure.u64(BigInt(input.policyId)),
|
|
@@ -6412,42 +7990,43 @@ async function buildSpendAccountPayKind2(input) {
|
|
|
6412
7990
|
tx.pure.u64(BigInt(input.amount)),
|
|
6413
7991
|
tx.pure.vector("u8", Array.from(input.paymentIdHash)),
|
|
6414
7992
|
tx.pure.vector("u8", Array.from(input.termsHash)),
|
|
7993
|
+
tx.pure.u64(BigInt(guarded.expiresAtMs)),
|
|
6415
7994
|
tx.sharedObjectRef({
|
|
6416
7995
|
objectId: SUI_CLOCK3,
|
|
6417
|
-
initialSharedVersion: "1",
|
|
7996
|
+
initialSharedVersion: input.clockInitialSharedVersion?.trim() || "1",
|
|
6418
7997
|
mutable: false
|
|
6419
7998
|
})
|
|
6420
7999
|
]
|
|
6421
8000
|
});
|
|
6422
8001
|
const bytes = await tx.build({ onlyTransactionKind: true });
|
|
6423
|
-
return { kindBytes:
|
|
8002
|
+
return { kindBytes: toBase647(bytes) };
|
|
6424
8003
|
}
|
|
6425
8004
|
function verifySpendAccountPayReconstructs(bytes, expected) {
|
|
6426
8005
|
let data;
|
|
6427
8006
|
try {
|
|
6428
|
-
data =
|
|
8007
|
+
data = Transaction8.from(fromBase6411(bytes)).getData();
|
|
6429
8008
|
} catch {
|
|
6430
8009
|
throw new Error("sponsored transaction is not valid Sui transaction bytes");
|
|
6431
8010
|
}
|
|
6432
|
-
if (!data.sender ||
|
|
8011
|
+
if (!data.sender || normalizeSuiAddress14(data.sender) !== normalizeSuiAddress14(expected.delegate)) {
|
|
6433
8012
|
throw new Error("sponsored transaction sender does not match delegate");
|
|
6434
8013
|
}
|
|
6435
8014
|
if (!data.commands || data.commands.length !== 1) {
|
|
6436
8015
|
throw new Error("sponsored transaction must contain exactly one command");
|
|
6437
8016
|
}
|
|
6438
8017
|
const auth = defaultAuthorityForRail("delegate", expected.packageId);
|
|
6439
|
-
const wanted = `${
|
|
8018
|
+
const wanted = `${normalizeSuiAddress14(auth.packageId)}::${auth.module}::${auth.payFunction}`;
|
|
6440
8019
|
const cmd = data.commands[0];
|
|
6441
8020
|
if (cmd.$kind !== "MoveCall" || !cmd.MoveCall) {
|
|
6442
8021
|
throw new Error("sponsored transaction is not a MoveCall");
|
|
6443
8022
|
}
|
|
6444
|
-
const got = `${
|
|
8023
|
+
const got = `${normalizeSuiAddress14(String(cmd.MoveCall.package))}::${cmd.MoveCall.module}::${cmd.MoveCall.function}`;
|
|
6445
8024
|
if (got !== wanted) {
|
|
6446
8025
|
throw new Error(`sponsored transaction does not reconstruct ${wanted}`);
|
|
6447
8026
|
}
|
|
6448
8027
|
let expectedKind;
|
|
6449
8028
|
try {
|
|
6450
|
-
expectedKind =
|
|
8029
|
+
expectedKind = Transaction8.fromKind(fromBase6411(expected.kindBytes)).getData();
|
|
6451
8030
|
} catch {
|
|
6452
8031
|
throw new Error("sponsored transaction kind baseline invalid");
|
|
6453
8032
|
}
|
|
@@ -6479,6 +8058,13 @@ async function prepareSpendAccountPayment(args) {
|
|
|
6479
8058
|
const isOpen = session.snapshot.accessMode === "open";
|
|
6480
8059
|
const callerBuild = deps.buildPayKind;
|
|
6481
8060
|
const buildPayKind = async (input) => {
|
|
8061
|
+
const guardedFields = {
|
|
8062
|
+
exclusionDirectoryId: input.exclusionDirectoryId,
|
|
8063
|
+
recipientRegistryId: input.recipientRegistryId,
|
|
8064
|
+
expiresAtMs: input.expiresAtMs,
|
|
8065
|
+
exclusionDirectoryInitialSharedVersion: input.exclusionDirectoryInitialSharedVersion,
|
|
8066
|
+
recipientRegistryInitialSharedVersion: input.recipientRegistryInitialSharedVersion
|
|
8067
|
+
};
|
|
6482
8068
|
if (callerBuild) {
|
|
6483
8069
|
return callerBuild({
|
|
6484
8070
|
delegate: input.delegate,
|
|
@@ -6491,9 +8077,16 @@ async function prepareSpendAccountPayment(args) {
|
|
|
6491
8077
|
paymentIdHash: input.paymentIdHash,
|
|
6492
8078
|
termsHash: input.termsHash,
|
|
6493
8079
|
recipient: input.recipient,
|
|
6494
|
-
accessMode: input.accessMode
|
|
8080
|
+
accessMode: input.accessMode,
|
|
8081
|
+
...guardedFields
|
|
6495
8082
|
});
|
|
6496
8083
|
}
|
|
8084
|
+
const readSharedObject = deps.readSharedObject ?? sharedObjectReaderForTests ?? chainSharedObjectReader(cfg, input.packageId);
|
|
8085
|
+
const versions = await resolveSharedObjectVersionsFromGetObject(readSharedObject, {
|
|
8086
|
+
poolObjectId: input.poolObjectId,
|
|
8087
|
+
grantObjectId: input.grantObjectId,
|
|
8088
|
+
clockObjectId: SUI_CLOCK3
|
|
8089
|
+
});
|
|
6497
8090
|
if (isOpen) {
|
|
6498
8091
|
return buildSpendAccountSettleOpenRecipientKind({
|
|
6499
8092
|
packageId: input.packageId,
|
|
@@ -6501,13 +8094,15 @@ async function prepareSpendAccountPayment(args) {
|
|
|
6501
8094
|
coinType: input.coinType,
|
|
6502
8095
|
poolObjectId: input.poolObjectId,
|
|
6503
8096
|
grantObjectId: input.grantObjectId,
|
|
6504
|
-
poolInitialSharedVersion:
|
|
6505
|
-
grantInitialSharedVersion:
|
|
8097
|
+
poolInitialSharedVersion: versions.poolInitialSharedVersion,
|
|
8098
|
+
grantInitialSharedVersion: versions.grantInitialSharedVersion,
|
|
8099
|
+
clockInitialSharedVersion: versions.clockInitialSharedVersion,
|
|
6506
8100
|
recipient: input.recipient,
|
|
6507
8101
|
targetHash: input.targetHash,
|
|
6508
8102
|
amount: input.amount,
|
|
6509
8103
|
paymentIdHash: input.paymentIdHash,
|
|
6510
|
-
termsHash: input.termsHash
|
|
8104
|
+
termsHash: input.termsHash,
|
|
8105
|
+
...guardedFields
|
|
6511
8106
|
});
|
|
6512
8107
|
}
|
|
6513
8108
|
return buildSpendAccountPayKind2({
|
|
@@ -6520,7 +8115,11 @@ async function prepareSpendAccountPayment(args) {
|
|
|
6520
8115
|
targetHash: input.targetHash,
|
|
6521
8116
|
amount: input.amount,
|
|
6522
8117
|
paymentIdHash: input.paymentIdHash,
|
|
6523
|
-
termsHash: input.termsHash
|
|
8118
|
+
termsHash: input.termsHash,
|
|
8119
|
+
poolInitialSharedVersion: versions.poolInitialSharedVersion,
|
|
8120
|
+
grantInitialSharedVersion: versions.grantInitialSharedVersion,
|
|
8121
|
+
clockInitialSharedVersion: versions.clockInitialSharedVersion,
|
|
8122
|
+
...guardedFields
|
|
6524
8123
|
});
|
|
6525
8124
|
};
|
|
6526
8125
|
const rawFetch = deps.fetchImpl ?? fetch;
|
|
@@ -6540,10 +8139,20 @@ async function prepareSpendAccountPayment(args) {
|
|
|
6540
8139
|
authority: session.snapshot,
|
|
6541
8140
|
assertLive: session.assertLive
|
|
6542
8141
|
};
|
|
8142
|
+
const directorySwitch = cfg.exclusionDirectoryObjectId ? cfg.exclusionDirectoryObjectId : deps.resolveExclusionRegistry ? null : await publishedExclusionDirectoryId(cfg.gatewayUrl);
|
|
8143
|
+
const resolveExclusionRegistry2 = deps.resolveExclusionRegistry ?? (directorySwitch ? (() => {
|
|
8144
|
+
const reader = createGrpcExclusionRegistryReader(rpcClient(cfg));
|
|
8145
|
+
return async ({ directoryId, recipient }) => resolveExclusionRegistry({
|
|
8146
|
+
directoryObjectId: directoryId,
|
|
8147
|
+
recipient,
|
|
8148
|
+
reader
|
|
8149
|
+
});
|
|
8150
|
+
})() : void 0);
|
|
6543
8151
|
const resolveDeps = {
|
|
6544
8152
|
fetchImpl: wrappedFetch,
|
|
6545
8153
|
preferredDialect: preferred,
|
|
6546
|
-
buildPayKind
|
|
8154
|
+
buildPayKind,
|
|
8155
|
+
...resolveExclusionRegistry2 ? { resolveExclusionRegistry: resolveExclusionRegistry2 } : {}
|
|
6547
8156
|
};
|
|
6548
8157
|
const result = await resolvePayment(resolveInput, resolveDeps);
|
|
6549
8158
|
if (result.status === "unpaid_ok") {
|
|
@@ -6604,7 +8213,7 @@ async function prepareSpendAccountPayment(args) {
|
|
|
6604
8213
|
const signingRequest = {
|
|
6605
8214
|
unsignedKindBytes: kindBytes,
|
|
6606
8215
|
delegateAddress: resolved.delegateAddress,
|
|
6607
|
-
packageId:
|
|
8216
|
+
packageId: normalizeSuiAddress14(authority.packageId),
|
|
6608
8217
|
poolObjectId: resolved.poolObjectId,
|
|
6609
8218
|
grantObjectId: resolved.grantObjectId,
|
|
6610
8219
|
policyId: resolved.onChainPolicyId,
|
|
@@ -6686,11 +8295,11 @@ function mapErrorCode(code) {
|
|
|
6686
8295
|
}
|
|
6687
8296
|
|
|
6688
8297
|
// src/discover-afford.ts
|
|
6689
|
-
import { normalizeStructTag as
|
|
8298
|
+
import { normalizeStructTag as normalizeStructTag9 } from "@mysten/sui/utils";
|
|
6690
8299
|
function sameCoinType2(rowAsset, grantCoin) {
|
|
6691
8300
|
if (typeof rowAsset !== "string" || !grantCoin) return false;
|
|
6692
8301
|
try {
|
|
6693
|
-
return
|
|
8302
|
+
return normalizeStructTag9(rowAsset) === normalizeStructTag9(grantCoin);
|
|
6694
8303
|
} catch {
|
|
6695
8304
|
return false;
|
|
6696
8305
|
}
|
|
@@ -6872,6 +8481,59 @@ function joinDiscoverCatalog(opts) {
|
|
|
6872
8481
|
return scoped ? { accessMode: "scoped", count: services.length, services, allowedTargetHashes } : { accessMode: "open", count: services.length, services };
|
|
6873
8482
|
}
|
|
6874
8483
|
|
|
8484
|
+
// src/tool-result.ts
|
|
8485
|
+
import { pathToFileURL } from "url";
|
|
8486
|
+
|
|
8487
|
+
// src/delivery-file.ts
|
|
8488
|
+
import { mkdirSync as mkdirSync2, writeFileSync, chmodSync as chmodSync2 } from "fs";
|
|
8489
|
+
import { dirname as dirname3, join as join4 } from "path";
|
|
8490
|
+
function mcpDeliveriesDir() {
|
|
8491
|
+
return join4(dirname3(mcpCredentialsPath()), "deliveries");
|
|
8492
|
+
}
|
|
8493
|
+
var EXT = {
|
|
8494
|
+
"image/png": ".png",
|
|
8495
|
+
"image/jpeg": ".jpg",
|
|
8496
|
+
"image/jpg": ".jpg",
|
|
8497
|
+
"image/webp": ".webp",
|
|
8498
|
+
"image/gif": ".gif",
|
|
8499
|
+
"image/svg+xml": ".svg",
|
|
8500
|
+
"audio/mpeg": ".mp3",
|
|
8501
|
+
"audio/wav": ".wav",
|
|
8502
|
+
"audio/ogg": ".ogg",
|
|
8503
|
+
"video/mp4": ".mp4",
|
|
8504
|
+
"application/pdf": ".pdf",
|
|
8505
|
+
"application/json": ".json",
|
|
8506
|
+
"text/plain": ".txt"
|
|
8507
|
+
};
|
|
8508
|
+
function extensionFor(contentType) {
|
|
8509
|
+
const type = contentType.split(";")[0]?.trim().toLowerCase() ?? "";
|
|
8510
|
+
return EXT[type] ?? ".bin";
|
|
8511
|
+
}
|
|
8512
|
+
function fileStem(digest, challengeId) {
|
|
8513
|
+
const raw = (digest ?? challengeId ?? `delivery-${Date.now()}`).trim();
|
|
8514
|
+
const safe = raw.replace(/[^A-Za-z0-9._-]+/g, "-").replace(/^-+|-+$/g, "");
|
|
8515
|
+
return (safe.slice(0, 80) || `delivery-${Date.now()}`).replace(/^\.+$/, "delivery");
|
|
8516
|
+
}
|
|
8517
|
+
function saveDeliveredBody(input) {
|
|
8518
|
+
if (input.bytes.byteLength === 0) return void 0;
|
|
8519
|
+
const dir = mcpDeliveriesDir();
|
|
8520
|
+
mkdirSync2(dir, { recursive: true, mode: 448 });
|
|
8521
|
+
try {
|
|
8522
|
+
chmodSync2(dir, 448);
|
|
8523
|
+
} catch {
|
|
8524
|
+
}
|
|
8525
|
+
const path = join4(
|
|
8526
|
+
dir,
|
|
8527
|
+
`${fileStem(input.digest, input.challengeId)}${extensionFor(input.contentType)}`
|
|
8528
|
+
);
|
|
8529
|
+
writeFileSync(path, input.bytes, { mode: 384 });
|
|
8530
|
+
try {
|
|
8531
|
+
chmodSync2(path, 384);
|
|
8532
|
+
} catch {
|
|
8533
|
+
}
|
|
8534
|
+
return path;
|
|
8535
|
+
}
|
|
8536
|
+
|
|
6875
8537
|
// src/tool-result.ts
|
|
6876
8538
|
var MAX_MCP_IMAGE_BYTES = 5 * 1024 * 1024;
|
|
6877
8539
|
function text(value) {
|
|
@@ -6893,6 +8555,12 @@ function closedError(code, detail) {
|
|
|
6893
8555
|
function closedPayError(code, detail) {
|
|
6894
8556
|
return error({ status: "failed", paid: false, code, detail });
|
|
6895
8557
|
}
|
|
8558
|
+
function receiptChallengeId(receipt) {
|
|
8559
|
+
if (!receipt || typeof receipt !== "object") return void 0;
|
|
8560
|
+
const id = receipt.challengeId;
|
|
8561
|
+
return typeof id === "string" && id.length > 0 ? id : void 0;
|
|
8562
|
+
}
|
|
8563
|
+
var MCP_SAVE_ERROR = "delivery file was not written; do not call pay again";
|
|
6896
8564
|
function paidResult(result) {
|
|
6897
8565
|
if (!isBinaryBody(result.body)) return text(result);
|
|
6898
8566
|
const body = result.body;
|
|
@@ -6901,8 +8569,33 @@ function paidResult(result) {
|
|
|
6901
8569
|
contentType: body.contentType,
|
|
6902
8570
|
byteLength: body.byteLength
|
|
6903
8571
|
};
|
|
8572
|
+
let savedTo;
|
|
8573
|
+
try {
|
|
8574
|
+
savedTo = saveDeliveredBody({
|
|
8575
|
+
bytes: body.bytes,
|
|
8576
|
+
contentType: body.contentType,
|
|
8577
|
+
digest: result.txDigest,
|
|
8578
|
+
challengeId: receiptChallengeId(result.receipt)
|
|
8579
|
+
});
|
|
8580
|
+
} catch {
|
|
8581
|
+
savedTo = void 0;
|
|
8582
|
+
}
|
|
8583
|
+
const payload = savedTo ? {
|
|
8584
|
+
savedTo,
|
|
8585
|
+
savedToUrl: pathToFileURL(savedTo).href,
|
|
8586
|
+
...result,
|
|
8587
|
+
body: shape
|
|
8588
|
+
} : {
|
|
8589
|
+
savedTo: null,
|
|
8590
|
+
saveError: MCP_SAVE_ERROR,
|
|
8591
|
+
...result,
|
|
8592
|
+
body: shape
|
|
8593
|
+
};
|
|
6904
8594
|
const content = [
|
|
6905
|
-
{
|
|
8595
|
+
{
|
|
8596
|
+
type: "text",
|
|
8597
|
+
text: JSON.stringify(payload, null, 2)
|
|
8598
|
+
}
|
|
6906
8599
|
];
|
|
6907
8600
|
if (body.contentType.startsWith("image/") && body.bytes.byteLength > 0 && body.bytes.byteLength <= MAX_MCP_IMAGE_BYTES) {
|
|
6908
8601
|
content.push({
|
|
@@ -6919,14 +8612,27 @@ function settlePayResult(result) {
|
|
|
6919
8612
|
}
|
|
6920
8613
|
|
|
6921
8614
|
// src/pay-file.ts
|
|
6922
|
-
import { isAbsolute } from "path";
|
|
6923
|
-
import {
|
|
6924
|
-
|
|
8615
|
+
import { isAbsolute, dirname as dirname4, sep } from "path";
|
|
8616
|
+
import {
|
|
8617
|
+
openSync as openSync2,
|
|
8618
|
+
closeSync as closeSync2,
|
|
8619
|
+
fstatSync,
|
|
8620
|
+
readFileSync as readFileSync3,
|
|
8621
|
+
realpathSync,
|
|
8622
|
+
constants
|
|
8623
|
+
} from "fs";
|
|
8624
|
+
var JSON_WRAP_SLACK_BYTES = 256;
|
|
8625
|
+
var MCP_MAX_FILE_FIELD_BYTES = Math.floor(
|
|
8626
|
+
(MAX_PAID_BODY_BYTES - JSON_WRAP_SLACK_BYTES) * 3 / 4
|
|
8627
|
+
);
|
|
6925
8628
|
var FIELD = /^[A-Za-z][A-Za-z0-9_]{0,63}$/;
|
|
6926
|
-
var FORBIDDEN = /* @__PURE__ */ new Set(["
|
|
8629
|
+
var FORBIDDEN = /* @__PURE__ */ new Set(["constructor", "prototype"]);
|
|
6927
8630
|
function fail2(code, detail) {
|
|
6928
8631
|
return { code, detail };
|
|
6929
8632
|
}
|
|
8633
|
+
function hasLocalFileArgs(input) {
|
|
8634
|
+
return input.file !== void 0 && input.file !== null || input.fileField !== void 0 && input.fileField !== null;
|
|
8635
|
+
}
|
|
6930
8636
|
function parseField(value) {
|
|
6931
8637
|
if (typeof value !== "string" || !FIELD.test(value) || FORBIDDEN.has(value)) {
|
|
6932
8638
|
return fail2("INVALID_FILE_FIELD", "fileField must be a simple JSON key");
|
|
@@ -6942,52 +8648,126 @@ function parsePath(value) {
|
|
|
6942
8648
|
}
|
|
6943
8649
|
return value;
|
|
6944
8650
|
}
|
|
8651
|
+
function parseMethod(value) {
|
|
8652
|
+
if (value === void 0 || value === null) return "GET";
|
|
8653
|
+
if (typeof value !== "string") {
|
|
8654
|
+
return fail2("MALFORMED_REQUEST", "method must be GET or POST");
|
|
8655
|
+
}
|
|
8656
|
+
const upper = value.trim().toUpperCase();
|
|
8657
|
+
if (upper === "GET" || upper === "POST") return upper;
|
|
8658
|
+
return fail2("MALFORMED_REQUEST", "method must be GET or POST");
|
|
8659
|
+
}
|
|
8660
|
+
function inside(root, candidate) {
|
|
8661
|
+
const prefix = root.endsWith(sep) ? root : root + sep;
|
|
8662
|
+
return candidate === root || candidate.startsWith(prefix);
|
|
8663
|
+
}
|
|
8664
|
+
function resolveContainedPath(raw) {
|
|
8665
|
+
const rootRaw = process.env.SUIPAY_MCP_FILE_ROOT;
|
|
8666
|
+
if (typeof rootRaw !== "string" || rootRaw.trim() === "") {
|
|
8667
|
+
return fail2(
|
|
8668
|
+
"FILE_ROOT_UNSET",
|
|
8669
|
+
"file attach requires SUIPAY_MCP_FILE_ROOT to be an absolute directory"
|
|
8670
|
+
);
|
|
8671
|
+
}
|
|
8672
|
+
if (!isAbsolute(rootRaw) || rootRaw.includes("://")) {
|
|
8673
|
+
return fail2(
|
|
8674
|
+
"FILE_ROOT_UNSET",
|
|
8675
|
+
"SUIPAY_MCP_FILE_ROOT must be an absolute filesystem path"
|
|
8676
|
+
);
|
|
8677
|
+
}
|
|
8678
|
+
let rootReal;
|
|
8679
|
+
let fileReal;
|
|
8680
|
+
try {
|
|
8681
|
+
rootReal = realpathSync(rootRaw);
|
|
8682
|
+
} catch {
|
|
8683
|
+
return fail2("FILE_ROOT_UNSET", "SUIPAY_MCP_FILE_ROOT is not a readable directory");
|
|
8684
|
+
}
|
|
8685
|
+
try {
|
|
8686
|
+
fileReal = realpathSync(raw);
|
|
8687
|
+
} catch (err) {
|
|
8688
|
+
const code = err.code;
|
|
8689
|
+
if (code === "ENOENT" || code === "ENOTDIR") {
|
|
8690
|
+
return fail2("FILE_NOT_FOUND", "file does not exist");
|
|
8691
|
+
}
|
|
8692
|
+
if (code === "EACCES") {
|
|
8693
|
+
return fail2("FILE_EACCES", "file is not readable");
|
|
8694
|
+
}
|
|
8695
|
+
return fail2(
|
|
8696
|
+
"INVALID_FILE",
|
|
8697
|
+
`file could not be read (${code ?? "unknown error"})`
|
|
8698
|
+
);
|
|
8699
|
+
}
|
|
8700
|
+
if (!inside(rootReal, fileReal)) {
|
|
8701
|
+
return fail2("FILE_OUTSIDE_ROOT", "file must be under SUIPAY_MCP_FILE_ROOT");
|
|
8702
|
+
}
|
|
8703
|
+
const credsDir2 = dirname4(mcpCredentialsPath());
|
|
8704
|
+
let credsReal = credsDir2;
|
|
8705
|
+
try {
|
|
8706
|
+
credsReal = realpathSync(credsDir2);
|
|
8707
|
+
} catch {
|
|
8708
|
+
}
|
|
8709
|
+
if (inside(credsReal, fileReal)) {
|
|
8710
|
+
return fail2("FILE_FORBIDDEN", "file must not be under ~/.suipay");
|
|
8711
|
+
}
|
|
8712
|
+
return fileReal;
|
|
8713
|
+
}
|
|
6945
8714
|
function readRegularFile(path) {
|
|
8715
|
+
if (constants.O_NOFOLLOW === void 0 || constants.O_NONBLOCK === void 0) {
|
|
8716
|
+
return fail2("INVALID_FILE", "symlink-safe nonblocking open is unavailable on this platform");
|
|
8717
|
+
}
|
|
6946
8718
|
let fd;
|
|
6947
8719
|
try {
|
|
6948
|
-
const flags = constants.O_RDONLY |
|
|
8720
|
+
const flags = constants.O_RDONLY | constants.O_NOFOLLOW | constants.O_NONBLOCK;
|
|
6949
8721
|
fd = openSync2(path, flags);
|
|
6950
8722
|
const st = fstatSync(fd);
|
|
6951
8723
|
if (!st.isFile()) {
|
|
6952
8724
|
return fail2("INVALID_FILE", "file must be a regular file");
|
|
6953
8725
|
}
|
|
6954
|
-
if (st.size === 0) {
|
|
6955
|
-
return fail2("EMPTY_FILE", "file is empty");
|
|
6956
|
-
}
|
|
6957
8726
|
if (st.size > MCP_MAX_FILE_FIELD_BYTES) {
|
|
6958
8727
|
return fail2(
|
|
6959
8728
|
"FILE_TOO_LARGE",
|
|
6960
|
-
`file exceeds ${MCP_MAX_FILE_FIELD_BYTES} decoded bytes`
|
|
8729
|
+
`file is ${st.size} bytes; exceeds ${MCP_MAX_FILE_FIELD_BYTES} decoded bytes`
|
|
6961
8730
|
);
|
|
6962
8731
|
}
|
|
6963
|
-
const bytes =
|
|
8732
|
+
const bytes = readFileSync3(fd);
|
|
6964
8733
|
if (bytes.byteLength === 0) {
|
|
6965
8734
|
return fail2("EMPTY_FILE", "file is empty");
|
|
6966
8735
|
}
|
|
6967
8736
|
if (bytes.byteLength > MCP_MAX_FILE_FIELD_BYTES) {
|
|
6968
8737
|
return fail2(
|
|
6969
8738
|
"FILE_TOO_LARGE",
|
|
6970
|
-
`file exceeds ${MCP_MAX_FILE_FIELD_BYTES} decoded bytes`
|
|
8739
|
+
`file is ${bytes.byteLength} bytes; exceeds ${MCP_MAX_FILE_FIELD_BYTES} decoded bytes`
|
|
6971
8740
|
);
|
|
6972
8741
|
}
|
|
6973
8742
|
return bytes;
|
|
6974
8743
|
} catch (err) {
|
|
6975
8744
|
const code = err.code;
|
|
6976
|
-
if (code === "ENOENT"
|
|
6977
|
-
|
|
8745
|
+
if (code === "ENOENT" || code === "ENOTDIR") {
|
|
8746
|
+
return fail2("FILE_NOT_FOUND", "file does not exist");
|
|
8747
|
+
}
|
|
8748
|
+
if (code === "ELOOP" || code === "EMLINK" || code === "ENXIO") {
|
|
6978
8749
|
return fail2("INVALID_FILE", "file must be a regular file");
|
|
6979
8750
|
}
|
|
6980
|
-
return fail2("
|
|
8751
|
+
if (code === "EACCES") return fail2("FILE_EACCES", "file is not readable");
|
|
8752
|
+
return fail2(
|
|
8753
|
+
"INVALID_FILE",
|
|
8754
|
+
`file could not be read (${code ?? "unknown error"})`
|
|
8755
|
+
);
|
|
6981
8756
|
} finally {
|
|
6982
|
-
if (fd !== void 0)
|
|
8757
|
+
if (fd !== void 0) {
|
|
8758
|
+
try {
|
|
8759
|
+
closeSync2(fd);
|
|
8760
|
+
} catch {
|
|
8761
|
+
}
|
|
8762
|
+
}
|
|
6983
8763
|
}
|
|
6984
8764
|
}
|
|
6985
8765
|
function toCanonicalBase64(bytes) {
|
|
6986
8766
|
return Buffer.from(bytes).toString("base64");
|
|
6987
8767
|
}
|
|
6988
8768
|
function jsonWithLocalFile(input) {
|
|
6989
|
-
const hasFile = input.file !== void 0 && input.file !== null
|
|
6990
|
-
const hasField = input.fileField !== void 0 && input.fileField !== null
|
|
8769
|
+
const hasFile = input.file !== void 0 && input.file !== null;
|
|
8770
|
+
const hasField = input.fileField !== void 0 && input.fileField !== null;
|
|
6991
8771
|
if (!hasFile && !hasField) return { json: input.json };
|
|
6992
8772
|
if (hasFile !== hasField) {
|
|
6993
8773
|
return {
|
|
@@ -6997,7 +8777,8 @@ function jsonWithLocalFile(input) {
|
|
|
6997
8777
|
)
|
|
6998
8778
|
};
|
|
6999
8779
|
}
|
|
7000
|
-
const method =
|
|
8780
|
+
const method = parseMethod(input.method);
|
|
8781
|
+
if (typeof method !== "string") return { error: method };
|
|
7001
8782
|
if (method !== "POST") {
|
|
7002
8783
|
return { error: fail2("INVALID_FILE_ARGS", "file is only valid on POST") };
|
|
7003
8784
|
}
|
|
@@ -7021,7 +8802,9 @@ function jsonWithLocalFile(input) {
|
|
|
7021
8802
|
)
|
|
7022
8803
|
};
|
|
7023
8804
|
}
|
|
7024
|
-
const
|
|
8805
|
+
const contained = resolveContainedPath(path);
|
|
8806
|
+
if (typeof contained !== "string") return { error: contained };
|
|
8807
|
+
const bytes = readRegularFile(contained);
|
|
7025
8808
|
if (!(bytes instanceof Uint8Array)) return { error: bytes };
|
|
7026
8809
|
json[field] = toCanonicalBase64(bytes);
|
|
7027
8810
|
return { json };
|
|
@@ -7063,7 +8846,7 @@ function normalizePayIntent(args) {
|
|
|
7063
8846
|
if (!/^0x[0-9a-fA-F]{1,64}$/.test(raw)) {
|
|
7064
8847
|
throw new Error("recipient must be a 0x-prefixed Sui address");
|
|
7065
8848
|
}
|
|
7066
|
-
const normalized =
|
|
8849
|
+
const normalized = normalizeSuiAddress15(raw);
|
|
7067
8850
|
if (!isValidSuiAddress(normalized) || /^0x0+$/.test(normalized)) {
|
|
7068
8851
|
throw new Error("recipient must be a non-zero Sui address");
|
|
7069
8852
|
}
|
|
@@ -7082,7 +8865,7 @@ function intentViolation(intent, prepared) {
|
|
|
7082
8865
|
if (intent.recipient) {
|
|
7083
8866
|
let preparedRecipient;
|
|
7084
8867
|
try {
|
|
7085
|
-
preparedRecipient =
|
|
8868
|
+
preparedRecipient = normalizeSuiAddress15(prepared.recipient);
|
|
7086
8869
|
} catch {
|
|
7087
8870
|
return "prepared payment does not name a valid recipient";
|
|
7088
8871
|
}
|
|
@@ -7105,8 +8888,7 @@ function intentViolation(intent, prepared) {
|
|
|
7105
8888
|
}
|
|
7106
8889
|
async function pay(args, cfg, deps = {}) {
|
|
7107
8890
|
if (!args.url) return error("url is required");
|
|
7108
|
-
|
|
7109
|
-
if (hasFileArg && deps.auth) {
|
|
8891
|
+
if (hasLocalFileArgs(args) && deps.transport === "http") {
|
|
7110
8892
|
return closedPayError(
|
|
7111
8893
|
"FILE_STDIO_ONLY",
|
|
7112
8894
|
"file is stdio only; hosted /mcp does not read the buyer disk"
|
|
@@ -7118,7 +8900,12 @@ async function pay(args, cfg, deps = {}) {
|
|
|
7118
8900
|
} catch {
|
|
7119
8901
|
return closedPayError("INVALID_INTENT", "payment intent is invalid");
|
|
7120
8902
|
}
|
|
7121
|
-
|
|
8903
|
+
let attached;
|
|
8904
|
+
try {
|
|
8905
|
+
attached = jsonWithLocalFile(args);
|
|
8906
|
+
} catch {
|
|
8907
|
+
return closedPayError("INVALID_FILE", "file could not be read");
|
|
8908
|
+
}
|
|
7122
8909
|
if ("error" in attached) {
|
|
7123
8910
|
return closedPayError(attached.error.code, attached.error.detail);
|
|
7124
8911
|
}
|
|
@@ -7128,7 +8915,7 @@ async function pay(args, cfg, deps = {}) {
|
|
|
7128
8915
|
request = normalizeMcpPaidHttpRequest(paidArgs);
|
|
7129
8916
|
} catch (err) {
|
|
7130
8917
|
const raw = err instanceof Error ? err.message : "";
|
|
7131
|
-
const detail = /method/i.test(raw) ? "method must be GET or POST" : /json/i.test(raw) ? "POST requires json" : "request could not be normalized";
|
|
8918
|
+
const detail = /too large/i.test(raw) ? `paid request body exceeds ${MAX_PAID_BODY_BYTES} bytes` : /method/i.test(raw) ? "method must be GET or POST" : /json/i.test(raw) ? "POST requires json" : "request could not be normalized";
|
|
7132
8919
|
return closedPayError("MALFORMED_REQUEST", detail);
|
|
7133
8920
|
}
|
|
7134
8921
|
if (deps.auth?.spendAccountPay) {
|
|
@@ -7153,6 +8940,7 @@ async function pay(args, cfg, deps = {}) {
|
|
|
7153
8940
|
deps: {
|
|
7154
8941
|
...deps.fetchImpl ? { fetchImpl: deps.fetchImpl } : {},
|
|
7155
8942
|
...trace ? { trace } : {},
|
|
8943
|
+
...deps.resolveExclusionRegistry ? { resolveExclusionRegistry: deps.resolveExclusionRegistry } : {},
|
|
7156
8944
|
...deps.buildPayKind ? { buildPayKind: deps.buildPayKind } : liveKindBuilder ? {
|
|
7157
8945
|
buildPayKind: (input) => sp.snapshot.accessMode === "open" && input.recipient ? liveKindBuilder.buildSettleOpenRecipientKind({
|
|
7158
8946
|
...input,
|
|
@@ -7572,10 +9360,15 @@ async function suipayLogin(args, cfg, deps = {}) {
|
|
|
7572
9360
|
});
|
|
7573
9361
|
}
|
|
7574
9362
|
if (live.kind === "unknown") {
|
|
7575
|
-
return
|
|
7576
|
-
status: "
|
|
9363
|
+
return text({
|
|
9364
|
+
status: "ok",
|
|
9365
|
+
reused: true,
|
|
9366
|
+
grantStatus: "unknown",
|
|
7577
9367
|
code: "GRANT_STATUS_UNKNOWN",
|
|
7578
|
-
|
|
9368
|
+
target: target ?? cfg.target,
|
|
9369
|
+
delegateAddress: identity.delegateAddress,
|
|
9370
|
+
gatewayUrl: endpoints.gatewayUrl,
|
|
9371
|
+
message: "Credentials are already saved for this agent. The existing grant could not be checked just now, so no new sign-in was started and the key file was left unchanged. Retry suipay_login in a moment."
|
|
7579
9372
|
});
|
|
7580
9373
|
}
|
|
7581
9374
|
}
|
|
@@ -7585,13 +9378,13 @@ async function suipayLogin(args, cfg, deps = {}) {
|
|
|
7585
9378
|
identity,
|
|
7586
9379
|
label,
|
|
7587
9380
|
timeoutMs: LOGIN_BG_TIMEOUT_MS,
|
|
7588
|
-
openBrowser:
|
|
9381
|
+
openBrowser: true,
|
|
7589
9382
|
onUrl: deps.onUrl
|
|
7590
9383
|
});
|
|
7591
9384
|
const url = started.connectUrl;
|
|
7592
9385
|
return text(
|
|
7593
9386
|
[
|
|
7594
|
-
`## ACTION REQUIRED:
|
|
9387
|
+
`## ACTION REQUIRED: The sign-in page was opened in the browser`,
|
|
7595
9388
|
``,
|
|
7596
9389
|
`**Target:** ${target ?? cfg.target ?? "custom"} \xB7 ${endpoints.gatewayUrl}`,
|
|
7597
9390
|
``,
|
|
@@ -7603,11 +9396,13 @@ async function suipayLogin(args, cfg, deps = {}) {
|
|
|
7603
9396
|
``,
|
|
7604
9397
|
`[Click here to open SuiPay sign-in](${url})`,
|
|
7605
9398
|
``,
|
|
7606
|
-
`**IMPORTANT for the assistant**:
|
|
7607
|
-
`
|
|
9399
|
+
`**IMPORTANT for the assistant**: the browser was already opened to this exact URL.`,
|
|
9400
|
+
`Do NOT navigate to /connect/agent yourself. That path without these query`,
|
|
9401
|
+
`parameters shows "Authorization link incomplete". If the page did not`,
|
|
9402
|
+
`appear, surface the URL above verbatim and do not shorten it.`,
|
|
7608
9403
|
``,
|
|
7609
|
-
`1.
|
|
7610
|
-
`2. Connect the owner wallet
|
|
9404
|
+
`1. Use the browser window that just opened, or open the URL above`,
|
|
9405
|
+
`2. Connect the owner wallet, choose or create a key, then Authorize on the review step`,
|
|
7611
9406
|
`3. Once Connected appears, retry the original request \u2014 credentials at \`~/.suipay/credentials.json\` are picked up on the next tool call`,
|
|
7612
9407
|
``,
|
|
7613
9408
|
`_The login link stays valid for 5 minutes. If it expires, call \`suipay_login\` again._`,
|
|
@@ -7647,7 +9442,7 @@ var TOOLS = [
|
|
|
7647
9442
|
},
|
|
7648
9443
|
{
|
|
7649
9444
|
name: "pay",
|
|
7650
|
-
description: "Pay a HTTP resource via delegate. On hosted /mcp the gateway PREPARES an unsigned transaction and returns it (prepare-only \u2014 it never holds the delegate key and cannot settle). Only a process holding the delegate key the grant names can complete settlement (e.g. local stdio MCP, or any self-hosted process with the matching key). Pass recipient and/or maxAmount to state who you intend to pay and what you will spend; both are checked before anything is signed or handed back. Local V1 allowance pay is retired.",
|
|
9445
|
+
description: "Pay a HTTP resource via delegate. On hosted /mcp the gateway PREPARES an unsigned transaction and returns it (prepare-only \u2014 it never holds the delegate key and cannot settle). Only a process holding the delegate key the grant names can complete settlement (e.g. local stdio MCP, or any self-hosted process with the matching key). Pass recipient and/or maxAmount to state who you intend to pay and what you will spend; both are checked before anything is signed or handed back. Local V1 allowance pay is retired. When this process settles a paid binary body, it writes the bytes under ~/.suipay/deliveries/ and names savedTo (absolute path) and savedToUrl (file://) first in the JSON. Hosted /mcp is prepare-only and does not write a buyer file. Always quote savedTo when present. If savedTo is null, quote saveError and do not call pay again. Do not assume the host rendered an image block. Never call pay again to display or save a delivery that already settled.",
|
|
7651
9446
|
inputSchema: {
|
|
7652
9447
|
type: "object",
|
|
7653
9448
|
properties: {
|
|
@@ -7663,11 +9458,11 @@ var TOOLS = [
|
|
|
7663
9458
|
},
|
|
7664
9459
|
file: {
|
|
7665
9460
|
type: "string",
|
|
7666
|
-
description: "Absolute
|
|
9461
|
+
description: "Absolute path under SUIPAY_MCP_FILE_ROOT (unset disables attach). Stdio POST only; requires fileField. Regular non-empty file whose decoded size fits the paid-body ceiling. Not a URL, FIFO, device, or path under ~/.suipay. Bytes fill json[fileField] as canonical base64."
|
|
7667
9462
|
},
|
|
7668
9463
|
fileField: {
|
|
7669
9464
|
type: "string",
|
|
7670
|
-
description: "JSON key to set from file (e.g. contentBase64). Required with file. Must not already be present on json."
|
|
9465
|
+
description: "JSON key to set from file (e.g. contentBase64). Required with file. Pattern [A-Za-z][A-Za-z0-9_]{0,63}. Must not already be present on json."
|
|
7671
9466
|
},
|
|
7672
9467
|
recipient: {
|
|
7673
9468
|
type: "string",
|
|
@@ -7776,6 +9571,7 @@ function toolError(value) {
|
|
|
7776
9571
|
}
|
|
7777
9572
|
function createServer2(cfg, auth, trace, options = {}) {
|
|
7778
9573
|
const resolveAgentHeld = options.resolveAgentHeld ?? resolveAgentHeldSettlement;
|
|
9574
|
+
const transport = options.transport ?? "stdio";
|
|
7779
9575
|
const server = new Server(
|
|
7780
9576
|
{ name: "suipay", version: "1.0.0" },
|
|
7781
9577
|
{
|
|
@@ -7802,21 +9598,32 @@ function createServer2(cfg, auth, trace, options = {}) {
|
|
|
7802
9598
|
return await accessContext({ auth }, cfg);
|
|
7803
9599
|
case "pay": {
|
|
7804
9600
|
const agentHeld = auth?.spendAccountPay ? resolveAgentHeld(cfg, auth.spendAccountPay) : null;
|
|
9601
|
+
const fileArgs = {
|
|
9602
|
+
...args.file !== void 0 ? { file: args.file } : {},
|
|
9603
|
+
...args.fileField !== void 0 ? { fileField: args.fileField } : {}
|
|
9604
|
+
};
|
|
9605
|
+
if (transport === "http" && hasLocalFileArgs(fileArgs)) {
|
|
9606
|
+
return closedPayError(
|
|
9607
|
+
"FILE_STDIO_ONLY",
|
|
9608
|
+
"file is stdio only; hosted /mcp does not read the buyer disk"
|
|
9609
|
+
);
|
|
9610
|
+
}
|
|
7805
9611
|
return await pay(
|
|
7806
9612
|
{
|
|
7807
9613
|
url: String(args.url ?? ""),
|
|
7808
9614
|
...args.method !== void 0 ? { method: args.method } : {},
|
|
7809
9615
|
...args.json !== void 0 ? { json: args.json } : {},
|
|
7810
|
-
...
|
|
7811
|
-
...args.fileField !== void 0 ? { fileField: args.fileField } : {},
|
|
9616
|
+
...transport === "stdio" ? fileArgs : {},
|
|
7812
9617
|
...args.recipient !== void 0 ? { recipient: args.recipient } : {},
|
|
7813
9618
|
...args.maxAmount !== void 0 ? { maxAmount: args.maxAmount } : {}
|
|
7814
9619
|
},
|
|
7815
9620
|
cfg,
|
|
7816
9621
|
{
|
|
7817
9622
|
auth,
|
|
9623
|
+
transport,
|
|
7818
9624
|
...trace ? { trace } : {},
|
|
7819
|
-
...agentHeld ? { agentHeld } : {}
|
|
9625
|
+
...agentHeld ? { agentHeld } : {},
|
|
9626
|
+
...options.resolveExclusionRegistry ? { resolveExclusionRegistry: options.resolveExclusionRegistry } : {}
|
|
7820
9627
|
}
|
|
7821
9628
|
);
|
|
7822
9629
|
}
|
|
@@ -7903,6 +9710,11 @@ function canonicalMcpRequestId(id) {
|
|
|
7903
9710
|
function asRecord(value) {
|
|
7904
9711
|
return typeof value === "object" && value !== null && !Array.isArray(value) ? value : null;
|
|
7905
9712
|
}
|
|
9713
|
+
function redactPayFileArg(args) {
|
|
9714
|
+
const record = asRecord(args);
|
|
9715
|
+
if (!record || !("file" in record)) return args;
|
|
9716
|
+
return { ...record, file: "[redacted]" };
|
|
9717
|
+
}
|
|
7906
9718
|
function readEntry(message) {
|
|
7907
9719
|
const record = asRecord(message);
|
|
7908
9720
|
if (!record) return null;
|
|
@@ -7914,7 +9726,7 @@ function readEntry(message) {
|
|
|
7914
9726
|
jsonRpcId: typeof rawId === "string" || typeof rawId === "number" ? rawId : null,
|
|
7915
9727
|
requestId: canonicalMcpRequestId(rawId),
|
|
7916
9728
|
toolName: params && typeof params.name === "string" ? params.name : null,
|
|
7917
|
-
toolArguments: params ? params.arguments : void 0,
|
|
9729
|
+
toolArguments: params ? redactPayFileArg(params.arguments) : void 0,
|
|
7918
9730
|
clientName: clientInfo && typeof clientInfo.name === "string" ? clientInfo.name : null,
|
|
7919
9731
|
clientVersion: clientInfo && typeof clientInfo.version === "string" ? clientInfo.version : null,
|
|
7920
9732
|
protocolVersion: params && typeof params.protocolVersion === "string" ? params.protocolVersion : null
|
|
@@ -8344,7 +10156,10 @@ async function handleSuipayMcpHttpRequest(req, cfg, auth, trace, options) {
|
|
|
8344
10156
|
sessionIdGenerator: void 0,
|
|
8345
10157
|
enableJsonResponse: true
|
|
8346
10158
|
});
|
|
8347
|
-
const server = createServer2(cfg, auth, hooks,
|
|
10159
|
+
const server = createServer2(cfg, auth, hooks, {
|
|
10160
|
+
...options,
|
|
10161
|
+
transport: "http"
|
|
10162
|
+
});
|
|
8348
10163
|
await server.connect(transport);
|
|
8349
10164
|
return transport.handleRequest(request);
|
|
8350
10165
|
}
|