@ironwallet/mcp-server 1.1.0 → 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.
Files changed (82) hide show
  1. package/README.md +6 -2
  2. package/dist/api/balances.d.ts +1 -0
  3. package/dist/api/balances.js +1 -1
  4. package/dist/api/balances.js.map +1 -1
  5. package/dist/api/history/index.d.ts +49 -0
  6. package/dist/api/history/index.js +206 -0
  7. package/dist/api/history/index.js.map +1 -0
  8. package/dist/api/history/merge.d.ts +63 -0
  9. package/dist/api/history/merge.js +208 -0
  10. package/dist/api/history/merge.js.map +1 -0
  11. package/dist/api/history/normalize.d.ts +37 -0
  12. package/dist/api/history/normalize.js +149 -0
  13. package/dist/api/history/normalize.js.map +1 -0
  14. package/dist/api/history/pace.d.ts +8 -0
  15. package/dist/api/history/pace.js +17 -0
  16. package/dist/api/history/pace.js.map +1 -0
  17. package/dist/api/history/providers/blockcypher.d.ts +24 -0
  18. package/dist/api/history/providers/blockcypher.js +70 -0
  19. package/dist/api/history/providers/blockcypher.js.map +1 -0
  20. package/dist/api/history/providers/esplora.d.ts +41 -0
  21. package/dist/api/history/providers/esplora.js +100 -0
  22. package/dist/api/history/providers/esplora.js.map +1 -0
  23. package/dist/api/history/providers/etherscan.d.ts +36 -0
  24. package/dist/api/history/providers/etherscan.js +118 -0
  25. package/dist/api/history/providers/etherscan.js.map +1 -0
  26. package/dist/api/history/providers/nodereal.d.ts +27 -0
  27. package/dist/api/history/providers/nodereal.js +93 -0
  28. package/dist/api/history/providers/nodereal.js.map +1 -0
  29. package/dist/api/history/providers/solana.d.ts +47 -0
  30. package/dist/api/history/providers/solana.js +211 -0
  31. package/dist/api/history/providers/solana.js.map +1 -0
  32. package/dist/api/history/providers/toncenter.d.ts +56 -0
  33. package/dist/api/history/providers/toncenter.js +203 -0
  34. package/dist/api/history/providers/toncenter.js.map +1 -0
  35. package/dist/api/history/providers/trongrid.d.ts +48 -0
  36. package/dist/api/history/providers/trongrid.js +144 -0
  37. package/dist/api/history/providers/trongrid.js.map +1 -0
  38. package/dist/api/history/providers/tronscan.d.ts +55 -0
  39. package/dist/api/history/providers/tronscan.js +153 -0
  40. package/dist/api/history/providers/tronscan.js.map +1 -0
  41. package/dist/api/history/providers/xrp.d.ts +39 -0
  42. package/dist/api/history/providers/xrp.js +150 -0
  43. package/dist/api/history/providers/xrp.js.map +1 -0
  44. package/dist/api/history/types.d.ts +112 -0
  45. package/dist/api/history/types.js +26 -0
  46. package/dist/api/history/types.js.map +1 -0
  47. package/dist/api/swap.d.ts +2 -0
  48. package/dist/api/swap.js +24 -10
  49. package/dist/api/swap.js.map +1 -1
  50. package/dist/config.d.ts +6 -0
  51. package/dist/config.js +66 -0
  52. package/dist/config.js.map +1 -1
  53. package/dist/consent/copy.d.ts +2 -2
  54. package/dist/consent/copy.js +2 -1
  55. package/dist/consent/copy.js.map +1 -1
  56. package/dist/generated/env-config.js +92 -1
  57. package/dist/generated/env-config.js.map +1 -1
  58. package/dist/generated/env-profile.d.ts +7 -0
  59. package/dist/keystore/store.js +33 -4
  60. package/dist/keystore/store.js.map +1 -1
  61. package/dist/npm-registry.d.ts +45 -0
  62. package/dist/npm-registry.js +175 -0
  63. package/dist/npm-registry.js.map +1 -0
  64. package/dist/server.js +3 -0
  65. package/dist/server.js.map +1 -1
  66. package/dist/tools/definitions.d.ts +23 -1
  67. package/dist/tools/definitions.js +72 -20
  68. package/dist/tools/definitions.js.map +1 -1
  69. package/dist/tools/history.d.ts +7 -0
  70. package/dist/tools/history.js +70 -0
  71. package/dist/tools/history.js.map +1 -0
  72. package/dist/tools/index.js +4 -0
  73. package/dist/tools/index.js.map +1 -1
  74. package/dist/tools/runtime.d.ts +8 -0
  75. package/dist/tools/runtime.js +110 -0
  76. package/dist/tools/runtime.js.map +1 -0
  77. package/dist/web/manager.d.ts +6 -0
  78. package/dist/web/manager.js +142 -4
  79. package/dist/web/manager.js.map +1 -1
  80. package/package.json +2 -2
  81. package/server.json +19 -3
  82. package/tools.json +89 -11
