@cmdoss/suipay-mcp 0.2.2-dev.1 → 0.2.2-dev.10

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.
@@ -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("withdraw"),
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
- if (!input.onChainPolicyIds.length) {
741
- throw new Error("create_grant requires at least one on-chain policy id");
742
- }
743
- if (!input.maxPerPayment || BigInt(input.maxPerPayment) <= 0n) {
744
- throw new Error("create_grant requires a non-zero max_per_payment");
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
- if (!input.sessionCap || BigInt(input.sessionCap) <= 0n) {
747
- throw new Error("create_grant requires a non-zero session_cap");
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(input.owner);
751
- const expires = input.expiresAtMs === void 0 || input.expiresAtMs === null ? 0n : BigInt(input.expiresAtMs);
752
- tx.moveCall({
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::settle_open_recipient_payment");
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::settle_open_recipient_payment");
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("settle_open_recipient_payment recipient must be a non-zero address");
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("settle_open_recipient_payment amount must be > 0");
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("settle_open_recipient_payment"),
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(BigInt(input.newSessionCap)),
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::settle_policy_payment");
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::settle_policy_payment");
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("settle_policy_payment"),
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
- function createSpendAccountFinalityReader(options) {
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 getFinalizedTx(digest) {
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 bcs4 } from "@mysten/sui/bcs";
1243
+ import { bcs as bcs5 } from "@mysten/sui/bcs";
1054
1244
  import { Transaction as Transaction4 } from "@mysten/sui/transactions";
1055
- import { fromBase64 as fromBase643, normalizeStructTag as normalizeStructTag5, normalizeSuiAddress as normalizeSuiAddress6, toBase64 as toBase643 } from "@mysten/sui/utils";
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 normalizeStructTag4, normalizeSuiAddress as normalizeSuiAddress3 } from "@mysten/sui/utils";
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
- "subscription"
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 === "transaction" || value === "finalized-proof";
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 ?? "transaction";
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 "settle_open_recipient_payment_to_refund_vault";
1524
+ return "settle_any_recipient_to_refund_vault";
1306
1525
  if (open2)
1307
- return "settle_open_recipient_payment";
1526
+ return "settle_any_recipient";
1308
1527
  if (refundable)
1309
- return "settle_policy_payment_to_refund_vault";
1310
- return "settle_policy_payment";
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: "finalized-proof", x402: "transaction" } : { mpp: "finalized-proof" }
1540
+ dialectModes: dual ? { mpp: "self-settled", x402: "gateway-settled" } : { mpp: "self-settled" }
1322
1541
  });
1323
1542
  }
