@t2000/engine 1.1.2 → 1.2.1

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.js CHANGED
@@ -1642,9 +1642,96 @@ async function fetchAudricHistory(address, opts, env, signal) {
1642
1642
  }
1643
1643
  }
1644
1644
 
1645
+ // src/sui-address.ts
1646
+ var SUI_MAINNET_URL2 = "https://fullnode.mainnet.sui.io:443";
1647
+ var SUI_ADDRESS_REGEX = /^0x[a-fA-F0-9]{1,64}$/i;
1648
+ var SUI_ADDRESS_STRICT_REGEX = /^0x[a-fA-F0-9]{64}$/i;
1649
+ var SUINS_NAME_REGEX = /^[a-z0-9-]+(\.[a-z0-9-]+)*\.sui$/;
1650
+ var InvalidAddressError = class extends Error {
1651
+ constructor(raw) {
1652
+ super(
1653
+ `"${raw}" isn't a valid Sui address or SuiNS name. Pass a 0x-prefixed hex address (e.g. 0x40cd\u20263e62) or a SuiNS name ending in .sui (e.g. alex.sui).`
1654
+ );
1655
+ this.raw = raw;
1656
+ this.name = "InvalidAddressError";
1657
+ }
1658
+ };
1659
+ var SuinsNotRegisteredError = class extends Error {
1660
+ constructor(name_) {
1661
+ super(
1662
+ `"${name_}" isn't a registered SuiNS name. Double-check the spelling, or paste the full Sui address (0x\u2026 64 hex characters).`
1663
+ );
1664
+ this.name_ = name_;
1665
+ this.name = "SuinsNotRegisteredError";
1666
+ }
1667
+ };
1668
+ var SuinsRpcError = class extends Error {
1669
+ constructor(name_, detail) {
1670
+ super(`SuiNS lookup failed for "${name_}" (${detail}). Try again, or paste the full Sui address.`);
1671
+ this.name_ = name_;
1672
+ this.name = "SuinsRpcError";
1673
+ }
1674
+ };
1675
+ function looksLikeSuiNs(value) {
1676
+ if (!value) return false;
1677
+ return SUINS_NAME_REGEX.test(value.trim().toLowerCase());
1678
+ }
1679
+ async function resolveSuinsViaRpc(rawName, ctx = {}) {
1680
+ const name = rawName.trim().toLowerCase();
1681
+ if (!SUINS_NAME_REGEX.test(name)) {
1682
+ throw new InvalidAddressError(rawName);
1683
+ }
1684
+ const url = ctx.suiRpcUrl || SUI_MAINNET_URL2;
1685
+ let res;
1686
+ try {
1687
+ res = await fetch(url, {
1688
+ method: "POST",
1689
+ headers: { "Content-Type": "application/json" },
1690
+ body: JSON.stringify({
1691
+ jsonrpc: "2.0",
1692
+ id: 1,
1693
+ method: "suix_resolveNameServiceAddress",
1694
+ params: [name]
1695
+ }),
1696
+ signal: ctx.signal ?? AbortSignal.timeout(8e3)
1697
+ });
1698
+ } catch (err) {
1699
+ const msg = err instanceof Error ? err.message : String(err);
1700
+ throw new SuinsRpcError(name, msg);
1701
+ }
1702
+ if (!res.ok) {
1703
+ throw new SuinsRpcError(name, `HTTP ${res.status}`);
1704
+ }
1705
+ let body;
1706
+ try {
1707
+ body = await res.json();
1708
+ } catch (err) {
1709
+ const msg = err instanceof Error ? err.message : String(err);
1710
+ throw new SuinsRpcError(name, `JSON parse failed: ${msg}`);
1711
+ }
1712
+ if (body.error) {
1713
+ throw new SuinsRpcError(name, body.error.message);
1714
+ }
1715
+ return body.result ?? null;
1716
+ }
1717
+ async function normalizeAddressInput(value, ctx = {}) {
1718
+ const trimmed = value.trim();
1719
+ if (SUI_ADDRESS_REGEX.test(trimmed)) {
1720
+ return { address: trimmed.toLowerCase(), suinsName: null, raw: value };
1721
+ }
1722
+ if (looksLikeSuiNs(trimmed)) {
1723
+ const name = trimmed.toLowerCase();
1724
+ const address = await resolveSuinsViaRpc(name, ctx);
1725
+ if (!address) {
1726
+ throw new SuinsNotRegisteredError(name);
1727
+ }
1728
+ return { address: address.toLowerCase(), suinsName: name, raw: value };
1729
+ }
1730
+ throw new InvalidAddressError(value);
1731
+ }
1732
+
1645
1733
  // src/tools/balance.ts
1646
1734
  var GAS_RESERVE_SUI2 = 0.05;
1647
- var SUI_ADDRESS_REGEX = /^0x[a-fA-F0-9]{1,64}$/;
1648
1735
  var VSUI_COIN_TYPE = "0x549e8b69270defbfafd4f94e17ec44cdbdd99820b33bda2278dea3b9a32d3f55::cert::CERT";
1649
1736
  var SUI_COIN_TYPE = "0x2::sui::SUI";
1650
1737
  var VSUI_FALLBACK_RATE = 1.05;
@@ -1696,17 +1783,16 @@ async function loadPortfolio(address, blockvisionApiKey, fallbackRpcUrl, cache)
1696
1783
  }
