@t2000/engine 0.47.1 → 0.48.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.d.ts +2 -0
- package/dist/index.js +143 -40
- package/dist/index.js.map +1 -1
- package/package.json +2 -2
package/dist/index.d.ts
CHANGED
|
@@ -1990,8 +1990,10 @@ declare const ratesInfoTool: Tool<{
|
|
|
1990
1990
|
declare const transactionHistoryTool: Tool<{
|
|
1991
1991
|
action?: "send" | "swap" | "transaction" | "lending" | undefined;
|
|
1992
1992
|
date?: string | undefined;
|
|
1993
|
+
address?: string | undefined;
|
|
1993
1994
|
direction?: "out" | "in" | undefined;
|
|
1994
1995
|
limit?: number | undefined;
|
|
1996
|
+
counterparty?: string | undefined;
|
|
1995
1997
|
minUsd?: number | undefined;
|
|
1996
1998
|
assetSymbol?: string | undefined;
|
|
1997
1999
|
}, Record<string, unknown>>;
|
package/dist/index.js
CHANGED
|
@@ -1317,7 +1317,8 @@ function parseRpcTx(tx, address) {
|
|
|
1317
1317
|
gasCost
|
|
1318
1318
|
};
|
|
1319
1319
|
}
|
|
1320
|
-
async function queryHistoryPage(rpcUrl, address, limit, cursor) {
|
|
1320
|
+
async function queryHistoryPage(rpcUrl, address, direction, limit, cursor) {
|
|
1321
|
+
const filter = direction === "from" ? { FromAddress: address } : { ToAddress: address };
|
|
1321
1322
|
const res = await fetch(rpcUrl, {
|
|
1322
1323
|
method: "POST",
|
|
1323
1324
|
headers: { "Content-Type": "application/json" },
|
|
@@ -1326,7 +1327,7 @@ async function queryHistoryPage(rpcUrl, address, limit, cursor) {
|
|
|
1326
1327
|
id: 1,
|
|
1327
1328
|
method: "suix_queryTransactionBlocks",
|
|
1328
1329
|
params: [
|
|
1329
|
-
{ filter
|
|
1330
|
+
{ filter, options: { showEffects: true, showInput: true, showBalanceChanges: true } },
|
|
1330
1331
|
cursor,
|
|
1331
1332
|
limit,
|
|
1332
1333
|
true
|
|
@@ -1343,9 +1344,20 @@ async function queryHistoryPage(rpcUrl, address, limit, cursor) {
|
|
|
1343
1344
|
hasNextPage: json.result?.hasNextPage ?? false
|
|
1344
1345
|
};
|
|
1345
1346
|
}
|
|
1347
|
+
function mergeAndDedupe(a, b) {
|
|
1348
|
+
const byDigest = /* @__PURE__ */ new Map();
|
|
1349
|
+
for (const tx of [...a, ...b]) {
|
|
1350
|
+
if (!byDigest.has(tx.digest)) byDigest.set(tx.digest, tx);
|
|
1351
|
+
}
|
|
1352
|
+
return [...byDigest.values()].sort((x, y) => Number(y.timestampMs ?? 0) - Number(x.timestampMs ?? 0));
|
|
1353
|
+
}
|
|
1346
1354
|
async function queryHistoryRpc(rpcUrl, address, limit) {
|
|
1347
|
-
const
|
|
1348
|
-
|
|
1355
|
+
const [fromPage, toPage] = await Promise.all([
|
|
1356
|
+
queryHistoryPage(rpcUrl, address, "from", limit, null).catch(() => ({ data: [], nextCursor: null, hasNextPage: false })),
|
|
1357
|
+
queryHistoryPage(rpcUrl, address, "to", limit, null).catch(() => ({ data: [], nextCursor: null, hasNextPage: false }))
|
|
1358
|
+
]);
|
|
1359
|
+
const merged = mergeAndDedupe(fromPage.data, toPage.data);
|
|
1360
|
+
return merged.slice(0, limit).map((tx) => parseRpcTx(tx, address));
|
|
1349
1361
|
}
|
|
1350
1362
|
async function queryHistoryByDate(rpcUrl, address, targetDate, limit) {
|
|
1351
1363
|
const target = new Date(targetDate);
|
|
@@ -1353,33 +1365,49 @@ async function queryHistoryByDate(rpcUrl, address, targetDate, limit) {
|
|
|
1353
1365
|
const dayEnd = dayStart + 864e5;
|
|
1354
1366
|
const MAX_PAGES = 20;
|
|
1355
1367
|
const PAGE_SIZE = 50;
|
|
1356
|
-
|
|
1357
|
-
|
|
1358
|
-
|
|
1359
|
-
|
|
1360
|
-
|
|
1361
|
-
|
|
1362
|
-
|
|
1363
|
-
|
|
1364
|
-
|
|
1365
|
-
return results.slice(0, limit);
|
|
1368
|
+
async function paginateDirection(direction) {
|
|
1369
|
+
const collected = [];
|
|
1370
|
+
let cursor = null;
|
|
1371
|
+
for (let page = 0; page < MAX_PAGES; page++) {
|
|
1372
|
+
let res;
|
|
1373
|
+
try {
|
|
1374
|
+
res = await queryHistoryPage(rpcUrl, address, direction, PAGE_SIZE, cursor);
|
|
1375
|
+
} catch {
|
|
1376
|
+
break;
|
|
1366
1377
|
}
|
|
1367
|
-
if (
|
|
1368
|
-
|
|
1378
|
+
if (res.data.length === 0) break;
|
|
1379
|
+
let reachedOld = false;
|
|
1380
|
+
for (const tx of res.data) {
|
|
1381
|
+
const ts = Number(tx.timestampMs ?? 0);
|
|
1382
|
+
if (ts === 0) continue;
|
|
1383
|
+
if (ts < dayStart) {
|
|
1384
|
+
reachedOld = true;
|
|
1385
|
+
break;
|
|
1386
|
+
}
|
|
1387
|
+
if (ts >= dayStart && ts < dayEnd) collected.push(tx);
|
|
1369
1388
|
}
|
|
1389
|
+
if (reachedOld || !res.hasNextPage || !res.nextCursor) break;
|
|
1390
|
+
cursor = res.nextCursor;
|
|
1370
1391
|
}
|
|
1371
|
-
|
|
1372
|
-
cursor = res.nextCursor;
|
|
1392
|
+
return collected;
|
|
1373
1393
|
}
|
|
1374
|
-
|
|
1394
|
+
const [fromTxs, toTxs] = await Promise.all([
|
|
1395
|
+
paginateDirection("from"),
|
|
1396
|
+
paginateDirection("to")
|
|
1397
|
+
]);
|
|
1398
|
+
const merged = mergeAndDedupe(fromTxs, toTxs);
|
|
1399
|
+
return merged.slice(0, limit).map((tx) => parseRpcTx(tx, address));
|
|
1375
1400
|
}
|
|
1376
1401
|
var HISTORY_ACTIONS = ["send", "lending", "swap", "transaction"];
|
|
1377
1402
|
var DEFAULT_LOOKBACK_DAYS = 30;
|
|
1403
|
+
var SUI_ADDRESS_REGEX = /^0x[0-9a-fA-F]{64}$/;
|
|
1378
1404
|
var transactionHistoryTool = buildTool({
|
|
1379
1405
|
name: "transaction_history",
|
|
1380
|
-
description: 'Retrieve recent transaction history (last 30 days by default): sends, saves, withdrawals, borrows, repayments, swaps, and rewards claims. Renders a rich transaction card.
|
|
1406
|
+
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.',
|
|
1381
1407
|
inputSchema: z.object({
|
|
1382
1408
|
limit: z.number().int().min(1).max(50).optional(),
|
|
1409
|
+
address: z.string().regex(SUI_ADDRESS_REGEX, "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."),
|
|
1410
|
+
counterparty: z.string().regex(SUI_ADDRESS_REGEX, "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).'),
|
|
1383
1411
|
date: z.string().optional().describe("Specific date to search for transactions (YYYY-MM-DD format). Paginates back to find that day."),
|
|
1384
1412
|
action: z.enum(HISTORY_ACTIONS).optional().describe("Filter by action: send, lending, swap, or transaction."),
|
|
1385
1413
|
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.'),
|
|
@@ -1393,6 +1421,14 @@ var transactionHistoryTool = buildTool({
|
|
|
1393
1421
|
type: "number",
|
|
1394
1422
|
description: "Maximum number of transactions to return (1-50, default 10)"
|
|
1395
1423
|
},
|
|
1424
|
+
address: {
|
|
1425
|
+
type: "string",
|
|
1426
|
+
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."
|
|
1427
|
+
},
|
|
1428
|
+
counterparty: {
|
|
1429
|
+
type: "string",
|
|
1430
|
+
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.'
|
|
1431
|
+
},
|
|
1396
1432
|
date: {
|
|
1397
1433
|
type: "string",
|
|
1398
1434
|
description: "Specific date to search for transactions (YYYY-MM-DD format). Paginates back to find that day."
|
|
@@ -1465,6 +1501,9 @@ var transactionHistoryTool = buildTool({
|
|
|
1465
1501
|
const assetSymbol = input.assetSymbol?.toLowerCase();
|
|
1466
1502
|
const direction = input.direction;
|
|
1467
1503
|
const minUsd = input.minUsd;
|
|
1504
|
+
const counterpartyLower = input.counterparty?.toLowerCase();
|
|
1505
|
+
const targetAddress = input.address ?? context.walletAddress;
|
|
1506
|
+
const isSelfQuery = !!targetAddress && !!context.walletAddress && targetAddress.toLowerCase() === context.walletAddress.toLowerCase();
|
|
1468
1507
|
const prices = context.tokenPrices;
|
|
1469
1508
|
const priceFor = (sym) => {
|
|
1470
1509
|
if (!sym || !prices) return void 0;
|
|
@@ -1479,6 +1518,9 @@ var transactionHistoryTool = buildTool({
|
|
|
1479
1518
|
if (direction) {
|
|
1480
1519
|
scoped = scoped.filter((r) => r.direction === direction);
|
|
1481
1520
|
}
|
|
1521
|
+
if (counterpartyLower) {
|
|
1522
|
+
scoped = scoped.filter((r) => r.recipient?.toLowerCase() === counterpartyLower);
|
|
1523
|
+
}
|
|
1482
1524
|
if (minUsd != null && minUsd > 0) {
|
|
1483
1525
|
scoped = scoped.filter((r) => {
|
|
1484
1526
|
if (r.amount == null) return false;
|
|
@@ -1496,9 +1538,17 @@ var transactionHistoryTool = buildTool({
|
|
|
1496
1538
|
action: action ?? null,
|
|
1497
1539
|
minUsd: minUsd ?? null,
|
|
1498
1540
|
assetSymbol: input.assetSymbol ?? null,
|
|
1499
|
-
direction: direction ?? null
|
|
1541
|
+
direction: direction ?? null,
|
|
1542
|
+
counterparty: input.counterparty ?? null,
|
|
1543
|
+
address: targetAddress ?? null,
|
|
1544
|
+
isSelfQuery
|
|
1500
1545
|
};
|
|
1501
1546
|
if (context.agent) {
|
|
1547
|
+
if (input.address && !isSelfQuery) {
|
|
1548
|
+
throw new Error(
|
|
1549
|
+
"transaction_history `address` parameter is not supported in CLI/SDK agent mode \u2014 only the signed-in user's history is available. Use the web client for third-party address queries."
|
|
1550
|
+
);
|
|
1551
|
+
}
|
|
1502
1552
|
const agent = requireAgent(context);
|
|
1503
1553
|
const records2 = await agent.history({ limit: input.date ? limit : Math.max(limit * 4, 50) });
|
|
1504
1554
|
const filtered2 = finalize(records2);
|
|
@@ -1507,13 +1557,13 @@ var transactionHistoryTool = buildTool({
|
|
|
1507
1557
|
displayText: `${filtered2.length} recent transaction(s)`
|
|
1508
1558
|
};
|
|
1509
1559
|
}
|
|
1510
|
-
if (!
|
|
1560
|
+
if (!targetAddress || !context.suiRpcUrl) {
|
|
1511
1561
|
throw new Error("Transaction history requires a wallet address");
|
|
1512
1562
|
}
|
|
1513
1563
|
if (input.date) {
|
|
1514
1564
|
const records2 = await queryHistoryByDate(
|
|
1515
1565
|
context.suiRpcUrl,
|
|
1516
|
-
|
|
1566
|
+
targetAddress,
|
|
1517
1567
|
input.date,
|
|
1518
1568
|
Math.max(limit * 4, 50)
|
|
1519
1569
|
);
|
|
@@ -1527,7 +1577,7 @@ var transactionHistoryTool = buildTool({
|
|
|
1527
1577
|
const cutoffMs = Date.now() - DEFAULT_LOOKBACK_DAYS * 864e5;
|
|
1528
1578
|
const records = await queryHistoryRpc(
|
|
1529
1579
|
context.suiRpcUrl,
|
|
1530
|
-
|
|
1580
|
+
targetAddress,
|
|
1531
1581
|
Math.max(limit * 4, 50)
|
|
1532
1582
|
);
|
|
1533
1583
|
const recent = records.filter((r) => r.timestamp >= cutoffMs);
|
|
@@ -2976,21 +3026,25 @@ var renderCanvasTool = buildTool({
|
|
|
2976
3026
|
|
|
2977
3027
|
Use when the user asks for a visual chart, simulator, or financial overview. Pick the most relevant template:
|
|
2978
3028
|
|
|
2979
|
-
- activity_heatmap \u2014 on-chain transaction history as a GitHub-style heatmap (WORKS NOW \u2014
|
|
2980
|
-
- portfolio_timeline \u2014 net worth over time, wallet/savings/debt breakdown (WORKS NOW \u2014
|
|
3029
|
+
- activity_heatmap \u2014 on-chain transaction history as a GitHub-style heatmap (WORKS NOW \u2014 accepts \`params.address\` to inspect any public Sui wallet; defaults to the signed-in user)
|
|
3030
|
+
- portfolio_timeline \u2014 net worth over time, wallet/savings/debt breakdown (WORKS NOW \u2014 accepts \`params.address\` for any public wallet; defaults to the signed-in user)
|
|
2981
3031
|
- yield_projector \u2014 compound yield simulator with amount/APY/period sliders (WORKS NOW \u2014 client-side)
|
|
2982
3032
|
- health_simulator \u2014 borrow health factor simulator with collateral/debt sliders (WORKS NOW \u2014 uses current position)
|
|
2983
3033
|
- dca_planner \u2014 savings plan curve for regular monthly deposits (WORKS NOW \u2014 client-side)
|
|
2984
|
-
- spending_breakdown \u2014 spending by service category (WORKS NOW \u2014
|
|
2985
|
-
- watch_address \u2014 portfolio overview for any public Sui address (WORKS NOW \u2014 pass address
|
|
3034
|
+
- spending_breakdown \u2014 spending by service category (WORKS NOW \u2014 accepts \`params.address\` for any public wallet; defaults to the signed-in user)
|
|
3035
|
+
- watch_address \u2014 portfolio overview for any public Sui address (WORKS NOW \u2014 pass \`params.address\`)
|
|
2986
3036
|
- full_portfolio \u2014 4-panel overview: savings, health, activity, spending (WORKS NOW \u2014 aggregates all data)
|
|
2987
3037
|
|
|
3038
|
+
When the user asks to inspect a saved contact or watched address \u2014 e.g. "show funkii's activity heatmap", "what's funkii's portfolio look like", "spending breakdown for 0x40cd\u2026" \u2014 pass that wallet's address as \`params.address\`. The four address-aware templates (activity_heatmap, portfolio_timeline, spending_breakdown, watch_address) will scope their data fetch to that address; the rest target the signed-in user regardless of params.
|
|
3039
|
+
|
|
2988
3040
|
Always prefer the canvas for visualisation requests. After rendering, offer to explain what the user sees.`,
|
|
2989
3041
|
inputSchema: z.object({
|
|
2990
3042
|
template: z.enum(CANVAS_TEMPLATES).describe("Which canvas template to render"),
|
|
2991
3043
|
params: z.object({
|
|
2992
3044
|
period: z.enum(["1m", "3m", "6m", "1y"]).optional().describe("Time period for time-based templates"),
|
|
2993
|
-
address: z.string().optional().describe(
|
|
3045
|
+
address: z.string().optional().describe(
|
|
3046
|
+
"Sui address for the four address-aware templates (activity_heatmap, portfolio_timeline, spending_breakdown, watch_address). Defaults to the signed-in user; pass an explicit address to inspect a contact, watched wallet, or any other public address."
|
|
3047
|
+
)
|
|
2994
3048
|
}).optional()
|
|
2995
3049
|
}),
|
|
2996
3050
|
jsonSchema: {
|
|
@@ -3015,6 +3069,13 @@ Always prefer the canvas for visualisation requests. After rendering, offer to e
|
|
|
3015
3069
|
async call(input, context) {
|
|
3016
3070
|
const { template, params } = input;
|
|
3017
3071
|
const title = CANVAS_TITLES[template];
|
|
3072
|
+
const resolveAddressTarget = () => {
|
|
3073
|
+
const fromParams = params?.address;
|
|
3074
|
+
const fromContext = context.walletAddress;
|
|
3075
|
+
const target = fromParams ?? fromContext ?? null;
|
|
3076
|
+
const isSelfRender = !!target && !!fromContext && target.toLowerCase() === fromContext.toLowerCase();
|
|
3077
|
+
return { address: target, isSelfRender };
|
|
3078
|
+
};
|
|
3018
3079
|
if (template === "full_portfolio") {
|
|
3019
3080
|
const pos = context.serverPositions;
|
|
3020
3081
|
const rate = normalizeSavingsRate(pos?.savingsRate);
|
|
@@ -3061,45 +3122,87 @@ Always prefer the canvas for visualisation requests. After rendering, offer to e
|
|
|
3061
3122
|
};
|
|
3062
3123
|
}
|
|
3063
3124
|
if (template === "portfolio_timeline") {
|
|
3125
|
+
const { address, isSelfRender } = resolveAddressTarget();
|
|
3126
|
+
if (!address) {
|
|
3127
|
+
return {
|
|
3128
|
+
data: {
|
|
3129
|
+
__canvas: true,
|
|
3130
|
+
template,
|
|
3131
|
+
title,
|
|
3132
|
+
templateData: { available: false, message: "Portfolio Timeline needs an address." }
|
|
3133
|
+
},
|
|
3134
|
+
displayText: "Portfolio Timeline requires an address."
|
|
3135
|
+
};
|
|
3136
|
+
}
|
|
3137
|
+
const titleSuffix = isSelfRender ? "" : ` \u2014 ${address.slice(0, 6)}\u2026${address.slice(-4)}`;
|
|
3064
3138
|
return {
|
|
3065
3139
|
data: {
|
|
3066
3140
|
__canvas: true,
|
|
3067
3141
|
template,
|
|
3068
|
-
title
|
|
3142
|
+
title: `${title}${titleSuffix}`,
|
|
3069
3143
|
templateData: {
|
|
3070
3144
|
available: true,
|
|
3071
|
-
address
|
|
3145
|
+
address,
|
|
3146
|
+
isSelfRender
|
|
3072
3147
|
}
|
|
3073
3148
|
},
|
|
3074
|
-
displayText: `Opened Portfolio Timeline. Shows your net worth, savings, and debt over time.`
|
|
3149
|
+
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)}.`
|
|
3075
3150
|
};
|
|
3076
3151
|
}
|
|
3077
3152
|
if (template === "spending_breakdown") {
|
|
3153
|
+
const { address, isSelfRender } = resolveAddressTarget();
|
|
3154
|
+
if (!address) {
|
|
3155
|
+
return {
|
|
3156
|
+
data: {
|
|
3157
|
+
__canvas: true,
|
|
3158
|
+
template,
|
|
3159
|
+
title,
|
|
3160
|
+
templateData: { available: false, message: "Spending Breakdown needs an address." }
|
|
3161
|
+
},
|
|
3162
|
+
displayText: "Spending Breakdown requires an address."
|
|
3163
|
+
};
|
|
3164
|
+
}
|
|
3165
|
+
const titleSuffix = isSelfRender ? "" : ` \u2014 ${address.slice(0, 6)}\u2026${address.slice(-4)}`;
|
|
3078
3166
|
return {
|
|
3079
3167
|
data: {
|
|
3080
3168
|
__canvas: true,
|
|
3081
3169
|
template,
|
|
3082
|
-
title
|
|
3170
|
+
title: `${title}${titleSuffix}`,
|
|
3083
3171
|
templateData: {
|
|
3084
3172
|
available: true,
|
|
3085
|
-
address
|
|
3173
|
+
address,
|
|
3174
|
+
isSelfRender
|
|
3086
3175
|
}
|
|
3087
3176
|
},
|
|
3088
|
-
displayText: `Opened Spending Breakdown. Shows your service spending by category.`
|
|
3177
|
+
displayText: isSelfRender ? `Opened Spending Breakdown. Shows your service spending by category.` : `Opened Spending Breakdown for ${address.slice(0, 6)}\u2026${address.slice(-4)}.`
|
|
3089
3178
|
};
|
|
3090
3179
|
}
|
|
3091
3180
|
if (template === "activity_heatmap") {
|
|
3181
|
+
const { address, isSelfRender } = resolveAddressTarget();
|
|
3182
|
+
if (!address) {
|
|
3183
|
+
return {
|
|
3184
|
+
data: {
|
|
3185
|
+
__canvas: true,
|
|
3186
|
+
template,
|
|
3187
|
+
title,
|
|
3188
|
+
templateData: { available: false, message: "Activity Heatmap needs an address." }
|
|
3189
|
+
},
|
|
3190
|
+
displayText: "Activity Heatmap requires an address."
|
|
3191
|
+
};
|
|
3192
|
+
}
|
|
3193
|
+
const titleSuffix = isSelfRender ? "" : ` \u2014 ${address.slice(0, 6)}\u2026${address.slice(-4)}`;
|
|
3092
3194
|
return {
|
|
3093
3195
|
data: {
|
|
3094
3196
|
__canvas: true,
|
|
3095
3197
|
template,
|
|
3096
|
-
title
|
|
3198
|
+
title: `${title}${titleSuffix}`,
|
|
3097
3199
|
templateData: {
|
|
3098
3200
|
available: true,
|
|
3099
|
-
address
|
|
3201
|
+
address,
|
|
3202
|
+
isSelfRender
|
|
3100
3203
|
}
|
|
3101
3204
|
},
|
|
3102
|
-
displayText: `Opened Activity Heatmap for your wallet. Click any day to explore transactions.`
|
|
3205
|
+
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.`
|
|
3103
3206
|
};
|
|
3104
3207
|
}
|
|
3105
3208
|
const positions = context.serverPositions;
|
|
@@ -3770,7 +3873,7 @@ function guardCostWarning(tool, _call, conversationText) {
|
|
|
3770
3873
|
message: "This action has a monetary cost. Confirm the user is aware before proceeding."
|
|
3771
3874
|
};
|
|
3772
3875
|
}
|
|
3773
|
-
var
|
|
3876
|
+
var SUI_ADDRESS_REGEX2 = /^0x[a-fA-F0-9]{64}$/;
|
|
3774
3877
|
function normalizeAddress(addr) {
|
|
3775
3878
|
return addr.trim().toLowerCase();
|
|
3776
3879
|
}
|
|
@@ -3835,7 +3938,7 @@ function guardAddressSource(tool, call, userText, contacts, walletAddress) {
|
|
|
3835
3938
|
if (!rawTo) {
|
|
3836
3939
|
return { verdict: "pass", gate: "address_source", tier: "safety" };
|
|
3837
3940
|
}
|
|
3838
|
-
if (!
|
|
3941
|
+
if (!SUI_ADDRESS_REGEX2.test(rawTo)) {
|
|
3839
3942
|
return { verdict: "pass", gate: "address_source", tier: "safety" };
|
|
3840
3943
|
}
|
|
3841
3944
|
const normalizedTo = normalizeAddress(rawTo);
|