@cmdoss/suipay-mcp 0.2.2-dev.6 → 0.2.2-dev.8

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) {
@@ -636,7 +636,7 @@ function createSpendAccountChain(options) {
636
636
  const tx = new Transaction2();
637
637
  tx.setSender(input.owner);
638
638
  tx.moveCall({
639
- target: target("withdraw"),
639
+ target: target("withdraw_to_owner"),
640
640
  typeArguments: [input.coinType],
641
641
  arguments: [
642
642
  tx.object(input.poolObjectId),
@@ -823,24 +823,27 @@ function createSpendAccountChain(options) {
823
823
  },
824
824
  async buildSettleOpenRecipientKind(input) {
825
825
  if (!input.termsHash || input.termsHash.length === 0) {
826
- throw new Error("termsHash must be non-empty for spend_account::settle_open_recipient_payment");
826
+ throw new Error("termsHash must be non-empty for spend_account::settle_any_recipient");
827
827
  }
828
828
  if (!input.paymentIdHash || input.paymentIdHash.length === 0) {
829
- throw new Error("paymentIdHash must be non-empty for spend_account::settle_open_recipient_payment");
829
+ throw new Error("paymentIdHash must be non-empty for spend_account::settle_any_recipient");
830
830
  }
831
831
  if (!input.recipient || isZeroAddress(input.recipient)) {
832
- throw new Error("settle_open_recipient_payment recipient must be a non-zero address");
832
+ throw new Error("settle_any_recipient recipient must be a non-zero address");
833
833
  }
834
834
  if (!input.amount || BigInt(input.amount) <= 0n) {
835
- throw new Error("settle_open_recipient_payment amount must be > 0");
835
+ throw new Error("settle_any_recipient amount must be > 0");
836
836
  }
837
+ const guarded = resolveGuardedInputs(input, "settle_any_recipient");
837
838
  const hash = targetHashBytes(input.targetHash);
838
839
  const tx = new Transaction2();
839
840
  tx.setSender(input.delegate);
840
841
  tx.moveCall({
841
- target: target("settle_open_recipient_payment"),
842
+ target: target("settle_any_recipient"),
842
843
  typeArguments: [input.coinType],
843
844
  arguments: [
845
+ tx.object(guarded.exclusionDirectoryId),
846
+ tx.object(guarded.recipientRegistryId),
844
847
  tx.object(input.poolObjectId),
845
848
  tx.object(input.grantObjectId),
846
849
  tx.pure.address(input.recipient),
@@ -848,6 +851,7 @@ function createSpendAccountChain(options) {
848
851
  tx.pure.vector("u8", Array.from(input.paymentIdHash)),
849
852
  tx.pure.vector("u8", Array.from(input.termsHash)),
850
853
  tx.pure.vector("u8", Array.from(hash)),
854
+ tx.pure.u64(guarded.expiresAtMs),
851
855
  tx.object(input.clockId ?? SUI_CLOCK)
852
856
  ]
853
857
  });
@@ -858,6 +862,7 @@ function createSpendAccountChain(options) {
858
862
  tx.setSender(input.owner);
859
863
  tx.moveCall({
860
864
  target: target("pause_grant"),
865
+ typeArguments: [input.coinType],
861
866
  arguments: [tx.object(input.grantObjectId)]
862
867
  });
863
868
  return kindBytes(tx);
@@ -867,6 +872,7 @@ function createSpendAccountChain(options) {
867
872
  tx.setSender(input.owner);
868
873
  tx.moveCall({
869
874
  target: target("resume_grant"),
875
+ typeArguments: [input.coinType],
870
876
  arguments: [tx.object(input.grantObjectId)]
871
877
  });
872
878
  return kindBytes(tx);
@@ -876,6 +882,7 @@ function createSpendAccountChain(options) {
876
882
  tx.setSender(input.owner);
877
883
  tx.moveCall({
878
884
  target: target("revoke_grant"),
885
+ typeArguments: [input.coinType],
879
886
  arguments: [tx.object(input.grantObjectId)]
880
887
  });
881
888
  return kindBytes(tx);
@@ -944,30 +951,57 @@ function createSpendAccountChain(options) {
944
951
  const tx = new Transaction2();
945
952
  tx.setSender(input.owner);
946
953
  const expires = input.newExpiresAtMs === void 0 || input.newExpiresAtMs === null ? 0n : BigInt(input.newExpiresAtMs);
954
+ const totalBudget = input.newTotalBudget === null || input.newTotalBudget === void 0 ? null : BigInt(input.newTotalBudget);
947
955
  tx.moveCall({
948
956
  target: target("tighten_grant_limits"),
957
+ // Grant<T> is generic; every sibling grant builder passes the coin type.
958
+ typeArguments: [input.coinType],
949
959
  arguments: [
950
960
  tx.object(input.grantObjectId),
951
- tx.pure.u64(BigInt(input.newSessionCap)),
961
+ tx.pure.option("u64", totalBudget),
952
962
  tx.pure.u64(expires)
953
963
  ]
954
964
  });
955
965
  return kindBytes(tx);
956
966
  },
967
+ async readGrantTotalSpent(grantObjectId) {
968
+ try {
969
+ const { object } = await client.core.getObject({
970
+ objectId: grantObjectId,
971
+ include: { json: true }
972
+ });
973
+ const json = object?.json;
974
+ if (!json || typeof json !== "object")
975
+ return null;
976
+ const spent = json.total_spent;
977
+ if (typeof spent === "string" && /^\d+$/.test(spent))
978
+ return BigInt(spent);
979
+ if (typeof spent === "number" && Number.isFinite(spent))
980
+ return BigInt(Math.trunc(spent));
981
+ if (typeof spent === "bigint")
982
+ return spent;
983
+ return null;
984
+ } catch {
985
+ return null;
986
+ }
987
+ },
957
988
  async buildPayKind(input) {
958
989
  if (!input.termsHash || input.termsHash.length === 0) {
959
- throw new Error("termsHash must be non-empty for spend_account::settle_policy_payment");
990
+ throw new Error("termsHash must be non-empty for spend_account::settle_policy");
960
991
  }
961
992
  if (!input.paymentIdHash || input.paymentIdHash.length === 0) {
962
- throw new Error("paymentIdHash must be non-empty for spend_account::settle_policy_payment");
993
+ throw new Error("paymentIdHash must be non-empty for spend_account::settle_policy");
963
994
  }
995
+ const guarded = resolveGuardedInputs(input, "settle_policy");
964
996
  const hash = targetHashBytes(input.targetHash);
965
997
  const tx = new Transaction2();
966
998
  tx.setSender(input.delegate);
967
999
  tx.moveCall({
968
- target: target("settle_policy_payment"),
1000
+ target: target("settle_policy"),
969
1001
  typeArguments: [input.coinType],
970
1002
  arguments: [
1003
+ tx.object(guarded.exclusionDirectoryId),
1004
+ tx.object(guarded.recipientRegistryId),
971
1005
  tx.object(input.poolObjectId),
972
1006
  tx.object(input.grantObjectId),
973
1007
  tx.pure.u64(BigInt(input.policyId)),
@@ -975,6 +1009,7 @@ function createSpendAccountChain(options) {
975
1009
  tx.pure.u64(BigInt(input.amount)),
976
1010
  tx.pure.vector("u8", Array.from(input.paymentIdHash)),
977
1011
  tx.pure.vector("u8", Array.from(input.termsHash)),
1012
+ tx.pure.u64(guarded.expiresAtMs),
978
1013
  tx.object(input.clockId ?? SUI_CLOCK)
979
1014
  ]
980
1015
  });
@@ -982,6 +1017,28 @@ function createSpendAccountChain(options) {
982
1017
  }
983
1018
  };
984
1019
  }
1020
+ function resolveGuardedInputs(input, fn) {
1021
+ const present = [
1022
+ input.exclusionDirectoryId,
1023
+ input.recipientRegistryId,
1024
+ input.expiresAtMs
1025
+ ].filter((v) => v !== void 0).length;
1026
+ if (present !== 3) {
1027
+ throw new Error(`${fn} on the fresh lineage requires exclusionDirectoryId, recipientRegistryId and expiresAtMs (mandatory guarded settlement \u2014 there is no unguarded twin)`);
1028
+ }
1029
+ const expires = BigInt(input.expiresAtMs);
1030
+ if (expires <= 0n) {
1031
+ throw new Error(`guarded ${fn} expiresAtMs must be a positive epoch-ms value`);
1032
+ }
1033
+ if (isZeroAddress(input.exclusionDirectoryId) || isZeroAddress(input.recipientRegistryId)) {
1034
+ throw new Error(`guarded ${fn} directory and registry ids must be non-zero`);
1035
+ }
1036
+ return {
1037
+ exclusionDirectoryId: input.exclusionDirectoryId,
1038
+ recipientRegistryId: input.recipientRegistryId,
1039
+ expiresAtMs: expires
1040
+ };
1041
+ }
985
1042
  var SUI_NETWORKS = ["testnet", "devnet", "localnet", "mainnet"];
986
1043
  function toGrpcNetwork(network) {
987
1044
  const short = (network ?? "testnet").replace(/^sui:/, "");
@@ -990,18 +1047,132 @@ function toGrpcNetwork(network) {
990
1047
  }
991
1048
  return short;
992
1049
  }
993
- function createSpendAccountFinalityReader(options) {
1050
+ var GUARDED_DELEGATE_ARG_LAYOUT = {
1051
+ settle_policy: {
1052
+ recipient: null,
1053
+ amount: 6,
1054
+ paymentIdHash: 7,
1055
+ expiresAtMs: 9
1056
+ },
1057
+ settle_any_recipient: {
1058
+ recipient: 4,
1059
+ amount: 5,
1060
+ paymentIdHash: 6,
1061
+ expiresAtMs: 9
1062
+ },
1063
+ settle_policy_to_refund_vault: {
1064
+ recipient: null,
1065
+ amount: 6,
1066
+ paymentIdHash: 7,
1067
+ expiresAtMs: 9
1068
+ },
1069
+ settle_any_recipient_to_refund_vault: {
1070
+ recipient: 4,
1071
+ amount: 5,
1072
+ paymentIdHash: 6,
1073
+ expiresAtMs: 9
1074
+ }
1075
+ };
1076
+ function pureBytesOf(data, arg) {
1077
+ if (!arg || arg.$kind !== "Input" || arg.Input === void 0)
1078
+ return null;
1079
+ const input = data.inputs?.[arg.Input];
1080
+ if (!input || input.$kind !== "Pure" || !input.Pure)
1081
+ return null;
1082
+ return input.Pure.bytes;
1083
+ }
1084
+ function readPureU64(data, arg) {
1085
+ const raw = pureBytesOf(data, arg);
1086
+ if (raw === null)
1087
+ return null;
1088
+ try {
1089
+ return bcs.u64().parse(fromBase642(raw));
1090
+ } catch {
1091
+ return null;
1092
+ }
1093
+ }
1094
+ function readPureVectorU8Hex(data, arg) {
1095
+ const raw = pureBytesOf(data, arg);
1096
+ if (raw === null)
1097
+ return null;
1098
+ try {
1099
+ const bytes = bcs.vector(bcs.u8()).parse(fromBase642(raw));
1100
+ return Buffer.from(bytes).toString("hex");
1101
+ } catch {
1102
+ return null;
1103
+ }
1104
+ }
1105
+ function readPureAddress(data, arg) {
1106
+ const raw = pureBytesOf(data, arg);
1107
+ if (raw === null)
1108
+ return null;
1109
+ try {
1110
+ return normalizeSuiAddress2(bcs.Address.parse(fromBase642(raw)));
1111
+ } catch {
1112
+ return null;
1113
+ }
1114
+ }
1115
+ function recoverGuardedDelegateCalls(finalized) {
1116
+ const raw = finalized?.bcs;
1117
+ let bytes;
1118
+ if (raw instanceof Uint8Array) {
1119
+ bytes = raw;
1120
+ } else if (typeof raw === "string") {
1121
+ try {
1122
+ bytes = fromBase642(raw);
1123
+ } catch {
1124
+ return [];
1125
+ }
1126
+ } else {
1127
+ return [];
1128
+ }
1129
+ let data;
1130
+ try {
1131
+ data = Transaction2.from(bytes).getData();
1132
+ } catch {
1133
+ return [];
1134
+ }
1135
+ const calls = [];
1136
+ for (const command of data.commands) {
1137
+ if (command.$kind !== "MoveCall")
1138
+ continue;
1139
+ const mc = command.MoveCall;
1140
+ if (!mc || mc.module !== "spend_account")
1141
+ continue;
1142
+ if (!(mc.function in GUARDED_DELEGATE_ARG_LAYOUT))
1143
+ continue;
1144
+ const fn = mc.function;
1145
+ const layout = GUARDED_DELEGATE_ARG_LAYOUT[fn];
1146
+ const args = mc.arguments ?? [];
1147
+ calls.push({
1148
+ packageId: normalizeSuiAddress2(mc.package),
1149
+ function: fn,
1150
+ paymentIdHash: readPureVectorU8Hex(data, args[layout.paymentIdHash]),
1151
+ amount: readPureU64(data, args[layout.amount]),
1152
+ recipient: layout.recipient === null ? null : readPureAddress(data, args[layout.recipient]),
1153
+ expiresAtMs: readPureU64(data, args[layout.expiresAtMs])
1154
+ });
1155
+ }
1156
+ return calls;
1157
+ }
1158
+ function createSpendAccountSettledTxReader(options) {
994
1159
  const network = toGrpcNetwork(options.network);
995
1160
  const client = options.client ?? createFailoverGrpcClient({
996
1161
  network,
997
1162
  url: options.url ?? `https://fullnode.${network}.sui.io:443`
998
1163
  });
999
1164
  return {
1000
- async getFinalizedTx(digest) {
1165
+ async getSettledTx(digest) {
1001
1166
  const result = await fetchGrpcTransaction(client, digest, {
1002
1167
  events: true,
1003
1168
  effects: true,
1004
- transaction: true
1169
+ transaction: true,
1170
+ // The guarded delegate recovery below decodes the authoritative
1171
+ // serialized transaction. Those bytes are a separate read-mask path
1172
+ // from the parsed `transaction` projection — without asking for them
1173
+ // the response carries none, recovery finds no guarded call, and every
1174
+ // delegate payment is refused after the funds already moved.
1175
+ bcs: true
1005
1176
  });
1006
1177
  if (!result)
1007
1178
  return null;
@@ -1037,26 +1208,28 @@ function createSpendAccountFinalityReader(options) {
1037
1208
  type: e.eventType,
1038
1209
  kind: e.eventType.split("::").pop() ?? e.eventType
1039
1210
  }));
1211
+ const guardedDelegateCalls = recoverGuardedDelegateCalls(finalized);
1040
1212
  return {
1041
1213
  status: "success",
1042
1214
  txDigest: digest,
1043
1215
  sender,
1044
1216
  moveCalls,
1045
1217
  objectIds,
1046
- events
1218
+ events,
1219
+ guardedDelegateCalls
1047
1220
  };
1048
1221
  }
1049
1222
  };
1050
1223
  }
1051
1224
 
1052
1225
  // ../sdk/dist/delegated-signer/payer.js
1053
- import { bcs as bcs4 } from "@mysten/sui/bcs";
1226
+ import { bcs as bcs5 } from "@mysten/sui/bcs";
1054
1227
  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";
1228
+ import { fromBase64 as fromBase646, normalizeStructTag as normalizeStructTag6, normalizeSuiAddress as normalizeSuiAddress6, toBase64 as toBase645 } from "@mysten/sui/utils";
1056
1229
  import { Ed25519Keypair } from "@mysten/sui/keypairs/ed25519";
1057
1230
 
1058
1231
  // ../sdk/dist/pay-operation/operation.js
1059
- import { normalizeStructTag as normalizeStructTag4, normalizeSuiAddress as normalizeSuiAddress3 } from "@mysten/sui/utils";
1232
+ import { normalizeStructTag as normalizeStructTag5, normalizeSuiAddress as normalizeSuiAddress3 } from "@mysten/sui/utils";
1060
1233
 
1061
1234
  // ../core/dist/policy/grant-target.js
1062
1235
  function normalizeHash(hex) {
@@ -1139,6 +1312,9 @@ function resolveGrantTarget(snapshot, offerTargetHash) {
1139
1312
  };
1140
1313
  }
1141
1314
 
1315
+ // ../sdk/dist/protocol/carrier.js
1316
+ import { fromBase64 as fromBase644, toBase64 as toBase643 } from "@mysten/sui/utils";
1317
+
1142
1318
  // ../sdk/dist/protocol/auth-params.js
1143
1319
  var TOKEN = /^[A-Za-z0-9!#$%&'*+\-.^_`|~]+$/;
1144
1320
  function formatAuthParams(params) {
@@ -1230,8 +1406,11 @@ function bytesToBase64Url(bytes) {
1230
1406
  var SETTLEMENT_RAILS = [
1231
1407
  "direct",
1232
1408
  "delegate",
1233
- "subscription"
1409
+ "recurring"
1234
1410
  ];
1411
+ function mintLiveRail(rail) {
1412
+ return rail;
1413
+ }
1235
1414
  function isDelegateRail(rail) {
1236
1415
  return rail === "delegate";
1237
1416
  }
@@ -1239,6 +1418,29 @@ var DIALECT_ORDER = ["mpp", "x402"];
1239
1418
  function isSettlementRail(value) {
1240
1419
  return typeof value === "string" && SETTLEMENT_RAILS.includes(value);
1241
1420
  }
1421
+ function decodeWireRail(value) {
1422
+ if (isSettlementRail(value))
1423
+ return value;
1424
+ if (value === "subscription")
1425
+ return "recurring";
1426
+ return null;
1427
+ }
1428
+ function decodeAcceptedRails(rails) {
1429
+ if (rails.length === 0)
1430
+ return null;
1431
+ const out = [];
1432
+ const seen = /* @__PURE__ */ new Set();
1433
+ for (const r of rails) {
1434
+ const decoded = decodeWireRail(r);
1435
+ if (!decoded)
1436
+ return null;
1437
+ if (seen.has(decoded))
1438
+ continue;
1439
+ seen.add(decoded);
1440
+ out.push(decoded);
1441
+ }
1442
+ return out;
1443
+ }
1242
1444
  function assertSettlementRail(value) {
1243
1445
  if (!isSettlementRail(value)) {
1244
1446
  throw new Error(`invalid settlement rail: ${String(value)} (expected ${SETTLEMENT_RAILS.join(" | ")})`);
@@ -1249,7 +1451,7 @@ function isDialect(value) {
1249
1451
  return value === "mpp" || value === "x402";
1250
1452
  }
1251
1453
  function isSettlementMode(value) {
1252
- return value === "transaction" || value === "finalized-proof";
1454
+ return value === "gateway-settled" || value === "self-settled";
1253
1455
  }
1254
1456
  function dialectsFromModes(modes) {
1255
1457
  return DIALECT_ORDER.filter((d) => modes[d] != null);
@@ -1287,7 +1489,7 @@ function createSettlementRailAuthority(input) {
1287
1489
  }
1288
1490
  const dialectModes = Object.freeze({ ...input.dialectModes });
1289
1491
  const supportedDialects = Object.freeze(dialectsFromModes(dialectModes));
1290
- const proofMode = dialectModes.mpp ?? dialectModes.x402 ?? "transaction";
1492
+ const proofMode = dialectModes.mpp ?? dialectModes.x402 ?? "gateway-settled";
1291
1493
  return Object.freeze({
1292
1494
  rail: input.rail,
1293
1495
  packageId: input.packageId.trim(),
@@ -1302,12 +1504,12 @@ function spendAccountDebitFunction(input) {
1302
1504
  const open2 = input?.accessMode === "open";
1303
1505
  const refundable = input?.refundable === true;
1304
1506
  if (open2 && refundable)
1305
- return "settle_open_recipient_payment_to_refund_vault";
1507
+ return "settle_any_recipient_to_refund_vault";
1306
1508
  if (open2)
1307
- return "settle_open_recipient_payment";
1509
+ return "settle_any_recipient";
1308
1510
  if (refundable)
1309
- return "settle_policy_payment_to_refund_vault";
1310
- return "settle_policy_payment";
1511
+ return "settle_policy_to_refund_vault";
1512
+ return "settle_policy";
1311
1513
  }
1312
1514
  function defaultAuthorityForRail(rail, packageId, opts) {
1313
1515
  switch (rail) {
@@ -1318,20 +1520,22 @@ function defaultAuthorityForRail(rail, packageId, opts) {
1318
1520
  packageId,
1319
1521
  module: "spend_account",
1320
1522
  payFunction: spendAccountDebitFunction(),
1321
- dialectModes: dual ? { mpp: "finalized-proof", x402: "transaction" } : { mpp: "finalized-proof" }
1523
+ dialectModes: dual ? { mpp: "self-settled", x402: "gateway-settled" } : { mpp: "self-settled" }
1322
1524
  });
1323
1525
  }
1324
- case "subscription":
1526
+ case "recurring":
1325
1527
  return createSettlementRailAuthority({
1326
- rail: "subscription",
1528
+ rail: "recurring",
1327
1529
  packageId,
1328
1530
  module: "subscription",
1329
1531
  // Real Move entry function is `subscription::settle_period`; `charge`
1330
- // never existed on-chain (a Move call to it would abort).
1532
+ // never existed on-chain (a Move call to it would abort). HTTP
1533
+ // `recurring` is the Move `RAIL_TAG`; the module name stays
1534
+ // `subscription`.
1331
1535
  payFunction: "settle_period",
1332
1536
  dialectModes: {
1333
- mpp: "transaction",
1334
- x402: "transaction"
1537
+ mpp: "gateway-settled",
1538
+ x402: "gateway-settled"
1335
1539
  }
1336
1540
  });
1337
1541
  case "direct":
@@ -1339,10 +1543,12 @@ function defaultAuthorityForRail(rail, packageId, opts) {
1339
1543
  rail: "direct",
1340
1544
  packageId,
1341
1545
  module: "payment",
1342
- payFunction: "pay",
1546
+ // Fresh lineage (ADR-0048 Option B): the canonical guarded direct settle
1547
+ // is `payment::settle` (guard folded in; no `pay`/`settle_guarded` twin).
1548
+ payFunction: "settle",
1343
1549
  dialectModes: {
1344
- mpp: "transaction",
1345
- x402: "transaction"
1550
+ mpp: "gateway-settled",
1551
+ x402: "gateway-settled"
1346
1552
  }
1347
1553
  });
1348
1554
  default: {
@@ -1351,9 +1557,24 @@ function defaultAuthorityForRail(rail, packageId, opts) {
1351
1557
  }
1352
1558
  }
1353
1559
  }
1560
+ function railSelfSettles(rail, opts) {
1561
+ const authority = defaultAuthorityForRail(rail, "0x0", opts);
1562
+ return Object.values(authority.dialectModes).some((mode) => mode === "self-settled");
1563
+ }
1354
1564
 
1355
1565
  // ../sdk/dist/protocol/challenge.js
1356
1566
  import { createHmac, randomBytes, timingSafeEqual } from "crypto";
1567
+
1568
+ // ../sdk/dist/protocol/refund-approval.js
1569
+ import { createHash as createHash3 } from "crypto";
1570
+ import { bcs as bcs2 } from "@mysten/sui/bcs";
1571
+ import { fromHex } from "@mysten/sui/utils";
1572
+
1573
+ // ../sdk/dist/protocol/coin-type-hash.js
1574
+ import { createHash as createHash2 } from "crypto";
1575
+ import { normalizeStructTag as normalizeStructTag4 } from "@mysten/sui/utils";
1576
+
1577
+ // ../sdk/dist/protocol/challenge.js
1357
1578
  var SIGNED_FIELDS_V1 = [
1358
1579
  "challengeId",
1359
1580
  "resource",
@@ -1402,6 +1623,12 @@ var SIGNED_FIELDS_V3 = [
1402
1623
  "refundTo",
1403
1624
  "settlementRef"
1404
1625
  ];
1626
+ var SIGNED_FIELDS_V4 = [
1627
+ ...SIGNED_FIELDS_V3,
1628
+ "acceptedRails",
1629
+ "exclusionProtocolVersion",
1630
+ "registryIdentity"
1631
+ ];
1405
1632
  var V3_OMIT_WHEN_ABSENT = /* @__PURE__ */ new Set([
1406
1633
  "intent",
1407
1634
  "periodMs",
@@ -1419,6 +1646,15 @@ var V3_OMIT_WHEN_ABSENT = /* @__PURE__ */ new Set([
1419
1646
  "sponsorGasPrice",
1420
1647
  "sponsorGasPayment"
1421
1648
  ]);
1649
+ var V4_OMIT_WHEN_ABSENT = /* @__PURE__ */ new Set([
1650
+ "acceptedRails",
1651
+ "exclusionProtocolVersion",
1652
+ "registryIdentity"
1653
+ ]);
1654
+ var OMIT_WHEN_ABSENT = /* @__PURE__ */ new Set([
1655
+ ...V3_OMIT_WHEN_ABSENT,
1656
+ ...V4_OMIT_WHEN_ABSENT
1657
+ ]);
1422
1658
  var BODY_HASH_RE = /^[A-Za-z0-9_-]{43}$/;
1423
1659
  var CONTENT_TYPE_RE = /^[a-z0-9!#$%&'*+.^_`|~-]+\/[a-z0-9!#$%&'*+.^_`|~-]+(?:; ?[a-z0-9!#$%&'*+.^_`|~-]+=(?:[a-zA-Z0-9!#$%&'*+.^_`|~-]+|"[^"\n\r]*"))*$/;
1424
1660
  function isBodyHash(value) {
@@ -1446,10 +1682,43 @@ function presentV3OnlyFields(bearer) {
1446
1682
  function carriesV3OnlyFields(bearer) {
1447
1683
  return presentV3OnlyFields(bearer).length > 0;
1448
1684
  }
1685
+ function presentV4OnlyFields(bearer) {
1686
+ return [
1687
+ bearer.acceptedRails !== void 0 ? "acceptedRails" : void 0,
1688
+ bearer.exclusionProtocolVersion !== void 0 ? "exclusionProtocolVersion" : void 0,
1689
+ bearer.registryIdentity !== void 0 ? "registryIdentity" : void 0
1690
+ ].filter((f) => f !== void 0);
1691
+ }
1692
+ function carriesV4OnlyFields(bearer) {
1693
+ return presentV4OnlyFields(bearer).length > 0;
1694
+ }
1695
+ function offerRequiresGuardedSettle(offer) {
1696
+ const rails = [
1697
+ ...offer.acceptedRails ?? [],
1698
+ ...offer.settlementRail ? [offer.settlementRail] : []
1699
+ ];
1700
+ if (!rails.length)
1701
+ return false;
1702
+ return [...new Set(rails)].some((r) => railSelfSettles(r, { x402DialectEnabled: false }));
1703
+ }
1704
+ function parseRegistryIdentity(registryIdentity) {
1705
+ if (!registryIdentity)
1706
+ return null;
1707
+ const idx = registryIdentity.indexOf("::");
1708
+ if (idx <= 0)
1709
+ return null;
1710
+ const packageId = registryIdentity.slice(0, idx);
1711
+ const directoryId = registryIdentity.slice(idx + 2);
1712
+ if (!packageId || !directoryId || directoryId.includes("::"))
1713
+ return null;
1714
+ return { packageId, directoryId };
1715
+ }
1449
1716
  function resolveDigestVersion(offer) {
1717
+ if (carriesV4OnlyFields(offer))
1718
+ return 4;
1450
1719
  if (carriesV3OnlyFields(offer))
1451
1720
  return 3;
1452
- if (offer.digestVersion === 3 || offer.digestVersion === 2 || offer.digestVersion === 1) {
1721
+ if (offer.digestVersion === 4 || offer.digestVersion === 3 || offer.digestVersion === 2 || offer.digestVersion === 1) {
1453
1722
  return offer.digestVersion;
1454
1723
  }
1455
1724
  if (offer.settlementRail)
@@ -1457,6 +1726,8 @@ function resolveDigestVersion(offer) {
1457
1726
  return 1;
1458
1727
  }
1459
1728
  function signedFieldsFor(version) {
1729
+ if (version === 4)
1730
+ return SIGNED_FIELDS_V4;
1460
1731
  if (version === 3)
1461
1732
  return SIGNED_FIELDS_V3;
1462
1733
  if (version === 2)
@@ -1467,12 +1738,14 @@ function fieldValue(offer, key) {
1467
1738
  const v = offer[key];
1468
1739
  if (v === void 0 || v === null)
1469
1740
  return "";
1741
+ if (Array.isArray(v))
1742
+ return v.join(",");
1470
1743
  return String(v);
1471
1744
  }
1472
1745
  function canonicalOffer(offer, version = resolveDigestVersion(offer)) {
1473
1746
  const lines = [];
1474
1747
  for (const f of signedFieldsFor(version)) {
1475
- if (V3_OMIT_WHEN_ABSENT.has(f) && (offer[f] === void 0 || offer[f] === null))
1748
+ if (OMIT_WHEN_ABSENT.has(f) && (offer[f] === void 0 || offer[f] === null))
1476
1749
  continue;
1477
1750
  lines.push(`${f}=${fieldValue(offer, f)}`);
1478
1751
  }
@@ -1482,15 +1755,46 @@ function signOffer(offer, secret, version) {
1482
1755
  const v = version ?? resolveDigestVersion(offer);
1483
1756
  return createHmac("sha256", secret).update(canonicalOffer(offer, v)).digest("base64url");
1484
1757
  }
1485
- function verifyOfferDigest(offer, secret) {
1758
+ function digestMatches(offer, secret) {
1486
1759
  const version = resolveDigestVersion(offer);
1487
1760
  const expected = Buffer.from(signOffer(offer, secret, version));
1488
1761
  const actual = Buffer.from(offer.digest ?? "");
1489
1762
  return expected.length === actual.length && timingSafeEqual(expected, actual);
1490
1763
  }
1764
+ function withLegacySubscriptionSpelling(offer) {
1765
+ const railIsRecurring = offer.settlementRail === "recurring";
1766
+ const railsHaveRecurring = offer.acceptedRails?.includes("recurring") === true;
1767
+ if (!railIsRecurring && !railsHaveRecurring)
1768
+ return null;
1769
+ return {
1770
+ ...offer,
1771
+ ...railIsRecurring ? { settlementRail: "subscription" } : {},
1772
+ ...railsHaveRecurring ? {
1773
+ acceptedRails: offer.acceptedRails.map((r) => r === "recurring" ? "subscription" : r)
1774
+ } : {}
1775
+ };
1776
+ }
1777
+ function verifyOfferDigest(offer, secret) {
1778
+ if (digestMatches(offer, secret))
1779
+ return true;
1780
+ const legacy = withLegacySubscriptionSpelling(offer);
1781
+ return legacy !== null && digestMatches(legacy, secret);
1782
+ }
1491
1783
  function isExpired(offer, now = Date.now()) {
1492
1784
  return now >= offer.expiresAt;
1493
1785
  }
1786
+ function decodeSponsorGasPayment(encoded) {
1787
+ if (typeof encoded !== "string" || !encoded.trim()) {
1788
+ throw new Error("sponsorGasPayment is required");
1789
+ }
1790
+ return encoded.split(",").map((part) => {
1791
+ const bits = part.split("@");
1792
+ if (bits.length !== 3 || !bits[0] || !bits[1] || !bits[2]) {
1793
+ throw new Error(`malformed sponsorGasPayment segment: ${part}`);
1794
+ }
1795
+ return { objectId: bits[0], version: bits[1], digest: bits[2] };
1796
+ });
1797
+ }
1494
1798
 
1495
1799
  // ../sdk/dist/protocol/mpp.js
1496
1800
  function offerToBase64Url(offer) {
@@ -1501,7 +1805,66 @@ var MPP_METHOD = "sui.charge";
1501
1805
  var MPP_CHALLENGE_HEADER = "WWW-Authenticate";
1502
1806
  var MPP_CREDENTIAL_HEADER = "Authorization";
1503
1807
  var MPP_RECEIPT_HEADER = "Payment-Receipt";
1504
- function decodeChallenge(header) {
1808
+ function splitChallenges(header) {
1809
+ const scheme = `${MPP_SCHEME} `;
1810
+ const value = header.trim();
1811
+ const starts = [];
1812
+ let inQuote = false;
1813
+ for (let i = 0; i < value.length; i++) {
1814
+ const ch = value[i];
1815
+ if (inQuote) {
1816
+ if (ch === "\\") {
1817
+ i++;
1818
+ continue;
1819
+ }
1820
+ if (ch === '"')
1821
+ inQuote = false;
1822
+ continue;
1823
+ }
1824
+ if (ch === '"') {
1825
+ inQuote = true;
1826
+ continue;
1827
+ }
1828
+ if (value.startsWith(scheme, i)) {
1829
+ let j = i - 1;
1830
+ while (j >= 0 && (value[j] === " " || value[j] === " "))
1831
+ j--;
1832
+ if (j < 0 || value[j] === ",")
1833
+ starts.push(i);
1834
+ }
1835
+ }
1836
+ if (starts.length === 0)
1837
+ return [];
1838
+ const parts = [];
1839
+ for (let k = 0; k < starts.length; k++) {
1840
+ const from = starts[k];
1841
+ const to = k + 1 < starts.length ? starts[k + 1] : value.length;
1842
+ parts.push(value.slice(from, to).replace(/,\s*$/, "").trim());
1843
+ }
1844
+ return parts;
1845
+ }
1846
+ function decodeChallenge(header, opts) {
1847
+ const parts = splitChallenges(header);
1848
+ if (parts.length <= 1) {
1849
+ return decodeSingleChallenge(header);
1850
+ }
1851
+ if (opts?.rail !== void 0) {
1852
+ for (const part of parts) {
1853
+ const offer = decodeSingleChallenge(part);
1854
+ if (offer && mintLiveRail(offer.settlementRail ?? "direct") === mintLiveRail(opts.rail)) {
1855
+ return offer;
1856
+ }
1857
+ }
1858
+ return null;
1859
+ }
1860
+ for (const part of parts) {
1861
+ const offer = decodeSingleChallenge(part);
1862
+ if (offer)
1863
+ return offer;
1864
+ }
1865
+ return null;
1866
+ }
1867
+ function decodeSingleChallenge(header) {
1505
1868
  const parsed = parseAuthHeader(header);
1506
1869
  if (!parsed || parsed.scheme !== MPP_SCHEME)
1507
1870
  return null;
@@ -1549,13 +1912,14 @@ function decodeChallenge(header) {
1549
1912
  offer.platformFee = p.platform_fee;
1550
1913
  if (p.digest_version !== void 0) {
1551
1914
  const v = Number(p.digest_version);
1552
- if (v === 1 || v === 2 || v === 3)
1915
+ if (v === 1 || v === 2 || v === 3 || v === 4)
1553
1916
  offer.digestVersion = v;
1554
1917
  }
1555
1918
  if (p.settlement_rail !== void 0) {
1556
- if (!isSettlementRail(p.settlement_rail))
1919
+ const rail = decodeWireRail(p.settlement_rail);
1920
+ if (!rail)
1557
1921
  return null;
1558
- offer.settlementRail = p.settlement_rail;
1922
+ offer.settlementRail = rail;
1559
1923
  }
1560
1924
  if (p.service_id !== void 0)
1561
1925
  offer.serviceId = p.service_id;
@@ -1632,6 +1996,21 @@ function decodeChallenge(header) {
1632
1996
  if (p.sponsor_gas_payment !== void 0) {
1633
1997
  offer.sponsorGasPayment = p.sponsor_gas_payment;
1634
1998
  }
1999
+ if (p.accepted_rails !== void 0) {
2000
+ const rails = decodeAcceptedRails(p.accepted_rails.split(","));
2001
+ if (!rails)
2002
+ return null;
2003
+ offer.acceptedRails = rails;
2004
+ }
2005
+ if (p.exclusion_protocol_version !== void 0) {
2006
+ const raw = p.exclusion_protocol_version;
2007
+ const v = Number(raw);
2008
+ if (raw.trim() === "" || !Number.isFinite(v))
2009
+ return null;
2010
+ offer.exclusionProtocolVersion = v;
2011
+ }
2012
+ if (p.registry_identity !== void 0)
2013
+ offer.registryIdentity = p.registry_identity;
1635
2014
  return offer;
1636
2015
  }
1637
2016
  function encodeProof(proof) {
@@ -1675,12 +2054,128 @@ function decodeReceipt(header) {
1675
2054
  }
1676
2055
 
1677
2056
  // ../sdk/dist/protocol/x402.js
2057
+ import { fromBase64 as fromBase643, toBase64 as toBase642 } from "@mysten/sui/utils";
2058
+ function utf8ToBase64(text2) {
2059
+ return toBase642(new TextEncoder().encode(text2));
2060
+ }
2061
+ function base64ToUtf8(value) {
2062
+ return new TextDecoder().decode(fromBase643(value));
2063
+ }
2064
+ var X402_VERSION = 2;
1678
2065
  var X402_SCHEME = "exact";
2066
+ var X402_PAYMENT_FLOW = "upfront";
2067
+ var X402_ERROR_REASON = {
2068
+ MALFORMED_CREDENTIAL: "invalid_payload",
2069
+ UNKNOWN_CHALLENGE: "invalid_payment_requirements",
2070
+ CHALLENGE_TAMPERED: "invalid_payment_requirements",
2071
+ CHALLENGE_EXPIRED: "invalid_payment_requirements",
2072
+ CHALLENGE_ALREADY_USED: "invalid_transaction_state",
2073
+ CHALLENGE_IN_FLIGHT: "invalid_transaction_state",
2074
+ TRANSACTION_INVALID: "invalid_payload",
2075
+ NETWORK_MISMATCH: "invalid_network",
2076
+ SIGNATURE_INVALID: "invalid_payload",
2077
+ INSUFFICIENT_FUNDS: "insufficient_funds",
2078
+ PAYER_MISMATCH: "invalid_payload",
2079
+ SETTLEMENT_REJECTED: "invalid_transaction_state",
2080
+ SETTLEMENT_MISMATCH: "unexpected_settle_error",
2081
+ AMBIGUOUS: "settlement_pending"
2082
+ };
1679
2083
  var X402_CHALLENGE_HEADER = "PAYMENT-REQUIRED";
2084
+ var X402_PAYMENT_HEADER = "PAYMENT-SIGNATURE";
1680
2085
  var X402_RESPONSE_HEADER = "PAYMENT-RESPONSE";
1681
2086
  var X402_RECEIPT_HEADER = "X-PAYMENT-RESPONSE";
2087
+ function toAccept(offer, authority, opts) {
2088
+ const auth = authority ?? defaultAuthorityForRail(offer.settlementRail ?? "direct", offer.packageId);
2089
+ const payFunction = auth.payFunction;
2090
+ const suipay = {
2091
+ challengeId: offer.challengeId,
2092
+ packageId: offer.packageId,
2093
+ module: auth.module,
2094
+ function: payFunction,
2095
+ decimals: offer.decimals,
2096
+ issuedAt: offer.issuedAt,
2097
+ expiresAt: offer.expiresAt,
2098
+ digest: offer.digest
2099
+ };
2100
+ if (offer.netAmount !== void 0)
2101
+ suipay.netAmount = offer.netAmount;
2102
+ if (offer.platformFee !== void 0)
2103
+ suipay.platformFee = offer.platformFee;
2104
+ if (offer.digestVersion !== void 0)
2105
+ suipay.digestVersion = offer.digestVersion;
2106
+ if (offer.settlementRail !== void 0)
2107
+ suipay.settlementRail = offer.settlementRail;
2108
+ if (offer.serviceId !== void 0)
2109
+ suipay.serviceId = offer.serviceId;
2110
+ if (offer.serviceRevision !== void 0)
2111
+ suipay.serviceRevision = offer.serviceRevision;
2112
+ if (offer.targetHash !== void 0)
2113
+ suipay.targetHash = offer.targetHash;
2114
+ if (offer.authorizationEpoch !== void 0) {
2115
+ suipay.authorizationEpoch = offer.authorizationEpoch;
2116
+ }
2117
+ if (offer.method !== void 0)
2118
+ suipay.method = offer.method;
2119
+ if (offer.path !== void 0)
2120
+ suipay.path = offer.path;
2121
+ if (offer.bodyHash !== void 0)
2122
+ suipay.bodyHash = offer.bodyHash;
2123
+ if (offer.contentType !== void 0)
2124
+ suipay.contentType = offer.contentType;
2125
+ if (offer.intent !== void 0)
2126
+ suipay.intent = offer.intent;
2127
+ if (offer.periodMs !== void 0)
2128
+ suipay.periodMs = offer.periodMs;
2129
+ if (offer.periodCount !== void 0)
2130
+ suipay.periodCount = offer.periodCount;
2131
+ if (offer.refundable !== void 0)
2132
+ suipay.refundable = offer.refundable;
2133
+ if (offer.refundWindowMs !== void 0)
2134
+ suipay.refundWindowMs = offer.refundWindowMs;
2135
+ if (offer.decideWindowMs !== void 0)
2136
+ suipay.decideWindowMs = offer.decideWindowMs;
2137
+ if (offer.chainId !== void 0)
2138
+ suipay.chainId = offer.chainId;
2139
+ if (offer.paymentSalt !== void 0)
2140
+ suipay.paymentSalt = offer.paymentSalt;
2141
+ if (offer.refundTo !== void 0)
2142
+ suipay.refundTo = offer.refundTo;
2143
+ if (offer.sponsorGasOwner !== void 0) {
2144
+ suipay.sponsorGasOwner = offer.sponsorGasOwner;
2145
+ }
2146
+ if (offer.sponsorGasBudget !== void 0) {
2147
+ suipay.sponsorGasBudget = offer.sponsorGasBudget;
2148
+ }
2149
+ if (offer.sponsorGasPrice !== void 0) {
2150
+ suipay.sponsorGasPrice = offer.sponsorGasPrice;
2151
+ }
2152
+ if (offer.sponsorGasPayment !== void 0) {
2153
+ suipay.sponsorGasPayment = offer.sponsorGasPayment;
2154
+ }
2155
+ if (offer.acceptedRails !== void 0)
2156
+ suipay.acceptedRails = offer.acceptedRails;
2157
+ if (offer.exclusionProtocolVersion !== void 0) {
2158
+ suipay.exclusionProtocolVersion = offer.exclusionProtocolVersion;
2159
+ }
2160
+ if (offer.registryIdentity !== void 0)
2161
+ suipay.registryIdentity = offer.registryIdentity;
2162
+ return {
2163
+ scheme: X402_SCHEME,
2164
+ network: offer.network,
2165
+ amount: offer.amount,
2166
+ asset: offer.asset,
2167
+ payTo: offer.payTo,
2168
+ maxTimeoutSeconds: Math.max(1, Math.round((offer.expiresAt - offer.issuedAt) / 1e3)),
2169
+ extra: {
2170
+ paymentFlow: X402_PAYMENT_FLOW,
2171
+ ...opts?.gasStation ? { gasStation: opts.gasStation } : {},
2172
+ suipay
2173
+ }
2174
+ };
2175
+ }
1682
2176
  function railOf(offer) {
1683
- return offer.settlementRail ?? "direct";
2177
+ const rail = offer.settlementRail ?? "direct";
2178
+ return mintLiveRail(rail);
1684
2179
  }
1685
2180
  function decodeChallenge2(body, opts) {
1686
2181
  const b = body;
@@ -1691,7 +2186,7 @@ function decodeChallenge2(body, opts) {
1691
2186
  if (a?.scheme !== X402_SCHEME || !a?.extra?.suipay)
1692
2187
  continue;
1693
2188
  const offer = fromAccept(a, b.resource);
1694
- if (offer && railOf(offer) === opts.rail)
2189
+ if (offer && railOf(offer) === mintLiveRail(opts.rail))
1695
2190
  return offer;
1696
2191
  }
1697
2192
  return null;
@@ -1726,13 +2221,14 @@ function fromAccept(accept, resource) {
1726
2221
  offer.netAmount = s.netAmount;
1727
2222
  if (s.platformFee !== void 0)
1728
2223
  offer.platformFee = s.platformFee;
1729
- if (s.digestVersion === 1 || s.digestVersion === 2 || s.digestVersion === 3) {
2224
+ if (s.digestVersion === 1 || s.digestVersion === 2 || s.digestVersion === 3 || s.digestVersion === 4) {
1730
2225
  offer.digestVersion = s.digestVersion;
1731
2226
  }
1732
2227
  if (s.settlementRail !== void 0) {
1733
- if (!isSettlementRail(s.settlementRail))
2228
+ const rail = decodeWireRail(s.settlementRail);
2229
+ if (!rail)
1734
2230
  return null;
1735
- offer.settlementRail = s.settlementRail;
2231
+ offer.settlementRail = rail;
1736
2232
  }
1737
2233
  if (s.serviceId !== void 0)
1738
2234
  offer.serviceId = s.serviceId;
@@ -1784,11 +2280,71 @@ function fromAccept(accept, resource) {
1784
2280
  if (s.sponsorGasPayment !== void 0) {
1785
2281
  offer.sponsorGasPayment = s.sponsorGasPayment;
1786
2282
  }
2283
+ if (s.acceptedRails !== void 0) {
2284
+ if (!Array.isArray(s.acceptedRails))
2285
+ return null;
2286
+ const rails = decodeAcceptedRails(s.acceptedRails);
2287
+ if (!rails)
2288
+ return null;
2289
+ offer.acceptedRails = rails;
2290
+ }
2291
+ if (s.exclusionProtocolVersion !== void 0) {
2292
+ const v = s.exclusionProtocolVersion;
2293
+ if (typeof v !== "number" || !Number.isFinite(v))
2294
+ return null;
2295
+ offer.exclusionProtocolVersion = v;
2296
+ }
2297
+ if (s.registryIdentity !== void 0)
2298
+ offer.registryIdentity = s.registryIdentity;
1787
2299
  return offer;
1788
2300
  }
2301
+ function encodeCredential(cred) {
2302
+ const payload = {
2303
+ x402Version: X402_VERSION,
2304
+ resource: {
2305
+ url: cred.offer.resource,
2306
+ description: cred.offer.description,
2307
+ mimeType: cred.offer.mimeType
2308
+ },
2309
+ accepted: toAccept(cred.offer),
2310
+ payload: {
2311
+ challengeId: cred.challengeId,
2312
+ payer: cred.payer ?? void 0,
2313
+ transaction: cred.txBytes,
2314
+ signature: cred.signature
2315
+ }
2316
+ };
2317
+ return utf8ToBase64(JSON.stringify(payload));
2318
+ }
2319
+ var X402_ERROR_REASONS = new Set(Object.values(X402_ERROR_REASON));
2320
+ function decodeSettlementResponse(header) {
2321
+ const receipt = decodeReceipt2(header);
2322
+ if (receipt)
2323
+ return receipt;
2324
+ try {
2325
+ const r = JSON.parse(base64ToUtf8(header));
2326
+ if (!r || typeof r !== "object" || r.success !== false)
2327
+ return null;
2328
+ if (typeof r.errorReason !== "string" || !X402_ERROR_REASONS.has(r.errorReason))
2329
+ return null;
2330
+ if (typeof r.network !== "string" || typeof r.transaction !== "string")
2331
+ return null;
2332
+ const failure = {
2333
+ success: false,
2334
+ errorReason: r.errorReason,
2335
+ transaction: r.transaction,
2336
+ network: r.network
2337
+ };
2338
+ if (typeof r.payer === "string")
2339
+ failure.payer = r.payer;
2340
+ return failure;
2341
+ } catch {
2342
+ return null;
2343
+ }
2344
+ }
1789
2345
  function decodeReceipt2(header) {
1790
2346
  try {
1791
- const r = JSON.parse(Buffer.from(header, "base64").toString("utf8"));
2347
+ const r = JSON.parse(base64ToUtf8(header));
1792
2348
  if (!r?.success || !r.transaction)
1793
2349
  return null;
1794
2350
  const receipt = {
@@ -1813,22 +2369,22 @@ function decodeReceipt2(header) {
1813
2369
  }
1814
2370
 
1815
2371
  // ../sdk/dist/protocol/carrier.js
1816
- async function readChallenge(res, dialect) {
2372
+ async function readChallenge(res, dialect, opts) {
1817
2373
  if (dialect === "mpp") {
1818
2374
  const header = res.headers.get(MPP_CHALLENGE_HEADER);
1819
- return header ? decodeChallenge(header) : null;
2375
+ return header ? decodeChallenge(header, opts) : null;
1820
2376
  }
1821
2377
  const headerVal = res.headers.get(X402_CHALLENGE_HEADER);
1822
2378
  if (headerVal) {
1823
2379
  try {
1824
- const parsed = JSON.parse(Buffer.from(headerVal, "base64").toString("utf8"));
1825
- const offer = decodeChallenge2(parsed);
2380
+ const parsed = JSON.parse(new TextDecoder().decode(fromBase644(headerVal)));
2381
+ const offer = decodeChallenge2(parsed, opts);
1826
2382
  if (offer)
1827
2383
  return offer;
1828
2384
  } catch {
1829
2385
  }
1830
2386
  }
1831
- return decodeChallenge2(await readBody(res));
2387
+ return decodeChallenge2(await readBody(res), opts);
1832
2388
  }
1833
2389
  function readReceipt(res, dialect) {
1834
2390
  const header = dialect === "mpp" ? res.headers.get(MPP_RECEIPT_HEADER) : res.headers.get(X402_RESPONSE_HEADER) ?? res.headers.get(X402_RECEIPT_HEADER);
@@ -1871,7 +2427,7 @@ async function readBody(res) {
1871
2427
  configurable: false
1872
2428
  });
1873
2429
  if (bytes.byteLength <= MAX_INLINE_BINARY_BYTES) {
1874
- binary.base64 = Buffer.from(bytes).toString("base64");
2430
+ binary.base64 = toBase643(bytes);
1875
2431
  }
1876
2432
  return binary;
1877
2433
  }
@@ -1923,10 +2479,10 @@ function requestBindingViolation(offer, request) {
1923
2479
  if (offer.intent === "subscription" && offer.bodyHash === void 0 && offer.contentType === void 0) {
1924
2480
  return null;
1925
2481
  }
1926
- if (offer.digestVersion !== 3) {
2482
+ if ((offer.digestVersion ?? 1) < 3) {
1927
2483
  return {
1928
2484
  code: "OFFER_NOT_BODY_BOUND",
1929
- reason: "a paid POST requires an offer digest v3 that signs the request body"
2485
+ reason: "a paid POST requires an offer digest that signs the request body"
1930
2486
  };
1931
2487
  }
1932
2488
  if (offer.bodyHash !== request.bodyHash) {
@@ -2157,24 +2713,74 @@ var PAY_OPERATION_ERROR_CODES = {
2157
2713
  /** The payment ledger reserve CAS lost (another attempt owns this id). */
2158
2714
  PAYMENT_ATTEMPT_IN_FLIGHT: "PAYMENT_ATTEMPT_IN_FLIGHT",
2159
2715
  /** The payment ledger could not persist the reservation. */
2160
- PAYMENT_LEDGER_FAILED: "PAYMENT_LEDGER_FAILED"
2716
+ PAYMENT_LEDGER_FAILED: "PAYMENT_LEDGER_FAILED",
2717
+ /** The offer's HMAC digest failed verification — tampered or wrong secret. */
2718
+ DIGEST_INVALID: "DIGEST_INVALID",
2719
+ /** The offer's expiry is in the past (or issuedAt/expiresAt are incoherent). */
2720
+ OFFER_EXPIRED: "OFFER_EXPIRED",
2721
+ /**
2722
+ * The offer's pinned sponsor gas fields are incomplete or inconsistent
2723
+ * (partial pin, non-numeric budget/price, empty payment) — reject before
2724
+ * building a tx against a gas pin that cannot be honored.
2725
+ */
2726
+ SPONSOR_GAS_INVALID: "SPONSOR_GAS_INVALID",
2727
+ /**
2728
+ * The recipient / amount / payment_id embedded in the built transaction does
2729
+ * not match the offer — the pre-sign verify-by-rebuild backstop. Nothing may
2730
+ * be signed once this fires.
2731
+ */
2732
+ TX_OFFER_MISMATCH: "TX_OFFER_MISMATCH",
2733
+ /**
2734
+ * Open-grant x402 is refused. This is PERMANENT and architectural: an open
2735
+ * grant does not pre-bind a recipient, so a buyer cannot verify the recipient
2736
+ * before signing (ADR-0002 / support matrix `open × x402` = unsupported). Do
2737
+ * not conflate with a rail that is merely not built yet.
2738
+ */
2739
+ UNSUPPORTED_SCOPE: "UNSUPPORTED_SCOPE",
2740
+ /**
2741
+ * A rail/dialect combination that is SUPPORTED by design but not yet
2742
+ * implemented in this build — currently scoped-grant spend_account x402
2743
+ * (the buyer credential-submit flow). Distinct from UNSUPPORTED_SCOPE so an
2744
+ * integrator reads "coming, not yet wired" rather than "permanently refused".
2745
+ * See docs/decisions/ADR-0047-buyer-side-x402-two-paths-one-entrypoint.md.
2746
+ */
2747
+ RAIL_NOT_YET_IMPLEMENTED: "RAIL_NOT_YET_IMPLEMENTED",
2748
+ /**
2749
+ * Grant rail is configured but required verify-kind RPC context (getObjectJson)
2750
+ * is missing — distinct from RAIL_NOT_YET_IMPLEMENTED, which means the rail
2751
+ * itself is not wired.
2752
+ */
2753
+ GRANT_VERIFY_MISCONFIGURED: "GRANT_VERIFY_MISCONFIGURED",
2754
+ /** The request URL or method is invalid and cannot be normalized. */
2755
+ REQUEST_INVALID: "REQUEST_INVALID",
2756
+ /** The 402 response body stream failed while reading the challenge. */
2757
+ CHALLENGE_READ_ERROR: "CHALLENGE_READ_ERROR",
2758
+ /**
2759
+ * Pre-sign transaction verification could not reach a verdict due to an RPC
2760
+ * or chain read outage (retryable, not tampering).
2761
+ */
2762
+ VERIFICATION_UNAVAILABLE: "VERIFICATION_UNAVAILABLE"
2161
2763
  };
2162
2764
  var CODE_SET = new Set(Object.values(PAY_OPERATION_ERROR_CODES));
2163
2765
 
2164
2766
  // ../sdk/dist/pay-operation/operation.js
2165
2767
  async function resolvePayment(input, deps) {
2166
2768
  const { url, gatewayUrl, packageId, network, authority, assertLive } = input;
2167
- const { fetchImpl = fetch, preferredDialect = "mpp", validateOffer, buildPayKind } = deps;
2769
+ const { fetchImpl = fetch, preferredDialect = "mpp", validateOffer, buildPayKind, resolveExclusionRegistry: resolveExclusionRegistry2 } = deps;
2168
2770
  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);
2771
+ const originViolation2 = validateOrigin(url, gatewayUrl);
2772
+ if (originViolation2) {
2773
+ return failed(PAY_OPERATION_ERROR_CODES.ORIGIN_MISMATCH, originViolation2);
2172
2774
  }
2173
2775
  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)}`);
2776
+ if (deps.prefetchedResponse) {
2777
+ response = deps.prefetchedResponse;
2778
+ } else {
2779
+ try {
2780
+ response = await fetchImpl(url, paidRequestInit(request));
2781
+ } catch (e) {
2782
+ return failed(PAY_OPERATION_ERROR_CODES.RESOURCE_UNAVAILABLE, `fetch failed: ${e instanceof Error ? e.message : String(e)}`);
2783
+ }
2178
2784
  }
2179
2785
  if (response.status !== 402) {
2180
2786
  if (response.status >= 200 && response.status < 300) {
@@ -2203,7 +2809,7 @@ async function resolvePayment(input, deps) {
2203
2809
  return failed(PAY_OPERATION_ERROR_CODES.REQUEST_BINDING_VIOLATED, bindingViolation.reason, challengeId);
2204
2810
  }
2205
2811
  if (!isDelegateRail(offer.settlementRail)) {
2206
- return failed(PAY_OPERATION_ERROR_CODES.UNSUPPORTED_RAIL, `rail ${offer.settlementRail ?? "none"} is not a spend-account rail`, challengeId);
2812
+ return failed(PAY_OPERATION_ERROR_CODES.UNSUPPORTED_RAIL, `rail ${offer.settlementRail ?? "none"} is not a delegate rail`, challengeId);
2207
2813
  }
2208
2814
  let packageMatches;
2209
2815
  try {
@@ -2248,6 +2854,41 @@ async function resolvePayment(input, deps) {
2248
2854
  recipient: resolved.authority.recipient,
2249
2855
  accessMode: resolved.authority.accessMode
2250
2856
  };
2857
+ let guarded;
2858
+ if (offerRequiresGuardedSettle(offer)) {
2859
+ const identity = parseRegistryIdentity(offer.registryIdentity);
2860
+ if (!identity) {
2861
+ return failed(PAY_OPERATION_ERROR_CODES.BUILD_FAILED, "guarded offer carries no valid registry identity", challengeId);
2862
+ }
2863
+ if (normalizeSuiAddress3(identity.packageId) !== normalizeSuiAddress3(offer.packageId)) {
2864
+ return failed(PAY_OPERATION_ERROR_CODES.BUILD_FAILED, `guarded registry identity package ${identity.packageId} does not match offer package ${offer.packageId}`, challengeId);
2865
+ }
2866
+ if (!resolveExclusionRegistry2) {
2867
+ return failed(PAY_OPERATION_ERROR_CODES.BUILD_FAILED, "a guarded delegate offer requires an exclusion registry resolver", challengeId);
2868
+ }
2869
+ let registryResolution;
2870
+ try {
2871
+ registryResolution = await resolveExclusionRegistry2({
2872
+ directoryId: identity.directoryId,
2873
+ packageId: offer.packageId,
2874
+ recipient: resolved.authority.recipient
2875
+ });
2876
+ } catch (e) {
2877
+ return failed(PAY_OPERATION_ERROR_CODES.BUILD_FAILED, e instanceof Error ? e.message : String(e), challengeId);
2878
+ }
2879
+ kindInput.exclusionDirectoryId = identity.directoryId;
2880
+ kindInput.recipientRegistryId = registryResolution.recipientRegistryId;
2881
+ kindInput.expiresAtMs = offer.expiresAt;
2882
+ kindInput.exclusionDirectoryInitialSharedVersion = registryResolution.directoryInitialSharedVersion;
2883
+ kindInput.recipientRegistryInitialSharedVersion = registryResolution.registryInitialSharedVersion;
2884
+ guarded = {
2885
+ exclusionDirectoryId: identity.directoryId,
2886
+ recipientRegistryId: registryResolution.recipientRegistryId,
2887
+ expiresAtMs: offer.expiresAt,
2888
+ exclusionDirectoryInitialSharedVersion: registryResolution.directoryInitialSharedVersion,
2889
+ recipientRegistryInitialSharedVersion: registryResolution.registryInitialSharedVersion
2890
+ };
2891
+ }
2251
2892
  let kindBytes;
2252
2893
  try {
2253
2894
  const result = await buildPayKind(kindInput);
@@ -2264,7 +2905,8 @@ async function resolvePayment(input, deps) {
2264
2905
  termsHash: tHash,
2265
2906
  kindBytes,
2266
2907
  paymentId: challengeId,
2267
- request
2908
+ request,
2909
+ ...guarded ? { guarded } : {}
2268
2910
  };
2269
2911
  }
2270
2912
  function failed(code, detail, paymentId) {
@@ -2314,7 +2956,11 @@ function resolveAuthority(authority, offer) {
2314
2956
  };
2315
2957
  }
2316
2958
  if (!offer.targetHash) {
2317
- return { ok: false, code: PAY_OPERATION_ERROR_CODES.POLICY_REJECTED, reason: "offer has no targetHash for scoped resolution" };
2959
+ return {
2960
+ ok: false,
2961
+ code: PAY_OPERATION_ERROR_CODES.POLICY_REJECTED,
2962
+ reason: "offer has no targetHash for scoped resolution"
2963
+ };
2318
2964
  }
2319
2965
  const result = resolveGrantTarget(authority, offer.targetHash);
2320
2966
  if (!result.ok) {
@@ -2337,8 +2983,8 @@ function resolveAuthority(authority, offer) {
2337
2983
  };
2338
2984
  }
2339
2985
  function validateAmount(offer, resolved) {
2340
- const offerCoinType = normalizeStructTag4(offer.asset);
2341
- const grantCoinType = normalizeStructTag4(resolved.coinType);
2986
+ const offerCoinType = normalizeStructTag5(offer.asset);
2987
+ const grantCoinType = normalizeStructTag5(resolved.coinType);
2342
2988
  if (offerCoinType !== grantCoinType) {
2343
2989
  return {
2344
2990
  code: PAY_OPERATION_ERROR_CODES.COIN_TYPE_MISMATCH,
@@ -2383,9 +3029,9 @@ function validateAmount(offer, resolved) {
2383
3029
  // ../sdk/dist/sponsor/personal-message.js
2384
3030
  import { randomBytes as randomBytes2 } from "crypto";
2385
3031
  import { sha256 as sha2563 } from "@noble/hashes/sha2.js";
2386
- import { bcs as bcs2 } from "@mysten/sui/bcs";
3032
+ import { bcs as bcs3 } from "@mysten/sui/bcs";
2387
3033
  import { isValidPersonalMessageSignature, verifyPersonalMessageSignature } from "@mysten/sui/verify";
2388
- import { fromBase64 as fromBase642, normalizeSuiAddress as normalizeSuiAddress4, toBase64 as toBase642 } from "@mysten/sui/utils";
3034
+ import { fromBase64 as fromBase645, normalizeSuiAddress as normalizeSuiAddress4, toBase64 as toBase644 } from "@mysten/sui/utils";
2389
3035
  import { Transaction as Transaction3 } from "@mysten/sui/transactions";
2390
3036
  var SPONSOR_PERSONAL_MESSAGE_DOMAIN = "suipay:sponsor-personal-message:v1";
2391
3037
  function encodeSponsorPersonalMessagePayload(payload) {
@@ -2406,20 +3052,20 @@ function encodeSponsorPersonalMessagePayload(payload) {
2406
3052
  throw new Error("network is required");
2407
3053
  const parts = [];
2408
3054
  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());
3055
+ push(bcs3.vector(bcs3.u8()).serialize([...Buffer.from(payload.domain, "utf8")]).toBytes());
3056
+ push(bcs3.vector(bcs3.u8()).serialize([...Buffer.from(payload.nonce, "utf8")]).toBytes());
3057
+ push(bcs3.u64().serialize(BigInt(payload.expiryMs)).toBytes());
3058
+ push(bcs3.Address.serialize(normalizeSuiAddress4(payload.delegate)).toBytes());
3059
+ push(bcs3.Address.serialize(normalizeSuiAddress4(payload.poolId)).toBytes());
3060
+ push(bcs3.Address.serialize(normalizeSuiAddress4(payload.grantId)).toBytes());
3061
+ push(bcs3.vector(bcs3.u8()).serialize([...Buffer.from(payload.network, "utf8")]).toBytes());
3062
+ push(bcs3.Address.serialize(normalizeSuiAddress4(payload.packageId)).toBytes());
3063
+ push(bcs3.vector(bcs3.u8()).serialize([...Buffer.from(payload.requestId, "utf8")]).toBytes());
3064
+ push(bcs3.vector(bcs3.u8()).serialize([...payload.txKindHash]).toBytes());
2419
3065
  return Buffer.concat(parts.map((p) => Buffer.from(p)));
2420
3066
  }
2421
3067
  function hashTransactionKindBytes(kindBytes) {
2422
- const raw = typeof kindBytes === "string" ? fromBase642(kindBytes) : kindBytes;
3068
+ const raw = typeof kindBytes === "string" ? fromBase645(kindBytes) : kindBytes;
2423
3069
  return sha2563(raw);
2424
3070
  }
2425
3071
  function buildSponsorPersonalMessagePayload(input) {
@@ -2444,7 +3090,7 @@ async function signSponsorPersonalMessagePayload(input) {
2444
3090
  // ../sdk/dist/protocol/signed-request.js
2445
3091
  import { sha256 as sha2564 } from "@noble/hashes/sha2.js";
2446
3092
  import { bytesToHex as bytesToHex2 } from "@noble/hashes/utils.js";
2447
- import { bcs as bcs3 } from "@mysten/sui/bcs";
3093
+ import { bcs as bcs4 } from "@mysten/sui/bcs";
2448
3094
  import { messageWithIntent } from "@mysten/sui/cryptography";
2449
3095
  import { isValidPersonalMessageSignature as isValidPersonalMessageSignature2, verifyPersonalMessageSignature as verifyPersonalMessageSignature2 } from "@mysten/sui/verify";
2450
3096
  import { normalizeSuiAddress as normalizeSuiAddress5 } from "@mysten/sui/utils";
@@ -2478,8 +3124,8 @@ function encodeBootstrapRequest(payload) {
2478
3124
  if (payload.domain !== BOOTSTRAP_REQUEST_DOMAIN) {
2479
3125
  throw new Error(`domain must be ${BOOTSTRAP_REQUEST_DOMAIN}`);
2480
3126
  }
2481
- const utf8Vec = (s) => bcs3.vector(bcs3.u8()).serialize([...Buffer.from(s, "utf8")]).toBytes();
2482
- const addr = (a) => bcs3.Address.serialize(normalizeSuiAddress5(a)).toBytes();
3127
+ const utf8Vec = (s) => bcs4.vector(bcs4.u8()).serialize([...Buffer.from(s, "utf8")]).toBytes();
3128
+ const addr = (a) => bcs4.Address.serialize(normalizeSuiAddress5(a)).toBytes();
2483
3129
  const parts = [
2484
3130
  utf8Vec(payload.domain),
2485
3131
  utf8Vec(payload.network),
@@ -2488,8 +3134,8 @@ function encodeBootstrapRequest(payload) {
2488
3134
  utf8Vec(payload.method),
2489
3135
  utf8Vec(payload.path),
2490
3136
  utf8Vec(payload.query),
2491
- bcs3.vector(bcs3.u8()).serialize([...hexToBytes32(payload.bodyHash)]).toBytes(),
2492
- bcs3.u64().serialize(BigInt(payload.timestampMs)).toBytes(),
3137
+ bcs4.vector(bcs4.u8()).serialize([...hexToBytes32(payload.bodyHash)]).toBytes(),
3138
+ bcs4.u64().serialize(BigInt(payload.timestampMs)).toBytes(),
2493
3139
  utf8Vec(payload.nonce)
2494
3140
  ];
2495
3141
  return Buffer.concat(parts.map((p) => Buffer.from(p)));
@@ -2498,8 +3144,8 @@ function encodeSignedRequest(payload) {
2498
3144
  if (payload.domain !== SIGNED_REQUEST_DOMAIN) {
2499
3145
  throw new Error(`domain must be ${SIGNED_REQUEST_DOMAIN}`);
2500
3146
  }
2501
- const utf8Vec = (s) => bcs3.vector(bcs3.u8()).serialize([...Buffer.from(s, "utf8")]).toBytes();
2502
- const addr = (a) => bcs3.Address.serialize(normalizeSuiAddress5(a)).toBytes();
3147
+ const utf8Vec = (s) => bcs4.vector(bcs4.u8()).serialize([...Buffer.from(s, "utf8")]).toBytes();
3148
+ const addr = (a) => bcs4.Address.serialize(normalizeSuiAddress5(a)).toBytes();
2503
3149
  const parts = [
2504
3150
  utf8Vec(payload.domain),
2505
3151
  utf8Vec(payload.network),
@@ -2509,8 +3155,8 @@ function encodeSignedRequest(payload) {
2509
3155
  utf8Vec(payload.method),
2510
3156
  utf8Vec(payload.path),
2511
3157
  utf8Vec(payload.query),
2512
- bcs3.vector(bcs3.u8()).serialize([...hexToBytes32(payload.bodyHash)]).toBytes(),
2513
- bcs3.u64().serialize(BigInt(payload.timestampMs)).toBytes(),
3158
+ bcs4.vector(bcs4.u8()).serialize([...hexToBytes32(payload.bodyHash)]).toBytes(),
3159
+ bcs4.u64().serialize(BigInt(payload.timestampMs)).toBytes(),
2514
3160
  utf8Vec(payload.nonce)
2515
3161
  ];
2516
3162
  return Buffer.concat(parts.map((p) => Buffer.from(p)));
@@ -2869,7 +3515,7 @@ async function readPublicSessionState(getObjectJson, grantObjectId) {
2869
3515
  const g = json;
2870
3516
  return {
2871
3517
  grantObjectId,
2872
- // Field names are the Move struct's, and SAIP-121 renamed three of them:
3518
+ // Field names are the Move struct's; three were renamed:
2873
3519
  // pool_id -> account_id, session_cap -> total_budget, session_spent ->
2874
3520
  // total_spent. Reading the old names yielded undefined, which the grant
2875
3521
  // verifier then read as "this grant names no pool" and refused every
@@ -2929,6 +3575,9 @@ var bootstrapPayerGrants = bootstrapPayerSessions;
2929
3575
  // ../sdk/dist/delegated-signer/payer.js
2930
3576
  var FINALIZATION_ATTEMPTS = 20;
2931
3577
  var FINALIZATION_DELAY_MS = 250;
3578
+ var X402_EXPIRATION_EPOCHS = 32n;
3579
+ var X402_PRESENT_ATTEMPTS = 10;
3580
+ var X402_PRESENT_DELAY_MS = 1e3;
2932
3581
  function delay(ms) {
2933
3582
  return new Promise((resolve) => {
2934
3583
  setTimeout(resolve, ms);
@@ -2996,7 +3645,16 @@ var DELEGATED_PAY_ERROR_CODES = {
2996
3645
  * AGENT's own ceiling, and it is the check that does not need the gateway's
2997
3646
  * secret to be meaningful.
2998
3647
  */
2999
- SPEND_INTENT_VIOLATED: "SPEND_INTENT_VIOLATED"
3648
+ SPEND_INTENT_VIOLATED: "SPEND_INTENT_VIOLATED",
3649
+ /**
3650
+ * x402 was asked for and cannot be paid from the spend account: the 402
3651
+ * carried no x402 offer (the gateway strips it when it has no gas lease), a
3652
+ * sponsor gas pin is missing or unreadable, or no current epoch could be read
3653
+ * to bound the transaction. Nothing was reserved or signed.
3654
+ */
3655
+ X402_UNAVAILABLE: "X402_UNAVAILABLE",
3656
+ /** The grant's scope cannot pay in the requested dialect (open-recipient over x402). */
3657
+ UNSUPPORTED_SCOPE: "UNSUPPORTED_SCOPE"
3000
3658
  };
3001
3659
  var DELEGATED_PAY_ERROR_CODE_SET = new Set(Object.values(DELEGATED_PAY_ERROR_CODES));
3002
3660
  function isDelegatedPayErrorCode(value) {
@@ -3033,7 +3691,9 @@ var PUBLIC_DETAIL = {
3033
3691
  FINALITY_UNAVAILABLE: "no finality reader configured; refusing to sign a pay whose settlement cannot be confirmed",
3034
3692
  PREPARED_OFFER_DIVERGED: "settlement offer does not match the terms verified before signing",
3035
3693
  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"
3694
+ SPEND_INTENT_VIOLATED: "offer does not match what this agent said it was buying",
3695
+ X402_UNAVAILABLE: "offer cannot be paid over x402 from the spend account (no x402 offer, sponsor gas pin, or current epoch)",
3696
+ UNSUPPORTED_SCOPE: "open-recipient grants cannot pay over x402"
3037
3697
  };
3038
3698
  function classifyNon402Status(status) {
3039
3699
  if (status >= 200 && status < 300)
@@ -3138,6 +3798,13 @@ function mapPayOpErrorToDelegated(code, detail) {
3138
3798
  return { code: "RESOURCE_REJECTED", detail: PUBLIC_DETAIL.RESOURCE_REJECTED };
3139
3799
  case PAY_OPERATION_ERROR_CODES.RESOURCE_UNAVAILABLE:
3140
3800
  return { code: "RESOURCE_UNAVAILABLE", detail: PUBLIC_DETAIL.RESOURCE_UNAVAILABLE };
3801
+ case PAY_OPERATION_ERROR_CODES.DIGEST_INVALID:
3802
+ return { code: "CHALLENGE_TAMPERED", detail: PUBLIC_DETAIL.CHALLENGE_TAMPERED };
3803
+ case PAY_OPERATION_ERROR_CODES.OFFER_EXPIRED:
3804
+ return { code: "CHALLENGE_EXPIRED", detail: PUBLIC_DETAIL.CHALLENGE_EXPIRED };
3805
+ // SPONSOR_GAS_INVALID and TX_OFFER_MISMATCH fall through to POLICY_REJECTED
3806
+ // for now; the caller that surfaces them (the tx-build/sign path) picks
3807
+ // their final outward code when it lands.
3141
3808
  // REQUEST_BINDING_VIOLATED, UNSUPPORTED_RAIL, PACKAGE_MISMATCH,
3142
3809
  // NETWORK_MISMATCH, COIN_TYPE_MISMATCH, OVERPAY_REJECTED, UNDERPAY_REJECTED,
3143
3810
  // ORIGIN_MISMATCH, and any unknown code all fall through to POLICY_REJECTED.
@@ -3145,7 +3812,7 @@ function mapPayOpErrorToDelegated(code, detail) {
3145
3812
  return { code: "POLICY_REJECTED", detail: PUBLIC_DETAIL.POLICY_REJECTED };
3146
3813
  }
3147
3814
  }
3148
- function sameAddress(a, b) {
3815
+ function sameAddress2(a, b) {
3149
3816
  try {
3150
3817
  return normalizeSuiAddress6(a) === normalizeSuiAddress6(b);
3151
3818
  } catch {
@@ -3161,7 +3828,7 @@ function atomicAmount(value) {
3161
3828
  }
3162
3829
  function sameStructTag(a, b) {
3163
3830
  try {
3164
- return normalizeStructTag5(a) === normalizeStructTag5(b);
3831
+ return normalizeStructTag6(a) === normalizeStructTag6(b);
3165
3832
  } catch {
3166
3833
  return false;
3167
3834
  }
@@ -3220,7 +3887,7 @@ async function resolveSigner(input) {
3220
3887
  return {
3221
3888
  address: kp.getPublicKey().toSuiAddress(),
3222
3889
  async signTransaction(txBytes) {
3223
- const { signature } = await kp.signTransaction(fromBase643(txBytes));
3890
+ const { signature } = await kp.signTransaction(fromBase646(txBytes));
3224
3891
  return signature;
3225
3892
  },
3226
3893
  async signPersonalMessage(message) {
@@ -3287,6 +3954,7 @@ async function buildSpendAccountPayKind(input) {
3287
3954
  throw new Error("grantInitialSharedVersion is required");
3288
3955
  }
3289
3956
  const hash = targetHashBytes(input.targetHash);
3957
+ const guarded = resolveGuardedSettleOfflineInputs(input);
3290
3958
  const target = payMoveCallTarget(input.packageId, "scoped");
3291
3959
  const clockVersion = input.clockInitialSharedVersion?.trim() || SUI_CLOCK_INITIAL_SHARED_VERSION;
3292
3960
  const tx = new Transaction4();
@@ -3295,6 +3963,16 @@ async function buildSpendAccountPayKind(input) {
3295
3963
  target,
3296
3964
  typeArguments: [input.coinType],
3297
3965
  arguments: [
3966
+ tx.sharedObjectRef({
3967
+ objectId: guarded.exclusionDirectoryId,
3968
+ initialSharedVersion: guarded.exclusionDirectoryInitialSharedVersion,
3969
+ mutable: false
3970
+ }),
3971
+ tx.sharedObjectRef({
3972
+ objectId: guarded.recipientRegistryId,
3973
+ initialSharedVersion: guarded.recipientRegistryInitialSharedVersion,
3974
+ mutable: true
3975
+ }),
3298
3976
  tx.sharedObjectRef({
3299
3977
  objectId: input.poolObjectId,
3300
3978
  initialSharedVersion: String(input.poolInitialSharedVersion),
@@ -3310,6 +3988,7 @@ async function buildSpendAccountPayKind(input) {
3310
3988
  tx.pure.u64(BigInt(input.amount)),
3311
3989
  tx.pure.vector("u8", Array.from(input.paymentIdHash)),
3312
3990
  tx.pure.vector("u8", Array.from(input.termsHash)),
3991
+ tx.pure.u64(BigInt(guarded.expiresAtMs)),
3313
3992
  tx.sharedObjectRef({
3314
3993
  objectId: SUI_CLOCK2,
3315
3994
  initialSharedVersion: clockVersion,
@@ -3318,7 +3997,7 @@ async function buildSpendAccountPayKind(input) {
3318
3997
  ]
3319
3998
  });
3320
3999
  const bytes = await tx.build({ onlyTransactionKind: true });
3321
- return { kindBytes: toBase643(bytes) };
4000
+ return { kindBytes: toBase645(bytes) };
3322
4001
  }
3323
4002
  async function buildSpendAccountSettleOpenRecipientKind(input) {
3324
4003
  if (!input.termsHash.length)
@@ -3333,9 +4012,10 @@ async function buildSpendAccountSettleOpenRecipientKind(input) {
3333
4012
  throw new Error("grantInitialSharedVersion is required");
3334
4013
  }
3335
4014
  if (!input.recipient || /^0x0+$/i.test(input.recipient)) {
3336
- throw new Error("settle_open_recipient_payment recipient must be a non-zero address");
4015
+ throw new Error("settle_any_recipient recipient must be a non-zero address");
3337
4016
  }
3338
4017
  const hash = targetHashBytes(input.targetHash);
4018
+ const guarded = resolveGuardedSettleOfflineInputs(input);
3339
4019
  const target = payMoveCallTarget(input.packageId, "open");
3340
4020
  const clockVersion = input.clockInitialSharedVersion?.trim() || SUI_CLOCK_INITIAL_SHARED_VERSION;
3341
4021
  const tx = new Transaction4();
@@ -3344,6 +4024,16 @@ async function buildSpendAccountSettleOpenRecipientKind(input) {
3344
4024
  target,
3345
4025
  typeArguments: [input.coinType],
3346
4026
  arguments: [
4027
+ tx.sharedObjectRef({
4028
+ objectId: guarded.exclusionDirectoryId,
4029
+ initialSharedVersion: guarded.exclusionDirectoryInitialSharedVersion,
4030
+ mutable: false
4031
+ }),
4032
+ tx.sharedObjectRef({
4033
+ objectId: guarded.recipientRegistryId,
4034
+ initialSharedVersion: guarded.recipientRegistryInitialSharedVersion,
4035
+ mutable: true
4036
+ }),
3347
4037
  tx.sharedObjectRef({
3348
4038
  objectId: input.poolObjectId,
3349
4039
  initialSharedVersion: String(input.poolInitialSharedVersion),
@@ -3359,6 +4049,7 @@ async function buildSpendAccountSettleOpenRecipientKind(input) {
3359
4049
  tx.pure.vector("u8", Array.from(input.paymentIdHash)),
3360
4050
  tx.pure.vector("u8", Array.from(input.termsHash)),
3361
4051
  tx.pure.vector("u8", Array.from(hash)),
4052
+ tx.pure.u64(BigInt(guarded.expiresAtMs)),
3362
4053
  tx.sharedObjectRef({
3363
4054
  objectId: SUI_CLOCK2,
3364
4055
  initialSharedVersion: clockVersion,
@@ -3367,14 +4058,14 @@ async function buildSpendAccountSettleOpenRecipientKind(input) {
3367
4058
  ]
3368
4059
  });
3369
4060
  const bytes = await tx.build({ onlyTransactionKind: true });
3370
- return { kindBytes: toBase643(bytes) };
4061
+ return { kindBytes: toBase645(bytes) };
3371
4062
  }
3372
4063
  async function buildSpendAccountPayTx(input) {
3373
4064
  if (!input.gas?.payment?.length) {
3374
4065
  throw new Error("agent gas payment objects are required for executable pay");
3375
4066
  }
3376
4067
  const { kindBytes } = await buildSpendAccountPayKind(input);
3377
- const tx = Transaction4.fromKind(fromBase643(kindBytes));
4068
+ const tx = Transaction4.fromKind(fromBase646(kindBytes));
3378
4069
  tx.setSender(input.delegate);
3379
4070
  tx.setGasOwner(input.gas.owner ?? input.delegate);
3380
4071
  tx.setGasBudget(BigInt(input.gas.budget ?? DEFAULT_GAS_BUDGET));
@@ -3384,7 +4075,7 @@ async function buildSpendAccountPayTx(input) {
3384
4075
  version: String(c.version),
3385
4076
  digest: c.digest
3386
4077
  })));
3387
- const txBytes = toBase643(await tx.build());
4078
+ const txBytes = toBase645(await tx.build());
3388
4079
  return { txBytes, kindBytes };
3389
4080
  }
3390
4081
  async function buildSpendAccountSettleOpenRecipientTx(input) {
@@ -3392,7 +4083,7 @@ async function buildSpendAccountSettleOpenRecipientTx(input) {
3392
4083
  throw new Error("agent gas payment objects are required for executable pay");
3393
4084
  }
3394
4085
  const { kindBytes } = await buildSpendAccountSettleOpenRecipientKind(input);
3395
- const tx = Transaction4.fromKind(fromBase643(kindBytes));
4086
+ const tx = Transaction4.fromKind(fromBase646(kindBytes));
3396
4087
  tx.setSender(input.delegate);
3397
4088
  tx.setGasOwner(input.gas.owner ?? input.delegate);
3398
4089
  tx.setGasBudget(BigInt(input.gas.budget ?? DEFAULT_GAS_BUDGET));
@@ -3402,23 +4093,59 @@ async function buildSpendAccountSettleOpenRecipientTx(input) {
3402
4093
  version: String(c.version),
3403
4094
  digest: c.digest
3404
4095
  })));
3405
- const txBytes = toBase643(await tx.build());
4096
+ const txBytes = toBase645(await tx.build());
4097
+ return { txBytes, kindBytes };
4098
+ }
4099
+ function readX402GasPin(offer) {
4100
+ const owner = offer.sponsorGasOwner?.trim();
4101
+ const budget = atomicAmount(offer.sponsorGasBudget ?? "");
4102
+ const price = atomicAmount(offer.sponsorGasPrice ?? "");
4103
+ if (!owner || budget === null || price === null || budget <= 0n || price <= 0n) {
4104
+ return null;
4105
+ }
4106
+ try {
4107
+ return {
4108
+ owner,
4109
+ budget,
4110
+ price,
4111
+ payment: decodeSponsorGasPayment(offer.sponsorGasPayment ?? "")
4112
+ };
4113
+ } catch {
4114
+ return null;
4115
+ }
4116
+ }
4117
+ async function buildSpendAccountX402PayTx(input) {
4118
+ const { kindBytes } = await buildSpendAccountPayKind(input);
4119
+ const tx = Transaction4.fromKind(fromBase646(kindBytes));
4120
+ tx.setSender(input.delegate);
4121
+ tx.setGasOwner(input.gas.owner);
4122
+ tx.setGasBudget(input.gas.budget);
4123
+ tx.setGasPrice(input.gas.price);
4124
+ tx.setGasPayment(input.gas.payment);
4125
+ tx.setExpiration({ Epoch: String(input.currentEpoch + X402_EXPIRATION_EPOCHS) });
4126
+ const txBytes = toBase645(await tx.build());
3406
4127
  return { txBytes, kindBytes };
3407
4128
  }
4129
+ function currentEpochReader(client) {
4130
+ return async () => {
4131
+ const { systemState } = await client.core.getCurrentSystemState();
4132
+ return BigInt(systemState.epoch);
4133
+ };
4134
+ }
3408
4135
  function assertPayTransaction(txBytes, expected) {
3409
4136
  let data;
3410
4137
  try {
3411
- data = Transaction4.from(fromBase643(txBytes)).getData();
4138
+ data = Transaction4.from(fromBase646(txBytes)).getData();
3412
4139
  } catch {
3413
4140
  throw new Error("transaction is not valid Sui transaction bytes");
3414
4141
  }
3415
- if (!data.sender || !sameAddress(data.sender, expected.delegate)) {
4142
+ if (!data.sender || !sameAddress2(data.sender, expected.delegate)) {
3416
4143
  throw new Error("transaction sender does not match delegate");
3417
4144
  }
3418
4145
  if (!data.commands || data.commands.length !== 1) {
3419
4146
  throw new Error("transaction must contain exactly one command");
3420
4147
  }
3421
- const wanted = payMoveCallTarget(expected.packageId, expected.accessMode);
4148
+ const wanted = payMoveCallTarget(expected.packageId, expected.accessMode, expected.guarded === true);
3422
4149
  const cmd = data.commands[0];
3423
4150
  if (cmd.$kind !== "MoveCall" || !cmd.MoveCall) {
3424
4151
  throw new Error("transaction is not a MoveCall");
@@ -3429,7 +4156,7 @@ function assertPayTransaction(txBytes, expected) {
3429
4156
  }
3430
4157
  let expectedKind;
3431
4158
  try {
3432
- expectedKind = Transaction4.fromKind(fromBase643(expected.kindBytes)).getData();
4159
+ expectedKind = Transaction4.fromKind(fromBase646(expected.kindBytes)).getData();
3433
4160
  } catch {
3434
4161
  throw new Error("intended pay kind baseline is invalid");
3435
4162
  }
@@ -3450,9 +4177,10 @@ function assertPayTransaction(txBytes, expected) {
3450
4177
  if (!expected.paymentIdHash.length || !expected.termsHash.length) {
3451
4178
  throw new Error("expected paymentIdHash and termsHash must be non-empty");
3452
4179
  }
4180
+ const expectedArgc = 11;
3453
4181
  const args = cmd.MoveCall.arguments;
3454
- if (args && args.length !== 8) {
3455
- throw new Error(`pay expects 8 arguments; got ${args.length}`);
4182
+ if (args && args.length !== expectedArgc) {
4183
+ throw new Error(`pay expects ${expectedArgc} arguments; got ${args.length}`);
3456
4184
  }
3457
4185
  }
3458
4186
  function asValidatedReceipt(value) {
@@ -3635,13 +4363,13 @@ function spendIntentViolation(offer, intent) {
3635
4363
  const ceiling = atomicAmount(intent.maxAmount);
3636
4364
  if (offered === null || ceiling === null || offered > ceiling)
3637
4365
  return "amount";
3638
- if (intent.recipient !== void 0 && !sameAddress(offer.payTo, intent.recipient)) {
4366
+ if (intent.recipient !== void 0 && !sameAddress2(offer.payTo, intent.recipient)) {
3639
4367
  return "recipient";
3640
4368
  }
3641
4369
  return null;
3642
4370
  }
3643
4371
  function preparedTermsDivergence(offer, terms) {
3644
- if (!sameAddress(offer.payTo, terms.recipient))
4372
+ if (!sameAddress2(offer.payTo, terms.recipient))
3645
4373
  return "recipient";
3646
4374
  const offered = atomicAmount(offer.amount);
3647
4375
  const verified = atomicAmount(terms.amount);
@@ -3724,6 +4452,10 @@ async function payDelegatedSpendAccount(args) {
3724
4452
  logInternal(logError, "resolve authority failed", err);
3725
4453
  return failed2("GRANT_DENIED", PUBLIC_DETAIL.GRANT_DENIED);
3726
4454
  }
4455
+ const x402Requested = args.dialect === "x402";
4456
+ if (x402Requested && snapshot.accessMode === "open") {
4457
+ return failed2("UNSUPPORTED_SCOPE", PUBLIC_DETAIL.UNSUPPORTED_SCOPE);
4458
+ }
3727
4459
  let poolInitialSharedVersion;
3728
4460
  let grantInitialSharedVersion;
3729
4461
  let clockInitialSharedVersion;
@@ -3797,7 +4529,7 @@ async function payDelegatedSpendAccount(args) {
3797
4529
  throw new Error("POLICY:no_target");
3798
4530
  }
3799
4531
  try {
3800
- normalizeStructTag5(offer2.asset);
4532
+ normalizeStructTag6(offer2.asset);
3801
4533
  } catch {
3802
4534
  throw new Error("POLICY:bad_asset");
3803
4535
  }
@@ -3817,7 +4549,15 @@ async function payDelegatedSpendAccount(args) {
3817
4549
  amount: input.amount,
3818
4550
  paymentIdHash: input.paymentIdHash,
3819
4551
  termsHash: input.termsHash,
3820
- recipient: input.recipient
4552
+ recipient: input.recipient,
4553
+ // Guarded settle inputs (undefined for an unguarded offer, which then
4554
+ // builds without the exclusion objects). resolvePayment set
4555
+ // these from the resolved exclusion registry.
4556
+ exclusionDirectoryId: input.exclusionDirectoryId,
4557
+ recipientRegistryId: input.recipientRegistryId,
4558
+ exclusionDirectoryInitialSharedVersion: input.exclusionDirectoryInitialSharedVersion,
4559
+ recipientRegistryInitialSharedVersion: input.recipientRegistryInitialSharedVersion,
4560
+ expiresAtMs: input.expiresAtMs
3821
4561
  };
3822
4562
  if (input.accessMode === "open") {
3823
4563
  return buildSpendAccountSettleOpenRecipientKind(kindInput);
@@ -3825,7 +4565,7 @@ async function payDelegatedSpendAccount(args) {
3825
4565
  return buildSpendAccountPayKind(kindInput);
3826
4566
  };
3827
4567
  try {
3828
- normalizeStructTag5(snapshot.coinType);
4568
+ normalizeStructTag6(snapshot.coinType);
3829
4569
  } catch {
3830
4570
  return failed2("POLICY_REJECTED", PUBLIC_DETAIL.POLICY_REJECTED);
3831
4571
  }
@@ -3839,9 +4579,13 @@ async function payDelegatedSpendAccount(args) {
3839
4579
  };
3840
4580
  const resolveDeps = {
3841
4581
  fetchImpl: memoizedFetch,
3842
- preferredDialect: "mpp",
4582
+ preferredDialect: x402Requested ? "x402" : "mpp",
3843
4583
  validateOffer: validateOfferCallback,
3844
- buildPayKind
4584
+ buildPayKind,
4585
+ // Guarded offers resolve the per-recipient exclusion registry
4586
+ // here; absent, resolvePayment fails closed (BUILD_FAILED) for such an offer
4587
+ // and never builds an unguarded settle. Unguarded offers never call it.
4588
+ resolveExclusionRegistry: deps.resolveExclusionRegistry
3845
4589
  };
3846
4590
  const resolution = await resolvePayment(resolveInput, resolveDeps);
3847
4591
  if (resolution.status === "unpaid_ok") {
@@ -3853,8 +4597,15 @@ async function payDelegatedSpendAccount(args) {
3853
4597
  paymentId: resolution.paymentId
3854
4598
  });
3855
4599
  }
3856
- const { offer, resolved: resolvedAuthority, kindBytes, paymentIdHash: payIdHash, termsHash: terms } = resolution;
4600
+ const { offer, resolved: resolvedAuthority, kindBytes, paymentIdHash: payIdHash, termsHash: terms, guarded: guardedResolution } = resolution;
3857
4601
  const paymentId = resolution.paymentId;
4602
+ const guardedBuildFields = guardedResolution ? {
4603
+ exclusionDirectoryId: guardedResolution.exclusionDirectoryId,
4604
+ recipientRegistryId: guardedResolution.recipientRegistryId,
4605
+ exclusionDirectoryInitialSharedVersion: guardedResolution.exclusionDirectoryInitialSharedVersion,
4606
+ recipientRegistryInitialSharedVersion: guardedResolution.recipientRegistryInitialSharedVersion,
4607
+ expiresAtMs: guardedResolution.expiresAtMs
4608
+ } : {};
3858
4609
  const isOpen = snapshot.accessMode === "open";
3859
4610
  const resolved = {
3860
4611
  poolObjectId: resolvedAuthority.poolObjectId,
@@ -3866,6 +4617,13 @@ async function payDelegatedSpendAccount(args) {
3866
4617
  recipient: resolvedAuthority.recipient
3867
4618
  };
3868
4619
  const offerDigest = offer.digest.trim();
4620
+ let x402Terms = null;
4621
+ if (x402Requested) {
4622
+ x402Terms = await x402Preflight(resolution.dialect, offer, deps, logError);
4623
+ if (!x402Terms) {
4624
+ return failed2("X402_UNAVAILABLE", PUBLIC_DETAIL.X402_UNAVAILABLE, { paymentId });
4625
+ }
4626
+ }
3869
4627
  const ledger = deps.paymentLedger;
3870
4628
  if (!ledger || typeof ledger.reserve !== "function" || typeof ledger.complete !== "function") {
3871
4629
  return failed2("PAYMENT_LEDGER_REQUIRED", PUBLIC_DETAIL.PAYMENT_LEDGER_REQUIRED, { paymentId });
@@ -3882,7 +4640,9 @@ async function payDelegatedSpendAccount(args) {
3882
4640
  if (reserveOutcome.state === "done") {
3883
4641
  const prior = asValidatedLedgerResult(reserveOutcome.prior, paymentId);
3884
4642
  if (!prior) {
3885
- return ambiguous("PAYMENT_ATTEMPT_IN_FLIGHT", PUBLIC_DETAIL.PAYMENT_ATTEMPT_IN_FLIGHT, { paymentId });
4643
+ return ambiguous("PAYMENT_ATTEMPT_IN_FLIGHT", PUBLIC_DETAIL.PAYMENT_ATTEMPT_IN_FLIGHT, {
4644
+ paymentId
4645
+ });
3886
4646
  }
3887
4647
  return {
3888
4648
  ...prior,
@@ -3892,10 +4652,14 @@ async function payDelegatedSpendAccount(args) {
3892
4652
  };
3893
4653
  }
3894
4654
  if (reserveOutcome.state === "in_flight") {
3895
- return ambiguous("PAYMENT_ATTEMPT_IN_FLIGHT", PUBLIC_DETAIL.PAYMENT_ATTEMPT_IN_FLIGHT, { paymentId });
4655
+ return ambiguous("PAYMENT_ATTEMPT_IN_FLIGHT", PUBLIC_DETAIL.PAYMENT_ATTEMPT_IN_FLIGHT, {
4656
+ paymentId
4657
+ });
3896
4658
  }
3897
4659
  if (reserveOutcome.state !== "reserved") {
3898
- logInternal(logError, "payment attempt ledger reserve returned unexpected state", { state: reserveOutcome.state });
4660
+ logInternal(logError, "payment attempt ledger reserve returned unexpected state", {
4661
+ state: reserveOutcome.state
4662
+ });
3899
4663
  return failed2("PAYMENT_LEDGER_FAILED", PUBLIC_DETAIL.PAYMENT_LEDGER_FAILED, {
3900
4664
  paymentId
3901
4665
  });
@@ -3905,27 +4669,6 @@ async function payDelegatedSpendAccount(args) {
3905
4669
  await releaseReservation(ledger, ownedPaymentId, logError);
3906
4670
  return failed2(code, detail, { paymentId });
3907
4671
  };
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
4672
  const kindBuildInput = {
3930
4673
  packageId: session.packageId,
3931
4674
  delegate: resolved.delegateAddress,
@@ -3940,8 +4683,69 @@ async function payDelegatedSpendAccount(args) {
3940
4683
  amount: offer.amount,
3941
4684
  paymentIdHash: payIdHash,
3942
4685
  termsHash: terms,
3943
- recipient: resolved.recipient
4686
+ recipient: resolved.recipient,
4687
+ ...guardedBuildFields
4688
+ };
4689
+ const expectedPay = {
4690
+ packageId: session.packageId,
4691
+ delegate: resolved.delegateAddress,
4692
+ poolObjectId: resolved.poolObjectId,
4693
+ grantObjectId: resolved.grantObjectId,
4694
+ policyId: resolved.onChainPolicyId,
4695
+ targetHash: resolved.targetHash,
4696
+ amount: offer.amount,
4697
+ paymentIdHash: payIdHash,
4698
+ termsHash: terms,
4699
+ coinType: resolved.coinType,
4700
+ kindBytes,
4701
+ accessMode: snapshot.accessMode,
4702
+ recipient: resolved.recipient,
4703
+ guarded: guardedResolution !== void 0
3944
4704
  };
4705
+ if (x402Terms) {
4706
+ return payX402Reserved({
4707
+ session,
4708
+ deps,
4709
+ fetchImpl,
4710
+ resourceUrl,
4711
+ request,
4712
+ ledger,
4713
+ paymentId,
4714
+ offer,
4715
+ expected: expectedPay,
4716
+ buildInput: {
4717
+ ...kindBuildInput,
4718
+ gas: x402Terms.gas,
4719
+ currentEpoch: x402Terms.currentEpoch
4720
+ },
4721
+ settlement: {
4722
+ status: "success",
4723
+ amount: offer.amount,
4724
+ recipient: resolved.recipient,
4725
+ coinType: resolved.coinType,
4726
+ asset: offer.asset,
4727
+ grantObjectId: resolved.grantObjectId,
4728
+ poolObjectId: resolved.poolObjectId
4729
+ },
4730
+ onProgress: args.onX402Progress,
4731
+ abort: abortBeforeSign
4732
+ });
4733
+ }
4734
+ const gasMode = resolveGasMode(deps);
4735
+ const execute = deps.executeTx;
4736
+ if (gasMode === "none") {
4737
+ return abortBeforeSign("EXECUTE_UNAVAILABLE", PUBLIC_DETAIL.EXECUTE_UNAVAILABLE);
4738
+ }
4739
+ if (gasMode === "agent" && !execute) {
4740
+ return abortBeforeSign("EXECUTE_UNAVAILABLE", PUBLIC_DETAIL.EXECUTE_UNAVAILABLE);
4741
+ }
4742
+ if (typeof deps.getSettledTx !== "function") {
4743
+ return abortBeforeSign("FINALITY_UNAVAILABLE", PUBLIC_DETAIL.FINALITY_UNAVAILABLE);
4744
+ }
4745
+ const signer = await resolveDelegateSigner(session.signer, resolved.delegateAddress, logError);
4746
+ if (typeof signer === "string") {
4747
+ return abortBeforeSign(signer, PUBLIC_DETAIL[signer]);
4748
+ }
3945
4749
  let txBytes;
3946
4750
  let sponsoredExecuteToken;
3947
4751
  let sponsoredCreateDigest;
@@ -4015,7 +4819,7 @@ async function payDelegatedSpendAccount(args) {
4015
4819
  return abortBeforeSign("GRANT_DENIED", grantDeniedDetail(err));
4016
4820
  }
4017
4821
  }
4018
- const allowedPayTarget = payMoveCallTarget(session.packageId, snapshot.accessMode);
4822
+ const allowedPayTarget = payMoveCallTarget(session.packageId, snapshot.accessMode, guardedResolution !== void 0);
4019
4823
  try {
4020
4824
  const createRes = await fetchSponsor(`${base}/api/sponsor`, {
4021
4825
  method: "POST",
@@ -4080,21 +4884,7 @@ async function payDelegatedSpendAccount(args) {
4080
4884
  }
4081
4885
  }
4082
4886
  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
- });
4887
+ assertPayTransaction(txBytes, expectedPay);
4098
4888
  } catch (err) {
4099
4889
  logInternal(logError, "pre-sign pay validation failed", err);
4100
4890
  return abortBeforeSign(gasMode === "sponsored" ? "SPONSORED_TX_MISMATCH" : "TX_MISMATCH", gasMode === "sponsored" ? PUBLIC_DETAIL.SPONSORED_TX_MISMATCH : PUBLIC_DETAIL.TX_MISMATCH);
@@ -4166,7 +4956,7 @@ async function payDelegatedSpendAccount(args) {
4166
4956
  txDigest: errDigest ?? finalDigest
4167
4957
  }), logError);
4168
4958
  }
4169
- const getFinalized = deps.getFinalizedTx;
4959
+ const getFinalized = deps.getSettledTx;
4170
4960
  let finalized = null;
4171
4961
  try {
4172
4962
  for (let attempt = 0; attempt < FINALIZATION_ATTEMPTS; attempt += 1) {
@@ -4177,7 +4967,7 @@ async function payDelegatedSpendAccount(args) {
4177
4967
  await delay(FINALIZATION_DELAY_MS);
4178
4968
  }
4179
4969
  } catch (err) {
4180
- logInternal(logError, "getFinalizedTx threw after broadcast", err);
4970
+ logInternal(logError, "getSettledTx threw after broadcast", err);
4181
4971
  return completeAttempt(ledger, paymentId, ambiguous("FINALITY_AMBIGUOUS", PUBLIC_DETAIL.FINALITY_AMBIGUOUS, {
4182
4972
  paymentId,
4183
4973
  txDigest: finalDigest
@@ -4232,10 +5022,18 @@ async function payDelegatedSpendAccount(args) {
4232
5022
  second = await fetchImpl(resourceUrl, paidRequestInit(request, { [MPP_CREDENTIAL_HEADER]: proof }));
4233
5023
  } catch (err) {
4234
5024
  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);
5025
+ return completeAttempt(ledger, paymentId, ambiguous("PROOF_PRESENTATION_FAILED", PUBLIC_DETAIL.PROOF_PRESENTATION_FAILED, {
5026
+ paymentId,
5027
+ txDigest: finalDigest,
5028
+ settlement
5029
+ }), logError);
4236
5030
  }
4237
5031
  if (second.status === 503) {
4238
- return completeAttempt(ledger, paymentId, ambiguous(gatewayRefusedCode(second, logError), PUBLIC_DETAIL.PROOF_PRESENTATION_FAILED, { paymentId, txDigest: finalDigest, settlement }), logError);
5032
+ return completeAttempt(ledger, paymentId, ambiguous(gatewayRefusedCode(second, logError), PUBLIC_DETAIL.PROOF_PRESENTATION_FAILED, {
5033
+ paymentId,
5034
+ txDigest: finalDigest,
5035
+ settlement
5036
+ }), logError);
4239
5037
  }
4240
5038
  let receipt = null;
4241
5039
  try {
@@ -4244,14 +5042,22 @@ async function payDelegatedSpendAccount(args) {
4244
5042
  logInternal(logError, "read receipt failed", err);
4245
5043
  }
4246
5044
  if (second.status !== 200) {
4247
- return completeAttempt(ledger, paymentId, ambiguous(gatewayRefusedCode(second, logError), PUBLIC_DETAIL.GATEWAY_REFUSED, { paymentId, txDigest: finalDigest, settlement }), logError);
5045
+ return completeAttempt(ledger, paymentId, ambiguous(gatewayRefusedCode(second, logError), PUBLIC_DETAIL.GATEWAY_REFUSED, {
5046
+ paymentId,
5047
+ txDigest: finalDigest,
5048
+ settlement
5049
+ }), logError);
4248
5050
  }
4249
5051
  let body;
4250
5052
  try {
4251
5053
  body = await readBody(second);
4252
5054
  } catch (err) {
4253
5055
  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);
5056
+ return completeAttempt(ledger, paymentId, ambiguous("PROOF_PRESENTATION_FAILED", PUBLIC_DETAIL.PROOF_PRESENTATION_FAILED, {
5057
+ paymentId,
5058
+ txDigest: finalDigest,
5059
+ settlement
5060
+ }), logError);
4255
5061
  }
4256
5062
  return completeAttempt(ledger, paymentId, {
4257
5063
  status: "ok",
@@ -4263,6 +5069,179 @@ async function payDelegatedSpendAccount(args) {
4263
5069
  settlement
4264
5070
  }, logError);
4265
5071
  }
5072
+ async function resolveDelegateSigner(input, delegateAddress, logError) {
5073
+ let signer;
5074
+ try {
5075
+ signer = await resolveSigner(input);
5076
+ } catch (err) {
5077
+ logInternal(logError, "resolve signer failed", err);
5078
+ return "SIGN_FAILED";
5079
+ }
5080
+ return sameAddress2(signer.address, delegateAddress) ? signer : "POLICY_REJECTED";
5081
+ }
5082
+ async function x402Preflight(dialect, offer, deps, logError) {
5083
+ if (dialect !== "x402") {
5084
+ logInternal(logError, "x402 requested but the 402 carried no x402 offer");
5085
+ return null;
5086
+ }
5087
+ const gas = readX402GasPin(offer);
5088
+ if (!gas) {
5089
+ logInternal(logError, "x402 offer lacks readable sponsor gas pins");
5090
+ return null;
5091
+ }
5092
+ if (typeof deps.getCurrentEpoch !== "function") {
5093
+ logInternal(logError, "x402 needs a current-epoch reader");
5094
+ return null;
5095
+ }
5096
+ try {
5097
+ return { gas, currentEpoch: BigInt(await deps.getCurrentEpoch()) };
5098
+ } catch (err) {
5099
+ logInternal(logError, "read current epoch failed", err);
5100
+ return null;
5101
+ }
5102
+ }
5103
+ function emitX402Progress(ctx, event) {
5104
+ if (!ctx.onProgress)
5105
+ return;
5106
+ try {
5107
+ ctx.onProgress(event);
5108
+ } catch (err) {
5109
+ logInternal(ctx.deps.logError, "x402 progress callback threw", err);
5110
+ }
5111
+ }
5112
+ async function payX402Reserved(ctx) {
5113
+ const logError = ctx.deps.logError;
5114
+ const signer = await resolveDelegateSigner(ctx.session.signer, ctx.expected.delegate, logError);
5115
+ if (typeof signer === "string")
5116
+ return ctx.abort(signer, PUBLIC_DETAIL[signer]);
5117
+ let txBytes;
5118
+ try {
5119
+ const build = ctx.deps.buildX402PayTx ?? buildSpendAccountX402PayTx;
5120
+ txBytes = (await build(ctx.buildInput)).txBytes;
5121
+ } catch (err) {
5122
+ logInternal(logError, "x402 pay build failed", err);
5123
+ return ctx.abort("BUILD_FAILED", PUBLIC_DETAIL.BUILD_FAILED);
5124
+ }
5125
+ try {
5126
+ assertPayTransaction(txBytes, ctx.expected);
5127
+ } catch (err) {
5128
+ logInternal(logError, "pre-sign x402 pay validation failed", err);
5129
+ return ctx.abort("TX_MISMATCH", PUBLIC_DETAIL.TX_MISMATCH);
5130
+ }
5131
+ emitX402Progress(ctx, { stage: "built", txBytes });
5132
+ if (ctx.session.assertLive) {
5133
+ try {
5134
+ await ctx.session.assertLive();
5135
+ } catch (err) {
5136
+ logInternal(logError, "assertLive failed at sign", err);
5137
+ return ctx.abort("GRANT_DENIED", grantDeniedDetail(err));
5138
+ }
5139
+ }
5140
+ let signature;
5141
+ try {
5142
+ signature = await signer.signTransaction(txBytes);
5143
+ } catch (err) {
5144
+ logInternal(logError, "delegate sign failed", err);
5145
+ return ctx.abort("SIGN_FAILED", PUBLIC_DETAIL.SIGN_FAILED);
5146
+ }
5147
+ emitX402Progress(ctx, { stage: "signed", signature });
5148
+ let header;
5149
+ try {
5150
+ header = encodeCredential({
5151
+ challengeId: ctx.offer.challengeId,
5152
+ offer: ctx.offer,
5153
+ txBytes,
5154
+ signature,
5155
+ payer: ctx.expected.delegate
5156
+ });
5157
+ } catch (err) {
5158
+ logInternal(logError, "encode x402 credential failed", err);
5159
+ return ctx.abort("BUILD_FAILED", PUBLIC_DETAIL.BUILD_FAILED);
5160
+ }
5161
+ return recordX402Answer(ctx, await presentX402Credential(ctx, header));
5162
+ }
5163
+ function isX402Failure(value) {
5164
+ return value.success === false;
5165
+ }
5166
+ async function presentX402Credential(ctx, header) {
5167
+ const attempts = ctx.deps.x402PresentAttempts ?? X402_PRESENT_ATTEMPTS;
5168
+ const sleep = ctx.deps.sleep ?? delay;
5169
+ let txDigest;
5170
+ for (let attempt = 1; attempt <= attempts; attempt += 1) {
5171
+ emitX402Progress(ctx, { stage: "presented", header, attempt });
5172
+ let response;
5173
+ try {
5174
+ response = await ctx.fetchImpl(ctx.resourceUrl, paidRequestInit(ctx.request, { [X402_PAYMENT_HEADER]: header }));
5175
+ } catch (err) {
5176
+ logInternal(ctx.deps.logError, "x402 credential presentation failed", err);
5177
+ return { kind: "unresolved", txDigest };
5178
+ }
5179
+ const paymentResponse = response.headers.get(X402_RESPONSE_HEADER);
5180
+ emitX402Progress(ctx, {
5181
+ stage: "answered",
5182
+ status: response.status,
5183
+ paymentResponse,
5184
+ attempt
5185
+ });
5186
+ const decoded = paymentResponse ? decodeSettlementResponse(paymentResponse) : null;
5187
+ if (decoded && isX402Failure(decoded) && decoded.transaction.trim()) {
5188
+ txDigest = decoded.transaction.trim();
5189
+ }
5190
+ if (response.status !== 503) {
5191
+ return classifyX402Answer(response, decoded, {
5192
+ challengeId: ctx.offer.challengeId,
5193
+ txDigest,
5194
+ firstPresentation: attempt === 1
5195
+ });
5196
+ }
5197
+ if (attempt < attempts)
5198
+ await sleep(X402_PRESENT_DELAY_MS);
5199
+ }
5200
+ return { kind: "unresolved", txDigest };
5201
+ }
5202
+ function classifyX402Answer(response, decoded, seen) {
5203
+ const receipt = asValidatedReceipt(decoded);
5204
+ if (response.ok && receipt && receipt.challengeId === seen.challengeId) {
5205
+ return { kind: "settled", receipt, response };
5206
+ }
5207
+ if (seen.firstPresentation && response.status === 402 && decoded && isX402Failure(decoded) && !decoded.transaction.trim()) {
5208
+ return { kind: "refused", response };
5209
+ }
5210
+ return { kind: "unresolved", txDigest: seen.txDigest };
5211
+ }
5212
+ async function recordX402Answer(ctx, answer) {
5213
+ const { ledger, paymentId } = ctx;
5214
+ const logError = ctx.deps.logError;
5215
+ if (answer.kind === "unresolved") {
5216
+ return completeAttempt(ledger, paymentId, ambiguous("EXECUTE_AMBIGUOUS", PUBLIC_DETAIL.EXECUTE_AMBIGUOUS, {
5217
+ paymentId,
5218
+ txDigest: answer.txDigest
5219
+ }), logError);
5220
+ }
5221
+ if (answer.kind === "refused") {
5222
+ return ctx.abort(gatewayRefusedCode(answer.response, logError), PUBLIC_DETAIL.GATEWAY_REFUSED);
5223
+ }
5224
+ const settlement = {
5225
+ ...ctx.settlement,
5226
+ txDigest: answer.receipt.txDigest
5227
+ };
5228
+ let body;
5229
+ try {
5230
+ body = await readBody(answer.response);
5231
+ } catch (err) {
5232
+ logInternal(logError, "read delivery body failed", err);
5233
+ return completeAttempt(ledger, paymentId, ambiguous("PROOF_PRESENTATION_FAILED", PUBLIC_DETAIL.PROOF_PRESENTATION_FAILED, { paymentId, txDigest: settlement.txDigest, settlement }), logError);
5234
+ }
5235
+ return completeAttempt(ledger, paymentId, {
5236
+ status: "ok",
5237
+ paid: true,
5238
+ body,
5239
+ receipt: answer.receipt,
5240
+ txDigest: settlement.txDigest,
5241
+ paymentId,
5242
+ settlement
5243
+ }, logError);
5244
+ }
4266
5245
  function extractDigestFromError(err) {
4267
5246
  if (!err || typeof err !== "object")
4268
5247
  return void 0;
@@ -4279,9 +5258,35 @@ function settleOpenRecipientTarget(packageId) {
4279
5258
  const fn = spendAccountDebitFunction({ accessMode: "open" });
4280
5259
  return `${normalizeSuiAddress6(packageId)}::spend_account::${fn}`;
4281
5260
  }
4282
- function payMoveCallTarget(packageId, accessMode = "scoped") {
5261
+ function payMoveCallTarget(packageId, accessMode = "scoped", _guarded = false) {
4283
5262
  return accessMode === "open" ? settleOpenRecipientTarget(packageId) : payTarget(packageId);
4284
5263
  }
5264
+ function resolveGuardedSettleOfflineInputs(input) {
5265
+ const core = [input.exclusionDirectoryId, input.recipientRegistryId, input.expiresAtMs];
5266
+ const present = core.filter((v) => v !== void 0).length;
5267
+ if (present !== 3) {
5268
+ throw new Error("guarded settle requires exclusionDirectoryId, recipientRegistryId and expiresAtMs together (all or none)");
5269
+ }
5270
+ const expires = BigInt(input.expiresAtMs);
5271
+ if (expires <= 0n) {
5272
+ throw new Error("guarded settle expiresAtMs must be a positive epoch-ms value");
5273
+ }
5274
+ if (/^0x0+$/i.test(input.exclusionDirectoryId) || /^0x0+$/i.test(input.recipientRegistryId)) {
5275
+ throw new Error("guarded settle directory and registry ids must be non-zero");
5276
+ }
5277
+ const dirVer = input.exclusionDirectoryInitialSharedVersion;
5278
+ const regVer = input.recipientRegistryInitialSharedVersion;
5279
+ if (!dirVer || !String(dirVer).trim() || !regVer || !String(regVer).trim()) {
5280
+ throw new Error("guarded offline settle requires directory and registry initial shared versions");
5281
+ }
5282
+ return {
5283
+ exclusionDirectoryId: input.exclusionDirectoryId,
5284
+ recipientRegistryId: input.recipientRegistryId,
5285
+ exclusionDirectoryInitialSharedVersion: String(dirVer),
5286
+ recipientRegistryInitialSharedVersion: String(regVer),
5287
+ expiresAtMs: input.expiresAtMs
5288
+ };
5289
+ }
4285
5290
  var PreparedVerificationUnavailableError = class extends Error {
4286
5291
  code = DELEGATED_PAY_ERROR_CODES.PREPARED_VERIFY_UNAVAILABLE;
4287
5292
  constructor(message, options) {
@@ -4299,17 +5304,42 @@ function preparedPureBytes(input) {
4299
5304
  if (!input || input.$kind !== "Pure" || !input.Pure?.bytes)
4300
5305
  return null;
4301
5306
  try {
4302
- return fromBase643(input.Pure.bytes);
5307
+ return fromBase646(input.Pure.bytes);
4303
5308
  } catch {
4304
5309
  return null;
4305
5310
  }
4306
5311
  }
5312
+ function preparedPureVectorU8(input) {
5313
+ const raw = preparedPureBytes(input);
5314
+ try {
5315
+ return Uint8Array.from(bcs5.vector(bcs5.u8()).parse(raw));
5316
+ } catch {
5317
+ }
5318
+ return null;
5319
+ }
5320
+ function sameBytes(a, b) {
5321
+ return Buffer.from(a).equals(Buffer.from(b));
5322
+ }
5323
+ function toHashBytes(val) {
5324
+ if (typeof val === "string") {
5325
+ const clean = val.startsWith("0x") ? val.slice(2) : val;
5326
+ return Uint8Array.from(Buffer.from(clean, "hex"));
5327
+ }
5328
+ return val;
5329
+ }
5330
+ function toTargetHashBytes(val) {
5331
+ if (typeof val === "string") {
5332
+ const clean = val.startsWith("0x") ? val.slice(2) : val;
5333
+ return targetHashBytes(clean);
5334
+ }
5335
+ return val;
5336
+ }
4307
5337
  function preparedPureAddress(input) {
4308
5338
  const raw = preparedPureBytes(input);
4309
5339
  if (!raw)
4310
5340
  return null;
4311
5341
  try {
4312
- return normalizeSuiAddress6(bcs4.Address.parse(raw));
5342
+ return normalizeSuiAddress6(bcs5.Address.parse(raw));
4313
5343
  } catch {
4314
5344
  return null;
4315
5345
  }
@@ -4319,7 +5349,7 @@ function preparedPureU64(input) {
4319
5349
  if (!raw)
4320
5350
  return null;
4321
5351
  try {
4322
- return BigInt(bcs4.u64().parse(raw));
5352
+ return BigInt(bcs5.u64().parse(raw));
4323
5353
  } catch {
4324
5354
  return null;
4325
5355
  }
@@ -4339,9 +5369,12 @@ function preparedSharedObjectId(input) {
4339
5369
  }
4340
5370
  async function verifyPreparedSettlementKind(input) {
4341
5371
  const accessMode = input.expected.accessMode ?? "open";
5372
+ if (accessMode !== "scoped" && accessMode !== "open") {
5373
+ throw new Error(`unsupported accessMode: ${String(accessMode)}`);
5374
+ }
4342
5375
  let data;
4343
5376
  try {
4344
- data = Transaction4.fromKind(fromBase643(input.kindBytes)).getData();
5377
+ data = Transaction4.fromKind(fromBase646(input.kindBytes)).getData();
4345
5378
  } catch {
4346
5379
  throw new Error("prepared transaction is not a valid settlement kind");
4347
5380
  }
@@ -4365,16 +5398,25 @@ async function verifyPreparedSettlementKind(input) {
4365
5398
  return void 0;
4366
5399
  return inputs[idx];
4367
5400
  };
4368
- const grantObjectId = preparedSharedObjectId(resolve(1));
5401
+ const poolObjectId = preparedSharedObjectId(resolve(2));
5402
+ if (input.expected.poolObjectId) {
5403
+ if (!poolObjectId || !sameAddress2(poolObjectId, input.expected.poolObjectId)) {
5404
+ throw new Error(`prepared pool ${poolObjectId ?? "(missing)"} does not match expected pool ${input.expected.poolObjectId}`);
5405
+ }
5406
+ }
5407
+ const grantObjectId = preparedSharedObjectId(resolve(3));
4369
5408
  if (!grantObjectId) {
4370
5409
  throw new Error("prepared transaction does not name a grant object");
4371
5410
  }
5411
+ if (input.expected.grantObjectId && !sameAddress2(grantObjectId, input.expected.grantObjectId)) {
5412
+ throw new Error(`prepared grant ${grantObjectId} does not match expected grant ${input.expected.grantObjectId}`);
5413
+ }
4372
5414
  if (accessMode === "open") {
4373
- const recipient = preparedPureAddress(resolve(2));
4374
- if (!recipient || !sameAddress(recipient, input.expected.recipient)) {
5415
+ const recipient = preparedPureAddress(resolve(4));
5416
+ if (!recipient || !sameAddress2(recipient, input.expected.recipient)) {
4375
5417
  throw new Error(`prepared recipient ${recipient ?? "(missing)"} does not match the requested recipient`);
4376
5418
  }
4377
- const amount = preparedPureU64(resolve(3));
5419
+ const amount = preparedPureU64(resolve(5));
4378
5420
  let expectedAmount;
4379
5421
  try {
4380
5422
  expectedAmount = BigInt(input.expected.amount);
@@ -4385,7 +5427,21 @@ async function verifyPreparedSettlementKind(input) {
4385
5427
  throw new Error(`prepared amount ${amount ?? "(missing)"} does not match the requested amount`);
4386
5428
  }
4387
5429
  } else {
4388
- const amount = preparedPureU64(resolve(4));
5430
+ if (input.expected.onChainPolicyId !== void 0) {
5431
+ const policyId = preparedPureU64(resolve(4));
5432
+ const expectedPolicyId = BigInt(input.expected.onChainPolicyId);
5433
+ if (policyId !== expectedPolicyId) {
5434
+ throw new Error(`prepared policy_id ${policyId ?? "(missing)"} does not match expected onChainPolicyId`);
5435
+ }
5436
+ }
5437
+ if (input.expected.targetHash) {
5438
+ const targetHash = preparedPureVectorU8(resolve(5));
5439
+ const expectedBytes = toTargetHashBytes(input.expected.targetHash);
5440
+ if (!targetHash || !sameBytes(targetHash, expectedBytes)) {
5441
+ throw new Error("prepared target_hash does not match expected targetHash");
5442
+ }
5443
+ }
5444
+ const amount = preparedPureU64(resolve(6));
4389
5445
  let expectedAmount;
4390
5446
  try {
4391
5447
  expectedAmount = BigInt(input.expected.amount);
@@ -4395,6 +5451,20 @@ async function verifyPreparedSettlementKind(input) {
4395
5451
  if (amount === null || amount !== expectedAmount) {
4396
5452
  throw new Error(`prepared amount ${amount ?? "(missing)"} does not match the requested amount`);
4397
5453
  }
5454
+ if (input.expected.paymentIdHash) {
5455
+ const paymentIdHash2 = preparedPureVectorU8(resolve(7));
5456
+ const expectedBytes = toHashBytes(input.expected.paymentIdHash);
5457
+ if (!paymentIdHash2 || !sameBytes(paymentIdHash2, expectedBytes)) {
5458
+ throw new Error("prepared payment_id_hash does not match expected paymentIdHash");
5459
+ }
5460
+ }
5461
+ if (input.expected.termsHash) {
5462
+ const termsHash2 = preparedPureVectorU8(resolve(8));
5463
+ const expectedBytes = toHashBytes(input.expected.termsHash);
5464
+ if (!termsHash2 || !sameBytes(termsHash2, expectedBytes)) {
5465
+ throw new Error("prepared terms_hash does not match expected termsHash");
5466
+ }
5467
+ }
4398
5468
  }
4399
5469
  let grant;
4400
5470
  try {
@@ -4405,9 +5475,12 @@ async function verifyPreparedSettlementKind(input) {
4405
5475
  if (!grant) {
4406
5476
  throw new Error("unsigned getObject could not read the named grant");
4407
5477
  }
4408
- if (!grant.delegateAddress || !sameAddress(grant.delegateAddress, input.expected.delegateAddress)) {
5478
+ if (!grant.delegateAddress || !sameAddress2(grant.delegateAddress, input.expected.delegateAddress)) {
4409
5479
  throw new Error(`grant delegate ${grant.delegateAddress ?? "(missing)"} does not match the agent address`);
4410
5480
  }
5481
+ if (input.expected.poolObjectId && grant.poolObjectId && !sameAddress2(grant.poolObjectId, input.expected.poolObjectId)) {
5482
+ throw new Error(`grant pool ${grant.poolObjectId} does not match expected pool ${input.expected.poolObjectId}`);
5483
+ }
4411
5484
  }
4412
5485
  function createMemoryLedger() {
4413
5486
  const map = /* @__PURE__ */ new Map();
@@ -4472,7 +5545,7 @@ import {
4472
5545
  } from "fs";
4473
5546
  import { decodeSuiPrivateKey } from "@mysten/sui/cryptography";
4474
5547
  import { Ed25519Keypair as Ed25519Keypair2 } from "@mysten/sui/keypairs/ed25519";
4475
- import { fromBase64 as fromBase644 } from "@mysten/sui/utils";
5548
+ import { fromBase64 as fromBase647 } from "@mysten/sui/utils";
4476
5549
  import { bytesToHex as bytesToHex4 } from "@noble/hashes/utils.js";
4477
5550
 
4478
5551
  // src/config.ts
@@ -4584,6 +5657,8 @@ function loadMcpConfig(env = process.env, argv = []) {
4584
5657
  const preset = MCP_TARGET_PRESETS[target];
4585
5658
  const gatewayUrl = env.SUIPAY_GATEWAY_URL?.trim() || preset.gatewayUrl;
4586
5659
  const consoleUrl = env.SUIPAY_CONSOLE_URL?.trim() || preset.consoleUrl;
5660
+ const rawDir = env.SUIPAY_EXCLUSION_DIRECTORY_OBJECT_ID?.trim();
5661
+ if (rawDir) assertValidPackageId("SUIPAY_EXCLUSION_DIRECTORY_OBJECT_ID", rawDir);
4587
5662
  return {
4588
5663
  target,
4589
5664
  consoleUrl,
@@ -4596,9 +5671,42 @@ function loadMcpConfig(env = process.env, argv = []) {
4596
5671
  sponsorUrl: env.SUIPAY_SPONSOR_URL ?? `${gatewayUrl.replace(/\/$/, "")}/api/sponsor`,
4597
5672
  minFunded: BigInt(env.SUIPAY_MIN_FUNDED ?? "0"),
4598
5673
  minMaxPerPayment: BigInt(env.SUIPAY_MIN_MAX_PER_PAYMENT ?? "0"),
4599
- label: env.SUIPAY_LABEL ?? "SuiPay MCP"
5674
+ label: env.SUIPAY_LABEL ?? "SuiPay MCP",
5675
+ exclusionDirectoryObjectId: rawDir || null
4600
5676
  };
4601
5677
  }
5678
+ var OBJECT_ID = /^0x(?:[0-9a-fA-F]{1,64})$/;
5679
+ async function publishedExclusionDirectoryId(gatewayUrl, fetchImpl = fetch) {
5680
+ let url;
5681
+ try {
5682
+ url = new URL("/v1/delegate/deployment", gatewayUrl).toString();
5683
+ } catch {
5684
+ return null;
5685
+ }
5686
+ let response;
5687
+ try {
5688
+ response = await fetchImpl(url, {
5689
+ method: "GET",
5690
+ redirect: "manual",
5691
+ signal: AbortSignal.timeout(1500)
5692
+ });
5693
+ } catch {
5694
+ return null;
5695
+ }
5696
+ if (response.status !== 200) return null;
5697
+ let body;
5698
+ try {
5699
+ body = await response.json();
5700
+ } catch {
5701
+ return null;
5702
+ }
5703
+ const id = body && typeof body === "object" && "exclusionDirectoryObjectId" in body ? body.exclusionDirectoryObjectId : void 0;
5704
+ if (typeof id !== "string" || !OBJECT_ID.test(id.trim())) return null;
5705
+ return id.trim();
5706
+ }
5707
+ function rpcClient(cfg) {
5708
+ return new SuiGrpcClient2({ network: cfg.network, baseUrl: cfg.rpcUrl });
5709
+ }
4602
5710
 
4603
5711
  // src/crypto.ts
4604
5712
  import { getPublicKeyAsync, utils } from "@noble/ed25519";
@@ -4774,7 +5882,7 @@ function walletFromSeedHex(seedHex) {
4774
5882
  return {
4775
5883
  address: kp.getPublicKey().toSuiAddress(),
4776
5884
  async signTransaction(txBytes) {
4777
- const { signature } = await kp.signTransaction(fromBase644(txBytes));
5885
+ const { signature } = await kp.signTransaction(fromBase647(txBytes));
4778
5886
  return signature;
4779
5887
  }
4780
5888
  };
@@ -4791,7 +5899,7 @@ function walletFromEnvSecret(raw) {
4791
5899
  return {
4792
5900
  address: kp.getPublicKey().toSuiAddress(),
4793
5901
  async signTransaction(txBytes) {
4794
- const { signature } = await kp.signTransaction(fromBase644(txBytes));
5902
+ const { signature } = await kp.signTransaction(fromBase647(txBytes));
4795
5903
  return signature;
4796
5904
  }
4797
5905
  };
@@ -4986,7 +6094,7 @@ function loadLocalSigner() {
4986
6094
  return {
4987
6095
  address: parsed.delegateAddress,
4988
6096
  async signTransaction(txBytes) {
4989
- const { signature } = await kp.signTransaction(fromBase644(txBytes));
6097
+ const { signature } = await kp.signTransaction(fromBase647(txBytes));
4990
6098
  return signature;
4991
6099
  },
4992
6100
  async signPersonalMessage(message) {
@@ -5072,7 +6180,7 @@ function objectWithOwner(object) {
5072
6180
  }
5073
6181
  return { ...object.json, owner: object.owner };
5074
6182
  }
5075
- function sameAddress2(a, b) {
6183
+ function sameAddress3(a, b) {
5076
6184
  try {
5077
6185
  return normalizeSuiAddress8(a) === normalizeSuiAddress8(b);
5078
6186
  } catch {
@@ -5088,7 +6196,7 @@ function resolveAgentHeldSettlement(cfg, spendAccountPay, env = process.env) {
5088
6196
  if (!secret) return null;
5089
6197
  const local = loadLocalSigner();
5090
6198
  if (!local) return null;
5091
- if (!sameAddress2(local.address, spendAccountPay.snapshot.delegateAddress)) {
6199
+ if (!sameAddress3(local.address, spendAccountPay.snapshot.delegateAddress)) {
5092
6200
  return null;
5093
6201
  }
5094
6202
  const chain = createSpendAccountChain({
@@ -5096,7 +6204,7 @@ function resolveAgentHeldSettlement(cfg, spendAccountPay, env = process.env) {
5096
6204
  network: cfg.network,
5097
6205
  url: cfg.rpcUrl
5098
6206
  });
5099
- const finality = createSpendAccountFinalityReader({ client: chain.client });
6207
+ const finality = createSpendAccountSettledTxReader({ client: chain.client });
5100
6208
  const getObjectJson = async (objectId) => {
5101
6209
  const { object } = await chain.client.core.getObject({
5102
6210
  objectId,
@@ -5119,7 +6227,7 @@ function resolveAgentHeldSettlement(cfg, spendAccountPay, env = process.env) {
5119
6227
  sponsor: { sponsorUrl: cfg.gatewayUrl },
5120
6228
  gasMode: "sponsored",
5121
6229
  resolveSharedObjectVersions: (ids) => resolveSharedObjectVersionsFromGetObject(getObjectJson, ids),
5122
- getFinalizedTx: (digest) => finality.getFinalizedTx(digest)
6230
+ getSettledTx: (digest) => finality.getSettledTx(digest)
5123
6231
  }
5124
6232
  };
5125
6233
  }
@@ -5176,9 +6284,16 @@ function toolAllowedByScope(toolName, grantedScope) {
5176
6284
  }
5177
6285
 
5178
6286
  // src/tools.ts
5179
- import { isValidSuiAddress, normalizeSuiAddress as normalizeSuiAddress12 } from "@mysten/sui/utils";
6287
+ import { isValidSuiAddress, normalizeSuiAddress as normalizeSuiAddress15 } from "@mysten/sui/utils";
5180
6288
 
5181
6289
  // src/settle.ts
6290
+ function accessModeFromMoveTarget(target) {
6291
+ const fn = target.split("::").pop() ?? "";
6292
+ if (fn === spendAccountDebitFunction({ accessMode: "open" }) || fn === spendAccountDebitFunction({ accessMode: "open", refundable: true })) {
6293
+ return "open";
6294
+ }
6295
+ return "scoped";
6296
+ }
5182
6297
  async function settlePreparedMcpPayment(args) {
5183
6298
  try {
5184
6299
  await verifyPreparedSettlementKind({
@@ -5188,9 +6303,9 @@ async function settlePreparedMcpPayment(args) {
5188
6303
  delegateAddress: args.expected.delegateAddress,
5189
6304
  amount: args.expected.amount,
5190
6305
  recipient: args.expected.recipient,
5191
- accessMode: args.signingRequest.submit.moveCallTarget.endsWith(
5192
- "settle_open_recipient_payment"
5193
- ) ? "open" : "scoped"
6306
+ accessMode: accessModeFromMoveTarget(
6307
+ args.signingRequest.submit.moveCallTarget
6308
+ )
5194
6309
  },
5195
6310
  getObjectJson: args.getObjectJson
5196
6311
  });
@@ -5236,12 +6351,104 @@ async function settlePreparedMcpPayment(args) {
5236
6351
  }
5237
6352
 
5238
6353
  // ../sdk/dist/agent-sdk/delegate-agent.js
5239
- import { normalizeSuiAddress as normalizeSuiAddress10 } from "@mysten/sui/utils";
6354
+ import { normalizeSuiAddress as normalizeSuiAddress11 } from "@mysten/sui/utils";
5240
6355
 
5241
- // ../sdk/dist/agent-sdk/verify-prepared-settlement.js
6356
+ // ../sdk/dist/buyer-control/exclusion-registry.js
6357
+ import { bcs as bcs6 } from "@mysten/sui/bcs";
5242
6358
  import { normalizeSuiAddress as normalizeSuiAddress9 } from "@mysten/sui/utils";
6359
+ var ExclusionRegistryResolutionError = class extends Error {
6360
+ code;
6361
+ constructor(code, message) {
6362
+ super(message);
6363
+ this.code = code;
6364
+ this.name = "ExclusionRegistryResolutionError";
6365
+ }
6366
+ };
6367
+ async function resolveExclusionRegistry(input) {
6368
+ const directoryObjectId = typeof input.directoryObjectId === "string" ? input.directoryObjectId.trim() : "";
6369
+ if (!directoryObjectId) {
6370
+ throw new ExclusionRegistryResolutionError("NO_DIRECTORY", "no exclusion directory object id configured for a guarded offer");
6371
+ }
6372
+ let registryId;
6373
+ try {
6374
+ registryId = await input.reader.boundRegistryId(directoryObjectId, input.recipient);
6375
+ } catch (err) {
6376
+ if (err instanceof ExclusionRegistryResolutionError)
6377
+ throw err;
6378
+ throw new ExclusionRegistryResolutionError("REGISTRY_UNREADABLE", "could not read the exclusion directory binding for this recipient");
6379
+ }
6380
+ if (!registryId || !registryId.trim()) {
6381
+ throw new ExclusionRegistryResolutionError("REGISTRY_NOT_PROVISIONED", "no RecipientRegistry is bound for this recipient in the exclusion directory");
6382
+ }
6383
+ const directoryInitialSharedVersion = await input.reader.initialSharedVersion(directoryObjectId);
6384
+ if (!directoryInitialSharedVersion || !directoryInitialSharedVersion.trim()) {
6385
+ throw new ExclusionRegistryResolutionError("DIRECTORY_VERSION_UNRESOLVED", "could not resolve the exclusion directory initial shared version");
6386
+ }
6387
+ const registryInitialSharedVersion = await input.reader.initialSharedVersion(registryId);
6388
+ if (!registryInitialSharedVersion || !registryInitialSharedVersion.trim()) {
6389
+ throw new ExclusionRegistryResolutionError("REGISTRY_VERSION_UNRESOLVED", "could not resolve the recipient registry initial shared version");
6390
+ }
6391
+ return {
6392
+ recipientRegistryId: normalizeSuiAddress9(registryId),
6393
+ directoryInitialSharedVersion,
6394
+ registryInitialSharedVersion
6395
+ };
6396
+ }
6397
+ function initialSharedVersionFromOwner2(owner) {
6398
+ if (!owner || typeof owner !== "object")
6399
+ return null;
6400
+ const shared = owner.Shared;
6401
+ if (shared?.initialSharedVersion != null) {
6402
+ return String(shared.initialSharedVersion);
6403
+ }
6404
+ return null;
6405
+ }
6406
+ function isAbsentDynamicFieldError(err) {
6407
+ const text2 = (err instanceof Error ? err.message : String(err)).toLowerCase();
6408
+ if (/timeout|timed out|unavailable|econnreset|econnrefused|network|fetch failed|socket| 502| 503| 429/.test(text2)) {
6409
+ return false;
6410
+ }
6411
+ return /\bnot[_ ]found\b|dynamic field not found|no such.*field/.test(text2);
6412
+ }
6413
+ function createGrpcExclusionRegistryReader(client) {
6414
+ return {
6415
+ async boundRegistryId(directoryObjectId, recipient) {
6416
+ let response;
6417
+ try {
6418
+ response = await client.core.getDynamicField({
6419
+ parentId: directoryObjectId,
6420
+ name: { type: "address", bcs: bcs6.Address.serialize(recipient).toBytes() }
6421
+ });
6422
+ } catch (err) {
6423
+ if (isAbsentDynamicFieldError(err))
6424
+ return null;
6425
+ throw new ExclusionRegistryResolutionError("REGISTRY_UNREADABLE", "could not read the exclusion directory binding for this recipient");
6426
+ }
6427
+ const raw = response?.dynamicField?.value?.bcs;
6428
+ if (!raw)
6429
+ return null;
6430
+ try {
6431
+ return normalizeSuiAddress9(bcs6.Address.parse(raw));
6432
+ } catch {
6433
+ return null;
6434
+ }
6435
+ },
6436
+ async initialSharedVersion(objectId) {
6437
+ let response;
6438
+ try {
6439
+ response = await client.core.getObject({ objectId });
6440
+ } catch {
6441
+ return null;
6442
+ }
6443
+ return initialSharedVersionFromOwner2(response?.object?.owner);
6444
+ }
6445
+ };
6446
+ }
6447
+
6448
+ // ../sdk/dist/agent-sdk/verify-prepared-settlement.js
6449
+ import { normalizeSuiAddress as normalizeSuiAddress10 } from "@mysten/sui/utils";
5243
6450
  import { Transaction as Transaction5 } from "@mysten/sui/transactions";
5244
- import { fromBase64 as fromBase645 } from "@mysten/sui/utils";
6451
+ import { fromBase64 as fromBase648 } from "@mysten/sui/utils";
5245
6452
 
5246
6453
  // ../sdk/dist/protocol/prepared-settlement.js
5247
6454
  var PREPARED_SETTLEMENT_VERSION = 1;
@@ -5269,6 +6476,11 @@ function asPreparedSettlement(value) {
5269
6476
  "coinType",
5270
6477
  "amount",
5271
6478
  "recipient",
6479
+ "exclusionDirectoryId",
6480
+ "recipientRegistryId",
6481
+ "exclusionDirectoryInitialSharedVersion",
6482
+ "recipientRegistryInitialSharedVersion",
6483
+ "expiresAtMs",
5272
6484
  "payUrl"
5273
6485
  ];
5274
6486
  for (const key of strings) {
@@ -5293,6 +6505,11 @@ function asPreparedSettlement(value) {
5293
6505
  coinType: v.coinType,
5294
6506
  amount: v.amount,
5295
6507
  recipient: v.recipient,
6508
+ exclusionDirectoryId: v.exclusionDirectoryId,
6509
+ recipientRegistryId: v.recipientRegistryId,
6510
+ exclusionDirectoryInitialSharedVersion: v.exclusionDirectoryInitialSharedVersion,
6511
+ recipientRegistryInitialSharedVersion: v.recipientRegistryInitialSharedVersion,
6512
+ expiresAtMs: v.expiresAtMs,
5296
6513
  payUrl: v.payUrl
5297
6514
  };
5298
6515
  }
@@ -5311,9 +6528,9 @@ var PreparedSettlementRejected = class extends Error {
5311
6528
  function reject(field, message) {
5312
6529
  throw new PreparedSettlementRejected(field, message);
5313
6530
  }
5314
- function sameAddress3(a, b) {
6531
+ function sameAddress4(a, b) {
5315
6532
  try {
5316
- return normalizeSuiAddress9(a) === normalizeSuiAddress9(b);
6533
+ return normalizeSuiAddress10(a) === normalizeSuiAddress10(b);
5317
6534
  } catch {
5318
6535
  return false;
5319
6536
  }
@@ -5324,7 +6541,7 @@ function sameCoinType(a, b) {
5324
6541
  if (pkg === void 0 || rest.length !== 2)
5325
6542
  return value.trim();
5326
6543
  try {
5327
- return `${normalizeSuiAddress9(pkg)}::${rest.join("::")}`;
6544
+ return `${normalizeSuiAddress10(pkg)}::${rest.join("::")}`;
5328
6545
  } catch {
5329
6546
  return value.trim();
5330
6547
  }
@@ -5344,26 +6561,26 @@ async function verifyPreparedPayment(input) {
5344
6561
  reject("shape", "the 402 carried no usable prepared settlement");
5345
6562
  }
5346
6563
  const { offer, delegateAddress } = input;
5347
- if (!sameAddress3(prepared.sender, delegateAddress)) {
6564
+ if (!sameAddress4(prepared.sender, delegateAddress)) {
5348
6565
  reject("sender", `prepared sender ${prepared.sender} is not this agent's address`);
5349
6566
  }
5350
6567
  let data;
5351
6568
  try {
5352
- data = Transaction5.from(fromBase645(prepared.transactionBytes)).getData();
6569
+ data = Transaction5.from(fromBase648(prepared.transactionBytes)).getData();
5353
6570
  } catch {
5354
6571
  reject("transactionBytes", "prepared transaction bytes are not a Sui transaction");
5355
6572
  }
5356
6573
  const gasOwner = data.gasData?.owner;
5357
- if (!gasOwner || sameAddress3(gasOwner, delegateAddress)) {
6574
+ if (!gasOwner || sameAddress4(gasOwner, delegateAddress)) {
5358
6575
  reject("gasOwner", "prepared transaction is not sponsored: gas would be paid by this agent");
5359
6576
  }
5360
- if (input.expectedGrantObjectId && !sameAddress3(prepared.grantObjectId, input.expectedGrantObjectId)) {
6577
+ if (input.expectedGrantObjectId && !sameAddress4(prepared.grantObjectId, input.expectedGrantObjectId)) {
5361
6578
  reject("grantObjectId", `prepared settlement draws on grant ${prepared.grantObjectId}, not the one this agent resolved`);
5362
6579
  }
5363
- if (input.expectedPoolObjectId && !sameAddress3(prepared.poolObjectId, input.expectedPoolObjectId)) {
6580
+ if (input.expectedPoolObjectId && !sameAddress4(prepared.poolObjectId, input.expectedPoolObjectId)) {
5364
6581
  reject("poolObjectId", `prepared settlement draws on pool ${prepared.poolObjectId}, not the one this agent resolved`);
5365
6582
  }
5366
- if (!offer.packageId || !sameAddress3(prepared.packageId, offer.packageId)) {
6583
+ if (!offer.packageId || !sameAddress4(prepared.packageId, offer.packageId)) {
5367
6584
  reject("packageId", "prepared package id does not match the offer");
5368
6585
  }
5369
6586
  const wantedTarget = payMoveCallTarget(offer.packageId, prepared.accessMode);
@@ -5376,9 +6593,19 @@ async function verifyPreparedPayment(input) {
5376
6593
  if (!sameAmount(prepared.amount, offer.amount)) {
5377
6594
  reject("amount", "prepared amount does not match the offer amount");
5378
6595
  }
5379
- if (!sameAddress3(prepared.recipient, offer.payTo)) {
6596
+ if (!sameAddress4(prepared.recipient, offer.payTo)) {
5380
6597
  reject("recipient", "prepared recipient does not match the offer payTo");
5381
6598
  }
6599
+ const identity = parseRegistryIdentity(offer.registryIdentity);
6600
+ if (!identity) {
6601
+ reject("registryIdentity", "offer carries no valid registry identity for a guarded settle");
6602
+ }
6603
+ if (!sameAddress4(prepared.exclusionDirectoryId, identity.directoryId)) {
6604
+ reject("exclusionDirectoryId", "prepared exclusion directory is not the one the offer committed to");
6605
+ }
6606
+ if (prepared.expiresAtMs !== String(offer.expiresAt)) {
6607
+ reject("expiresAtMs", "prepared guarded expiry does not match the offer expiry");
6608
+ }
5382
6609
  const targetHash = offer.targetHash?.trim();
5383
6610
  if (!targetHash) {
5384
6611
  reject("targetHash", "offer carries no targetHash to bind the settlement to");
@@ -5398,7 +6625,14 @@ async function verifyPreparedPayment(input) {
5398
6625
  targetHash,
5399
6626
  amount: offer.amount,
5400
6627
  paymentIdHash: paymentIdHash(offer.challengeId),
5401
- termsHash: termsHash(offer)
6628
+ termsHash: termsHash(offer),
6629
+ // Guarded-settle inputs, rebuilt from the prepared bytes and validated
6630
+ // against the offer above.
6631
+ exclusionDirectoryId: prepared.exclusionDirectoryId,
6632
+ recipientRegistryId: prepared.recipientRegistryId,
6633
+ exclusionDirectoryInitialSharedVersion: prepared.exclusionDirectoryInitialSharedVersion,
6634
+ recipientRegistryInitialSharedVersion: prepared.recipientRegistryInitialSharedVersion,
6635
+ expiresAtMs: prepared.expiresAtMs
5402
6636
  };
5403
6637
  let rebuilt;
5404
6638
  try {
@@ -5733,7 +6967,23 @@ async function fetchGatewayDeployment(options) {
5733
6967
  }
5734
6968
  };
5735
6969
  }
5736
- return { ok: true, value: { network: d.network, packageId: d.packageId } };
6970
+ if (d.exclusionDirectoryObjectId !== void 0 && !isNonEmptyString2(d.exclusionDirectoryObjectId)) {
6971
+ return {
6972
+ ok: false,
6973
+ error: {
6974
+ code: MANIFEST_ERROR_CODES.MALFORMED_MANIFEST,
6975
+ detail: "gateway deployment identity carries an unusable exclusionDirectoryObjectId"
6976
+ }
6977
+ };
6978
+ }
6979
+ return {
6980
+ ok: true,
6981
+ value: {
6982
+ network: d.network,
6983
+ packageId: d.packageId,
6984
+ ...isNonEmptyString2(d.exclusionDirectoryObjectId) ? { exclusionDirectoryObjectId: d.exclusionDirectoryObjectId } : {}
6985
+ }
6986
+ };
5737
6987
  }
5738
6988
  function explainBlockedGrant(raw, error2) {
5739
6989
  if (error2.code !== MANIFEST_ERROR_CODES.MISSING_POLICY_BINDING)
@@ -5825,7 +7075,7 @@ async function resolvePayerSession(options) {
5825
7075
  ...options.select ? { select: options.select } : {}
5826
7076
  });
5827
7077
  if (whole.ok)
5828
- return whole;
7078
+ return { ok: true, value: { ...whole.value, deployment } };
5829
7079
  const data = bootstrap.data;
5830
7080
  const rawGrants = Array.isArray(data.grants) ? data.grants : [];
5831
7081
  const usable = [];
@@ -5860,11 +7110,12 @@ async function resolvePayerSession(options) {
5860
7110
  const pick = scoped ?? blocked[0];
5861
7111
  return pick ? { ok: false, error: explainBlockedGrant(pick.raw, pick.error) } : whole;
5862
7112
  }
5863
- return manifestToSession({
7113
+ const resolved = manifestToSession({
5864
7114
  manifest: { ...data, grants: usable },
5865
7115
  signer: options.signer,
5866
7116
  ...options.select ? { select: options.select } : {}
5867
7117
  });
7118
+ return resolved.ok ? { ok: true, value: { ...resolved.value, deployment } } : resolved;
5868
7119
  }
5869
7120
 
5870
7121
  // ../sdk/dist/buyer/transport.js
@@ -5894,6 +7145,15 @@ function mergeObjectEnvelope(object) {
5894
7145
  var SECRET_KEY_SET = new Set(FORBIDDEN_SECRET_KEYS.map((k) => k.toLowerCase()));
5895
7146
 
5896
7147
  // ../sdk/dist/agent-sdk/delegate-agent.js
7148
+ function resolveExclusionDirectoryId(explicit, discovered) {
7149
+ return explicit ?? discovered;
7150
+ }
7151
+ function createExclusionRegistryResolver(client, exclusionDirectoryObjectId) {
7152
+ if (!exclusionDirectoryObjectId || !exclusionDirectoryObjectId.trim())
7153
+ return void 0;
7154
+ const reader = createGrpcExclusionRegistryReader(client);
7155
+ return async ({ directoryId, recipient }) => resolveExclusionRegistry({ directoryObjectId: directoryId, recipient, reader });
7156
+ }
5897
7157
  async function verifyGrantOnChain(input) {
5898
7158
  let state;
5899
7159
  try {
@@ -5920,10 +7180,10 @@ async function verifyGrantOnChain(input) {
5920
7180
  ok: false,
5921
7181
  error: { code: MANIFEST_ERROR_CODES.GRANT_NOT_VERIFIED, detail }
5922
7182
  });
5923
- if (!state.delegateAddress || normalizeSuiAddress10(state.delegateAddress) !== normalizeSuiAddress10(input.delegateAddress)) {
7183
+ if (!state.delegateAddress || normalizeSuiAddress11(state.delegateAddress) !== normalizeSuiAddress11(input.delegateAddress)) {
5924
7184
  return notVerified(`grant ${input.grantObjectId} does not name this delegate on chain`);
5925
7185
  }
5926
- if (!state.poolObjectId || normalizeSuiAddress10(state.poolObjectId) !== normalizeSuiAddress10(input.poolObjectId)) {
7186
+ if (!state.poolObjectId || normalizeSuiAddress11(state.poolObjectId) !== normalizeSuiAddress11(input.poolObjectId)) {
5927
7187
  return notVerified(`grant ${input.grantObjectId} draws on a different pool than the one resolved`);
5928
7188
  }
5929
7189
  if (state.revoked === true) {
@@ -5958,6 +7218,7 @@ async function createPayer(options) {
5958
7218
  network: session.network,
5959
7219
  packageId: session.packageId
5960
7220
  };
7221
+ const exclusionDirectoryObjectId = resolveExclusionDirectoryId(options.exclusionDirectoryObjectId, resolved.value.deployment.exclusionDirectoryObjectId);
5961
7222
  let network;
5962
7223
  try {
5963
7224
  network = toGrpcNetwork(deployment.network);
@@ -5987,7 +7248,7 @@ async function createPayer(options) {
5987
7248
  }
5988
7249
  };
5989
7250
  }
5990
- const finality = createSpendAccountFinalityReader({ client: chain.client });
7251
+ const finality = createSpendAccountSettledTxReader({ client: chain.client });
5991
7252
  const getObjectJson = async (objectId) => {
5992
7253
  const { object } = await chain.client.core.getObject({
5993
7254
  objectId,
@@ -6010,7 +7271,18 @@ async function createPayer(options) {
6010
7271
  sponsor: { sponsorUrl: options.gatewayUrl },
6011
7272
  gasMode: "sponsored",
6012
7273
  resolveSharedObjectVersions: (ids) => resolveSharedObjectVersionsFromGetObject(getObjectJson, ids),
6013
- getFinalizedTx: finality.getFinalizedTx,
7274
+ getSettledTx: finality.getSettledTx,
7275
+ // A guarded offer resolves its per-recipient exclusion registry
7276
+ // through this dep, bound to the agent's own gRPC reader. Undefined when no
7277
+ // directory id was configured, so such an offer fails closed (BUILD_FAILED)
7278
+ // rather than building an unguarded settle. Unguarded offers never call it.
7279
+ ...(() => {
7280
+ const resolve = createExclusionRegistryResolver(chain.client, exclusionDirectoryObjectId);
7281
+ return resolve ? { resolveExclusionRegistry: resolve } : {};
7282
+ })(),
7283
+ // x402 bounds the transaction's expiration by the current epoch, read from
7284
+ // the same client as every other chain read here.
7285
+ getCurrentEpoch: currentEpochReader(chain.client),
6014
7286
  ...options.offerSecret ? { offerSecret: options.offerSecret } : {},
6015
7287
  ...options.fetchImpl ? { fetchImpl: options.fetchImpl } : {},
6016
7288
  ...options.logError ? { logError: options.logError } : {}
@@ -6040,7 +7312,9 @@ async function createPayer(options) {
6040
7312
  cfg: { gatewayUrl: options.gatewayUrl },
6041
7313
  deps,
6042
7314
  intent: input.intent,
6043
- ...input.preparedTerms ? { preparedTerms: input.preparedTerms } : {}
7315
+ ...input.preparedTerms ? { preparedTerms: input.preparedTerms } : {},
7316
+ dialect: input.dialect,
7317
+ onX402Progress: input.onX402Progress
6044
7318
  });
6045
7319
  }
6046
7320
  async function verifySettlement(prepared, offer) {
@@ -6346,13 +7620,43 @@ async function startStdioLogin(opts) {
6346
7620
  }
6347
7621
 
6348
7622
  // src/spend-account-pay.ts
6349
- import { Transaction as Transaction6 } from "@mysten/sui/transactions";
7623
+ import { Transaction as Transaction8 } from "@mysten/sui/transactions";
6350
7624
  import {
6351
- fromBase64 as fromBase646,
6352
- normalizeSuiAddress as normalizeSuiAddress11,
6353
- toBase64 as toBase644
7625
+ fromBase64 as fromBase6411,
7626
+ normalizeSuiAddress as normalizeSuiAddress14,
7627
+ toBase64 as toBase647
6354
7628
  } from "@mysten/sui/utils";
7629
+
7630
+ // ../sdk/dist/pay-operation/verify-x402-offer.js
7631
+ import { normalizeStructTag as normalizeStructTag7, normalizeSuiAddress as normalizeSuiAddress12 } from "@mysten/sui/utils";
7632
+
7633
+ // ../sdk/dist/pay-operation/verify-direct-tx.js
7634
+ import { Transaction as Transaction6 } from "@mysten/sui/transactions";
7635
+ import { bcs as bcs7 } from "@mysten/sui/bcs";
7636
+ import { fromBase64 as fromBase649, normalizeStructTag as normalizeStructTag8, normalizeSuiAddress as normalizeSuiAddress13 } from "@mysten/sui/utils";
7637
+
7638
+ // ../sdk/dist/pay-operation/resolve-grant-x402.js
7639
+ import { Transaction as Transaction7 } from "@mysten/sui/transactions";
7640
+ import { fromBase64 as fromBase6410, toBase64 as toBase646 } from "@mysten/sui/utils";
7641
+
7642
+ // src/spend-account-pay.ts
6355
7643
  var SUI_CLOCK3 = "0x6";
7644
+ var sharedObjectReaderForTests = null;
7645
+ function chainSharedObjectReader(cfg, packageId) {
7646
+ const chain = createSpendAccountChain({
7647
+ packageId,
7648
+ network: cfg.network,
7649
+ url: cfg.rpcUrl
7650
+ });
7651
+ return async (objectId) => {
7652
+ const { object } = await chain.client.core.getObject({
7653
+ objectId,
7654
+ include: { json: true }
7655
+ });
7656
+ if (!object?.owner) return null;
7657
+ return { owner: object.owner };
7658
+ };
7659
+ }
6356
7660
  function traceEmitter(trace) {
6357
7661
  if (!trace) return () => void 0;
6358
7662
  const now = trace.now ?? Date.now;
@@ -6386,25 +7690,38 @@ function toHex(bytes) {
6386
7690
  async function buildSpendAccountPayKind2(input) {
6387
7691
  if (!input.termsHash.length) throw new Error("termsHash must be non-empty");
6388
7692
  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
7693
  const hashHex = input.targetHash.replace(/^0x/i, "").toLowerCase();
6392
7694
  if (!/^[0-9a-f]{64}$/.test(hashHex)) throw new Error("targetHash must be 32-byte hex");
6393
7695
  const hash = Uint8Array.from(Buffer.from(hashHex, "hex"));
6394
- const tx = new Transaction6();
7696
+ const guarded = resolveGuardedSettleOfflineInputs(input);
7697
+ const target = payMoveCallTarget(
7698
+ input.packageId,
7699
+ "scoped"
7700
+ );
7701
+ const tx = new Transaction8();
6395
7702
  tx.setSender(input.delegate);
6396
7703
  tx.moveCall({
6397
7704
  target,
6398
7705
  typeArguments: [input.coinType],
6399
7706
  arguments: [
7707
+ tx.sharedObjectRef({
7708
+ objectId: guarded.exclusionDirectoryId,
7709
+ initialSharedVersion: guarded.exclusionDirectoryInitialSharedVersion,
7710
+ mutable: false
7711
+ }),
7712
+ tx.sharedObjectRef({
7713
+ objectId: guarded.recipientRegistryId,
7714
+ initialSharedVersion: guarded.recipientRegistryInitialSharedVersion,
7715
+ mutable: true
7716
+ }),
6400
7717
  tx.sharedObjectRef({
6401
7718
  objectId: input.poolObjectId,
6402
- initialSharedVersion: "1",
7719
+ initialSharedVersion: input.poolInitialSharedVersion?.trim() || "1",
6403
7720
  mutable: true
6404
7721
  }),
6405
7722
  tx.sharedObjectRef({
6406
7723
  objectId: input.grantObjectId,
6407
- initialSharedVersion: "1",
7724
+ initialSharedVersion: input.grantInitialSharedVersion?.trim() || "1",
6408
7725
  mutable: true
6409
7726
  }),
6410
7727
  tx.pure.u64(BigInt(input.policyId)),
@@ -6412,42 +7729,43 @@ async function buildSpendAccountPayKind2(input) {
6412
7729
  tx.pure.u64(BigInt(input.amount)),
6413
7730
  tx.pure.vector("u8", Array.from(input.paymentIdHash)),
6414
7731
  tx.pure.vector("u8", Array.from(input.termsHash)),
7732
+ tx.pure.u64(BigInt(guarded.expiresAtMs)),
6415
7733
  tx.sharedObjectRef({
6416
7734
  objectId: SUI_CLOCK3,
6417
- initialSharedVersion: "1",
7735
+ initialSharedVersion: input.clockInitialSharedVersion?.trim() || "1",
6418
7736
  mutable: false
6419
7737
  })
6420
7738
  ]
6421
7739
  });
6422
7740
  const bytes = await tx.build({ onlyTransactionKind: true });
6423
- return { kindBytes: toBase644(bytes) };
7741
+ return { kindBytes: toBase647(bytes) };
6424
7742
  }
6425
7743
  function verifySpendAccountPayReconstructs(bytes, expected) {
6426
7744
  let data;
6427
7745
  try {
6428
- data = Transaction6.from(fromBase646(bytes)).getData();
7746
+ data = Transaction8.from(fromBase6411(bytes)).getData();
6429
7747
  } catch {
6430
7748
  throw new Error("sponsored transaction is not valid Sui transaction bytes");
6431
7749
  }
6432
- if (!data.sender || normalizeSuiAddress11(data.sender) !== normalizeSuiAddress11(expected.delegate)) {
7750
+ if (!data.sender || normalizeSuiAddress14(data.sender) !== normalizeSuiAddress14(expected.delegate)) {
6433
7751
  throw new Error("sponsored transaction sender does not match delegate");
6434
7752
  }
6435
7753
  if (!data.commands || data.commands.length !== 1) {
6436
7754
  throw new Error("sponsored transaction must contain exactly one command");
6437
7755
  }
6438
7756
  const auth = defaultAuthorityForRail("delegate", expected.packageId);
6439
- const wanted = `${normalizeSuiAddress11(auth.packageId)}::${auth.module}::${auth.payFunction}`;
7757
+ const wanted = `${normalizeSuiAddress14(auth.packageId)}::${auth.module}::${auth.payFunction}`;
6440
7758
  const cmd = data.commands[0];
6441
7759
  if (cmd.$kind !== "MoveCall" || !cmd.MoveCall) {
6442
7760
  throw new Error("sponsored transaction is not a MoveCall");
6443
7761
  }
6444
- const got = `${normalizeSuiAddress11(String(cmd.MoveCall.package))}::${cmd.MoveCall.module}::${cmd.MoveCall.function}`;
7762
+ const got = `${normalizeSuiAddress14(String(cmd.MoveCall.package))}::${cmd.MoveCall.module}::${cmd.MoveCall.function}`;
6445
7763
  if (got !== wanted) {
6446
7764
  throw new Error(`sponsored transaction does not reconstruct ${wanted}`);
6447
7765
  }
6448
7766
  let expectedKind;
6449
7767
  try {
6450
- expectedKind = Transaction6.fromKind(fromBase646(expected.kindBytes)).getData();
7768
+ expectedKind = Transaction8.fromKind(fromBase6411(expected.kindBytes)).getData();
6451
7769
  } catch {
6452
7770
  throw new Error("sponsored transaction kind baseline invalid");
6453
7771
  }
@@ -6479,6 +7797,13 @@ async function prepareSpendAccountPayment(args) {
6479
7797
  const isOpen = session.snapshot.accessMode === "open";
6480
7798
  const callerBuild = deps.buildPayKind;
6481
7799
  const buildPayKind = async (input) => {
7800
+ const guardedFields = {
7801
+ exclusionDirectoryId: input.exclusionDirectoryId,
7802
+ recipientRegistryId: input.recipientRegistryId,
7803
+ expiresAtMs: input.expiresAtMs,
7804
+ exclusionDirectoryInitialSharedVersion: input.exclusionDirectoryInitialSharedVersion,
7805
+ recipientRegistryInitialSharedVersion: input.recipientRegistryInitialSharedVersion
7806
+ };
6482
7807
  if (callerBuild) {
6483
7808
  return callerBuild({
6484
7809
  delegate: input.delegate,
@@ -6491,9 +7816,16 @@ async function prepareSpendAccountPayment(args) {
6491
7816
  paymentIdHash: input.paymentIdHash,
6492
7817
  termsHash: input.termsHash,
6493
7818
  recipient: input.recipient,
6494
- accessMode: input.accessMode
7819
+ accessMode: input.accessMode,
7820
+ ...guardedFields
6495
7821
  });
6496
7822
  }
7823
+ const readSharedObject = deps.readSharedObject ?? sharedObjectReaderForTests ?? chainSharedObjectReader(cfg, input.packageId);
7824
+ const versions = await resolveSharedObjectVersionsFromGetObject(readSharedObject, {
7825
+ poolObjectId: input.poolObjectId,
7826
+ grantObjectId: input.grantObjectId,
7827
+ clockObjectId: SUI_CLOCK3
7828
+ });
6497
7829
  if (isOpen) {
6498
7830
  return buildSpendAccountSettleOpenRecipientKind({
6499
7831
  packageId: input.packageId,
@@ -6501,13 +7833,15 @@ async function prepareSpendAccountPayment(args) {
6501
7833
  coinType: input.coinType,
6502
7834
  poolObjectId: input.poolObjectId,
6503
7835
  grantObjectId: input.grantObjectId,
6504
- poolInitialSharedVersion: "1",
6505
- grantInitialSharedVersion: "1",
7836
+ poolInitialSharedVersion: versions.poolInitialSharedVersion,
7837
+ grantInitialSharedVersion: versions.grantInitialSharedVersion,
7838
+ clockInitialSharedVersion: versions.clockInitialSharedVersion,
6506
7839
  recipient: input.recipient,
6507
7840
  targetHash: input.targetHash,
6508
7841
  amount: input.amount,
6509
7842
  paymentIdHash: input.paymentIdHash,
6510
- termsHash: input.termsHash
7843
+ termsHash: input.termsHash,
7844
+ ...guardedFields
6511
7845
  });
6512
7846
  }
6513
7847
  return buildSpendAccountPayKind2({
@@ -6520,7 +7854,11 @@ async function prepareSpendAccountPayment(args) {
6520
7854
  targetHash: input.targetHash,
6521
7855
  amount: input.amount,
6522
7856
  paymentIdHash: input.paymentIdHash,
6523
- termsHash: input.termsHash
7857
+ termsHash: input.termsHash,
7858
+ poolInitialSharedVersion: versions.poolInitialSharedVersion,
7859
+ grantInitialSharedVersion: versions.grantInitialSharedVersion,
7860
+ clockInitialSharedVersion: versions.clockInitialSharedVersion,
7861
+ ...guardedFields
6524
7862
  });
6525
7863
  };
6526
7864
  const rawFetch = deps.fetchImpl ?? fetch;
@@ -6540,10 +7878,20 @@ async function prepareSpendAccountPayment(args) {
6540
7878
  authority: session.snapshot,
6541
7879
  assertLive: session.assertLive
6542
7880
  };
7881
+ const directorySwitch = cfg.exclusionDirectoryObjectId ? cfg.exclusionDirectoryObjectId : deps.resolveExclusionRegistry ? null : await publishedExclusionDirectoryId(cfg.gatewayUrl);
7882
+ const resolveExclusionRegistry2 = deps.resolveExclusionRegistry ?? (directorySwitch ? (() => {
7883
+ const reader = createGrpcExclusionRegistryReader(rpcClient(cfg));
7884
+ return async ({ directoryId, recipient }) => resolveExclusionRegistry({
7885
+ directoryObjectId: directoryId,
7886
+ recipient,
7887
+ reader
7888
+ });
7889
+ })() : void 0);
6543
7890
  const resolveDeps = {
6544
7891
  fetchImpl: wrappedFetch,
6545
7892
  preferredDialect: preferred,
6546
- buildPayKind
7893
+ buildPayKind,
7894
+ ...resolveExclusionRegistry2 ? { resolveExclusionRegistry: resolveExclusionRegistry2 } : {}
6547
7895
  };
6548
7896
  const result = await resolvePayment(resolveInput, resolveDeps);
6549
7897
  if (result.status === "unpaid_ok") {
@@ -6604,7 +7952,7 @@ async function prepareSpendAccountPayment(args) {
6604
7952
  const signingRequest = {
6605
7953
  unsignedKindBytes: kindBytes,
6606
7954
  delegateAddress: resolved.delegateAddress,
6607
- packageId: normalizeSuiAddress11(authority.packageId),
7955
+ packageId: normalizeSuiAddress14(authority.packageId),
6608
7956
  poolObjectId: resolved.poolObjectId,
6609
7957
  grantObjectId: resolved.grantObjectId,
6610
7958
  policyId: resolved.onChainPolicyId,
@@ -6686,11 +8034,11 @@ function mapErrorCode(code) {
6686
8034
  }
6687
8035
 
6688
8036
  // src/discover-afford.ts
6689
- import { normalizeStructTag as normalizeStructTag6 } from "@mysten/sui/utils";
8037
+ import { normalizeStructTag as normalizeStructTag9 } from "@mysten/sui/utils";
6690
8038
  function sameCoinType2(rowAsset, grantCoin) {
6691
8039
  if (typeof rowAsset !== "string" || !grantCoin) return false;
6692
8040
  try {
6693
- return normalizeStructTag6(rowAsset) === normalizeStructTag6(grantCoin);
8041
+ return normalizeStructTag9(rowAsset) === normalizeStructTag9(grantCoin);
6694
8042
  } catch {
6695
8043
  return false;
6696
8044
  }
@@ -7237,7 +8585,7 @@ function normalizePayIntent(args) {
7237
8585
  if (!/^0x[0-9a-fA-F]{1,64}$/.test(raw)) {
7238
8586
  throw new Error("recipient must be a 0x-prefixed Sui address");
7239
8587
  }
7240
- const normalized = normalizeSuiAddress12(raw);
8588
+ const normalized = normalizeSuiAddress15(raw);
7241
8589
  if (!isValidSuiAddress(normalized) || /^0x0+$/.test(normalized)) {
7242
8590
  throw new Error("recipient must be a non-zero Sui address");
7243
8591
  }
@@ -7256,7 +8604,7 @@ function intentViolation(intent, prepared) {
7256
8604
  if (intent.recipient) {
7257
8605
  let preparedRecipient;
7258
8606
  try {
7259
- preparedRecipient = normalizeSuiAddress12(prepared.recipient);
8607
+ preparedRecipient = normalizeSuiAddress15(prepared.recipient);
7260
8608
  } catch {
7261
8609
  return "prepared payment does not name a valid recipient";
7262
8610
  }
@@ -7331,6 +8679,7 @@ async function pay(args, cfg, deps = {}) {
7331
8679
  deps: {
7332
8680
  ...deps.fetchImpl ? { fetchImpl: deps.fetchImpl } : {},
7333
8681
  ...trace ? { trace } : {},
8682
+ ...deps.resolveExclusionRegistry ? { resolveExclusionRegistry: deps.resolveExclusionRegistry } : {},
7334
8683
  ...deps.buildPayKind ? { buildPayKind: deps.buildPayKind } : liveKindBuilder ? {
7335
8684
  buildPayKind: (input) => sp.snapshot.accessMode === "open" && input.recipient ? liveKindBuilder.buildSettleOpenRecipientKind({
7336
8685
  ...input,
@@ -7763,13 +9112,13 @@ async function suipayLogin(args, cfg, deps = {}) {
7763
9112
  identity,
7764
9113
  label,
7765
9114
  timeoutMs: LOGIN_BG_TIMEOUT_MS,
7766
- openBrowser: false,
9115
+ openBrowser: true,
7767
9116
  onUrl: deps.onUrl
7768
9117
  });
7769
9118
  const url = started.connectUrl;
7770
9119
  return text(
7771
9120
  [
7772
- `## ACTION REQUIRED: User must click this URL to sign in`,
9121
+ `## ACTION REQUIRED: The sign-in page was opened in the browser`,
7773
9122
  ``,
7774
9123
  `**Target:** ${target ?? cfg.target ?? "custom"} \xB7 ${endpoints.gatewayUrl}`,
7775
9124
  ``,
@@ -7781,11 +9130,13 @@ async function suipayLogin(args, cfg, deps = {}) {
7781
9130
  ``,
7782
9131
  `[Click here to open SuiPay sign-in](${url})`,
7783
9132
  ``,
7784
- `**IMPORTANT for the assistant**: do NOT summarize or omit the URL above.`,
7785
- `The user CANNOT proceed without seeing the exact URL. Surface it verbatim.`,
9133
+ `**IMPORTANT for the assistant**: the browser was already opened to this exact URL.`,
9134
+ `Do NOT navigate to /connect/agent yourself. That path without these query`,
9135
+ `parameters shows "Authorization link incomplete". If the page did not`,
9136
+ `appear, surface the URL above verbatim and do not shorten it.`,
7786
9137
  ``,
7787
- `1. Open the URL in any browser`,
7788
- `2. Connect the owner wallet and approve the spend-account grant (Access keys / policies)`,
9138
+ `1. Use the browser window that just opened, or open the URL above`,
9139
+ `2. Connect the owner wallet, choose or create a key, then Authorize on the review step`,
7789
9140
  `3. Once Connected appears, retry the original request \u2014 credentials at \`~/.suipay/credentials.json\` are picked up on the next tool call`,
7790
9141
  ``,
7791
9142
  `_The login link stays valid for 5 minutes. If it expires, call \`suipay_login\` again._`,
@@ -8005,7 +9356,8 @@ function createServer2(cfg, auth, trace, options = {}) {
8005
9356
  auth,
8006
9357
  transport,
8007
9358
  ...trace ? { trace } : {},
8008
- ...agentHeld ? { agentHeld } : {}
9359
+ ...agentHeld ? { agentHeld } : {},
9360
+ ...options.resolveExclusionRegistry ? { resolveExclusionRegistry: options.resolveExclusionRegistry } : {}
8009
9361
  }
8010
9362
  );
8011
9363
  }