@ironwallet/mcp-server 1.0.3 → 1.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +6 -2
- package/dist/api/balances.d.ts +1 -0
- package/dist/api/balances.js +1 -1
- package/dist/api/balances.js.map +1 -1
- package/dist/api/history/index.d.ts +49 -0
- package/dist/api/history/index.js +206 -0
- package/dist/api/history/index.js.map +1 -0
- package/dist/api/history/merge.d.ts +63 -0
- package/dist/api/history/merge.js +208 -0
- package/dist/api/history/merge.js.map +1 -0
- package/dist/api/history/normalize.d.ts +37 -0
- package/dist/api/history/normalize.js +149 -0
- package/dist/api/history/normalize.js.map +1 -0
- package/dist/api/history/pace.d.ts +8 -0
- package/dist/api/history/pace.js +17 -0
- package/dist/api/history/pace.js.map +1 -0
- package/dist/api/history/providers/blockcypher.d.ts +24 -0
- package/dist/api/history/providers/blockcypher.js +70 -0
- package/dist/api/history/providers/blockcypher.js.map +1 -0
- package/dist/api/history/providers/esplora.d.ts +41 -0
- package/dist/api/history/providers/esplora.js +100 -0
- package/dist/api/history/providers/esplora.js.map +1 -0
- package/dist/api/history/providers/etherscan.d.ts +36 -0
- package/dist/api/history/providers/etherscan.js +118 -0
- package/dist/api/history/providers/etherscan.js.map +1 -0
- package/dist/api/history/providers/nodereal.d.ts +27 -0
- package/dist/api/history/providers/nodereal.js +93 -0
- package/dist/api/history/providers/nodereal.js.map +1 -0
- package/dist/api/history/providers/solana.d.ts +47 -0
- package/dist/api/history/providers/solana.js +211 -0
- package/dist/api/history/providers/solana.js.map +1 -0
- package/dist/api/history/providers/toncenter.d.ts +56 -0
- package/dist/api/history/providers/toncenter.js +203 -0
- package/dist/api/history/providers/toncenter.js.map +1 -0
- package/dist/api/history/providers/trongrid.d.ts +48 -0
- package/dist/api/history/providers/trongrid.js +144 -0
- package/dist/api/history/providers/trongrid.js.map +1 -0
- package/dist/api/history/providers/tronscan.d.ts +55 -0
- package/dist/api/history/providers/tronscan.js +153 -0
- package/dist/api/history/providers/tronscan.js.map +1 -0
- package/dist/api/history/providers/xrp.d.ts +39 -0
- package/dist/api/history/providers/xrp.js +150 -0
- package/dist/api/history/providers/xrp.js.map +1 -0
- package/dist/api/history/types.d.ts +112 -0
- package/dist/api/history/types.js +26 -0
- package/dist/api/history/types.js.map +1 -0
- package/dist/api/swap.d.ts +2 -0
- package/dist/api/swap.js +24 -10
- package/dist/api/swap.js.map +1 -1
- package/dist/config.d.ts +6 -0
- package/dist/config.js +66 -0
- package/dist/config.js.map +1 -1
- package/dist/consent/copy.d.ts +2 -2
- package/dist/consent/copy.js +2 -1
- package/dist/consent/copy.js.map +1 -1
- package/dist/generated/env-config.js +96 -5
- package/dist/generated/env-config.js.map +1 -1
- package/dist/generated/env-profile.d.ts +7 -0
- package/dist/keystore/store.js +33 -4
- package/dist/keystore/store.js.map +1 -1
- package/dist/npm-registry.d.ts +45 -0
- package/dist/npm-registry.js +175 -0
- package/dist/npm-registry.js.map +1 -0
- package/dist/server.js +3 -0
- package/dist/server.js.map +1 -1
- package/dist/tools/definitions.d.ts +23 -1
- package/dist/tools/definitions.js +72 -20
- package/dist/tools/definitions.js.map +1 -1
- package/dist/tools/history.d.ts +7 -0
- package/dist/tools/history.js +70 -0
- package/dist/tools/history.js.map +1 -0
- package/dist/tools/index.js +4 -0
- package/dist/tools/index.js.map +1 -1
- package/dist/tools/runtime.d.ts +8 -0
- package/dist/tools/runtime.js +110 -0
- package/dist/tools/runtime.js.map +1 -0
- package/dist/web/manager.d.ts +6 -0
- package/dist/web/manager.js +142 -4
- package/dist/web/manager.js.map +1 -1
- package/package.json +2 -2
- package/server.json +19 -3
- package/tools.json +89 -11
|
@@ -0,0 +1,208 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* K-way merge of newest-first sources with a resumable cursor.
|
|
3
|
+
*
|
|
4
|
+
* Each source is a paged stream. Per source we remember which page we are on
|
|
5
|
+
* (`cursor`, the value that fetched it) and how many items of that page were
|
|
6
|
+
* already emitted (`skip`). Re-fetching a partially consumed page and skipping
|
|
7
|
+
* is what makes partial consumption exact for cursor-only APIs (TronGrid
|
|
8
|
+
* fingerprints, Esplora last_seen_txid, Solana `before`), where a mid-page
|
|
9
|
+
* cursor cannot be synthesized.
|
|
10
|
+
*
|
|
11
|
+
* Per call we load at most two pages per source, so a window always holds at
|
|
12
|
+
* least `limit` items unless the source is exhausted. The merge then emits up
|
|
13
|
+
* to `limit` items strictly by timestamp. If some source has an empty window
|
|
14
|
+
* but is not exhausted (an API returned fewer rows than asked), we stop early
|
|
15
|
+
* rather than emit out-of-order items — unless nothing was emitted yet, in
|
|
16
|
+
* which case progress beats perfect ordering.
|
|
17
|
+
*
|
|
18
|
+
* Offset-paged indexers (Etherscan-compatible, NodeReal, TronScan, TonCenter)
|
|
19
|
+
* drift when a new transaction lands between two calls: every page shifts by
|
|
20
|
+
* one and the last row of the previous page shows up again. The cursor
|
|
21
|
+
* therefore also carries the boundary — the oldest emitted timestamp and the
|
|
22
|
+
* hashes emitted at it — and later pages drop anything newer than or equal to
|
|
23
|
+
* that boundary. Newer rows are either duplicates or transactions that arrived
|
|
24
|
+
* after the first page; both belong to a fresh query, not to page N.
|
|
25
|
+
*/
|
|
26
|
+
const CURSOR_VERSION = 1;
|
|
27
|
+
export function encodeCursor(state) {
|
|
28
|
+
return Buffer.from(JSON.stringify(state), "utf8").toString("base64url");
|
|
29
|
+
}
|
|
30
|
+
export function decodeCursor(cursor) {
|
|
31
|
+
let parsed;
|
|
32
|
+
try {
|
|
33
|
+
parsed = JSON.parse(Buffer.from(cursor, "base64url").toString("utf8"));
|
|
34
|
+
}
|
|
35
|
+
catch {
|
|
36
|
+
throw new Error("Invalid cursor: not a cursor issued by get_transaction_history.");
|
|
37
|
+
}
|
|
38
|
+
if (!parsed ||
|
|
39
|
+
typeof parsed !== "object" ||
|
|
40
|
+
parsed.v !== CURSOR_VERSION ||
|
|
41
|
+
typeof parsed.p !== "string" ||
|
|
42
|
+
!parsed.s ||
|
|
43
|
+
typeof parsed.s !== "object") {
|
|
44
|
+
throw new Error("Invalid cursor: unsupported format. Call again without cursor.");
|
|
45
|
+
}
|
|
46
|
+
const state = parsed;
|
|
47
|
+
for (const [name, st] of Object.entries(state.s)) {
|
|
48
|
+
if (!st ||
|
|
49
|
+
typeof st !== "object" ||
|
|
50
|
+
(st.cursor !== null && typeof st.cursor !== "string") ||
|
|
51
|
+
!Number.isInteger(st.skip) ||
|
|
52
|
+
st.skip < 0 ||
|
|
53
|
+
typeof st.done !== "boolean") {
|
|
54
|
+
throw new Error(`Invalid cursor: bad state for source "${name}".`);
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
if (state.b !== undefined) {
|
|
58
|
+
const b = state.b;
|
|
59
|
+
if (!b ||
|
|
60
|
+
typeof b !== "object" ||
|
|
61
|
+
!Number.isFinite(b.ts) ||
|
|
62
|
+
!Array.isArray(b.h) ||
|
|
63
|
+
b.h.some((h) => typeof h !== "string")) {
|
|
64
|
+
throw new Error("Invalid cursor: bad boundary.");
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
return state;
|
|
68
|
+
}
|
|
69
|
+
/** True when a row is at or above the boundary, i.e. already emitted or newer than page 1. */
|
|
70
|
+
function beforeBoundary(item, b) {
|
|
71
|
+
if (!b)
|
|
72
|
+
return false;
|
|
73
|
+
return item.ts > b.ts || (item.ts === b.ts && b.h.includes(item.item.hash));
|
|
74
|
+
}
|
|
75
|
+
async function loadWindow(source, state, limit, ctx, boundary) {
|
|
76
|
+
if (state.done)
|
|
77
|
+
return { source, entries: [], exhausted: true, state };
|
|
78
|
+
const entries = [];
|
|
79
|
+
let pageCursor = state.cursor;
|
|
80
|
+
let skip = state.skip;
|
|
81
|
+
let exhausted = false;
|
|
82
|
+
for (let pages = 0; pages < 2; pages++) {
|
|
83
|
+
const page = await source.fetchPage(pageCursor, limit, ctx);
|
|
84
|
+
const items = page.items;
|
|
85
|
+
for (let i = skip; i < items.length; i++) {
|
|
86
|
+
const lastOfPage = i === items.length - 1;
|
|
87
|
+
const after = lastOfPage && page.next === null
|
|
88
|
+
? { cursor: pageCursor, skip: i + 1, done: true }
|
|
89
|
+
: lastOfPage
|
|
90
|
+
? { cursor: page.next, skip: 0, done: false }
|
|
91
|
+
: { cursor: pageCursor, skip: i + 1, done: false };
|
|
92
|
+
// Drifted duplicates and rows newer than page 1 are consumed (state
|
|
93
|
+
// advances past them) but never emitted.
|
|
94
|
+
if (beforeBoundary(items[i], boundary)) {
|
|
95
|
+
if (entries.length === 0)
|
|
96
|
+
state = after;
|
|
97
|
+
continue;
|
|
98
|
+
}
|
|
99
|
+
entries.push({ item: items[i], after });
|
|
100
|
+
}
|
|
101
|
+
if (page.next === null) {
|
|
102
|
+
exhausted = true;
|
|
103
|
+
break;
|
|
104
|
+
}
|
|
105
|
+
if (entries.length >= limit)
|
|
106
|
+
break;
|
|
107
|
+
pageCursor = page.next;
|
|
108
|
+
skip = 0;
|
|
109
|
+
}
|
|
110
|
+
// A page that ended exactly at its boundary with `skip` covering all rows:
|
|
111
|
+
// nothing new here, but the stream continues from the next page.
|
|
112
|
+
if (entries.length === 0 && !exhausted) {
|
|
113
|
+
return { source, entries, exhausted: false, state: { cursor: pageCursor, skip: 0, done: false } };
|
|
114
|
+
}
|
|
115
|
+
if (entries.length === 0 && exhausted) {
|
|
116
|
+
return { source, entries, exhausted: true, state: { ...state, done: true } };
|
|
117
|
+
}
|
|
118
|
+
return { source, entries, exhausted, state };
|
|
119
|
+
}
|
|
120
|
+
const KIND_RANK = {
|
|
121
|
+
token_transfer: 0,
|
|
122
|
+
transfer: 1,
|
|
123
|
+
contract_call: 2,
|
|
124
|
+
other: 3,
|
|
125
|
+
};
|
|
126
|
+
/**
|
|
127
|
+
* Newest first. Ties (same millisecond — common for a token transfer and the
|
|
128
|
+
* call that carried it) go to: the row sharing the last emitted hash, then
|
|
129
|
+
* the more informative kind, then source order.
|
|
130
|
+
*/
|
|
131
|
+
function pickNext(windows, heads, last) {
|
|
132
|
+
let best = -1;
|
|
133
|
+
for (let i = 0; i < windows.length; i++) {
|
|
134
|
+
if (heads[i] >= windows[i].entries.length)
|
|
135
|
+
continue;
|
|
136
|
+
if (best === -1) {
|
|
137
|
+
best = i;
|
|
138
|
+
continue;
|
|
139
|
+
}
|
|
140
|
+
const a = windows[i].entries[heads[i]].item;
|
|
141
|
+
const b = windows[best].entries[heads[best]].item;
|
|
142
|
+
if (a.ts !== b.ts) {
|
|
143
|
+
if (a.ts > b.ts)
|
|
144
|
+
best = i;
|
|
145
|
+
continue;
|
|
146
|
+
}
|
|
147
|
+
const aSame = last !== undefined && a.item.hash === last.hash;
|
|
148
|
+
const bSame = last !== undefined && b.item.hash === last.hash;
|
|
149
|
+
if (aSame !== bSame) {
|
|
150
|
+
if (aSame)
|
|
151
|
+
best = i;
|
|
152
|
+
continue;
|
|
153
|
+
}
|
|
154
|
+
if (KIND_RANK[a.item.kind] < KIND_RANK[b.item.kind])
|
|
155
|
+
best = i;
|
|
156
|
+
}
|
|
157
|
+
return best;
|
|
158
|
+
}
|
|
159
|
+
/**
|
|
160
|
+
* @param limit rows to emit
|
|
161
|
+
* @param pageSize rows to request per source page (defaults to `limit`); a
|
|
162
|
+
* refill round asks for a full page while emitting only the
|
|
163
|
+
* remainder, so it does not crawl in tiny requests
|
|
164
|
+
*/
|
|
165
|
+
export async function mergeSources(sources, limit, previous, ctx, pageSize = limit) {
|
|
166
|
+
const windows = await Promise.all(sources.map((source) => loadWindow(source, previous?.s[source.name] ?? { cursor: null, skip: 0, done: false }, Math.max(limit, pageSize), ctx, previous?.b)));
|
|
167
|
+
const heads = windows.map(() => 0);
|
|
168
|
+
const out = [];
|
|
169
|
+
let lastTs = 0;
|
|
170
|
+
for (;;) {
|
|
171
|
+
const best = pickNext(windows, heads, out[out.length - 1]);
|
|
172
|
+
if (best === -1)
|
|
173
|
+
break;
|
|
174
|
+
const entry = windows[best].entries[heads[best]];
|
|
175
|
+
// Past the limit we only continue while the next row belongs to the same
|
|
176
|
+
// transaction as the last one, so a tx is never split across pages.
|
|
177
|
+
if (out.length >= limit && entry.item.item.hash !== out[out.length - 1]?.hash)
|
|
178
|
+
break;
|
|
179
|
+
const starved = windows.some((w, i) => heads[i] >= w.entries.length && !w.exhausted && i !== best);
|
|
180
|
+
if (starved && out.length > 0)
|
|
181
|
+
break;
|
|
182
|
+
heads[best]++;
|
|
183
|
+
windows[best].state = entry.after;
|
|
184
|
+
out.push(entry.item.item);
|
|
185
|
+
lastTs = entry.item.ts;
|
|
186
|
+
}
|
|
187
|
+
const state = {};
|
|
188
|
+
let hasMore = false;
|
|
189
|
+
windows.forEach((w, i) => {
|
|
190
|
+
state[w.source.name] = w.state;
|
|
191
|
+
if (heads[i] < w.entries.length || !w.exhausted)
|
|
192
|
+
hasMore = true;
|
|
193
|
+
});
|
|
194
|
+
let boundary = previous?.b;
|
|
195
|
+
if (out.length > 0) {
|
|
196
|
+
const hashes = new Set();
|
|
197
|
+
windows.forEach((w, i) => {
|
|
198
|
+
for (let k = 0; k < heads[i]; k++) {
|
|
199
|
+
const e = w.entries[k];
|
|
200
|
+
if (e.item.ts === lastTs)
|
|
201
|
+
hashes.add(e.item.item.hash);
|
|
202
|
+
}
|
|
203
|
+
});
|
|
204
|
+
boundary = { ts: lastTs, h: [...hashes] };
|
|
205
|
+
}
|
|
206
|
+
return { items: out, state, boundary, hasMore };
|
|
207
|
+
}
|
|
208
|
+
//# sourceMappingURL=merge.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"merge.js","sourceRoot":"","sources":["../../../src/api/history/merge.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Helpers shared by history providers: untrusted-string hygiene, amount
|
|
3
|
+
* formatting, direction detection. Indexer payloads are attacker-influenced
|
|
4
|
+
* (anyone can deploy a token called "IGNORE PREVIOUS INSTRUCTIONS"), so token
|
|
5
|
+
* symbols are reduced to a short printable string before they reach the agent.
|
|
6
|
+
*/
|
|
7
|
+
import type { HistoryAmount, HistoryAsset, HistoryDirection, HistoryFee } from "./types.js";
|
|
8
|
+
/**
|
|
9
|
+
* Keep only printable, non-whitespace-control characters, collapse spaces,
|
|
10
|
+
* cap the length. Falls back when nothing usable remains. `₮` (Tether's
|
|
11
|
+
* jetton ticker) is folded to `T` like the balance reader does.
|
|
12
|
+
*/
|
|
13
|
+
export declare function sanitizeSymbol(value: unknown, fallback: string): string;
|
|
14
|
+
/** Heuristic spam flag for token symbols copied from an indexer. */
|
|
15
|
+
export declare function symbolWarning(symbol: string): string | undefined;
|
|
16
|
+
/** Sanitized token asset with a spam warning when the symbol looks off. */
|
|
17
|
+
export declare function tokenAsset(rawSymbol: unknown, fallback: string, contractAddress: string | null, decimals: number): HistoryAsset;
|
|
18
|
+
/** Token decimals from an indexer field; anything absurd becomes `fallback`. */
|
|
19
|
+
export declare function clampDecimals(value: unknown, fallback: number): number;
|
|
20
|
+
/** Parse a decimal or 0x-hex integer string/number into a bigint; NaN-ish → 0n. */
|
|
21
|
+
export declare function toBigInt(value: unknown): bigint;
|
|
22
|
+
export declare function amountOf(raw: bigint, decimals: number): HistoryAmount;
|
|
23
|
+
export declare function feeOf(raw: bigint, decimals: number, symbol: string): HistoryFee | null;
|
|
24
|
+
/** Seconds or milliseconds since epoch → { ts (ms), iso }. Null when unusable. */
|
|
25
|
+
export declare function timeFrom(value: unknown): {
|
|
26
|
+
ts: number;
|
|
27
|
+
iso: string;
|
|
28
|
+
} | null;
|
|
29
|
+
/** Pending items sort to the top of the newest-first list. */
|
|
30
|
+
export declare function pendingTs(): number;
|
|
31
|
+
export declare function directionOf(from: string | null | undefined, to: string | null | undefined, isSelf: (candidate: string | null | undefined) => boolean): HistoryDirection;
|
|
32
|
+
/** Case-insensitive comparator for hex (EVM) addresses. */
|
|
33
|
+
export declare function evmAddressMatcher(self: string): (candidate: string | null | undefined) => boolean;
|
|
34
|
+
/** Exact comparator for base58 / bech32 style addresses. */
|
|
35
|
+
export declare function exactAddressMatcher(self: string): (candidate: string | null | undefined) => boolean;
|
|
36
|
+
export declare function toBlockNumber(value: unknown): number | null;
|
|
37
|
+
export declare function asString(value: unknown): string | null;
|
|
@@ -0,0 +1,149 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Helpers shared by history providers: untrusted-string hygiene, amount
|
|
3
|
+
* formatting, direction detection. Indexer payloads are attacker-influenced
|
|
4
|
+
* (anyone can deploy a token called "IGNORE PREVIOUS INSTRUCTIONS"), so token
|
|
5
|
+
* symbols are reduced to a short printable string before they reach the agent.
|
|
6
|
+
*/
|
|
7
|
+
import { formatUnits } from "ethers";
|
|
8
|
+
const MAX_SYMBOL_LENGTH = 24;
|
|
9
|
+
/**
|
|
10
|
+
* Keep only printable, non-whitespace-control characters, collapse spaces,
|
|
11
|
+
* cap the length. Falls back when nothing usable remains. `₮` (Tether's
|
|
12
|
+
* jetton ticker) is folded to `T` like the balance reader does.
|
|
13
|
+
*/
|
|
14
|
+
export function sanitizeSymbol(value, fallback) {
|
|
15
|
+
if (typeof value !== "string")
|
|
16
|
+
return fallback;
|
|
17
|
+
const cleaned = value
|
|
18
|
+
.replaceAll("₮", "T")
|
|
19
|
+
.replace(/[\p{Cc}\p{Cf}\p{Zl}\p{Zp}]/gu, "")
|
|
20
|
+
.replace(/\s+/g, " ")
|
|
21
|
+
.trim()
|
|
22
|
+
.slice(0, MAX_SYMBOL_LENGTH)
|
|
23
|
+
.trim();
|
|
24
|
+
return cleaned.length > 0 ? cleaned : fallback;
|
|
25
|
+
}
|
|
26
|
+
const LINK_IN_SYMBOL = /https?:|www\.|t\.me|\.(com|net|org|io|club|xyz|me|app|site|top|info|pro|cc|link|live|online|vip|fun|finance|gift|claim)\b/i;
|
|
27
|
+
/** Heuristic spam flag for token symbols copied from an indexer. */
|
|
28
|
+
export function symbolWarning(symbol) {
|
|
29
|
+
if (LINK_IN_SYMBOL.test(symbol)) {
|
|
30
|
+
return "Symbol contains a link. Unsolicited tokens like this are usually scams; do not interact with the contract.";
|
|
31
|
+
}
|
|
32
|
+
// eslint-disable-next-line no-control-regex
|
|
33
|
+
if (/[^\x20-\x7E]/.test(symbol)) {
|
|
34
|
+
return "Symbol uses non-ASCII characters, possibly imitating another ticker. Verify the contract address before trusting it.";
|
|
35
|
+
}
|
|
36
|
+
if (/[|\\/<>{}[\]]/.test(symbol) || symbol.length > 12) {
|
|
37
|
+
return "Unusual symbol for a token. Verify the contract address before trusting it.";
|
|
38
|
+
}
|
|
39
|
+
return undefined;
|
|
40
|
+
}
|
|
41
|
+
/** Sanitized token asset with a spam warning when the symbol looks off. */
|
|
42
|
+
export function tokenAsset(rawSymbol, fallback, contractAddress, decimals) {
|
|
43
|
+
const symbol = sanitizeSymbol(rawSymbol, fallback);
|
|
44
|
+
const warning = symbol === fallback ? undefined : symbolWarning(symbol);
|
|
45
|
+
return { symbol, contractAddress, decimals, ...(warning ? { warning } : {}) };
|
|
46
|
+
}
|
|
47
|
+
/** Token decimals from an indexer field; anything absurd becomes `fallback`. */
|
|
48
|
+
export function clampDecimals(value, fallback) {
|
|
49
|
+
const n = typeof value === "number" ? value : Number.parseInt(String(value ?? ""), 10);
|
|
50
|
+
if (!Number.isInteger(n) || n < 0 || n > 36)
|
|
51
|
+
return fallback;
|
|
52
|
+
return n;
|
|
53
|
+
}
|
|
54
|
+
/** Parse a decimal or 0x-hex integer string/number into a bigint; NaN-ish → 0n. */
|
|
55
|
+
export function toBigInt(value) {
|
|
56
|
+
if (typeof value === "bigint")
|
|
57
|
+
return value;
|
|
58
|
+
if (typeof value === "number") {
|
|
59
|
+
return Number.isFinite(value) ? BigInt(Math.trunc(value)) : 0n;
|
|
60
|
+
}
|
|
61
|
+
if (typeof value !== "string")
|
|
62
|
+
return 0n;
|
|
63
|
+
const s = value.trim();
|
|
64
|
+
if (s.length === 0)
|
|
65
|
+
return 0n;
|
|
66
|
+
try {
|
|
67
|
+
if (/^-?0x[0-9a-f]+$/i.test(s)) {
|
|
68
|
+
return s.startsWith("-") ? -BigInt(s.slice(1)) : BigInt(s);
|
|
69
|
+
}
|
|
70
|
+
if (/^-?\d+$/.test(s))
|
|
71
|
+
return BigInt(s);
|
|
72
|
+
// "123.0" style from some indexers.
|
|
73
|
+
const m = /^(-?\d+)\.0*$/.exec(s);
|
|
74
|
+
if (m)
|
|
75
|
+
return BigInt(m[1]);
|
|
76
|
+
}
|
|
77
|
+
catch {
|
|
78
|
+
return 0n;
|
|
79
|
+
}
|
|
80
|
+
return 0n;
|
|
81
|
+
}
|
|
82
|
+
export function amountOf(raw, decimals) {
|
|
83
|
+
const abs = raw < 0n ? -raw : raw;
|
|
84
|
+
return { raw: abs.toString(), formatted: formatUnits(abs, decimals) };
|
|
85
|
+
}
|
|
86
|
+
export function feeOf(raw, decimals, symbol) {
|
|
87
|
+
if (raw <= 0n)
|
|
88
|
+
return null;
|
|
89
|
+
return { ...amountOf(raw, decimals), symbol };
|
|
90
|
+
}
|
|
91
|
+
/** Seconds or milliseconds since epoch → { ts (ms), iso }. Null when unusable. */
|
|
92
|
+
export function timeFrom(value) {
|
|
93
|
+
let n;
|
|
94
|
+
if (typeof value === "number")
|
|
95
|
+
n = value;
|
|
96
|
+
else if (typeof value === "string" && /^\d+$/.test(value.trim()))
|
|
97
|
+
n = Number(value.trim());
|
|
98
|
+
else if (typeof value === "string" && value.trim().length > 0) {
|
|
99
|
+
const parsed = Date.parse(value);
|
|
100
|
+
if (Number.isNaN(parsed))
|
|
101
|
+
return null;
|
|
102
|
+
return { ts: parsed, iso: new Date(parsed).toISOString() };
|
|
103
|
+
}
|
|
104
|
+
else
|
|
105
|
+
return null;
|
|
106
|
+
if (!Number.isFinite(n) || n <= 0)
|
|
107
|
+
return null;
|
|
108
|
+
// Anything below ~1e11 is seconds (that is year 5138 in seconds, 1973 in ms).
|
|
109
|
+
const ms = n < 1e11 ? n * 1000 : n;
|
|
110
|
+
return { ts: ms, iso: new Date(ms).toISOString() };
|
|
111
|
+
}
|
|
112
|
+
/** Pending items sort to the top of the newest-first list. */
|
|
113
|
+
export function pendingTs() {
|
|
114
|
+
return Date.now();
|
|
115
|
+
}
|
|
116
|
+
export function directionOf(from, to, isSelf) {
|
|
117
|
+
const fromSelf = isSelf(from);
|
|
118
|
+
const toSelf = isSelf(to);
|
|
119
|
+
if (fromSelf && toSelf)
|
|
120
|
+
return "self";
|
|
121
|
+
if (fromSelf)
|
|
122
|
+
return "out";
|
|
123
|
+
return "in";
|
|
124
|
+
}
|
|
125
|
+
/** Case-insensitive comparator for hex (EVM) addresses. */
|
|
126
|
+
export function evmAddressMatcher(self) {
|
|
127
|
+
const target = self.toLowerCase();
|
|
128
|
+
return (candidate) => typeof candidate === "string" && candidate.toLowerCase() === target;
|
|
129
|
+
}
|
|
130
|
+
/** Exact comparator for base58 / bech32 style addresses. */
|
|
131
|
+
export function exactAddressMatcher(self) {
|
|
132
|
+
return (candidate) => candidate === self;
|
|
133
|
+
}
|
|
134
|
+
export function toBlockNumber(value) {
|
|
135
|
+
if (typeof value === "number")
|
|
136
|
+
return Number.isFinite(value) && value >= 0 ? Math.trunc(value) : null;
|
|
137
|
+
if (typeof value !== "string")
|
|
138
|
+
return null;
|
|
139
|
+
const s = value.trim();
|
|
140
|
+
if (/^0x[0-9a-f]+$/i.test(s))
|
|
141
|
+
return Number.parseInt(s, 16);
|
|
142
|
+
if (/^\d+$/.test(s))
|
|
143
|
+
return Number.parseInt(s, 10);
|
|
144
|
+
return null;
|
|
145
|
+
}
|
|
146
|
+
export function asString(value) {
|
|
147
|
+
return typeof value === "string" && value.length > 0 ? value : null;
|
|
148
|
+
}
|
|
149
|
+
//# sourceMappingURL=normalize.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"normalize.js","sourceRoot":"","sources":["../../../src/api/history/normalize.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Process-wide request gates for keyless public indexers with per-IP rate
|
|
3
|
+
* limits (TonCenter ≈ 1 rps, TronGrid 3 rps). A provider's sources run in
|
|
4
|
+
* parallel and may load two pages each, so without a gate a single tool call
|
|
5
|
+
* bursts past the limit and the indexer suspends the IP for several seconds.
|
|
6
|
+
*/
|
|
7
|
+
/** Run `fn` no sooner than `gapMs` after the previous call through the same gate. */
|
|
8
|
+
export declare function paced<T>(gate: string, gapMs: number, fn: () => Promise<T>): Promise<T>;
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Process-wide request gates for keyless public indexers with per-IP rate
|
|
3
|
+
* limits (TonCenter ≈ 1 rps, TronGrid 3 rps). A provider's sources run in
|
|
4
|
+
* parallel and may load two pages each, so without a gate a single tool call
|
|
5
|
+
* bursts past the limit and the indexer suspends the IP for several seconds.
|
|
6
|
+
*/
|
|
7
|
+
const gates = new Map();
|
|
8
|
+
/** Run `fn` no sooner than `gapMs` after the previous call through the same gate. */
|
|
9
|
+
export async function paced(gate, gapMs, fn) {
|
|
10
|
+
const now = Date.now();
|
|
11
|
+
const startAt = Math.max(now, gates.get(gate) ?? 0);
|
|
12
|
+
gates.set(gate, startAt + gapMs);
|
|
13
|
+
if (startAt > now)
|
|
14
|
+
await new Promise((resolve) => setTimeout(resolve, startAt - now));
|
|
15
|
+
return fn();
|
|
16
|
+
}
|
|
17
|
+
//# sourceMappingURL=pace.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"pace.js","sourceRoot":"","sources":["../../../src/api/history/pace.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,MAAM,KAAK,GAAG,IAAI,GAAG,EAAkB,CAAC;AAExC,qFAAqF;AACrF,MAAM,CAAC,KAAK,UAAU,KAAK,CAAI,IAAY,EAAE,KAAa,EAAE,EAAoB;IAC9E,MAAM,GAAG,GAAG,IAAI,CAAC,GAAG,EAAE,CAAC;IACvB,MAAM,OAAO,GAAG,IAAI,CAAC,GAAG,CAAC,GAAG,EAAE,KAAK,CAAC,GAAG,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC,CAAC;IACpD,KAAK,CAAC,GAAG,CAAC,IAAI,EAAE,OAAO,GAAG,KAAK,CAAC,CAAC;IACjC,IAAI,OAAO,GAAG,GAAG;QAAE,MAAM,IAAI,OAAO,CAAC,CAAC,OAAO,EAAE,EAAE,CAAC,UAAU,CAAC,OAAO,EAAE,OAAO,GAAG,GAAG,CAAC,CAAC,CAAC;IACtF,OAAO,EAAE,EAAE,CAAC;AACd,CAAC"}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* BlockCypher `/addrs/{a}/full` for Dogecoin. Newest first, `limit` ≤ 50,
|
|
3
|
+
* paged with `before=<block height>` (exclusive). Keyless quota is small
|
|
4
|
+
* (~100 req/h), which is fine for a wallet's occasional history check.
|
|
5
|
+
*/
|
|
6
|
+
import type { NetworkId } from "../../../networks.js";
|
|
7
|
+
import type { HistoryProvider, SourceItem } from "../types.js";
|
|
8
|
+
export interface BlockCypherTx {
|
|
9
|
+
hash?: string;
|
|
10
|
+
block_height?: number;
|
|
11
|
+
confirmed?: string;
|
|
12
|
+
received?: string;
|
|
13
|
+
fees?: number;
|
|
14
|
+
inputs?: Array<{
|
|
15
|
+
addresses?: string[];
|
|
16
|
+
output_value?: number;
|
|
17
|
+
}>;
|
|
18
|
+
outputs?: Array<{
|
|
19
|
+
addresses?: string[];
|
|
20
|
+
value?: number;
|
|
21
|
+
}>;
|
|
22
|
+
}
|
|
23
|
+
export declare function mapBlockCypherTx(tx: BlockCypherTx, address: string, network: NetworkId): SourceItem | null;
|
|
24
|
+
export declare function blockcypherProvider(baseUrl: string, address: string, network: NetworkId): HistoryProvider;
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* BlockCypher `/addrs/{a}/full` for Dogecoin. Newest first, `limit` ≤ 50,
|
|
3
|
+
* paged with `before=<block height>` (exclusive). Keyless quota is small
|
|
4
|
+
* (~100 req/h), which is fine for a wallet's occasional history check.
|
|
5
|
+
*/
|
|
6
|
+
import { NATIVE_SYMBOL } from "../../balances.js";
|
|
7
|
+
import { httpJson } from "../../http.js";
|
|
8
|
+
import { asString, feeOf, pendingTs, timeFrom, toBigInt } from "../normalize.js";
|
|
9
|
+
import { utxoSummary } from "./esplora.js";
|
|
10
|
+
export function mapBlockCypherTx(tx, address, network) {
|
|
11
|
+
const hash = asString(tx.hash);
|
|
12
|
+
if (!hash)
|
|
13
|
+
return null;
|
|
14
|
+
const confirmed = typeof tx.block_height === "number" && tx.block_height > 0;
|
|
15
|
+
const time = confirmed ? timeFrom(tx.confirmed) : null;
|
|
16
|
+
const inputs = (tx.inputs ?? []).map((i) => ({
|
|
17
|
+
address: i.addresses?.[0] ?? null,
|
|
18
|
+
value: toBigInt(i.output_value ?? 0),
|
|
19
|
+
}));
|
|
20
|
+
const outputs = (tx.outputs ?? []).map((o) => ({
|
|
21
|
+
address: o.addresses?.[0] ?? null,
|
|
22
|
+
value: toBigInt(o.value ?? 0),
|
|
23
|
+
}));
|
|
24
|
+
const feeRaw = toBigInt(tx.fees ?? 0);
|
|
25
|
+
const summary = utxoSummary(inputs, outputs, address);
|
|
26
|
+
const symbol = NATIVE_SYMBOL[network];
|
|
27
|
+
const item = {
|
|
28
|
+
hash,
|
|
29
|
+
timestamp: time?.iso ?? null,
|
|
30
|
+
block: confirmed ? tx.block_height : null,
|
|
31
|
+
kind: "transfer",
|
|
32
|
+
direction: summary.direction,
|
|
33
|
+
status: confirmed ? "confirmed" : "pending",
|
|
34
|
+
from: summary.from,
|
|
35
|
+
to: summary.to,
|
|
36
|
+
asset: { symbol, contractAddress: null, decimals: 8 },
|
|
37
|
+
amount: summary.amount,
|
|
38
|
+
fee: summary.direction === "in" ? null : feeOf(feeRaw, 8, symbol),
|
|
39
|
+
};
|
|
40
|
+
return { ts: time?.ts ?? pendingTs(), item };
|
|
41
|
+
}
|
|
42
|
+
export function blockcypherProvider(baseUrl, address, network) {
|
|
43
|
+
const source = {
|
|
44
|
+
name: "utxo",
|
|
45
|
+
async fetchPage(cursor, pageSize, ctx) {
|
|
46
|
+
if (cursor !== null && !/^\d+$/.test(cursor))
|
|
47
|
+
throw new Error("Invalid cursor for blockcypher source.");
|
|
48
|
+
const url = `${baseUrl}/addrs/${encodeURIComponent(address)}/full?limit=${pageSize}` +
|
|
49
|
+
(cursor !== null ? `&before=${cursor}` : "");
|
|
50
|
+
const res = await httpJson(url, { method: "GET" }, { ...ctx.http, label: "history.blockcypher" });
|
|
51
|
+
if (res.error)
|
|
52
|
+
throw new Error(`blockcypher: ${res.error}`);
|
|
53
|
+
const rows = res.txs ?? [];
|
|
54
|
+
const items = rows.map((tx) => mapBlockCypherTx(tx, address, network)).filter((x) => x !== null);
|
|
55
|
+
const heights = rows
|
|
56
|
+
.map((tx) => tx.block_height)
|
|
57
|
+
.filter((h) => typeof h === "number" && h > 0);
|
|
58
|
+
// `before` is exclusive, so a page cut in the middle of a block would
|
|
59
|
+
// skip the rest of that block. Re-include the oldest block instead; the
|
|
60
|
+
// merge boundary drops the rows already emitted. When the whole page is
|
|
61
|
+
// one block that would loop forever, so step past it in that case.
|
|
62
|
+
const oldest = heights.length > 0 ? Math.min(...heights) : null;
|
|
63
|
+
const newest = heights.length > 0 ? Math.max(...heights) : null;
|
|
64
|
+
const next = res.hasMore && oldest !== null ? String(oldest === newest ? oldest : oldest + 1) : null;
|
|
65
|
+
return { items, next };
|
|
66
|
+
},
|
|
67
|
+
};
|
|
68
|
+
return { label: "blockcypher", sources: [source] };
|
|
69
|
+
}
|
|
70
|
+
//# sourceMappingURL=blockcypher.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"blockcypher.js","sourceRoot":"","sources":["../../../../src/api/history/providers/blockcypher.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Esplora REST API (blockstream.info, mempool.space, litecoinspace.org).
|
|
3
|
+
* `/address/{a}/txs` returns mempool + the newest 25 confirmed transactions;
|
|
4
|
+
* `/address/{a}/txs/chain/{last_seen_txid}` pages further. One source; the
|
|
5
|
+
* page cursor is the last confirmed txid we saw. Direction and amount are
|
|
6
|
+
* derived from the wallet's share of inputs and outputs.
|
|
7
|
+
*/
|
|
8
|
+
import type { NetworkId } from "../../../networks.js";
|
|
9
|
+
import type { HistoryItem, HistoryProvider, SourceItem } from "../types.js";
|
|
10
|
+
export interface EsploraTx {
|
|
11
|
+
txid?: string;
|
|
12
|
+
fee?: number;
|
|
13
|
+
status?: {
|
|
14
|
+
confirmed?: boolean;
|
|
15
|
+
block_height?: number;
|
|
16
|
+
block_time?: number;
|
|
17
|
+
};
|
|
18
|
+
vin?: Array<{
|
|
19
|
+
prevout?: {
|
|
20
|
+
scriptpubkey_address?: string;
|
|
21
|
+
value?: number;
|
|
22
|
+
} | null;
|
|
23
|
+
is_coinbase?: boolean;
|
|
24
|
+
}>;
|
|
25
|
+
vout?: Array<{
|
|
26
|
+
scriptpubkey_address?: string;
|
|
27
|
+
value?: number;
|
|
28
|
+
}>;
|
|
29
|
+
}
|
|
30
|
+
export interface UtxoIo {
|
|
31
|
+
address: string | null;
|
|
32
|
+
value: bigint;
|
|
33
|
+
}
|
|
34
|
+
/**
|
|
35
|
+
* Wallet-relative view of a UTXO transaction. `sent` = value of our inputs,
|
|
36
|
+
* `received` = value of our outputs. Out: amount = what left to others
|
|
37
|
+
* (inputs − change − fee). In: amount = what we received.
|
|
38
|
+
*/
|
|
39
|
+
export declare function utxoSummary(inputs: UtxoIo[], outputs: UtxoIo[], address: string): Pick<HistoryItem, "direction" | "amount" | "from" | "to">;
|
|
40
|
+
export declare function mapEsploraTx(tx: EsploraTx, address: string, network: NetworkId): SourceItem | null;
|
|
41
|
+
export declare function esploraProvider(baseUrl: string, address: string, network: NetworkId): HistoryProvider;
|