@t2000/sdk 5.0.0 → 5.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.cjs CHANGED
@@ -576,8 +576,8 @@ async function addSwapToTx(tx, client, address, input) {
576
576
  inputCoin = result.coin;
577
577
  effectiveRaw = result.effectiveAmount;
578
578
  } else {
579
- const { selectAndSplitCoin: selectAndSplitCoin3 } = await Promise.resolve().then(() => (init_coinSelection(), coinSelection_exports));
580
- const result = await selectAndSplitCoin3(tx, client, address, fromType, requestedRaw, {
579
+ const { selectAndSplitCoin: selectAndSplitCoin2 } = await Promise.resolve().then(() => (init_coinSelection(), coinSelection_exports));
580
+ const result = await selectAndSplitCoin2(tx, client, address, fromType, requestedRaw, {
581
581
  sponsoredContext: input.sponsoredContext ?? false,
582
582
  mergeCache: input.coinMergeCache
583
583
  });
@@ -997,55 +997,126 @@ async function executeTx(client, signer, buildTx, options = {}) {
997
997
  return { digest: txn.digest, gasCostSui, effects };
998
998
  }
999
999
 
1000
- // src/mpp-cost.ts
1001
- function parseChallengeAmount(challenge) {
1002
- const raw = challenge?.request?.amount;
1003
- const n = typeof raw === "string" ? Number(raw) : typeof raw === "number" ? raw : Number.NaN;
1004
- return Number.isFinite(n) ? n : void 0;
1005
- }
1006
-
1007
1000
  // src/wallet/pay.ts
1001
+ init_errors();
1008
1002
  async function payWithMpp(args) {
1009
1003
  const { signer, client, options } = args;
1010
- const { Mppx } = await import('mppx/client');
1011
- const { sui, USDC } = await import('@suimpp/mpp/client');
1012
- const { SuiGrpcClient: SuiGrpcClient2 } = await import('@mysten/sui/grpc');
1013
- const signerAddress = signer.getAddress();
1014
- let paymentDigest;
1015
- let gasCostSui = 0;
1016
- let chargedAmount;
1017
- const network = client.network === "testnet" ? "testnet" : "mainnet";
1018
- const grpcBaseUrl = network === "testnet" ? "https://fullnode.testnet.sui.io" : "https://fullnode.mainnet.sui.io";
1019
- const grpcClient = new SuiGrpcClient2({ baseUrl: grpcBaseUrl, network });
1020
- const mppx = Mppx.create({
1021
- polyfill: false,
1022
- onChallenge: async (challenge) => {
1023
- const parsed = parseChallengeAmount(challenge);
1024
- if (parsed !== void 0) chargedAmount = parsed;
1025
- return void 0;
1026
- },
1027
- methods: [sui({
1028
- client,
1029
- currency: USDC,
1030
- signer: {
1031
- toSuiAddress: () => signerAddress,
1032
- signPersonalMessage: (bytes) => signer.signPersonalMessage(bytes)
1033
- },
1034
- execute: async (tx) => {
1035
- const result = await executeTx(client, signer, () => tx, { buildClient: grpcClient });
1036
- paymentDigest = result.digest;
1037
- gasCostSui = result.gasCostSui;
1038
- return { digest: result.digest };
1039
- }
1040
- })]
1041
- });
1042
1004
  const method = (options.method ?? "GET").toUpperCase();
1043
1005
  const canHaveBody = method !== "GET" && method !== "HEAD";
1044
- const response = await mppx.fetch(options.url, {
1006
+ const reqInit = {
1045
1007
  method,
1046
1008
  headers: options.headers,
1047
1009
  body: canHaveBody ? options.body : void 0
1010
+ };
1011
+ const probe = await fetch(options.url, reqInit);
1012
+ if (probe.status !== 402) {
1013
+ return finalize(probe, { paid: false });
1014
+ }
1015
+ const requirements = await pickSuiExactRequirements(probe, client.network);
1016
+ if (!requirements) {
1017
+ throw new exports.T2000Error(
1018
+ "FACILITATOR_REJECTION",
1019
+ `Endpoint returned 402 without an x402 'exact' / sui:${client.network} payment requirement. This SDK only speaks the x402 dialect.`
1020
+ );
1021
+ }
1022
+ return payViaX402({ signer, client, options, reqInit, requirements });
1023
+ }
1024
+ async function pickSuiExactRequirements(response, network) {
1025
+ try {
1026
+ const body = await response.clone().json();
1027
+ const want = `sui:${network === "testnet" ? "testnet" : "mainnet"}`;
1028
+ return body.accepts?.find((a) => a.scheme === "exact" && a.network === want);
1029
+ } catch {
1030
+ return void 0;
1031
+ }
1032
+ }
1033
+ async function payViaX402(args) {
1034
+ const { signer, client, options, reqInit, requirements } = args;
1035
+ const { buildX402SignedPayment, X402_PAYMENT_HEADER, X402_PAYMENT_RESPONSE_HEADER } = await import('@suimpp/mpp/x402');
1036
+ const amountRaw = BigInt(requirements.maxAmountRequired);
1037
+ const migrationGasSui = await ensureAddressBalanceCovers({
1038
+ signer,
1039
+ client,
1040
+ asset: requirements.asset,
1041
+ amountRaw
1042
+ });
1043
+ const signerAdapter = {
1044
+ toSuiAddress: () => signer.getAddress(),
1045
+ signTransaction: (bytes) => signer.signTransaction(bytes)
1046
+ };
1047
+ const { header } = await buildX402SignedPayment({ requirements, signer: signerAdapter });
1048
+ const res = await fetch(options.url, {
1049
+ ...reqInit,
1050
+ headers: { ...options.headers ?? {}, [X402_PAYMENT_HEADER]: header }
1048
1051
  });
1052
+ const settleHeader = res.headers.get(X402_PAYMENT_RESPONSE_HEADER);
1053
+ const paid = !!settleHeader;
1054
+ let digest;
1055
+ if (settleHeader) {
1056
+ try {
1057
+ digest = JSON.parse(new TextDecoder().decode(utils.fromBase64(settleHeader))).transaction;
1058
+ } catch {
1059
+ digest = void 0;
1060
+ }
1061
+ }
1062
+ const result = await finalize(res, { paid });
1063
+ if (!paid) return { ...result, dialect: "x402" };
1064
+ return {
1065
+ ...result,
1066
+ dialect: "x402",
1067
+ cost: atomicToHuman(amountRaw, await assetDecimals(requirements.asset)),
1068
+ gasCostSui: migrationGasSui,
1069
+ receipt: digest ? { reference: digest, timestamp: (/* @__PURE__ */ new Date()).toISOString() } : result.receipt
1070
+ };
1071
+ }
1072
+ async function ensureAddressBalanceCovers(args) {
1073
+ const { signer, client, asset, amountRaw } = args;
1074
+ const owner = signer.getAddress();
1075
+ const balanceResp = await client.core.getBalance({ owner, coinType: asset });
1076
+ const total = BigInt(balanceResp.balance.balance);
1077
+ if (total < amountRaw) {
1078
+ throw new exports.T2000Error("INSUFFICIENT_BALANCE", `Insufficient ${asset} to pay`, {
1079
+ available: total.toString(),
1080
+ required: amountRaw.toString()
1081
+ });
1082
+ }
1083
+ let coinSum = 0n;
1084
+ let cursor;
1085
+ let hasNext = true;
1086
+ while (hasNext) {
1087
+ const page = await client.core.listCoins({ owner, coinType: asset, cursor: cursor ?? void 0 });
1088
+ for (const c of page.objects) coinSum += BigInt(c.balance);
1089
+ cursor = page.cursor;
1090
+ hasNext = page.hasNextPage;
1091
+ }
1092
+ const addressBalance = total - coinSum;
1093
+ if (addressBalance >= amountRaw) return 0;
1094
+ const shortfall = amountRaw - addressBalance;
1095
+ const { Transaction: Transaction6 } = await import('@mysten/sui/transactions');
1096
+ const { selectAndSplitCoin: selectAndSplitCoin2 } = await Promise.resolve().then(() => (init_coinSelection(), coinSelection_exports));
1097
+ const grpcClient = await makeGrpcBuildClient(client);
1098
+ const migration = await executeTx(
1099
+ client,
1100
+ signer,
1101
+ async () => {
1102
+ const tx = new Transaction6();
1103
+ const { coin } = await selectAndSplitCoin2(tx, client, owner, asset, shortfall, {
1104
+ sponsoredContext: true,
1105
+ // coin-objects-only — never the address balance
1106
+ allowSwapAll: false
1107
+ });
1108
+ tx.moveCall({
1109
+ target: "0x2::coin::send_funds",
1110
+ typeArguments: [asset],
1111
+ arguments: [coin, tx.pure.address(owner)]
1112
+ });
1113
+ return tx;
1114
+ },
1115
+ { buildClient: grpcClient }
1116
+ );
1117
+ return migration.gasCostSui;
1118
+ }
1119
+ async function finalize(response, opts) {
1049
1120
  const contentType = response.headers.get("content-type") ?? "";
1050
1121
  let body;
1051
1122
  try {
@@ -1053,15 +1124,25 @@ async function payWithMpp(args) {
1053
1124
  } catch {
1054
1125
  body = null;
1055
1126
  }
1056
- const paid = !!paymentDigest;
1057
- return {
1058
- status: response.status,
1059
- body,
1060
- paid,
1061
- cost: paid ? chargedAmount ?? options.maxPrice ?? void 0 : void 0,
1062
- gasCostSui: paid ? gasCostSui : void 0,
1063
- receipt: paymentDigest ? { reference: paymentDigest, timestamp: (/* @__PURE__ */ new Date()).toISOString() } : void 0
1064
- };
1127
+ return { status: response.status, body, paid: opts.paid };
1128
+ }
1129
+ async function makeGrpcBuildClient(client) {
1130
+ const { SuiGrpcClient: SuiGrpcClient2 } = await import('@mysten/sui/grpc');
1131
+ const network = client.network === "testnet" ? "testnet" : "mainnet";
1132
+ const baseUrl = network === "testnet" ? "https://fullnode.testnet.sui.io" : "https://fullnode.mainnet.sui.io";
1133
+ return new SuiGrpcClient2({ baseUrl, network });
1134
+ }
1135
+ function atomicToHuman(raw, decimals) {
1136
+ return Number(raw) / 10 ** decimals;
1137
+ }
1138
+ async function assetDecimals(coinType) {
1139
+ try {
1140
+ const { getDecimalsForCoinType: getDecimalsForCoinType2 } = await Promise.resolve().then(() => (init_token_registry(), token_registry_exports));
1141
+ const d = getDecimalsForCoinType2(coinType);
1142
+ return typeof d === "number" ? d : 6;
1143
+ } catch {
1144
+ return 6;
1145
+ }
1065
1146
  }
1066
1147
 
1067
1148
  // src/wallet/zkLoginSigner.ts
@@ -1684,170 +1765,198 @@ async function normalizeAddressInput(value, ctx = {}) {
1684
1765
 
1685
1766
  // src/t2000.ts
1686
1767
  init_errors();
1768
+ var DEFAULT_CONFIG_DIR = path.join(os.homedir(), ".t2000");
1769
+ function resolveConfigPath(configDir) {
1770
+ return path.join(configDir ?? DEFAULT_CONFIG_DIR, "config.json");
1771
+ }
1772
+ function todayUtc() {
1773
+ return (/* @__PURE__ */ new Date()).toISOString().slice(0, 10);
1774
+ }
1775
+ function readLimitsFile(configDir) {
1776
+ const path = resolveConfigPath(configDir);
1777
+ try {
1778
+ return sanitize(JSON.parse(fs.readFileSync(path, "utf-8")));
1779
+ } catch {
1780
+ return {};
1781
+ }
1782
+ }
1783
+ function writeLimitsFile(file, configDir) {
1784
+ const path$1 = resolveConfigPath(configDir);
1785
+ let existing = {};
1786
+ try {
1787
+ existing = JSON.parse(fs.readFileSync(path$1, "utf-8"));
1788
+ } catch {
1789
+ existing = {};
1790
+ }
1791
+ for (const dead of ["maxPerTx", "maxDailySend", "dailyUsed", "dailyResetDate", "locked"]) {
1792
+ delete existing[dead];
1793
+ }
1794
+ const merged = { ...existing };
1795
+ if (file.limits && (file.limits.perTxUsd !== void 0 || file.limits.dailyUsd !== void 0)) {
1796
+ merged.limits = file.limits;
1797
+ } else {
1798
+ delete merged.limits;
1799
+ }
1800
+ if (file.dailySpend) merged.dailySpend = file.dailySpend;
1801
+ else delete merged.dailySpend;
1802
+ const dir = path.dirname(path$1);
1803
+ if (!fs.existsSync(dir)) fs.mkdirSync(dir, { recursive: true });
1804
+ fs.writeFileSync(path$1, JSON.stringify(merged, null, 2) + "\n", { mode: 384 });
1805
+ return path$1;
1806
+ }
1807
+ function getLimits(configDir) {
1808
+ return readLimitsFile(configDir).limits;
1809
+ }
1810
+ function hasLimits(configDir) {
1811
+ const l = readLimitsFile(configDir).limits;
1812
+ return !!l && (l.perTxUsd !== void 0 || l.dailyUsd !== void 0);
1813
+ }
1814
+ function setLimits(limits, configDir) {
1815
+ const file = readLimitsFile(configDir);
1816
+ const merged = { ...file.limits };
1817
+ if (limits.perTxUsd !== void 0) merged.perTxUsd = limits.perTxUsd;
1818
+ if (limits.dailyUsd !== void 0) merged.dailyUsd = limits.dailyUsd;
1819
+ return writeLimitsFile({ ...file, limits: merged }, configDir);
1820
+ }
1821
+ function clearLimits(configDir) {
1822
+ const file = readLimitsFile(configDir);
1823
+ return writeLimitsFile({ ...file, limits: void 0 }, configDir);
1824
+ }
1825
+ function dailySpentToday(configDir) {
1826
+ const { dailySpend } = readLimitsFile(configDir);
1827
+ if (!dailySpend || dailySpend.date !== todayUtc()) return 0;
1828
+ return dailySpend.usd;
1829
+ }
1830
+ function recordDailySpend(usd, configDir) {
1831
+ if (!Number.isFinite(usd) || usd <= 0) return;
1832
+ const file = readLimitsFile(configDir);
1833
+ const today = todayUtc();
1834
+ const prior = file.dailySpend && file.dailySpend.date === today ? file.dailySpend.usd : 0;
1835
+ writeLimitsFile({ ...file, dailySpend: { date: today, usd: prior + usd } }, configDir);
1836
+ }
1837
+ function sanitize(raw) {
1838
+ if (typeof raw !== "object" || raw === null) return {};
1839
+ const r = raw;
1840
+ const out = {};
1841
+ if (typeof r.limits === "object" && r.limits !== null) {
1842
+ const l = r.limits;
1843
+ const limits = {};
1844
+ if (typeof l.perTxUsd === "number" && l.perTxUsd > 0) limits.perTxUsd = l.perTxUsd;
1845
+ if (typeof l.dailyUsd === "number" && l.dailyUsd > 0) limits.dailyUsd = l.dailyUsd;
1846
+ else if (typeof l.dailySendUsd === "number" && l.dailySendUsd > 0) limits.dailyUsd = l.dailySendUsd;
1847
+ if (limits.perTxUsd !== void 0 || limits.dailyUsd !== void 0) out.limits = limits;
1848
+ }
1849
+ if (typeof r.dailySpend === "object" && r.dailySpend !== null) {
1850
+ const d = r.dailySpend;
1851
+ if (typeof d.date === "string" && typeof d.usd === "number" && d.usd >= 0) {
1852
+ out.dailySpend = { date: d.date, usd: d.usd };
1853
+ }
1854
+ }
1855
+ return out;
1856
+ }
1687
1857
 
1688
- // src/safeguards/types.ts
1689
- var DEFAULT_SAFEGUARD_CONFIG = {
1690
- locked: false,
1691
- maxPerTx: 0,
1692
- maxDailySend: 0,
1693
- dailyUsed: 0,
1694
- dailyResetDate: ""
1695
- };
1696
-
1697
- // src/safeguards/errors.ts
1698
- init_errors();
1699
- var SafeguardError = class extends exports.T2000Error {
1700
- rule;
1701
- details;
1702
- constructor(rule, details, message) {
1703
- const msg = message ?? buildMessage(rule, details);
1704
- super("SAFEGUARD_BLOCKED", msg, { rule, ...details });
1705
- this.name = "SafeguardError";
1706
- this.rule = rule;
1707
- this.details = details;
1858
+ // src/limits/errors.ts
1859
+ var LimitExceededError = class extends Error {
1860
+ code = "LIMIT_EXCEEDED";
1861
+ operation;
1862
+ limitKind;
1863
+ limit;
1864
+ attempted;
1865
+ constructor(params) {
1866
+ const label = params.limitKind === "perTxUsd" ? "per-transaction limit" : "daily spend limit";
1867
+ super(
1868
+ `Exceeds ${label} ($${params.limit}). Attempted $${params.attempted.toFixed(2)}. Use --force / force:true to override.`
1869
+ );
1870
+ this.name = "LimitExceededError";
1871
+ this.operation = params.operation;
1872
+ this.limitKind = params.limitKind;
1873
+ this.limit = params.limit;
1874
+ this.attempted = params.attempted;
1708
1875
  }
1709
1876
  toJSON() {
1710
1877
  return {
1711
- error: "SAFEGUARD_BLOCKED",
1878
+ error: this.code,
1712
1879
  message: this.message,
1713
- retryable: this.retryable,
1714
- data: { rule: this.rule, ...this.details }
1880
+ operation: this.operation,
1881
+ limitKind: this.limitKind,
1882
+ limit: this.limit,
1883
+ attempted: this.attempted
1715
1884
  };
1716
1885
  }
1717
1886
  };
1718
- function buildMessage(rule, details) {
1719
- switch (rule) {
1720
- case "locked":
1721
- return "Agent is locked. All operations are frozen.";
1722
- case "maxPerTx":
1723
- return `Amount $${(details.attempted ?? 0).toFixed(2)} exceeds per-transaction limit ($${(details.limit ?? 0).toFixed(2)})`;
1724
- case "maxDailySend":
1725
- return `Daily send limit reached ($${(details.current ?? 0).toFixed(2)}/$${(details.limit ?? 0).toFixed(2)} used today)`;
1887
+
1888
+ // src/limits/enforce.ts
1889
+ function approxUsdValue(asset, amount) {
1890
+ const symbol = asset.toUpperCase();
1891
+ if (symbol === "USDC" || symbol === "USDSUI") return amount;
1892
+ return null;
1893
+ }
1894
+ function assertLimitConfig(input) {
1895
+ if (input.force) return;
1896
+ if (!Number.isFinite(input.amountUsd) || input.amountUsd <= 0) return;
1897
+ const { limits } = input;
1898
+ if (!limits) return;
1899
+ if (limits.perTxUsd !== void 0 && input.amountUsd > limits.perTxUsd) {
1900
+ throw new LimitExceededError({
1901
+ operation: input.operation,
1902
+ limitKind: "perTxUsd",
1903
+ limit: limits.perTxUsd,
1904
+ attempted: input.amountUsd
1905
+ });
1906
+ }
1907
+ if (limits.dailyUsd !== void 0 && input.spentTodayUsd + input.amountUsd > limits.dailyUsd) {
1908
+ throw new LimitExceededError({
1909
+ operation: input.operation,
1910
+ limitKind: "dailyUsd",
1911
+ // The cap is total/day; report the cap (attempted is THIS write's USD).
1912
+ limit: limits.dailyUsd,
1913
+ attempted: input.spentTodayUsd + input.amountUsd
1914
+ });
1726
1915
  }
1727
1916
  }
1728
-
1729
- // src/safeguards/enforcer.ts
1730
- var SafeguardEnforcer = class {
1731
- config;
1732
- configPath;
1917
+ var LimitEnforcer = class {
1733
1918
  constructor(configDir) {
1734
- this.config = { ...DEFAULT_SAFEGUARD_CONFIG };
1735
- this.configPath = configDir ? path.join(configDir, "config.json") : null;
1919
+ this.configDir = configDir;
1920
+ }
1921
+ /** Throws `LimitExceededError` when the write exceeds an opted-in cap. */
1922
+ assert(input) {
1923
+ assertLimitConfig({
1924
+ limits: getLimits(this.configDir),
1925
+ spentTodayUsd: dailySpentToday(this.configDir),
1926
+ operation: input.operation,
1927
+ amountUsd: input.amountUsd,
1928
+ force: input.force
1929
+ });
1736
1930
  }
1737
- load() {
1738
- if (!this.configPath) return;
1739
- try {
1740
- const raw = JSON.parse(fs.readFileSync(this.configPath, "utf-8"));
1741
- this.config = {
1742
- ...DEFAULT_SAFEGUARD_CONFIG,
1743
- locked: raw.locked ?? false,
1744
- maxPerTx: raw.maxPerTx ?? 0,
1745
- maxDailySend: raw.maxDailySend ?? 0,
1746
- dailyUsed: raw.dailyUsed ?? 0,
1747
- dailyResetDate: raw.dailyResetDate ?? ""
1748
- };
1749
- } catch {
1750
- this.config = { ...DEFAULT_SAFEGUARD_CONFIG };
1751
- }
1931
+ /** Add a settled write's USD value to today's cumulative total. */
1932
+ record(amountUsd) {
1933
+ recordDailySpend(amountUsd, this.configDir);
1752
1934
  }
1753
- assertNotLocked() {
1754
- this.load();
1755
- if (this.config.locked) {
1756
- throw new SafeguardError("locked", {});
1757
- }
1935
+ getLimits() {
1936
+ return getLimits(this.configDir);
1758
1937
  }
1759
- check(metadata) {
1760
- this.load();
1761
- if (this.config.locked) {
1762
- throw new SafeguardError("locked", {});
1763
- }
1764
- const amount = metadata.amount ?? 0;
1765
- if (this.config.maxPerTx > 0 && amount > this.config.maxPerTx) {
1766
- throw new SafeguardError("maxPerTx", {
1767
- attempted: amount,
1768
- limit: this.config.maxPerTx
1769
- });
1770
- }
1771
- this.resetDailyIfNewDay();
1772
- if (this.config.maxDailySend > 0 && this.config.dailyUsed + amount > this.config.maxDailySend) {
1773
- throw new SafeguardError("maxDailySend", {
1774
- attempted: amount,
1775
- limit: this.config.maxDailySend,
1776
- current: this.config.dailyUsed
1777
- });
1778
- }
1938
+ hasLimits() {
1939
+ return hasLimits(this.configDir);
1779
1940
  }
1780
- recordUsage(amount) {
1781
- this.resetDailyIfNewDay();
1782
- this.config.dailyUsed += amount;
1783
- this.save();
1784
- }
1785
- lock() {
1786
- this.config.locked = true;
1787
- this.save();
1788
- }
1789
- unlock() {
1790
- this.config.locked = false;
1791
- this.save();
1792
- }
1793
- set(key, value) {
1794
- if (key === "locked" && typeof value === "boolean") {
1795
- this.config.locked = value;
1796
- } else if (key === "maxPerTx" && typeof value === "number") {
1797
- this.config.maxPerTx = value;
1798
- } else if (key === "maxDailySend" && typeof value === "number") {
1799
- this.config.maxDailySend = value;
1800
- }
1801
- this.save();
1802
- }
1803
- getConfig() {
1804
- this.load();
1805
- this.resetDailyIfNewDay();
1806
- return { ...this.config };
1807
- }
1808
- isConfigured() {
1809
- return this.config.maxPerTx > 0 || this.config.maxDailySend > 0;
1810
- }
1811
- resetDailyIfNewDay() {
1812
- const today = (/* @__PURE__ */ new Date()).toISOString().slice(0, 10);
1813
- if (this.config.dailyResetDate !== today) {
1814
- this.config.dailyUsed = 0;
1815
- this.config.dailyResetDate = today;
1816
- this.save();
1817
- }
1941
+ setLimits(limits) {
1942
+ setLimits(limits, this.configDir);
1818
1943
  }
1819
- save() {
1820
- if (!this.configPath) return;
1821
- try {
1822
- let existing = {};
1823
- try {
1824
- existing = JSON.parse(fs.readFileSync(this.configPath, "utf-8"));
1825
- } catch {
1826
- }
1827
- const merged = {
1828
- ...existing,
1829
- locked: this.config.locked,
1830
- maxPerTx: this.config.maxPerTx,
1831
- maxDailySend: this.config.maxDailySend,
1832
- dailyUsed: this.config.dailyUsed,
1833
- dailyResetDate: this.config.dailyResetDate
1834
- };
1835
- const dir = this.configPath.replace(/[/\\][^/\\]+$/, "");
1836
- if (!fs.existsSync(dir)) {
1837
- fs.mkdirSync(dir, { recursive: true });
1838
- }
1839
- fs.writeFileSync(this.configPath, JSON.stringify(merged, null, 2) + "\n");
1840
- } catch {
1841
- }
1944
+ clearLimits() {
1945
+ clearLimits(this.configDir);
1946
+ }
1947
+ dailySpentToday() {
1948
+ return dailySpentToday(this.configDir);
1842
1949
  }
1843
1950
  };
1844
- var DEFAULT_CONFIG_DIR = path.join(os.homedir(), ".t2000");
1951
+ var DEFAULT_CONFIG_DIR2 = path.join(os.homedir(), ".t2000");
1845
1952
  var T2000 = class _T2000 extends eventemitter3.EventEmitter {
1846
1953
  _signer;
1847
1954
  _keypair;
1848
1955
  client;
1849
1956
  _address;
1850
- enforcer;
1957
+ /** Unified spending-limit gate (per-tx + cumulative daily, USD). Shared by
1958
+ * CLI + MCP + programmatic writes — one gate, no bypass (R-0 Finding 1). */
1959
+ limits;
1851
1960
  constructor(keypairOrSigner, client, configDir, isSignerMode) {
1852
1961
  super();
1853
1962
  if (isSignerMode) {
@@ -1861,8 +1970,7 @@ var T2000 = class _T2000 extends eventemitter3.EventEmitter {
1861
1970
  this._address = getAddress(kp);
1862
1971
  }
1863
1972
  this.client = client;
1864
- this.enforcer = new SafeguardEnforcer(configDir);
1865
- this.enforcer.load();
1973
+ this.limits = new LimitEnforcer(configDir);
1866
1974
  }
1867
1975
  static async create(options = {}) {
1868
1976
  const { keyPath, rpcUrl } = options;
@@ -1875,7 +1983,7 @@ var T2000 = class _T2000 extends eventemitter3.EventEmitter {
1875
1983
  );
1876
1984
  }
1877
1985
  const keypair = await loadKey(void 0, keyPath);
1878
- return new _T2000(keypair, client, DEFAULT_CONFIG_DIR);
1986
+ return new _T2000(keypair, client, DEFAULT_CONFIG_DIR2);
1879
1987
  }
1880
1988
  static fromPrivateKey(privateKey, options = {}) {
1881
1989
  const keypair = keypairFromPrivateKey(privateKey);
@@ -1886,7 +1994,7 @@ var T2000 = class _T2000 extends eventemitter3.EventEmitter {
1886
1994
  const keypair = generateKeypair();
1887
1995
  await saveKey(keypair, void 0, options.keyPath);
1888
1996
  const client = getSuiClient();
1889
- const agent = new _T2000(keypair, client, DEFAULT_CONFIG_DIR);
1997
+ const agent = new _T2000(keypair, client, DEFAULT_CONFIG_DIR2);
1890
1998
  const address = agent.address();
1891
1999
  return { agent, address };
1892
2000
  }
@@ -1908,11 +2016,10 @@ var T2000 = class _T2000 extends eventemitter3.EventEmitter {
1908
2016
  }
1909
2017
  // -- MPP Payments --
1910
2018
  async pay(options) {
1911
- this.enforcer.assertNotLocked();
1912
- this.enforcer.check({ operation: "pay", amount: options.maxPrice ?? 1 });
2019
+ this.limits.assert({ operation: "pay", amountUsd: options.maxPrice ?? 0, force: options.force });
1913
2020
  const result = await payWithMpp({ signer: this._signer, client: this.client, options });
1914
2021
  if (result.paid) {
1915
- this.enforcer.recordUsage(result.cost ?? options.maxPrice ?? 1);
2022
+ this.limits.record(result.cost ?? options.maxPrice ?? 0);
1916
2023
  }
1917
2024
  return result;
1918
2025
  }
@@ -1926,7 +2033,11 @@ var T2000 = class _T2000 extends eventemitter3.EventEmitter {
1926
2033
  // and the S.323 build-tracker entry.
1927
2034
  // -- Swap --
1928
2035
  async swap(params) {
1929
- this.enforcer.assertNotLocked();
2036
+ this.limits.assert({
2037
+ operation: "swap",
2038
+ amountUsd: approxUsdValue(params.from, params.amount) ?? 0,
2039
+ force: params.force
2040
+ });
1930
2041
  const { findSwapRoute: findSwapRoute2, buildSwapTx: buildSwapTx2, resolveTokenType: resolveTokenType2 } = await Promise.resolve().then(() => (init_cetus_swap(), cetus_swap_exports));
1931
2042
  const fromType = resolveTokenType2(params.from);
1932
2043
  const toType = resolveTokenType2(params.to);
@@ -2018,6 +2129,7 @@ var T2000 = class _T2000 extends eventemitter3.EventEmitter {
2018
2129
  const fromName = resolveSymbol(fromType);
2019
2130
  const toName = resolveSymbol(toType);
2020
2131
  const routeDesc = route.routerData.paths?.map((p) => p.provider).filter(Boolean).slice(0, 3).join(" + ") ?? "Cetus Aggregator";
2132
+ this.limits.record(approxUsdValue(params.from, params.amount) ?? 0);
2021
2133
  return {
2022
2134
  success: true,
2023
2135
  tx: gasResult.digest,
@@ -2074,7 +2186,6 @@ var T2000 = class _T2000 extends eventemitter3.EventEmitter {
2074
2186
  * the "build via gRPC, execute via JSON-RPC" hybrid).
2075
2187
  */
2076
2188
  async send(params) {
2077
- this.enforcer.assertNotLocked();
2078
2189
  const asset = params.asset;
2079
2190
  if (!asset) {
2080
2191
  throw new exports.T2000Error(
@@ -2084,6 +2195,11 @@ var T2000 = class _T2000 extends eventemitter3.EventEmitter {
2084
2195
  }
2085
2196
  assertAllowedAsset("send", asset);
2086
2197
  const sendableAsset = asset;
2198
+ this.limits.assert({
2199
+ operation: "send",
2200
+ amountUsd: approxUsdValue(sendableAsset, params.amount) ?? 0,
2201
+ force: params.force
2202
+ });
2087
2203
  const resolved = await this.resolveRecipient(params.to);
2088
2204
  const sendAmount = params.amount;
2089
2205
  const sendTo = resolved.address;
@@ -2095,7 +2211,7 @@ var T2000 = class _T2000 extends eventemitter3.EventEmitter {
2095
2211
  () => buildSendTx({ client: this.client, address: this._address, to: sendTo, amount: sendAmount, asset: sendableAsset }),
2096
2212
  { buildClient }
2097
2213
  );
2098
- this.enforcer.recordUsage(sendAmount);
2214
+ this.limits.record(approxUsdValue(sendableAsset, sendAmount) ?? 0);
2099
2215
  const balance = await this.balance();
2100
2216
  this.emitBalanceChange(sendableAsset, sendAmount, "send", gasResult.digest);
2101
2217
  return {
@@ -2653,7 +2769,6 @@ exports.CETUS_USDC_SUI_POOL = CETUS_USDC_SUI_POOL;
2653
2769
  exports.CLOCK_ID = CLOCK_ID;
2654
2770
  exports.DEFAULT_GRPC_URL = DEFAULT_GRPC_URL;
2655
2771
  exports.DEFAULT_NETWORK = DEFAULT_NETWORK;
2656
- exports.DEFAULT_SAFEGUARD_CONFIG = DEFAULT_SAFEGUARD_CONFIG;
2657
2772
  exports.GASLESS_MIN_STABLE_AMOUNT = GASLESS_MIN_STABLE_AMOUNT;
2658
2773
  exports.GASLESS_STABLE_TYPES = GASLESS_STABLE_TYPES;
2659
2774
  exports.GAS_RESERVE_MIN = GAS_RESERVE_MIN;
@@ -2661,6 +2776,8 @@ exports.InvalidAddressError = InvalidAddressError;
2661
2776
  exports.KNOWN_TARGETS = KNOWN_TARGETS;
2662
2777
  exports.KeypairSigner = KeypairSigner;
2663
2778
  exports.LABEL_PATTERNS = LABEL_PATTERNS;
2779
+ exports.LimitEnforcer = LimitEnforcer;
2780
+ exports.LimitExceededError = LimitExceededError;
2664
2781
  exports.MIST_PER_SUI = MIST_PER_SUI;
2665
2782
  exports.OPERATION_ASSETS = OPERATION_ASSETS;
2666
2783
  exports.SENDABLE_ASSETS = SENDABLE_ASSETS;
@@ -2671,8 +2788,6 @@ exports.SUI_ADDRESS_REGEX = SUI_ADDRESS_REGEX;
2671
2788
  exports.SUI_ADDRESS_STRICT_REGEX = SUI_ADDRESS_STRICT_REGEX;
2672
2789
  exports.SUI_DECIMALS = SUI_DECIMALS;
2673
2790
  exports.SUPPORTED_ASSETS = SUPPORTED_ASSETS;
2674
- exports.SafeguardEnforcer = SafeguardEnforcer;
2675
- exports.SafeguardError = SafeguardError;
2676
2791
  exports.SuinsNotRegisteredError = SuinsNotRegisteredError;
2677
2792
  exports.SuinsRpcError = SuinsRpcError;
2678
2793
  exports.T2000 = T2000;
@@ -2682,7 +2797,9 @@ exports.WRITE_APPENDER_REGISTRY = WRITE_APPENDER_REGISTRY;
2682
2797
  exports.ZkLoginSigner = ZkLoginSigner;
2683
2798
  exports.addSendToTx = addSendToTx;
2684
2799
  exports.addSwapToTx = addSwapToTx;
2800
+ exports.approxUsdValue = approxUsdValue;
2685
2801
  exports.assertAllowedAsset = assertAllowedAsset;
2802
+ exports.assertLimitConfig = assertLimitConfig;
2686
2803
  exports.buildAddLeafTx = buildAddLeafTx;
2687
2804
  exports.buildRevokeLeafTx = buildRevokeLeafTx;
2688
2805
  exports.buildSendTx = buildSendTx;
@@ -2690,7 +2807,9 @@ exports.buildSwapTx = buildSwapTx;
2690
2807
  exports.classifyAction = classifyAction;
2691
2808
  exports.classifyLabel = classifyLabel;
2692
2809
  exports.classifyTransaction = classifyTransaction;
2810
+ exports.clearLimits = clearLimits;
2693
2811
  exports.composeTx = composeTx;
2812
+ exports.dailySpentToday = dailySpentToday;
2694
2813
  exports.deriveAllowedAddressesFromPtb = deriveAllowedAddressesFromPtb;
2695
2814
  exports.deserializeCetusRoute = deserializeCetusRoute;
2696
2815
  exports.displayHandle = displayHandle;
@@ -2712,10 +2831,12 @@ exports.getAddress = getAddress;
2712
2831
  exports.getCoinMeta = getCoinMeta;
2713
2832
  exports.getDecimals = getDecimals;
2714
2833
  exports.getDecimalsForCoinType = getDecimalsForCoinType;
2834
+ exports.getLimits = getLimits;
2715
2835
  exports.getSponsoredSwapProviders = getSponsoredSwapProviders;
2716
2836
  exports.getSuiClient = getSuiClient;
2717
2837
  exports.getSuiGrpcClient = getSuiGrpcClient;
2718
2838
  exports.getSwapQuote = getSwapQuote;
2839
+ exports.hasLimits = hasLimits;
2719
2840
  exports.isAllowedAsset = isAllowedAsset;
2720
2841
  exports.isCetusRouteFresh = isCetusRouteFresh;
2721
2842
  exports.isInRegistry = isInRegistry;
@@ -2734,6 +2855,8 @@ exports.queryHistory = queryHistory;
2734
2855
  exports.queryTransaction = queryTransaction;
2735
2856
  exports.rawToStable = rawToStable;
2736
2857
  exports.rawToUsdc = rawToUsdc;
2858
+ exports.readLimitsFile = readLimitsFile;
2859
+ exports.recordDailySpend = recordDailySpend;
2737
2860
  exports.refineLendingLabel = refineLendingLabel;
2738
2861
  exports.resolveAddressToSuinsViaRpc = resolveAddressToSuinsViaRpc;
2739
2862
  exports.resolveSuinsViaRpc = resolveSuinsViaRpc;
@@ -2744,6 +2867,7 @@ exports.saveKey = saveKey;
2744
2867
  exports.selectAndSplitCoin = selectAndSplitCoin;
2745
2868
  exports.selectSuiCoin = selectSuiCoin;
2746
2869
  exports.serializeCetusRoute = serializeCetusRoute;
2870
+ exports.setLimits = setLimits;
2747
2871
  exports.simulateTransaction = simulateTransaction;
2748
2872
  exports.stableToRaw = stableToRaw;
2749
2873
  exports.suiToMist = suiToMist;
@@ -2754,5 +2878,6 @@ exports.validateAddress = validateAddress;
2754
2878
  exports.validateLabel = validateLabel;
2755
2879
  exports.verifyCetusRouteCoinMatch = verifyCetusRouteCoinMatch;
2756
2880
  exports.walletExists = walletExists;
2881
+ exports.writeLimitsFile = writeLimitsFile;
2757
2882
  //# sourceMappingURL=index.cjs.map
2758
2883
  //# sourceMappingURL=index.cjs.map