1697
1784
  var balanceCheckTool = buildTool({
1698
1785
  name: "balance_check",
1699
- description: "Get the full balance breakdown for the signed-in user OR any public Sui address. Returns wallet holdings (tokens the address owns \u2014 NOT savings), NAVI savings deposits (USDC and/or USDsui deposited into NAVI Protocol earning yield), outstanding debt, pending rewards, gas reserve, total net worth, saveableUsdc (USDC wallet balance available to save), and saveableUsdsui (USDsui wallet balance available to save \u2014 surfaces only when > 0). IMPORTANT: wallet holdings like GOLD, SUI, USDT, USDe are NOT savings positions and are NOT saveable \u2014 only USDC and USDsui can be saved/borrowed. Pass `address` to inspect a contact / watched / public wallet; defaults to the signed-in user when omitted.",
1786
+ description: 'Get the full balance breakdown for the signed-in user OR any public Sui address or SuiNS name. Returns wallet holdings (tokens the address owns \u2014 NOT savings), NAVI savings deposits (USDC and/or USDsui deposited into NAVI Protocol earning yield), outstanding debt, pending rewards, gas reserve, total net worth, saveableUsdc (USDC wallet balance available to save), and saveableUsdsui (USDsui wallet balance available to save \u2014 surfaces only when > 0). IMPORTANT: wallet holdings like GOLD, SUI, USDT, USDe are NOT savings positions and are NOT saveable \u2014 only USDC and USDsui can be saved/borrowed. Pass `address` as a 0x address OR a SuiNS name (e.g. "alex.sui") to inspect a contact / watched / public wallet; defaults to the signed-in user when omitted.',
1700
1787
  inputSchema: z.object({
1701
- address: z.string().regex(SUI_ADDRESS_REGEX).optional().describe("Sui address to inspect (defaults to the signed-in wallet)")
1788
+ address: z.string().optional().describe("Sui address (0x\u2026) or SuiNS name (alex.sui). Defaults to the signed-in wallet when omitted.")
1702
1789
  }),
1703
1790
  jsonSchema: {
1704
1791
  type: "object",
1705
1792
  properties: {
1706
1793
  address: {
1707
1794
  type: "string",
1708
- pattern: "^0x[a-fA-F0-9]{1,64}$",
1709
- description: "Sui address to inspect (defaults to the signed-in wallet)"
1795
+ description: "Sui address (0x\u2026) or SuiNS name (e.g. alex.sui). The engine resolves the name to an on-chain address before querying. Omit to default to the signed-in wallet."
1710
1796
  }
1711
1797
  },
1712
1798
  required: []
@@ -1718,7 +1804,18 @@ var balanceCheckTool = buildTool({
1718
1804
  // calls — each one reflects a different on-chain + market snapshot.
1719
1805
  cacheable: false,
1720
1806
  async call(input, context) {
1721
- const targetAddress = input.address ?? context.walletAddress;
1807
+ let suinsName = null;
1808
+ let targetAddress;
1809
+ if (input.address) {
1810
+ const normalized = await normalizeAddressInput(input.address, {
1811
+ suiRpcUrl: context.suiRpcUrl,
1812
+ signal: context.signal
1813
+ });
1814
+ targetAddress = normalized.address;
1815
+ suinsName = normalized.suinsName;
1816
+ } else {
1817
+ targetAddress = context.walletAddress;
1818
+ }
1722
1819
  const isSelfQuery = !!context.walletAddress && !!targetAddress && targetAddress.toLowerCase() === context.walletAddress.toLowerCase();
1723
1820
  if (hasNaviMcpGlobal(context)) {
1724
1821
  if (!targetAddress) {
@@ -1850,10 +1947,12 @@ var balanceCheckTool = buildTool({
1850
1947
  saveableUsdsui,
1851
1948
  priceSource: portfolio.source,
1852
1949
  address,
1853
- isSelfQuery
1950
+ isSelfQuery,
1951
+ suinsName
1854
1952
  };
1855
1953
  const holdingsList = visibleHoldings.map((h) => `${h.symbol}: ${h.balance < 1 ? h.balance.toFixed(6) : h.balance.toFixed(2)} ($${h.usdValue.toFixed(2)})`).join(", ");
1856
- const subjectPrefix = isSelfQuery ? "Balance" : `Balance for ${address.slice(0, 6)}\u2026${address.slice(-4)}`;
1954
+ const subjectLabel = suinsName ?? `${address.slice(0, 6)}\u2026${address.slice(-4)}`;
1955
+ const subjectPrefix = isSelfQuery ? "Balance" : `Balance for ${subjectLabel}`;
1857
1956
  const defiSummaryText = (() => {
1858
1957
  if (defi2.source === "degraded") {
1859
1958
  return ' DeFi positions (Bluefin / Suilend / Cetus / etc.): UNAVAILABLE \u2014 DeFi data source is currently unreachable. Do NOT assert "no DeFi positions"; tell the user this slice is temporarily unknown and the total above EXCLUDES DeFi.';
@@ -1877,9 +1976,9 @@ var balanceCheckTool = buildTool({
1877
1976
  displayText: `${subjectPrefix}: $${bal.total.toFixed(2)} total. Wallet holdings (NOT savings): ${holdingsList || "none"}. NAVI savings deposits: $${bal.savings.toFixed(2)}.${defiSummaryText} ${saveableSummary}`
1878
1977
  };
1879
1978
  }
1880
- if (input.address && context.walletAddress && input.address.toLowerCase() !== context.walletAddress.toLowerCase()) {
1979
+ if (targetAddress && context.walletAddress && targetAddress.toLowerCase() !== context.walletAddress.toLowerCase()) {
1881
1980
  throw new Error(
1882
- `Cannot inspect ${input.address.slice(0, 8)}\u2026 without NAVI MCP enabled. Configure NAVI MCP to enable third-party address reads.`
1981
+ `Cannot inspect ${targetAddress.slice(0, 8)}\u2026 without NAVI MCP enabled. Configure NAVI MCP to enable third-party address reads.`
1883
1982
  );
1884
1983
  }
1885
1984
  const agent = requireAgent(context);
@@ -1941,7 +2040,8 @@ var balanceCheckTool = buildTool({
1941
2040
  saveableUsdc: sdkSaveableUsdc,
1942
2041
  saveableUsdsui: sdkSaveableUsdsui,
1943
2042
  address: targetAddress ?? "",
1944
- isSelfQuery: true
2043
+ isSelfQuery: true,
2044
+ suinsName
1945
2045
  },
1946
2046
  displayText: `Balance: $${sdkTotal.toFixed(2)} total. Wallet: $${balance.available.toFixed(2)} available. NAVI savings deposits: $${balance.savings.toFixed(2)}.${sdkDefiSummaryText} ${sdkSaveableUsdsui > 0 ? `Saveable: ${sdkSaveableUsdc.toFixed(2)} USDC + ${sdkSaveableUsdsui.toFixed(sdkSaveableUsdsui < 1 ? 4 : 2)} USDsui (only USDC and USDsui can be saved/borrowed).` : `Saveable USDC (only USDC and USDsui can be saved): ${sdkSaveableUsdc.toFixed(2)} USDC.`}`
1947
2047
  };
@@ -2173,7 +2273,6 @@ async function fetchProtocolStats(manager, opts) {
2173
2273
 
2174
2274
  // src/tools/savings.ts
2175
2275
  var DUST_THRESHOLD_USD = 0.01;
2176
- var SUI_ADDRESS_REGEX2 = /^0x[a-fA-F0-9]{1,64}$/;
2177
2276
  function buildSavingsFromPositions(sp) {
2178
2277
  const positions = [
2179
2278
  ...sp.supplies.filter((s) => s.amountUsd >= DUST_THRESHOLD_USD).map((s) => ({
@@ -2215,11 +2314,12 @@ function buildSavingsFromPositions(sp) {
2215
2314
  }
2216
2315
  };
2217
2316
  }
2218
- function formatSavingsDisplay(result, isSelfQuery = true, address) {
2317
+ function formatSavingsDisplay(result, isSelfQuery = true, address, suinsName) {
2219
2318
  const { positions, earnings, fundStatus } = result;
2220
2319
  const supplies = positions.filter((p) => p.type === "supply");
2221
2320
  const borrows = positions.filter((p) => p.type === "borrow");
2222
- const subjectPrefix = isSelfQuery || !address ? "" : `${address.slice(0, 6)}\u2026${address.slice(-4)} \u2014 `;
2321
+ const subjectLabel = suinsName ?? (address ? `${address.slice(0, 6)}\u2026${address.slice(-4)}` : null);
2322
+ const subjectPrefix = isSelfQuery || !subjectLabel ? "" : `${subjectLabel} \u2014 `;
2223
2323
  const lines = [];
2224
2324
  if (supplies.length > 0) {
2225
2325
  lines.push(`${subjectPrefix}Savings: $${fundStatus.supplied.toFixed(2)} at ${(earnings.currentApy * 100).toFixed(2)}% blended APY`);
@@ -2239,17 +2339,16 @@ function formatSavingsDisplay(result, isSelfQuery = true, address) {
2239
2339
  }
2240
2340
  var savingsInfoTool = buildTool({
2241
2341
  name: "savings_info",
2242
- description: "Get detailed savings positions and earnings for the signed-in user OR any public Sui address: current deposits by protocol, APY, total yield earned, daily earning rate, and projected monthly returns. Pass `address` to inspect a contact / watched / public wallet; defaults to the signed-in user when omitted.",
2342
+ description: 'Get detailed savings positions and earnings for the signed-in user OR any public Sui address or SuiNS name: current deposits by protocol, APY, total yield earned, daily earning rate, and projected monthly returns. Pass `address` as a 0x address OR a SuiNS name (e.g. "alex.sui") to inspect a contact / watched / public wallet; defaults to the signed-in user when omitted.',
2243
2343
  inputSchema: z.object({
2244
- address: z.string().regex(SUI_ADDRESS_REGEX2).optional().describe("Sui address to inspect (defaults to the signed-in wallet)")
2344
+ address: z.string().optional().describe("Sui address (0x\u2026) or SuiNS name (alex.sui). Defaults to the signed-in wallet when omitted.")
2245
2345
  }),
2246
2346
  jsonSchema: {
2247
2347
  type: "object",
2248
2348
  properties: {
2249
2349
  address: {
2250
2350
  type: "string",
2251
- pattern: "^0x[a-fA-F0-9]{1,64}$",
2252
- description: "Sui address to inspect (defaults to the signed-in wallet)"
2351
+ description: "Sui address (0x\u2026) or SuiNS name (e.g. alex.sui). The engine resolves the name to an on-chain address before querying. Omit to default to the signed-in wallet."
2253
2352
  }
2254
2353
  },
2255
2354
  required: []
@@ -2259,23 +2358,34 @@ var savingsInfoTool = buildTool({
2259
2358
  // Each call reflects a fresh on-chain snapshot — never dedupe.
2260
2359
  cacheable: false,
2261
2360
  async call(input, context) {
2262
- const targetAddress = input.address ?? context.walletAddress;
2361
+ let suinsName = null;
2362
+ let targetAddress;
2363
+ if (input.address) {
2364
+ const normalized = await normalizeAddressInput(input.address, {
2365
+ suiRpcUrl: context.suiRpcUrl,
2366
+ signal: context.signal
2367
+ });
2368
+ targetAddress = normalized.address;
2369
+ suinsName = normalized.suinsName;
2370
+ } else {
2371
+ targetAddress = context.walletAddress;
2372
+ }
2263
2373
  const isSelfQuery = !!context.walletAddress && !!targetAddress && targetAddress.toLowerCase() === context.walletAddress.toLowerCase();
2264
2374
  if (context.positionFetcher && targetAddress) {
2265
2375
  const sp = await context.positionFetcher(targetAddress);
2266
2376
  const result2 = buildSavingsFromPositions(sp);
2267
- const stamped2 = { ...result2, address: targetAddress, isSelfQuery };
2268
- return { data: stamped2, displayText: formatSavingsDisplay(result2, isSelfQuery, targetAddress) };
2377
+ const stamped2 = { ...result2, address: targetAddress, isSelfQuery, suinsName };
2378
+ return { data: stamped2, displayText: formatSavingsDisplay(result2, isSelfQuery, targetAddress, suinsName) };
2269
2379
  }
2270
2380
  if (hasNaviMcpGlobal(context) && targetAddress) {
2271
2381
  const savings = await fetchSavings(getMcpManager(context), targetAddress);
2272
2382
  savings.positions = savings.positions.filter((p) => p.valueUsd >= DUST_THRESHOLD_USD);
2273
- const stamped2 = { ...savings, address: targetAddress, isSelfQuery };
2274
- return { data: stamped2, displayText: formatSavingsDisplay(savings, isSelfQuery, targetAddress) };
2383
+ const stamped2 = { ...savings, address: targetAddress, isSelfQuery, suinsName };
2384
+ return { data: stamped2, displayText: formatSavingsDisplay(savings, isSelfQuery, targetAddress, suinsName) };
2275
2385
  }
2276
- if (input.address && context.walletAddress && input.address.toLowerCase() !== context.walletAddress.toLowerCase()) {
2386
+ if (targetAddress && context.walletAddress && targetAddress.toLowerCase() !== context.walletAddress.toLowerCase()) {
2277
2387
  throw new Error(
2278
- `Cannot inspect ${input.address.slice(0, 8)}\u2026 without NAVI MCP or a positionFetcher. Configure NAVI MCP to enable third-party address reads.`
2388
+ `Cannot inspect ${targetAddress.slice(0, 8)}\u2026 without NAVI MCP or a positionFetcher. Configure NAVI MCP to enable third-party address reads.`
2279
2389
  );
2280
2390
  }
2281
2391
  const agent = requireAgent(context);
@@ -2309,11 +2419,10 @@ var savingsInfoTool = buildTool({
2309
2419
  projectedMonthly: fundStatus.projectedMonthly
2310
2420
  }
2311
2421
  };
2312
- const stamped = { ...result, address: targetAddress ?? "", isSelfQuery: true };
2313
- return { data: stamped, displayText: formatSavingsDisplay(result, true, void 0) };
2422
+ const stamped = { ...result, address: targetAddress ?? "", isSelfQuery: true, suinsName };
2423
+ return { data: stamped, displayText: formatSavingsDisplay(result, true, void 0, suinsName) };
2314
2424
  }
2315
2425
  });
2316
- var SUI_ADDRESS_REGEX3 = /^0x[a-fA-F0-9]{1,64}$/;
2317
2426
  var DEBT_DUST_USD = 0.01;
2318
2427
  function hfStatus(hf, borrowed) {
2319
2428
  if (borrowed <= DEBT_DUST_USD) return "healthy";
@@ -2327,8 +2436,9 @@ function serializeHf(hf, borrowed) {
2327
2436
  if (hf == null || !Number.isFinite(hf)) return null;
2328
2437
  return hf;
2329
2438
  }
2330
- function displayHfText(hf, borrowed, status, isSelfQuery = true, address) {
2331
- const subject = isSelfQuery || !address ? "Health Factor" : `Health Factor for ${address.slice(0, 6)}\u2026${address.slice(-4)}`;
2439
+ function displayHfText(hf, borrowed, status, isSelfQuery = true, address, suinsName) {
2440
+ const subjectLabel = suinsName ?? (address ? `${address.slice(0, 6)}\u2026${address.slice(-4)}` : null);
2441
+ const subject = isSelfQuery || !subjectLabel ? "Health Factor" : `Health Factor for ${subjectLabel}`;
2332
2442
  if (hf == null) {
2333
2443
  return `${subject}: \u221E (${status} \u2014 no debt)`;
2334
2444
  }
@@ -2336,17 +2446,16 @@ function displayHfText(hf, borrowed, status, isSelfQuery = true, address) {
2336
2446
  }
2337
2447
  var healthCheckTool = buildTool({
2338
2448
  name: "health_check",
2339
- description: 'Check the lending health factor for the signed-in user OR any public Sui address: current HF ratio, total supplied collateral, total borrowed, max additional borrow capacity, and liquidation threshold. HF < 1.5 is risky, < 1.2 is critical. When the address has no debt the tool returns healthFactor=null (semantically infinity) \u2014 render that as "Healthy" / \u221E, never as 0 or "Critical". Pass `address` to inspect a contact / watched / public wallet; defaults to the signed-in user when omitted.',
2449
+ description: 'Check the lending health factor for the signed-in user OR any public Sui address or SuiNS name: current HF ratio, total supplied collateral, total borrowed, max additional borrow capacity, and liquidation threshold. HF < 1.5 is risky, < 1.2 is critical. When the address has no debt the tool returns healthFactor=null (semantically infinity) \u2014 render that as "Healthy" / \u221E, never as 0 or "Critical". Pass `address` as a 0x address OR a SuiNS name (e.g. "alex.sui") to inspect a contact / watched / public wallet; defaults to the signed-in user when omitted.',
2340
2450
  inputSchema: z.object({
2341
- address: z.string().regex(SUI_ADDRESS_REGEX3).optional().describe("Sui address to inspect (defaults to the signed-in wallet)")
2451
+ address: z.string().optional().describe("Sui address (0x\u2026) or SuiNS name (alex.sui). Defaults to the signed-in wallet when omitted.")
2342
2452
  }),
2343
2453
  jsonSchema: {
2344
2454
  type: "object",
2345
2455
  properties: {
2346
2456
  address: {
2347
2457
  type: "string",
2348
- pattern: "^0x[a-fA-F0-9]{1,64}$",
2349
- description: "Sui address to inspect (defaults to the signed-in wallet)"
2458
+ description: "Sui address (0x\u2026) or SuiNS name (e.g. alex.sui). The engine resolves the name to an on-chain address before querying. Omit to default to the signed-in wallet."
2350
2459
  }
2351
2460
  },
2352
2461
  required: []
@@ -2356,7 +2465,18 @@ var healthCheckTool = buildTool({
2356
2465
  // movement and even passively as oracle prices update. Never dedupe.
2357
2466
  cacheable: false,
2358
2467
  async call(input, context) {
2359
- const targetAddress = input.address ?? context.walletAddress;
2468
+ let suinsName = null;
2469
+ let targetAddress;
2470
+ if (input.address) {
2471
+ const normalized = await normalizeAddressInput(input.address, {
2472
+ suiRpcUrl: context.suiRpcUrl,
2473
+ signal: context.signal
2474
+ });
2475
+ targetAddress = normalized.address;
2476
+ suinsName = normalized.suinsName;
2477
+ } else {
2478
+ targetAddress = context.walletAddress;
2479
+ }
2360
2480
  const isSelfQuery = !!context.walletAddress && !!targetAddress && targetAddress.toLowerCase() === context.walletAddress.toLowerCase();
2361
2481
  if (context.positionFetcher && targetAddress) {
2362
2482
  const sp = await context.positionFetcher(targetAddress);
@@ -2373,9 +2493,10 @@ var healthCheckTool = buildTool({
2373
2493
  liquidationThreshold: 0,
2374
2494
  status: status2,
2375
2495
  address: targetAddress,
2376
- isSelfQuery
2496
+ isSelfQuery,
2497
+ suinsName
2377
2498
  },
2378
- displayText: displayHfText(transportHf2, borrowed2, status2, isSelfQuery, targetAddress)
2499
+ displayText: displayHfText(transportHf2, borrowed2, status2, isSelfQuery, targetAddress, suinsName)
2379
2500
  };
2380
2501
  }
2381
2502
  if (hasNaviMcpGlobal(context) && targetAddress) {
@@ -2384,13 +2505,13 @@ var healthCheckTool = buildTool({
2384
2505
  const status2 = hfStatus(hf2.healthFactor, borrowed2);
2385
2506
  const transportHf2 = serializeHf(hf2.healthFactor, borrowed2);
2386
2507
  return {
2387
- data: { ...hf2, healthFactor: transportHf2, status: status2, address: targetAddress, isSelfQuery },
2388
- displayText: displayHfText(transportHf2, borrowed2, status2, isSelfQuery, targetAddress)
2508
+ data: { ...hf2, healthFactor: transportHf2, status: status2, address: targetAddress, isSelfQuery, suinsName },
2509
+ displayText: displayHfText(transportHf2, borrowed2, status2, isSelfQuery, targetAddress, suinsName)
2389
2510
  };
2390
2511
  }
2391
- if (input.address && context.walletAddress && input.address.toLowerCase() !== context.walletAddress.toLowerCase()) {
2512
+ if (targetAddress && context.walletAddress && targetAddress.toLowerCase() !== context.walletAddress.toLowerCase()) {
2392
2513
  throw new Error(
2393
- `Cannot inspect ${input.address.slice(0, 8)}\u2026 without NAVI MCP or a positionFetcher. Configure NAVI MCP to enable third-party address reads.`
2514
+ `Cannot inspect ${targetAddress.slice(0, 8)}\u2026 without NAVI MCP or a positionFetcher. Configure NAVI MCP to enable third-party address reads.`
2394
2515
  );
2395
2516
  }
2396
2517
  const agent = requireAgent(context);
@@ -2407,9 +2528,10 @@ var healthCheckTool = buildTool({
2407
2528
  liquidationThreshold: hf.liquidationThreshold,
2408
2529
  status,
2409
2530
  address: targetAddress ?? "",
2410
- isSelfQuery: true
2531
+ isSelfQuery: true,
2532
+ suinsName
2411
2533
  },
2412
- displayText: displayHfText(transportHf, borrowed, status, true, void 0)
2534
+ displayText: displayHfText(transportHf, borrowed, status, true, void 0, suinsName)
2413
2535
  };
2414
2536
  }
2415
2537
  });
@@ -2633,14 +2755,13 @@ async function queryHistoryByDate(rpcUrl, address, targetDate, limit) {
2633
2755
  }
2634
2756
  var HISTORY_ACTIONS = ["send", "lending", "swap", "transaction"];
2635
2757
  var DEFAULT_LOOKBACK_DAYS = 30;
2636
- var SUI_ADDRESS_REGEX4 = /^0x[0-9a-fA-F]{64}$/;
2637
2758
  var transactionHistoryTool = buildTool({
2638
2759
  name: "transaction_history",
2639
2760
  description: 'Retrieve recent transaction history (last 30 days by default): sends, saves, withdrawals, borrows, repayments, swaps, and rewards claims. Renders a rich transaction card.\n\nBy default, queries the SIGNED-IN USER\'S history. To inspect another wallet (a saved contact, a watched address, any public Sui address), pass `address` \u2014 e.g. user asks "show funkii\'s recent transactions" with funkii at 0x40cd\u20263e62, call with `address: "0x40cd\u20263e62"`. To filter the user\'s own history to a specific counterparty (user asks "show transactions WITH funkii"), pass `counterparty` \u2014 keeps the query rooted in the user\'s wallet but shows only rows where funkii is the recipient or sender.\n\nFilter args: `date` (YYYY-MM-DD), `action` (send/lending/swap), `minUsd` (minimum amount in USD \u2014 use this for "transactions over $X" instead of post-filtering), `assetSymbol` (e.g. "USDC", "SUI"), `direction` ("in" or "out"). The card itself respects all filters \u2014 never re-list the rows in narration.\n\nInternally queries both `FromAddress` and `ToAddress` filters in parallel and dedupes by digest, so pure-receive transactions (someone sends to the queried address with no balance-affecting outbound) are no longer dropped.',
2640
2761
  inputSchema: z.object({
2641
2762
  limit: z.number().int().min(1).max(50).optional(),
2642
- address: z.string().regex(SUI_ADDRESS_REGEX4, "Must be a 0x-prefixed 64-hex Sui address").optional().describe("Sui address to query history FOR. When omitted, defaults to the signed-in user's wallet. Pass this when the user asks about a contact's, watched address's, or any other public wallet's history."),
2643
- counterparty: z.string().regex(SUI_ADDRESS_REGEX4, "Must be a 0x-prefixed 64-hex Sui address").optional().describe('Sui address to filter rows by \u2014 only transactions where the queried address sent to or received from this counterparty are returned. Use for "show transactions with <contact>" queries. Compares against `tx.recipient` (case-insensitive).'),
2763
+ address: z.string().optional().describe(`Sui address (0x\u2026) or SuiNS name (e.g. "alex.sui") to query history FOR. When omitted, defaults to the signed-in user's wallet. Pass this when the user asks about a contact's, watched address's, or any other public wallet's history.`),
2764
+ counterparty: z.string().optional().describe('Sui address (0x\u2026) or SuiNS name (e.g. "alex.sui") to filter rows by \u2014 only transactions where the queried address sent to or received from this counterparty are returned. Use for "show transactions with <contact>" queries. Compares against `tx.recipient` (case-insensitive).'),
2644
2765
  date: z.string().optional().describe("Specific date to search for transactions (YYYY-MM-DD format). Paginates back to find that day."),
2645
2766
  action: z.enum(HISTORY_ACTIONS).optional().describe("Filter by action: send, lending, swap, or transaction."),
2646
2767
  minUsd: z.number().min(0).optional().describe('Minimum transaction amount in USD. Use this for "transactions over $X" \u2014 the amount is converted to USD using the asset price snapshot.'),
@@ -2656,11 +2777,11 @@ var transactionHistoryTool = buildTool({
2656
2777
  },
2657
2778
  address: {
2658
2779
  type: "string",
2659
- description: "Sui address to query history FOR (defaults to the signed-in user when omitted). Use for queries about a contact's, watched address's, or any other wallet's history."
2780
+ description: "Sui address (0x\u2026) or SuiNS name (e.g. alex.sui) to query history FOR (defaults to the signed-in user when omitted). The engine resolves SuiNS names to addresses before querying. Use for queries about a contact's, watched address's, or any other wallet's history."
2660
2781
  },
2661
2782
  counterparty: {
2662
2783
  type: "string",
2663
- description: 'Sui address to filter rows by \u2014 only transactions where the queried address sent to or received from this counterparty are returned. Use for "show transactions with <contact>" queries.'
2784
+ description: 'Sui address (0x\u2026) or SuiNS name (e.g. alex.sui) to filter rows by \u2014 only transactions where the queried address sent to or received from this counterparty are returned. Use for "show transactions with <contact>" queries.'
2664
2785
  },
2665
2786
  date: {
2666
2787
  type: "string",
@@ -2734,8 +2855,20 @@ var transactionHistoryTool = buildTool({
2734
2855
  const assetSymbol = input.assetSymbol?.toLowerCase();
2735
2856
  const direction = input.direction;
2736
2857
  const minUsd = input.minUsd;
2737
- const counterpartyLower = input.counterparty?.toLowerCase();
2738
- const targetAddress = input.address ?? context.walletAddress;
2858
+ const [normalizedAddress, normalizedCounterparty] = await Promise.all([
2859
+ input.address ? normalizeAddressInput(input.address, {
2860
+ suiRpcUrl: context.suiRpcUrl,
2861
+ signal: context.signal
2862
+ }) : Promise.resolve(null),
2863
+ input.counterparty ? normalizeAddressInput(input.counterparty, {
2864
+ suiRpcUrl: context.suiRpcUrl,
2865
+ signal: context.signal
2866
+ }) : Promise.resolve(null)
2867
+ ]);
2868
+ const suinsName = normalizedAddress?.suinsName ?? null;
2869
+ const counterpartySuinsName = normalizedCounterparty?.suinsName ?? null;
2870
+ const counterpartyLower = normalizedCounterparty?.address.toLowerCase();
2871
+ const targetAddress = normalizedAddress?.address ?? context.walletAddress;
2739
2872
  const isSelfQuery = !!targetAddress && !!context.walletAddress && targetAddress.toLowerCase() === context.walletAddress.toLowerCase();
2740
2873
  const prices = context.tokenPrices;
2741
2874
  const priceFor2 = (sym) => {
@@ -2772,9 +2905,13 @@ var transactionHistoryTool = buildTool({
2772
2905
  minUsd: minUsd ?? null,
2773
2906
  assetSymbol: input.assetSymbol ?? null,
2774
2907
  direction: direction ?? null,
2775
- counterparty: input.counterparty ?? null,
2908
+ // Pre-resolved counterparty address (for the LLM/UI). Original
2909
+ // SuiNS name is preserved in `counterpartySuinsName` below.
2910
+ counterparty: normalizedCounterparty?.address ?? null,
2911
+ counterpartySuinsName,
2776
2912
  address: targetAddress ?? null,
2777
- isSelfQuery
2913
+ isSelfQuery,
2914
+ suinsName
2778
2915
  };
2779
2916
  if (context.agent) {
2780
2917
  if (input.address && !isSelfQuery) {
@@ -3761,23 +3898,37 @@ ${summary}`
3761
3898
  }
3762
3899
  });
3763
3900
  var inputSchema3 = z.object({
3764
- address: z.string().optional().describe("Sui address to analyze (defaults to connected wallet)")
3901
+ address: z.string().optional().describe("Sui address (0x\u2026) or SuiNS name (alex.sui) to analyze. Defaults to the signed-in wallet when omitted.")
3765
3902
  });
3766
3903
  var STABLECOINS = /* @__PURE__ */ new Set(["USDC", "USDT", "USDe", "USDsui"]);
3767
3904
  var portfolioAnalysisTool = buildTool({
3768
3905
  name: "portfolio_analysis",
3769
- description: "Analyze portfolio allocation, risk exposure, and yield optimization. Shows asset breakdown, diversification score, health factor assessment, and actionable suggestions.",
3906
+ description: 'Analyze portfolio allocation, risk exposure, and yield optimization for the signed-in user OR any public Sui address or SuiNS name. Shows asset breakdown, diversification score, health factor assessment, and actionable suggestions. Pass `address` as a 0x address OR a SuiNS name (e.g. "alex.sui") to analyze a contact / watched / public wallet.',
3770
3907
  inputSchema: inputSchema3,
3771
3908
  jsonSchema: {
3772
3909
  type: "object",
3773
3910
  properties: {
3774
- address: { type: "string", description: "Sui address to analyze (defaults to connected wallet)" }
3911
+ address: {
3912
+ type: "string",
3913
+ description: "Sui address (0x\u2026) or SuiNS name (e.g. alex.sui). The engine resolves the name to an on-chain address before querying. Omit to default to the signed-in wallet."
3914
+ }
3775
3915
  },
3776
3916
  required: []
3777
3917
  },
3778
3918
  isReadOnly: true,
3779
3919
  async call(input, context) {
3780
- const address = input.address ?? context.walletAddress;
3920
+ let suinsName = null;
3921
+ let address;
3922
+ if (input.address) {
3923
+ const normalized = await normalizeAddressInput(input.address, {
3924
+ suiRpcUrl: context.suiRpcUrl,
3925
+ signal: context.signal
3926
+ });
3927
+ address = normalized.address;
3928
+ suinsName = normalized.suinsName;
3929
+ } else {
3930
+ address = context.walletAddress;
3931
+ }
3781
3932
  if (!address) {
3782
3933
  throw new Error("No wallet address provided. Sign in first.");
3783
3934
  }
@@ -3956,7 +4107,10 @@ var portfolioAnalysisTool = buildTool({
3956
4107
  savingsApy,
3957
4108
  dailyEarning,
3958
4109
  weekChange,
3959
- priceSource: portfolio.source
4110
+ priceSource: portfolio.source,
4111
+ address,
4112
+ isSelfQuery: !!context.walletAddress && address.toLowerCase() === context.walletAddress.toLowerCase(),
4113
+ suinsName
3960
4114
  };
3961
4115
  const defiSegment = defiValue > 0 ? ` | DeFi: $${defiValue.toFixed(2)}${defiSummary.source === "partial" ? " (partial)" : ""}` : "";
3962
4116
  const topLine = `Total: $${totalValue.toFixed(2)} | Wallet: $${walletValue.toFixed(2)} | Savings: $${savingsValue.toFixed(2)}${defiSegment}`;
@@ -4422,15 +4576,34 @@ Always prefer the canvas for visualisation requests. After rendering, offer to e
4422
4576
  async call(input, context) {
4423
4577
  const { template, params } = input;
4424
4578
  const title = CANVAS_TITLES[template];
4579
+ const ADDRESS_AWARE_TEMPLATES = /* @__PURE__ */ new Set([
4580
+ "full_portfolio",
4581
+ "watch_address",
4582
+ "portfolio_timeline",
4583
+ "spending_breakdown",
4584
+ "activity_heatmap",
4585
+ "health_simulator"
4586
+ ]);
4587
+ let suinsName = null;
4588
+ let resolvedParamAddress = null;
4589
+ if (params?.address && ADDRESS_AWARE_TEMPLATES.has(template)) {
4590
+ const normalized = await normalizeAddressInput(params.address, {
4591
+ suiRpcUrl: context.suiRpcUrl,
4592
+ signal: context.signal
4593
+ });
4594
+ resolvedParamAddress = normalized.address;
4595
+ suinsName = normalized.suinsName;
4596
+ }
4425
4597
  const resolveAddressTarget = () => {
4426
- const fromParams = params?.address;
4598
+ const fromParams = resolvedParamAddress;
4427
4599
  const fromContext = context.walletAddress;
4428
4600
  const target = fromParams ?? fromContext ?? null;
4429
4601
  const isSelfRender = !!target && !!fromContext && target.toLowerCase() === fromContext.toLowerCase();
4430
- return { address: target, isSelfRender };
4602
+ return { address: target, isSelfRender, suinsName };
4431
4603
  };
4604
+ const formatAddrLabel = (address, suins) => suins ?? `${address.slice(0, 6)}\u2026${address.slice(-4)}`;
4432
4605
  if (template === "full_portfolio") {
4433
- const { address, isSelfRender } = resolveAddressTarget();
4606
+ const { address, isSelfRender, suinsName: resolvedSuins } = resolveAddressTarget();
4434
4607
  if (!address) {
4435
4608
  return {
4436
4609
  data: {
@@ -4442,7 +4615,8 @@ Always prefer the canvas for visualisation requests. After rendering, offer to e
4442
4615
  displayText: "Full Portfolio requires an address."
4443
4616
  };
4444
4617
  }
4445
- const titleSuffix = isSelfRender ? "" : ` \u2014 ${address.slice(0, 6)}\u2026${address.slice(-4)}`;
4618
+ const addrLabel = formatAddrLabel(address, resolvedSuins);
4619
+ const titleSuffix = isSelfRender ? "" : ` \u2014 ${addrLabel}`;
4446
4620
  const pos = isSelfRender ? context.serverPositions : null;
4447
4621
  const rate = normalizeSavingsRate(pos?.savingsRate);
4448
4622
  const savings = pos?.savings ?? 0;
@@ -4456,40 +4630,42 @@ Always prefer the canvas for visualisation requests. After rendering, offer to e
4456
4630
  available: true,
4457
4631
  address,
4458
4632
  isSelfRender,
4633
+ suinsName: resolvedSuins,
4459
4634
  currentSavings: savings,
4460
4635
  currentDebt: borrows,
4461
4636
  healthFactor: pos?.healthFactor ?? null,
4462
4637
  savingsRate: rate
4463
4638
  }
4464
4639
  },
4465
- displayText: isSelfRender ? `Opened Full Portfolio Overview.` : `Opened Full Portfolio Overview for ${address.slice(0, 6)}\u2026${address.slice(-4)}.`
4640
+ displayText: isSelfRender ? `Opened Full Portfolio Overview.` : `Opened Full Portfolio Overview for ${addrLabel}.`
4466
4641
  };
4467
4642
  }
4468
4643
  if (template === "watch_address") {
4469
- const targetAddress = params?.address ?? "";
4470
- if (!targetAddress || !targetAddress.startsWith("0x")) {
4644
+ const targetAddress = resolvedParamAddress ?? "";
4645
+ if (!targetAddress) {
4471
4646
  return {
4472
4647
  data: {
4473
4648
  __canvas: true,
4474
4649
  template,
4475
4650
  title,
4476
- templateData: { available: false, message: "Please provide a valid Sui address to watch." }
4651
+ templateData: { available: false, message: "Please provide a valid Sui address or SuiNS name to watch." }
4477
4652
  },
4478
- displayText: "No valid address provided. Ask the user for a Sui address."
4653
+ displayText: "No valid address provided. Ask the user for a Sui address or SuiNS name."
4479
4654
  };
4480
4655
  }
4656
+ const addrLabel = formatAddrLabel(targetAddress, suinsName);
4481
4657
  return {
4482
4658
  data: {
4483
4659
  __canvas: true,
4484
4660
  template,
4485
- title: `Watch ${targetAddress.slice(0, 6)}\u2026${targetAddress.slice(-4)}`,
4486
- templateData: { available: true, address: targetAddress }
4661
+ title: `Watch ${addrLabel}`,
4662
+ templateData: { available: true, address: targetAddress, suinsName }
4487
4663
  },
4488
- displayText: `Opened Watch Address canvas for ${targetAddress.slice(0, 6)}\u2026${targetAddress.slice(-4)}.`
4664
+ displayText: `Opened Watch Address canvas for ${addrLabel}.`
4489
4665
  };
4490
4666
  }
4491
4667
  if (template === "portfolio_timeline") {
4492
- const { address, isSelfRender } = resolveAddressTarget();
4668
+ const { address, isSelfRender, suinsName: resolvedSuins } = resolveAddressTarget();
4493
4669
  if (!address) {
4494
4670
  return {
4495
4671
  data: {
@@ -4501,7 +4677,8 @@ Always prefer the canvas for visualisation requests. After rendering, offer to e
4501
4677
  displayText: "Portfolio Timeline requires an address."
4502
4678
  };
4503
4679
  }
4504
- const titleSuffix = isSelfRender ? "" : ` \u2014 ${address.slice(0, 6)}\u2026${address.slice(-4)}`;
4680
+ const addrLabel = formatAddrLabel(address, resolvedSuins);
4681
+ const titleSuffix = isSelfRender ? "" : ` \u2014 ${addrLabel}`;
4505
4682
  return {
4506
4683
  data: {
4507
4684
  __canvas: true,
@@ -4510,14 +4687,15 @@ Always prefer the canvas for visualisation requests. After rendering, offer to e
4510
4687
  templateData: {
4511
4688
  available: true,
4512
4689
  address,
4513
- isSelfRender
4690
+ isSelfRender,
4691
+ suinsName: resolvedSuins
4514
4692
  }
4515
4693
  },
4516
- displayText: isSelfRender ? `Opened Portfolio Timeline. Shows your net worth, savings, and debt over time.` : `Opened Portfolio Timeline for ${address.slice(0, 6)}\u2026${address.slice(-4)}.`
4694
+ displayText: isSelfRender ? `Opened Portfolio Timeline. Shows your net worth, savings, and debt over time.` : `Opened Portfolio Timeline for ${addrLabel}.`
4517
4695
  };
4518
4696
  }
4519
4697
  if (template === "spending_breakdown") {
4520
- const { address, isSelfRender } = resolveAddressTarget();
4698
+ const { address, isSelfRender, suinsName: resolvedSuins } = resolveAddressTarget();
4521
4699
  if (!address) {
4522
4700
  return {
4523
4701
  data: {
@@ -4529,7 +4707,8 @@ Always prefer the canvas for visualisation requests. After rendering, offer to e
4529
4707
  displayText: "Spending Breakdown requires an address."
4530
4708
  };
4531
4709
  }
4532
- const titleSuffix = isSelfRender ? "" : ` \u2014 ${address.slice(0, 6)}\u2026${address.slice(-4)}`;
4710
+ const addrLabel = formatAddrLabel(address, resolvedSuins);
4711
+ const titleSuffix = isSelfRender ? "" : ` \u2014 ${addrLabel}`;
4533
4712
  return {
4534
4713
  data: {
4535
4714
  __canvas: true,
@@ -4538,14 +4717,15 @@ Always prefer the canvas for visualisation requests. After rendering, offer to e
4538
4717
  templateData: {
4539
4718
  available: true,
4540
4719
  address,
4541
- isSelfRender
4720
+ isSelfRender,
4721
+ suinsName: resolvedSuins
4542
4722
  }
4543
4723
  },
4544
- displayText: isSelfRender ? `Opened Spending Breakdown. Shows your service spending by category.` : `Opened Spending Breakdown for ${address.slice(0, 6)}\u2026${address.slice(-4)}.`
4724
+ displayText: isSelfRender ? `Opened Spending Breakdown. Shows your service spending by category.` : `Opened Spending Breakdown for ${addrLabel}.`
4545
4725
  };
4546
4726
  }
4547
4727
  if (template === "activity_heatmap") {
4548
- const { address, isSelfRender } = resolveAddressTarget();
4728
+ const { address, isSelfRender, suinsName: resolvedSuins } = resolveAddressTarget();
4549
4729
  if (!address) {
4550
4730
  return {
4551
4731
  data: {
@@ -4557,7 +4737,8 @@ Always prefer the canvas for visualisation requests. After rendering, offer to e
4557
4737
  displayText: "Activity Heatmap requires an address."
4558
4738
  };
4559
4739
  }
4560
- const titleSuffix = isSelfRender ? "" : ` \u2014 ${address.slice(0, 6)}\u2026${address.slice(-4)}`;
4740
+ const addrLabel = formatAddrLabel(address, resolvedSuins);
4741
+ const titleSuffix = isSelfRender ? "" : ` \u2014 ${addrLabel}`;
4561
4742
  return {
4562
4743
  data: {
4563
4744
  __canvas: true,
@@ -4566,10 +4747,11 @@ Always prefer the canvas for visualisation requests. After rendering, offer to e
4566
4747
  templateData: {
4567
4748
  available: true,
4568
4749
  address,
4569
- isSelfRender
4750
+ isSelfRender,
4751
+ suinsName: resolvedSuins
4570
4752
  }
4571
4753
  },
4572
- displayText: isSelfRender ? `Opened Activity Heatmap for your wallet. Click any day to explore transactions.` : `Opened Activity Heatmap for ${address.slice(0, 6)}\u2026${address.slice(-4)}. Click any day to explore that address's transactions.`
4754
+ displayText: isSelfRender ? `Opened Activity Heatmap for your wallet. Click any day to explore transactions.` : `Opened Activity Heatmap for ${addrLabel}. Click any day to explore that address's transactions.`
4573
4755
  };
4574
4756
  }
4575
4757
  const positions = context.serverPositions;
@@ -4593,13 +4775,13 @@ Always prefer the canvas for visualisation requests. After rendering, offer to e
4593
4775
  };
4594
4776
  }
4595
4777
  if (template === "health_simulator") {
4596
- const { address: targetAddress, isSelfRender } = resolveAddressTarget();
4778
+ const { address: targetAddress, isSelfRender, suinsName: resolvedSuins } = resolveAddressTarget();
4597
4779
  const seedFromPos = isSelfRender;
4598
4780
  const seedSavings = seedFromPos ? totalSavings : 0;
4599
4781
  const seedBorrows = seedFromPos ? totalBorrows : 0;
4600
4782
  const seedHf = seedFromPos ? healthFactor : null;
4601
4783
  const roundedDebt = seedBorrows >= 1 ? Math.round(seedBorrows) : seedBorrows > 0 ? parseFloat(seedBorrows.toFixed(4)) : 0;
4602
- const titleSuffix = !targetAddress || isSelfRender ? "" : ` \u2014 ${targetAddress.slice(0, 6)}\u2026${targetAddress.slice(-4)}`;
4784
+ const titleSuffix = !targetAddress || isSelfRender ? "" : ` \u2014 ${formatAddrLabel(targetAddress, resolvedSuins)}`;
4603
4785
  return {
4604
4786
  data: {
4605
4787
  __canvas: true,
@@ -4609,6 +4791,7 @@ Always prefer the canvas for visualisation requests. After rendering, offer to e
4609
4791
  available: true,
4610
4792
  address: targetAddress ?? "",
4611
4793
  isSelfRender,
4794
+ suinsName: resolvedSuins,
4612
4795
  initialCollateral: seedSavings > 0 ? Math.round(seedSavings) : 1500,
4613
4796
  initialDebt: roundedDebt > 0 ? roundedDebt : seedSavings > 0 ? 0 : 500,
4614
4797
  currentHf: seedHf
@@ -4729,13 +4912,12 @@ var yieldSummaryTool = buildTool({
4729
4912
  }
4730
4913
  }
4731
4914
  });
4732
- var SUI_ADDRESS_REGEX5 = /^0x[a-fA-F0-9]{1,64}$/;
4733
4915
  var activitySummaryTool = buildTool({
4734
4916
  name: "activity_summary",
4735
- description: "Returns a categorised DeFi activity summary for the signed-in user OR any public Sui address: transaction count, breakdown by action type (saves, sends, borrows, repayments, swaps, payments), total moved, net savings change, and yield earned. Use when the user asks about activity, transaction history summary, or what someone has done recently. Pass `address` to inspect a contact / watched / public wallet; defaults to the signed-in user when omitted.",
4917
+ description: 'Returns a categorised DeFi activity summary for the signed-in user OR any public Sui address or SuiNS name: transaction count, breakdown by action type (saves, sends, borrows, repayments, swaps, payments), total moved, net savings change, and yield earned. Use when the user asks about activity, transaction history summary, or what someone has done recently. Pass `address` as a 0x address OR a SuiNS name (e.g. "alex.sui") to inspect a contact / watched / public wallet; defaults to the signed-in user when omitted.',
4736
4918
  inputSchema: z.object({
4737
4919
  period: z.enum(["week", "month", "year", "all"]).optional().describe("Time period. Defaults to current month."),
4738
- address: z.string().regex(SUI_ADDRESS_REGEX5).optional().describe("Sui address to inspect (defaults to the signed-in wallet)")
4920
+ address: z.string().optional().describe("Sui address (0x\u2026) or SuiNS name (alex.sui). Defaults to the signed-in wallet when omitted.")
4739
4921
  }),
4740
4922
  jsonSchema: {
4741
4923
  type: "object",
@@ -4743,8 +4925,7 @@ var activitySummaryTool = buildTool({
4743
4925
  period: { type: "string", enum: ["week", "month", "year", "all"] },
4744
4926
  address: {
4745
4927
  type: "string",
4746
- pattern: "^0x[a-fA-F0-9]{1,64}$",
4747
- description: "Sui address to inspect (defaults to the signed-in wallet)"
4928
+ description: "Sui address (0x\u2026) or SuiNS name (e.g. alex.sui). The engine resolves the name to an on-chain address before querying. Omit to default to the signed-in wallet."
4748
4929
  }
4749
4930
  }
4750
4931
  },
@@ -4752,7 +4933,18 @@ var activitySummaryTool = buildTool({
4752
4933
  async call(input, context) {
4753
4934
  const period = input.period ?? "month";
4754
4935
  const apiUrl = context.env?.AUDRIC_INTERNAL_API_URL;
4755
- const targetAddress = input.address ?? context.walletAddress;
4936
+ let suinsName = null;
4937
+ let targetAddress;
4938
+ if (input.address) {
4939
+ const normalized = await normalizeAddressInput(input.address, {
4940
+ suiRpcUrl: context.suiRpcUrl,
4941
+ signal: context.signal
4942
+ });
4943
+ targetAddress = normalized.address;
4944
+ suinsName = normalized.suinsName;
4945
+ } else {
4946
+ targetAddress = context.walletAddress;
4947
+ }
4756
4948
  const isSelfQuery = !!context.walletAddress && !!targetAddress && targetAddress.toLowerCase() === context.walletAddress.toLowerCase();
4757
4949
  const empty = {
4758
4950
  period,
@@ -4762,7 +4954,8 @@ var activitySummaryTool = buildTool({
4762
4954
  netSavingsUsd: 0,
4763
4955
  yieldEarnedUsd: 0,
4764
4956
  address: targetAddress,
4765
- isSelfQuery
4957
+ isSelfQuery,
4958
+ suinsName
4766
4959
  };
4767
4960
  if (!apiUrl || !targetAddress) {
4768
4961
  return { data: empty, displayText: "Activity summary not available." };
@@ -4777,11 +4970,12 @@ var activitySummaryTool = buildTool({
4777
4970
  return { data: empty, displayText: `Could not fetch activity data (HTTP ${res.status}).` };
4778
4971
  }
4779
4972
  const raw = await res.json();
4780
- const data = { ...raw, address: targetAddress, isSelfQuery };
4973
+ const data = { ...raw, address: targetAddress, isSelfQuery, suinsName };
4781
4974
  const sorted = [...data.byAction ?? []].sort((a, b) => b.count - a.count);
4782
4975
  const top = sorted.slice(0, 3).map((a) => `${a.action} (${a.count})`).join(", ");
4783
4976
  const periodLabel = data.period === "all" ? "all time" : `this ${data.period}`;
4784
- const subjectPrefix = isSelfQuery ? "" : `${targetAddress.slice(0, 6)}\u2026${targetAddress.slice(-4)} \u2014 `;
4977
+ const subjectLabel = suinsName ?? `${targetAddress.slice(0, 6)}\u2026${targetAddress.slice(-4)}`;
4978
+ const subjectPrefix = isSelfQuery ? "" : `${subjectLabel} \u2014 `;
4785
4979
  return {
4786
4980
  data,
4787
4981
  displayText: data.totalTransactions > 0 ? `${subjectPrefix}${data.totalTransactions} transactions ${periodLabel}. Top: ${top}. Total moved: $${data.totalMovedUsd.toFixed(2)}. Net savings: $${data.netSavingsUsd.toFixed(2)}.` : `${subjectPrefix}No activity recorded for ${periodLabel}.`
@@ -4791,6 +4985,67 @@ var activitySummaryTool = buildTool({
4791
4985
  }
4792
4986
  }
4793
4987
  });
4988
+ var inputSchema5 = z.object({
4989
+ name: z.string().describe(
4990
+ 'A SuiNS name ending in `.sui` (e.g. "alex.sui", "team.alex.sui"). The engine resolves the name on-chain via Sui JSON-RPC.'
4991
+ )
4992
+ });
4993
+ var resolveSuinsTool = buildTool({
4994
+ name: "resolve_suins",
4995
+ description: 'Resolve a SuiNS name (e.g. "alex.sui", "obehi.sui") to its on-chain Sui address. Use this whenever the user asks for the address of a `.sui` name, asks who owns a name, or wants to verify a SuiNS name is registered. Returns the 0x-prefixed 64-hex address, or `registered: false` when the name isn\'t registered. Never use `web_search` for this \u2014 web_search doesn\'t index SuiNS records, but this tool queries the canonical on-chain registry. NOTE: For lookup queries about money flows ("what\'s alex.sui\'s balance / portfolio / health / transactions"), call the relevant read tool directly with `address: "alex.sui"` \u2014 those tools normalize SuiNS names internally, so an explicit `resolve_suins` round-trip is wasted.',
4996
+ inputSchema: inputSchema5,
4997
+ jsonSchema: {
4998
+ type: "object",
4999
+ properties: {
5000
+ name: {
5001
+ type: "string",
5002
+ description: 'A SuiNS name ending in .sui (e.g. "alex.sui"). The engine resolves it on-chain.'
5003
+ }
5004
+ },
5005
+ required: ["name"]
5006
+ },
5007
+ isReadOnly: true,
5008
+ // Names map to addresses on a per-block basis (registry can change).
5009
+ // Cheap, deterministic for a given block — safe to dedupe within a turn.
5010
+ cacheable: true,
5011
+ preflight: (input) => {
5012
+ const trimmed = input.name?.trim().toLowerCase();
5013
+ if (!trimmed) {
5014
+ return { valid: false, error: "name is required" };
5015
+ }
5016
+ if (!SUINS_NAME_REGEX.test(trimmed)) {
5017
+ return {
5018
+ valid: false,
5019
+ error: `"${input.name}" doesn't look like a SuiNS name. Names must end in .sui and use only lowercase letters, digits, and hyphens (e.g. alex.sui).`
5020
+ };
5021
+ }
5022
+ return { valid: true };
5023
+ },
5024
+ async call(input, context) {
5025
+ const name = input.name.trim().toLowerCase();
5026
+ let address;
5027
+ try {
5028
+ address = await resolveSuinsViaRpc(name, {
5029
+ suiRpcUrl: context.suiRpcUrl,
5030
+ signal: context.signal
5031
+ });
5032
+ } catch (err) {
5033
+ if (err instanceof SuinsRpcError) {
5034
+ throw err;
5035
+ }
5036
+ throw err;
5037
+ }
5038
+ const result = {
5039
+ name,
5040
+ address,
5041
+ registered: address !== null
5042
+ };
5043
+ return {
5044
+ data: result,
5045
+ displayText: address ? `${name} \u2192 \`${address.slice(0, 10)}\u2026${address.slice(-6)}\`` : `${name} is not a registered SuiNS name.`
5046
+ };
5047
+ }
5048
+ });
4794
5049
  var tokenPricesTool = buildTool({
4795
5050
  name: "token_prices",
4796
5051
  description: 'Get current USD prices for Sui tokens, with optional 24h change. Accepts full coin type strings (e.g. "0x2::sui::SUI"). Returns price per token and (when requested) 24h change percentage. Use for "what is X worth?" or "did Y move today?". For balance + portfolio rendering, prefer balance_check / portfolio_analysis instead \u2014 they bundle the same prices into the standard cards.',
@@ -4872,7 +5127,8 @@ var READ_TOOLS = [
4872
5127
  createInvoiceTool,
4873
5128
  spendingAnalyticsTool,
4874
5129
  yieldSummaryTool,
4875
- activitySummaryTool
5130
+ activitySummaryTool,
5131
+ resolveSuinsTool
4876
5132
  ];
4877
5133
  var WRITE_TOOLS = [
4878
5134
  saveDepositTool,
@@ -5274,7 +5530,7 @@ function guardCostWarning(tool, _call, conversationText) {
5274
5530
  message: "This action has a monetary cost. Confirm the user is aware before proceeding."
5275
5531
  };
5276
5532
  }
5277
- var SUI_ADDRESS_REGEX6 = /^0x[a-fA-F0-9]{64}$/;
5533
+ var SUI_ADDRESS_REGEX2 = /^0x[a-fA-F0-9]{64}$/;
5278
5534
  function normalizeAddress(addr) {
5279
5535
  return addr.trim().toLowerCase();
5280
5536
  }
@@ -5339,7 +5595,7 @@ function guardAddressSource(tool, call, userText, contacts, walletAddress) {
5339
5595
  if (!rawTo) {
5340
5596
  return { verdict: "pass", gate: "address_source", tier: "safety" };
5341
5597
  }
5342
- if (!SUI_ADDRESS_REGEX6.test(rawTo)) {
5598
+ if (!SUI_ADDRESS_REGEX2.test(rawTo)) {
5343
5599
  return { verdict: "pass", gate: "address_source", tier: "safety" };
5344
5600
  }
5345
5601
  const normalizedTo = normalizeAddress(rawTo);
@@ -8268,6 +8524,6 @@ function sanitizeAnthropicMessages(messages) {
8268
8524
  return merged;
8269
8525
  }
8270
8526
 
8271
- export { AnthropicProvider, BalanceTracker, CANVAS_TEMPLATES, ContextBudget, CostTracker, DEFAULT_GUARD_CONFIG, DEFAULT_LEASE_SEC, DEFAULT_PERMISSION_CONFIG, DEFAULT_POLL_BUDGET_MS, DEFAULT_POLL_INTERVAL_MS, DEFAULT_SYSTEM_PROMPT, EarlyToolDispatcher, InMemoryDefiCacheStore, InMemoryFetchLock, InMemoryNaviCacheStore, InMemoryWalletCacheStore, McpClientManager, McpResponseCache, MemorySessionStore, NAVI_ADDR_TTL_SEC, NAVI_MCP_CONFIG, NAVI_MCP_URL, NAVI_RATES_TTL_SEC, NAVI_SERVER_NAME, NaviTools, PERMISSION_PRESETS, QueryEngine, READ_TOOLS, RecipeRegistry, RetryTracker, TOOL_FLAGS, TOOL_MODIFIABLE_FIELDS, TxMutex, WRITE_TOOLS, _resetNaviCircuitBreaker, activitySummaryTool, adaptAllMcpTools, adaptAllServerTools, adaptMcpTool, applyToolFlags, awaitOrFetch, balanceCheckTool, borrowTool, budgetToolResult, buildCachedSystemPrompt, buildMcpTools, buildProactivenessInstructions, buildProfileContext, buildSelfEvaluationInstruction, buildStateContext, buildTool, claimRewardsTool, classifyEffort, clearPortfolioCache, clearPortfolioCacheFor, clearPriceMapCache, compactMessages, createGuardRunnerState, engineToSSE, estimateTokens, explainTxTool, extractConversationText, extractMcpText, fetchAddressDefiPortfolio, fetchAddressPortfolio, fetchAudricHistory, fetchAudricPortfolio, fetchAvailableRewards, fetchBalance, fetchHealthFactor, fetchPositions, fetchProtocolStats, fetchRates, fetchSavings, fetchTokenPrices, fetchWalletCoins, findTool, getAudricApiBase, getDefaultTools, getDefiCacheStore, getFetchLock, getMcpManager, getModifiableFields, getNaviCacheStore, getTelemetrySink, getToolFlags, getWalletAddress, getWalletCacheStore, guardArtifactPreview, guardStaleData, hasNaviMcp, healthCheckTool, loadRecipes, microcompact, mppServicesTool, naviKey, parseMcpJson, parseRecipe, parseSSE, payApiTool, portfolioAnalysisTool, protocolDeepDiveTool, ratesInfoTool, registerEngineTools, renderCanvasTool, repayDebtTool, requireAgent, resetDefiCacheStore, resetFetchLock, resetNaviCacheStore, resetTelemetrySink, resetWalletCacheStore, resolvePermissionTier, resolveUsdValue, runGuards, runTools, saveContactTool, saveDepositTool, savingsInfoTool, sendTransferTool, serializeSSE, setDefiCacheStore, setFetchLock, setNaviCacheStore, setTelemetrySink, setWalletCacheStore, spendingAnalyticsTool, swapExecuteTool, swapQuoteTool, tokenPricesTool, toolNameToOperation, toolsToDefinitions, transactionHistoryTool, transformBalance, transformHealthFactor, transformPositions, transformRates, transformRewards, transformSavings, updateGuardStateAfterToolResult, validateHistory, voloStakeTool, voloStatsTool, voloUnstakeTool, webSearchTool, withdrawTool, yieldSummaryTool };
8527
+ export { AnthropicProvider, BalanceTracker, CANVAS_TEMPLATES, ContextBudget, CostTracker, DEFAULT_GUARD_CONFIG, DEFAULT_LEASE_SEC, DEFAULT_PERMISSION_CONFIG, DEFAULT_POLL_BUDGET_MS, DEFAULT_POLL_INTERVAL_MS, DEFAULT_SYSTEM_PROMPT, EarlyToolDispatcher, InMemoryDefiCacheStore, InMemoryFetchLock, InMemoryNaviCacheStore, InMemoryWalletCacheStore, InvalidAddressError, McpClientManager, McpResponseCache, MemorySessionStore, NAVI_ADDR_TTL_SEC, NAVI_MCP_CONFIG, NAVI_MCP_URL, NAVI_RATES_TTL_SEC, NAVI_SERVER_NAME, NaviTools, PERMISSION_PRESETS, QueryEngine, READ_TOOLS, RecipeRegistry, RetryTracker, SUINS_NAME_REGEX, SUI_ADDRESS_REGEX, SUI_ADDRESS_STRICT_REGEX, SuinsNotRegisteredError, SuinsRpcError, TOOL_FLAGS, TOOL_MODIFIABLE_FIELDS, TxMutex, WRITE_TOOLS, _resetNaviCircuitBreaker, activitySummaryTool, adaptAllMcpTools, adaptAllServerTools, adaptMcpTool, applyToolFlags, awaitOrFetch, balanceCheckTool, borrowTool, budgetToolResult, buildCachedSystemPrompt, buildMcpTools, buildProactivenessInstructions, buildProfileContext, buildSelfEvaluationInstruction, buildStateContext, buildTool, claimRewardsTool, classifyEffort, clearPortfolioCache, clearPortfolioCacheFor, clearPriceMapCache, compactMessages, createGuardRunnerState, engineToSSE, estimateTokens, explainTxTool, extractConversationText, extractMcpText, fetchAddressDefiPortfolio, fetchAddressPortfolio, fetchAudricHistory, fetchAudricPortfolio, fetchAvailableRewards, fetchBalance, fetchHealthFactor, fetchPositions, fetchProtocolStats, fetchRates, fetchSavings, fetchTokenPrices, fetchWalletCoins, findTool, getAudricApiBase, getDefaultTools, getDefiCacheStore, getFetchLock, getMcpManager, getModifiableFields, getNaviCacheStore, getTelemetrySink, getToolFlags, getWalletAddress, getWalletCacheStore, guardArtifactPreview, guardStaleData, hasNaviMcp, healthCheckTool, loadRecipes, looksLikeSuiNs, microcompact, mppServicesTool, naviKey, normalizeAddressInput, parseMcpJson, parseRecipe, parseSSE, payApiTool, portfolioAnalysisTool, protocolDeepDiveTool, ratesInfoTool, registerEngineTools, renderCanvasTool, repayDebtTool, requireAgent, resetDefiCacheStore, resetFetchLock, resetNaviCacheStore, resetTelemetrySink, resetWalletCacheStore, resolvePermissionTier, resolveSuinsTool, resolveSuinsViaRpc, resolveUsdValue, runGuards, runTools, saveContactTool, saveDepositTool, savingsInfoTool, sendTransferTool, serializeSSE, setDefiCacheStore, setFetchLock, setNaviCacheStore, setTelemetrySink, setWalletCacheStore, spendingAnalyticsTool, swapExecuteTool, swapQuoteTool, tokenPricesTool, toolNameToOperation, toolsToDefinitions, transactionHistoryTool, transformBalance, transformHealthFactor, transformPositions, transformRates, transformRewards, transformSavings, updateGuardStateAfterToolResult, validateHistory, voloStakeTool, voloStatsTool, voloUnstakeTool, webSearchTool, withdrawTool, yieldSummaryTool };
8272
8528
  //# sourceMappingURL=index.js.map
8273
8529
  //# sourceMappingURL=index.js.map