1324
- case "subscription":
1543
+ case "recurring":
1325
1544
  return createSettlementRailAuthority({
1326
- rail: "subscription",
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: "transaction",
1334
- x402: "transaction"
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
- payFunction: "pay",
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: "transaction",
1345
- x402: "transaction"
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 (V3_OMIT_WHEN_ABSENT.has(f) && (offer[f] === void 0 || offer[f] === null))
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 verifyOfferDigest(offer, secret) {
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 decodeChallenge(header) {
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
- if (!isSettlementRail(p.settlement_rail))
1936
+ const rail = decodeWireRail(p.settlement_rail);
1937
+ if (!rail)
1557
1938
  return null;
1558
- offer.settlementRail = p.settlement_rail;
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
- return offer.settlementRail ?? "direct";
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
- if (!isSettlementRail(s.settlementRail))
2245
+ const rail = decodeWireRail(s.settlementRail);
2246
+ if (!rail)
1734
2247
  return null;
1735
- offer.settlementRail = s.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(Buffer.from(header, "base64").toString("utf8"));
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(Buffer.from(headerVal, "base64").toString("utf8"));
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 = Buffer.from(bytes).toString("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 !== 3) {
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 v3 that signs the request body"
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 originViolation = validateOrigin(url, gatewayUrl);
2170
- if (originViolation) {
2171
- return failed(PAY_OPERATION_ERROR_CODES.ORIGIN_MISMATCH, originViolation);
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
- try {
2175
- response = await fetchImpl(url, paidRequestInit(request));
2176
- } catch (e) {
2177
- return failed(PAY_OPERATION_ERROR_CODES.RESOURCE_UNAVAILABLE, `fetch failed: ${e instanceof Error ? e.message : String(e)}`);
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 spend-account rail`, challengeId);
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 { ok: false, code: PAY_OPERATION_ERROR_CODES.POLICY_REJECTED, reason: "offer has no targetHash for scoped resolution" };
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 = normalizeStructTag4(offer.asset);
2341
- const grantCoinType = normalizeStructTag4(resolved.coinType);
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 bcs2 } from "@mysten/sui/bcs";
3049
+ import { bcs as bcs3 } from "@mysten/sui/bcs";
2387
3050
  import { isValidPersonalMessageSignature, verifyPersonalMessageSignature } from "@mysten/sui/verify";
2388
- import { fromBase64 as fromBase642, normalizeSuiAddress as normalizeSuiAddress4, toBase64 as toBase642 } from "@mysten/sui/utils";
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(bcs2.vector(bcs2.u8()).serialize([...Buffer.from(payload.domain, "utf8")]).toBytes());
2410
- push(bcs2.vector(bcs2.u8()).serialize([...Buffer.from(payload.nonce, "utf8")]).toBytes());
2411
- push(bcs2.u64().serialize(BigInt(payload.expiryMs)).toBytes());
2412
- push(bcs2.Address.serialize(normalizeSuiAddress4(payload.delegate)).toBytes());
2413
- push(bcs2.Address.serialize(normalizeSuiAddress4(payload.poolId)).toBytes());
2414
- push(bcs2.Address.serialize(normalizeSuiAddress4(payload.grantId)).toBytes());
2415
- push(bcs2.vector(bcs2.u8()).serialize([...Buffer.from(payload.network, "utf8")]).toBytes());
2416
- push(bcs2.Address.serialize(normalizeSuiAddress4(payload.packageId)).toBytes());
2417
- push(bcs2.vector(bcs2.u8()).serialize([...Buffer.from(payload.requestId, "utf8")]).toBytes());
2418
- push(bcs2.vector(bcs2.u8()).serialize([...payload.txKindHash]).toBytes());
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" ? fromBase642(kindBytes) : kindBytes;
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 bcs3 } from "@mysten/sui/bcs";
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) => bcs3.vector(bcs3.u8()).serialize([...Buffer.from(s, "utf8")]).toBytes();
2482
- const addr = (a) => bcs3.Address.serialize(normalizeSuiAddress5(a)).toBytes();
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
- bcs3.vector(bcs3.u8()).serialize([...hexToBytes32(payload.bodyHash)]).toBytes(),
2492
- bcs3.u64().serialize(BigInt(payload.timestampMs)).toBytes(),
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) => bcs3.vector(bcs3.u8()).serialize([...Buffer.from(s, "utf8")]).toBytes();
2502
- const addr = (a) => bcs3.Address.serialize(normalizeSuiAddress5(a)).toBytes();
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
- bcs3.vector(bcs3.u8()).serialize([...hexToBytes32(payload.bodyHash)]).toBytes(),
2513
- bcs3.u64().serialize(BigInt(payload.timestampMs)).toBytes(),
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, and SAIP-121 renamed three of them:
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 sameAddress(a, b) {
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 normalizeStructTag5(a) === normalizeStructTag5(b);
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(fromBase643(txBytes));
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: toBase643(bytes) };
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("settle_open_recipient_payment recipient must be a non-zero address");
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: toBase643(bytes) };
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(fromBase643(kindBytes));
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 = toBase643(await tx.build());
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(fromBase643(kindBytes));
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 = toBase643(await tx.build());
4113
+ const txBytes = toBase645(await tx.build());
3406
4114
  return { txBytes, kindBytes };
3407
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());
4144
+ return { txBytes, kindBytes };
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(fromBase643(txBytes)).getData();
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 || !sameAddress(data.sender, expected.delegate)) {
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(fromBase643(expected.kindBytes)).getData();
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 !== 8) {
3455
- throw new Error(`pay expects 8 arguments; got ${args.length}`);
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 && !sameAddress(offer.payTo, intent.recipient)) {
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 (!sameAddress(offer.payTo, terms.recipient))
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
- normalizeStructTag5(offer2.asset);
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
- normalizeStructTag5(snapshot.coinType);
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, { paymentId });
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, { paymentId });
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", { state: reserveOutcome.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
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
3944
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.getFinalizedTx;
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, "getFinalizedTx threw after broadcast", err);
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, { paymentId, txDigest: finalDigest, settlement }), logError);
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, { paymentId, txDigest: finalDigest, settlement }), logError);
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, { paymentId, txDigest: finalDigest, settlement }), logError);
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, { paymentId, txDigest: finalDigest, settlement }), logError);
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 fromBase643(input.Pure.bytes);
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(bcs4.Address.parse(raw));
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(bcs4.u64().parse(raw));
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(fromBase643(input.kindBytes)).getData();
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 grantObjectId = preparedSharedObjectId(resolve(1));
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(2));
4374
- if (!recipient || !sameAddress(recipient, input.expected.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(3));
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
- const amount = preparedPureU64(resolve(4));
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 || !sameAddress(grant.delegateAddress, input.expected.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();
@@ -4472,7 +5562,7 @@ 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 fromBase644 } from "@mysten/sui/utils";
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
@@ -4490,7 +5580,7 @@ var MCP_TARGET_PRESETS = {
4490
5580
  gatewayUrl: "https://gateway-dev-ba84.up.railway.app"
4491
5581
  },
4492
5582
  local: {
4493
- consoleUrl: "http://localhost:3000",
5583
+ consoleUrl: "https://suipay-dev.up.railway.app",
4494
5584
  gatewayUrl: "http://127.0.0.1:4340"
4495
5585
  }
4496
5586
  };
@@ -4584,6 +5674,8 @@ function loadMcpConfig(env = process.env, argv = []) {
4584
5674
  const preset = MCP_TARGET_PRESETS[target];
4585
5675
  const gatewayUrl = env.SUIPAY_GATEWAY_URL?.trim() || preset.gatewayUrl;
4586
5676
  const consoleUrl = env.SUIPAY_CONSOLE_URL?.trim() || preset.consoleUrl;
5677
+ const rawDir = env.SUIPAY_EXCLUSION_DIRECTORY_OBJECT_ID?.trim();
5678
+ if (rawDir) assertValidPackageId("SUIPAY_EXCLUSION_DIRECTORY_OBJECT_ID", rawDir);
4587
5679
  return {
4588
5680
  target,
4589
5681
  consoleUrl,
@@ -4596,9 +5688,42 @@ function loadMcpConfig(env = process.env, argv = []) {
4596
5688
  sponsorUrl: env.SUIPAY_SPONSOR_URL ?? `${gatewayUrl.replace(/\/$/, "")}/api/sponsor`,
4597
5689
  minFunded: BigInt(env.SUIPAY_MIN_FUNDED ?? "0"),
4598
5690
  minMaxPerPayment: BigInt(env.SUIPAY_MIN_MAX_PER_PAYMENT ?? "0"),
4599
- label: env.SUIPAY_LABEL ?? "SuiPay MCP"
5691
+ label: env.SUIPAY_LABEL ?? "SuiPay MCP",
5692
+ exclusionDirectoryObjectId: rawDir || null
4600
5693
  };
4601
5694
  }
5695
+ var OBJECT_ID = /^0x(?:[0-9a-fA-F]{1,64})$/;
5696
+ async function publishedExclusionDirectoryId(gatewayUrl, fetchImpl = fetch) {
5697
+ let url;
5698
+ try {
5699
+ url = new URL("/v1/delegate/deployment", gatewayUrl).toString();
5700
+ } catch {
5701
+ return null;
5702
+ }
5703
+ let response;
5704
+ try {
5705
+ response = await fetchImpl(url, {
5706
+ method: "GET",
5707
+ redirect: "manual",
5708
+ signal: AbortSignal.timeout(1500)
5709
+ });
5710
+ } catch {
5711
+ return null;
5712
+ }
5713
+ if (response.status !== 200) return null;
5714
+ let body;
5715
+ try {
5716
+ body = await response.json();
5717
+ } catch {
5718
+ return null;
5719
+ }
5720
+ const id = body && typeof body === "object" && "exclusionDirectoryObjectId" in body ? body.exclusionDirectoryObjectId : void 0;
5721
+ if (typeof id !== "string" || !OBJECT_ID.test(id.trim())) return null;
5722
+ return id.trim();
5723
+ }
5724
+ function rpcClient(cfg) {
5725
+ return new SuiGrpcClient2({ network: cfg.network, baseUrl: cfg.rpcUrl });
5726
+ }
4602
5727
 
4603
5728
  // src/crypto.ts
4604
5729
  import { getPublicKeyAsync, utils } from "@noble/ed25519";
@@ -4774,7 +5899,7 @@ function walletFromSeedHex(seedHex) {
4774
5899
  return {
4775
5900
  address: kp.getPublicKey().toSuiAddress(),
4776
5901
  async signTransaction(txBytes) {
4777
- const { signature } = await kp.signTransaction(fromBase644(txBytes));
5902
+ const { signature } = await kp.signTransaction(fromBase647(txBytes));
4778
5903
  return signature;
4779
5904
  }
4780
5905
  };
@@ -4791,7 +5916,7 @@ function walletFromEnvSecret(raw) {
4791
5916
  return {
4792
5917
  address: kp.getPublicKey().toSuiAddress(),
4793
5918
  async signTransaction(txBytes) {
4794
- const { signature } = await kp.signTransaction(fromBase644(txBytes));
5919
+ const { signature } = await kp.signTransaction(fromBase647(txBytes));
4795
5920
  return signature;
4796
5921
  }
4797
5922
  };
@@ -4888,9 +6013,29 @@ function recordFromKeypair(kp, opts = {}, existing, operatorSecret) {
4888
6013
  };
4889
6014
  }
4890
6015
  var ENV_KEY_MISMATCH = "SUIPAY_MCP_KEY does not match the stored delegate key - refusing to overwrite";
6016
+ function signingMaterial(parsed) {
6017
+ if (!parsed || typeof parsed !== "object") return null;
6018
+ if (isLocalSigningKeyRecord(parsed)) return parsed;
6019
+ const c = parsed;
6020
+ 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)) {
6021
+ return null;
6022
+ }
6023
+ const label = typeof c.label === "string" ? c.label.trim() : "";
6024
+ return {
6025
+ version: 2,
6026
+ delegatePrivateKey: c.delegatePrivateKey,
6027
+ delegatePublicKeyHex: c.delegatePublicKeyHex,
6028
+ delegateAddress: c.delegateAddress,
6029
+ createdAt: typeof c.createdAt === "string" && c.createdAt ? c.createdAt : (/* @__PURE__ */ new Date(0)).toISOString(),
6030
+ ...isHttpUrl(c.gatewayUrl) ? { gatewayUrl: c.gatewayUrl } : {},
6031
+ ...label ? { label } : {}
6032
+ };
6033
+ }
4891
6034
  function existingRecordForEnv(parsed) {
4892
6035
  if (parsed === null) return null;
4893
- if (isLocalSigningKeyRecord(parsed) || isValid(parsed)) return parsed;
6036
+ if (isValid(parsed)) return parsed;
6037
+ const material = signingMaterial(parsed);
6038
+ if (material) return material;
4894
6039
  throw new Error(
4895
6040
  `credentials file at ${credsPath()} failed schema validation - refusing to continue`
4896
6041
  );
@@ -4928,16 +6073,25 @@ async function ensureLocalSigningKey(opts = {}) {
4928
6073
  }
4929
6074
  const parsed = readCredentialsJson();
4930
6075
  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)) {
6076
+ if (isValid(parsed) && !isLocalSigningKeyRecord(parsed)) {
4939
6077
  return rewriteV1ToLocalSigningKey(parsed, opts);
4940
6078
  }
6079
+ const material = signingMaterial(parsed);
6080
+ if (material) {
6081
+ const identity = identityFromStored(
6082
+ material.delegatePrivateKey,
6083
+ material.delegatePublicKeyHex,
6084
+ material.delegateAddress
6085
+ );
6086
+ if (!isLocalSigningKeyRecord(parsed)) {
6087
+ const label = opts.label?.trim() || material.label;
6088
+ writeCredentialsFile({
6089
+ ...material,
6090
+ ...label ? { label } : {}
6091
+ });
6092
+ }
6093
+ return identity;
6094
+ }
4941
6095
  throw new Error(`credentials file at ${credsPath()} failed schema validation - refusing to continue`);
4942
6096
  }
4943
6097
  const kp = await generateKeypair();
@@ -4986,7 +6140,7 @@ function loadLocalSigner() {
4986
6140
  return {
4987
6141
  address: parsed.delegateAddress,
4988
6142
  async signTransaction(txBytes) {
4989
- const { signature } = await kp.signTransaction(fromBase644(txBytes));
6143
+ const { signature } = await kp.signTransaction(fromBase647(txBytes));
4990
6144
  return signature;
4991
6145
  },
4992
6146
  async signPersonalMessage(message) {
@@ -5028,6 +6182,32 @@ function loadLocalKeypair() {
5028
6182
  Uint8Array.from(Buffer.from(parsed.delegatePrivateKey, "hex"))
5029
6183
  );
5030
6184
  }
6185
+ function replaceLocalSigningKey(input) {
6186
+ identityFromStored(
6187
+ input.delegatePrivateKey,
6188
+ input.delegatePublicKeyHex,
6189
+ input.delegateAddress
6190
+ );
6191
+ const parsed = readCredentialsJson();
6192
+ let gatewayUrl;
6193
+ let label;
6194
+ if (parsed && typeof parsed === "object") {
6195
+ const record = parsed;
6196
+ if (isHttpUrl(record.gatewayUrl)) gatewayUrl = record.gatewayUrl;
6197
+ if (typeof record.label === "string" && record.label.trim()) {
6198
+ label = record.label.trim();
6199
+ }
6200
+ }
6201
+ writeCredentialsFile({
6202
+ version: 2,
6203
+ delegatePrivateKey: input.delegatePrivateKey,
6204
+ delegatePublicKeyHex: input.delegatePublicKeyHex,
6205
+ delegateAddress: input.delegateAddress,
6206
+ createdAt: (/* @__PURE__ */ new Date()).toISOString(),
6207
+ ...gatewayUrl ? { gatewayUrl } : {},
6208
+ ...label ? { label } : {}
6209
+ });
6210
+ }
5031
6211
  function persistGatewayUrl(gatewayUrl) {
5032
6212
  if (!isHttpUrl(gatewayUrl)) {
5033
6213
  throw new Error("gatewayUrl must be an http(s) origin");
@@ -5072,7 +6252,7 @@ function objectWithOwner(object) {
5072
6252
  }
5073
6253
  return { ...object.json, owner: object.owner };
5074
6254
  }
5075
- function sameAddress2(a, b) {
6255
+ function sameAddress3(a, b) {
5076
6256
  try {
5077
6257
  return normalizeSuiAddress8(a) === normalizeSuiAddress8(b);
5078
6258
  } catch {
@@ -5088,7 +6268,7 @@ function resolveAgentHeldSettlement(cfg, spendAccountPay, env = process.env) {
5088
6268
  if (!secret) return null;
5089
6269
  const local = loadLocalSigner();
5090
6270
  if (!local) return null;
5091
- if (!sameAddress2(local.address, spendAccountPay.snapshot.delegateAddress)) {
6271
+ if (!sameAddress3(local.address, spendAccountPay.snapshot.delegateAddress)) {
5092
6272
  return null;
5093
6273
  }
5094
6274
  const chain = createSpendAccountChain({
@@ -5096,7 +6276,7 @@ function resolveAgentHeldSettlement(cfg, spendAccountPay, env = process.env) {
5096
6276
  network: cfg.network,
5097
6277
  url: cfg.rpcUrl
5098
6278
  });
5099
- const finality = createSpendAccountFinalityReader({ client: chain.client });
6279
+ const finality = createSpendAccountSettledTxReader({ client: chain.client });
5100
6280
  const getObjectJson = async (objectId) => {
5101
6281
  const { object } = await chain.client.core.getObject({
5102
6282
  objectId,
@@ -5119,7 +6299,7 @@ function resolveAgentHeldSettlement(cfg, spendAccountPay, env = process.env) {
5119
6299
  sponsor: { sponsorUrl: cfg.gatewayUrl },
5120
6300
  gasMode: "sponsored",
5121
6301
  resolveSharedObjectVersions: (ids) => resolveSharedObjectVersionsFromGetObject(getObjectJson, ids),
5122
- getFinalizedTx: (digest) => finality.getFinalizedTx(digest)
6302
+ getSettledTx: (digest) => finality.getSettledTx(digest)
5123
6303
  }
5124
6304
  };
5125
6305
  }
@@ -5176,9 +6356,16 @@ function toolAllowedByScope(toolName, grantedScope) {
5176
6356
  }
5177
6357
 
5178
6358
  // src/tools.ts
5179
- import { isValidSuiAddress, normalizeSuiAddress as normalizeSuiAddress12 } from "@mysten/sui/utils";
6359
+ import { isValidSuiAddress, normalizeSuiAddress as normalizeSuiAddress15 } from "@mysten/sui/utils";
5180
6360
 
5181
6361
  // src/settle.ts
6362
+ function accessModeFromMoveTarget(target) {
6363
+ const fn = target.split("::").pop() ?? "";
6364
+ if (fn === spendAccountDebitFunction({ accessMode: "open" }) || fn === spendAccountDebitFunction({ accessMode: "open", refundable: true })) {
6365
+ return "open";
6366
+ }
6367
+ return "scoped";
6368
+ }
5182
6369
  async function settlePreparedMcpPayment(args) {
5183
6370
  try {
5184
6371
  await verifyPreparedSettlementKind({
@@ -5188,9 +6375,9 @@ async function settlePreparedMcpPayment(args) {
5188
6375
  delegateAddress: args.expected.delegateAddress,
5189
6376
  amount: args.expected.amount,
5190
6377
  recipient: args.expected.recipient,
5191
- accessMode: args.signingRequest.submit.moveCallTarget.endsWith(
5192
- "settle_open_recipient_payment"
5193
- ) ? "open" : "scoped"
6378
+ accessMode: accessModeFromMoveTarget(
6379
+ args.signingRequest.submit.moveCallTarget
6380
+ )
5194
6381
  },
5195
6382
  getObjectJson: args.getObjectJson
5196
6383
  });
@@ -5236,12 +6423,104 @@ async function settlePreparedMcpPayment(args) {
5236
6423
  }
5237
6424
 
5238
6425
  // ../sdk/dist/agent-sdk/delegate-agent.js
5239
- import { normalizeSuiAddress as normalizeSuiAddress10 } from "@mysten/sui/utils";
6426
+ import { normalizeSuiAddress as normalizeSuiAddress11 } from "@mysten/sui/utils";
5240
6427
 
5241
- // ../sdk/dist/agent-sdk/verify-prepared-settlement.js
6428
+ // ../sdk/dist/buyer-control/exclusion-registry.js
6429
+ import { bcs as bcs6 } from "@mysten/sui/bcs";
5242
6430
  import { normalizeSuiAddress as normalizeSuiAddress9 } from "@mysten/sui/utils";
6431
+ var ExclusionRegistryResolutionError = class extends Error {
6432
+ code;
6433
+ constructor(code, message) {
6434
+ super(message);
6435
+ this.code = code;
6436
+ this.name = "ExclusionRegistryResolutionError";
6437
+ }
6438
+ };
6439
+ async function resolveExclusionRegistry(input) {
6440
+ const directoryObjectId = typeof input.directoryObjectId === "string" ? input.directoryObjectId.trim() : "";
6441
+ if (!directoryObjectId) {
6442
+ throw new ExclusionRegistryResolutionError("NO_DIRECTORY", "no exclusion directory object id configured for a guarded offer");
6443
+ }
6444
+ let registryId;
6445
+ try {
6446
+ registryId = await input.reader.boundRegistryId(directoryObjectId, input.recipient);
6447
+ } catch (err) {
6448
+ if (err instanceof ExclusionRegistryResolutionError)
6449
+ throw err;
6450
+ throw new ExclusionRegistryResolutionError("REGISTRY_UNREADABLE", "could not read the exclusion directory binding for this recipient");
6451
+ }
6452
+ if (!registryId || !registryId.trim()) {
6453
+ throw new ExclusionRegistryResolutionError("REGISTRY_NOT_PROVISIONED", "no RecipientRegistry is bound for this recipient in the exclusion directory");
6454
+ }
6455
+ const directoryInitialSharedVersion = await input.reader.initialSharedVersion(directoryObjectId);
6456
+ if (!directoryInitialSharedVersion || !directoryInitialSharedVersion.trim()) {
6457
+ throw new ExclusionRegistryResolutionError("DIRECTORY_VERSION_UNRESOLVED", "could not resolve the exclusion directory initial shared version");
6458
+ }
6459
+ const registryInitialSharedVersion = await input.reader.initialSharedVersion(registryId);
6460
+ if (!registryInitialSharedVersion || !registryInitialSharedVersion.trim()) {
6461
+ throw new ExclusionRegistryResolutionError("REGISTRY_VERSION_UNRESOLVED", "could not resolve the recipient registry initial shared version");
6462
+ }
6463
+ return {
6464
+ recipientRegistryId: normalizeSuiAddress9(registryId),
6465
+ directoryInitialSharedVersion,
6466
+ registryInitialSharedVersion
6467
+ };
6468
+ }
6469
+ function initialSharedVersionFromOwner2(owner) {
6470
+ if (!owner || typeof owner !== "object")
6471
+ return null;
6472
+ const shared = owner.Shared;
6473
+ if (shared?.initialSharedVersion != null) {
6474
+ return String(shared.initialSharedVersion);
6475
+ }
6476
+ return null;
6477
+ }
6478
+ function isAbsentDynamicFieldError(err) {
6479
+ const text2 = (err instanceof Error ? err.message : String(err)).toLowerCase();
6480
+ if (/timeout|timed out|unavailable|econnreset|econnrefused|network|fetch failed|socket| 502| 503| 429/.test(text2)) {
6481
+ return false;
6482
+ }
6483
+ return /\bnot[_ ]found\b|dynamic field not found|no such.*field/.test(text2);
6484
+ }
6485
+ function createGrpcExclusionRegistryReader(client) {
6486
+ return {
6487
+ async boundRegistryId(directoryObjectId, recipient) {
6488
+ let response;
6489
+ try {
6490
+ response = await client.core.getDynamicField({
6491
+ parentId: directoryObjectId,
6492
+ name: { type: "address", bcs: bcs6.Address.serialize(recipient).toBytes() }
6493
+ });
6494
+ } catch (err) {
6495
+ if (isAbsentDynamicFieldError(err))
6496
+ return null;
6497
+ throw new ExclusionRegistryResolutionError("REGISTRY_UNREADABLE", "could not read the exclusion directory binding for this recipient");
6498
+ }
6499
+ const raw = response?.dynamicField?.value?.bcs;
6500
+ if (!raw)
6501
+ return null;
6502
+ try {
6503
+ return normalizeSuiAddress9(bcs6.Address.parse(raw));
6504
+ } catch {
6505
+ return null;
6506
+ }
6507
+ },
6508
+ async initialSharedVersion(objectId) {
6509
+ let response;
6510
+ try {
6511
+ response = await client.core.getObject({ objectId });
6512
+ } catch {
6513
+ return null;
6514
+ }
6515
+ return initialSharedVersionFromOwner2(response?.object?.owner);
6516
+ }
6517
+ };
6518
+ }
6519
+
6520
+ // ../sdk/dist/agent-sdk/verify-prepared-settlement.js
6521
+ import { normalizeSuiAddress as normalizeSuiAddress10 } from "@mysten/sui/utils";
5243
6522
  import { Transaction as Transaction5 } from "@mysten/sui/transactions";
5244
- import { fromBase64 as fromBase645 } from "@mysten/sui/utils";
6523
+ import { fromBase64 as fromBase648 } from "@mysten/sui/utils";
5245
6524
 
5246
6525
  // ../sdk/dist/protocol/prepared-settlement.js
5247
6526
  var PREPARED_SETTLEMENT_VERSION = 1;
@@ -5269,6 +6548,11 @@ function asPreparedSettlement(value) {
5269
6548
  "coinType",
5270
6549
  "amount",
5271
6550
  "recipient",
6551
+ "exclusionDirectoryId",
6552
+ "recipientRegistryId",
6553
+ "exclusionDirectoryInitialSharedVersion",
6554
+ "recipientRegistryInitialSharedVersion",
6555
+ "expiresAtMs",
5272
6556
  "payUrl"
5273
6557
  ];
5274
6558
  for (const key of strings) {
@@ -5293,6 +6577,11 @@ function asPreparedSettlement(value) {
5293
6577
  coinType: v.coinType,
5294
6578
  amount: v.amount,
5295
6579
  recipient: v.recipient,
6580
+ exclusionDirectoryId: v.exclusionDirectoryId,
6581
+ recipientRegistryId: v.recipientRegistryId,
6582
+ exclusionDirectoryInitialSharedVersion: v.exclusionDirectoryInitialSharedVersion,
6583
+ recipientRegistryInitialSharedVersion: v.recipientRegistryInitialSharedVersion,
6584
+ expiresAtMs: v.expiresAtMs,
5296
6585
  payUrl: v.payUrl
5297
6586
  };
5298
6587
  }
@@ -5311,9 +6600,9 @@ var PreparedSettlementRejected = class extends Error {
5311
6600
  function reject(field, message) {
5312
6601
  throw new PreparedSettlementRejected(field, message);
5313
6602
  }
5314
- function sameAddress3(a, b) {
6603
+ function sameAddress4(a, b) {
5315
6604
  try {
5316
- return normalizeSuiAddress9(a) === normalizeSuiAddress9(b);
6605
+ return normalizeSuiAddress10(a) === normalizeSuiAddress10(b);
5317
6606
  } catch {
5318
6607
  return false;
5319
6608
  }
@@ -5324,7 +6613,7 @@ function sameCoinType(a, b) {
5324
6613
  if (pkg === void 0 || rest.length !== 2)
5325
6614
  return value.trim();
5326
6615
  try {
5327
- return `${normalizeSuiAddress9(pkg)}::${rest.join("::")}`;
6616
+ return `${normalizeSuiAddress10(pkg)}::${rest.join("::")}`;
5328
6617
  } catch {
5329
6618
  return value.trim();
5330
6619
  }
@@ -5344,26 +6633,26 @@ async function verifyPreparedPayment(input) {
5344
6633
  reject("shape", "the 402 carried no usable prepared settlement");
5345
6634
  }
5346
6635
  const { offer, delegateAddress } = input;
5347
- if (!sameAddress3(prepared.sender, delegateAddress)) {
6636
+ if (!sameAddress4(prepared.sender, delegateAddress)) {
5348
6637
  reject("sender", `prepared sender ${prepared.sender} is not this agent's address`);
5349
6638
  }
5350
6639
  let data;
5351
6640
  try {
5352
- data = Transaction5.from(fromBase645(prepared.transactionBytes)).getData();
6641
+ data = Transaction5.from(fromBase648(prepared.transactionBytes)).getData();
5353
6642
  } catch {
5354
6643
  reject("transactionBytes", "prepared transaction bytes are not a Sui transaction");
5355
6644
  }
5356
6645
  const gasOwner = data.gasData?.owner;
5357
- if (!gasOwner || sameAddress3(gasOwner, delegateAddress)) {
6646
+ if (!gasOwner || sameAddress4(gasOwner, delegateAddress)) {
5358
6647
  reject("gasOwner", "prepared transaction is not sponsored: gas would be paid by this agent");
5359
6648
  }
5360
- if (input.expectedGrantObjectId && !sameAddress3(prepared.grantObjectId, input.expectedGrantObjectId)) {
6649
+ if (input.expectedGrantObjectId && !sameAddress4(prepared.grantObjectId, input.expectedGrantObjectId)) {
5361
6650
  reject("grantObjectId", `prepared settlement draws on grant ${prepared.grantObjectId}, not the one this agent resolved`);
5362
6651
  }
5363
- if (input.expectedPoolObjectId && !sameAddress3(prepared.poolObjectId, input.expectedPoolObjectId)) {
6652
+ if (input.expectedPoolObjectId && !sameAddress4(prepared.poolObjectId, input.expectedPoolObjectId)) {
5364
6653
  reject("poolObjectId", `prepared settlement draws on pool ${prepared.poolObjectId}, not the one this agent resolved`);
5365
6654
  }
5366
- if (!offer.packageId || !sameAddress3(prepared.packageId, offer.packageId)) {
6655
+ if (!offer.packageId || !sameAddress4(prepared.packageId, offer.packageId)) {
5367
6656
  reject("packageId", "prepared package id does not match the offer");
5368
6657
  }
5369
6658
  const wantedTarget = payMoveCallTarget(offer.packageId, prepared.accessMode);
@@ -5376,9 +6665,19 @@ async function verifyPreparedPayment(input) {
5376
6665
  if (!sameAmount(prepared.amount, offer.amount)) {
5377
6666
  reject("amount", "prepared amount does not match the offer amount");
5378
6667
  }
5379
- if (!sameAddress3(prepared.recipient, offer.payTo)) {
6668
+ if (!sameAddress4(prepared.recipient, offer.payTo)) {
5380
6669
  reject("recipient", "prepared recipient does not match the offer payTo");
5381
6670
  }
6671
+ const identity = parseRegistryIdentity(offer.registryIdentity);
6672
+ if (!identity) {
6673
+ reject("registryIdentity", "offer carries no valid registry identity for a guarded settle");
6674
+ }
6675
+ if (!sameAddress4(prepared.exclusionDirectoryId, identity.directoryId)) {
6676
+ reject("exclusionDirectoryId", "prepared exclusion directory is not the one the offer committed to");
6677
+ }
6678
+ if (prepared.expiresAtMs !== String(offer.expiresAt)) {
6679
+ reject("expiresAtMs", "prepared guarded expiry does not match the offer expiry");
6680
+ }
5382
6681
  const targetHash = offer.targetHash?.trim();
5383
6682
  if (!targetHash) {
5384
6683
  reject("targetHash", "offer carries no targetHash to bind the settlement to");
@@ -5398,7 +6697,14 @@ async function verifyPreparedPayment(input) {
5398
6697
  targetHash,
5399
6698
  amount: offer.amount,
5400
6699
  paymentIdHash: paymentIdHash(offer.challengeId),
5401
- termsHash: termsHash(offer)
6700
+ termsHash: termsHash(offer),
6701
+ // Guarded-settle inputs, rebuilt from the prepared bytes and validated
6702
+ // against the offer above.
6703
+ exclusionDirectoryId: prepared.exclusionDirectoryId,
6704
+ recipientRegistryId: prepared.recipientRegistryId,
6705
+ exclusionDirectoryInitialSharedVersion: prepared.exclusionDirectoryInitialSharedVersion,
6706
+ recipientRegistryInitialSharedVersion: prepared.recipientRegistryInitialSharedVersion,
6707
+ expiresAtMs: prepared.expiresAtMs
5402
6708
  };
5403
6709
  let rebuilt;
5404
6710
  try {
@@ -5733,7 +7039,23 @@ async function fetchGatewayDeployment(options) {
5733
7039
  }
5734
7040
  };
5735
7041
  }
5736
- return { ok: true, value: { network: d.network, packageId: d.packageId } };
7042
+ if (d.exclusionDirectoryObjectId !== void 0 && !isNonEmptyString2(d.exclusionDirectoryObjectId)) {
7043
+ return {
7044
+ ok: false,
7045
+ error: {
7046
+ code: MANIFEST_ERROR_CODES.MALFORMED_MANIFEST,
7047
+ detail: "gateway deployment identity carries an unusable exclusionDirectoryObjectId"
7048
+ }
7049
+ };
7050
+ }
7051
+ return {
7052
+ ok: true,
7053
+ value: {
7054
+ network: d.network,
7055
+ packageId: d.packageId,
7056
+ ...isNonEmptyString2(d.exclusionDirectoryObjectId) ? { exclusionDirectoryObjectId: d.exclusionDirectoryObjectId } : {}
7057
+ }
7058
+ };
5737
7059
  }
5738
7060
  function explainBlockedGrant(raw, error2) {
5739
7061
  if (error2.code !== MANIFEST_ERROR_CODES.MISSING_POLICY_BINDING)
@@ -5825,7 +7147,7 @@ async function resolvePayerSession(options) {
5825
7147
  ...options.select ? { select: options.select } : {}
5826
7148
  });
5827
7149
  if (whole.ok)
5828
- return whole;
7150
+ return { ok: true, value: { ...whole.value, deployment } };
5829
7151
  const data = bootstrap.data;
5830
7152
  const rawGrants = Array.isArray(data.grants) ? data.grants : [];
5831
7153
  const usable = [];
@@ -5860,11 +7182,12 @@ async function resolvePayerSession(options) {
5860
7182
  const pick = scoped ?? blocked[0];
5861
7183
  return pick ? { ok: false, error: explainBlockedGrant(pick.raw, pick.error) } : whole;
5862
7184
  }
5863
- return manifestToSession({
7185
+ const resolved = manifestToSession({
5864
7186
  manifest: { ...data, grants: usable },
5865
7187
  signer: options.signer,
5866
7188
  ...options.select ? { select: options.select } : {}
5867
7189
  });
7190
+ return resolved.ok ? { ok: true, value: { ...resolved.value, deployment } } : resolved;
5868
7191
  }
5869
7192
 
5870
7193
  // ../sdk/dist/buyer/transport.js
@@ -5894,6 +7217,15 @@ function mergeObjectEnvelope(object) {
5894
7217
  var SECRET_KEY_SET = new Set(FORBIDDEN_SECRET_KEYS.map((k) => k.toLowerCase()));
5895
7218
 
5896
7219
  // ../sdk/dist/agent-sdk/delegate-agent.js
7220
+ function resolveExclusionDirectoryId(explicit, discovered) {
7221
+ return explicit?.trim() ? explicit : discovered;
7222
+ }
7223
+ function createExclusionRegistryResolver(client, exclusionDirectoryObjectId) {
7224
+ if (!exclusionDirectoryObjectId || !exclusionDirectoryObjectId.trim())
7225
+ return void 0;
7226
+ const reader = createGrpcExclusionRegistryReader(client);
7227
+ return async ({ directoryId, recipient }) => resolveExclusionRegistry({ directoryObjectId: directoryId, recipient, reader });
7228
+ }
5897
7229
  async function verifyGrantOnChain(input) {
5898
7230
  let state;
5899
7231
  try {
@@ -5920,10 +7252,10 @@ async function verifyGrantOnChain(input) {
5920
7252
  ok: false,
5921
7253
  error: { code: MANIFEST_ERROR_CODES.GRANT_NOT_VERIFIED, detail }
5922
7254
  });
5923
- if (!state.delegateAddress || normalizeSuiAddress10(state.delegateAddress) !== normalizeSuiAddress10(input.delegateAddress)) {
7255
+ if (!state.delegateAddress || normalizeSuiAddress11(state.delegateAddress) !== normalizeSuiAddress11(input.delegateAddress)) {
5924
7256
  return notVerified(`grant ${input.grantObjectId} does not name this delegate on chain`);
5925
7257
  }
5926
- if (!state.poolObjectId || normalizeSuiAddress10(state.poolObjectId) !== normalizeSuiAddress10(input.poolObjectId)) {
7258
+ if (!state.poolObjectId || normalizeSuiAddress11(state.poolObjectId) !== normalizeSuiAddress11(input.poolObjectId)) {
5927
7259
  return notVerified(`grant ${input.grantObjectId} draws on a different pool than the one resolved`);
5928
7260
  }
5929
7261
  if (state.revoked === true) {
@@ -5956,8 +7288,17 @@ async function createPayer(options) {
5956
7288
  const { session, binding } = resolved.value;
5957
7289
  const deployment = {
5958
7290
  network: session.network,
5959
- packageId: session.packageId
7291
+ packageId: session.packageId,
7292
+ // What the GATEWAY published, not what this payer settled on: a caller
7293
+ // override belongs to the payer's configuration, not to the deployment it
7294
+ // is describing. Dropping this left the field permanently undefined on the
7295
+ // one instance a consumer can reach, so anything asking the payer whether
7296
+ // guarded settles are available got "no" while it was doing them.
7297
+ ...resolved.value.deployment.exclusionDirectoryObjectId ? {
7298
+ exclusionDirectoryObjectId: resolved.value.deployment.exclusionDirectoryObjectId
7299
+ } : {}
5960
7300
  };
7301
+ const exclusionDirectoryObjectId = resolveExclusionDirectoryId(options.exclusionDirectoryObjectId, resolved.value.deployment.exclusionDirectoryObjectId);
5961
7302
  let network;
5962
7303
  try {
5963
7304
  network = toGrpcNetwork(deployment.network);
@@ -5987,7 +7328,7 @@ async function createPayer(options) {
5987
7328
  }
5988
7329
  };
5989
7330
  }
5990
- const finality = createSpendAccountFinalityReader({ client: chain.client });
7331
+ const finality = createSpendAccountSettledTxReader({ client: chain.client });
5991
7332
  const getObjectJson = async (objectId) => {
5992
7333
  const { object } = await chain.client.core.getObject({
5993
7334
  objectId,
@@ -6010,7 +7351,18 @@ async function createPayer(options) {
6010
7351
  sponsor: { sponsorUrl: options.gatewayUrl },
6011
7352
  gasMode: "sponsored",
6012
7353
  resolveSharedObjectVersions: (ids) => resolveSharedObjectVersionsFromGetObject(getObjectJson, ids),
6013
- getFinalizedTx: finality.getFinalizedTx,
7354
+ getSettledTx: finality.getSettledTx,
7355
+ // A guarded offer resolves its per-recipient exclusion registry
7356
+ // through this dep, bound to the agent's own gRPC reader. Undefined when no
7357
+ // directory id was configured, so such an offer fails closed (BUILD_FAILED)
7358
+ // rather than building an unguarded settle. Unguarded offers never call it.
7359
+ ...(() => {
7360
+ const resolve = createExclusionRegistryResolver(chain.client, exclusionDirectoryObjectId);
7361
+ return resolve ? { resolveExclusionRegistry: resolve } : {};
7362
+ })(),
7363
+ // x402 bounds the transaction's expiration by the current epoch, read from
7364
+ // the same client as every other chain read here.
7365
+ getCurrentEpoch: currentEpochReader(chain.client),
6014
7366
  ...options.offerSecret ? { offerSecret: options.offerSecret } : {},
6015
7367
  ...options.fetchImpl ? { fetchImpl: options.fetchImpl } : {},
6016
7368
  ...options.logError ? { logError: options.logError } : {}
@@ -6040,7 +7392,9 @@ async function createPayer(options) {
6040
7392
  cfg: { gatewayUrl: options.gatewayUrl },
6041
7393
  deps,
6042
7394
  intent: input.intent,
6043
- ...input.preparedTerms ? { preparedTerms: input.preparedTerms } : {}
7395
+ ...input.preparedTerms ? { preparedTerms: input.preparedTerms } : {},
7396
+ dialect: input.dialect,
7397
+ onX402Progress: input.onX402Progress
6044
7398
  });
6045
7399
  }
6046
7400
  async function verifySettlement(prepared, offer) {
@@ -6062,6 +7416,7 @@ async function createPayer(options) {
6062
7416
  import { createServer } from "http";
6063
7417
  import { randomBytes as randomBytes3, timingSafeEqual as timingSafeEqual2 } from "crypto";
6064
7418
  import open from "open";
7419
+ import { Ed25519Keypair as Ed25519Keypair3 } from "@mysten/sui/keypairs/ed25519";
6065
7420
  var HEX642 = /^[0-9a-f]{64}$/;
6066
7421
  var PUBHEX = /^[0-9a-fA-F]{64}$/;
6067
7422
  function isPreflight(obj) {
@@ -6069,6 +7424,11 @@ function isPreflight(obj) {
6069
7424
  const o = obj;
6070
7425
  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
7426
  }
7427
+ function isAdoptKey(obj) {
7428
+ if (!obj || typeof obj !== "object") return false;
7429
+ const o = obj;
7430
+ 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);
7431
+ }
6072
7432
  function isCallback(obj) {
6073
7433
  if (!obj || typeof obj !== "object") return false;
6074
7434
  const o = obj;
@@ -6193,7 +7553,7 @@ async function startStdioLogin(opts) {
6193
7553
  res.writeHead(code, { "content-type": type, ...SECURITY_HEADERS, ...extra });
6194
7554
  res.end(body);
6195
7555
  };
6196
- const okPath = url.pathname === "/preflight" || url.pathname === "/callback";
7556
+ const okPath = url.pathname === "/preflight" || url.pathname === "/callback" || url.pathname === "/key";
6197
7557
  if (req.method === "OPTIONS") {
6198
7558
  const extra = { ...cors };
6199
7559
  if (okHost && okOrigin && okPath && req.headers["access-control-request-private-network"] === "true") {
@@ -6264,6 +7624,55 @@ async function startStdioLogin(opts) {
6264
7624
  );
6265
7625
  return;
6266
7626
  }
7627
+ if (url.pathname === "/key") {
7628
+ if (!preflightVerified) {
7629
+ res.writeHead(428, {
7630
+ "content-type": "application/json",
7631
+ ...cors,
7632
+ ...SECURITY_HEADERS
7633
+ });
7634
+ res.end(JSON.stringify({ ok: false, error: "preflight required" }));
7635
+ return;
7636
+ }
7637
+ if (!isAdoptKey(parsed) || !stateEquals(parsed.state, connectState)) {
7638
+ res.writeHead(400, {
7639
+ "content-type": "application/json",
7640
+ ...cors,
7641
+ ...SECURITY_HEADERS
7642
+ });
7643
+ res.end(JSON.stringify({ ok: false, error: "invalid key payload" }));
7644
+ return;
7645
+ }
7646
+ try {
7647
+ const kp = Ed25519Keypair3.fromSecretKey(
7648
+ Uint8Array.from(Buffer.from(parsed.delegatePrivateKey, "hex"))
7649
+ );
7650
+ const derived = kp.getPublicKey().toSuiAddress();
7651
+ if (derived.toLowerCase() !== parsed.delegateAddress.toLowerCase()) {
7652
+ throw new Error("address mismatch");
7653
+ }
7654
+ replaceLocalSigningKey({
7655
+ delegatePrivateKey: parsed.delegatePrivateKey,
7656
+ delegatePublicKeyHex: parsed.delegatePublicKeyHex,
7657
+ delegateAddress: derived
7658
+ });
7659
+ } catch {
7660
+ res.writeHead(400, {
7661
+ "content-type": "application/json",
7662
+ ...cors,
7663
+ ...SECURITY_HEADERS
7664
+ });
7665
+ res.end(JSON.stringify({ ok: false, error: "key rejected" }));
7666
+ return;
7667
+ }
7668
+ res.writeHead(200, {
7669
+ "content-type": "application/json",
7670
+ ...cors,
7671
+ ...SECURITY_HEADERS
7672
+ });
7673
+ res.end(JSON.stringify({ ok: true }));
7674
+ return;
7675
+ }
6267
7676
  if (!preflightVerified) {
6268
7677
  res.writeHead(428, {
6269
7678
  "content-type": "text/html",
@@ -6346,13 +7755,43 @@ async function startStdioLogin(opts) {
6346
7755
  }
6347
7756
 
6348
7757
  // src/spend-account-pay.ts
6349
- import { Transaction as Transaction6 } from "@mysten/sui/transactions";
7758
+ import { Transaction as Transaction8 } from "@mysten/sui/transactions";
6350
7759
  import {
6351
- fromBase64 as fromBase646,
6352
- normalizeSuiAddress as normalizeSuiAddress11,
6353
- toBase64 as toBase644
7760
+ fromBase64 as fromBase6411,
7761
+ normalizeSuiAddress as normalizeSuiAddress14,
7762
+ toBase64 as toBase647
6354
7763
  } from "@mysten/sui/utils";
7764
+
7765
+ // ../sdk/dist/pay-operation/verify-x402-offer.js
7766
+ import { normalizeStructTag as normalizeStructTag7, normalizeSuiAddress as normalizeSuiAddress12 } from "@mysten/sui/utils";
7767
+
7768
+ // ../sdk/dist/pay-operation/verify-direct-tx.js
7769
+ import { Transaction as Transaction6 } from "@mysten/sui/transactions";
7770
+ import { bcs as bcs7 } from "@mysten/sui/bcs";
7771
+ import { fromBase64 as fromBase649, normalizeStructTag as normalizeStructTag8, normalizeSuiAddress as normalizeSuiAddress13 } from "@mysten/sui/utils";
7772
+
7773
+ // ../sdk/dist/pay-operation/resolve-grant-x402.js
7774
+ import { Transaction as Transaction7 } from "@mysten/sui/transactions";
7775
+ import { fromBase64 as fromBase6410, toBase64 as toBase646 } from "@mysten/sui/utils";
7776
+
7777
+ // src/spend-account-pay.ts
6355
7778
  var SUI_CLOCK3 = "0x6";
7779
+ var sharedObjectReaderForTests = null;
7780
+ function chainSharedObjectReader(cfg, packageId) {
7781
+ const chain = createSpendAccountChain({
7782
+ packageId,
7783
+ network: cfg.network,
7784
+ url: cfg.rpcUrl
7785
+ });
7786
+ return async (objectId) => {
7787
+ const { object } = await chain.client.core.getObject({
7788
+ objectId,
7789
+ include: { json: true }
7790
+ });
7791
+ if (!object?.owner) return null;
7792
+ return { owner: object.owner };
7793
+ };
7794
+ }
6356
7795
  function traceEmitter(trace) {
6357
7796
  if (!trace) return () => void 0;
6358
7797
  const now = trace.now ?? Date.now;
@@ -6386,25 +7825,38 @@ function toHex(bytes) {
6386
7825
  async function buildSpendAccountPayKind2(input) {
6387
7826
  if (!input.termsHash.length) throw new Error("termsHash must be non-empty");
6388
7827
  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
7828
  const hashHex = input.targetHash.replace(/^0x/i, "").toLowerCase();
6392
7829
  if (!/^[0-9a-f]{64}$/.test(hashHex)) throw new Error("targetHash must be 32-byte hex");
6393
7830
  const hash = Uint8Array.from(Buffer.from(hashHex, "hex"));
6394
- const tx = new Transaction6();
7831
+ const guarded = resolveGuardedSettleOfflineInputs(input);
7832
+ const target = payMoveCallTarget(
7833
+ input.packageId,
7834
+ "scoped"
7835
+ );
7836
+ const tx = new Transaction8();
6395
7837
  tx.setSender(input.delegate);
6396
7838
  tx.moveCall({
6397
7839
  target,
6398
7840
  typeArguments: [input.coinType],
6399
7841
  arguments: [
7842
+ tx.sharedObjectRef({
7843
+ objectId: guarded.exclusionDirectoryId,
7844
+ initialSharedVersion: guarded.exclusionDirectoryInitialSharedVersion,
7845
+ mutable: false
7846
+ }),
7847
+ tx.sharedObjectRef({
7848
+ objectId: guarded.recipientRegistryId,
7849
+ initialSharedVersion: guarded.recipientRegistryInitialSharedVersion,
7850
+ mutable: true
7851
+ }),
6400
7852
  tx.sharedObjectRef({
6401
7853
  objectId: input.poolObjectId,
6402
- initialSharedVersion: "1",
7854
+ initialSharedVersion: input.poolInitialSharedVersion?.trim() || "1",
6403
7855
  mutable: true
6404
7856
  }),
6405
7857
  tx.sharedObjectRef({
6406
7858
  objectId: input.grantObjectId,
6407
- initialSharedVersion: "1",
7859
+ initialSharedVersion: input.grantInitialSharedVersion?.trim() || "1",
6408
7860
  mutable: true
6409
7861
  }),
6410
7862
  tx.pure.u64(BigInt(input.policyId)),
@@ -6412,42 +7864,43 @@ async function buildSpendAccountPayKind2(input) {
6412
7864
  tx.pure.u64(BigInt(input.amount)),
6413
7865
  tx.pure.vector("u8", Array.from(input.paymentIdHash)),
6414
7866
  tx.pure.vector("u8", Array.from(input.termsHash)),
7867
+ tx.pure.u64(BigInt(guarded.expiresAtMs)),
6415
7868
  tx.sharedObjectRef({
6416
7869
  objectId: SUI_CLOCK3,
6417
- initialSharedVersion: "1",
7870
+ initialSharedVersion: input.clockInitialSharedVersion?.trim() || "1",
6418
7871
  mutable: false
6419
7872
  })
6420
7873
  ]
6421
7874
  });
6422
7875
  const bytes = await tx.build({ onlyTransactionKind: true });
6423
- return { kindBytes: toBase644(bytes) };
7876
+ return { kindBytes: toBase647(bytes) };
6424
7877
  }
6425
7878
  function verifySpendAccountPayReconstructs(bytes, expected) {
6426
7879
  let data;
6427
7880
  try {
6428
- data = Transaction6.from(fromBase646(bytes)).getData();
7881
+ data = Transaction8.from(fromBase6411(bytes)).getData();
6429
7882
  } catch {
6430
7883
  throw new Error("sponsored transaction is not valid Sui transaction bytes");
6431
7884
  }
6432
- if (!data.sender || normalizeSuiAddress11(data.sender) !== normalizeSuiAddress11(expected.delegate)) {
7885
+ if (!data.sender || normalizeSuiAddress14(data.sender) !== normalizeSuiAddress14(expected.delegate)) {
6433
7886
  throw new Error("sponsored transaction sender does not match delegate");
6434
7887
  }
6435
7888
  if (!data.commands || data.commands.length !== 1) {
6436
7889
  throw new Error("sponsored transaction must contain exactly one command");
6437
7890
  }
6438
7891
  const auth = defaultAuthorityForRail("delegate", expected.packageId);
6439
- const wanted = `${normalizeSuiAddress11(auth.packageId)}::${auth.module}::${auth.payFunction}`;
7892
+ const wanted = `${normalizeSuiAddress14(auth.packageId)}::${auth.module}::${auth.payFunction}`;
6440
7893
  const cmd = data.commands[0];
6441
7894
  if (cmd.$kind !== "MoveCall" || !cmd.MoveCall) {
6442
7895
  throw new Error("sponsored transaction is not a MoveCall");
6443
7896
  }
6444
- const got = `${normalizeSuiAddress11(String(cmd.MoveCall.package))}::${cmd.MoveCall.module}::${cmd.MoveCall.function}`;
7897
+ const got = `${normalizeSuiAddress14(String(cmd.MoveCall.package))}::${cmd.MoveCall.module}::${cmd.MoveCall.function}`;
6445
7898
  if (got !== wanted) {
6446
7899
  throw new Error(`sponsored transaction does not reconstruct ${wanted}`);
6447
7900
  }
6448
7901
  let expectedKind;
6449
7902
  try {
6450
- expectedKind = Transaction6.fromKind(fromBase646(expected.kindBytes)).getData();
7903
+ expectedKind = Transaction8.fromKind(fromBase6411(expected.kindBytes)).getData();
6451
7904
  } catch {
6452
7905
  throw new Error("sponsored transaction kind baseline invalid");
6453
7906
  }
@@ -6479,6 +7932,13 @@ async function prepareSpendAccountPayment(args) {
6479
7932
  const isOpen = session.snapshot.accessMode === "open";
6480
7933
  const callerBuild = deps.buildPayKind;
6481
7934
  const buildPayKind = async (input) => {
7935
+ const guardedFields = {
7936
+ exclusionDirectoryId: input.exclusionDirectoryId,
7937
+ recipientRegistryId: input.recipientRegistryId,
7938
+ expiresAtMs: input.expiresAtMs,
7939
+ exclusionDirectoryInitialSharedVersion: input.exclusionDirectoryInitialSharedVersion,
7940
+ recipientRegistryInitialSharedVersion: input.recipientRegistryInitialSharedVersion
7941
+ };
6482
7942
  if (callerBuild) {
6483
7943
  return callerBuild({
6484
7944
  delegate: input.delegate,
@@ -6491,9 +7951,16 @@ async function prepareSpendAccountPayment(args) {
6491
7951
  paymentIdHash: input.paymentIdHash,
6492
7952
  termsHash: input.termsHash,
6493
7953
  recipient: input.recipient,
6494
- accessMode: input.accessMode
7954
+ accessMode: input.accessMode,
7955
+ ...guardedFields
6495
7956
  });
6496
7957
  }
7958
+ const readSharedObject = deps.readSharedObject ?? sharedObjectReaderForTests ?? chainSharedObjectReader(cfg, input.packageId);
7959
+ const versions = await resolveSharedObjectVersionsFromGetObject(readSharedObject, {
7960
+ poolObjectId: input.poolObjectId,
7961
+ grantObjectId: input.grantObjectId,
7962
+ clockObjectId: SUI_CLOCK3
7963
+ });
6497
7964
  if (isOpen) {
6498
7965
  return buildSpendAccountSettleOpenRecipientKind({
6499
7966
  packageId: input.packageId,
@@ -6501,13 +7968,15 @@ async function prepareSpendAccountPayment(args) {
6501
7968
  coinType: input.coinType,
6502
7969
  poolObjectId: input.poolObjectId,
6503
7970
  grantObjectId: input.grantObjectId,
6504
- poolInitialSharedVersion: "1",
6505
- grantInitialSharedVersion: "1",
7971
+ poolInitialSharedVersion: versions.poolInitialSharedVersion,
7972
+ grantInitialSharedVersion: versions.grantInitialSharedVersion,
7973
+ clockInitialSharedVersion: versions.clockInitialSharedVersion,
6506
7974
  recipient: input.recipient,
6507
7975
  targetHash: input.targetHash,
6508
7976
  amount: input.amount,
6509
7977
  paymentIdHash: input.paymentIdHash,
6510
- termsHash: input.termsHash
7978
+ termsHash: input.termsHash,
7979
+ ...guardedFields
6511
7980
  });
6512
7981
  }
6513
7982
  return buildSpendAccountPayKind2({
@@ -6520,7 +7989,11 @@ async function prepareSpendAccountPayment(args) {
6520
7989
  targetHash: input.targetHash,
6521
7990
  amount: input.amount,
6522
7991
  paymentIdHash: input.paymentIdHash,
6523
- termsHash: input.termsHash
7992
+ termsHash: input.termsHash,
7993
+ poolInitialSharedVersion: versions.poolInitialSharedVersion,
7994
+ grantInitialSharedVersion: versions.grantInitialSharedVersion,
7995
+ clockInitialSharedVersion: versions.clockInitialSharedVersion,
7996
+ ...guardedFields
6524
7997
  });
6525
7998
  };
6526
7999
  const rawFetch = deps.fetchImpl ?? fetch;
@@ -6540,10 +8013,20 @@ async function prepareSpendAccountPayment(args) {
6540
8013
  authority: session.snapshot,
6541
8014
  assertLive: session.assertLive
6542
8015
  };
8016
+ const directorySwitch = cfg.exclusionDirectoryObjectId ? cfg.exclusionDirectoryObjectId : deps.resolveExclusionRegistry ? null : await publishedExclusionDirectoryId(cfg.gatewayUrl);
8017
+ const resolveExclusionRegistry2 = deps.resolveExclusionRegistry ?? (directorySwitch ? (() => {
8018
+ const reader = createGrpcExclusionRegistryReader(rpcClient(cfg));
8019
+ return async ({ directoryId, recipient }) => resolveExclusionRegistry({
8020
+ directoryObjectId: directoryId,
8021
+ recipient,
8022
+ reader
8023
+ });
8024
+ })() : void 0);
6543
8025
  const resolveDeps = {
6544
8026
  fetchImpl: wrappedFetch,
6545
8027
  preferredDialect: preferred,
6546
- buildPayKind
8028
+ buildPayKind,
8029
+ ...resolveExclusionRegistry2 ? { resolveExclusionRegistry: resolveExclusionRegistry2 } : {}
6547
8030
  };
6548
8031
  const result = await resolvePayment(resolveInput, resolveDeps);
6549
8032
  if (result.status === "unpaid_ok") {
@@ -6604,7 +8087,7 @@ async function prepareSpendAccountPayment(args) {
6604
8087
  const signingRequest = {
6605
8088
  unsignedKindBytes: kindBytes,
6606
8089
  delegateAddress: resolved.delegateAddress,
6607
- packageId: normalizeSuiAddress11(authority.packageId),
8090
+ packageId: normalizeSuiAddress14(authority.packageId),
6608
8091
  poolObjectId: resolved.poolObjectId,
6609
8092
  grantObjectId: resolved.grantObjectId,
6610
8093
  policyId: resolved.onChainPolicyId,
@@ -6686,11 +8169,11 @@ function mapErrorCode(code) {
6686
8169
  }
6687
8170
 
6688
8171
  // src/discover-afford.ts
6689
- import { normalizeStructTag as normalizeStructTag6 } from "@mysten/sui/utils";
8172
+ import { normalizeStructTag as normalizeStructTag9 } from "@mysten/sui/utils";
6690
8173
  function sameCoinType2(rowAsset, grantCoin) {
6691
8174
  if (typeof rowAsset !== "string" || !grantCoin) return false;
6692
8175
  try {
6693
- return normalizeStructTag6(rowAsset) === normalizeStructTag6(grantCoin);
8176
+ return normalizeStructTag9(rowAsset) === normalizeStructTag9(grantCoin);
6694
8177
  } catch {
6695
8178
  return false;
6696
8179
  }
@@ -6872,6 +8355,59 @@ function joinDiscoverCatalog(opts) {
6872
8355
  return scoped ? { accessMode: "scoped", count: services.length, services, allowedTargetHashes } : { accessMode: "open", count: services.length, services };
6873
8356
  }
6874
8357
 
8358
+ // src/tool-result.ts
8359
+ import { pathToFileURL } from "url";
8360
+
8361
+ // src/delivery-file.ts
8362
+ import { mkdirSync as mkdirSync2, writeFileSync, chmodSync as chmodSync2 } from "fs";
8363
+ import { dirname as dirname2, join as join3 } from "path";
8364
+ function mcpDeliveriesDir() {
8365
+ return join3(dirname2(mcpCredentialsPath()), "deliveries");
8366
+ }
8367
+ var EXT = {
8368
+ "image/png": ".png",
8369
+ "image/jpeg": ".jpg",
8370
+ "image/jpg": ".jpg",
8371
+ "image/webp": ".webp",
8372
+ "image/gif": ".gif",
8373
+ "image/svg+xml": ".svg",
8374
+ "audio/mpeg": ".mp3",
8375
+ "audio/wav": ".wav",
8376
+ "audio/ogg": ".ogg",
8377
+ "video/mp4": ".mp4",
8378
+ "application/pdf": ".pdf",
8379
+ "application/json": ".json",
8380
+ "text/plain": ".txt"
8381
+ };
8382
+ function extensionFor(contentType) {
8383
+ const type = contentType.split(";")[0]?.trim().toLowerCase() ?? "";
8384
+ return EXT[type] ?? ".bin";
8385
+ }
8386
+ function fileStem(digest, challengeId) {
8387
+ const raw = (digest ?? challengeId ?? `delivery-${Date.now()}`).trim();
8388
+ const safe = raw.replace(/[^A-Za-z0-9._-]+/g, "-").replace(/^-+|-+$/g, "");
8389
+ return (safe.slice(0, 80) || `delivery-${Date.now()}`).replace(/^\.+$/, "delivery");
8390
+ }
8391
+ function saveDeliveredBody(input) {
8392
+ if (input.bytes.byteLength === 0) return void 0;
8393
+ const dir = mcpDeliveriesDir();
8394
+ mkdirSync2(dir, { recursive: true, mode: 448 });
8395
+ try {
8396
+ chmodSync2(dir, 448);
8397
+ } catch {
8398
+ }
8399
+ const path = join3(
8400
+ dir,
8401
+ `${fileStem(input.digest, input.challengeId)}${extensionFor(input.contentType)}`
8402
+ );
8403
+ writeFileSync(path, input.bytes, { mode: 384 });
8404
+ try {
8405
+ chmodSync2(path, 384);
8406
+ } catch {
8407
+ }
8408
+ return path;
8409
+ }
8410
+
6875
8411
  // src/tool-result.ts
6876
8412
  var MAX_MCP_IMAGE_BYTES = 5 * 1024 * 1024;
6877
8413
  function text(value) {
@@ -6893,6 +8429,12 @@ function closedError(code, detail) {
6893
8429
  function closedPayError(code, detail) {
6894
8430
  return error({ status: "failed", paid: false, code, detail });
6895
8431
  }
8432
+ function receiptChallengeId(receipt) {
8433
+ if (!receipt || typeof receipt !== "object") return void 0;
8434
+ const id = receipt.challengeId;
8435
+ return typeof id === "string" && id.length > 0 ? id : void 0;
8436
+ }
8437
+ var MCP_SAVE_ERROR = "delivery file was not written; do not call pay again";
6896
8438
  function paidResult(result) {
6897
8439
  if (!isBinaryBody(result.body)) return text(result);
6898
8440
  const body = result.body;
@@ -6901,8 +8443,33 @@ function paidResult(result) {
6901
8443
  contentType: body.contentType,
6902
8444
  byteLength: body.byteLength
6903
8445
  };
8446
+ let savedTo;
8447
+ try {
8448
+ savedTo = saveDeliveredBody({
8449
+ bytes: body.bytes,
8450
+ contentType: body.contentType,
8451
+ digest: result.txDigest,
8452
+ challengeId: receiptChallengeId(result.receipt)
8453
+ });
8454
+ } catch {
8455
+ savedTo = void 0;
8456
+ }
8457
+ const payload = savedTo ? {
8458
+ savedTo,
8459
+ savedToUrl: pathToFileURL(savedTo).href,
8460
+ ...result,
8461
+ body: shape
8462
+ } : {
8463
+ savedTo: null,
8464
+ saveError: MCP_SAVE_ERROR,
8465
+ ...result,
8466
+ body: shape
8467
+ };
6904
8468
  const content = [
6905
- { type: "text", text: JSON.stringify({ ...result, body: shape }, null, 2) }
8469
+ {
8470
+ type: "text",
8471
+ text: JSON.stringify(payload, null, 2)
8472
+ }
6906
8473
  ];
6907
8474
  if (body.contentType.startsWith("image/") && body.bytes.byteLength > 0 && body.bytes.byteLength <= MAX_MCP_IMAGE_BYTES) {
6908
8475
  content.push({
@@ -6918,6 +8485,205 @@ function settlePayResult(result) {
6918
8485
  return error(result);
6919
8486
  }
6920
8487
 
8488
+ // src/pay-file.ts
8489
+ import { isAbsolute, dirname as dirname3, sep } from "path";
8490
+ import {
8491
+ openSync as openSync2,
8492
+ closeSync as closeSync2,
8493
+ fstatSync,
8494
+ readFileSync as readFileSync2,
8495
+ realpathSync,
8496
+ constants
8497
+ } from "fs";
8498
+ var JSON_WRAP_SLACK_BYTES = 256;
8499
+ var MCP_MAX_FILE_FIELD_BYTES = Math.floor(
8500
+ (MAX_PAID_BODY_BYTES - JSON_WRAP_SLACK_BYTES) * 3 / 4
8501
+ );
8502
+ var FIELD = /^[A-Za-z][A-Za-z0-9_]{0,63}$/;
8503
+ var FORBIDDEN = /* @__PURE__ */ new Set(["constructor", "prototype"]);
8504
+ function fail2(code, detail) {
8505
+ return { code, detail };
8506
+ }
8507
+ function hasLocalFileArgs(input) {
8508
+ return input.file !== void 0 && input.file !== null || input.fileField !== void 0 && input.fileField !== null;
8509
+ }
8510
+ function parseField(value) {
8511
+ if (typeof value !== "string" || !FIELD.test(value) || FORBIDDEN.has(value)) {
8512
+ return fail2("INVALID_FILE_FIELD", "fileField must be a simple JSON key");
8513
+ }
8514
+ return value;
8515
+ }
8516
+ function parsePath(value) {
8517
+ if (typeof value !== "string" || value.length === 0 || value.includes("\0")) {
8518
+ return fail2("INVALID_FILE", "file must be a non-empty absolute path");
8519
+ }
8520
+ if (!isAbsolute(value) || value.includes("://")) {
8521
+ return fail2("INVALID_FILE", "file must be an absolute filesystem path, not a URL");
8522
+ }
8523
+ return value;
8524
+ }
8525
+ function parseMethod(value) {
8526
+ if (value === void 0 || value === null) return "GET";
8527
+ if (typeof value !== "string") {
8528
+ return fail2("MALFORMED_REQUEST", "method must be GET or POST");
8529
+ }
8530
+ const upper = value.trim().toUpperCase();
8531
+ if (upper === "GET" || upper === "POST") return upper;
8532
+ return fail2("MALFORMED_REQUEST", "method must be GET or POST");
8533
+ }
8534
+ function inside(root, candidate) {
8535
+ const prefix = root.endsWith(sep) ? root : root + sep;
8536
+ return candidate === root || candidate.startsWith(prefix);
8537
+ }
8538
+ function resolveContainedPath(raw) {
8539
+ const rootRaw = process.env.SUIPAY_MCP_FILE_ROOT;
8540
+ if (typeof rootRaw !== "string" || rootRaw.trim() === "") {
8541
+ return fail2(
8542
+ "FILE_ROOT_UNSET",
8543
+ "file attach requires SUIPAY_MCP_FILE_ROOT to be an absolute directory"
8544
+ );
8545
+ }
8546
+ if (!isAbsolute(rootRaw) || rootRaw.includes("://")) {
8547
+ return fail2(
8548
+ "FILE_ROOT_UNSET",
8549
+ "SUIPAY_MCP_FILE_ROOT must be an absolute filesystem path"
8550
+ );
8551
+ }
8552
+ let rootReal;
8553
+ let fileReal;
8554
+ try {
8555
+ rootReal = realpathSync(rootRaw);
8556
+ } catch {
8557
+ return fail2("FILE_ROOT_UNSET", "SUIPAY_MCP_FILE_ROOT is not a readable directory");
8558
+ }
8559
+ try {
8560
+ fileReal = realpathSync(raw);
8561
+ } catch (err) {
8562
+ const code = err.code;
8563
+ if (code === "ENOENT" || code === "ENOTDIR") {
8564
+ return fail2("FILE_NOT_FOUND", "file does not exist");
8565
+ }
8566
+ if (code === "EACCES") {
8567
+ return fail2("FILE_EACCES", "file is not readable");
8568
+ }
8569
+ return fail2(
8570
+ "INVALID_FILE",
8571
+ `file could not be read (${code ?? "unknown error"})`
8572
+ );
8573
+ }
8574
+ if (!inside(rootReal, fileReal)) {
8575
+ return fail2("FILE_OUTSIDE_ROOT", "file must be under SUIPAY_MCP_FILE_ROOT");
8576
+ }
8577
+ const credsDir2 = dirname3(mcpCredentialsPath());
8578
+ let credsReal = credsDir2;
8579
+ try {
8580
+ credsReal = realpathSync(credsDir2);
8581
+ } catch {
8582
+ }
8583
+ if (inside(credsReal, fileReal)) {
8584
+ return fail2("FILE_FORBIDDEN", "file must not be under ~/.suipay");
8585
+ }
8586
+ return fileReal;
8587
+ }
8588
+ function readRegularFile(path) {
8589
+ if (constants.O_NOFOLLOW === void 0 || constants.O_NONBLOCK === void 0) {
8590
+ return fail2("INVALID_FILE", "symlink-safe nonblocking open is unavailable on this platform");
8591
+ }
8592
+ let fd;
8593
+ try {
8594
+ const flags = constants.O_RDONLY | constants.O_NOFOLLOW | constants.O_NONBLOCK;
8595
+ fd = openSync2(path, flags);
8596
+ const st = fstatSync(fd);
8597
+ if (!st.isFile()) {
8598
+ return fail2("INVALID_FILE", "file must be a regular file");
8599
+ }
8600
+ if (st.size > MCP_MAX_FILE_FIELD_BYTES) {
8601
+ return fail2(
8602
+ "FILE_TOO_LARGE",
8603
+ `file is ${st.size} bytes; exceeds ${MCP_MAX_FILE_FIELD_BYTES} decoded bytes`
8604
+ );
8605
+ }
8606
+ const bytes = readFileSync2(fd);
8607
+ if (bytes.byteLength === 0) {
8608
+ return fail2("EMPTY_FILE", "file is empty");
8609
+ }
8610
+ if (bytes.byteLength > MCP_MAX_FILE_FIELD_BYTES) {
8611
+ return fail2(
8612
+ "FILE_TOO_LARGE",
8613
+ `file is ${bytes.byteLength} bytes; exceeds ${MCP_MAX_FILE_FIELD_BYTES} decoded bytes`
8614
+ );
8615
+ }
8616
+ return bytes;
8617
+ } catch (err) {
8618
+ const code = err.code;
8619
+ if (code === "ENOENT" || code === "ENOTDIR") {
8620
+ return fail2("FILE_NOT_FOUND", "file does not exist");
8621
+ }
8622
+ if (code === "ELOOP" || code === "EMLINK" || code === "ENXIO") {
8623
+ return fail2("INVALID_FILE", "file must be a regular file");
8624
+ }
8625
+ if (code === "EACCES") return fail2("FILE_EACCES", "file is not readable");
8626
+ return fail2(
8627
+ "INVALID_FILE",
8628
+ `file could not be read (${code ?? "unknown error"})`
8629
+ );
8630
+ } finally {
8631
+ if (fd !== void 0) {
8632
+ try {
8633
+ closeSync2(fd);
8634
+ } catch {
8635
+ }
8636
+ }
8637
+ }
8638
+ }
8639
+ function toCanonicalBase64(bytes) {
8640
+ return Buffer.from(bytes).toString("base64");
8641
+ }
8642
+ function jsonWithLocalFile(input) {
8643
+ const hasFile = input.file !== void 0 && input.file !== null;
8644
+ const hasField = input.fileField !== void 0 && input.fileField !== null;
8645
+ if (!hasFile && !hasField) return { json: input.json };
8646
+ if (hasFile !== hasField) {
8647
+ return {
8648
+ error: fail2(
8649
+ "INVALID_FILE_ARGS",
8650
+ "file and fileField are required together"
8651
+ )
8652
+ };
8653
+ }
8654
+ const method = parseMethod(input.method);
8655
+ if (typeof method !== "string") return { error: method };
8656
+ if (method !== "POST") {
8657
+ return { error: fail2("INVALID_FILE_ARGS", "file is only valid on POST") };
8658
+ }
8659
+ const field = parseField(input.fileField);
8660
+ if (typeof field !== "string") return { error: field };
8661
+ const path = parsePath(input.file);
8662
+ if (typeof path !== "string") return { error: path };
8663
+ let json;
8664
+ if (input.json === void 0) {
8665
+ json = {};
8666
+ } else if (typeof input.json === "object" && input.json !== null && !Array.isArray(input.json)) {
8667
+ json = { ...input.json };
8668
+ } else {
8669
+ return { error: fail2("MALFORMED_REQUEST", "POST requires json object") };
8670
+ }
8671
+ if (Object.prototype.hasOwnProperty.call(json, field)) {
8672
+ return {
8673
+ error: fail2(
8674
+ "FILE_FIELD_EXISTS",
8675
+ "json already has fileField; refuse to overwrite"
8676
+ )
8677
+ };
8678
+ }
8679
+ const contained = resolveContainedPath(path);
8680
+ if (typeof contained !== "string") return { error: contained };
8681
+ const bytes = readRegularFile(contained);
8682
+ if (!(bytes instanceof Uint8Array)) return { error: bytes };
8683
+ json[field] = toCanonicalBase64(bytes);
8684
+ return { json };
8685
+ }
8686
+
6921
8687
  // src/tools.ts
6922
8688
  function payerTrace(deps) {
6923
8689
  const hooks = deps.trace;
@@ -6954,7 +8720,7 @@ function normalizePayIntent(args) {
6954
8720
  if (!/^0x[0-9a-fA-F]{1,64}$/.test(raw)) {
6955
8721
  throw new Error("recipient must be a 0x-prefixed Sui address");
6956
8722
  }
6957
- const normalized = normalizeSuiAddress12(raw);
8723
+ const normalized = normalizeSuiAddress15(raw);
6958
8724
  if (!isValidSuiAddress(normalized) || /^0x0+$/.test(normalized)) {
6959
8725
  throw new Error("recipient must be a non-zero Sui address");
6960
8726
  }
@@ -6973,7 +8739,7 @@ function intentViolation(intent, prepared) {
6973
8739
  if (intent.recipient) {
6974
8740
  let preparedRecipient;
6975
8741
  try {
6976
- preparedRecipient = normalizeSuiAddress12(prepared.recipient);
8742
+ preparedRecipient = normalizeSuiAddress15(prepared.recipient);
6977
8743
  } catch {
6978
8744
  return "prepared payment does not name a valid recipient";
6979
8745
  }
@@ -6996,18 +8762,34 @@ function intentViolation(intent, prepared) {
6996
8762
  }
6997
8763
  async function pay(args, cfg, deps = {}) {
6998
8764
  if (!args.url) return error("url is required");
8765
+ if (hasLocalFileArgs(args) && deps.transport === "http") {
8766
+ return closedPayError(
8767
+ "FILE_STDIO_ONLY",
8768
+ "file is stdio only; hosted /mcp does not read the buyer disk"
8769
+ );
8770
+ }
6999
8771
  let intent;
7000
8772
  try {
7001
8773
  intent = normalizePayIntent(args);
7002
8774
  } catch {
7003
8775
  return closedPayError("INVALID_INTENT", "payment intent is invalid");
7004
8776
  }
8777
+ let attached;
8778
+ try {
8779
+ attached = jsonWithLocalFile(args);
8780
+ } catch {
8781
+ return closedPayError("INVALID_FILE", "file could not be read");
8782
+ }
8783
+ if ("error" in attached) {
8784
+ return closedPayError(attached.error.code, attached.error.detail);
8785
+ }
8786
+ const paidArgs = { ...args, json: attached.json };
7005
8787
  let request;
7006
8788
  try {
7007
- request = normalizeMcpPaidHttpRequest(args);
8789
+ request = normalizeMcpPaidHttpRequest(paidArgs);
7008
8790
  } catch (err) {
7009
8791
  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";
8792
+ 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
8793
  return closedPayError("MALFORMED_REQUEST", detail);
7012
8794
  }
7013
8795
  if (deps.auth?.spendAccountPay) {
@@ -7032,6 +8814,7 @@ async function pay(args, cfg, deps = {}) {
7032
8814
  deps: {
7033
8815
  ...deps.fetchImpl ? { fetchImpl: deps.fetchImpl } : {},
7034
8816
  ...trace ? { trace } : {},
8817
+ ...deps.resolveExclusionRegistry ? { resolveExclusionRegistry: deps.resolveExclusionRegistry } : {},
7035
8818
  ...deps.buildPayKind ? { buildPayKind: deps.buildPayKind } : liveKindBuilder ? {
7036
8819
  buildPayKind: (input) => sp.snapshot.accessMode === "open" && input.recipient ? liveKindBuilder.buildSettleOpenRecipientKind({
7037
8820
  ...input,
@@ -7451,10 +9234,15 @@ async function suipayLogin(args, cfg, deps = {}) {
7451
9234
  });
7452
9235
  }
7453
9236
  if (live.kind === "unknown") {
7454
- return error({
7455
- status: "failed",
9237
+ return text({
9238
+ status: "ok",
9239
+ reused: true,
9240
+ grantStatus: "unknown",
7456
9241
  code: "GRANT_STATUS_UNKNOWN",
7457
- detail: "Could not verify the existing grant. Credentials were not modified. Retry later; do not start a new consent (that would mint a second grant)."
9242
+ target: target ?? cfg.target,
9243
+ delegateAddress: identity.delegateAddress,
9244
+ gatewayUrl: endpoints.gatewayUrl,
9245
+ 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
9246
  });
7459
9247
  }
7460
9248
  }
@@ -7464,13 +9252,13 @@ async function suipayLogin(args, cfg, deps = {}) {
7464
9252
  identity,
7465
9253
  label,
7466
9254
  timeoutMs: LOGIN_BG_TIMEOUT_MS,
7467
- openBrowser: false,
9255
+ openBrowser: true,
7468
9256
  onUrl: deps.onUrl
7469
9257
  });
7470
9258
  const url = started.connectUrl;
7471
9259
  return text(
7472
9260
  [
7473
- `## ACTION REQUIRED: User must click this URL to sign in`,
9261
+ `## ACTION REQUIRED: The sign-in page was opened in the browser`,
7474
9262
  ``,
7475
9263
  `**Target:** ${target ?? cfg.target ?? "custom"} \xB7 ${endpoints.gatewayUrl}`,
7476
9264
  ``,
@@ -7482,11 +9270,13 @@ async function suipayLogin(args, cfg, deps = {}) {
7482
9270
  ``,
7483
9271
  `[Click here to open SuiPay sign-in](${url})`,
7484
9272
  ``,
7485
- `**IMPORTANT for the assistant**: do NOT summarize or omit the URL above.`,
7486
- `The user CANNOT proceed without seeing the exact URL. Surface it verbatim.`,
9273
+ `**IMPORTANT for the assistant**: the browser was already opened to this exact URL.`,
9274
+ `Do NOT navigate to /connect/agent yourself. That path without these query`,
9275
+ `parameters shows "Authorization link incomplete". If the page did not`,
9276
+ `appear, surface the URL above verbatim and do not shorten it.`,
7487
9277
  ``,
7488
- `1. Open the URL in any browser`,
7489
- `2. Connect the owner wallet and approve the spend-account grant (Access keys / policies)`,
9278
+ `1. Use the browser window that just opened, or open the URL above`,
9279
+ `2. Connect the owner wallet, choose or create a key, then Authorize on the review step`,
7490
9280
  `3. Once Connected appears, retry the original request \u2014 credentials at \`~/.suipay/credentials.json\` are picked up on the next tool call`,
7491
9281
  ``,
7492
9282
  `_The login link stays valid for 5 minutes. If it expires, call \`suipay_login\` again._`,
@@ -7526,7 +9316,7 @@ var TOOLS = [
7526
9316
  },
7527
9317
  {
7528
9318
  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.",
9319
+ 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
9320
  inputSchema: {
7531
9321
  type: "object",
7532
9322
  properties: {
@@ -7540,6 +9330,14 @@ var TOOLS = [
7540
9330
  type: "object",
7541
9331
  description: "JSON request body for a POST. Sent as application/json and bound to the payment."
7542
9332
  },
9333
+ file: {
9334
+ type: "string",
9335
+ 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."
9336
+ },
9337
+ fileField: {
9338
+ type: "string",
9339
+ 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."
9340
+ },
7543
9341
  recipient: {
7544
9342
  type: "string",
7545
9343
  description: "Sui address you expect to be paid. The payment is refused, unsigned, if the prepared transaction pays anyone else."
@@ -7647,6 +9445,7 @@ function toolError(value) {
7647
9445
  }
7648
9446
  function createServer2(cfg, auth, trace, options = {}) {
7649
9447
  const resolveAgentHeld = options.resolveAgentHeld ?? resolveAgentHeldSettlement;
9448
+ const transport = options.transport ?? "stdio";
7650
9449
  const server = new Server(
7651
9450
  { name: "suipay", version: "1.0.0" },
7652
9451
  {
@@ -7673,19 +9472,32 @@ function createServer2(cfg, auth, trace, options = {}) {
7673
9472
  return await accessContext({ auth }, cfg);
7674
9473
  case "pay": {
7675
9474
  const agentHeld = auth?.spendAccountPay ? resolveAgentHeld(cfg, auth.spendAccountPay) : null;
9475
+ const fileArgs = {
9476
+ ...args.file !== void 0 ? { file: args.file } : {},
9477
+ ...args.fileField !== void 0 ? { fileField: args.fileField } : {}
9478
+ };
9479
+ if (transport === "http" && hasLocalFileArgs(fileArgs)) {
9480
+ return closedPayError(
9481
+ "FILE_STDIO_ONLY",
9482
+ "file is stdio only; hosted /mcp does not read the buyer disk"
9483
+ );
9484
+ }
7676
9485
  return await pay(
7677
9486
  {
7678
9487
  url: String(args.url ?? ""),
7679
9488
  ...args.method !== void 0 ? { method: args.method } : {},
7680
9489
  ...args.json !== void 0 ? { json: args.json } : {},
9490
+ ...transport === "stdio" ? fileArgs : {},
7681
9491
  ...args.recipient !== void 0 ? { recipient: args.recipient } : {},
7682
9492
  ...args.maxAmount !== void 0 ? { maxAmount: args.maxAmount } : {}
7683
9493
  },
7684
9494
  cfg,
7685
9495
  {
7686
9496
  auth,
9497
+ transport,
7687
9498
  ...trace ? { trace } : {},
7688
- ...agentHeld ? { agentHeld } : {}
9499
+ ...agentHeld ? { agentHeld } : {},
9500
+ ...options.resolveExclusionRegistry ? { resolveExclusionRegistry: options.resolveExclusionRegistry } : {}
7689
9501
  }
7690
9502
  );
7691
9503
  }
@@ -7772,6 +9584,11 @@ function canonicalMcpRequestId(id) {
7772
9584
  function asRecord(value) {
7773
9585
  return typeof value === "object" && value !== null && !Array.isArray(value) ? value : null;
7774
9586
  }
9587
+ function redactPayFileArg(args) {
9588
+ const record = asRecord(args);
9589
+ if (!record || !("file" in record)) return args;
9590
+ return { ...record, file: "[redacted]" };
9591
+ }
7775
9592
  function readEntry(message) {
7776
9593
  const record = asRecord(message);
7777
9594
  if (!record) return null;
@@ -7783,7 +9600,7 @@ function readEntry(message) {
7783
9600
  jsonRpcId: typeof rawId === "string" || typeof rawId === "number" ? rawId : null,
7784
9601
  requestId: canonicalMcpRequestId(rawId),
7785
9602
  toolName: params && typeof params.name === "string" ? params.name : null,
7786
- toolArguments: params ? params.arguments : void 0,
9603
+ toolArguments: params ? redactPayFileArg(params.arguments) : void 0,
7787
9604
  clientName: clientInfo && typeof clientInfo.name === "string" ? clientInfo.name : null,
7788
9605
  clientVersion: clientInfo && typeof clientInfo.version === "string" ? clientInfo.version : null,
7789
9606
  protocolVersion: params && typeof params.protocolVersion === "string" ? params.protocolVersion : null
@@ -8213,7 +10030,10 @@ async function handleSuipayMcpHttpRequest(req, cfg, auth, trace, options) {
8213
10030
  sessionIdGenerator: void 0,
8214
10031
  enableJsonResponse: true
8215
10032
  });
8216
- const server = createServer2(cfg, auth, hooks, options);
10033
+ const server = createServer2(cfg, auth, hooks, {
10034
+ ...options,
10035
+ transport: "http"
10036
+ });
8217
10037
  await server.connect(transport);
8218
10038
  return transport.handleRequest(request);
8219
10039
  }