@t2000/engine 0.46.11 → 0.46.12

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.d.ts CHANGED
@@ -228,6 +228,14 @@ interface GuardConfig {
228
228
  * upstream guard (e.g. an off-process verifier).
229
229
  */
230
230
  addressSource?: boolean;
231
+ /**
232
+ * Companion to `addressSource`: blocks send_transfer that defaults to
233
+ * USDC when the user's recent messages clearly named a non-USDC token
234
+ * (SUI, USDT, WAL, etc.). Without this, the LLM would call
235
+ * `send_transfer({ amount, to })` for a "send my SUI" request and the
236
+ * tool would silently ship USDC. Default on.
237
+ */
238
+ assetIntent?: boolean;
231
239
  }
232
240
  declare const DEFAULT_GUARD_CONFIG: GuardConfig;
233
241
  declare class BalanceTracker {
@@ -1862,11 +1870,13 @@ declare const withdrawTool: Tool<{
1862
1870
  declare const sendTransferTool: Tool<{
1863
1871
  to: string;
1864
1872
  amount: number;
1873
+ asset?: string | undefined;
1865
1874
  memo?: string | undefined;
1866
1875
  }, {
1867
1876
  success: boolean;
1868
1877
  tx: string;
1869
1878
  amount: number;
1879
+ asset: "USDC" | "USDT" | "SUI" | "USDe" | "USDsui" | "WAL" | "ETH" | "NAVX" | "GOLD";
1870
1880
  to: string;
1871
1881
  contactName: string | undefined;
1872
1882
  gasCost: number;
package/dist/index.js CHANGED
@@ -1,5 +1,5 @@
1
1
  import { z } from 'zod';
2
- import { resolveSymbol, getDecimalsForCoinType, assertAllowedAsset, getSwapQuote, extractTransferDetails, classifyTransaction } from '@t2000/sdk';
2
+ import { ALL_NAVI_ASSETS, resolveSymbol, getDecimalsForCoinType, assertAllowedAsset, SUPPORTED_ASSETS, getSwapQuote, extractTransferDetails, classifyTransaction } from '@t2000/sdk';
3
3
  import { readdirSync, readFileSync } from 'fs';
4
4
  import { join } from 'path';
5
5
  import yaml from 'js-yaml';
@@ -1447,12 +1447,14 @@ var withdrawTool = buildTool({
1447
1447
  };
1448
1448
  }
1449
1449
  });
1450
+ var ASSET_LIST = ALL_NAVI_ASSETS.map((a) => String(a)).join(", ");
1450
1451
  var sendTransferTool = buildTool({
1451
1452
  name: "send_transfer",
1452
- description: "Send USDC to another Sui address or contact name. Validates the address, checks balance, and executes the on-chain transfer. Returns tx hash, gas cost, and updated balance.",
1453
+ description: `Send ANY supported token (${ASSET_LIST}) to another Sui address or contact name. Validates the address, checks balance, and executes the on-chain transfer. MUST set the \`asset\` field to the token symbol you want to send (case-insensitive). If \`asset\` is omitted, USDC is assumed \u2014 only do this when the user explicitly asks for USDC. When the user asks to send a token by name (SUI, USDT, etc.) or to send the proceeds of a just-completed swap, you MUST pass \`asset\` matching that token. Returns tx hash, gas cost, and updated balance.`,
1453
1454
  inputSchema: z.object({
1454
1455
  to: z.string().min(1),
1455
1456
  amount: z.number().positive(),
1457
+ asset: z.string().optional(),
1456
1458
  memo: z.string().optional()
1457
1459
  }),
1458
1460
  jsonSchema: {
@@ -1464,7 +1466,11 @@ var sendTransferTool = buildTool({
1464
1466
  },
1465
1467
  amount: {
1466
1468
  type: "number",
1467
- description: "Amount in USD to send"
1469
+ description: "Amount of the asset to send (denominated in the asset\u2019s own units, NOT USD). For USDC this is the USDC count; for SUI this is the SUI count."
1470
+ },
1471
+ asset: {
1472
+ type: "string",
1473
+ description: `Token symbol to send. One of: ${ASSET_LIST}. Defaults to USDC if omitted. REQUIRED whenever the user names a non-USDC token or you are forwarding the proceeds of a swap.`
1468
1474
  },
1469
1475
  memo: {
1470
1476
  type: "string",
@@ -1487,16 +1493,27 @@ var sendTransferTool = buildTool({
1487
1493
  if (input.amount <= 0) {
1488
1494
  return { valid: false, error: "Amount must be positive." };
1489
1495
  }
1496
+ if (input.asset !== void 0) {
1497
+ const normalized = String(input.asset).toUpperCase();
1498
+ if (!(normalized in SUPPORTED_ASSETS)) {
1499
+ return {
1500
+ valid: false,
1501
+ error: `Unsupported asset "${input.asset}". send_transfer accepts: ${ASSET_LIST}.`
1502
+ };
1503
+ }
1504
+ }
1490
1505
  return { valid: true };
1491
1506
  },
1492
1507
  async call(input, context) {
1493
1508
  const agent = requireAgent(context);
1494
- const result = await agent.send({ to: input.to, amount: input.amount });
1509
+ const asset = input.asset ? String(input.asset).toUpperCase() : "USDC";
1510
+ const result = await agent.send({ to: input.to, amount: input.amount, asset });
1495
1511
  return {
1496
1512
  data: {
1497
1513
  success: result.success,
1498
1514
  tx: result.tx,
1499
1515
  amount: result.amount,
1516
+ asset,
1500
1517
  to: result.to,
1501
1518
  contactName: result.contactName,
1502
1519
  gasCost: result.gasCost,
@@ -1504,7 +1521,7 @@ var sendTransferTool = buildTool({
1504
1521
  balance: result.balance,
1505
1522
  memo: input.memo ?? null
1506
1523
  },
1507
- displayText: `Sent $${result.amount.toFixed(2)} to ${result.contactName ?? result.to.slice(0, 10)}\u2026 (tx: ${result.tx.slice(0, 8)}\u2026)`
1524
+ displayText: `Sent ${result.amount} ${asset} to ${result.contactName ?? `${result.to.slice(0, 10)}\u2026`} (tx: ${result.tx.slice(0, 8)}\u2026)`
1508
1525
  };
1509
1526
  }
1510
1527
  });
@@ -3610,7 +3627,8 @@ var DEFAULT_GUARD_CONFIG = {
3610
3627
  costWarning: true,
3611
3628
  retryProtection: true,
3612
3629
  inputValidation: true,
3613
- addressSource: true
3630
+ addressSource: true,
3631
+ assetIntent: true
3614
3632
  };
3615
3633
  var BalanceTracker = class {
3616
3634
  lastBalanceAt = 0;
@@ -3793,6 +3811,37 @@ var SUI_ADDRESS_REGEX = /^0x[a-fA-F0-9]{64}$/;
3793
3811
  function normalizeAddress(addr) {
3794
3812
  return addr.trim().toLowerCase();
3795
3813
  }
3814
+ var NON_USDC_TOKEN_WORDS = [
3815
+ // Patterns are anchored with \b on both sides. Case-insensitive.
3816
+ { symbol: "SUI", pattern: /\bSUI\b/i },
3817
+ { symbol: "USDT", pattern: /\bUSDT\b/i },
3818
+ { symbol: "USDe", pattern: /\bUSDe\b/i },
3819
+ { symbol: "USDsui", pattern: /\bUSDsui\b/i },
3820
+ { symbol: "WAL", pattern: /\bWAL\b/i },
3821
+ { symbol: "ETH", pattern: /\bETH\b/i },
3822
+ { symbol: "NAVX", pattern: /\bNAVX\b/i },
3823
+ { symbol: "GOLD", pattern: /\bGOLD\b/i }
3824
+ ];
3825
+ function guardAssetIntent(tool, call, userText) {
3826
+ if (tool.name !== "send_transfer") {
3827
+ return { verdict: "pass", gate: "asset_intent", tier: "safety" };
3828
+ }
3829
+ const input = call.input;
3830
+ const assetWasSet = !(input.asset === void 0 || input.asset === null || input.asset === "");
3831
+ if (assetWasSet) {
3832
+ return { verdict: "pass", gate: "asset_intent", tier: "safety" };
3833
+ }
3834
+ const mentioned = NON_USDC_TOKEN_WORDS.find((t) => t.pattern.test(userText));
3835
+ if (!mentioned) {
3836
+ return { verdict: "pass", gate: "asset_intent", tier: "safety" };
3837
+ }
3838
+ return {
3839
+ verdict: "block",
3840
+ gate: "asset_intent",
3841
+ tier: "safety",
3842
+ message: `Asset mismatch: the user's recent messages mention "${mentioned.symbol}" but send_transfer was called without an \`asset\` field (defaults to USDC). If the user asked you to send ${mentioned.symbol}, re-issue send_transfer with \`asset: "${mentioned.symbol}"\`. If the user really meant USDC, set \`asset: "USDC"\` explicitly to confirm intent. Never default to USDC when the user named a different token.`
3843
+ };
3844
+ }
3796
3845
  function guardAddressSource(tool, call, userText, contacts, walletAddress) {
3797
3846
  if (tool.name !== "send_transfer") {
3798
3847
  return { verdict: "pass", gate: "address_source", tier: "safety" };
@@ -3903,6 +3952,9 @@ function runGuards(tool, call, state, config, conversationContext, onGuardFired,
3903
3952
  )
3904
3953
  );
3905
3954
  }
3955
+ if (config.assetIntent !== false) {
3956
+ results.push(guardAssetIntent(tool, call, conversationContext.recentUserText));
3957
+ }
3906
3958
  if (config.irreversibility !== false) {
3907
3959
  results.push(guardIrreversibility(tool, call, conversationContext.fullText));
3908
3960
  }