@floatonnear/mcp 0.6.4 → 0.9.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.
@@ -0,0 +1,42 @@
1
+ /**
2
+ * What a token is CALLED, and how many decimals it really has.
3
+ *
4
+ * WHY. Caps, balances and errors were all rendered with the raw NEP-141
5
+ * account id, and on mainnet USDC that is
6
+ * `17208628f84f5d6ad33f0da3bbbeb27ffcb398eac501a31bd6ad2011e36133a1` — 64
7
+ * hex characters appearing in `vault_tree`, `vault_budget`, the teardown
8
+ * report and every "no limit for that token" message. Nobody reading a budget
9
+ * can tell that is USDC, and two different tokens look identical at a glance
10
+ * until you diff the strings.
11
+ *
12
+ * AND IT FIXES A REAL CORRECTNESS LIMIT, not just readability. `vault_tree`
13
+ * could only humanise the ONE token whose decimals the operator had
14
+ * configured; any other kept base units, because scaling by a guessed
15
+ * exponent prints a figure wrong by orders of magnitude while looking tidy.
16
+ * The chain knows the answer — `ft_metadata` returns it — so the guess was
17
+ * never necessary. Every token can now be rendered correctly.
18
+ *
19
+ * CACHED FOR THE PROCESS LIFETIME. Metadata is immutable in practice (a token
20
+ * that changed its decimals would break every holder), and a budget read
21
+ * should not make an extra round trip per node per token. A failed lookup is
22
+ * cached as `null` too: the common cause is an account that is not a token at
23
+ * all, and retrying that on every render turns one bad id into a storm.
24
+ */
25
+ export interface TokenMeta {
26
+ symbol: string;
27
+ decimals: number;
28
+ }
29
+ type Fetcher = (rpcUrl: string, token: string) => Promise<unknown>;
30
+ /** Visible for tests; a process-lifetime cache is otherwise untestable. */
31
+ export declare function clearTokenMetaCache(): void;
32
+ export declare function tokenMeta(rpcUrl: string, token: string, view: Fetcher): Promise<TokenMeta | null>;
33
+ /**
34
+ * `USDC` where known, the raw id where not — never a symbol alone.
35
+ *
36
+ * The id stays because a symbol is NOT unique: anyone can deploy a token
37
+ * calling itself USDC, and a budget that names only the symbol cannot be
38
+ * checked against the one the user meant. This is money; the identifier has
39
+ * to remain visible.
40
+ */
41
+ export declare function label(token: string, meta: TokenMeta | null): string;
42
+ export {};
@@ -0,0 +1,46 @@
1
+ const cache = new Map();
2
+ /** Visible for tests; a process-lifetime cache is otherwise untestable. */
3
+ export function clearTokenMetaCache() {
4
+ cache.clear();
5
+ }
6
+ export async function tokenMeta(rpcUrl, token, view) {
7
+ if (cache.has(token))
8
+ return cache.get(token) ?? null;
9
+ let meta = null;
10
+ try {
11
+ const raw = (await view(rpcUrl, token));
12
+ // BOTH FIELDS OR NEITHER. A partial answer is worse than none: a symbol
13
+ // without decimals invites rendering with the configured default, which
14
+ // is the wrong-exponent bug this exists to remove.
15
+ if (raw &&
16
+ typeof raw.symbol === "string" &&
17
+ raw.symbol.length > 0 &&
18
+ // REDUNDANT WITH THE NEXT LINE, kept for readability. Mutation-tested:
19
+ // removing this changes nothing because Number.isInteger(undefined) is
20
+ // already false, so `Number.isInteger` is the clause actually carrying
21
+ // the guard. Delete that one and the suite reds; delete this one and it
22
+ // does not.
23
+ typeof raw.decimals === "number" &&
24
+ Number.isInteger(raw.decimals) &&
25
+ raw.decimals >= 0 &&
26
+ raw.decimals <= 24) {
27
+ meta = { symbol: raw.symbol, decimals: raw.decimals };
28
+ }
29
+ }
30
+ catch {
31
+ meta = null;
32
+ }
33
+ cache.set(token, meta);
34
+ return meta;
35
+ }
36
+ /**
37
+ * `USDC` where known, the raw id where not — never a symbol alone.
38
+ *
39
+ * The id stays because a symbol is NOT unique: anyone can deploy a token
40
+ * calling itself USDC, and a budget that names only the symbol cannot be
41
+ * checked against the one the user meant. This is money; the identifier has
42
+ * to remain visible.
43
+ */
44
+ export function label(token, meta) {
45
+ return meta ? `${meta.symbol} (${token})` : token;
46
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@floatonnear/mcp",
3
- "version": "0.6.4",
3
+ "version": "0.9.0",
4
4
  "type": "module",
5
5
  "description": "MCP server for the hierarchical budget vault. The agent key signs request_payment only; owner methods emit unsigned payloads for wallet approval.",
6
6
  "license": "MIT",