@cmdoss/suipay-mcp 0.2.2-dev.1 → 0.2.2-dev.11
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 +41 -9
- package/dist/bin/suipay.js +2 -2
- package/dist/{chunk-K6WM4Z7H.js → chunk-6GJ7IGYM.js} +2 -2
- package/dist/{chunk-NVTYOOMY.js → chunk-WRN7FRUI.js} +2180 -301
- package/dist/{http-B4YTVw0k.d.ts → http-RP-Xlg60.d.ts} +257 -4
- package/dist/http.d.ts +4 -1
- package/dist/http.js +1 -1
- package/dist/index.d.ts +14 -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,46 @@ 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
|
+
}
|
|
1494
1815
|
|
|
1495
1816
|
// ../sdk/dist/protocol/mpp.js
|
|
1496
1817
|
function offerToBase64Url(offer) {
|
|
@@ -1501,7 +1822,66 @@ var MPP_METHOD = "sui.charge";
|
|
|
1501
1822
|
var MPP_CHALLENGE_HEADER = "WWW-Authenticate";
|
|
1502
1823
|
var MPP_CREDENTIAL_HEADER = "Authorization";
|
|
1503
1824
|
var MPP_RECEIPT_HEADER = "Payment-Receipt";
|
|
1504
|
-
function
|
|
1825
|
+
function splitChallenges(header) {
|
|
1826
|
+
const scheme = `${MPP_SCHEME} `;
|
|
1827
|
+
const value = header.trim();
|
|
1828
|
+
const starts = [];
|
|
1829
|
+
let inQuote = false;
|
|
1830
|
+
for (let i = 0; i < value.length; i++) {
|
|
1831
|
+
const ch = value[i];
|
|
1832
|
+
if (inQuote) {
|
|
1833
|
+
if (ch === "\\") {
|
|
1834
|
+
i++;
|
|
1835
|
+
continue;
|
|
1836
|
+
}
|
|
1837
|
+
if (ch === '"')
|
|
1838
|
+
inQuote = false;
|
|
1839
|
+
continue;
|
|
1840
|
+
}
|
|
1841
|
+
if (ch === '"') {
|
|
1842
|
+
inQuote = true;
|
|
1843
|
+
continue;
|
|
1844
|
+
}
|
|
1845
|
+
if (value.startsWith(scheme, i)) {
|
|
1846
|
+
let j = i - 1;
|
|
1847
|
+
while (j >= 0 && (value[j] === " " || value[j] === " "))
|
|
1848
|
+
j--;
|
|
1849
|
+
if (j < 0 || value[j] === ",")
|
|
1850
|
+
starts.push(i);
|
|
1851
|
+
}
|
|
1852
|
+
}
|
|
1853
|
+
if (starts.length === 0)
|
|
1854
|
+
return [];
|
|
1855
|
+
const parts = [];
|
|
1856
|
+
for (let k = 0; k < starts.length; k++) {
|
|
1857
|
+
const from = starts[k];
|
|
1858
|
+
const to = k + 1 < starts.length ? starts[k + 1] : value.length;
|
|
1859
|
+
parts.push(value.slice(from, to).replace(/,\s*$/, "").trim());
|
|
1860
|
+
}
|
|
1861
|
+
return parts;
|
|
1862
|
+
}
|
|
1863
|
+
function decodeChallenge(header, opts) {
|
|
1864
|
+
const parts = splitChallenges(header);
|
|
1865
|
+
if (parts.length <= 1) {
|
|
1866
|
+
return decodeSingleChallenge(header);
|
|
1867
|
+
}
|
|
1868
|
+
if (opts?.rail !== void 0) {
|
|
1869
|
+
for (const part of parts) {
|
|
1870
|
+
const offer = decodeSingleChallenge(part);
|
|
1871
|
+
if (offer && mintLiveRail(offer.settlementRail ?? "direct") === mintLiveRail(opts.rail)) {
|
|
1872
|
+
return offer;
|
|
1873
|
+
}
|
|
1874
|
+
}
|
|
1875
|
+
return null;
|
|
1876
|
+
}
|
|
1877
|
+
for (const part of parts) {
|
|
1878
|
+
const offer = decodeSingleChallenge(part);
|
|
1879
|
+
if (offer)
|
|
1880
|
+
return offer;
|
|
1881
|
+
}
|
|
1882
|
+
return null;
|
|
1883
|
+
}
|
|
1884
|
+
function decodeSingleChallenge(header) {
|
|
1505
1885
|
const parsed = parseAuthHeader(header);
|
|
1506
1886
|
if (!parsed || parsed.scheme !== MPP_SCHEME)
|
|
1507
1887
|
return null;
|
|
@@ -1549,13 +1929,14 @@ function decodeChallenge(header) {
|
|
|
1549
1929
|
offer.platformFee = p.platform_fee;
|
|
1550
1930
|
if (p.digest_version !== void 0) {
|
|
1551
1931
|
const v = Number(p.digest_version);
|
|
1552
|
-
if (v === 1 || v === 2 || v === 3)
|
|
1932
|
+
if (v === 1 || v === 2 || v === 3 || v === 4)
|
|
1553
1933
|
offer.digestVersion = v;
|
|
1554
1934
|
}
|
|
1555
1935
|
if (p.settlement_rail !== void 0) {
|
|
1556
|
-
|
|
1936
|
+
const rail = decodeWireRail(p.settlement_rail);
|
|
1937
|
+
if (!rail)
|
|
1557
1938
|
return null;
|
|
1558
|
-
offer.settlementRail =
|
|
1939
|
+
offer.settlementRail = rail;
|
|
1559
1940
|
}
|
|
1560
1941
|
if (p.service_id !== void 0)
|
|
1561
1942
|
offer.serviceId = p.service_id;
|
|
@@ -1632,6 +2013,21 @@ function decodeChallenge(header) {
|
|
|
1632
2013
|
if (p.sponsor_gas_payment !== void 0) {
|
|
1633
2014
|
offer.sponsorGasPayment = p.sponsor_gas_payment;
|
|
1634
2015
|
}
|
|
2016
|
+
if (p.accepted_rails !== void 0) {
|
|
2017
|
+
const rails = decodeAcceptedRails(p.accepted_rails.split(","));
|
|
2018
|
+
if (!rails)
|
|
2019
|
+
return null;
|
|
2020
|
+
offer.acceptedRails = rails;
|
|
2021
|
+
}
|
|
2022
|
+
if (p.exclusion_protocol_version !== void 0) {
|
|
2023
|
+
const raw = p.exclusion_protocol_version;
|
|
2024
|
+
const v = Number(raw);
|
|
2025
|
+
if (raw.trim() === "" || !Number.isFinite(v))
|
|
2026
|
+
return null;
|
|
2027
|
+
offer.exclusionProtocolVersion = v;
|
|
2028
|
+
}
|
|
2029
|
+
if (p.registry_identity !== void 0)
|
|
2030
|
+
offer.registryIdentity = p.registry_identity;
|
|
1635
2031
|
return offer;
|
|
1636
2032
|
}
|
|
1637
2033
|
function encodeProof(proof) {
|
|
@@ -1675,12 +2071,128 @@ function decodeReceipt(header) {
|
|
|
1675
2071
|
}
|
|
1676
2072
|
|
|
1677
2073
|
// ../sdk/dist/protocol/x402.js
|
|
2074
|
+
import { fromBase64 as fromBase643, toBase64 as toBase642 } from "@mysten/sui/utils";
|
|
2075
|
+
function utf8ToBase64(text2) {
|
|
2076
|
+
return toBase642(new TextEncoder().encode(text2));
|
|
2077
|
+
}
|
|
2078
|
+
function base64ToUtf8(value) {
|
|
2079
|
+
return new TextDecoder().decode(fromBase643(value));
|
|
2080
|
+
}
|
|
2081
|
+
var X402_VERSION = 2;
|
|
1678
2082
|
var X402_SCHEME = "exact";
|
|
2083
|
+
var X402_PAYMENT_FLOW = "upfront";
|
|
2084
|
+
var X402_ERROR_REASON = {
|
|
2085
|
+
MALFORMED_CREDENTIAL: "invalid_payload",
|
|
2086
|
+
UNKNOWN_CHALLENGE: "invalid_payment_requirements",
|
|
2087
|
+
CHALLENGE_TAMPERED: "invalid_payment_requirements",
|
|
2088
|
+
CHALLENGE_EXPIRED: "invalid_payment_requirements",
|
|
2089
|
+
CHALLENGE_ALREADY_USED: "invalid_transaction_state",
|
|
2090
|
+
CHALLENGE_IN_FLIGHT: "invalid_transaction_state",
|
|
2091
|
+
TRANSACTION_INVALID: "invalid_payload",
|
|
2092
|
+
NETWORK_MISMATCH: "invalid_network",
|
|
2093
|
+
SIGNATURE_INVALID: "invalid_payload",
|
|
2094
|
+
INSUFFICIENT_FUNDS: "insufficient_funds",
|
|
2095
|
+
PAYER_MISMATCH: "invalid_payload",
|
|
2096
|
+
SETTLEMENT_REJECTED: "invalid_transaction_state",
|
|
2097
|
+
SETTLEMENT_MISMATCH: "unexpected_settle_error",
|
|
2098
|
+
AMBIGUOUS: "settlement_pending"
|
|
2099
|
+
};
|
|
1679
2100
|
var X402_CHALLENGE_HEADER = "PAYMENT-REQUIRED";
|
|
2101
|
+
var X402_PAYMENT_HEADER = "PAYMENT-SIGNATURE";
|
|
1680
2102
|
var X402_RESPONSE_HEADER = "PAYMENT-RESPONSE";
|
|
1681
2103
|
var X402_RECEIPT_HEADER = "X-PAYMENT-RESPONSE";
|
|
2104
|
+
function toAccept(offer, authority, opts) {
|
|
2105
|
+
const auth = authority ?? defaultAuthorityForRail(offer.settlementRail ?? "direct", offer.packageId);
|
|
2106
|
+
const payFunction = auth.payFunction;
|
|
2107
|
+
const suipay = {
|
|
2108
|
+
challengeId: offer.challengeId,
|
|
2109
|
+
packageId: offer.packageId,
|
|
2110
|
+
module: auth.module,
|
|
2111
|
+
function: payFunction,
|
|
2112
|
+
decimals: offer.decimals,
|
|
2113
|
+
issuedAt: offer.issuedAt,
|
|
2114
|
+
expiresAt: offer.expiresAt,
|
|
2115
|
+
digest: offer.digest
|
|
2116
|
+
};
|
|
2117
|
+
if (offer.netAmount !== void 0)
|
|
2118
|
+
suipay.netAmount = offer.netAmount;
|
|
2119
|
+
if (offer.platformFee !== void 0)
|
|
2120
|
+
suipay.platformFee = offer.platformFee;
|
|
2121
|
+
if (offer.digestVersion !== void 0)
|
|
2122
|
+
suipay.digestVersion = offer.digestVersion;
|
|
2123
|
+
if (offer.settlementRail !== void 0)
|
|
2124
|
+
suipay.settlementRail = offer.settlementRail;
|
|
2125
|
+
if (offer.serviceId !== void 0)
|
|
2126
|
+
suipay.serviceId = offer.serviceId;
|
|
2127
|
+
if (offer.serviceRevision !== void 0)
|
|
2128
|
+
suipay.serviceRevision = offer.serviceRevision;
|
|
2129
|
+
if (offer.targetHash !== void 0)
|
|
2130
|
+
suipay.targetHash = offer.targetHash;
|
|
2131
|
+
if (offer.authorizationEpoch !== void 0) {
|
|
2132
|
+
suipay.authorizationEpoch = offer.authorizationEpoch;
|
|
2133
|
+
}
|
|
2134
|
+
if (offer.method !== void 0)
|
|
2135
|
+
suipay.method = offer.method;
|
|
2136
|
+
if (offer.path !== void 0)
|
|
2137
|
+
suipay.path = offer.path;
|
|
2138
|
+
if (offer.bodyHash !== void 0)
|
|
2139
|
+
suipay.bodyHash = offer.bodyHash;
|
|
2140
|
+
if (offer.contentType !== void 0)
|
|
2141
|
+
suipay.contentType = offer.contentType;
|
|
2142
|
+
if (offer.intent !== void 0)
|
|
2143
|
+
suipay.intent = offer.intent;
|
|
2144
|
+
if (offer.periodMs !== void 0)
|
|
2145
|
+
suipay.periodMs = offer.periodMs;
|
|
2146
|
+
if (offer.periodCount !== void 0)
|
|
2147
|
+
suipay.periodCount = offer.periodCount;
|
|
2148
|
+
if (offer.refundable !== void 0)
|
|
2149
|
+
suipay.refundable = offer.refundable;
|
|
2150
|
+
if (offer.refundWindowMs !== void 0)
|
|
2151
|
+
suipay.refundWindowMs = offer.refundWindowMs;
|
|
2152
|
+
if (offer.decideWindowMs !== void 0)
|
|
2153
|
+
suipay.decideWindowMs = offer.decideWindowMs;
|
|
2154
|
+
if (offer.chainId !== void 0)
|
|
2155
|
+
suipay.chainId = offer.chainId;
|
|
2156
|
+
if (offer.paymentSalt !== void 0)
|
|
2157
|
+
suipay.paymentSalt = offer.paymentSalt;
|
|
2158
|
+
if (offer.refundTo !== void 0)
|
|
2159
|
+
suipay.refundTo = offer.refundTo;
|
|
2160
|
+
if (offer.sponsorGasOwner !== void 0) {
|
|
2161
|
+
suipay.sponsorGasOwner = offer.sponsorGasOwner;
|
|
2162
|
+
}
|
|
2163
|
+
if (offer.sponsorGasBudget !== void 0) {
|
|
2164
|
+
suipay.sponsorGasBudget = offer.sponsorGasBudget;
|
|
2165
|
+
}
|
|
2166
|
+
if (offer.sponsorGasPrice !== void 0) {
|
|
2167
|
+
suipay.sponsorGasPrice = offer.sponsorGasPrice;
|
|
2168
|
+
}
|
|
2169
|
+
if (offer.sponsorGasPayment !== void 0) {
|
|
2170
|
+
suipay.sponsorGasPayment = offer.sponsorGasPayment;
|
|
2171
|
+
}
|
|
2172
|
+
if (offer.acceptedRails !== void 0)
|
|
2173
|
+
suipay.acceptedRails = offer.acceptedRails;
|
|
2174
|
+
if (offer.exclusionProtocolVersion !== void 0) {
|
|
2175
|
+
suipay.exclusionProtocolVersion = offer.exclusionProtocolVersion;
|
|
2176
|
+
}
|
|
2177
|
+
if (offer.registryIdentity !== void 0)
|
|
2178
|
+
suipay.registryIdentity = offer.registryIdentity;
|
|
2179
|
+
return {
|
|
2180
|
+
scheme: X402_SCHEME,
|
|
2181
|
+
network: offer.network,
|
|
2182
|
+
amount: offer.amount,
|
|
2183
|
+
asset: offer.asset,
|
|
2184
|
+
payTo: offer.payTo,
|
|
2185
|
+
maxTimeoutSeconds: Math.max(1, Math.round((offer.expiresAt - offer.issuedAt) / 1e3)),
|
|
2186
|
+
extra: {
|
|
2187
|
+
paymentFlow: X402_PAYMENT_FLOW,
|
|
2188
|
+
...opts?.gasStation ? { gasStation: opts.gasStation } : {},
|
|
2189
|
+
suipay
|
|
2190
|
+
}
|
|
2191
|
+
};
|
|
2192
|
+
}
|
|
1682
2193
|
function railOf(offer) {
|
|
1683
|
-
|
|
2194
|
+
const rail = offer.settlementRail ?? "direct";
|
|
2195
|
+
return mintLiveRail(rail);
|
|
1684
2196
|
}
|
|
1685
2197
|
function decodeChallenge2(body, opts) {
|
|
1686
2198
|
const b = body;
|
|
@@ -1691,7 +2203,7 @@ function decodeChallenge2(body, opts) {
|
|
|
1691
2203
|
if (a?.scheme !== X402_SCHEME || !a?.extra?.suipay)
|
|
1692
2204
|
continue;
|
|
1693
2205
|
const offer = fromAccept(a, b.resource);
|
|
1694
|
-
if (offer && railOf(offer) === opts.rail)
|
|
2206
|
+
if (offer && railOf(offer) === mintLiveRail(opts.rail))
|
|
1695
2207
|
return offer;
|
|
1696
2208
|
}
|
|
1697
2209
|
return null;
|
|
@@ -1726,13 +2238,14 @@ function fromAccept(accept, resource) {
|
|
|
1726
2238
|
offer.netAmount = s.netAmount;
|
|
1727
2239
|
if (s.platformFee !== void 0)
|
|
1728
2240
|
offer.platformFee = s.platformFee;
|
|
1729
|
-
if (s.digestVersion === 1 || s.digestVersion === 2 || s.digestVersion === 3) {
|
|
2241
|
+
if (s.digestVersion === 1 || s.digestVersion === 2 || s.digestVersion === 3 || s.digestVersion === 4) {
|
|
1730
2242
|
offer.digestVersion = s.digestVersion;
|
|
1731
2243
|
}
|
|
1732
2244
|
if (s.settlementRail !== void 0) {
|
|
1733
|
-
|
|
2245
|
+
const rail = decodeWireRail(s.settlementRail);
|
|
2246
|
+
if (!rail)
|
|
1734
2247
|
return null;
|
|
1735
|
-
offer.settlementRail =
|
|
2248
|
+
offer.settlementRail = rail;
|
|
1736
2249
|
}
|
|
1737
2250
|
if (s.serviceId !== void 0)
|
|
1738
2251
|
offer.serviceId = s.serviceId;
|
|
@@ -1784,11 +2297,71 @@ function fromAccept(accept, resource) {
|
|
|
1784
2297
|
if (s.sponsorGasPayment !== void 0) {
|
|
1785
2298
|
offer.sponsorGasPayment = s.sponsorGasPayment;
|
|
1786
2299
|
}
|
|
2300
|
+
if (s.acceptedRails !== void 0) {
|
|
2301
|
+
if (!Array.isArray(s.acceptedRails))
|
|
2302
|
+
return null;
|
|
2303
|
+
const rails = decodeAcceptedRails(s.acceptedRails);
|
|
2304
|
+
if (!rails)
|
|
2305
|
+
return null;
|
|
2306
|
+
offer.acceptedRails = rails;
|
|
2307
|
+
}
|
|
2308
|
+
if (s.exclusionProtocolVersion !== void 0) {
|
|
2309
|
+
const v = s.exclusionProtocolVersion;
|
|
2310
|
+
if (typeof v !== "number" || !Number.isFinite(v))
|
|
2311
|
+
return null;
|
|
2312
|
+
offer.exclusionProtocolVersion = v;
|
|
2313
|
+
}
|
|
2314
|
+
if (s.registryIdentity !== void 0)
|
|
2315
|
+
offer.registryIdentity = s.registryIdentity;
|
|
1787
2316
|
return offer;
|
|
1788
2317
|
}
|
|
2318
|
+
function encodeCredential(cred) {
|
|
2319
|
+
const payload = {
|
|
2320
|
+
x402Version: X402_VERSION,
|
|
2321
|
+
resource: {
|
|
2322
|
+
url: cred.offer.resource,
|
|
2323
|
+
description: cred.offer.description,
|
|
2324
|
+
mimeType: cred.offer.mimeType
|
|
2325
|
+
},
|
|
2326
|
+
accepted: toAccept(cred.offer),
|
|
2327
|
+
payload: {
|
|
2328
|
+
challengeId: cred.challengeId,
|
|
2329
|
+
payer: cred.payer ?? void 0,
|
|
2330
|
+
transaction: cred.txBytes,
|
|
2331
|
+
signature: cred.signature
|
|
2332
|
+
}
|
|
2333
|
+
};
|
|
2334
|
+
return utf8ToBase64(JSON.stringify(payload));
|
|
2335
|
+
}
|
|
2336
|
+
var X402_ERROR_REASONS = new Set(Object.values(X402_ERROR_REASON));
|
|
2337
|
+
function decodeSettlementResponse(header) {
|
|
2338
|
+
const receipt = decodeReceipt2(header);
|
|
2339
|
+
if (receipt)
|
|
2340
|
+
return receipt;
|
|
2341
|
+
try {
|
|
2342
|
+
const r = JSON.parse(base64ToUtf8(header));
|
|
2343
|
+
if (!r || typeof r !== "object" || r.success !== false)
|
|
2344
|
+
return null;
|
|
2345
|
+
if (typeof r.errorReason !== "string" || !X402_ERROR_REASONS.has(r.errorReason))
|
|
2346
|
+
return null;
|
|
2347
|
+
if (typeof r.network !== "string" || typeof r.transaction !== "string")
|
|
2348
|
+
return null;
|
|
2349
|
+
const failure = {
|
|
2350
|
+
success: false,
|
|
2351
|
+
errorReason: r.errorReason,
|
|
2352
|
+
transaction: r.transaction,
|
|
2353
|
+
network: r.network
|
|
2354
|
+
};
|
|
2355
|
+
if (typeof r.payer === "string")
|
|
2356
|
+
failure.payer = r.payer;
|
|
2357
|
+
return failure;
|
|
2358
|
+
} catch {
|
|
2359
|
+
return null;
|
|
2360
|
+
}
|
|
2361
|
+
}
|
|
1789
2362
|
function decodeReceipt2(header) {
|
|
1790
2363
|
try {
|
|
1791
|
-
const r = JSON.parse(
|
|
2364
|
+
const r = JSON.parse(base64ToUtf8(header));
|
|
1792
2365
|
if (!r?.success || !r.transaction)
|
|
1793
2366
|
return null;
|
|
1794
2367
|
const receipt = {
|
|
@@ -1813,22 +2386,22 @@ function decodeReceipt2(header) {
|
|
|
1813
2386
|
}
|
|
1814
2387
|
|
|
1815
2388
|
// ../sdk/dist/protocol/carrier.js
|
|
1816
|
-
async function readChallenge(res, dialect) {
|
|
2389
|
+
async function readChallenge(res, dialect, opts) {
|
|
1817
2390
|
if (dialect === "mpp") {
|
|
1818
2391
|
const header = res.headers.get(MPP_CHALLENGE_HEADER);
|
|
1819
|
-
return header ? decodeChallenge(header) : null;
|
|
2392
|
+
return header ? decodeChallenge(header, opts) : null;
|
|
1820
2393
|
}
|
|
1821
2394
|
const headerVal = res.headers.get(X402_CHALLENGE_HEADER);
|
|
1822
2395
|
if (headerVal) {
|
|
1823
2396
|
try {
|
|
1824
|
-
const parsed = JSON.parse(
|
|
1825
|
-
const offer = decodeChallenge2(parsed);
|
|
2397
|
+
const parsed = JSON.parse(new TextDecoder().decode(fromBase644(headerVal)));
|
|
2398
|
+
const offer = decodeChallenge2(parsed, opts);
|
|
1826
2399
|
if (offer)
|
|
1827
2400
|
return offer;
|
|
1828
2401
|
} catch {
|
|
1829
2402
|
}
|
|
1830
2403
|
}
|
|
1831
|
-
return decodeChallenge2(await readBody(res));
|
|
2404
|
+
return decodeChallenge2(await readBody(res), opts);
|
|
1832
2405
|
}
|
|
1833
2406
|
function readReceipt(res, dialect) {
|
|
1834
2407
|
const header = dialect === "mpp" ? res.headers.get(MPP_RECEIPT_HEADER) : res.headers.get(X402_RESPONSE_HEADER) ?? res.headers.get(X402_RECEIPT_HEADER);
|
|
@@ -1871,7 +2444,7 @@ async function readBody(res) {
|
|
|
1871
2444
|
configurable: false
|
|
1872
2445
|
});
|
|
1873
2446
|
if (bytes.byteLength <= MAX_INLINE_BINARY_BYTES) {
|
|
1874
|
-
binary.base64 =
|
|
2447
|
+
binary.base64 = toBase643(bytes);
|
|
1875
2448
|
}
|
|
1876
2449
|
return binary;
|
|
1877
2450
|
}
|
|
@@ -1923,10 +2496,10 @@ function requestBindingViolation(offer, request) {
|
|
|
1923
2496
|
if (offer.intent === "subscription" && offer.bodyHash === void 0 && offer.contentType === void 0) {
|
|
1924
2497
|
return null;
|
|
1925
2498
|
}
|
|
1926
|
-
if (offer.digestVersion
|
|
2499
|
+
if ((offer.digestVersion ?? 1) < 3) {
|
|
1927
2500
|
return {
|
|
1928
2501
|
code: "OFFER_NOT_BODY_BOUND",
|
|
1929
|
-
reason: "a paid POST requires an offer digest
|
|
2502
|
+
reason: "a paid POST requires an offer digest that signs the request body"
|
|
1930
2503
|
};
|
|
1931
2504
|
}
|
|
1932
2505
|
if (offer.bodyHash !== request.bodyHash) {
|
|
@@ -2157,24 +2730,74 @@ var PAY_OPERATION_ERROR_CODES = {
|
|
|
2157
2730
|
/** The payment ledger reserve CAS lost (another attempt owns this id). */
|
|
2158
2731
|
PAYMENT_ATTEMPT_IN_FLIGHT: "PAYMENT_ATTEMPT_IN_FLIGHT",
|
|
2159
2732
|
/** The payment ledger could not persist the reservation. */
|
|
2160
|
-
PAYMENT_LEDGER_FAILED: "PAYMENT_LEDGER_FAILED"
|
|
2733
|
+
PAYMENT_LEDGER_FAILED: "PAYMENT_LEDGER_FAILED",
|
|
2734
|
+
/** The offer's HMAC digest failed verification — tampered or wrong secret. */
|
|
2735
|
+
DIGEST_INVALID: "DIGEST_INVALID",
|
|
2736
|
+
/** The offer's expiry is in the past (or issuedAt/expiresAt are incoherent). */
|
|
2737
|
+
OFFER_EXPIRED: "OFFER_EXPIRED",
|
|
2738
|
+
/**
|
|
2739
|
+
* The offer's pinned sponsor gas fields are incomplete or inconsistent
|
|
2740
|
+
* (partial pin, non-numeric budget/price, empty payment) — reject before
|
|
2741
|
+
* building a tx against a gas pin that cannot be honored.
|
|
2742
|
+
*/
|
|
2743
|
+
SPONSOR_GAS_INVALID: "SPONSOR_GAS_INVALID",
|
|
2744
|
+
/**
|
|
2745
|
+
* The recipient / amount / payment_id embedded in the built transaction does
|
|
2746
|
+
* not match the offer — the pre-sign verify-by-rebuild backstop. Nothing may
|
|
2747
|
+
* be signed once this fires.
|
|
2748
|
+
*/
|
|
2749
|
+
TX_OFFER_MISMATCH: "TX_OFFER_MISMATCH",
|
|
2750
|
+
/**
|
|
2751
|
+
* Open-grant x402 is refused. This is PERMANENT and architectural: an open
|
|
2752
|
+
* grant does not pre-bind a recipient, so a buyer cannot verify the recipient
|
|
2753
|
+
* before signing (ADR-0002 / support matrix `open × x402` = unsupported). Do
|
|
2754
|
+
* not conflate with a rail that is merely not built yet.
|
|
2755
|
+
*/
|
|
2756
|
+
UNSUPPORTED_SCOPE: "UNSUPPORTED_SCOPE",
|
|
2757
|
+
/**
|
|
2758
|
+
* A rail/dialect combination that is SUPPORTED by design but not yet
|
|
2759
|
+
* implemented in this build — currently scoped-grant spend_account x402
|
|
2760
|
+
* (the buyer credential-submit flow). Distinct from UNSUPPORTED_SCOPE so an
|
|
2761
|
+
* integrator reads "coming, not yet wired" rather than "permanently refused".
|
|
2762
|
+
* See docs/decisions/ADR-0047-buyer-side-x402-two-paths-one-entrypoint.md.
|
|
2763
|
+
*/
|
|
2764
|
+
RAIL_NOT_YET_IMPLEMENTED: "RAIL_NOT_YET_IMPLEMENTED",
|
|
2765
|
+
/**
|
|
2766
|
+
* Grant rail is configured but required verify-kind RPC context (getObjectJson)
|
|
2767
|
+
* is missing — distinct from RAIL_NOT_YET_IMPLEMENTED, which means the rail
|
|
2768
|
+
* itself is not wired.
|
|
2769
|
+
*/
|
|
2770
|
+
GRANT_VERIFY_MISCONFIGURED: "GRANT_VERIFY_MISCONFIGURED",
|
|
2771
|
+
/** The request URL or method is invalid and cannot be normalized. */
|
|
2772
|
+
REQUEST_INVALID: "REQUEST_INVALID",
|
|
2773
|
+
/** The 402 response body stream failed while reading the challenge. */
|
|
2774
|
+
CHALLENGE_READ_ERROR: "CHALLENGE_READ_ERROR",
|
|
2775
|
+
/**
|
|
2776
|
+
* Pre-sign transaction verification could not reach a verdict due to an RPC
|
|
2777
|
+
* or chain read outage (retryable, not tampering).
|
|
2778
|
+
*/
|
|
2779
|
+
VERIFICATION_UNAVAILABLE: "VERIFICATION_UNAVAILABLE"
|
|
2161
2780
|
};
|
|
2162
2781
|
var CODE_SET = new Set(Object.values(PAY_OPERATION_ERROR_CODES));
|
|
2163
2782
|
|
|
2164
2783
|
// ../sdk/dist/pay-operation/operation.js
|
|
2165
2784
|
async function resolvePayment(input, deps) {
|
|
2166
2785
|
const { url, gatewayUrl, packageId, network, authority, assertLive } = input;
|
|
2167
|
-
const { fetchImpl = fetch, preferredDialect = "mpp", validateOffer, buildPayKind } = deps;
|
|
2786
|
+
const { fetchImpl = fetch, preferredDialect = "mpp", validateOffer, buildPayKind, resolveExclusionRegistry: resolveExclusionRegistry2 } = deps;
|
|
2168
2787
|
const request = input.request ?? normalizePaidHttpRequest({ url, method: "GET" });
|
|
2169
|
-
const
|
|
2170
|
-
if (
|
|
2171
|
-
return failed(PAY_OPERATION_ERROR_CODES.ORIGIN_MISMATCH,
|
|
2788
|
+
const originViolation2 = validateOrigin(url, gatewayUrl);
|
|
2789
|
+
if (originViolation2) {
|
|
2790
|
+
return failed(PAY_OPERATION_ERROR_CODES.ORIGIN_MISMATCH, originViolation2);
|
|
2172
2791
|
}
|
|
2173
2792
|
let response;
|
|
2174
|
-
|
|
2175
|
-
response =
|
|
2176
|
-
}
|
|
2177
|
-
|
|
2793
|
+
if (deps.prefetchedResponse) {
|
|
2794
|
+
response = deps.prefetchedResponse;
|
|
2795
|
+
} else {
|
|
2796
|
+
try {
|
|
2797
|
+
response = await fetchImpl(url, paidRequestInit(request));
|
|
2798
|
+
} catch (e) {
|
|
2799
|
+
return failed(PAY_OPERATION_ERROR_CODES.RESOURCE_UNAVAILABLE, `fetch failed: ${e instanceof Error ? e.message : String(e)}`);
|
|
2800
|
+
}
|
|
2178
2801
|
}
|
|
2179
2802
|
if (response.status !== 402) {
|
|
2180
2803
|
if (response.status >= 200 && response.status < 300) {
|
|
@@ -2203,7 +2826,7 @@ async function resolvePayment(input, deps) {
|
|
|
2203
2826
|
return failed(PAY_OPERATION_ERROR_CODES.REQUEST_BINDING_VIOLATED, bindingViolation.reason, challengeId);
|
|
2204
2827
|
}
|
|
2205
2828
|
if (!isDelegateRail(offer.settlementRail)) {
|
|
2206
|
-
return failed(PAY_OPERATION_ERROR_CODES.UNSUPPORTED_RAIL, `rail ${offer.settlementRail ?? "none"} is not a
|
|
2829
|
+
return failed(PAY_OPERATION_ERROR_CODES.UNSUPPORTED_RAIL, `rail ${offer.settlementRail ?? "none"} is not a delegate rail`, challengeId);
|
|
2207
2830
|
}
|
|
2208
2831
|
let packageMatches;
|
|
2209
2832
|
try {
|
|
@@ -2248,6 +2871,41 @@ async function resolvePayment(input, deps) {
|
|
|
2248
2871
|
recipient: resolved.authority.recipient,
|
|
2249
2872
|
accessMode: resolved.authority.accessMode
|
|
2250
2873
|
};
|
|
2874
|
+
let guarded;
|
|
2875
|
+
if (offerRequiresGuardedSettle(offer)) {
|
|
2876
|
+
const identity = parseRegistryIdentity(offer.registryIdentity);
|
|
2877
|
+
if (!identity) {
|
|
2878
|
+
return failed(PAY_OPERATION_ERROR_CODES.BUILD_FAILED, "guarded offer carries no valid registry identity", challengeId);
|
|
2879
|
+
}
|
|
2880
|
+
if (normalizeSuiAddress3(identity.packageId) !== normalizeSuiAddress3(offer.packageId)) {
|
|
2881
|
+
return failed(PAY_OPERATION_ERROR_CODES.BUILD_FAILED, `guarded registry identity package ${identity.packageId} does not match offer package ${offer.packageId}`, challengeId);
|
|
2882
|
+
}
|
|
2883
|
+
if (!resolveExclusionRegistry2) {
|
|
2884
|
+
return failed(PAY_OPERATION_ERROR_CODES.BUILD_FAILED, "a guarded delegate offer requires an exclusion registry resolver", challengeId);
|
|
2885
|
+
}
|
|
2886
|
+
let registryResolution;
|
|
2887
|
+
try {
|
|
2888
|
+
registryResolution = await resolveExclusionRegistry2({
|
|
2889
|
+
directoryId: identity.directoryId,
|
|
2890
|
+
packageId: offer.packageId,
|
|
2891
|
+
recipient: resolved.authority.recipient
|
|
2892
|
+
});
|
|
2893
|
+
} catch (e) {
|
|
2894
|
+
return failed(PAY_OPERATION_ERROR_CODES.BUILD_FAILED, e instanceof Error ? e.message : String(e), challengeId);
|
|
2895
|
+
}
|
|
2896
|
+
kindInput.exclusionDirectoryId = identity.directoryId;
|
|
2897
|
+
kindInput.recipientRegistryId = registryResolution.recipientRegistryId;
|
|
2898
|
+
kindInput.expiresAtMs = offer.expiresAt;
|
|
2899
|
+
kindInput.exclusionDirectoryInitialSharedVersion = registryResolution.directoryInitialSharedVersion;
|
|
2900
|
+
kindInput.recipientRegistryInitialSharedVersion = registryResolution.registryInitialSharedVersion;
|
|
2901
|
+
guarded = {
|
|
2902
|
+
exclusionDirectoryId: identity.directoryId,
|
|
2903
|
+
recipientRegistryId: registryResolution.recipientRegistryId,
|
|
2904
|
+
expiresAtMs: offer.expiresAt,
|
|
2905
|
+
exclusionDirectoryInitialSharedVersion: registryResolution.directoryInitialSharedVersion,
|
|
2906
|
+
recipientRegistryInitialSharedVersion: registryResolution.registryInitialSharedVersion
|
|
2907
|
+
};
|
|
2908
|
+
}
|
|
2251
2909
|
let kindBytes;
|
|
2252
2910
|
try {
|
|
2253
2911
|
const result = await buildPayKind(kindInput);
|
|
@@ -2264,7 +2922,8 @@ async function resolvePayment(input, deps) {
|
|
|
2264
2922
|
termsHash: tHash,
|
|
2265
2923
|
kindBytes,
|
|
2266
2924
|
paymentId: challengeId,
|
|
2267
|
-
request
|
|
2925
|
+
request,
|
|
2926
|
+
...guarded ? { guarded } : {}
|
|
2268
2927
|
};
|
|
2269
2928
|
}
|
|
2270
2929
|
function failed(code, detail, paymentId) {
|
|
@@ -2314,7 +2973,11 @@ function resolveAuthority(authority, offer) {
|
|
|
2314
2973
|
};
|
|
2315
2974
|
}
|
|
2316
2975
|
if (!offer.targetHash) {
|
|
2317
|
-
return {
|
|
2976
|
+
return {
|
|
2977
|
+
ok: false,
|
|
2978
|
+
code: PAY_OPERATION_ERROR_CODES.POLICY_REJECTED,
|
|
2979
|
+
reason: "offer has no targetHash for scoped resolution"
|
|
2980
|
+
};
|
|
2318
2981
|
}
|
|
2319
2982
|
const result = resolveGrantTarget(authority, offer.targetHash);
|
|
2320
2983
|
if (!result.ok) {
|
|
@@ -2337,8 +3000,8 @@ function resolveAuthority(authority, offer) {
|
|
|
2337
3000
|
};
|
|
2338
3001
|
}
|
|
2339
3002
|
function validateAmount(offer, resolved) {
|
|
2340
|
-
const offerCoinType =
|
|
2341
|
-
const grantCoinType =
|
|
3003
|
+
const offerCoinType = normalizeStructTag5(offer.asset);
|
|
3004
|
+
const grantCoinType = normalizeStructTag5(resolved.coinType);
|
|
2342
3005
|
if (offerCoinType !== grantCoinType) {
|
|
2343
3006
|
return {
|
|
2344
3007
|
code: PAY_OPERATION_ERROR_CODES.COIN_TYPE_MISMATCH,
|
|
@@ -2383,9 +3046,9 @@ function validateAmount(offer, resolved) {
|
|
|
2383
3046
|
// ../sdk/dist/sponsor/personal-message.js
|
|
2384
3047
|
import { randomBytes as randomBytes2 } from "crypto";
|
|
2385
3048
|
import { sha256 as sha2563 } from "@noble/hashes/sha2.js";
|
|
2386
|
-
import { bcs as
|
|
3049
|
+
import { bcs as bcs3 } from "@mysten/sui/bcs";
|
|
2387
3050
|
import { isValidPersonalMessageSignature, verifyPersonalMessageSignature } from "@mysten/sui/verify";
|
|
2388
|
-
import { fromBase64 as
|
|
3051
|
+
import { fromBase64 as fromBase645, normalizeSuiAddress as normalizeSuiAddress4, toBase64 as toBase644 } from "@mysten/sui/utils";
|
|
2389
3052
|
import { Transaction as Transaction3 } from "@mysten/sui/transactions";
|
|
2390
3053
|
var SPONSOR_PERSONAL_MESSAGE_DOMAIN = "suipay:sponsor-personal-message:v1";
|
|
2391
3054
|
function encodeSponsorPersonalMessagePayload(payload) {
|
|
@@ -2406,20 +3069,20 @@ function encodeSponsorPersonalMessagePayload(payload) {
|
|
|
2406
3069
|
throw new Error("network is required");
|
|
2407
3070
|
const parts = [];
|
|
2408
3071
|
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(
|
|
3072
|
+
push(bcs3.vector(bcs3.u8()).serialize([...Buffer.from(payload.domain, "utf8")]).toBytes());
|
|
3073
|
+
push(bcs3.vector(bcs3.u8()).serialize([...Buffer.from(payload.nonce, "utf8")]).toBytes());
|
|
3074
|
+
push(bcs3.u64().serialize(BigInt(payload.expiryMs)).toBytes());
|
|
3075
|
+
push(bcs3.Address.serialize(normalizeSuiAddress4(payload.delegate)).toBytes());
|
|
3076
|
+
push(bcs3.Address.serialize(normalizeSuiAddress4(payload.poolId)).toBytes());
|
|
3077
|
+
push(bcs3.Address.serialize(normalizeSuiAddress4(payload.grantId)).toBytes());
|
|
3078
|
+
push(bcs3.vector(bcs3.u8()).serialize([...Buffer.from(payload.network, "utf8")]).toBytes());
|
|
3079
|
+
push(bcs3.Address.serialize(normalizeSuiAddress4(payload.packageId)).toBytes());
|
|
3080
|
+
push(bcs3.vector(bcs3.u8()).serialize([...Buffer.from(payload.requestId, "utf8")]).toBytes());
|
|
3081
|
+
push(bcs3.vector(bcs3.u8()).serialize([...payload.txKindHash]).toBytes());
|
|
2419
3082
|
return Buffer.concat(parts.map((p) => Buffer.from(p)));
|
|
2420
3083
|
}
|
|
2421
3084
|
function hashTransactionKindBytes(kindBytes) {
|
|
2422
|
-
const raw = typeof kindBytes === "string" ?
|
|
3085
|
+
const raw = typeof kindBytes === "string" ? fromBase645(kindBytes) : kindBytes;
|
|
2423
3086
|
return sha2563(raw);
|
|
2424
3087
|
}
|
|
2425
3088
|
function buildSponsorPersonalMessagePayload(input) {
|
|
@@ -2444,7 +3107,7 @@ async function signSponsorPersonalMessagePayload(input) {
|
|
|
2444
3107
|
// ../sdk/dist/protocol/signed-request.js
|
|
2445
3108
|
import { sha256 as sha2564 } from "@noble/hashes/sha2.js";
|
|
2446
3109
|
import { bytesToHex as bytesToHex2 } from "@noble/hashes/utils.js";
|
|
2447
|
-
import { bcs as
|
|
3110
|
+
import { bcs as bcs4 } from "@mysten/sui/bcs";
|
|
2448
3111
|
import { messageWithIntent } from "@mysten/sui/cryptography";
|
|
2449
3112
|
import { isValidPersonalMessageSignature as isValidPersonalMessageSignature2, verifyPersonalMessageSignature as verifyPersonalMessageSignature2 } from "@mysten/sui/verify";
|
|
2450
3113
|
import { normalizeSuiAddress as normalizeSuiAddress5 } from "@mysten/sui/utils";
|
|
@@ -2478,8 +3141,8 @@ function encodeBootstrapRequest(payload) {
|
|
|
2478
3141
|
if (payload.domain !== BOOTSTRAP_REQUEST_DOMAIN) {
|
|
2479
3142
|
throw new Error(`domain must be ${BOOTSTRAP_REQUEST_DOMAIN}`);
|
|
2480
3143
|
}
|
|
2481
|
-
const utf8Vec = (s) =>
|
|
2482
|
-
const addr = (a) =>
|
|
3144
|
+
const utf8Vec = (s) => bcs4.vector(bcs4.u8()).serialize([...Buffer.from(s, "utf8")]).toBytes();
|
|
3145
|
+
const addr = (a) => bcs4.Address.serialize(normalizeSuiAddress5(a)).toBytes();
|
|
2483
3146
|
const parts = [
|
|
2484
3147
|
utf8Vec(payload.domain),
|
|
2485
3148
|
utf8Vec(payload.network),
|
|
@@ -2488,8 +3151,8 @@ function encodeBootstrapRequest(payload) {
|
|
|
2488
3151
|
utf8Vec(payload.method),
|
|
2489
3152
|
utf8Vec(payload.path),
|
|
2490
3153
|
utf8Vec(payload.query),
|
|
2491
|
-
|
|
2492
|
-
|
|
3154
|
+
bcs4.vector(bcs4.u8()).serialize([...hexToBytes32(payload.bodyHash)]).toBytes(),
|
|
3155
|
+
bcs4.u64().serialize(BigInt(payload.timestampMs)).toBytes(),
|
|
2493
3156
|
utf8Vec(payload.nonce)
|
|
2494
3157
|
];
|
|
2495
3158
|
return Buffer.concat(parts.map((p) => Buffer.from(p)));
|
|
@@ -2498,8 +3161,8 @@ function encodeSignedRequest(payload) {
|
|
|
2498
3161
|
if (payload.domain !== SIGNED_REQUEST_DOMAIN) {
|
|
2499
3162
|
throw new Error(`domain must be ${SIGNED_REQUEST_DOMAIN}`);
|
|
2500
3163
|
}
|
|
2501
|
-
const utf8Vec = (s) =>
|
|
2502
|
-
const addr = (a) =>
|
|
3164
|
+
const utf8Vec = (s) => bcs4.vector(bcs4.u8()).serialize([...Buffer.from(s, "utf8")]).toBytes();
|
|
3165
|
+
const addr = (a) => bcs4.Address.serialize(normalizeSuiAddress5(a)).toBytes();
|
|
2503
3166
|
const parts = [
|
|
2504
3167
|
utf8Vec(payload.domain),
|
|
2505
3168
|
utf8Vec(payload.network),
|
|
@@ -2509,8 +3172,8 @@ function encodeSignedRequest(payload) {
|
|
|
2509
3172
|
utf8Vec(payload.method),
|
|
2510
3173
|
utf8Vec(payload.path),
|
|
2511
3174
|
utf8Vec(payload.query),
|
|
2512
|
-
|
|
2513
|
-
|
|
3175
|
+
bcs4.vector(bcs4.u8()).serialize([...hexToBytes32(payload.bodyHash)]).toBytes(),
|
|
3176
|
+
bcs4.u64().serialize(BigInt(payload.timestampMs)).toBytes(),
|
|
2514
3177
|
utf8Vec(payload.nonce)
|
|
2515
3178
|
];
|
|
2516
3179
|
return Buffer.concat(parts.map((p) => Buffer.from(p)));
|
|
@@ -2869,7 +3532,7 @@ async function readPublicSessionState(getObjectJson, grantObjectId) {
|
|
|
2869
3532
|
const g = json;
|
|
2870
3533
|
return {
|
|
2871
3534
|
grantObjectId,
|
|
2872
|
-
// Field names are the Move struct's
|
|
3535
|
+
// Field names are the Move struct's; three were renamed:
|
|
2873
3536
|
// pool_id -> account_id, session_cap -> total_budget, session_spent ->
|
|
2874
3537
|
// total_spent. Reading the old names yielded undefined, which the grant
|
|
2875
3538
|
// verifier then read as "this grant names no pool" and refused every
|
|
@@ -2929,6 +3592,9 @@ var bootstrapPayerGrants = bootstrapPayerSessions;
|
|
|
2929
3592
|
// ../sdk/dist/delegated-signer/payer.js
|
|
2930
3593
|
var FINALIZATION_ATTEMPTS = 20;
|
|
2931
3594
|
var FINALIZATION_DELAY_MS = 250;
|
|
3595
|
+
var X402_EXPIRATION_EPOCHS = 32n;
|
|
3596
|
+
var X402_PRESENT_ATTEMPTS = 10;
|
|
3597
|
+
var X402_PRESENT_DELAY_MS = 1e3;
|
|
2932
3598
|
function delay(ms) {
|
|
2933
3599
|
return new Promise((resolve) => {
|
|
2934
3600
|
setTimeout(resolve, ms);
|
|
@@ -2996,7 +3662,16 @@ var DELEGATED_PAY_ERROR_CODES = {
|
|
|
2996
3662
|
* AGENT's own ceiling, and it is the check that does not need the gateway's
|
|
2997
3663
|
* secret to be meaningful.
|
|
2998
3664
|
*/
|
|
2999
|
-
SPEND_INTENT_VIOLATED: "SPEND_INTENT_VIOLATED"
|
|
3665
|
+
SPEND_INTENT_VIOLATED: "SPEND_INTENT_VIOLATED",
|
|
3666
|
+
/**
|
|
3667
|
+
* x402 was asked for and cannot be paid from the spend account: the 402
|
|
3668
|
+
* carried no x402 offer (the gateway strips it when it has no gas lease), a
|
|
3669
|
+
* sponsor gas pin is missing or unreadable, or no current epoch could be read
|
|
3670
|
+
* to bound the transaction. Nothing was reserved or signed.
|
|
3671
|
+
*/
|
|
3672
|
+
X402_UNAVAILABLE: "X402_UNAVAILABLE",
|
|
3673
|
+
/** The grant's scope cannot pay in the requested dialect (open-recipient over x402). */
|
|
3674
|
+
UNSUPPORTED_SCOPE: "UNSUPPORTED_SCOPE"
|
|
3000
3675
|
};
|
|
3001
3676
|
var DELEGATED_PAY_ERROR_CODE_SET = new Set(Object.values(DELEGATED_PAY_ERROR_CODES));
|
|
3002
3677
|
function isDelegatedPayErrorCode(value) {
|
|
@@ -3033,7 +3708,9 @@ var PUBLIC_DETAIL = {
|
|
|
3033
3708
|
FINALITY_UNAVAILABLE: "no finality reader configured; refusing to sign a pay whose settlement cannot be confirmed",
|
|
3034
3709
|
PREPARED_OFFER_DIVERGED: "settlement offer does not match the terms verified before signing",
|
|
3035
3710
|
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"
|
|
3711
|
+
SPEND_INTENT_VIOLATED: "offer does not match what this agent said it was buying",
|
|
3712
|
+
X402_UNAVAILABLE: "offer cannot be paid over x402 from the spend account (no x402 offer, sponsor gas pin, or current epoch)",
|
|
3713
|
+
UNSUPPORTED_SCOPE: "open-recipient grants cannot pay over x402"
|
|
3037
3714
|
};
|
|
3038
3715
|
function classifyNon402Status(status) {
|
|
3039
3716
|
if (status >= 200 && status < 300)
|
|
@@ -3138,6 +3815,13 @@ function mapPayOpErrorToDelegated(code, detail) {
|
|
|
3138
3815
|
return { code: "RESOURCE_REJECTED", detail: PUBLIC_DETAIL.RESOURCE_REJECTED };
|
|
3139
3816
|
case PAY_OPERATION_ERROR_CODES.RESOURCE_UNAVAILABLE:
|
|
3140
3817
|
return { code: "RESOURCE_UNAVAILABLE", detail: PUBLIC_DETAIL.RESOURCE_UNAVAILABLE };
|
|
3818
|
+
case PAY_OPERATION_ERROR_CODES.DIGEST_INVALID:
|
|
3819
|
+
return { code: "CHALLENGE_TAMPERED", detail: PUBLIC_DETAIL.CHALLENGE_TAMPERED };
|
|
3820
|
+
case PAY_OPERATION_ERROR_CODES.OFFER_EXPIRED:
|
|
3821
|
+
return { code: "CHALLENGE_EXPIRED", detail: PUBLIC_DETAIL.CHALLENGE_EXPIRED };
|
|
3822
|
+
// SPONSOR_GAS_INVALID and TX_OFFER_MISMATCH fall through to POLICY_REJECTED
|
|
3823
|
+
// for now; the caller that surfaces them (the tx-build/sign path) picks
|
|
3824
|
+
// their final outward code when it lands.
|
|
3141
3825
|
// REQUEST_BINDING_VIOLATED, UNSUPPORTED_RAIL, PACKAGE_MISMATCH,
|
|
3142
3826
|
// NETWORK_MISMATCH, COIN_TYPE_MISMATCH, OVERPAY_REJECTED, UNDERPAY_REJECTED,
|
|
3143
3827
|
// ORIGIN_MISMATCH, and any unknown code all fall through to POLICY_REJECTED.
|
|
@@ -3145,7 +3829,7 @@ function mapPayOpErrorToDelegated(code, detail) {
|
|
|
3145
3829
|
return { code: "POLICY_REJECTED", detail: PUBLIC_DETAIL.POLICY_REJECTED };
|
|
3146
3830
|
}
|
|
3147
3831
|
}
|
|
3148
|
-
function
|
|
3832
|
+
function sameAddress2(a, b) {
|
|
3149
3833
|
try {
|
|
3150
3834
|
return normalizeSuiAddress6(a) === normalizeSuiAddress6(b);
|
|
3151
3835
|
} catch {
|
|
@@ -3161,7 +3845,7 @@ function atomicAmount(value) {
|
|
|
3161
3845
|
}
|
|
3162
3846
|
function sameStructTag(a, b) {
|
|
3163
3847
|
try {
|
|
3164
|
-
return
|
|
3848
|
+
return normalizeStructTag6(a) === normalizeStructTag6(b);
|
|
3165
3849
|
} catch {
|
|
3166
3850
|
return false;
|
|
3167
3851
|
}
|
|
@@ -3220,7 +3904,7 @@ async function resolveSigner(input) {
|
|
|
3220
3904
|
return {
|
|
3221
3905
|
address: kp.getPublicKey().toSuiAddress(),
|
|
3222
3906
|
async signTransaction(txBytes) {
|
|
3223
|
-
const { signature } = await kp.signTransaction(
|
|
3907
|
+
const { signature } = await kp.signTransaction(fromBase646(txBytes));
|
|
3224
3908
|
return signature;
|
|
3225
3909
|
},
|
|
3226
3910
|
async signPersonalMessage(message) {
|
|
@@ -3287,6 +3971,7 @@ async function buildSpendAccountPayKind(input) {
|
|
|
3287
3971
|
throw new Error("grantInitialSharedVersion is required");
|
|
3288
3972
|
}
|
|
3289
3973
|
const hash = targetHashBytes(input.targetHash);
|
|
3974
|
+
const guarded = resolveGuardedSettleOfflineInputs(input);
|
|
3290
3975
|
const target = payMoveCallTarget(input.packageId, "scoped");
|
|
3291
3976
|
const clockVersion = input.clockInitialSharedVersion?.trim() || SUI_CLOCK_INITIAL_SHARED_VERSION;
|
|
3292
3977
|
const tx = new Transaction4();
|
|
@@ -3295,6 +3980,16 @@ async function buildSpendAccountPayKind(input) {
|
|
|
3295
3980
|
target,
|
|
3296
3981
|
typeArguments: [input.coinType],
|
|
3297
3982
|
arguments: [
|
|
3983
|
+
tx.sharedObjectRef({
|
|
3984
|
+
objectId: guarded.exclusionDirectoryId,
|
|
3985
|
+
initialSharedVersion: guarded.exclusionDirectoryInitialSharedVersion,
|
|
3986
|
+
mutable: false
|
|
3987
|
+
}),
|
|
3988
|
+
tx.sharedObjectRef({
|
|
3989
|
+
objectId: guarded.recipientRegistryId,
|
|
3990
|
+
initialSharedVersion: guarded.recipientRegistryInitialSharedVersion,
|
|
3991
|
+
mutable: true
|
|
3992
|
+
}),
|
|
3298
3993
|
tx.sharedObjectRef({
|
|
3299
3994
|
objectId: input.poolObjectId,
|
|
3300
3995
|
initialSharedVersion: String(input.poolInitialSharedVersion),
|
|
@@ -3310,6 +4005,7 @@ async function buildSpendAccountPayKind(input) {
|
|
|
3310
4005
|
tx.pure.u64(BigInt(input.amount)),
|
|
3311
4006
|
tx.pure.vector("u8", Array.from(input.paymentIdHash)),
|
|
3312
4007
|
tx.pure.vector("u8", Array.from(input.termsHash)),
|
|
4008
|
+
tx.pure.u64(BigInt(guarded.expiresAtMs)),
|
|
3313
4009
|
tx.sharedObjectRef({
|
|
3314
4010
|
objectId: SUI_CLOCK2,
|
|
3315
4011
|
initialSharedVersion: clockVersion,
|
|
@@ -3318,7 +4014,7 @@ async function buildSpendAccountPayKind(input) {
|
|
|
3318
4014
|
]
|
|
3319
4015
|
});
|
|
3320
4016
|
const bytes = await tx.build({ onlyTransactionKind: true });
|
|
3321
|
-
return { kindBytes:
|
|
4017
|
+
return { kindBytes: toBase645(bytes) };
|
|
3322
4018
|
}
|
|
3323
4019
|
async function buildSpendAccountSettleOpenRecipientKind(input) {
|
|
3324
4020
|
if (!input.termsHash.length)
|
|
@@ -3333,9 +4029,10 @@ async function buildSpendAccountSettleOpenRecipientKind(input) {
|
|
|
3333
4029
|
throw new Error("grantInitialSharedVersion is required");
|
|
3334
4030
|
}
|
|
3335
4031
|
if (!input.recipient || /^0x0+$/i.test(input.recipient)) {
|
|
3336
|
-
throw new Error("
|
|
4032
|
+
throw new Error("settle_any_recipient recipient must be a non-zero address");
|
|
3337
4033
|
}
|
|
3338
4034
|
const hash = targetHashBytes(input.targetHash);
|
|
4035
|
+
const guarded = resolveGuardedSettleOfflineInputs(input);
|
|
3339
4036
|
const target = payMoveCallTarget(input.packageId, "open");
|
|
3340
4037
|
const clockVersion = input.clockInitialSharedVersion?.trim() || SUI_CLOCK_INITIAL_SHARED_VERSION;
|
|
3341
4038
|
const tx = new Transaction4();
|
|
@@ -3344,6 +4041,16 @@ async function buildSpendAccountSettleOpenRecipientKind(input) {
|
|
|
3344
4041
|
target,
|
|
3345
4042
|
typeArguments: [input.coinType],
|
|
3346
4043
|
arguments: [
|
|
4044
|
+
tx.sharedObjectRef({
|
|
4045
|
+
objectId: guarded.exclusionDirectoryId,
|
|
4046
|
+
initialSharedVersion: guarded.exclusionDirectoryInitialSharedVersion,
|
|
4047
|
+
mutable: false
|
|
4048
|
+
}),
|
|
4049
|
+
tx.sharedObjectRef({
|
|
4050
|
+
objectId: guarded.recipientRegistryId,
|
|
4051
|
+
initialSharedVersion: guarded.recipientRegistryInitialSharedVersion,
|
|
4052
|
+
mutable: true
|
|
4053
|
+
}),
|
|
3347
4054
|
tx.sharedObjectRef({
|
|
3348
4055
|
objectId: input.poolObjectId,
|
|
3349
4056
|
initialSharedVersion: String(input.poolInitialSharedVersion),
|
|
@@ -3359,6 +4066,7 @@ async function buildSpendAccountSettleOpenRecipientKind(input) {
|
|
|
3359
4066
|
tx.pure.vector("u8", Array.from(input.paymentIdHash)),
|
|
3360
4067
|
tx.pure.vector("u8", Array.from(input.termsHash)),
|
|
3361
4068
|
tx.pure.vector("u8", Array.from(hash)),
|
|
4069
|
+
tx.pure.u64(BigInt(guarded.expiresAtMs)),
|
|
3362
4070
|
tx.sharedObjectRef({
|
|
3363
4071
|
objectId: SUI_CLOCK2,
|
|
3364
4072
|
initialSharedVersion: clockVersion,
|
|
@@ -3367,14 +4075,14 @@ async function buildSpendAccountSettleOpenRecipientKind(input) {
|
|
|
3367
4075
|
]
|
|
3368
4076
|
});
|
|
3369
4077
|
const bytes = await tx.build({ onlyTransactionKind: true });
|
|
3370
|
-
return { kindBytes:
|
|
4078
|
+
return { kindBytes: toBase645(bytes) };
|
|
3371
4079
|
}
|
|
3372
4080
|
async function buildSpendAccountPayTx(input) {
|
|
3373
4081
|
if (!input.gas?.payment?.length) {
|
|
3374
4082
|
throw new Error("agent gas payment objects are required for executable pay");
|
|
3375
4083
|
}
|
|
3376
4084
|
const { kindBytes } = await buildSpendAccountPayKind(input);
|
|
3377
|
-
const tx = Transaction4.fromKind(
|
|
4085
|
+
const tx = Transaction4.fromKind(fromBase646(kindBytes));
|
|
3378
4086
|
tx.setSender(input.delegate);
|
|
3379
4087
|
tx.setGasOwner(input.gas.owner ?? input.delegate);
|
|
3380
4088
|
tx.setGasBudget(BigInt(input.gas.budget ?? DEFAULT_GAS_BUDGET));
|
|
@@ -3384,7 +4092,7 @@ async function buildSpendAccountPayTx(input) {
|
|
|
3384
4092
|
version: String(c.version),
|
|
3385
4093
|
digest: c.digest
|
|
3386
4094
|
})));
|
|
3387
|
-
const txBytes =
|
|
4095
|
+
const txBytes = toBase645(await tx.build());
|
|
3388
4096
|
return { txBytes, kindBytes };
|
|
3389
4097
|
}
|
|
3390
4098
|
async function buildSpendAccountSettleOpenRecipientTx(input) {
|
|
@@ -3392,7 +4100,7 @@ async function buildSpendAccountSettleOpenRecipientTx(input) {
|
|
|
3392
4100
|
throw new Error("agent gas payment objects are required for executable pay");
|
|
3393
4101
|
}
|
|
3394
4102
|
const { kindBytes } = await buildSpendAccountSettleOpenRecipientKind(input);
|
|
3395
|
-
const tx = Transaction4.fromKind(
|
|
4103
|
+
const tx = Transaction4.fromKind(fromBase646(kindBytes));
|
|
3396
4104
|
tx.setSender(input.delegate);
|
|
3397
4105
|
tx.setGasOwner(input.gas.owner ?? input.delegate);
|
|
3398
4106
|
tx.setGasBudget(BigInt(input.gas.budget ?? DEFAULT_GAS_BUDGET));
|
|
@@ -3402,23 +4110,59 @@ async function buildSpendAccountSettleOpenRecipientTx(input) {
|
|
|
3402
4110
|
version: String(c.version),
|
|
3403
4111
|
digest: c.digest
|
|
3404
4112
|
})));
|
|
3405
|
-
const txBytes =
|
|
4113
|
+
const txBytes = toBase645(await tx.build());
|
|
4114
|
+
return { txBytes, kindBytes };
|
|
4115
|
+
}
|
|
4116
|
+
function readX402GasPin(offer) {
|
|
4117
|
+
const owner = offer.sponsorGasOwner?.trim();
|
|
4118
|
+
const budget = atomicAmount(offer.sponsorGasBudget ?? "");
|
|
4119
|
+
const price = atomicAmount(offer.sponsorGasPrice ?? "");
|
|
4120
|
+
if (!owner || budget === null || price === null || budget <= 0n || price <= 0n) {
|
|
4121
|
+
return null;
|
|
4122
|
+
}
|
|
4123
|
+
try {
|
|
4124
|
+
return {
|
|
4125
|
+
owner,
|
|
4126
|
+
budget,
|
|
4127
|
+
price,
|
|
4128
|
+
payment: decodeSponsorGasPayment(offer.sponsorGasPayment ?? "")
|
|
4129
|
+
};
|
|
4130
|
+
} catch {
|
|
4131
|
+
return null;
|
|
4132
|
+
}
|
|
4133
|
+
}
|
|
4134
|
+
async function buildSpendAccountX402PayTx(input) {
|
|
4135
|
+
const { kindBytes } = await buildSpendAccountPayKind(input);
|
|
4136
|
+
const tx = Transaction4.fromKind(fromBase646(kindBytes));
|
|
4137
|
+
tx.setSender(input.delegate);
|
|
4138
|
+
tx.setGasOwner(input.gas.owner);
|
|
4139
|
+
tx.setGasBudget(input.gas.budget);
|
|
4140
|
+
tx.setGasPrice(input.gas.price);
|
|
4141
|
+
tx.setGasPayment(input.gas.payment);
|
|
4142
|
+
tx.setExpiration({ Epoch: String(input.currentEpoch + X402_EXPIRATION_EPOCHS) });
|
|
4143
|
+
const txBytes = toBase645(await tx.build());
|
|
3406
4144
|
return { txBytes, kindBytes };
|
|
3407
4145
|
}
|
|
4146
|
+
function currentEpochReader(client) {
|
|
4147
|
+
return async () => {
|
|
4148
|
+
const { systemState } = await client.core.getCurrentSystemState();
|
|
4149
|
+
return BigInt(systemState.epoch);
|
|
4150
|
+
};
|
|
4151
|
+
}
|
|
3408
4152
|
function assertPayTransaction(txBytes, expected) {
|
|
3409
4153
|
let data;
|
|
3410
4154
|
try {
|
|
3411
|
-
data = Transaction4.from(
|
|
4155
|
+
data = Transaction4.from(fromBase646(txBytes)).getData();
|
|
3412
4156
|
} catch {
|
|
3413
4157
|
throw new Error("transaction is not valid Sui transaction bytes");
|
|
3414
4158
|
}
|
|
3415
|
-
if (!data.sender || !
|
|
4159
|
+
if (!data.sender || !sameAddress2(data.sender, expected.delegate)) {
|
|
3416
4160
|
throw new Error("transaction sender does not match delegate");
|
|
3417
4161
|
}
|
|
3418
4162
|
if (!data.commands || data.commands.length !== 1) {
|
|
3419
4163
|
throw new Error("transaction must contain exactly one command");
|
|
3420
4164
|
}
|
|
3421
|
-
const wanted = payMoveCallTarget(expected.packageId, expected.accessMode);
|
|
4165
|
+
const wanted = payMoveCallTarget(expected.packageId, expected.accessMode, expected.guarded === true);
|
|
3422
4166
|
const cmd = data.commands[0];
|
|
3423
4167
|
if (cmd.$kind !== "MoveCall" || !cmd.MoveCall) {
|
|
3424
4168
|
throw new Error("transaction is not a MoveCall");
|
|
@@ -3429,7 +4173,7 @@ function assertPayTransaction(txBytes, expected) {
|
|
|
3429
4173
|
}
|
|
3430
4174
|
let expectedKind;
|
|
3431
4175
|
try {
|
|
3432
|
-
expectedKind = Transaction4.fromKind(
|
|
4176
|
+
expectedKind = Transaction4.fromKind(fromBase646(expected.kindBytes)).getData();
|
|
3433
4177
|
} catch {
|
|
3434
4178
|
throw new Error("intended pay kind baseline is invalid");
|
|
3435
4179
|
}
|
|
@@ -3450,9 +4194,10 @@ function assertPayTransaction(txBytes, expected) {
|
|
|
3450
4194
|
if (!expected.paymentIdHash.length || !expected.termsHash.length) {
|
|
3451
4195
|
throw new Error("expected paymentIdHash and termsHash must be non-empty");
|
|
3452
4196
|
}
|
|
4197
|
+
const expectedArgc = 11;
|
|
3453
4198
|
const args = cmd.MoveCall.arguments;
|
|
3454
|
-
if (args && args.length !==
|
|
3455
|
-
throw new Error(`pay expects
|
|
4199
|
+
if (args && args.length !== expectedArgc) {
|
|
4200
|
+
throw new Error(`pay expects ${expectedArgc} arguments; got ${args.length}`);
|
|
3456
4201
|
}
|
|
3457
4202
|
}
|
|
3458
4203
|
function asValidatedReceipt(value) {
|
|
@@ -3635,13 +4380,13 @@ function spendIntentViolation(offer, intent) {
|
|
|
3635
4380
|
const ceiling = atomicAmount(intent.maxAmount);
|
|
3636
4381
|
if (offered === null || ceiling === null || offered > ceiling)
|
|
3637
4382
|
return "amount";
|
|
3638
|
-
if (intent.recipient !== void 0 && !
|
|
4383
|
+
if (intent.recipient !== void 0 && !sameAddress2(offer.payTo, intent.recipient)) {
|
|
3639
4384
|
return "recipient";
|
|
3640
4385
|
}
|
|
3641
4386
|
return null;
|
|
3642
4387
|
}
|
|
3643
4388
|
function preparedTermsDivergence(offer, terms) {
|
|
3644
|
-
if (!
|
|
4389
|
+
if (!sameAddress2(offer.payTo, terms.recipient))
|
|
3645
4390
|
return "recipient";
|
|
3646
4391
|
const offered = atomicAmount(offer.amount);
|
|
3647
4392
|
const verified = atomicAmount(terms.amount);
|
|
@@ -3724,6 +4469,10 @@ async function payDelegatedSpendAccount(args) {
|
|
|
3724
4469
|
logInternal(logError, "resolve authority failed", err);
|
|
3725
4470
|
return failed2("GRANT_DENIED", PUBLIC_DETAIL.GRANT_DENIED);
|
|
3726
4471
|
}
|
|
4472
|
+
const x402Requested = args.dialect === "x402";
|
|
4473
|
+
if (x402Requested && snapshot.accessMode === "open") {
|
|
4474
|
+
return failed2("UNSUPPORTED_SCOPE", PUBLIC_DETAIL.UNSUPPORTED_SCOPE);
|
|
4475
|
+
}
|
|
3727
4476
|
let poolInitialSharedVersion;
|
|
3728
4477
|
let grantInitialSharedVersion;
|
|
3729
4478
|
let clockInitialSharedVersion;
|
|
@@ -3797,7 +4546,7 @@ async function payDelegatedSpendAccount(args) {
|
|
|
3797
4546
|
throw new Error("POLICY:no_target");
|
|
3798
4547
|
}
|
|
3799
4548
|
try {
|
|
3800
|
-
|
|
4549
|
+
normalizeStructTag6(offer2.asset);
|
|
3801
4550
|
} catch {
|
|
3802
4551
|
throw new Error("POLICY:bad_asset");
|
|
3803
4552
|
}
|
|
@@ -3817,7 +4566,15 @@ async function payDelegatedSpendAccount(args) {
|
|
|
3817
4566
|
amount: input.amount,
|
|
3818
4567
|
paymentIdHash: input.paymentIdHash,
|
|
3819
4568
|
termsHash: input.termsHash,
|
|
3820
|
-
recipient: input.recipient
|
|
4569
|
+
recipient: input.recipient,
|
|
4570
|
+
// Guarded settle inputs (undefined for an unguarded offer, which then
|
|
4571
|
+
// builds without the exclusion objects). resolvePayment set
|
|
4572
|
+
// these from the resolved exclusion registry.
|
|
4573
|
+
exclusionDirectoryId: input.exclusionDirectoryId,
|
|
4574
|
+
recipientRegistryId: input.recipientRegistryId,
|
|
4575
|
+
exclusionDirectoryInitialSharedVersion: input.exclusionDirectoryInitialSharedVersion,
|
|
4576
|
+
recipientRegistryInitialSharedVersion: input.recipientRegistryInitialSharedVersion,
|
|
4577
|
+
expiresAtMs: input.expiresAtMs
|
|
3821
4578
|
};
|
|
3822
4579
|
if (input.accessMode === "open") {
|
|
3823
4580
|
return buildSpendAccountSettleOpenRecipientKind(kindInput);
|
|
@@ -3825,7 +4582,7 @@ async function payDelegatedSpendAccount(args) {
|
|
|
3825
4582
|
return buildSpendAccountPayKind(kindInput);
|
|
3826
4583
|
};
|
|
3827
4584
|
try {
|
|
3828
|
-
|
|
4585
|
+
normalizeStructTag6(snapshot.coinType);
|
|
3829
4586
|
} catch {
|
|
3830
4587
|
return failed2("POLICY_REJECTED", PUBLIC_DETAIL.POLICY_REJECTED);
|
|
3831
4588
|
}
|
|
@@ -3839,9 +4596,13 @@ async function payDelegatedSpendAccount(args) {
|
|
|
3839
4596
|
};
|
|
3840
4597
|
const resolveDeps = {
|
|
3841
4598
|
fetchImpl: memoizedFetch,
|
|
3842
|
-
preferredDialect: "mpp",
|
|
4599
|
+
preferredDialect: x402Requested ? "x402" : "mpp",
|
|
3843
4600
|
validateOffer: validateOfferCallback,
|
|
3844
|
-
buildPayKind
|
|
4601
|
+
buildPayKind,
|
|
4602
|
+
// Guarded offers resolve the per-recipient exclusion registry
|
|
4603
|
+
// here; absent, resolvePayment fails closed (BUILD_FAILED) for such an offer
|
|
4604
|
+
// and never builds an unguarded settle. Unguarded offers never call it.
|
|
4605
|
+
resolveExclusionRegistry: deps.resolveExclusionRegistry
|
|
3845
4606
|
};
|
|
3846
4607
|
const resolution = await resolvePayment(resolveInput, resolveDeps);
|
|
3847
4608
|
if (resolution.status === "unpaid_ok") {
|
|
@@ -3853,8 +4614,15 @@ async function payDelegatedSpendAccount(args) {
|
|
|
3853
4614
|
paymentId: resolution.paymentId
|
|
3854
4615
|
});
|
|
3855
4616
|
}
|
|
3856
|
-
const { offer, resolved: resolvedAuthority, kindBytes, paymentIdHash: payIdHash, termsHash: terms } = resolution;
|
|
4617
|
+
const { offer, resolved: resolvedAuthority, kindBytes, paymentIdHash: payIdHash, termsHash: terms, guarded: guardedResolution } = resolution;
|
|
3857
4618
|
const paymentId = resolution.paymentId;
|
|
4619
|
+
const guardedBuildFields = guardedResolution ? {
|
|
4620
|
+
exclusionDirectoryId: guardedResolution.exclusionDirectoryId,
|
|
4621
|
+
recipientRegistryId: guardedResolution.recipientRegistryId,
|
|
4622
|
+
exclusionDirectoryInitialSharedVersion: guardedResolution.exclusionDirectoryInitialSharedVersion,
|
|
4623
|
+
recipientRegistryInitialSharedVersion: guardedResolution.recipientRegistryInitialSharedVersion,
|
|
4624
|
+
expiresAtMs: guardedResolution.expiresAtMs
|
|
4625
|
+
} : {};
|
|
3858
4626
|
const isOpen = snapshot.accessMode === "open";
|
|
3859
4627
|
const resolved = {
|
|
3860
4628
|
poolObjectId: resolvedAuthority.poolObjectId,
|
|
@@ -3866,6 +4634,13 @@ async function payDelegatedSpendAccount(args) {
|
|
|
3866
4634
|
recipient: resolvedAuthority.recipient
|
|
3867
4635
|
};
|
|
3868
4636
|
const offerDigest = offer.digest.trim();
|
|
4637
|
+
let x402Terms = null;
|
|
4638
|
+
if (x402Requested) {
|
|
4639
|
+
x402Terms = await x402Preflight(resolution.dialect, offer, deps, logError);
|
|
4640
|
+
if (!x402Terms) {
|
|
4641
|
+
return failed2("X402_UNAVAILABLE", PUBLIC_DETAIL.X402_UNAVAILABLE, { paymentId });
|
|
4642
|
+
}
|
|
4643
|
+
}
|
|
3869
4644
|
const ledger = deps.paymentLedger;
|
|
3870
4645
|
if (!ledger || typeof ledger.reserve !== "function" || typeof ledger.complete !== "function") {
|
|
3871
4646
|
return failed2("PAYMENT_LEDGER_REQUIRED", PUBLIC_DETAIL.PAYMENT_LEDGER_REQUIRED, { paymentId });
|
|
@@ -3882,7 +4657,9 @@ async function payDelegatedSpendAccount(args) {
|
|
|
3882
4657
|
if (reserveOutcome.state === "done") {
|
|
3883
4658
|
const prior = asValidatedLedgerResult(reserveOutcome.prior, paymentId);
|
|
3884
4659
|
if (!prior) {
|
|
3885
|
-
return ambiguous("PAYMENT_ATTEMPT_IN_FLIGHT", PUBLIC_DETAIL.PAYMENT_ATTEMPT_IN_FLIGHT, {
|
|
4660
|
+
return ambiguous("PAYMENT_ATTEMPT_IN_FLIGHT", PUBLIC_DETAIL.PAYMENT_ATTEMPT_IN_FLIGHT, {
|
|
4661
|
+
paymentId
|
|
4662
|
+
});
|
|
3886
4663
|
}
|
|
3887
4664
|
return {
|
|
3888
4665
|
...prior,
|
|
@@ -3892,10 +4669,14 @@ async function payDelegatedSpendAccount(args) {
|
|
|
3892
4669
|
};
|
|
3893
4670
|
}
|
|
3894
4671
|
if (reserveOutcome.state === "in_flight") {
|
|
3895
|
-
return ambiguous("PAYMENT_ATTEMPT_IN_FLIGHT", PUBLIC_DETAIL.PAYMENT_ATTEMPT_IN_FLIGHT, {
|
|
4672
|
+
return ambiguous("PAYMENT_ATTEMPT_IN_FLIGHT", PUBLIC_DETAIL.PAYMENT_ATTEMPT_IN_FLIGHT, {
|
|
4673
|
+
paymentId
|
|
4674
|
+
});
|
|
3896
4675
|
}
|
|
3897
4676
|
if (reserveOutcome.state !== "reserved") {
|
|
3898
|
-
logInternal(logError, "payment attempt ledger reserve returned unexpected state", {
|
|
4677
|
+
logInternal(logError, "payment attempt ledger reserve returned unexpected state", {
|
|
4678
|
+
state: reserveOutcome.state
|
|
4679
|
+
});
|
|
3899
4680
|
return failed2("PAYMENT_LEDGER_FAILED", PUBLIC_DETAIL.PAYMENT_LEDGER_FAILED, {
|
|
3900
4681
|
paymentId
|
|
3901
4682
|
});
|
|
@@ -3905,27 +4686,6 @@ async function payDelegatedSpendAccount(args) {
|
|
|
3905
4686
|
await releaseReservation(ledger, ownedPaymentId, logError);
|
|
3906
4687
|
return failed2(code, detail, { paymentId });
|
|
3907
4688
|
};
|
|
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
4689
|
const kindBuildInput = {
|
|
3930
4690
|
packageId: session.packageId,
|
|
3931
4691
|
delegate: resolved.delegateAddress,
|
|
@@ -3940,8 +4700,69 @@ async function payDelegatedSpendAccount(args) {
|
|
|
3940
4700
|
amount: offer.amount,
|
|
3941
4701
|
paymentIdHash: payIdHash,
|
|
3942
4702
|
termsHash: terms,
|
|
3943
|
-
recipient: resolved.recipient
|
|
4703
|
+
recipient: resolved.recipient,
|
|
4704
|
+
...guardedBuildFields
|
|
3944
4705
|
};
|
|
4706
|
+
const expectedPay = {
|
|
4707
|
+
packageId: session.packageId,
|
|
4708
|
+
delegate: resolved.delegateAddress,
|
|
4709
|
+
poolObjectId: resolved.poolObjectId,
|
|
4710
|
+
grantObjectId: resolved.grantObjectId,
|
|
4711
|
+
policyId: resolved.onChainPolicyId,
|
|
4712
|
+
targetHash: resolved.targetHash,
|
|
4713
|
+
amount: offer.amount,
|
|
4714
|
+
paymentIdHash: payIdHash,
|
|
4715
|
+
termsHash: terms,
|
|
4716
|
+
coinType: resolved.coinType,
|
|
4717
|
+
kindBytes,
|
|
4718
|
+
accessMode: snapshot.accessMode,
|
|
4719
|
+
recipient: resolved.recipient,
|
|
4720
|
+
guarded: guardedResolution !== void 0
|
|
4721
|
+
};
|
|
4722
|
+
if (x402Terms) {
|
|
4723
|
+
return payX402Reserved({
|
|
4724
|
+
session,
|
|
4725
|
+
deps,
|
|
4726
|
+
fetchImpl,
|
|
4727
|
+
resourceUrl,
|
|
4728
|
+
request,
|
|
4729
|
+
ledger,
|
|
4730
|
+
paymentId,
|
|
4731
|
+
offer,
|
|
4732
|
+
expected: expectedPay,
|
|
4733
|
+
buildInput: {
|
|
4734
|
+
...kindBuildInput,
|
|
4735
|
+
gas: x402Terms.gas,
|
|
4736
|
+
currentEpoch: x402Terms.currentEpoch
|
|
4737
|
+
},
|
|
4738
|
+
settlement: {
|
|
4739
|
+
status: "success",
|
|
4740
|
+
amount: offer.amount,
|
|
4741
|
+
recipient: resolved.recipient,
|
|
4742
|
+
coinType: resolved.coinType,
|
|
4743
|
+
asset: offer.asset,
|
|
4744
|
+
grantObjectId: resolved.grantObjectId,
|
|
4745
|
+
poolObjectId: resolved.poolObjectId
|
|
4746
|
+
},
|
|
4747
|
+
onProgress: args.onX402Progress,
|
|
4748
|
+
abort: abortBeforeSign
|
|
4749
|
+
});
|
|
4750
|
+
}
|
|
4751
|
+
const gasMode = resolveGasMode(deps);
|
|
4752
|
+
const execute = deps.executeTx;
|
|
4753
|
+
if (gasMode === "none") {
|
|
4754
|
+
return abortBeforeSign("EXECUTE_UNAVAILABLE", PUBLIC_DETAIL.EXECUTE_UNAVAILABLE);
|
|
4755
|
+
}
|
|
4756
|
+
if (gasMode === "agent" && !execute) {
|
|
4757
|
+
return abortBeforeSign("EXECUTE_UNAVAILABLE", PUBLIC_DETAIL.EXECUTE_UNAVAILABLE);
|
|
4758
|
+
}
|
|
4759
|
+
if (typeof deps.getSettledTx !== "function") {
|
|
4760
|
+
return abortBeforeSign("FINALITY_UNAVAILABLE", PUBLIC_DETAIL.FINALITY_UNAVAILABLE);
|
|
4761
|
+
}
|
|
4762
|
+
const signer = await resolveDelegateSigner(session.signer, resolved.delegateAddress, logError);
|
|
4763
|
+
if (typeof signer === "string") {
|
|
4764
|
+
return abortBeforeSign(signer, PUBLIC_DETAIL[signer]);
|
|
4765
|
+
}
|
|
3945
4766
|
let txBytes;
|
|
3946
4767
|
let sponsoredExecuteToken;
|
|
3947
4768
|
let sponsoredCreateDigest;
|
|
@@ -4015,7 +4836,7 @@ async function payDelegatedSpendAccount(args) {
|
|
|
4015
4836
|
return abortBeforeSign("GRANT_DENIED", grantDeniedDetail(err));
|
|
4016
4837
|
}
|
|
4017
4838
|
}
|
|
4018
|
-
const allowedPayTarget = payMoveCallTarget(session.packageId, snapshot.accessMode);
|
|
4839
|
+
const allowedPayTarget = payMoveCallTarget(session.packageId, snapshot.accessMode, guardedResolution !== void 0);
|
|
4019
4840
|
try {
|
|
4020
4841
|
const createRes = await fetchSponsor(`${base}/api/sponsor`, {
|
|
4021
4842
|
method: "POST",
|
|
@@ -4080,21 +4901,7 @@ async function payDelegatedSpendAccount(args) {
|
|
|
4080
4901
|
}
|
|
4081
4902
|
}
|
|
4082
4903
|
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
|
-
});
|
|
4904
|
+
assertPayTransaction(txBytes, expectedPay);
|
|
4098
4905
|
} catch (err) {
|
|
4099
4906
|
logInternal(logError, "pre-sign pay validation failed", err);
|
|
4100
4907
|
return abortBeforeSign(gasMode === "sponsored" ? "SPONSORED_TX_MISMATCH" : "TX_MISMATCH", gasMode === "sponsored" ? PUBLIC_DETAIL.SPONSORED_TX_MISMATCH : PUBLIC_DETAIL.TX_MISMATCH);
|
|
@@ -4166,7 +4973,7 @@ async function payDelegatedSpendAccount(args) {
|
|
|
4166
4973
|
txDigest: errDigest ?? finalDigest
|
|
4167
4974
|
}), logError);
|
|
4168
4975
|
}
|
|
4169
|
-
const getFinalized = deps.
|
|
4976
|
+
const getFinalized = deps.getSettledTx;
|
|
4170
4977
|
let finalized = null;
|
|
4171
4978
|
try {
|
|
4172
4979
|
for (let attempt = 0; attempt < FINALIZATION_ATTEMPTS; attempt += 1) {
|
|
@@ -4177,7 +4984,7 @@ async function payDelegatedSpendAccount(args) {
|
|
|
4177
4984
|
await delay(FINALIZATION_DELAY_MS);
|
|
4178
4985
|
}
|
|
4179
4986
|
} catch (err) {
|
|
4180
|
-
logInternal(logError, "
|
|
4987
|
+
logInternal(logError, "getSettledTx threw after broadcast", err);
|
|
4181
4988
|
return completeAttempt(ledger, paymentId, ambiguous("FINALITY_AMBIGUOUS", PUBLIC_DETAIL.FINALITY_AMBIGUOUS, {
|
|
4182
4989
|
paymentId,
|
|
4183
4990
|
txDigest: finalDigest
|
|
@@ -4232,10 +5039,18 @@ async function payDelegatedSpendAccount(args) {
|
|
|
4232
5039
|
second = await fetchImpl(resourceUrl, paidRequestInit(request, { [MPP_CREDENTIAL_HEADER]: proof }));
|
|
4233
5040
|
} catch (err) {
|
|
4234
5041
|
logInternal(logError, "proof presentation failed", err);
|
|
4235
|
-
return completeAttempt(ledger, paymentId, ambiguous("PROOF_PRESENTATION_FAILED", PUBLIC_DETAIL.PROOF_PRESENTATION_FAILED, {
|
|
5042
|
+
return completeAttempt(ledger, paymentId, ambiguous("PROOF_PRESENTATION_FAILED", PUBLIC_DETAIL.PROOF_PRESENTATION_FAILED, {
|
|
5043
|
+
paymentId,
|
|
5044
|
+
txDigest: finalDigest,
|
|
5045
|
+
settlement
|
|
5046
|
+
}), logError);
|
|
4236
5047
|
}
|
|
4237
5048
|
if (second.status === 503) {
|
|
4238
|
-
return completeAttempt(ledger, paymentId, ambiguous(gatewayRefusedCode(second, logError), PUBLIC_DETAIL.PROOF_PRESENTATION_FAILED, {
|
|
5049
|
+
return completeAttempt(ledger, paymentId, ambiguous(gatewayRefusedCode(second, logError), PUBLIC_DETAIL.PROOF_PRESENTATION_FAILED, {
|
|
5050
|
+
paymentId,
|
|
5051
|
+
txDigest: finalDigest,
|
|
5052
|
+
settlement
|
|
5053
|
+
}), logError);
|
|
4239
5054
|
}
|
|
4240
5055
|
let receipt = null;
|
|
4241
5056
|
try {
|
|
@@ -4244,14 +5059,22 @@ async function payDelegatedSpendAccount(args) {
|
|
|
4244
5059
|
logInternal(logError, "read receipt failed", err);
|
|
4245
5060
|
}
|
|
4246
5061
|
if (second.status !== 200) {
|
|
4247
|
-
return completeAttempt(ledger, paymentId, ambiguous(gatewayRefusedCode(second, logError), PUBLIC_DETAIL.GATEWAY_REFUSED, {
|
|
5062
|
+
return completeAttempt(ledger, paymentId, ambiguous(gatewayRefusedCode(second, logError), PUBLIC_DETAIL.GATEWAY_REFUSED, {
|
|
5063
|
+
paymentId,
|
|
5064
|
+
txDigest: finalDigest,
|
|
5065
|
+
settlement
|
|
5066
|
+
}), logError);
|
|
4248
5067
|
}
|
|
4249
5068
|
let body;
|
|
4250
5069
|
try {
|
|
4251
5070
|
body = await readBody(second);
|
|
4252
5071
|
} catch (err) {
|
|
4253
5072
|
logInternal(logError, "read delivery body failed", err);
|
|
4254
|
-
return completeAttempt(ledger, paymentId, ambiguous("PROOF_PRESENTATION_FAILED", PUBLIC_DETAIL.PROOF_PRESENTATION_FAILED, {
|
|
5073
|
+
return completeAttempt(ledger, paymentId, ambiguous("PROOF_PRESENTATION_FAILED", PUBLIC_DETAIL.PROOF_PRESENTATION_FAILED, {
|
|
5074
|
+
paymentId,
|
|
5075
|
+
txDigest: finalDigest,
|
|
5076
|
+
settlement
|
|
5077
|
+
}), logError);
|
|
4255
5078
|
}
|
|
4256
5079
|
return completeAttempt(ledger, paymentId, {
|
|
4257
5080
|
status: "ok",
|
|
@@ -4263,6 +5086,179 @@ async function payDelegatedSpendAccount(args) {
|
|
|
4263
5086
|
settlement
|
|
4264
5087
|
}, logError);
|
|
4265
5088
|
}
|
|
5089
|
+
async function resolveDelegateSigner(input, delegateAddress, logError) {
|
|
5090
|
+
let signer;
|
|
5091
|
+
try {
|
|
5092
|
+
signer = await resolveSigner(input);
|
|
5093
|
+
} catch (err) {
|
|
5094
|
+
logInternal(logError, "resolve signer failed", err);
|
|
5095
|
+
return "SIGN_FAILED";
|
|
5096
|
+
}
|
|
5097
|
+
return sameAddress2(signer.address, delegateAddress) ? signer : "POLICY_REJECTED";
|
|
5098
|
+
}
|
|
5099
|
+
async function x402Preflight(dialect, offer, deps, logError) {
|
|
5100
|
+
if (dialect !== "x402") {
|
|
5101
|
+
logInternal(logError, "x402 requested but the 402 carried no x402 offer");
|
|
5102
|
+
return null;
|
|
5103
|
+
}
|
|
5104
|
+
const gas = readX402GasPin(offer);
|
|
5105
|
+
if (!gas) {
|
|
5106
|
+
logInternal(logError, "x402 offer lacks readable sponsor gas pins");
|
|
5107
|
+
return null;
|
|
5108
|
+
}
|
|
5109
|
+
if (typeof deps.getCurrentEpoch !== "function") {
|
|
5110
|
+
logInternal(logError, "x402 needs a current-epoch reader");
|
|
5111
|
+
return null;
|
|
5112
|
+
}
|
|
5113
|
+
try {
|
|
5114
|
+
return { gas, currentEpoch: BigInt(await deps.getCurrentEpoch()) };
|
|
5115
|
+
} catch (err) {
|
|
5116
|
+
logInternal(logError, "read current epoch failed", err);
|
|
5117
|
+
return null;
|
|
5118
|
+
}
|
|
5119
|
+
}
|
|
5120
|
+
function emitX402Progress(ctx, event) {
|
|
5121
|
+
if (!ctx.onProgress)
|
|
5122
|
+
return;
|
|
5123
|
+
try {
|
|
5124
|
+
ctx.onProgress(event);
|
|
5125
|
+
} catch (err) {
|
|
5126
|
+
logInternal(ctx.deps.logError, "x402 progress callback threw", err);
|
|
5127
|
+
}
|
|
5128
|
+
}
|
|
5129
|
+
async function payX402Reserved(ctx) {
|
|
5130
|
+
const logError = ctx.deps.logError;
|
|
5131
|
+
const signer = await resolveDelegateSigner(ctx.session.signer, ctx.expected.delegate, logError);
|
|
5132
|
+
if (typeof signer === "string")
|
|
5133
|
+
return ctx.abort(signer, PUBLIC_DETAIL[signer]);
|
|
5134
|
+
let txBytes;
|
|
5135
|
+
try {
|
|
5136
|
+
const build = ctx.deps.buildX402PayTx ?? buildSpendAccountX402PayTx;
|
|
5137
|
+
txBytes = (await build(ctx.buildInput)).txBytes;
|
|
5138
|
+
} catch (err) {
|
|
5139
|
+
logInternal(logError, "x402 pay build failed", err);
|
|
5140
|
+
return ctx.abort("BUILD_FAILED", PUBLIC_DETAIL.BUILD_FAILED);
|
|
5141
|
+
}
|
|
5142
|
+
try {
|
|
5143
|
+
assertPayTransaction(txBytes, ctx.expected);
|
|
5144
|
+
} catch (err) {
|
|
5145
|
+
logInternal(logError, "pre-sign x402 pay validation failed", err);
|
|
5146
|
+
return ctx.abort("TX_MISMATCH", PUBLIC_DETAIL.TX_MISMATCH);
|
|
5147
|
+
}
|
|
5148
|
+
emitX402Progress(ctx, { stage: "built", txBytes });
|
|
5149
|
+
if (ctx.session.assertLive) {
|
|
5150
|
+
try {
|
|
5151
|
+
await ctx.session.assertLive();
|
|
5152
|
+
} catch (err) {
|
|
5153
|
+
logInternal(logError, "assertLive failed at sign", err);
|
|
5154
|
+
return ctx.abort("GRANT_DENIED", grantDeniedDetail(err));
|
|
5155
|
+
}
|
|
5156
|
+
}
|
|
5157
|
+
let signature;
|
|
5158
|
+
try {
|
|
5159
|
+
signature = await signer.signTransaction(txBytes);
|
|
5160
|
+
} catch (err) {
|
|
5161
|
+
logInternal(logError, "delegate sign failed", err);
|
|
5162
|
+
return ctx.abort("SIGN_FAILED", PUBLIC_DETAIL.SIGN_FAILED);
|
|
5163
|
+
}
|
|
5164
|
+
emitX402Progress(ctx, { stage: "signed", signature });
|
|
5165
|
+
let header;
|
|
5166
|
+
try {
|
|
5167
|
+
header = encodeCredential({
|
|
5168
|
+
challengeId: ctx.offer.challengeId,
|
|
5169
|
+
offer: ctx.offer,
|
|
5170
|
+
txBytes,
|
|
5171
|
+
signature,
|
|
5172
|
+
payer: ctx.expected.delegate
|
|
5173
|
+
});
|
|
5174
|
+
} catch (err) {
|
|
5175
|
+
logInternal(logError, "encode x402 credential failed", err);
|
|
5176
|
+
return ctx.abort("BUILD_FAILED", PUBLIC_DETAIL.BUILD_FAILED);
|
|
5177
|
+
}
|
|
5178
|
+
return recordX402Answer(ctx, await presentX402Credential(ctx, header));
|
|
5179
|
+
}
|
|
5180
|
+
function isX402Failure(value) {
|
|
5181
|
+
return value.success === false;
|
|
5182
|
+
}
|
|
5183
|
+
async function presentX402Credential(ctx, header) {
|
|
5184
|
+
const attempts = ctx.deps.x402PresentAttempts ?? X402_PRESENT_ATTEMPTS;
|
|
5185
|
+
const sleep = ctx.deps.sleep ?? delay;
|
|
5186
|
+
let txDigest;
|
|
5187
|
+
for (let attempt = 1; attempt <= attempts; attempt += 1) {
|
|
5188
|
+
emitX402Progress(ctx, { stage: "presented", header, attempt });
|
|
5189
|
+
let response;
|
|
5190
|
+
try {
|
|
5191
|
+
response = await ctx.fetchImpl(ctx.resourceUrl, paidRequestInit(ctx.request, { [X402_PAYMENT_HEADER]: header }));
|
|
5192
|
+
} catch (err) {
|
|
5193
|
+
logInternal(ctx.deps.logError, "x402 credential presentation failed", err);
|
|
5194
|
+
return { kind: "unresolved", txDigest };
|
|
5195
|
+
}
|
|
5196
|
+
const paymentResponse = response.headers.get(X402_RESPONSE_HEADER);
|
|
5197
|
+
emitX402Progress(ctx, {
|
|
5198
|
+
stage: "answered",
|
|
5199
|
+
status: response.status,
|
|
5200
|
+
paymentResponse,
|
|
5201
|
+
attempt
|
|
5202
|
+
});
|
|
5203
|
+
const decoded = paymentResponse ? decodeSettlementResponse(paymentResponse) : null;
|
|
5204
|
+
if (decoded && isX402Failure(decoded) && decoded.transaction.trim()) {
|
|
5205
|
+
txDigest = decoded.transaction.trim();
|
|
5206
|
+
}
|
|
5207
|
+
if (response.status !== 503) {
|
|
5208
|
+
return classifyX402Answer(response, decoded, {
|
|
5209
|
+
challengeId: ctx.offer.challengeId,
|
|
5210
|
+
txDigest,
|
|
5211
|
+
firstPresentation: attempt === 1
|
|
5212
|
+
});
|
|
5213
|
+
}
|
|
5214
|
+
if (attempt < attempts)
|
|
5215
|
+
await sleep(X402_PRESENT_DELAY_MS);
|
|
5216
|
+
}
|
|
5217
|
+
return { kind: "unresolved", txDigest };
|
|
5218
|
+
}
|
|
5219
|
+
function classifyX402Answer(response, decoded, seen) {
|
|
5220
|
+
const receipt = asValidatedReceipt(decoded);
|
|
5221
|
+
if (response.ok && receipt && receipt.challengeId === seen.challengeId) {
|
|
5222
|
+
return { kind: "settled", receipt, response };
|
|
5223
|
+
}
|
|
5224
|
+
if (seen.firstPresentation && response.status === 402 && decoded && isX402Failure(decoded) && !decoded.transaction.trim()) {
|
|
5225
|
+
return { kind: "refused", response };
|
|
5226
|
+
}
|
|
5227
|
+
return { kind: "unresolved", txDigest: seen.txDigest };
|
|
5228
|
+
}
|
|
5229
|
+
async function recordX402Answer(ctx, answer) {
|
|
5230
|
+
const { ledger, paymentId } = ctx;
|
|
5231
|
+
const logError = ctx.deps.logError;
|
|
5232
|
+
if (answer.kind === "unresolved") {
|
|
5233
|
+
return completeAttempt(ledger, paymentId, ambiguous("EXECUTE_AMBIGUOUS", PUBLIC_DETAIL.EXECUTE_AMBIGUOUS, {
|
|
5234
|
+
paymentId,
|
|
5235
|
+
txDigest: answer.txDigest
|
|
5236
|
+
}), logError);
|
|
5237
|
+
}
|
|
5238
|
+
if (answer.kind === "refused") {
|
|
5239
|
+
return ctx.abort(gatewayRefusedCode(answer.response, logError), PUBLIC_DETAIL.GATEWAY_REFUSED);
|
|
5240
|
+
}
|
|
5241
|
+
const settlement = {
|
|
5242
|
+
...ctx.settlement,
|
|
5243
|
+
txDigest: answer.receipt.txDigest
|
|
5244
|
+
};
|
|
5245
|
+
let body;
|
|
5246
|
+
try {
|
|
5247
|
+
body = await readBody(answer.response);
|
|
5248
|
+
} catch (err) {
|
|
5249
|
+
logInternal(logError, "read delivery body failed", err);
|
|
5250
|
+
return completeAttempt(ledger, paymentId, ambiguous("PROOF_PRESENTATION_FAILED", PUBLIC_DETAIL.PROOF_PRESENTATION_FAILED, { paymentId, txDigest: settlement.txDigest, settlement }), logError);
|
|
5251
|
+
}
|
|
5252
|
+
return completeAttempt(ledger, paymentId, {
|
|
5253
|
+
status: "ok",
|
|
5254
|
+
paid: true,
|
|
5255
|
+
body,
|
|
5256
|
+
receipt: answer.receipt,
|
|
5257
|
+
txDigest: settlement.txDigest,
|
|
5258
|
+
paymentId,
|
|
5259
|
+
settlement
|
|
5260
|
+
}, logError);
|
|
5261
|
+
}
|
|
4266
5262
|
function extractDigestFromError(err) {
|
|
4267
5263
|
if (!err || typeof err !== "object")
|
|
4268
5264
|
return void 0;
|
|
@@ -4279,9 +5275,35 @@ function settleOpenRecipientTarget(packageId) {
|
|
|
4279
5275
|
const fn = spendAccountDebitFunction({ accessMode: "open" });
|
|
4280
5276
|
return `${normalizeSuiAddress6(packageId)}::spend_account::${fn}`;
|
|
4281
5277
|
}
|
|
4282
|
-
function payMoveCallTarget(packageId, accessMode = "scoped") {
|
|
5278
|
+
function payMoveCallTarget(packageId, accessMode = "scoped", _guarded = false) {
|
|
4283
5279
|
return accessMode === "open" ? settleOpenRecipientTarget(packageId) : payTarget(packageId);
|
|
4284
5280
|
}
|
|
5281
|
+
function resolveGuardedSettleOfflineInputs(input) {
|
|
5282
|
+
const core = [input.exclusionDirectoryId, input.recipientRegistryId, input.expiresAtMs];
|
|
5283
|
+
const present = core.filter((v) => v !== void 0).length;
|
|
5284
|
+
if (present !== 3) {
|
|
5285
|
+
throw new Error("guarded settle requires exclusionDirectoryId, recipientRegistryId and expiresAtMs together (all or none)");
|
|
5286
|
+
}
|
|
5287
|
+
const expires = BigInt(input.expiresAtMs);
|
|
5288
|
+
if (expires <= 0n) {
|
|
5289
|
+
throw new Error("guarded settle expiresAtMs must be a positive epoch-ms value");
|
|
5290
|
+
}
|
|
5291
|
+
if (/^0x0+$/i.test(input.exclusionDirectoryId) || /^0x0+$/i.test(input.recipientRegistryId)) {
|
|
5292
|
+
throw new Error("guarded settle directory and registry ids must be non-zero");
|
|
5293
|
+
}
|
|
5294
|
+
const dirVer = input.exclusionDirectoryInitialSharedVersion;
|
|
5295
|
+
const regVer = input.recipientRegistryInitialSharedVersion;
|
|
5296
|
+
if (!dirVer || !String(dirVer).trim() || !regVer || !String(regVer).trim()) {
|
|
5297
|
+
throw new Error("guarded offline settle requires directory and registry initial shared versions");
|
|
5298
|
+
}
|
|
5299
|
+
return {
|
|
5300
|
+
exclusionDirectoryId: input.exclusionDirectoryId,
|
|
5301
|
+
recipientRegistryId: input.recipientRegistryId,
|
|
5302
|
+
exclusionDirectoryInitialSharedVersion: String(dirVer),
|
|
5303
|
+
recipientRegistryInitialSharedVersion: String(regVer),
|
|
5304
|
+
expiresAtMs: input.expiresAtMs
|
|
5305
|
+
};
|
|
5306
|
+
}
|
|
4285
5307
|
var PreparedVerificationUnavailableError = class extends Error {
|
|
4286
5308
|
code = DELEGATED_PAY_ERROR_CODES.PREPARED_VERIFY_UNAVAILABLE;
|
|
4287
5309
|
constructor(message, options) {
|
|
@@ -4299,17 +5321,42 @@ function preparedPureBytes(input) {
|
|
|
4299
5321
|
if (!input || input.$kind !== "Pure" || !input.Pure?.bytes)
|
|
4300
5322
|
return null;
|
|
4301
5323
|
try {
|
|
4302
|
-
return
|
|
5324
|
+
return fromBase646(input.Pure.bytes);
|
|
4303
5325
|
} catch {
|
|
4304
5326
|
return null;
|
|
4305
5327
|
}
|
|
4306
5328
|
}
|
|
5329
|
+
function preparedPureVectorU8(input) {
|
|
5330
|
+
const raw = preparedPureBytes(input);
|
|
5331
|
+
try {
|
|
5332
|
+
return Uint8Array.from(bcs5.vector(bcs5.u8()).parse(raw));
|
|
5333
|
+
} catch {
|
|
5334
|
+
}
|
|
5335
|
+
return null;
|
|
5336
|
+
}
|
|
5337
|
+
function sameBytes(a, b) {
|
|
5338
|
+
return Buffer.from(a).equals(Buffer.from(b));
|
|
5339
|
+
}
|
|
5340
|
+
function toHashBytes(val) {
|
|
5341
|
+
if (typeof val === "string") {
|
|
5342
|
+
const clean = val.startsWith("0x") ? val.slice(2) : val;
|
|
5343
|
+
return Uint8Array.from(Buffer.from(clean, "hex"));
|
|
5344
|
+
}
|
|
5345
|
+
return val;
|
|
5346
|
+
}
|
|
5347
|
+
function toTargetHashBytes(val) {
|
|
5348
|
+
if (typeof val === "string") {
|
|
5349
|
+
const clean = val.startsWith("0x") ? val.slice(2) : val;
|
|
5350
|
+
return targetHashBytes(clean);
|
|
5351
|
+
}
|
|
5352
|
+
return val;
|
|
5353
|
+
}
|
|
4307
5354
|
function preparedPureAddress(input) {
|
|
4308
5355
|
const raw = preparedPureBytes(input);
|
|
4309
5356
|
if (!raw)
|
|
4310
5357
|
return null;
|
|
4311
5358
|
try {
|
|
4312
|
-
return normalizeSuiAddress6(
|
|
5359
|
+
return normalizeSuiAddress6(bcs5.Address.parse(raw));
|
|
4313
5360
|
} catch {
|
|
4314
5361
|
return null;
|
|
4315
5362
|
}
|
|
@@ -4319,7 +5366,7 @@ function preparedPureU64(input) {
|
|
|
4319
5366
|
if (!raw)
|
|
4320
5367
|
return null;
|
|
4321
5368
|
try {
|
|
4322
|
-
return BigInt(
|
|
5369
|
+
return BigInt(bcs5.u64().parse(raw));
|
|
4323
5370
|
} catch {
|
|
4324
5371
|
return null;
|
|
4325
5372
|
}
|
|
@@ -4339,9 +5386,12 @@ function preparedSharedObjectId(input) {
|
|
|
4339
5386
|
}
|
|
4340
5387
|
async function verifyPreparedSettlementKind(input) {
|
|
4341
5388
|
const accessMode = input.expected.accessMode ?? "open";
|
|
5389
|
+
if (accessMode !== "scoped" && accessMode !== "open") {
|
|
5390
|
+
throw new Error(`unsupported accessMode: ${String(accessMode)}`);
|
|
5391
|
+
}
|
|
4342
5392
|
let data;
|
|
4343
5393
|
try {
|
|
4344
|
-
data = Transaction4.fromKind(
|
|
5394
|
+
data = Transaction4.fromKind(fromBase646(input.kindBytes)).getData();
|
|
4345
5395
|
} catch {
|
|
4346
5396
|
throw new Error("prepared transaction is not a valid settlement kind");
|
|
4347
5397
|
}
|
|
@@ -4365,16 +5415,25 @@ async function verifyPreparedSettlementKind(input) {
|
|
|
4365
5415
|
return void 0;
|
|
4366
5416
|
return inputs[idx];
|
|
4367
5417
|
};
|
|
4368
|
-
const
|
|
5418
|
+
const poolObjectId = preparedSharedObjectId(resolve(2));
|
|
5419
|
+
if (input.expected.poolObjectId) {
|
|
5420
|
+
if (!poolObjectId || !sameAddress2(poolObjectId, input.expected.poolObjectId)) {
|
|
5421
|
+
throw new Error(`prepared pool ${poolObjectId ?? "(missing)"} does not match expected pool ${input.expected.poolObjectId}`);
|
|
5422
|
+
}
|
|
5423
|
+
}
|
|
5424
|
+
const grantObjectId = preparedSharedObjectId(resolve(3));
|
|
4369
5425
|
if (!grantObjectId) {
|
|
4370
5426
|
throw new Error("prepared transaction does not name a grant object");
|
|
4371
5427
|
}
|
|
5428
|
+
if (input.expected.grantObjectId && !sameAddress2(grantObjectId, input.expected.grantObjectId)) {
|
|
5429
|
+
throw new Error(`prepared grant ${grantObjectId} does not match expected grant ${input.expected.grantObjectId}`);
|
|
5430
|
+
}
|
|
4372
5431
|
if (accessMode === "open") {
|
|
4373
|
-
const recipient = preparedPureAddress(resolve(
|
|
4374
|
-
if (!recipient || !
|
|
5432
|
+
const recipient = preparedPureAddress(resolve(4));
|
|
5433
|
+
if (!recipient || !sameAddress2(recipient, input.expected.recipient)) {
|
|
4375
5434
|
throw new Error(`prepared recipient ${recipient ?? "(missing)"} does not match the requested recipient`);
|
|
4376
5435
|
}
|
|
4377
|
-
const amount = preparedPureU64(resolve(
|
|
5436
|
+
const amount = preparedPureU64(resolve(5));
|
|
4378
5437
|
let expectedAmount;
|
|
4379
5438
|
try {
|
|
4380
5439
|
expectedAmount = BigInt(input.expected.amount);
|
|
@@ -4385,7 +5444,21 @@ async function verifyPreparedSettlementKind(input) {
|
|
|
4385
5444
|
throw new Error(`prepared amount ${amount ?? "(missing)"} does not match the requested amount`);
|
|
4386
5445
|
}
|
|
4387
5446
|
} else {
|
|
4388
|
-
|
|
5447
|
+
if (input.expected.onChainPolicyId !== void 0) {
|
|
5448
|
+
const policyId = preparedPureU64(resolve(4));
|
|
5449
|
+
const expectedPolicyId = BigInt(input.expected.onChainPolicyId);
|
|
5450
|
+
if (policyId !== expectedPolicyId) {
|
|
5451
|
+
throw new Error(`prepared policy_id ${policyId ?? "(missing)"} does not match expected onChainPolicyId`);
|
|
5452
|
+
}
|
|
5453
|
+
}
|
|
5454
|
+
if (input.expected.targetHash) {
|
|
5455
|
+
const targetHash = preparedPureVectorU8(resolve(5));
|
|
5456
|
+
const expectedBytes = toTargetHashBytes(input.expected.targetHash);
|
|
5457
|
+
if (!targetHash || !sameBytes(targetHash, expectedBytes)) {
|
|
5458
|
+
throw new Error("prepared target_hash does not match expected targetHash");
|
|
5459
|
+
}
|
|
5460
|
+
}
|
|
5461
|
+
const amount = preparedPureU64(resolve(6));
|
|
4389
5462
|
let expectedAmount;
|
|
4390
5463
|
try {
|
|
4391
5464
|
expectedAmount = BigInt(input.expected.amount);
|
|
@@ -4395,6 +5468,20 @@ async function verifyPreparedSettlementKind(input) {
|
|
|
4395
5468
|
if (amount === null || amount !== expectedAmount) {
|
|
4396
5469
|
throw new Error(`prepared amount ${amount ?? "(missing)"} does not match the requested amount`);
|
|
4397
5470
|
}
|
|
5471
|
+
if (input.expected.paymentIdHash) {
|
|
5472
|
+
const paymentIdHash2 = preparedPureVectorU8(resolve(7));
|
|
5473
|
+
const expectedBytes = toHashBytes(input.expected.paymentIdHash);
|
|
5474
|
+
if (!paymentIdHash2 || !sameBytes(paymentIdHash2, expectedBytes)) {
|
|
5475
|
+
throw new Error("prepared payment_id_hash does not match expected paymentIdHash");
|
|
5476
|
+
}
|
|
5477
|
+
}
|
|
5478
|
+
if (input.expected.termsHash) {
|
|
5479
|
+
const termsHash2 = preparedPureVectorU8(resolve(8));
|
|
5480
|
+
const expectedBytes = toHashBytes(input.expected.termsHash);
|
|
5481
|
+
if (!termsHash2 || !sameBytes(termsHash2, expectedBytes)) {
|
|
5482
|
+
throw new Error("prepared terms_hash does not match expected termsHash");
|
|
5483
|
+
}
|
|
5484
|
+
}
|
|
4398
5485
|
}
|
|
4399
5486
|
let grant;
|
|
4400
5487
|
try {
|
|
@@ -4405,9 +5492,12 @@ async function verifyPreparedSettlementKind(input) {
|
|
|
4405
5492
|
if (!grant) {
|
|
4406
5493
|
throw new Error("unsigned getObject could not read the named grant");
|
|
4407
5494
|
}
|
|
4408
|
-
if (!grant.delegateAddress || !
|
|
5495
|
+
if (!grant.delegateAddress || !sameAddress2(grant.delegateAddress, input.expected.delegateAddress)) {
|
|
4409
5496
|
throw new Error(`grant delegate ${grant.delegateAddress ?? "(missing)"} does not match the agent address`);
|
|
4410
5497
|
}
|
|
5498
|
+
if (input.expected.poolObjectId && grant.poolObjectId && !sameAddress2(grant.poolObjectId, input.expected.poolObjectId)) {
|
|
5499
|
+
throw new Error(`grant pool ${grant.poolObjectId} does not match expected pool ${input.expected.poolObjectId}`);
|
|
5500
|
+
}
|
|
4411
5501
|
}
|
|
4412
5502
|
function createMemoryLedger() {
|
|
4413
5503
|
const map = /* @__PURE__ */ new Map();
|
|
@@ -4457,10 +5547,10 @@ function createMemoryLedger() {
|
|
|
4457
5547
|
}
|
|
4458
5548
|
|
|
4459
5549
|
// src/auth.ts
|
|
4460
|
-
import { dirname, join as
|
|
5550
|
+
import { dirname as dirname2, join as join3 } from "path";
|
|
4461
5551
|
import {
|
|
4462
5552
|
mkdirSync,
|
|
4463
|
-
readFileSync,
|
|
5553
|
+
readFileSync as readFileSync2,
|
|
4464
5554
|
renameSync,
|
|
4465
5555
|
chmodSync,
|
|
4466
5556
|
unlinkSync,
|
|
@@ -4472,17 +5562,75 @@ import {
|
|
|
4472
5562
|
} from "fs";
|
|
4473
5563
|
import { decodeSuiPrivateKey } from "@mysten/sui/cryptography";
|
|
4474
5564
|
import { Ed25519Keypair as Ed25519Keypair2 } from "@mysten/sui/keypairs/ed25519";
|
|
4475
|
-
import { fromBase64 as
|
|
5565
|
+
import { fromBase64 as fromBase647 } from "@mysten/sui/utils";
|
|
4476
5566
|
import { bytesToHex as bytesToHex4 } from "@noble/hashes/utils.js";
|
|
4477
5567
|
|
|
4478
5568
|
// src/config.ts
|
|
4479
5569
|
import { homedir } from "os";
|
|
4480
|
-
import { join } from "path";
|
|
5570
|
+
import { join as join2 } from "path";
|
|
4481
5571
|
import { SuiGrpcClient as SuiGrpcClient2 } from "@mysten/sui/grpc";
|
|
4482
5572
|
import { normalizeSuiAddress as normalizeSuiAddress7 } from "@mysten/sui/utils";
|
|
5573
|
+
|
|
5574
|
+
// src/hosted-defaults.ts
|
|
5575
|
+
import { existsSync, readFileSync } from "fs";
|
|
5576
|
+
import { dirname, join } from "path";
|
|
5577
|
+
import { fileURLToPath } from "url";
|
|
5578
|
+
var MCP_LOCAL_GATEWAY_URL = "https://gateway-dev-ba84.up.railway.app";
|
|
5579
|
+
var MCP_LOCAL_CONSOLE_URL = "https://suipay-dev.up.railway.app";
|
|
5580
|
+
var MCP_DEV_GATEWAY_URL = "https://gateway-dev-ba84.up.railway.app";
|
|
5581
|
+
var MCP_DEV_CONSOLE_URL = "https://suipay-dev.up.railway.app";
|
|
5582
|
+
var MCP_PROD_GATEWAY_URL = "https://gateway.suipay.dev";
|
|
5583
|
+
var MCP_PROD_CONSOLE_URL = "https://www.sui.io/agentpayments";
|
|
5584
|
+
var MCP_CHANNEL_DEFAULTS = {
|
|
5585
|
+
local: { gatewayUrl: MCP_LOCAL_GATEWAY_URL, consoleUrl: MCP_LOCAL_CONSOLE_URL },
|
|
5586
|
+
dev: { gatewayUrl: MCP_DEV_GATEWAY_URL, consoleUrl: MCP_DEV_CONSOLE_URL },
|
|
5587
|
+
prod: { gatewayUrl: MCP_PROD_GATEWAY_URL, consoleUrl: MCP_PROD_CONSOLE_URL }
|
|
5588
|
+
};
|
|
5589
|
+
function isNodeModulesInstall(moduleUrl) {
|
|
5590
|
+
const text2 = moduleUrl.replace(/\\/g, "/");
|
|
5591
|
+
if (text2.includes("/packages/mcp/")) return false;
|
|
5592
|
+
return text2.includes("/node_modules/");
|
|
5593
|
+
}
|
|
5594
|
+
function detectMcpDefaultChannel(opts) {
|
|
5595
|
+
if (!isNodeModulesInstall(opts.moduleUrl)) return "local";
|
|
5596
|
+
if (typeof opts.version === "string" && opts.version.includes("-dev.")) return "dev";
|
|
5597
|
+
if (!opts.version) return "local";
|
|
5598
|
+
return "prod";
|
|
5599
|
+
}
|
|
5600
|
+
function readOwnPackageVersion(moduleUrl = import.meta.url) {
|
|
5601
|
+
let dir = dirname(fileURLToPath(moduleUrl));
|
|
5602
|
+
for (let i = 0; i < 6; i += 1) {
|
|
5603
|
+
const candidate = join(dir, "package.json");
|
|
5604
|
+
if (existsSync(candidate)) {
|
|
5605
|
+
try {
|
|
5606
|
+
const pkg = JSON.parse(readFileSync(candidate, "utf8"));
|
|
5607
|
+
if (pkg.name === "@cmdoss/suipay-mcp" && typeof pkg.version === "string") {
|
|
5608
|
+
return pkg.version;
|
|
5609
|
+
}
|
|
5610
|
+
} catch {
|
|
5611
|
+
}
|
|
5612
|
+
}
|
|
5613
|
+
const parent = dirname(dir);
|
|
5614
|
+
if (parent === dir) break;
|
|
5615
|
+
dir = parent;
|
|
5616
|
+
}
|
|
5617
|
+
return "";
|
|
5618
|
+
}
|
|
5619
|
+
function resolveMcpHostedDefaults(opts = {}) {
|
|
5620
|
+
const env = opts.env ?? process.env;
|
|
5621
|
+
const moduleUrl = opts.moduleUrl ?? import.meta.url;
|
|
5622
|
+
const version = opts.version ?? readOwnPackageVersion(moduleUrl);
|
|
5623
|
+
const defaults = MCP_CHANNEL_DEFAULTS[detectMcpDefaultChannel({ moduleUrl, version })];
|
|
5624
|
+
return {
|
|
5625
|
+
gatewayUrl: env.SUIPAY_GATEWAY_URL ?? defaults.gatewayUrl,
|
|
5626
|
+
consoleUrl: env.SUIPAY_CONSOLE_URL ?? defaults.consoleUrl
|
|
5627
|
+
};
|
|
5628
|
+
}
|
|
5629
|
+
|
|
5630
|
+
// src/config.ts
|
|
4483
5631
|
function mcpCredentialsPath() {
|
|
4484
5632
|
const home = process.env.HOME ?? process.env.USERPROFILE ?? homedir();
|
|
4485
|
-
return
|
|
5633
|
+
return join2(home, ".suipay", "credentials.json");
|
|
4486
5634
|
}
|
|
4487
5635
|
var MCP_TARGET_PRESETS = {
|
|
4488
5636
|
dev: {
|
|
@@ -4490,7 +5638,7 @@ var MCP_TARGET_PRESETS = {
|
|
|
4490
5638
|
gatewayUrl: "https://gateway-dev-ba84.up.railway.app"
|
|
4491
5639
|
},
|
|
4492
5640
|
local: {
|
|
4493
|
-
consoleUrl: "
|
|
5641
|
+
consoleUrl: "https://suipay-dev.up.railway.app",
|
|
4494
5642
|
gatewayUrl: "http://127.0.0.1:4340"
|
|
4495
5643
|
}
|
|
4496
5644
|
};
|
|
@@ -4580,12 +5728,15 @@ function sameMcpGateway(a, b) {
|
|
|
4580
5728
|
}
|
|
4581
5729
|
function loadMcpConfig(env = process.env, argv = []) {
|
|
4582
5730
|
const network = env.SUIPAY_NETWORK ?? "testnet";
|
|
4583
|
-
const
|
|
4584
|
-
const
|
|
4585
|
-
const
|
|
4586
|
-
const
|
|
5731
|
+
const explicit = parseMcpTargetFlag(argv) ?? parseMcpTargetName(env.SUIPAY_MCP_TARGET);
|
|
5732
|
+
const hosted = resolveMcpHostedDefaults({ env });
|
|
5733
|
+
const preset = explicit ? MCP_TARGET_PRESETS[explicit] : null;
|
|
5734
|
+
const gatewayUrl = preset ? env.SUIPAY_GATEWAY_URL?.trim() || preset.gatewayUrl : hosted.gatewayUrl;
|
|
5735
|
+
const consoleUrl = preset ? env.SUIPAY_CONSOLE_URL?.trim() || preset.consoleUrl : hosted.consoleUrl;
|
|
5736
|
+
const rawDir = env.SUIPAY_EXCLUSION_DIRECTORY_OBJECT_ID?.trim();
|
|
5737
|
+
if (rawDir) assertValidPackageId("SUIPAY_EXCLUSION_DIRECTORY_OBJECT_ID", rawDir);
|
|
4587
5738
|
return {
|
|
4588
|
-
target,
|
|
5739
|
+
...explicit ? { target: explicit } : {},
|
|
4589
5740
|
consoleUrl,
|
|
4590
5741
|
gatewayUrl,
|
|
4591
5742
|
rpcUrl: env.SUIPAY_RPC_URL ?? GRPC_URLS[network],
|
|
@@ -4596,9 +5747,42 @@ function loadMcpConfig(env = process.env, argv = []) {
|
|
|
4596
5747
|
sponsorUrl: env.SUIPAY_SPONSOR_URL ?? `${gatewayUrl.replace(/\/$/, "")}/api/sponsor`,
|
|
4597
5748
|
minFunded: BigInt(env.SUIPAY_MIN_FUNDED ?? "0"),
|
|
4598
5749
|
minMaxPerPayment: BigInt(env.SUIPAY_MIN_MAX_PER_PAYMENT ?? "0"),
|
|
4599
|
-
label: env.SUIPAY_LABEL ?? "SuiPay MCP"
|
|
5750
|
+
label: env.SUIPAY_LABEL ?? "SuiPay MCP",
|
|
5751
|
+
exclusionDirectoryObjectId: rawDir || null
|
|
4600
5752
|
};
|
|
4601
5753
|
}
|
|
5754
|
+
var OBJECT_ID = /^0x(?:[0-9a-fA-F]{1,64})$/;
|
|
5755
|
+
async function publishedExclusionDirectoryId(gatewayUrl, fetchImpl = fetch) {
|
|
5756
|
+
let url;
|
|
5757
|
+
try {
|
|
5758
|
+
url = new URL("/v1/delegate/deployment", gatewayUrl).toString();
|
|
5759
|
+
} catch {
|
|
5760
|
+
return null;
|
|
5761
|
+
}
|
|
5762
|
+
let response;
|
|
5763
|
+
try {
|
|
5764
|
+
response = await fetchImpl(url, {
|
|
5765
|
+
method: "GET",
|
|
5766
|
+
redirect: "manual",
|
|
5767
|
+
signal: AbortSignal.timeout(1500)
|
|
5768
|
+
});
|
|
5769
|
+
} catch {
|
|
5770
|
+
return null;
|
|
5771
|
+
}
|
|
5772
|
+
if (response.status !== 200) return null;
|
|
5773
|
+
let body;
|
|
5774
|
+
try {
|
|
5775
|
+
body = await response.json();
|
|
5776
|
+
} catch {
|
|
5777
|
+
return null;
|
|
5778
|
+
}
|
|
5779
|
+
const id = body && typeof body === "object" && "exclusionDirectoryObjectId" in body ? body.exclusionDirectoryObjectId : void 0;
|
|
5780
|
+
if (typeof id !== "string" || !OBJECT_ID.test(id.trim())) return null;
|
|
5781
|
+
return id.trim();
|
|
5782
|
+
}
|
|
5783
|
+
function rpcClient(cfg) {
|
|
5784
|
+
return new SuiGrpcClient2({ network: cfg.network, baseUrl: cfg.rpcUrl });
|
|
5785
|
+
}
|
|
4602
5786
|
|
|
4603
5787
|
// src/crypto.ts
|
|
4604
5788
|
import { getPublicKeyAsync, utils } from "@noble/ed25519";
|
|
@@ -4626,7 +5810,7 @@ function credsPath() {
|
|
|
4626
5810
|
return mcpCredentialsPath();
|
|
4627
5811
|
}
|
|
4628
5812
|
function credsDir() {
|
|
4629
|
-
return
|
|
5813
|
+
return dirname2(credsPath());
|
|
4630
5814
|
}
|
|
4631
5815
|
var HEX64 = /^[0-9a-fA-F]{64}$/;
|
|
4632
5816
|
var ADDR = /^0x[0-9a-fA-F]{64}$/;
|
|
@@ -4657,7 +5841,7 @@ function readCredentialsJson() {
|
|
|
4657
5841
|
const path = credsPath();
|
|
4658
5842
|
let raw;
|
|
4659
5843
|
try {
|
|
4660
|
-
raw =
|
|
5844
|
+
raw = readFileSync2(path, "utf8");
|
|
4661
5845
|
} catch (error2) {
|
|
4662
5846
|
const code = error2.code;
|
|
4663
5847
|
if (code === "ENOENT" || code === "ENOTDIR") return null;
|
|
@@ -4699,7 +5883,7 @@ function fsyncDir(dir) {
|
|
|
4699
5883
|
}
|
|
4700
5884
|
}
|
|
4701
5885
|
function writeTmpCredentials(dir, payload) {
|
|
4702
|
-
const tmp =
|
|
5886
|
+
const tmp = join3(dir, `.credentials.${process.pid}.${randomSuffix()}.tmp`);
|
|
4703
5887
|
const fd = openSync(tmp, "w", 384);
|
|
4704
5888
|
try {
|
|
4705
5889
|
writeSync(fd, JSON.stringify(payload, null, 2));
|
|
@@ -4774,7 +5958,7 @@ function walletFromSeedHex(seedHex) {
|
|
|
4774
5958
|
return {
|
|
4775
5959
|
address: kp.getPublicKey().toSuiAddress(),
|
|
4776
5960
|
async signTransaction(txBytes) {
|
|
4777
|
-
const { signature } = await kp.signTransaction(
|
|
5961
|
+
const { signature } = await kp.signTransaction(fromBase647(txBytes));
|
|
4778
5962
|
return signature;
|
|
4779
5963
|
}
|
|
4780
5964
|
};
|
|
@@ -4791,7 +5975,7 @@ function walletFromEnvSecret(raw) {
|
|
|
4791
5975
|
return {
|
|
4792
5976
|
address: kp.getPublicKey().toSuiAddress(),
|
|
4793
5977
|
async signTransaction(txBytes) {
|
|
4794
|
-
const { signature } = await kp.signTransaction(
|
|
5978
|
+
const { signature } = await kp.signTransaction(fromBase647(txBytes));
|
|
4795
5979
|
return signature;
|
|
4796
5980
|
}
|
|
4797
5981
|
};
|
|
@@ -4888,9 +6072,29 @@ function recordFromKeypair(kp, opts = {}, existing, operatorSecret) {
|
|
|
4888
6072
|
};
|
|
4889
6073
|
}
|
|
4890
6074
|
var ENV_KEY_MISMATCH = "SUIPAY_MCP_KEY does not match the stored delegate key - refusing to overwrite";
|
|
6075
|
+
function signingMaterial(parsed) {
|
|
6076
|
+
if (!parsed || typeof parsed !== "object") return null;
|
|
6077
|
+
if (isLocalSigningKeyRecord(parsed)) return parsed;
|
|
6078
|
+
const c = parsed;
|
|
6079
|
+
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)) {
|
|
6080
|
+
return null;
|
|
6081
|
+
}
|
|
6082
|
+
const label = typeof c.label === "string" ? c.label.trim() : "";
|
|
6083
|
+
return {
|
|
6084
|
+
version: 2,
|
|
6085
|
+
delegatePrivateKey: c.delegatePrivateKey,
|
|
6086
|
+
delegatePublicKeyHex: c.delegatePublicKeyHex,
|
|
6087
|
+
delegateAddress: c.delegateAddress,
|
|
6088
|
+
createdAt: typeof c.createdAt === "string" && c.createdAt ? c.createdAt : (/* @__PURE__ */ new Date(0)).toISOString(),
|
|
6089
|
+
...isHttpUrl(c.gatewayUrl) ? { gatewayUrl: c.gatewayUrl } : {},
|
|
6090
|
+
...label ? { label } : {}
|
|
6091
|
+
};
|
|
6092
|
+
}
|
|
4891
6093
|
function existingRecordForEnv(parsed) {
|
|
4892
6094
|
if (parsed === null) return null;
|
|
4893
|
-
if (
|
|
6095
|
+
if (isValid(parsed)) return parsed;
|
|
6096
|
+
const material = signingMaterial(parsed);
|
|
6097
|
+
if (material) return material;
|
|
4894
6098
|
throw new Error(
|
|
4895
6099
|
`credentials file at ${credsPath()} failed schema validation - refusing to continue`
|
|
4896
6100
|
);
|
|
@@ -4928,16 +6132,25 @@ async function ensureLocalSigningKey(opts = {}) {
|
|
|
4928
6132
|
}
|
|
4929
6133
|
const parsed = readCredentialsJson();
|
|
4930
6134
|
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)) {
|
|
6135
|
+
if (isValid(parsed) && !isLocalSigningKeyRecord(parsed)) {
|
|
4939
6136
|
return rewriteV1ToLocalSigningKey(parsed, opts);
|
|
4940
6137
|
}
|
|
6138
|
+
const material = signingMaterial(parsed);
|
|
6139
|
+
if (material) {
|
|
6140
|
+
const identity = identityFromStored(
|
|
6141
|
+
material.delegatePrivateKey,
|
|
6142
|
+
material.delegatePublicKeyHex,
|
|
6143
|
+
material.delegateAddress
|
|
6144
|
+
);
|
|
6145
|
+
if (!isLocalSigningKeyRecord(parsed)) {
|
|
6146
|
+
const label = opts.label?.trim() || material.label;
|
|
6147
|
+
writeCredentialsFile({
|
|
6148
|
+
...material,
|
|
6149
|
+
...label ? { label } : {}
|
|
6150
|
+
});
|
|
6151
|
+
}
|
|
6152
|
+
return identity;
|
|
6153
|
+
}
|
|
4941
6154
|
throw new Error(`credentials file at ${credsPath()} failed schema validation - refusing to continue`);
|
|
4942
6155
|
}
|
|
4943
6156
|
const kp = await generateKeypair();
|
|
@@ -4986,7 +6199,7 @@ function loadLocalSigner() {
|
|
|
4986
6199
|
return {
|
|
4987
6200
|
address: parsed.delegateAddress,
|
|
4988
6201
|
async signTransaction(txBytes) {
|
|
4989
|
-
const { signature } = await kp.signTransaction(
|
|
6202
|
+
const { signature } = await kp.signTransaction(fromBase647(txBytes));
|
|
4990
6203
|
return signature;
|
|
4991
6204
|
},
|
|
4992
6205
|
async signPersonalMessage(message) {
|
|
@@ -5028,6 +6241,32 @@ function loadLocalKeypair() {
|
|
|
5028
6241
|
Uint8Array.from(Buffer.from(parsed.delegatePrivateKey, "hex"))
|
|
5029
6242
|
);
|
|
5030
6243
|
}
|
|
6244
|
+
function replaceLocalSigningKey(input) {
|
|
6245
|
+
identityFromStored(
|
|
6246
|
+
input.delegatePrivateKey,
|
|
6247
|
+
input.delegatePublicKeyHex,
|
|
6248
|
+
input.delegateAddress
|
|
6249
|
+
);
|
|
6250
|
+
const parsed = readCredentialsJson();
|
|
6251
|
+
let gatewayUrl;
|
|
6252
|
+
let label;
|
|
6253
|
+
if (parsed && typeof parsed === "object") {
|
|
6254
|
+
const record = parsed;
|
|
6255
|
+
if (isHttpUrl(record.gatewayUrl)) gatewayUrl = record.gatewayUrl;
|
|
6256
|
+
if (typeof record.label === "string" && record.label.trim()) {
|
|
6257
|
+
label = record.label.trim();
|
|
6258
|
+
}
|
|
6259
|
+
}
|
|
6260
|
+
writeCredentialsFile({
|
|
6261
|
+
version: 2,
|
|
6262
|
+
delegatePrivateKey: input.delegatePrivateKey,
|
|
6263
|
+
delegatePublicKeyHex: input.delegatePublicKeyHex,
|
|
6264
|
+
delegateAddress: input.delegateAddress,
|
|
6265
|
+
createdAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
6266
|
+
...gatewayUrl ? { gatewayUrl } : {},
|
|
6267
|
+
...label ? { label } : {}
|
|
6268
|
+
});
|
|
6269
|
+
}
|
|
5031
6270
|
function persistGatewayUrl(gatewayUrl) {
|
|
5032
6271
|
if (!isHttpUrl(gatewayUrl)) {
|
|
5033
6272
|
throw new Error("gatewayUrl must be an http(s) origin");
|
|
@@ -5072,7 +6311,7 @@ function objectWithOwner(object) {
|
|
|
5072
6311
|
}
|
|
5073
6312
|
return { ...object.json, owner: object.owner };
|
|
5074
6313
|
}
|
|
5075
|
-
function
|
|
6314
|
+
function sameAddress3(a, b) {
|
|
5076
6315
|
try {
|
|
5077
6316
|
return normalizeSuiAddress8(a) === normalizeSuiAddress8(b);
|
|
5078
6317
|
} catch {
|
|
@@ -5088,7 +6327,7 @@ function resolveAgentHeldSettlement(cfg, spendAccountPay, env = process.env) {
|
|
|
5088
6327
|
if (!secret) return null;
|
|
5089
6328
|
const local = loadLocalSigner();
|
|
5090
6329
|
if (!local) return null;
|
|
5091
|
-
if (!
|
|
6330
|
+
if (!sameAddress3(local.address, spendAccountPay.snapshot.delegateAddress)) {
|
|
5092
6331
|
return null;
|
|
5093
6332
|
}
|
|
5094
6333
|
const chain = createSpendAccountChain({
|
|
@@ -5096,7 +6335,7 @@ function resolveAgentHeldSettlement(cfg, spendAccountPay, env = process.env) {
|
|
|
5096
6335
|
network: cfg.network,
|
|
5097
6336
|
url: cfg.rpcUrl
|
|
5098
6337
|
});
|
|
5099
|
-
const finality =
|
|
6338
|
+
const finality = createSpendAccountSettledTxReader({ client: chain.client });
|
|
5100
6339
|
const getObjectJson = async (objectId) => {
|
|
5101
6340
|
const { object } = await chain.client.core.getObject({
|
|
5102
6341
|
objectId,
|
|
@@ -5119,7 +6358,7 @@ function resolveAgentHeldSettlement(cfg, spendAccountPay, env = process.env) {
|
|
|
5119
6358
|
sponsor: { sponsorUrl: cfg.gatewayUrl },
|
|
5120
6359
|
gasMode: "sponsored",
|
|
5121
6360
|
resolveSharedObjectVersions: (ids) => resolveSharedObjectVersionsFromGetObject(getObjectJson, ids),
|
|
5122
|
-
|
|
6361
|
+
getSettledTx: (digest) => finality.getSettledTx(digest)
|
|
5123
6362
|
}
|
|
5124
6363
|
};
|
|
5125
6364
|
}
|
|
@@ -5176,9 +6415,16 @@ function toolAllowedByScope(toolName, grantedScope) {
|
|
|
5176
6415
|
}
|
|
5177
6416
|
|
|
5178
6417
|
// src/tools.ts
|
|
5179
|
-
import { isValidSuiAddress, normalizeSuiAddress as
|
|
6418
|
+
import { isValidSuiAddress, normalizeSuiAddress as normalizeSuiAddress15 } from "@mysten/sui/utils";
|
|
5180
6419
|
|
|
5181
6420
|
// src/settle.ts
|
|
6421
|
+
function accessModeFromMoveTarget(target) {
|
|
6422
|
+
const fn = target.split("::").pop() ?? "";
|
|
6423
|
+
if (fn === spendAccountDebitFunction({ accessMode: "open" }) || fn === spendAccountDebitFunction({ accessMode: "open", refundable: true })) {
|
|
6424
|
+
return "open";
|
|
6425
|
+
}
|
|
6426
|
+
return "scoped";
|
|
6427
|
+
}
|
|
5182
6428
|
async function settlePreparedMcpPayment(args) {
|
|
5183
6429
|
try {
|
|
5184
6430
|
await verifyPreparedSettlementKind({
|
|
@@ -5188,9 +6434,9 @@ async function settlePreparedMcpPayment(args) {
|
|
|
5188
6434
|
delegateAddress: args.expected.delegateAddress,
|
|
5189
6435
|
amount: args.expected.amount,
|
|
5190
6436
|
recipient: args.expected.recipient,
|
|
5191
|
-
accessMode:
|
|
5192
|
-
|
|
5193
|
-
)
|
|
6437
|
+
accessMode: accessModeFromMoveTarget(
|
|
6438
|
+
args.signingRequest.submit.moveCallTarget
|
|
6439
|
+
)
|
|
5194
6440
|
},
|
|
5195
6441
|
getObjectJson: args.getObjectJson
|
|
5196
6442
|
});
|
|
@@ -5236,12 +6482,104 @@ async function settlePreparedMcpPayment(args) {
|
|
|
5236
6482
|
}
|
|
5237
6483
|
|
|
5238
6484
|
// ../sdk/dist/agent-sdk/delegate-agent.js
|
|
5239
|
-
import { normalizeSuiAddress as
|
|
6485
|
+
import { normalizeSuiAddress as normalizeSuiAddress11 } from "@mysten/sui/utils";
|
|
5240
6486
|
|
|
5241
|
-
// ../sdk/dist/
|
|
6487
|
+
// ../sdk/dist/buyer-control/exclusion-registry.js
|
|
6488
|
+
import { bcs as bcs6 } from "@mysten/sui/bcs";
|
|
5242
6489
|
import { normalizeSuiAddress as normalizeSuiAddress9 } from "@mysten/sui/utils";
|
|
6490
|
+
var ExclusionRegistryResolutionError = class extends Error {
|
|
6491
|
+
code;
|
|
6492
|
+
constructor(code, message) {
|
|
6493
|
+
super(message);
|
|
6494
|
+
this.code = code;
|
|
6495
|
+
this.name = "ExclusionRegistryResolutionError";
|
|
6496
|
+
}
|
|
6497
|
+
};
|
|
6498
|
+
async function resolveExclusionRegistry(input) {
|
|
6499
|
+
const directoryObjectId = typeof input.directoryObjectId === "string" ? input.directoryObjectId.trim() : "";
|
|
6500
|
+
if (!directoryObjectId) {
|
|
6501
|
+
throw new ExclusionRegistryResolutionError("NO_DIRECTORY", "no exclusion directory object id configured for a guarded offer");
|
|
6502
|
+
}
|
|
6503
|
+
let registryId;
|
|
6504
|
+
try {
|
|
6505
|
+
registryId = await input.reader.boundRegistryId(directoryObjectId, input.recipient);
|
|
6506
|
+
} catch (err) {
|
|
6507
|
+
if (err instanceof ExclusionRegistryResolutionError)
|
|
6508
|
+
throw err;
|
|
6509
|
+
throw new ExclusionRegistryResolutionError("REGISTRY_UNREADABLE", "could not read the exclusion directory binding for this recipient");
|
|
6510
|
+
}
|
|
6511
|
+
if (!registryId || !registryId.trim()) {
|
|
6512
|
+
throw new ExclusionRegistryResolutionError("REGISTRY_NOT_PROVISIONED", "no RecipientRegistry is bound for this recipient in the exclusion directory");
|
|
6513
|
+
}
|
|
6514
|
+
const directoryInitialSharedVersion = await input.reader.initialSharedVersion(directoryObjectId);
|
|
6515
|
+
if (!directoryInitialSharedVersion || !directoryInitialSharedVersion.trim()) {
|
|
6516
|
+
throw new ExclusionRegistryResolutionError("DIRECTORY_VERSION_UNRESOLVED", "could not resolve the exclusion directory initial shared version");
|
|
6517
|
+
}
|
|
6518
|
+
const registryInitialSharedVersion = await input.reader.initialSharedVersion(registryId);
|
|
6519
|
+
if (!registryInitialSharedVersion || !registryInitialSharedVersion.trim()) {
|
|
6520
|
+
throw new ExclusionRegistryResolutionError("REGISTRY_VERSION_UNRESOLVED", "could not resolve the recipient registry initial shared version");
|
|
6521
|
+
}
|
|
6522
|
+
return {
|
|
6523
|
+
recipientRegistryId: normalizeSuiAddress9(registryId),
|
|
6524
|
+
directoryInitialSharedVersion,
|
|
6525
|
+
registryInitialSharedVersion
|
|
6526
|
+
};
|
|
6527
|
+
}
|
|
6528
|
+
function initialSharedVersionFromOwner2(owner) {
|
|
6529
|
+
if (!owner || typeof owner !== "object")
|
|
6530
|
+
return null;
|
|
6531
|
+
const shared = owner.Shared;
|
|
6532
|
+
if (shared?.initialSharedVersion != null) {
|
|
6533
|
+
return String(shared.initialSharedVersion);
|
|
6534
|
+
}
|
|
6535
|
+
return null;
|
|
6536
|
+
}
|
|
6537
|
+
function isAbsentDynamicFieldError(err) {
|
|
6538
|
+
const text2 = (err instanceof Error ? err.message : String(err)).toLowerCase();
|
|
6539
|
+
if (/timeout|timed out|unavailable|econnreset|econnrefused|network|fetch failed|socket| 502| 503| 429/.test(text2)) {
|
|
6540
|
+
return false;
|
|
6541
|
+
}
|
|
6542
|
+
return /\bnot[_ ]found\b|dynamic field not found|no such.*field/.test(text2);
|
|
6543
|
+
}
|
|
6544
|
+
function createGrpcExclusionRegistryReader(client) {
|
|
6545
|
+
return {
|
|
6546
|
+
async boundRegistryId(directoryObjectId, recipient) {
|
|
6547
|
+
let response;
|
|
6548
|
+
try {
|
|
6549
|
+
response = await client.core.getDynamicField({
|
|
6550
|
+
parentId: directoryObjectId,
|
|
6551
|
+
name: { type: "address", bcs: bcs6.Address.serialize(recipient).toBytes() }
|
|
6552
|
+
});
|
|
6553
|
+
} catch (err) {
|
|
6554
|
+
if (isAbsentDynamicFieldError(err))
|
|
6555
|
+
return null;
|
|
6556
|
+
throw new ExclusionRegistryResolutionError("REGISTRY_UNREADABLE", "could not read the exclusion directory binding for this recipient");
|
|
6557
|
+
}
|
|
6558
|
+
const raw = response?.dynamicField?.value?.bcs;
|
|
6559
|
+
if (!raw)
|
|
6560
|
+
return null;
|
|
6561
|
+
try {
|
|
6562
|
+
return normalizeSuiAddress9(bcs6.Address.parse(raw));
|
|
6563
|
+
} catch {
|
|
6564
|
+
return null;
|
|
6565
|
+
}
|
|
6566
|
+
},
|
|
6567
|
+
async initialSharedVersion(objectId) {
|
|
6568
|
+
let response;
|
|
6569
|
+
try {
|
|
6570
|
+
response = await client.core.getObject({ objectId });
|
|
6571
|
+
} catch {
|
|
6572
|
+
return null;
|
|
6573
|
+
}
|
|
6574
|
+
return initialSharedVersionFromOwner2(response?.object?.owner);
|
|
6575
|
+
}
|
|
6576
|
+
};
|
|
6577
|
+
}
|
|
6578
|
+
|
|
6579
|
+
// ../sdk/dist/agent-sdk/verify-prepared-settlement.js
|
|
6580
|
+
import { normalizeSuiAddress as normalizeSuiAddress10 } from "@mysten/sui/utils";
|
|
5243
6581
|
import { Transaction as Transaction5 } from "@mysten/sui/transactions";
|
|
5244
|
-
import { fromBase64 as
|
|
6582
|
+
import { fromBase64 as fromBase648 } from "@mysten/sui/utils";
|
|
5245
6583
|
|
|
5246
6584
|
// ../sdk/dist/protocol/prepared-settlement.js
|
|
5247
6585
|
var PREPARED_SETTLEMENT_VERSION = 1;
|
|
@@ -5269,6 +6607,11 @@ function asPreparedSettlement(value) {
|
|
|
5269
6607
|
"coinType",
|
|
5270
6608
|
"amount",
|
|
5271
6609
|
"recipient",
|
|
6610
|
+
"exclusionDirectoryId",
|
|
6611
|
+
"recipientRegistryId",
|
|
6612
|
+
"exclusionDirectoryInitialSharedVersion",
|
|
6613
|
+
"recipientRegistryInitialSharedVersion",
|
|
6614
|
+
"expiresAtMs",
|
|
5272
6615
|
"payUrl"
|
|
5273
6616
|
];
|
|
5274
6617
|
for (const key of strings) {
|
|
@@ -5293,6 +6636,11 @@ function asPreparedSettlement(value) {
|
|
|
5293
6636
|
coinType: v.coinType,
|
|
5294
6637
|
amount: v.amount,
|
|
5295
6638
|
recipient: v.recipient,
|
|
6639
|
+
exclusionDirectoryId: v.exclusionDirectoryId,
|
|
6640
|
+
recipientRegistryId: v.recipientRegistryId,
|
|
6641
|
+
exclusionDirectoryInitialSharedVersion: v.exclusionDirectoryInitialSharedVersion,
|
|
6642
|
+
recipientRegistryInitialSharedVersion: v.recipientRegistryInitialSharedVersion,
|
|
6643
|
+
expiresAtMs: v.expiresAtMs,
|
|
5296
6644
|
payUrl: v.payUrl
|
|
5297
6645
|
};
|
|
5298
6646
|
}
|
|
@@ -5311,9 +6659,9 @@ var PreparedSettlementRejected = class extends Error {
|
|
|
5311
6659
|
function reject(field, message) {
|
|
5312
6660
|
throw new PreparedSettlementRejected(field, message);
|
|
5313
6661
|
}
|
|
5314
|
-
function
|
|
6662
|
+
function sameAddress4(a, b) {
|
|
5315
6663
|
try {
|
|
5316
|
-
return
|
|
6664
|
+
return normalizeSuiAddress10(a) === normalizeSuiAddress10(b);
|
|
5317
6665
|
} catch {
|
|
5318
6666
|
return false;
|
|
5319
6667
|
}
|
|
@@ -5324,7 +6672,7 @@ function sameCoinType(a, b) {
|
|
|
5324
6672
|
if (pkg === void 0 || rest.length !== 2)
|
|
5325
6673
|
return value.trim();
|
|
5326
6674
|
try {
|
|
5327
|
-
return `${
|
|
6675
|
+
return `${normalizeSuiAddress10(pkg)}::${rest.join("::")}`;
|
|
5328
6676
|
} catch {
|
|
5329
6677
|
return value.trim();
|
|
5330
6678
|
}
|
|
@@ -5344,26 +6692,26 @@ async function verifyPreparedPayment(input) {
|
|
|
5344
6692
|
reject("shape", "the 402 carried no usable prepared settlement");
|
|
5345
6693
|
}
|
|
5346
6694
|
const { offer, delegateAddress } = input;
|
|
5347
|
-
if (!
|
|
6695
|
+
if (!sameAddress4(prepared.sender, delegateAddress)) {
|
|
5348
6696
|
reject("sender", `prepared sender ${prepared.sender} is not this agent's address`);
|
|
5349
6697
|
}
|
|
5350
6698
|
let data;
|
|
5351
6699
|
try {
|
|
5352
|
-
data = Transaction5.from(
|
|
6700
|
+
data = Transaction5.from(fromBase648(prepared.transactionBytes)).getData();
|
|
5353
6701
|
} catch {
|
|
5354
6702
|
reject("transactionBytes", "prepared transaction bytes are not a Sui transaction");
|
|
5355
6703
|
}
|
|
5356
6704
|
const gasOwner = data.gasData?.owner;
|
|
5357
|
-
if (!gasOwner ||
|
|
6705
|
+
if (!gasOwner || sameAddress4(gasOwner, delegateAddress)) {
|
|
5358
6706
|
reject("gasOwner", "prepared transaction is not sponsored: gas would be paid by this agent");
|
|
5359
6707
|
}
|
|
5360
|
-
if (input.expectedGrantObjectId && !
|
|
6708
|
+
if (input.expectedGrantObjectId && !sameAddress4(prepared.grantObjectId, input.expectedGrantObjectId)) {
|
|
5361
6709
|
reject("grantObjectId", `prepared settlement draws on grant ${prepared.grantObjectId}, not the one this agent resolved`);
|
|
5362
6710
|
}
|
|
5363
|
-
if (input.expectedPoolObjectId && !
|
|
6711
|
+
if (input.expectedPoolObjectId && !sameAddress4(prepared.poolObjectId, input.expectedPoolObjectId)) {
|
|
5364
6712
|
reject("poolObjectId", `prepared settlement draws on pool ${prepared.poolObjectId}, not the one this agent resolved`);
|
|
5365
6713
|
}
|
|
5366
|
-
if (!offer.packageId || !
|
|
6714
|
+
if (!offer.packageId || !sameAddress4(prepared.packageId, offer.packageId)) {
|
|
5367
6715
|
reject("packageId", "prepared package id does not match the offer");
|
|
5368
6716
|
}
|
|
5369
6717
|
const wantedTarget = payMoveCallTarget(offer.packageId, prepared.accessMode);
|
|
@@ -5376,9 +6724,19 @@ async function verifyPreparedPayment(input) {
|
|
|
5376
6724
|
if (!sameAmount(prepared.amount, offer.amount)) {
|
|
5377
6725
|
reject("amount", "prepared amount does not match the offer amount");
|
|
5378
6726
|
}
|
|
5379
|
-
if (!
|
|
6727
|
+
if (!sameAddress4(prepared.recipient, offer.payTo)) {
|
|
5380
6728
|
reject("recipient", "prepared recipient does not match the offer payTo");
|
|
5381
6729
|
}
|
|
6730
|
+
const identity = parseRegistryIdentity(offer.registryIdentity);
|
|
6731
|
+
if (!identity) {
|
|
6732
|
+
reject("registryIdentity", "offer carries no valid registry identity for a guarded settle");
|
|
6733
|
+
}
|
|
6734
|
+
if (!sameAddress4(prepared.exclusionDirectoryId, identity.directoryId)) {
|
|
6735
|
+
reject("exclusionDirectoryId", "prepared exclusion directory is not the one the offer committed to");
|
|
6736
|
+
}
|
|
6737
|
+
if (prepared.expiresAtMs !== String(offer.expiresAt)) {
|
|
6738
|
+
reject("expiresAtMs", "prepared guarded expiry does not match the offer expiry");
|
|
6739
|
+
}
|
|
5382
6740
|
const targetHash = offer.targetHash?.trim();
|
|
5383
6741
|
if (!targetHash) {
|
|
5384
6742
|
reject("targetHash", "offer carries no targetHash to bind the settlement to");
|
|
@@ -5398,7 +6756,14 @@ async function verifyPreparedPayment(input) {
|
|
|
5398
6756
|
targetHash,
|
|
5399
6757
|
amount: offer.amount,
|
|
5400
6758
|
paymentIdHash: paymentIdHash(offer.challengeId),
|
|
5401
|
-
termsHash: termsHash(offer)
|
|
6759
|
+
termsHash: termsHash(offer),
|
|
6760
|
+
// Guarded-settle inputs, rebuilt from the prepared bytes and validated
|
|
6761
|
+
// against the offer above.
|
|
6762
|
+
exclusionDirectoryId: prepared.exclusionDirectoryId,
|
|
6763
|
+
recipientRegistryId: prepared.recipientRegistryId,
|
|
6764
|
+
exclusionDirectoryInitialSharedVersion: prepared.exclusionDirectoryInitialSharedVersion,
|
|
6765
|
+
recipientRegistryInitialSharedVersion: prepared.recipientRegistryInitialSharedVersion,
|
|
6766
|
+
expiresAtMs: prepared.expiresAtMs
|
|
5402
6767
|
};
|
|
5403
6768
|
let rebuilt;
|
|
5404
6769
|
try {
|
|
@@ -5733,7 +7098,23 @@ async function fetchGatewayDeployment(options) {
|
|
|
5733
7098
|
}
|
|
5734
7099
|
};
|
|
5735
7100
|
}
|
|
5736
|
-
|
|
7101
|
+
if (d.exclusionDirectoryObjectId !== void 0 && !isNonEmptyString2(d.exclusionDirectoryObjectId)) {
|
|
7102
|
+
return {
|
|
7103
|
+
ok: false,
|
|
7104
|
+
error: {
|
|
7105
|
+
code: MANIFEST_ERROR_CODES.MALFORMED_MANIFEST,
|
|
7106
|
+
detail: "gateway deployment identity carries an unusable exclusionDirectoryObjectId"
|
|
7107
|
+
}
|
|
7108
|
+
};
|
|
7109
|
+
}
|
|
7110
|
+
return {
|
|
7111
|
+
ok: true,
|
|
7112
|
+
value: {
|
|
7113
|
+
network: d.network,
|
|
7114
|
+
packageId: d.packageId,
|
|
7115
|
+
...isNonEmptyString2(d.exclusionDirectoryObjectId) ? { exclusionDirectoryObjectId: d.exclusionDirectoryObjectId } : {}
|
|
7116
|
+
}
|
|
7117
|
+
};
|
|
5737
7118
|
}
|
|
5738
7119
|
function explainBlockedGrant(raw, error2) {
|
|
5739
7120
|
if (error2.code !== MANIFEST_ERROR_CODES.MISSING_POLICY_BINDING)
|
|
@@ -5825,7 +7206,7 @@ async function resolvePayerSession(options) {
|
|
|
5825
7206
|
...options.select ? { select: options.select } : {}
|
|
5826
7207
|
});
|
|
5827
7208
|
if (whole.ok)
|
|
5828
|
-
return whole;
|
|
7209
|
+
return { ok: true, value: { ...whole.value, deployment } };
|
|
5829
7210
|
const data = bootstrap.data;
|
|
5830
7211
|
const rawGrants = Array.isArray(data.grants) ? data.grants : [];
|
|
5831
7212
|
const usable = [];
|
|
@@ -5860,11 +7241,12 @@ async function resolvePayerSession(options) {
|
|
|
5860
7241
|
const pick = scoped ?? blocked[0];
|
|
5861
7242
|
return pick ? { ok: false, error: explainBlockedGrant(pick.raw, pick.error) } : whole;
|
|
5862
7243
|
}
|
|
5863
|
-
|
|
7244
|
+
const resolved = manifestToSession({
|
|
5864
7245
|
manifest: { ...data, grants: usable },
|
|
5865
7246
|
signer: options.signer,
|
|
5866
7247
|
...options.select ? { select: options.select } : {}
|
|
5867
7248
|
});
|
|
7249
|
+
return resolved.ok ? { ok: true, value: { ...resolved.value, deployment } } : resolved;
|
|
5868
7250
|
}
|
|
5869
7251
|
|
|
5870
7252
|
// ../sdk/dist/buyer/transport.js
|
|
@@ -5894,6 +7276,15 @@ function mergeObjectEnvelope(object) {
|
|
|
5894
7276
|
var SECRET_KEY_SET = new Set(FORBIDDEN_SECRET_KEYS.map((k) => k.toLowerCase()));
|
|
5895
7277
|
|
|
5896
7278
|
// ../sdk/dist/agent-sdk/delegate-agent.js
|
|
7279
|
+
function resolveExclusionDirectoryId(explicit, discovered) {
|
|
7280
|
+
return explicit?.trim() ? explicit : discovered;
|
|
7281
|
+
}
|
|
7282
|
+
function createExclusionRegistryResolver(client, exclusionDirectoryObjectId) {
|
|
7283
|
+
if (!exclusionDirectoryObjectId || !exclusionDirectoryObjectId.trim())
|
|
7284
|
+
return void 0;
|
|
7285
|
+
const reader = createGrpcExclusionRegistryReader(client);
|
|
7286
|
+
return async ({ directoryId, recipient }) => resolveExclusionRegistry({ directoryObjectId: directoryId, recipient, reader });
|
|
7287
|
+
}
|
|
5897
7288
|
async function verifyGrantOnChain(input) {
|
|
5898
7289
|
let state;
|
|
5899
7290
|
try {
|
|
@@ -5920,10 +7311,10 @@ async function verifyGrantOnChain(input) {
|
|
|
5920
7311
|
ok: false,
|
|
5921
7312
|
error: { code: MANIFEST_ERROR_CODES.GRANT_NOT_VERIFIED, detail }
|
|
5922
7313
|
});
|
|
5923
|
-
if (!state.delegateAddress ||
|
|
7314
|
+
if (!state.delegateAddress || normalizeSuiAddress11(state.delegateAddress) !== normalizeSuiAddress11(input.delegateAddress)) {
|
|
5924
7315
|
return notVerified(`grant ${input.grantObjectId} does not name this delegate on chain`);
|
|
5925
7316
|
}
|
|
5926
|
-
if (!state.poolObjectId ||
|
|
7317
|
+
if (!state.poolObjectId || normalizeSuiAddress11(state.poolObjectId) !== normalizeSuiAddress11(input.poolObjectId)) {
|
|
5927
7318
|
return notVerified(`grant ${input.grantObjectId} draws on a different pool than the one resolved`);
|
|
5928
7319
|
}
|
|
5929
7320
|
if (state.revoked === true) {
|
|
@@ -5956,8 +7347,17 @@ async function createPayer(options) {
|
|
|
5956
7347
|
const { session, binding } = resolved.value;
|
|
5957
7348
|
const deployment = {
|
|
5958
7349
|
network: session.network,
|
|
5959
|
-
packageId: session.packageId
|
|
7350
|
+
packageId: session.packageId,
|
|
7351
|
+
// What the GATEWAY published, not what this payer settled on: a caller
|
|
7352
|
+
// override belongs to the payer's configuration, not to the deployment it
|
|
7353
|
+
// is describing. Dropping this left the field permanently undefined on the
|
|
7354
|
+
// one instance a consumer can reach, so anything asking the payer whether
|
|
7355
|
+
// guarded settles are available got "no" while it was doing them.
|
|
7356
|
+
...resolved.value.deployment.exclusionDirectoryObjectId ? {
|
|
7357
|
+
exclusionDirectoryObjectId: resolved.value.deployment.exclusionDirectoryObjectId
|
|
7358
|
+
} : {}
|
|
5960
7359
|
};
|
|
7360
|
+
const exclusionDirectoryObjectId = resolveExclusionDirectoryId(options.exclusionDirectoryObjectId, resolved.value.deployment.exclusionDirectoryObjectId);
|
|
5961
7361
|
let network;
|
|
5962
7362
|
try {
|
|
5963
7363
|
network = toGrpcNetwork(deployment.network);
|
|
@@ -5987,7 +7387,7 @@ async function createPayer(options) {
|
|
|
5987
7387
|
}
|
|
5988
7388
|
};
|
|
5989
7389
|
}
|
|
5990
|
-
const finality =
|
|
7390
|
+
const finality = createSpendAccountSettledTxReader({ client: chain.client });
|
|
5991
7391
|
const getObjectJson = async (objectId) => {
|
|
5992
7392
|
const { object } = await chain.client.core.getObject({
|
|
5993
7393
|
objectId,
|
|
@@ -6010,7 +7410,18 @@ async function createPayer(options) {
|
|
|
6010
7410
|
sponsor: { sponsorUrl: options.gatewayUrl },
|
|
6011
7411
|
gasMode: "sponsored",
|
|
6012
7412
|
resolveSharedObjectVersions: (ids) => resolveSharedObjectVersionsFromGetObject(getObjectJson, ids),
|
|
6013
|
-
|
|
7413
|
+
getSettledTx: finality.getSettledTx,
|
|
7414
|
+
// A guarded offer resolves its per-recipient exclusion registry
|
|
7415
|
+
// through this dep, bound to the agent's own gRPC reader. Undefined when no
|
|
7416
|
+
// directory id was configured, so such an offer fails closed (BUILD_FAILED)
|
|
7417
|
+
// rather than building an unguarded settle. Unguarded offers never call it.
|
|
7418
|
+
...(() => {
|
|
7419
|
+
const resolve = createExclusionRegistryResolver(chain.client, exclusionDirectoryObjectId);
|
|
7420
|
+
return resolve ? { resolveExclusionRegistry: resolve } : {};
|
|
7421
|
+
})(),
|
|
7422
|
+
// x402 bounds the transaction's expiration by the current epoch, read from
|
|
7423
|
+
// the same client as every other chain read here.
|
|
7424
|
+
getCurrentEpoch: currentEpochReader(chain.client),
|
|
6014
7425
|
...options.offerSecret ? { offerSecret: options.offerSecret } : {},
|
|
6015
7426
|
...options.fetchImpl ? { fetchImpl: options.fetchImpl } : {},
|
|
6016
7427
|
...options.logError ? { logError: options.logError } : {}
|
|
@@ -6040,7 +7451,9 @@ async function createPayer(options) {
|
|
|
6040
7451
|
cfg: { gatewayUrl: options.gatewayUrl },
|
|
6041
7452
|
deps,
|
|
6042
7453
|
intent: input.intent,
|
|
6043
|
-
...input.preparedTerms ? { preparedTerms: input.preparedTerms } : {}
|
|
7454
|
+
...input.preparedTerms ? { preparedTerms: input.preparedTerms } : {},
|
|
7455
|
+
dialect: input.dialect,
|
|
7456
|
+
onX402Progress: input.onX402Progress
|
|
6044
7457
|
});
|
|
6045
7458
|
}
|
|
6046
7459
|
async function verifySettlement(prepared, offer) {
|
|
@@ -6062,6 +7475,7 @@ async function createPayer(options) {
|
|
|
6062
7475
|
import { createServer } from "http";
|
|
6063
7476
|
import { randomBytes as randomBytes3, timingSafeEqual as timingSafeEqual2 } from "crypto";
|
|
6064
7477
|
import open from "open";
|
|
7478
|
+
import { Ed25519Keypair as Ed25519Keypair3 } from "@mysten/sui/keypairs/ed25519";
|
|
6065
7479
|
var HEX642 = /^[0-9a-f]{64}$/;
|
|
6066
7480
|
var PUBHEX = /^[0-9a-fA-F]{64}$/;
|
|
6067
7481
|
function isPreflight(obj) {
|
|
@@ -6069,6 +7483,11 @@ function isPreflight(obj) {
|
|
|
6069
7483
|
const o = obj;
|
|
6070
7484
|
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
7485
|
}
|
|
7486
|
+
function isAdoptKey(obj) {
|
|
7487
|
+
if (!obj || typeof obj !== "object") return false;
|
|
7488
|
+
const o = obj;
|
|
7489
|
+
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);
|
|
7490
|
+
}
|
|
6072
7491
|
function isCallback(obj) {
|
|
6073
7492
|
if (!obj || typeof obj !== "object") return false;
|
|
6074
7493
|
const o = obj;
|
|
@@ -6193,7 +7612,7 @@ async function startStdioLogin(opts) {
|
|
|
6193
7612
|
res.writeHead(code, { "content-type": type, ...SECURITY_HEADERS, ...extra });
|
|
6194
7613
|
res.end(body);
|
|
6195
7614
|
};
|
|
6196
|
-
const okPath = url.pathname === "/preflight" || url.pathname === "/callback";
|
|
7615
|
+
const okPath = url.pathname === "/preflight" || url.pathname === "/callback" || url.pathname === "/key";
|
|
6197
7616
|
if (req.method === "OPTIONS") {
|
|
6198
7617
|
const extra = { ...cors };
|
|
6199
7618
|
if (okHost && okOrigin && okPath && req.headers["access-control-request-private-network"] === "true") {
|
|
@@ -6264,6 +7683,55 @@ async function startStdioLogin(opts) {
|
|
|
6264
7683
|
);
|
|
6265
7684
|
return;
|
|
6266
7685
|
}
|
|
7686
|
+
if (url.pathname === "/key") {
|
|
7687
|
+
if (!preflightVerified) {
|
|
7688
|
+
res.writeHead(428, {
|
|
7689
|
+
"content-type": "application/json",
|
|
7690
|
+
...cors,
|
|
7691
|
+
...SECURITY_HEADERS
|
|
7692
|
+
});
|
|
7693
|
+
res.end(JSON.stringify({ ok: false, error: "preflight required" }));
|
|
7694
|
+
return;
|
|
7695
|
+
}
|
|
7696
|
+
if (!isAdoptKey(parsed) || !stateEquals(parsed.state, connectState)) {
|
|
7697
|
+
res.writeHead(400, {
|
|
7698
|
+
"content-type": "application/json",
|
|
7699
|
+
...cors,
|
|
7700
|
+
...SECURITY_HEADERS
|
|
7701
|
+
});
|
|
7702
|
+
res.end(JSON.stringify({ ok: false, error: "invalid key payload" }));
|
|
7703
|
+
return;
|
|
7704
|
+
}
|
|
7705
|
+
try {
|
|
7706
|
+
const kp = Ed25519Keypair3.fromSecretKey(
|
|
7707
|
+
Uint8Array.from(Buffer.from(parsed.delegatePrivateKey, "hex"))
|
|
7708
|
+
);
|
|
7709
|
+
const derived = kp.getPublicKey().toSuiAddress();
|
|
7710
|
+
if (derived.toLowerCase() !== parsed.delegateAddress.toLowerCase()) {
|
|
7711
|
+
throw new Error("address mismatch");
|
|
7712
|
+
}
|
|
7713
|
+
replaceLocalSigningKey({
|
|
7714
|
+
delegatePrivateKey: parsed.delegatePrivateKey,
|
|
7715
|
+
delegatePublicKeyHex: parsed.delegatePublicKeyHex,
|
|
7716
|
+
delegateAddress: derived
|
|
7717
|
+
});
|
|
7718
|
+
} catch {
|
|
7719
|
+
res.writeHead(400, {
|
|
7720
|
+
"content-type": "application/json",
|
|
7721
|
+
...cors,
|
|
7722
|
+
...SECURITY_HEADERS
|
|
7723
|
+
});
|
|
7724
|
+
res.end(JSON.stringify({ ok: false, error: "key rejected" }));
|
|
7725
|
+
return;
|
|
7726
|
+
}
|
|
7727
|
+
res.writeHead(200, {
|
|
7728
|
+
"content-type": "application/json",
|
|
7729
|
+
...cors,
|
|
7730
|
+
...SECURITY_HEADERS
|
|
7731
|
+
});
|
|
7732
|
+
res.end(JSON.stringify({ ok: true }));
|
|
7733
|
+
return;
|
|
7734
|
+
}
|
|
6267
7735
|
if (!preflightVerified) {
|
|
6268
7736
|
res.writeHead(428, {
|
|
6269
7737
|
"content-type": "text/html",
|
|
@@ -6346,13 +7814,43 @@ async function startStdioLogin(opts) {
|
|
|
6346
7814
|
}
|
|
6347
7815
|
|
|
6348
7816
|
// src/spend-account-pay.ts
|
|
6349
|
-
import { Transaction as
|
|
7817
|
+
import { Transaction as Transaction8 } from "@mysten/sui/transactions";
|
|
6350
7818
|
import {
|
|
6351
|
-
fromBase64 as
|
|
6352
|
-
normalizeSuiAddress as
|
|
6353
|
-
toBase64 as
|
|
7819
|
+
fromBase64 as fromBase6411,
|
|
7820
|
+
normalizeSuiAddress as normalizeSuiAddress14,
|
|
7821
|
+
toBase64 as toBase647
|
|
6354
7822
|
} from "@mysten/sui/utils";
|
|
7823
|
+
|
|
7824
|
+
// ../sdk/dist/pay-operation/verify-x402-offer.js
|
|
7825
|
+
import { normalizeStructTag as normalizeStructTag7, normalizeSuiAddress as normalizeSuiAddress12 } from "@mysten/sui/utils";
|
|
7826
|
+
|
|
7827
|
+
// ../sdk/dist/pay-operation/verify-direct-tx.js
|
|
7828
|
+
import { Transaction as Transaction6 } from "@mysten/sui/transactions";
|
|
7829
|
+
import { bcs as bcs7 } from "@mysten/sui/bcs";
|
|
7830
|
+
import { fromBase64 as fromBase649, normalizeStructTag as normalizeStructTag8, normalizeSuiAddress as normalizeSuiAddress13 } from "@mysten/sui/utils";
|
|
7831
|
+
|
|
7832
|
+
// ../sdk/dist/pay-operation/resolve-grant-x402.js
|
|
7833
|
+
import { Transaction as Transaction7 } from "@mysten/sui/transactions";
|
|
7834
|
+
import { fromBase64 as fromBase6410, toBase64 as toBase646 } from "@mysten/sui/utils";
|
|
7835
|
+
|
|
7836
|
+
// src/spend-account-pay.ts
|
|
6355
7837
|
var SUI_CLOCK3 = "0x6";
|
|
7838
|
+
var sharedObjectReaderForTests = null;
|
|
7839
|
+
function chainSharedObjectReader(cfg, packageId) {
|
|
7840
|
+
const chain = createSpendAccountChain({
|
|
7841
|
+
packageId,
|
|
7842
|
+
network: cfg.network,
|
|
7843
|
+
url: cfg.rpcUrl
|
|
7844
|
+
});
|
|
7845
|
+
return async (objectId) => {
|
|
7846
|
+
const { object } = await chain.client.core.getObject({
|
|
7847
|
+
objectId,
|
|
7848
|
+
include: { json: true }
|
|
7849
|
+
});
|
|
7850
|
+
if (!object?.owner) return null;
|
|
7851
|
+
return { owner: object.owner };
|
|
7852
|
+
};
|
|
7853
|
+
}
|
|
6356
7854
|
function traceEmitter(trace) {
|
|
6357
7855
|
if (!trace) return () => void 0;
|
|
6358
7856
|
const now = trace.now ?? Date.now;
|
|
@@ -6386,25 +7884,38 @@ function toHex(bytes) {
|
|
|
6386
7884
|
async function buildSpendAccountPayKind2(input) {
|
|
6387
7885
|
if (!input.termsHash.length) throw new Error("termsHash must be non-empty");
|
|
6388
7886
|
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
7887
|
const hashHex = input.targetHash.replace(/^0x/i, "").toLowerCase();
|
|
6392
7888
|
if (!/^[0-9a-f]{64}$/.test(hashHex)) throw new Error("targetHash must be 32-byte hex");
|
|
6393
7889
|
const hash = Uint8Array.from(Buffer.from(hashHex, "hex"));
|
|
6394
|
-
const
|
|
7890
|
+
const guarded = resolveGuardedSettleOfflineInputs(input);
|
|
7891
|
+
const target = payMoveCallTarget(
|
|
7892
|
+
input.packageId,
|
|
7893
|
+
"scoped"
|
|
7894
|
+
);
|
|
7895
|
+
const tx = new Transaction8();
|
|
6395
7896
|
tx.setSender(input.delegate);
|
|
6396
7897
|
tx.moveCall({
|
|
6397
7898
|
target,
|
|
6398
7899
|
typeArguments: [input.coinType],
|
|
6399
7900
|
arguments: [
|
|
7901
|
+
tx.sharedObjectRef({
|
|
7902
|
+
objectId: guarded.exclusionDirectoryId,
|
|
7903
|
+
initialSharedVersion: guarded.exclusionDirectoryInitialSharedVersion,
|
|
7904
|
+
mutable: false
|
|
7905
|
+
}),
|
|
7906
|
+
tx.sharedObjectRef({
|
|
7907
|
+
objectId: guarded.recipientRegistryId,
|
|
7908
|
+
initialSharedVersion: guarded.recipientRegistryInitialSharedVersion,
|
|
7909
|
+
mutable: true
|
|
7910
|
+
}),
|
|
6400
7911
|
tx.sharedObjectRef({
|
|
6401
7912
|
objectId: input.poolObjectId,
|
|
6402
|
-
initialSharedVersion: "1",
|
|
7913
|
+
initialSharedVersion: input.poolInitialSharedVersion?.trim() || "1",
|
|
6403
7914
|
mutable: true
|
|
6404
7915
|
}),
|
|
6405
7916
|
tx.sharedObjectRef({
|
|
6406
7917
|
objectId: input.grantObjectId,
|
|
6407
|
-
initialSharedVersion: "1",
|
|
7918
|
+
initialSharedVersion: input.grantInitialSharedVersion?.trim() || "1",
|
|
6408
7919
|
mutable: true
|
|
6409
7920
|
}),
|
|
6410
7921
|
tx.pure.u64(BigInt(input.policyId)),
|
|
@@ -6412,42 +7923,43 @@ async function buildSpendAccountPayKind2(input) {
|
|
|
6412
7923
|
tx.pure.u64(BigInt(input.amount)),
|
|
6413
7924
|
tx.pure.vector("u8", Array.from(input.paymentIdHash)),
|
|
6414
7925
|
tx.pure.vector("u8", Array.from(input.termsHash)),
|
|
7926
|
+
tx.pure.u64(BigInt(guarded.expiresAtMs)),
|
|
6415
7927
|
tx.sharedObjectRef({
|
|
6416
7928
|
objectId: SUI_CLOCK3,
|
|
6417
|
-
initialSharedVersion: "1",
|
|
7929
|
+
initialSharedVersion: input.clockInitialSharedVersion?.trim() || "1",
|
|
6418
7930
|
mutable: false
|
|
6419
7931
|
})
|
|
6420
7932
|
]
|
|
6421
7933
|
});
|
|
6422
7934
|
const bytes = await tx.build({ onlyTransactionKind: true });
|
|
6423
|
-
return { kindBytes:
|
|
7935
|
+
return { kindBytes: toBase647(bytes) };
|
|
6424
7936
|
}
|
|
6425
7937
|
function verifySpendAccountPayReconstructs(bytes, expected) {
|
|
6426
7938
|
let data;
|
|
6427
7939
|
try {
|
|
6428
|
-
data =
|
|
7940
|
+
data = Transaction8.from(fromBase6411(bytes)).getData();
|
|
6429
7941
|
} catch {
|
|
6430
7942
|
throw new Error("sponsored transaction is not valid Sui transaction bytes");
|
|
6431
7943
|
}
|
|
6432
|
-
if (!data.sender ||
|
|
7944
|
+
if (!data.sender || normalizeSuiAddress14(data.sender) !== normalizeSuiAddress14(expected.delegate)) {
|
|
6433
7945
|
throw new Error("sponsored transaction sender does not match delegate");
|
|
6434
7946
|
}
|
|
6435
7947
|
if (!data.commands || data.commands.length !== 1) {
|
|
6436
7948
|
throw new Error("sponsored transaction must contain exactly one command");
|
|
6437
7949
|
}
|
|
6438
7950
|
const auth = defaultAuthorityForRail("delegate", expected.packageId);
|
|
6439
|
-
const wanted = `${
|
|
7951
|
+
const wanted = `${normalizeSuiAddress14(auth.packageId)}::${auth.module}::${auth.payFunction}`;
|
|
6440
7952
|
const cmd = data.commands[0];
|
|
6441
7953
|
if (cmd.$kind !== "MoveCall" || !cmd.MoveCall) {
|
|
6442
7954
|
throw new Error("sponsored transaction is not a MoveCall");
|
|
6443
7955
|
}
|
|
6444
|
-
const got = `${
|
|
7956
|
+
const got = `${normalizeSuiAddress14(String(cmd.MoveCall.package))}::${cmd.MoveCall.module}::${cmd.MoveCall.function}`;
|
|
6445
7957
|
if (got !== wanted) {
|
|
6446
7958
|
throw new Error(`sponsored transaction does not reconstruct ${wanted}`);
|
|
6447
7959
|
}
|
|
6448
7960
|
let expectedKind;
|
|
6449
7961
|
try {
|
|
6450
|
-
expectedKind =
|
|
7962
|
+
expectedKind = Transaction8.fromKind(fromBase6411(expected.kindBytes)).getData();
|
|
6451
7963
|
} catch {
|
|
6452
7964
|
throw new Error("sponsored transaction kind baseline invalid");
|
|
6453
7965
|
}
|
|
@@ -6479,6 +7991,13 @@ async function prepareSpendAccountPayment(args) {
|
|
|
6479
7991
|
const isOpen = session.snapshot.accessMode === "open";
|
|
6480
7992
|
const callerBuild = deps.buildPayKind;
|
|
6481
7993
|
const buildPayKind = async (input) => {
|
|
7994
|
+
const guardedFields = {
|
|
7995
|
+
exclusionDirectoryId: input.exclusionDirectoryId,
|
|
7996
|
+
recipientRegistryId: input.recipientRegistryId,
|
|
7997
|
+
expiresAtMs: input.expiresAtMs,
|
|
7998
|
+
exclusionDirectoryInitialSharedVersion: input.exclusionDirectoryInitialSharedVersion,
|
|
7999
|
+
recipientRegistryInitialSharedVersion: input.recipientRegistryInitialSharedVersion
|
|
8000
|
+
};
|
|
6482
8001
|
if (callerBuild) {
|
|
6483
8002
|
return callerBuild({
|
|
6484
8003
|
delegate: input.delegate,
|
|
@@ -6491,9 +8010,16 @@ async function prepareSpendAccountPayment(args) {
|
|
|
6491
8010
|
paymentIdHash: input.paymentIdHash,
|
|
6492
8011
|
termsHash: input.termsHash,
|
|
6493
8012
|
recipient: input.recipient,
|
|
6494
|
-
accessMode: input.accessMode
|
|
8013
|
+
accessMode: input.accessMode,
|
|
8014
|
+
...guardedFields
|
|
6495
8015
|
});
|
|
6496
8016
|
}
|
|
8017
|
+
const readSharedObject = deps.readSharedObject ?? sharedObjectReaderForTests ?? chainSharedObjectReader(cfg, input.packageId);
|
|
8018
|
+
const versions = await resolveSharedObjectVersionsFromGetObject(readSharedObject, {
|
|
8019
|
+
poolObjectId: input.poolObjectId,
|
|
8020
|
+
grantObjectId: input.grantObjectId,
|
|
8021
|
+
clockObjectId: SUI_CLOCK3
|
|
8022
|
+
});
|
|
6497
8023
|
if (isOpen) {
|
|
6498
8024
|
return buildSpendAccountSettleOpenRecipientKind({
|
|
6499
8025
|
packageId: input.packageId,
|
|
@@ -6501,13 +8027,15 @@ async function prepareSpendAccountPayment(args) {
|
|
|
6501
8027
|
coinType: input.coinType,
|
|
6502
8028
|
poolObjectId: input.poolObjectId,
|
|
6503
8029
|
grantObjectId: input.grantObjectId,
|
|
6504
|
-
poolInitialSharedVersion:
|
|
6505
|
-
grantInitialSharedVersion:
|
|
8030
|
+
poolInitialSharedVersion: versions.poolInitialSharedVersion,
|
|
8031
|
+
grantInitialSharedVersion: versions.grantInitialSharedVersion,
|
|
8032
|
+
clockInitialSharedVersion: versions.clockInitialSharedVersion,
|
|
6506
8033
|
recipient: input.recipient,
|
|
6507
8034
|
targetHash: input.targetHash,
|
|
6508
8035
|
amount: input.amount,
|
|
6509
8036
|
paymentIdHash: input.paymentIdHash,
|
|
6510
|
-
termsHash: input.termsHash
|
|
8037
|
+
termsHash: input.termsHash,
|
|
8038
|
+
...guardedFields
|
|
6511
8039
|
});
|
|
6512
8040
|
}
|
|
6513
8041
|
return buildSpendAccountPayKind2({
|
|
@@ -6520,7 +8048,11 @@ async function prepareSpendAccountPayment(args) {
|
|
|
6520
8048
|
targetHash: input.targetHash,
|
|
6521
8049
|
amount: input.amount,
|
|
6522
8050
|
paymentIdHash: input.paymentIdHash,
|
|
6523
|
-
termsHash: input.termsHash
|
|
8051
|
+
termsHash: input.termsHash,
|
|
8052
|
+
poolInitialSharedVersion: versions.poolInitialSharedVersion,
|
|
8053
|
+
grantInitialSharedVersion: versions.grantInitialSharedVersion,
|
|
8054
|
+
clockInitialSharedVersion: versions.clockInitialSharedVersion,
|
|
8055
|
+
...guardedFields
|
|
6524
8056
|
});
|
|
6525
8057
|
};
|
|
6526
8058
|
const rawFetch = deps.fetchImpl ?? fetch;
|
|
@@ -6540,10 +8072,20 @@ async function prepareSpendAccountPayment(args) {
|
|
|
6540
8072
|
authority: session.snapshot,
|
|
6541
8073
|
assertLive: session.assertLive
|
|
6542
8074
|
};
|
|
8075
|
+
const directorySwitch = cfg.exclusionDirectoryObjectId ? cfg.exclusionDirectoryObjectId : deps.resolveExclusionRegistry ? null : await publishedExclusionDirectoryId(cfg.gatewayUrl);
|
|
8076
|
+
const resolveExclusionRegistry2 = deps.resolveExclusionRegistry ?? (directorySwitch ? (() => {
|
|
8077
|
+
const reader = createGrpcExclusionRegistryReader(rpcClient(cfg));
|
|
8078
|
+
return async ({ directoryId, recipient }) => resolveExclusionRegistry({
|
|
8079
|
+
directoryObjectId: directoryId,
|
|
8080
|
+
recipient,
|
|
8081
|
+
reader
|
|
8082
|
+
});
|
|
8083
|
+
})() : void 0);
|
|
6543
8084
|
const resolveDeps = {
|
|
6544
8085
|
fetchImpl: wrappedFetch,
|
|
6545
8086
|
preferredDialect: preferred,
|
|
6546
|
-
buildPayKind
|
|
8087
|
+
buildPayKind,
|
|
8088
|
+
...resolveExclusionRegistry2 ? { resolveExclusionRegistry: resolveExclusionRegistry2 } : {}
|
|
6547
8089
|
};
|
|
6548
8090
|
const result = await resolvePayment(resolveInput, resolveDeps);
|
|
6549
8091
|
if (result.status === "unpaid_ok") {
|
|
@@ -6604,7 +8146,7 @@ async function prepareSpendAccountPayment(args) {
|
|
|
6604
8146
|
const signingRequest = {
|
|
6605
8147
|
unsignedKindBytes: kindBytes,
|
|
6606
8148
|
delegateAddress: resolved.delegateAddress,
|
|
6607
|
-
packageId:
|
|
8149
|
+
packageId: normalizeSuiAddress14(authority.packageId),
|
|
6608
8150
|
poolObjectId: resolved.poolObjectId,
|
|
6609
8151
|
grantObjectId: resolved.grantObjectId,
|
|
6610
8152
|
policyId: resolved.onChainPolicyId,
|
|
@@ -6686,11 +8228,11 @@ function mapErrorCode(code) {
|
|
|
6686
8228
|
}
|
|
6687
8229
|
|
|
6688
8230
|
// src/discover-afford.ts
|
|
6689
|
-
import { normalizeStructTag as
|
|
8231
|
+
import { normalizeStructTag as normalizeStructTag9 } from "@mysten/sui/utils";
|
|
6690
8232
|
function sameCoinType2(rowAsset, grantCoin) {
|
|
6691
8233
|
if (typeof rowAsset !== "string" || !grantCoin) return false;
|
|
6692
8234
|
try {
|
|
6693
|
-
return
|
|
8235
|
+
return normalizeStructTag9(rowAsset) === normalizeStructTag9(grantCoin);
|
|
6694
8236
|
} catch {
|
|
6695
8237
|
return false;
|
|
6696
8238
|
}
|
|
@@ -6872,6 +8414,59 @@ function joinDiscoverCatalog(opts) {
|
|
|
6872
8414
|
return scoped ? { accessMode: "scoped", count: services.length, services, allowedTargetHashes } : { accessMode: "open", count: services.length, services };
|
|
6873
8415
|
}
|
|
6874
8416
|
|
|
8417
|
+
// src/tool-result.ts
|
|
8418
|
+
import { pathToFileURL } from "url";
|
|
8419
|
+
|
|
8420
|
+
// src/delivery-file.ts
|
|
8421
|
+
import { mkdirSync as mkdirSync2, writeFileSync, chmodSync as chmodSync2 } from "fs";
|
|
8422
|
+
import { dirname as dirname3, join as join4 } from "path";
|
|
8423
|
+
function mcpDeliveriesDir() {
|
|
8424
|
+
return join4(dirname3(mcpCredentialsPath()), "deliveries");
|
|
8425
|
+
}
|
|
8426
|
+
var EXT = {
|
|
8427
|
+
"image/png": ".png",
|
|
8428
|
+
"image/jpeg": ".jpg",
|
|
8429
|
+
"image/jpg": ".jpg",
|
|
8430
|
+
"image/webp": ".webp",
|
|
8431
|
+
"image/gif": ".gif",
|
|
8432
|
+
"image/svg+xml": ".svg",
|
|
8433
|
+
"audio/mpeg": ".mp3",
|
|
8434
|
+
"audio/wav": ".wav",
|
|
8435
|
+
"audio/ogg": ".ogg",
|
|
8436
|
+
"video/mp4": ".mp4",
|
|
8437
|
+
"application/pdf": ".pdf",
|
|
8438
|
+
"application/json": ".json",
|
|
8439
|
+
"text/plain": ".txt"
|
|
8440
|
+
};
|
|
8441
|
+
function extensionFor(contentType) {
|
|
8442
|
+
const type = contentType.split(";")[0]?.trim().toLowerCase() ?? "";
|
|
8443
|
+
return EXT[type] ?? ".bin";
|
|
8444
|
+
}
|
|
8445
|
+
function fileStem(digest, challengeId) {
|
|
8446
|
+
const raw = (digest ?? challengeId ?? `delivery-${Date.now()}`).trim();
|
|
8447
|
+
const safe = raw.replace(/[^A-Za-z0-9._-]+/g, "-").replace(/^-+|-+$/g, "");
|
|
8448
|
+
return (safe.slice(0, 80) || `delivery-${Date.now()}`).replace(/^\.+$/, "delivery");
|
|
8449
|
+
}
|
|
8450
|
+
function saveDeliveredBody(input) {
|
|
8451
|
+
if (input.bytes.byteLength === 0) return void 0;
|
|
8452
|
+
const dir = mcpDeliveriesDir();
|
|
8453
|
+
mkdirSync2(dir, { recursive: true, mode: 448 });
|
|
8454
|
+
try {
|
|
8455
|
+
chmodSync2(dir, 448);
|
|
8456
|
+
} catch {
|
|
8457
|
+
}
|
|
8458
|
+
const path = join4(
|
|
8459
|
+
dir,
|
|
8460
|
+
`${fileStem(input.digest, input.challengeId)}${extensionFor(input.contentType)}`
|
|
8461
|
+
);
|
|
8462
|
+
writeFileSync(path, input.bytes, { mode: 384 });
|
|
8463
|
+
try {
|
|
8464
|
+
chmodSync2(path, 384);
|
|
8465
|
+
} catch {
|
|
8466
|
+
}
|
|
8467
|
+
return path;
|
|
8468
|
+
}
|
|
8469
|
+
|
|
6875
8470
|
// src/tool-result.ts
|
|
6876
8471
|
var MAX_MCP_IMAGE_BYTES = 5 * 1024 * 1024;
|
|
6877
8472
|
function text(value) {
|
|
@@ -6893,6 +8488,12 @@ function closedError(code, detail) {
|
|
|
6893
8488
|
function closedPayError(code, detail) {
|
|
6894
8489
|
return error({ status: "failed", paid: false, code, detail });
|
|
6895
8490
|
}
|
|
8491
|
+
function receiptChallengeId(receipt) {
|
|
8492
|
+
if (!receipt || typeof receipt !== "object") return void 0;
|
|
8493
|
+
const id = receipt.challengeId;
|
|
8494
|
+
return typeof id === "string" && id.length > 0 ? id : void 0;
|
|
8495
|
+
}
|
|
8496
|
+
var MCP_SAVE_ERROR = "delivery file was not written; do not call pay again";
|
|
6896
8497
|
function paidResult(result) {
|
|
6897
8498
|
if (!isBinaryBody(result.body)) return text(result);
|
|
6898
8499
|
const body = result.body;
|
|
@@ -6901,8 +8502,33 @@ function paidResult(result) {
|
|
|
6901
8502
|
contentType: body.contentType,
|
|
6902
8503
|
byteLength: body.byteLength
|
|
6903
8504
|
};
|
|
8505
|
+
let savedTo;
|
|
8506
|
+
try {
|
|
8507
|
+
savedTo = saveDeliveredBody({
|
|
8508
|
+
bytes: body.bytes,
|
|
8509
|
+
contentType: body.contentType,
|
|
8510
|
+
digest: result.txDigest,
|
|
8511
|
+
challengeId: receiptChallengeId(result.receipt)
|
|
8512
|
+
});
|
|
8513
|
+
} catch {
|
|
8514
|
+
savedTo = void 0;
|
|
8515
|
+
}
|
|
8516
|
+
const payload = savedTo ? {
|
|
8517
|
+
savedTo,
|
|
8518
|
+
savedToUrl: pathToFileURL(savedTo).href,
|
|
8519
|
+
...result,
|
|
8520
|
+
body: shape
|
|
8521
|
+
} : {
|
|
8522
|
+
savedTo: null,
|
|
8523
|
+
saveError: MCP_SAVE_ERROR,
|
|
8524
|
+
...result,
|
|
8525
|
+
body: shape
|
|
8526
|
+
};
|
|
6904
8527
|
const content = [
|
|
6905
|
-
{
|
|
8528
|
+
{
|
|
8529
|
+
type: "text",
|
|
8530
|
+
text: JSON.stringify(payload, null, 2)
|
|
8531
|
+
}
|
|
6906
8532
|
];
|
|
6907
8533
|
if (body.contentType.startsWith("image/") && body.bytes.byteLength > 0 && body.bytes.byteLength <= MAX_MCP_IMAGE_BYTES) {
|
|
6908
8534
|
content.push({
|
|
@@ -6918,6 +8544,205 @@ function settlePayResult(result) {
|
|
|
6918
8544
|
return error(result);
|
|
6919
8545
|
}
|
|
6920
8546
|
|
|
8547
|
+
// src/pay-file.ts
|
|
8548
|
+
import { isAbsolute, dirname as dirname4, sep } from "path";
|
|
8549
|
+
import {
|
|
8550
|
+
openSync as openSync2,
|
|
8551
|
+
closeSync as closeSync2,
|
|
8552
|
+
fstatSync,
|
|
8553
|
+
readFileSync as readFileSync3,
|
|
8554
|
+
realpathSync,
|
|
8555
|
+
constants
|
|
8556
|
+
} from "fs";
|
|
8557
|
+
var JSON_WRAP_SLACK_BYTES = 256;
|
|
8558
|
+
var MCP_MAX_FILE_FIELD_BYTES = Math.floor(
|
|
8559
|
+
(MAX_PAID_BODY_BYTES - JSON_WRAP_SLACK_BYTES) * 3 / 4
|
|
8560
|
+
);
|
|
8561
|
+
var FIELD = /^[A-Za-z][A-Za-z0-9_]{0,63}$/;
|
|
8562
|
+
var FORBIDDEN = /* @__PURE__ */ new Set(["constructor", "prototype"]);
|
|
8563
|
+
function fail2(code, detail) {
|
|
8564
|
+
return { code, detail };
|
|
8565
|
+
}
|
|
8566
|
+
function hasLocalFileArgs(input) {
|
|
8567
|
+
return input.file !== void 0 && input.file !== null || input.fileField !== void 0 && input.fileField !== null;
|
|
8568
|
+
}
|
|
8569
|
+
function parseField(value) {
|
|
8570
|
+
if (typeof value !== "string" || !FIELD.test(value) || FORBIDDEN.has(value)) {
|
|
8571
|
+
return fail2("INVALID_FILE_FIELD", "fileField must be a simple JSON key");
|
|
8572
|
+
}
|
|
8573
|
+
return value;
|
|
8574
|
+
}
|
|
8575
|
+
function parsePath(value) {
|
|
8576
|
+
if (typeof value !== "string" || value.length === 0 || value.includes("\0")) {
|
|
8577
|
+
return fail2("INVALID_FILE", "file must be a non-empty absolute path");
|
|
8578
|
+
}
|
|
8579
|
+
if (!isAbsolute(value) || value.includes("://")) {
|
|
8580
|
+
return fail2("INVALID_FILE", "file must be an absolute filesystem path, not a URL");
|
|
8581
|
+
}
|
|
8582
|
+
return value;
|
|
8583
|
+
}
|
|
8584
|
+
function parseMethod(value) {
|
|
8585
|
+
if (value === void 0 || value === null) return "GET";
|
|
8586
|
+
if (typeof value !== "string") {
|
|
8587
|
+
return fail2("MALFORMED_REQUEST", "method must be GET or POST");
|
|
8588
|
+
}
|
|
8589
|
+
const upper = value.trim().toUpperCase();
|
|
8590
|
+
if (upper === "GET" || upper === "POST") return upper;
|
|
8591
|
+
return fail2("MALFORMED_REQUEST", "method must be GET or POST");
|
|
8592
|
+
}
|
|
8593
|
+
function inside(root, candidate) {
|
|
8594
|
+
const prefix = root.endsWith(sep) ? root : root + sep;
|
|
8595
|
+
return candidate === root || candidate.startsWith(prefix);
|
|
8596
|
+
}
|
|
8597
|
+
function resolveContainedPath(raw) {
|
|
8598
|
+
const rootRaw = process.env.SUIPAY_MCP_FILE_ROOT;
|
|
8599
|
+
if (typeof rootRaw !== "string" || rootRaw.trim() === "") {
|
|
8600
|
+
return fail2(
|
|
8601
|
+
"FILE_ROOT_UNSET",
|
|
8602
|
+
"file attach requires SUIPAY_MCP_FILE_ROOT to be an absolute directory"
|
|
8603
|
+
);
|
|
8604
|
+
}
|
|
8605
|
+
if (!isAbsolute(rootRaw) || rootRaw.includes("://")) {
|
|
8606
|
+
return fail2(
|
|
8607
|
+
"FILE_ROOT_UNSET",
|
|
8608
|
+
"SUIPAY_MCP_FILE_ROOT must be an absolute filesystem path"
|
|
8609
|
+
);
|
|
8610
|
+
}
|
|
8611
|
+
let rootReal;
|
|
8612
|
+
let fileReal;
|
|
8613
|
+
try {
|
|
8614
|
+
rootReal = realpathSync(rootRaw);
|
|
8615
|
+
} catch {
|
|
8616
|
+
return fail2("FILE_ROOT_UNSET", "SUIPAY_MCP_FILE_ROOT is not a readable directory");
|
|
8617
|
+
}
|
|
8618
|
+
try {
|
|
8619
|
+
fileReal = realpathSync(raw);
|
|
8620
|
+
} catch (err) {
|
|
8621
|
+
const code = err.code;
|
|
8622
|
+
if (code === "ENOENT" || code === "ENOTDIR") {
|
|
8623
|
+
return fail2("FILE_NOT_FOUND", "file does not exist");
|
|
8624
|
+
}
|
|
8625
|
+
if (code === "EACCES") {
|
|
8626
|
+
return fail2("FILE_EACCES", "file is not readable");
|
|
8627
|
+
}
|
|
8628
|
+
return fail2(
|
|
8629
|
+
"INVALID_FILE",
|
|
8630
|
+
`file could not be read (${code ?? "unknown error"})`
|
|
8631
|
+
);
|
|
8632
|
+
}
|
|
8633
|
+
if (!inside(rootReal, fileReal)) {
|
|
8634
|
+
return fail2("FILE_OUTSIDE_ROOT", "file must be under SUIPAY_MCP_FILE_ROOT");
|
|
8635
|
+
}
|
|
8636
|
+
const credsDir2 = dirname4(mcpCredentialsPath());
|
|
8637
|
+
let credsReal = credsDir2;
|
|
8638
|
+
try {
|
|
8639
|
+
credsReal = realpathSync(credsDir2);
|
|
8640
|
+
} catch {
|
|
8641
|
+
}
|
|
8642
|
+
if (inside(credsReal, fileReal)) {
|
|
8643
|
+
return fail2("FILE_FORBIDDEN", "file must not be under ~/.suipay");
|
|
8644
|
+
}
|
|
8645
|
+
return fileReal;
|
|
8646
|
+
}
|
|
8647
|
+
function readRegularFile(path) {
|
|
8648
|
+
if (constants.O_NOFOLLOW === void 0 || constants.O_NONBLOCK === void 0) {
|
|
8649
|
+
return fail2("INVALID_FILE", "symlink-safe nonblocking open is unavailable on this platform");
|
|
8650
|
+
}
|
|
8651
|
+
let fd;
|
|
8652
|
+
try {
|
|
8653
|
+
const flags = constants.O_RDONLY | constants.O_NOFOLLOW | constants.O_NONBLOCK;
|
|
8654
|
+
fd = openSync2(path, flags);
|
|
8655
|
+
const st = fstatSync(fd);
|
|
8656
|
+
if (!st.isFile()) {
|
|
8657
|
+
return fail2("INVALID_FILE", "file must be a regular file");
|
|
8658
|
+
}
|
|
8659
|
+
if (st.size > MCP_MAX_FILE_FIELD_BYTES) {
|
|
8660
|
+
return fail2(
|
|
8661
|
+
"FILE_TOO_LARGE",
|
|
8662
|
+
`file is ${st.size} bytes; exceeds ${MCP_MAX_FILE_FIELD_BYTES} decoded bytes`
|
|
8663
|
+
);
|
|
8664
|
+
}
|
|
8665
|
+
const bytes = readFileSync3(fd);
|
|
8666
|
+
if (bytes.byteLength === 0) {
|
|
8667
|
+
return fail2("EMPTY_FILE", "file is empty");
|
|
8668
|
+
}
|
|
8669
|
+
if (bytes.byteLength > MCP_MAX_FILE_FIELD_BYTES) {
|
|
8670
|
+
return fail2(
|
|
8671
|
+
"FILE_TOO_LARGE",
|
|
8672
|
+
`file is ${bytes.byteLength} bytes; exceeds ${MCP_MAX_FILE_FIELD_BYTES} decoded bytes`
|
|
8673
|
+
);
|
|
8674
|
+
}
|
|
8675
|
+
return bytes;
|
|
8676
|
+
} catch (err) {
|
|
8677
|
+
const code = err.code;
|
|
8678
|
+
if (code === "ENOENT" || code === "ENOTDIR") {
|
|
8679
|
+
return fail2("FILE_NOT_FOUND", "file does not exist");
|
|
8680
|
+
}
|
|
8681
|
+
if (code === "ELOOP" || code === "EMLINK" || code === "ENXIO") {
|
|
8682
|
+
return fail2("INVALID_FILE", "file must be a regular file");
|
|
8683
|
+
}
|
|
8684
|
+
if (code === "EACCES") return fail2("FILE_EACCES", "file is not readable");
|
|
8685
|
+
return fail2(
|
|
8686
|
+
"INVALID_FILE",
|
|
8687
|
+
`file could not be read (${code ?? "unknown error"})`
|
|
8688
|
+
);
|
|
8689
|
+
} finally {
|
|
8690
|
+
if (fd !== void 0) {
|
|
8691
|
+
try {
|
|
8692
|
+
closeSync2(fd);
|
|
8693
|
+
} catch {
|
|
8694
|
+
}
|
|
8695
|
+
}
|
|
8696
|
+
}
|
|
8697
|
+
}
|
|
8698
|
+
function toCanonicalBase64(bytes) {
|
|
8699
|
+
return Buffer.from(bytes).toString("base64");
|
|
8700
|
+
}
|
|
8701
|
+
function jsonWithLocalFile(input) {
|
|
8702
|
+
const hasFile = input.file !== void 0 && input.file !== null;
|
|
8703
|
+
const hasField = input.fileField !== void 0 && input.fileField !== null;
|
|
8704
|
+
if (!hasFile && !hasField) return { json: input.json };
|
|
8705
|
+
if (hasFile !== hasField) {
|
|
8706
|
+
return {
|
|
8707
|
+
error: fail2(
|
|
8708
|
+
"INVALID_FILE_ARGS",
|
|
8709
|
+
"file and fileField are required together"
|
|
8710
|
+
)
|
|
8711
|
+
};
|
|
8712
|
+
}
|
|
8713
|
+
const method = parseMethod(input.method);
|
|
8714
|
+
if (typeof method !== "string") return { error: method };
|
|
8715
|
+
if (method !== "POST") {
|
|
8716
|
+
return { error: fail2("INVALID_FILE_ARGS", "file is only valid on POST") };
|
|
8717
|
+
}
|
|
8718
|
+
const field = parseField(input.fileField);
|
|
8719
|
+
if (typeof field !== "string") return { error: field };
|
|
8720
|
+
const path = parsePath(input.file);
|
|
8721
|
+
if (typeof path !== "string") return { error: path };
|
|
8722
|
+
let json;
|
|
8723
|
+
if (input.json === void 0) {
|
|
8724
|
+
json = {};
|
|
8725
|
+
} else if (typeof input.json === "object" && input.json !== null && !Array.isArray(input.json)) {
|
|
8726
|
+
json = { ...input.json };
|
|
8727
|
+
} else {
|
|
8728
|
+
return { error: fail2("MALFORMED_REQUEST", "POST requires json object") };
|
|
8729
|
+
}
|
|
8730
|
+
if (Object.prototype.hasOwnProperty.call(json, field)) {
|
|
8731
|
+
return {
|
|
8732
|
+
error: fail2(
|
|
8733
|
+
"FILE_FIELD_EXISTS",
|
|
8734
|
+
"json already has fileField; refuse to overwrite"
|
|
8735
|
+
)
|
|
8736
|
+
};
|
|
8737
|
+
}
|
|
8738
|
+
const contained = resolveContainedPath(path);
|
|
8739
|
+
if (typeof contained !== "string") return { error: contained };
|
|
8740
|
+
const bytes = readRegularFile(contained);
|
|
8741
|
+
if (!(bytes instanceof Uint8Array)) return { error: bytes };
|
|
8742
|
+
json[field] = toCanonicalBase64(bytes);
|
|
8743
|
+
return { json };
|
|
8744
|
+
}
|
|
8745
|
+
|
|
6921
8746
|
// src/tools.ts
|
|
6922
8747
|
function payerTrace(deps) {
|
|
6923
8748
|
const hooks = deps.trace;
|
|
@@ -6954,7 +8779,7 @@ function normalizePayIntent(args) {
|
|
|
6954
8779
|
if (!/^0x[0-9a-fA-F]{1,64}$/.test(raw)) {
|
|
6955
8780
|
throw new Error("recipient must be a 0x-prefixed Sui address");
|
|
6956
8781
|
}
|
|
6957
|
-
const normalized =
|
|
8782
|
+
const normalized = normalizeSuiAddress15(raw);
|
|
6958
8783
|
if (!isValidSuiAddress(normalized) || /^0x0+$/.test(normalized)) {
|
|
6959
8784
|
throw new Error("recipient must be a non-zero Sui address");
|
|
6960
8785
|
}
|
|
@@ -6973,7 +8798,7 @@ function intentViolation(intent, prepared) {
|
|
|
6973
8798
|
if (intent.recipient) {
|
|
6974
8799
|
let preparedRecipient;
|
|
6975
8800
|
try {
|
|
6976
|
-
preparedRecipient =
|
|
8801
|
+
preparedRecipient = normalizeSuiAddress15(prepared.recipient);
|
|
6977
8802
|
} catch {
|
|
6978
8803
|
return "prepared payment does not name a valid recipient";
|
|
6979
8804
|
}
|
|
@@ -6996,18 +8821,34 @@ function intentViolation(intent, prepared) {
|
|
|
6996
8821
|
}
|
|
6997
8822
|
async function pay(args, cfg, deps = {}) {
|
|
6998
8823
|
if (!args.url) return error("url is required");
|
|
8824
|
+
if (hasLocalFileArgs(args) && deps.transport === "http") {
|
|
8825
|
+
return closedPayError(
|
|
8826
|
+
"FILE_STDIO_ONLY",
|
|
8827
|
+
"file is stdio only; hosted /mcp does not read the buyer disk"
|
|
8828
|
+
);
|
|
8829
|
+
}
|
|
6999
8830
|
let intent;
|
|
7000
8831
|
try {
|
|
7001
8832
|
intent = normalizePayIntent(args);
|
|
7002
8833
|
} catch {
|
|
7003
8834
|
return closedPayError("INVALID_INTENT", "payment intent is invalid");
|
|
7004
8835
|
}
|
|
8836
|
+
let attached;
|
|
8837
|
+
try {
|
|
8838
|
+
attached = jsonWithLocalFile(args);
|
|
8839
|
+
} catch {
|
|
8840
|
+
return closedPayError("INVALID_FILE", "file could not be read");
|
|
8841
|
+
}
|
|
8842
|
+
if ("error" in attached) {
|
|
8843
|
+
return closedPayError(attached.error.code, attached.error.detail);
|
|
8844
|
+
}
|
|
8845
|
+
const paidArgs = { ...args, json: attached.json };
|
|
7005
8846
|
let request;
|
|
7006
8847
|
try {
|
|
7007
|
-
request = normalizeMcpPaidHttpRequest(
|
|
8848
|
+
request = normalizeMcpPaidHttpRequest(paidArgs);
|
|
7008
8849
|
} catch (err) {
|
|
7009
8850
|
const raw = err instanceof Error ? err.message : "";
|
|
7010
|
-
const detail = /method/i.test(raw) ? "method must be GET or POST" : /json/i.test(raw) ? "POST requires json" : "request could not be normalized";
|
|
8851
|
+
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";
|
|
7011
8852
|
return closedPayError("MALFORMED_REQUEST", detail);
|
|
7012
8853
|
}
|
|
7013
8854
|
if (deps.auth?.spendAccountPay) {
|
|
@@ -7032,6 +8873,7 @@ async function pay(args, cfg, deps = {}) {
|
|
|
7032
8873
|
deps: {
|
|
7033
8874
|
...deps.fetchImpl ? { fetchImpl: deps.fetchImpl } : {},
|
|
7034
8875
|
...trace ? { trace } : {},
|
|
8876
|
+
...deps.resolveExclusionRegistry ? { resolveExclusionRegistry: deps.resolveExclusionRegistry } : {},
|
|
7035
8877
|
...deps.buildPayKind ? { buildPayKind: deps.buildPayKind } : liveKindBuilder ? {
|
|
7036
8878
|
buildPayKind: (input) => sp.snapshot.accessMode === "open" && input.recipient ? liveKindBuilder.buildSettleOpenRecipientKind({
|
|
7037
8879
|
...input,
|
|
@@ -7451,10 +9293,15 @@ async function suipayLogin(args, cfg, deps = {}) {
|
|
|
7451
9293
|
});
|
|
7452
9294
|
}
|
|
7453
9295
|
if (live.kind === "unknown") {
|
|
7454
|
-
return
|
|
7455
|
-
status: "
|
|
9296
|
+
return text({
|
|
9297
|
+
status: "ok",
|
|
9298
|
+
reused: true,
|
|
9299
|
+
grantStatus: "unknown",
|
|
7456
9300
|
code: "GRANT_STATUS_UNKNOWN",
|
|
7457
|
-
|
|
9301
|
+
target: target ?? cfg.target,
|
|
9302
|
+
delegateAddress: identity.delegateAddress,
|
|
9303
|
+
gatewayUrl: endpoints.gatewayUrl,
|
|
9304
|
+
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."
|
|
7458
9305
|
});
|
|
7459
9306
|
}
|
|
7460
9307
|
}
|
|
@@ -7464,13 +9311,13 @@ async function suipayLogin(args, cfg, deps = {}) {
|
|
|
7464
9311
|
identity,
|
|
7465
9312
|
label,
|
|
7466
9313
|
timeoutMs: LOGIN_BG_TIMEOUT_MS,
|
|
7467
|
-
openBrowser:
|
|
9314
|
+
openBrowser: true,
|
|
7468
9315
|
onUrl: deps.onUrl
|
|
7469
9316
|
});
|
|
7470
9317
|
const url = started.connectUrl;
|
|
7471
9318
|
return text(
|
|
7472
9319
|
[
|
|
7473
|
-
`## ACTION REQUIRED:
|
|
9320
|
+
`## ACTION REQUIRED: The sign-in page was opened in the browser`,
|
|
7474
9321
|
``,
|
|
7475
9322
|
`**Target:** ${target ?? cfg.target ?? "custom"} \xB7 ${endpoints.gatewayUrl}`,
|
|
7476
9323
|
``,
|
|
@@ -7482,11 +9329,13 @@ async function suipayLogin(args, cfg, deps = {}) {
|
|
|
7482
9329
|
``,
|
|
7483
9330
|
`[Click here to open SuiPay sign-in](${url})`,
|
|
7484
9331
|
``,
|
|
7485
|
-
`**IMPORTANT for the assistant**:
|
|
7486
|
-
`
|
|
9332
|
+
`**IMPORTANT for the assistant**: the browser was already opened to this exact URL.`,
|
|
9333
|
+
`Do NOT navigate to /connect/agent yourself. That path without these query`,
|
|
9334
|
+
`parameters shows "Authorization link incomplete". If the page did not`,
|
|
9335
|
+
`appear, surface the URL above verbatim and do not shorten it.`,
|
|
7487
9336
|
``,
|
|
7488
|
-
`1.
|
|
7489
|
-
`2. Connect the owner wallet
|
|
9337
|
+
`1. Use the browser window that just opened, or open the URL above`,
|
|
9338
|
+
`2. Connect the owner wallet, choose or create a key, then Authorize on the review step`,
|
|
7490
9339
|
`3. Once Connected appears, retry the original request \u2014 credentials at \`~/.suipay/credentials.json\` are picked up on the next tool call`,
|
|
7491
9340
|
``,
|
|
7492
9341
|
`_The login link stays valid for 5 minutes. If it expires, call \`suipay_login\` again._`,
|
|
@@ -7526,7 +9375,7 @@ var TOOLS = [
|
|
|
7526
9375
|
},
|
|
7527
9376
|
{
|
|
7528
9377
|
name: "pay",
|
|
7529
|
-
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.",
|
|
9378
|
+
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.",
|
|
7530
9379
|
inputSchema: {
|
|
7531
9380
|
type: "object",
|
|
7532
9381
|
properties: {
|
|
@@ -7540,6 +9389,14 @@ var TOOLS = [
|
|
|
7540
9389
|
type: "object",
|
|
7541
9390
|
description: "JSON request body for a POST. Sent as application/json and bound to the payment."
|
|
7542
9391
|
},
|
|
9392
|
+
file: {
|
|
9393
|
+
type: "string",
|
|
9394
|
+
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."
|
|
9395
|
+
},
|
|
9396
|
+
fileField: {
|
|
9397
|
+
type: "string",
|
|
9398
|
+
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."
|
|
9399
|
+
},
|
|
7543
9400
|
recipient: {
|
|
7544
9401
|
type: "string",
|
|
7545
9402
|
description: "Sui address you expect to be paid. The payment is refused, unsigned, if the prepared transaction pays anyone else."
|
|
@@ -7647,6 +9504,7 @@ function toolError(value) {
|
|
|
7647
9504
|
}
|
|
7648
9505
|
function createServer2(cfg, auth, trace, options = {}) {
|
|
7649
9506
|
const resolveAgentHeld = options.resolveAgentHeld ?? resolveAgentHeldSettlement;
|
|
9507
|
+
const transport = options.transport ?? "stdio";
|
|
7650
9508
|
const server = new Server(
|
|
7651
9509
|
{ name: "suipay", version: "1.0.0" },
|
|
7652
9510
|
{
|
|
@@ -7673,19 +9531,32 @@ function createServer2(cfg, auth, trace, options = {}) {
|
|
|
7673
9531
|
return await accessContext({ auth }, cfg);
|
|
7674
9532
|
case "pay": {
|
|
7675
9533
|
const agentHeld = auth?.spendAccountPay ? resolveAgentHeld(cfg, auth.spendAccountPay) : null;
|
|
9534
|
+
const fileArgs = {
|
|
9535
|
+
...args.file !== void 0 ? { file: args.file } : {},
|
|
9536
|
+
...args.fileField !== void 0 ? { fileField: args.fileField } : {}
|
|
9537
|
+
};
|
|
9538
|
+
if (transport === "http" && hasLocalFileArgs(fileArgs)) {
|
|
9539
|
+
return closedPayError(
|
|
9540
|
+
"FILE_STDIO_ONLY",
|
|
9541
|
+
"file is stdio only; hosted /mcp does not read the buyer disk"
|
|
9542
|
+
);
|
|
9543
|
+
}
|
|
7676
9544
|
return await pay(
|
|
7677
9545
|
{
|
|
7678
9546
|
url: String(args.url ?? ""),
|
|
7679
9547
|
...args.method !== void 0 ? { method: args.method } : {},
|
|
7680
9548
|
...args.json !== void 0 ? { json: args.json } : {},
|
|
9549
|
+
...transport === "stdio" ? fileArgs : {},
|
|
7681
9550
|
...args.recipient !== void 0 ? { recipient: args.recipient } : {},
|
|
7682
9551
|
...args.maxAmount !== void 0 ? { maxAmount: args.maxAmount } : {}
|
|
7683
9552
|
},
|
|
7684
9553
|
cfg,
|
|
7685
9554
|
{
|
|
7686
9555
|
auth,
|
|
9556
|
+
transport,
|
|
7687
9557
|
...trace ? { trace } : {},
|
|
7688
|
-
...agentHeld ? { agentHeld } : {}
|
|
9558
|
+
...agentHeld ? { agentHeld } : {},
|
|
9559
|
+
...options.resolveExclusionRegistry ? { resolveExclusionRegistry: options.resolveExclusionRegistry } : {}
|
|
7689
9560
|
}
|
|
7690
9561
|
);
|
|
7691
9562
|
}
|
|
@@ -7772,6 +9643,11 @@ function canonicalMcpRequestId(id) {
|
|
|
7772
9643
|
function asRecord(value) {
|
|
7773
9644
|
return typeof value === "object" && value !== null && !Array.isArray(value) ? value : null;
|
|
7774
9645
|
}
|
|
9646
|
+
function redactPayFileArg(args) {
|
|
9647
|
+
const record = asRecord(args);
|
|
9648
|
+
if (!record || !("file" in record)) return args;
|
|
9649
|
+
return { ...record, file: "[redacted]" };
|
|
9650
|
+
}
|
|
7775
9651
|
function readEntry(message) {
|
|
7776
9652
|
const record = asRecord(message);
|
|
7777
9653
|
if (!record) return null;
|
|
@@ -7783,7 +9659,7 @@ function readEntry(message) {
|
|
|
7783
9659
|
jsonRpcId: typeof rawId === "string" || typeof rawId === "number" ? rawId : null,
|
|
7784
9660
|
requestId: canonicalMcpRequestId(rawId),
|
|
7785
9661
|
toolName: params && typeof params.name === "string" ? params.name : null,
|
|
7786
|
-
toolArguments: params ? params.arguments : void 0,
|
|
9662
|
+
toolArguments: params ? redactPayFileArg(params.arguments) : void 0,
|
|
7787
9663
|
clientName: clientInfo && typeof clientInfo.name === "string" ? clientInfo.name : null,
|
|
7788
9664
|
clientVersion: clientInfo && typeof clientInfo.version === "string" ? clientInfo.version : null,
|
|
7789
9665
|
protocolVersion: params && typeof params.protocolVersion === "string" ? params.protocolVersion : null
|
|
@@ -8213,7 +10089,10 @@ async function handleSuipayMcpHttpRequest(req, cfg, auth, trace, options) {
|
|
|
8213
10089
|
sessionIdGenerator: void 0,
|
|
8214
10090
|
enableJsonResponse: true
|
|
8215
10091
|
});
|
|
8216
|
-
const server = createServer2(cfg, auth, hooks,
|
|
10092
|
+
const server = createServer2(cfg, auth, hooks, {
|
|
10093
|
+
...options,
|
|
10094
|
+
transport: "http"
|
|
10095
|
+
});
|
|
8217
10096
|
await server.connect(transport);
|
|
8218
10097
|
return transport.handleRequest(request);
|
|
8219
10098
|
}
|