@@ -0,0 +1,100 @@
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 { NATIVE_SYMBOL } from "../../balances.js";
9
+ import { httpJson } from "../../http.js";
10
+ import { amountOf, asString, feeOf, pendingTs, timeFrom, toBigInt } from "../normalize.js";
11
+ /**
12
+ * Wallet-relative view of a UTXO transaction. `sent` = value of our inputs,
13
+ * `received` = value of our outputs. Out: amount = what left to others
14
+ * (inputs − change − fee). In: amount = what we received.
15
+ */
16
+ export function utxoSummary(inputs, outputs, address) {
17
+ const ours = (io) => io.address === address;
18
+ const sent = inputs.filter(ours).reduce((s, io) => s + io.value, 0n);
19
+ const received = outputs.filter(ours).reduce((s, io) => s + io.value, 0n);
20
+ const otherOutputs = outputs.filter((io) => !ours(io) && io.address);
21
+ const otherInputs = inputs.filter((io) => !ours(io) && io.address);
22
+ if (sent > 0n) {
23
+ const toOthers = otherOutputs.reduce((s, io) => s + io.value, 0n);
24
+ if (toOthers === 0n) {
25
+ return { direction: "self", amount: amountOf(received, 8), from: address, to: address };
26
+ }
27
+ return {
28
+ direction: "out",
29
+ amount: amountOf(toOthers, 8),
30
+ from: address,
31
+ to: otherOutputs[0]?.address ?? null,
32
+ };
33
+ }
34
+ return {
35
+ direction: "in",
36
+ amount: amountOf(received, 8),
37
+ from: otherInputs[0]?.address ?? null,
38
+ to: address,
39
+ };
40
+ }
41
+ export function mapEsploraTx(tx, address, network) {
42
+ const hash = asString(tx.txid);
43
+ if (!hash)
44
+ return null;
45
+ const confirmed = tx.status?.confirmed === true;
46
+ const time = confirmed ? timeFrom(tx.status?.block_time) : null;
47
+ const inputs = (tx.vin ?? []).map((v) => ({
48
+ address: asString(v.prevout?.scriptpubkey_address),
49
+ value: toBigInt(v.prevout?.value ?? 0),
50
+ }));
51
+ const outputs = (tx.vout ?? []).map((v) => ({
52
+ address: asString(v.scriptpubkey_address),
53
+ value: toBigInt(v.value ?? 0),
54
+ }));
55
+ const feeRaw = toBigInt(tx.fee ?? 0);
56
+ const summary = utxoSummary(inputs, outputs, address);
57
+ const symbol = NATIVE_SYMBOL[network];
58
+ const item = {
59
+ hash,
60
+ timestamp: time?.iso ?? null,
61
+ block: confirmed && typeof tx.status?.block_height === "number" ? tx.status.block_height : null,
62
+ kind: "transfer",
63
+ direction: summary.direction,
64
+ status: confirmed ? "confirmed" : "pending",
65
+ from: summary.from,
66
+ to: summary.to,
67
+ asset: { symbol, contractAddress: null, decimals: 8 },
68
+ amount: summary.amount,
69
+ fee: summary.direction === "in" ? null : feeOf(feeRaw, 8, symbol),
70
+ };
71
+ return { ts: time?.ts ?? pendingTs(), item };
72
+ }
73
+ export function esploraProvider(baseUrl, address, network) {
74
+ const label = /mempool\.space/i.test(baseUrl)
75
+ ? "mempool.space"
76
+ : /blockstream/i.test(baseUrl)
77
+ ? "blockstream"
78
+ : /litecoinspace/i.test(baseUrl)
79
+ ? "litecoinspace"
80
+ : "esplora";
81
+ const source = {
82
+ name: "utxo",
83
+ async fetchPage(cursor, _pageSize, ctx) {
84
+ const url = cursor === null
85
+ ? `${baseUrl}/address/${encodeURIComponent(address)}/txs`
86
+ : `${baseUrl}/address/${encodeURIComponent(address)}/txs/chain/${encodeURIComponent(cursor)}`;
87
+ const rows = await httpJson(url, { method: "GET" }, { ...ctx.http, label: "history.esplora" });
88
+ if (!Array.isArray(rows))
89
+ throw new Error("esplora: unexpected response");
90
+ const items = rows.map((tx) => mapEsploraTx(tx, address, network)).filter((x) => x !== null);
91
+ // Esplora pages hold 25 confirmed txs; fewer means we reached the end.
92
+ const confirmed = rows.filter((tx) => tx.status?.confirmed === true);
93
+ const last = confirmed[confirmed.length - 1]?.txid;
94
+ const next = confirmed.length < 25 || !last ? null : last;
95
+ return { items, next };
96
+ },
97
+ };
98
+ return { label, sources: [source] };
99
+ }
100
+ //# sourceMappingURL=esplora.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"esplora.js","sourceRoot":"","sources":["../../../../src/api/history/providers/esplora.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,36 @@
1
+ /**
2
+ * Etherscan-compatible `module=account` API (Blockscout, Routescan/Snowtrace).
3
+ * Two newest-first sources: `txlist` (native transfers + contract calls made or
4
+ * received by the address) and `tokentx` (ERC-20 transfers). Page-based
5
+ * (`page` / `offset`), so a page cursor is just the page number.
6
+ */
7
+ import type { NetworkId } from "../../../networks.js";
8
+ import { type HistoryProvider, type SourceItem } from "../types.js";
9
+ interface EtherscanEnvelope {
10
+ status?: string;
11
+ message?: string;
12
+ result?: unknown;
13
+ }
14
+ export interface EtherscanTx {
15
+ blockNumber?: string;
16
+ timeStamp?: string;
17
+ hash?: string;
18
+ from?: string;
19
+ to?: string;
20
+ value?: string;
21
+ gasPrice?: string;
22
+ gasUsed?: string;
23
+ isError?: string;
24
+ txreceipt_status?: string;
25
+ input?: string;
26
+ contractAddress?: string;
27
+ /** tokentx only */
28
+ tokenSymbol?: string;
29
+ tokenDecimal?: string;
30
+ }
31
+ /** Empty result sets come back as status "0" with a "No … found" message; treat as []. */
32
+ export declare function unwrapEtherscanResult(res: EtherscanEnvelope, label: string): EtherscanTx[];
33
+ export declare function mapNativeTx(tx: EtherscanTx, address: string, network: NetworkId): SourceItem | null;
34
+ export declare function mapTokenTx(tx: EtherscanTx, address: string, network: NetworkId): SourceItem | null;
35
+ export declare function etherscanProvider(baseUrl: string, address: string, network: NetworkId): HistoryProvider;
36
+ export {};
@@ -0,0 +1,118 @@
1
+ /**
2
+ * Etherscan-compatible `module=account` API (Blockscout, Routescan/Snowtrace).
3
+ * Two newest-first sources: `txlist` (native transfers + contract calls made or
4
+ * received by the address) and `tokentx` (ERC-20 transfers). Page-based
5
+ * (`page` / `offset`), so a page cursor is just the page number.
6
+ */
7
+ import { NATIVE_SYMBOL } from "../../balances.js";
8
+ import { httpJson } from "../../http.js";
9
+ import { amountOf, asString, clampDecimals, directionOf, evmAddressMatcher, feeOf, timeFrom, tokenAsset, toBigInt, toBlockNumber, } from "../normalize.js";
10
+ import { HISTORY_MAX_LIMIT, } from "../types.js";
11
+ /** Empty result sets come back as status "0" with a "No … found" message; treat as []. */
12
+ export function unwrapEtherscanResult(res, label) {
13
+ if (Array.isArray(res.result))
14
+ return res.result;
15
+ const message = `${res.message ?? ""} ${typeof res.result === "string" ? res.result : ""}`;
16
+ if (/no (transactions|token transfers|records) found/i.test(message))
17
+ return [];
18
+ throw new Error(`${label}: ${message.trim() || "unexpected response"}`);
19
+ }
20
+ export function mapNativeTx(tx, address, network) {
21
+ const hash = asString(tx.hash);
22
+ if (!hash)
23
+ return null;
24
+ const time = timeFrom(tx.timeStamp);
25
+ const isSelf = evmAddressMatcher(address);
26
+ const from = asString(tx.from);
27
+ const to = asString(tx.to) ?? asString(tx.contractAddress);
28
+ const value = toBigInt(tx.value);
29
+ const input = tx.input ?? "0x";
30
+ const failed = tx.isError === "1" || tx.txreceipt_status === "0";
31
+ const direction = directionOf(from, to, isSelf);
32
+ const feeRaw = toBigInt(tx.gasUsed) * toBigInt(tx.gasPrice);
33
+ const symbol = NATIVE_SYMBOL[network];
34
+ const item = {
35
+ hash,
36
+ timestamp: time?.iso ?? null,
37
+ block: toBlockNumber(tx.blockNumber),
38
+ kind: value > 0n ? "transfer" : input.length > 2 ? "contract_call" : "other",
39
+ direction,
40
+ status: failed ? "failed" : "confirmed",
41
+ from,
42
+ to,
43
+ asset: { symbol, contractAddress: null, decimals: 18 },
44
+ amount: amountOf(value, 18),
45
+ fee: direction === "in" ? null : feeOf(feeRaw, 18, symbol),
46
+ };
47
+ return { ts: time?.ts ?? 0, item };
48
+ }
49
+ export function mapTokenTx(tx, address, network) {
50
+ const hash = asString(tx.hash);
51
+ const contract = asString(tx.contractAddress);
52
+ if (!hash || !contract)
53
+ return null;
54
+ const time = timeFrom(tx.timeStamp);
55
+ const isSelf = evmAddressMatcher(address);
56
+ const from = asString(tx.from);
57
+ const to = asString(tx.to);
58
+ const decimals = clampDecimals(tx.tokenDecimal, 18);
59
+ const direction = directionOf(from, to, isSelf);
60
+ const feeRaw = toBigInt(tx.gasUsed) * toBigInt(tx.gasPrice);
61
+ const item = {
62
+ hash,
63
+ timestamp: time?.iso ?? null,
64
+ block: toBlockNumber(tx.blockNumber),
65
+ kind: "token_transfer",
66
+ direction,
67
+ status: "confirmed",
68
+ from,
69
+ to,
70
+ asset: tokenAsset(tx.tokenSymbol, "TOKEN", contract, decimals),
71
+ amount: amountOf(toBigInt(tx.value), decimals),
72
+ fee: direction === "in" ? null : feeOf(feeRaw, 18, NATIVE_SYMBOL[network]),
73
+ };
74
+ return { ts: time?.ts ?? 0, item };
75
+ }
76
+ function pageNumber(cursor) {
77
+ if (cursor === null)
78
+ return 1;
79
+ const n = Number.parseInt(cursor, 10);
80
+ if (!Number.isInteger(n) || n < 1)
81
+ throw new Error("Invalid cursor for etherscan source.");
82
+ return n;
83
+ }
84
+ /**
85
+ * `page` only means something for a fixed `offset` (page size), and the
86
+ * cursor must survive a different `limit` on the next call — so the size
87
+ * requested by the merge is ignored and every page is PAGE_SIZE rows.
88
+ */
89
+ const PAGE_SIZE = HISTORY_MAX_LIMIT;
90
+ function makeSource(name, action, baseUrl, address, network, map) {
91
+ return {
92
+ name,
93
+ async fetchPage(cursor, _pageSize, ctx) {
94
+ const page = pageNumber(cursor);
95
+ const url = `${baseUrl}?module=account&action=${action}&address=${encodeURIComponent(address)}` +
96
+ `&page=${page}&offset=${PAGE_SIZE}&sort=desc`;
97
+ const res = await httpJson(url, { method: "GET" }, { ...ctx.http, label: `history.etherscan.${action}` });
98
+ const rows = unwrapEtherscanResult(res, `etherscan ${action}`);
99
+ const items = rows.map((tx) => map(tx, address, network)).filter((x) => x !== null);
100
+ return { items, next: rows.length < PAGE_SIZE ? null : String(page + 1) };
101
+ },
102
+ };
103
+ }
104
+ export function etherscanProvider(baseUrl, address, network) {
105
+ const label = /blockscout\.com/i.test(baseUrl)
106
+ ? "blockscout"
107
+ : /routescan\.io|snowtrace/i.test(baseUrl)
108
+ ? "routescan"
109
+ : "etherscan-compatible";
110
+ return {
111
+ label,
112
+ sources: [
113
+ makeSource("native", "txlist", baseUrl, address, network, mapNativeTx),
114
+ makeSource("erc20", "tokentx", baseUrl, address, network, mapTokenTx),
115
+ ],
116
+ };
117
+ }
118
+ //# sourceMappingURL=etherscan.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"etherscan.js","sourceRoot":"","sources":["../../../../src/api/history/providers/etherscan.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,27 @@
1
+ /**
2
+ * NodeReal BSC explorer backend (the API behind bsctrace.com). Undocumented and
3
+ * keyless; the only keyless address-scoped BSC history we found. Two sources:
4
+ * `external` (top-level transactions) and `20` (BEP-20 transfers). Page-based.
5
+ * Any non-2xx (including its odd HTTP 566) fails the source so the tool can
6
+ * report "unavailable" instead of an empty history.
7
+ */
8
+ import type { NetworkId } from "../../../networks.js";
9
+ import { type HistoryProvider, type SourceItem } from "../types.js";
10
+ export interface NodeRealTransfer {
11
+ category?: string;
12
+ blockNum?: string;
13
+ from?: string;
14
+ to?: string;
15
+ value?: string;
16
+ asset?: string;
17
+ hash?: string;
18
+ contractAddress?: string;
19
+ decimal?: string;
20
+ blockTimeStamp?: number;
21
+ gasPrice?: number;
22
+ gasUsed?: number;
23
+ receiptsStatus?: number;
24
+ input?: string;
25
+ }
26
+ export declare function mapNodeRealTransfer(row: NodeRealTransfer, address: string, network: NetworkId): SourceItem | null;
27
+ export declare function noderealProvider(baseUrl: string, address: string, network: NetworkId): HistoryProvider;
@@ -0,0 +1,93 @@
1
+ /**
2
+ * NodeReal BSC explorer backend (the API behind bsctrace.com). Undocumented and
3
+ * keyless; the only keyless address-scoped BSC history we found. Two sources:
4
+ * `external` (top-level transactions) and `20` (BEP-20 transfers). Page-based.
5
+ * Any non-2xx (including its odd HTTP 566) fails the source so the tool can
6
+ * report "unavailable" instead of an empty history.
7
+ */
8
+ import { NATIVE_SYMBOL } from "../../balances.js";
9
+ import { httpJson } from "../../http.js";
10
+ import { amountOf, asString, clampDecimals, directionOf, evmAddressMatcher, feeOf, timeFrom, tokenAsset, toBigInt, toBlockNumber, } from "../normalize.js";
11
+ import { HISTORY_MAX_LIMIT, } from "../types.js";
12
+ const ZERO_ADDRESS = "0x0000000000000000000000000000000000000000";
13
+ export function mapNodeRealTransfer(row, address, network) {
14
+ const hash = asString(row.hash);
15
+ if (!hash)
16
+ return null;
17
+ const time = timeFrom(row.blockTimeStamp);
18
+ const isSelf = evmAddressMatcher(address);
19
+ const from = asString(row.from);
20
+ const to = asString(row.to);
21
+ const direction = directionOf(from, to, isSelf);
22
+ const value = toBigInt(row.value);
23
+ const failed = row.receiptsStatus === 0;
24
+ const feeRaw = toBigInt(row.gasUsed) * toBigInt(row.gasPrice);
25
+ const nativeSymbol = NATIVE_SYMBOL[network];
26
+ const isToken = row.category === "20";
27
+ const contract = asString(row.contractAddress);
28
+ const item = {
29
+ hash,
30
+ timestamp: time?.iso ?? null,
31
+ block: toBlockNumber(row.blockNum),
32
+ kind: isToken
33
+ ? "token_transfer"
34
+ : value > 0n
35
+ ? "transfer"
36
+ : (row.input ?? "0x").length > 2
37
+ ? "contract_call"
38
+ : "other",
39
+ direction,
40
+ status: failed ? "failed" : "confirmed",
41
+ from,
42
+ to,
43
+ asset: isToken
44
+ ? tokenAsset(row.asset, "TOKEN", contract && contract !== ZERO_ADDRESS ? contract : null, clampDecimals(row.decimal, 18))
45
+ : { symbol: nativeSymbol, contractAddress: null, decimals: 18 },
46
+ amount: amountOf(value, isToken ? clampDecimals(row.decimal, 18) : 18),
47
+ fee: direction === "in" ? null : feeOf(feeRaw, 18, nativeSymbol),
48
+ };
49
+ return { ts: time?.ts ?? 0, item };
50
+ }
51
+ function pageNumber(cursor) {
52
+ if (cursor === null)
53
+ return 1;
54
+ const n = Number.parseInt(cursor, 10);
55
+ if (!Number.isInteger(n) || n < 1)
56
+ throw new Error("Invalid cursor for nodereal source.");
57
+ return n;
58
+ }
59
+ /**
60
+ * Page numbers only mean something for a fixed page size, and the cursor
61
+ * must survive a different `limit` on the next call — so the size requested
62
+ * by the merge is ignored and every page is PAGE_SIZE rows.
63
+ */
64
+ const PAGE_SIZE = HISTORY_MAX_LIMIT;
65
+ function makeSource(name, type, baseUrl, address, network) {
66
+ return {
67
+ name,
68
+ async fetchPage(cursor, _pageSize, ctx) {
69
+ const page = pageNumber(cursor);
70
+ const url = `${baseUrl}/tx/getAssetTransferByAddress?address=${encodeURIComponent(address)}` +
71
+ `&type=${type}&pageSize=${PAGE_SIZE}&page=${page}&order=desc`;
72
+ const res = await httpJson(url, { method: "GET" }, { ...ctx.http, label: `history.nodereal.${type}` });
73
+ if (res.code !== 0 || !res.data) {
74
+ throw new Error(`nodereal ${type}: ${res.msg ?? "unexpected response"}`);
75
+ }
76
+ const rows = res.data.list ?? [];
77
+ const items = rows
78
+ .map((row) => mapNodeRealTransfer(row, address, network))
79
+ .filter((x) => x !== null);
80
+ return { items, next: rows.length < PAGE_SIZE ? null : String(page + 1) };
81
+ },
82
+ };
83
+ }
84
+ export function noderealProvider(baseUrl, address, network) {
85
+ return {
86
+ label: "nodereal",
87
+ sources: [
88
+ makeSource("native", "external", baseUrl, address, network),
89
+ makeSource("bep20", "20", baseUrl, address, network),
90
+ ],
91
+ };
92
+ }
93
+ //# sourceMappingURL=nodereal.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"nodereal.js","sourceRoot":"","sources":["../../../../src/api/history/providers/nodereal.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAGH,OAAO,EAAE,aAAa,EAAE,MAAM,mBAAmB,CAAC;AAClD,OAAO,EAAE,QAAQ,EAAE,MAAM,eAAe,CAAC;AACzC,OAAO,EACL,QAAQ,EACR,QAAQ,EACR,aAAa,EACb,WAAW,EACX,iBAAiB,EACjB,KAAK,EACL,QAAQ,EACR,UAAU,EACV,QAAQ,EACR,aAAa,GACd,MAAM,iBAAiB,CAAC;AACzB,OAAO,EACL,iBAAiB,GAOlB,MAAM,aAAa,CAAC;AAyBrB,MAAM,YAAY,GAAG,4CAA4C,CAAC;AAElE,MAAM,UAAU,mBAAmB,CACjC,GAAqB,EACrB,OAAe,EACf,OAAkB;IAElB,MAAM,IAAI,GAAG,QAAQ,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC;IAChC,IAAI,CAAC,IAAI;QAAE,OAAO,IAAI,CAAC;IACvB,MAAM,IAAI,GAAG,QAAQ,CAAC,GAAG,CAAC,cAAc,CAAC,CAAC;IAC1C,MAAM,MAAM,GAAG,iBAAiB,CAAC,OAAO,CAAC,CAAC;IAC1C,MAAM,IAAI,GAAG,QAAQ,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC;IAChC,MAAM,EAAE,GAAG,QAAQ,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC;IAC5B,MAAM,SAAS,GAAG,WAAW,CAAC,IAAI,EAAE,EAAE,EAAE,MAAM,CAAC,CAAC;IAChD,MAAM,KAAK,GAAG,QAAQ,CAAC,GAAG,CAAC,KAAK,CAAC,CAAC;IAClC,MAAM,MAAM,GAAG,GAAG,CAAC,cAAc,KAAK,CAAC,CAAC;IACxC,MAAM,MAAM,GAAG,QAAQ,CAAC,GAAG,CAAC,OAAO,CAAC,GAAG,QAAQ,CAAC,GAAG,CAAC,QAAQ,CAAC,CAAC;IAC9D,MAAM,YAAY,GAAG,aAAa,CAAC,OAAO,CAAC,CAAC;IAC5C,MAAM,OAAO,GAAG,GAAG,CAAC,QAAQ,KAAK,IAAI,CAAC;IACtC,MAAM,QAAQ,GAAG,QAAQ,CAAC,GAAG,CAAC,eAAe,CAAC,CAAC;IAE/C,MAAM,IAAI,GAAgB;QACxB,IAAI;QACJ,SAAS,EAAE,IAAI,EAAE,GAAG,IAAI,IAAI;QAC5B,KAAK,EAAE,aAAa,CAAC,GAAG,CAAC,QAAQ,CAAC;QAClC,IAAI,EAAE,OAAO;YACX,CAAC,CAAC,gBAAgB;YAClB,CAAC,CAAC,KAAK,GAAG,EAAE;gBACV,CAAC,CAAC,UAAU;gBACZ,CAAC,CAAC,CAAC,GAAG,CAAC,KAAK,IAAI,IAAI,CAAC,CAAC,MAAM,GAAG,CAAC;oBAC9B,CAAC,CAAC,eAAe;oBACjB,CAAC,CAAC,OAAO;QACf,SAAS;QACT,MAAM,EAAE,MAAM,CAAC,CAAC,CAAC,QAAQ,CAAC,CAAC,CAAC,WAAW;QACvC,IAAI;QACJ,EAAE;QACF,KAAK,EAAE,OAAO;YACZ,CAAC,CAAC,UAAU,CACR,GAAG,CAAC,KAAK,EACT,OAAO,EACP,QAAQ,IAAI,QAAQ,KAAK,YAAY,CAAC,CAAC,CAAC,QAAQ,CAAC,CAAC,CAAC,IAAI,EACvD,aAAa,CAAC,GAAG,CAAC,OAAO,EAAE,EAAE,CAAC,CAC/B;YACH,CAAC,CAAC,EAAE,MAAM,EAAE,YAAY,EAAE,eAAe,EAAE,IAAI,EAAE,QAAQ,EAAE,EAAE,EAAE;QACjE,MAAM,EAAE,QAAQ,CAAC,KAAK,EAAE,OAAO,CAAC,CAAC,CAAC,aAAa,CAAC,GAAG,CAAC,OAAO,EAAE,EAAE,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC;QACtE,GAAG,EAAE,SAAS,KAAK,IAAI,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,KAAK,CAAC,MAAM,EAAE,EAAE,EAAE,YAAY,CAAC;KACjE,CAAC;IACF,OAAO,EAAE,EAAE,EAAE,IAAI,EAAE,EAAE,IAAI,CAAC,EAAE,IAAI,EAAE,CAAC;AACrC,CAAC;AAED,SAAS,UAAU,CAAC,MAAqB;IACvC,IAAI,MAAM,KAAK,IAAI;QAAE,OAAO,CAAC,CAAC;IAC9B,MAAM,CAAC,GAAG,MAAM,CAAC,QAAQ,CAAC,MAAM,EAAE,EAAE,CAAC,CAAC;IACtC,IAAI,CAAC,MAAM,CAAC,SAAS,CAAC,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC;QAAE,MAAM,IAAI,KAAK,CAAC,qCAAqC,CAAC,CAAC;IAC1F,OAAO,CAAC,CAAC;AACX,CAAC;AAED;;;;GAIG;AACH,MAAM,SAAS,GAAG,iBAAiB,CAAC;AAEpC,SAAS,UAAU,CAAC,IAAY,EAAE,IAAY,EAAE,OAAe,EAAE,OAAe,EAAE,OAAkB;IAClG,OAAO;QACL,IAAI;QACJ,KAAK,CAAC,SAAS,CAAC,MAAqB,EAAE,SAAiB,EAAE,GAAkB;YAC1E,MAAM,IAAI,GAAG,UAAU,CAAC,MAAM,CAAC,CAAC;YAChC,MAAM,GAAG,GACP,GAAG,OAAO,yCAAyC,kBAAkB,CAAC,OAAO,CAAC,EAAE;gBAChF,SAAS,IAAI,aAAa,SAAS,SAAS,IAAI,aAAa,CAAC;YAChE,MAAM,GAAG,GAAG,MAAM,QAAQ,CACxB,GAAG,EACH,EAAE,MAAM,EAAE,KAAK,EAAE,EACjB,EAAE,GAAG,GAAG,CAAC,IAAI,EAAE,KAAK,EAAE,oBAAoB,IAAI,EAAE,EAAE,CACnD,CAAC;YACF,IAAI,GAAG,CAAC,IAAI,KAAK,CAAC,IAAI,CAAC,GAAG,CAAC,IAAI,EAAE,CAAC;gBAChC,MAAM,IAAI,KAAK,CAAC,YAAY,IAAI,KAAK,GAAG,CAAC,GAAG,IAAI,qBAAqB,EAAE,CAAC,CAAC;YAC3E,CAAC;YACD,MAAM,IAAI,GAAG,GAAG,CAAC,IAAI,CAAC,IAAI,IAAI,EAAE,CAAC;YACjC,MAAM,KAAK,GAAG,IAAI;iBACf,GAAG,CAAC,CAAC,GAAG,EAAE,EAAE,CAAC,mBAAmB,CAAC,GAAG,EAAE,OAAO,EAAE,OAAO,CAAC,CAAC;iBACxD,MAAM,CAAC,CAAC,CAAC,EAAmB,EAAE,CAAC,CAAC,KAAK,IAAI,CAAC,CAAC;YAC9C,OAAO,EAAE,KAAK,EAAE,IAAI,EAAE,IAAI,CAAC,MAAM,GAAG,SAAS,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,MAAM,CAAC,IAAI,GAAG,CAAC,CAAC,EAAE,CAAC;QAC5E,CAAC;KACF,CAAC;AACJ,CAAC;AAED,MAAM,UAAU,gBAAgB,CAAC,OAAe,EAAE,OAAe,EAAE,OAAkB;IACnF,OAAO;QACL,KAAK,EAAE,UAAU;QACjB,OAAO,EAAE;YACP,UAAU,CAAC,QAAQ,EAAE,UAAU,EAAE,OAAO,EAAE,OAAO,EAAE,OAAO,CAAC;YAC3D,UAAU,CAAC,OAAO,EAAE,IAAI,EAAE,OAAO,EAAE,OAAO,EAAE,OAAO,CAAC;SACrD;KACF,CAAC;AACJ,CAAC"}
@@ -0,0 +1,47 @@
1
+ /**
2
+ * Solana JSON-RPC: getSignaturesForAddress → one batched getTransaction call
3
+ * (jsonParsed). Amounts are the wallet's lamport / token balance deltas from
4
+ * the transaction meta, so swaps and program interactions show what actually
5
+ * left or arrived. Cursor = last signature (`before`). Memos are deliberately
6
+ * not surfaced: they are free-form text from strangers.
7
+ */
8
+ import type { HistoryProvider, SourceItem } from "../types.js";
9
+ interface SignatureInfo {
10
+ signature?: string;
11
+ slot?: number;
12
+ blockTime?: number | null;
13
+ err?: unknown;
14
+ }
15
+ interface TokenBalance {
16
+ accountIndex?: number;
17
+ mint?: string;
18
+ owner?: string;
19
+ uiTokenAmount?: {
20
+ amount?: string;
21
+ decimals?: number;
22
+ };
23
+ }
24
+ export interface SolanaTx {
25
+ slot?: number;
26
+ blockTime?: number | null;
27
+ meta?: {
28
+ err?: unknown;
29
+ fee?: number;
30
+ preBalances?: number[];
31
+ postBalances?: number[];
32
+ preTokenBalances?: TokenBalance[];
33
+ postTokenBalances?: TokenBalance[];
34
+ } | null;
35
+ transaction?: {
36
+ message?: {
37
+ accountKeys?: Array<{
38
+ pubkey?: string;
39
+ signer?: boolean;
40
+ } | string>;
41
+ };
42
+ };
43
+ }
44
+ /** Wallet-relative items for one transaction (one per asset that moved). */
45
+ export declare function mapSolanaTx(signature: string, tx: SolanaTx | null, info: SignatureInfo, address: string): SourceItem[];
46
+ export declare function solanaProvider(rpcUrl: string, address: string): HistoryProvider;
47
+ export {};
@@ -0,0 +1,211 @@
1
+ /**
2
+ * Solana JSON-RPC: getSignaturesForAddress → one batched getTransaction call
3
+ * (jsonParsed). Amounts are the wallet's lamport / token balance deltas from
4
+ * the transaction meta, so swaps and program interactions show what actually
5
+ * left or arrived. Cursor = last signature (`before`). Memos are deliberately
6
+ * not surfaced: they are free-form text from strangers.
7
+ */
8
+ import { httpJson } from "../../http.js";
9
+ import { amountOf, asString, feeOf, pendingTs, sanitizeSymbol, timeFrom, toBigInt } from "../normalize.js";
10
+ function keyOf(entry) {
11
+ if (typeof entry === "string")
12
+ return entry;
13
+ return asString(entry?.pubkey);
14
+ }
15
+ /** Wallet-relative items for one transaction (one per asset that moved). */
16
+ export function mapSolanaTx(signature, tx, info, address) {
17
+ const time = timeFrom(tx?.blockTime ?? info.blockTime ?? undefined);
18
+ const ts = time?.ts ?? pendingTs();
19
+ const failed = (tx?.meta?.err ?? info.err) != null;
20
+ const base = {
21
+ hash: signature,
22
+ timestamp: time?.iso ?? null,
23
+ block: typeof (tx?.slot ?? info.slot) === "number" ? (tx?.slot ?? info.slot) : null,
24
+ status: failed ? "failed" : "confirmed",
25
+ };
26
+ const meta = tx?.meta;
27
+ const keys = (tx?.transaction?.message?.accountKeys ?? []).map(keyOf);
28
+ const selfIndex = keys.indexOf(address);
29
+ if (!meta || selfIndex === -1) {
30
+ return [
31
+ {
32
+ ts,
33
+ item: {
34
+ ...base,
35
+ kind: "other",
36
+ direction: "in",
37
+ from: null,
38
+ to: address,
39
+ asset: { symbol: "SOL", contractAddress: null, decimals: 9 },
40
+ amount: amountOf(0n, 9),
41
+ fee: null,
42
+ },
43
+ },
44
+ ];
45
+ }
46
+ const feePayer = keys[0] === address;
47
+ const fee = toBigInt(meta.fee ?? 0);
48
+ const feeInfo = feePayer ? feeOf(fee, 9, "SOL") : null;
49
+ const items = [];
50
+ // SPL deltas for token accounts owned by this wallet, per mint.
51
+ const tokenDelta = new Map();
52
+ const addTokens = (balances, sign) => {
53
+ for (const b of balances ?? []) {
54
+ if (b.owner !== address || !b.mint)
55
+ continue;
56
+ const amount = toBigInt(b.uiTokenAmount?.amount ?? "0") * sign;
57
+ const prev = tokenDelta.get(b.mint) ?? { delta: 0n, decimals: b.uiTokenAmount?.decimals ?? 0 };
58
+ tokenDelta.set(b.mint, { delta: prev.delta + amount, decimals: prev.decimals });
59
+ }
60
+ };
61
+ addTokens(meta.preTokenBalances, -1n);
62
+ addTokens(meta.postTokenBalances, 1n);
63
+ // Counterparty owners for the same mint (opposite sign).
64
+ const ownerDeltaByMint = new Map();
65
+ const addOwners = (balances, sign) => {
66
+ for (const b of balances ?? []) {
67
+ if (!b.mint || !b.owner || b.owner === address)
68
+ continue;
69
+ const byOwner = ownerDeltaByMint.get(b.mint) ?? new Map();
70
+ byOwner.set(b.owner, (byOwner.get(b.owner) ?? 0n) + toBigInt(b.uiTokenAmount?.amount ?? "0") * sign);
71
+ ownerDeltaByMint.set(b.mint, byOwner);
72
+ }
73
+ };
74
+ addOwners(meta.preTokenBalances, -1n);
75
+ addOwners(meta.postTokenBalances, 1n);
76
+ for (const [mint, { delta, decimals }] of tokenDelta) {
77
+ if (delta === 0n)
78
+ continue;
79
+ const direction = delta < 0n ? "out" : "in";
80
+ let counterparty = null;
81
+ let best = 0n;
82
+ for (const [owner, d] of ownerDeltaByMint.get(mint) ?? []) {
83
+ if (direction === "out" ? d > best : d < best) {
84
+ best = d;
85
+ counterparty = owner;
86
+ }
87
+ }
88
+ items.push({
89
+ ts,
90
+ item: {
91
+ ...base,
92
+ kind: "token_transfer",
93
+ direction,
94
+ from: direction === "out" ? address : counterparty,
95
+ to: direction === "out" ? counterparty : address,
96
+ asset: { symbol: sanitizeSymbol(null, "SPL"), contractAddress: mint, decimals },
97
+ amount: amountOf(delta, decimals),
98
+ fee: items.length === 0 && direction === "out" ? feeInfo : null,
99
+ },
100
+ });
101
+ }
102
+ const pre = toBigInt(meta.preBalances?.[selfIndex] ?? 0);
103
+ const post = toBigInt(meta.postBalances?.[selfIndex] ?? 0);
104
+ const solDelta = post - pre + (feePayer ? fee : 0n);
105
+ if (solDelta !== 0n) {
106
+ const direction = solDelta < 0n ? "out" : "in";
107
+ let counterparty = null;
108
+ let best = 0n;
109
+ keys.forEach((key, i) => {
110
+ if (!key || i === selfIndex)
111
+ return;
112
+ const d = toBigInt(meta.postBalances?.[i] ?? 0) - toBigInt(meta.preBalances?.[i] ?? 0);
113
+ if (direction === "out" ? d > best : d < best) {
114
+ best = d;
115
+ counterparty = key;
116
+ }
117
+ });
118
+ items.push({
119
+ ts,
120
+ item: {
121
+ ...base,
122
+ kind: "transfer",
123
+ direction,
124
+ from: direction === "out" ? address : counterparty,
125
+ to: direction === "out" ? counterparty : address,
126
+ asset: { symbol: "SOL", contractAddress: null, decimals: 9 },
127
+ amount: amountOf(solDelta, 9),
128
+ fee: items.length === 0 && direction === "out" ? feeInfo : null,
129
+ },
130
+ });
131
+ }
132
+ if (items.length === 0) {
133
+ items.push({
134
+ ts,
135
+ item: {
136
+ ...base,
137
+ kind: "contract_call",
138
+ direction: feePayer ? "out" : "in",
139
+ from: feePayer ? address : keys[0] ?? null,
140
+ to: feePayer ? null : address,
141
+ asset: { symbol: "SOL", contractAddress: null, decimals: 9 },
142
+ amount: amountOf(0n, 9),
143
+ fee: feeInfo,
144
+ },
145
+ });
146
+ }
147
+ return items;
148
+ }
149
+ const TX_CONCURRENCY = 5;
150
+ export function solanaProvider(rpcUrl, address) {
151
+ const label = /publicnode/i.test(rpcUrl) ? "solana-publicnode" : "solana-rpc";
152
+ const source = {
153
+ name: "sol",
154
+ async fetchPage(cursor, pageSize, ctx) {
155
+ const opts = { ...ctx.http, retry: true };
156
+ const sigRes = await httpJson(rpcUrl, {
157
+ method: "POST",
158
+ headers: { "Content-Type": "application/json" },
159
+ body: JSON.stringify({
160
+ jsonrpc: "2.0",
161
+ id: 1,
162
+ method: "getSignaturesForAddress",
163
+ params: [address, { limit: pageSize, ...(cursor ? { before: cursor } : {}) }],
164
+ }),
165
+ }, { ...opts, label: "history.solana.signatures" });
166
+ if (sigRes.error)
167
+ throw new Error(`solana getSignaturesForAddress: ${sigRes.error.message}`);
168
+ const infos = (sigRes.result ?? []).filter((s) => typeof s.signature === "string");
169
+ if (infos.length === 0)
170
+ return { items: [], next: null };
171
+ // One getTransaction per request with bounded concurrency: publicnode
172
+ // rejects batches with more than one getTransaction, and mainnet-beta
173
+ // answers big batches with per-item 429s. Any failed detail fails the
174
+ // page (→ next RPC in the chain) instead of degrading rows to "other".
175
+ const details = new Array(infos.length).fill(null);
176
+ let nextIndex = 0;
177
+ const worker = async () => {
178
+ for (;;) {
179
+ const i = nextIndex++;
180
+ if (i >= infos.length)
181
+ return;
182
+ const res = await httpJson(rpcUrl, {
183
+ method: "POST",
184
+ headers: { "Content-Type": "application/json" },
185
+ body: JSON.stringify({
186
+ jsonrpc: "2.0",
187
+ id: i,
188
+ method: "getTransaction",
189
+ params: [
190
+ infos[i].signature,
191
+ { encoding: "jsonParsed", maxSupportedTransactionVersion: 0, commitment: "confirmed" },
192
+ ],
193
+ }),
194
+ }, { ...opts, retries: 0, label: "history.solana.transaction" });
195
+ if (res.error)
196
+ throw new Error(`solana getTransaction: ${res.error.message}`);
197
+ details[i] = res.result ?? null;
198
+ }
199
+ };
200
+ await Promise.all(Array.from({ length: Math.min(TX_CONCURRENCY, infos.length) }, worker));
201
+ const items = [];
202
+ infos.forEach((info, i) => {
203
+ items.push(...mapSolanaTx(info.signature, details[i], info, address));
204
+ });
205
+ const last = infos[infos.length - 1].signature;
206
+ return { items, next: infos.length < pageSize ? null : last };
207
+ },
208
+ };
209
+ return { label, sources: [source] };
210
+ }
211
+ //# sourceMappingURL=solana.js.map