@wopr-network/crypto-plugins 1.0.1

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 (211) hide show
  1. package/.github/workflows/ci.yml +33 -0
  2. package/.github/workflows/publish.yml +12 -0
  3. package/biome.json +23 -0
  4. package/dist/__tests__/bitcoin-encoder.test.d.ts +2 -0
  5. package/dist/__tests__/bitcoin-encoder.test.d.ts.map +1 -0
  6. package/dist/__tests__/bitcoin-encoder.test.js +97 -0
  7. package/dist/__tests__/bitcoin-encoder.test.js.map +1 -0
  8. package/dist/__tests__/dogecoin-encoder.test.d.ts +2 -0
  9. package/dist/__tests__/dogecoin-encoder.test.d.ts.map +1 -0
  10. package/dist/__tests__/dogecoin-encoder.test.js +57 -0
  11. package/dist/__tests__/dogecoin-encoder.test.js.map +1 -0
  12. package/dist/__tests__/litecoin-encoder.test.d.ts +2 -0
  13. package/dist/__tests__/litecoin-encoder.test.d.ts.map +1 -0
  14. package/dist/__tests__/litecoin-encoder.test.js +44 -0
  15. package/dist/__tests__/litecoin-encoder.test.js.map +1 -0
  16. package/dist/__tests__/registry.test.d.ts +2 -0
  17. package/dist/__tests__/registry.test.d.ts.map +1 -0
  18. package/dist/__tests__/registry.test.js +75 -0
  19. package/dist/__tests__/registry.test.js.map +1 -0
  20. package/dist/__tests__/rpc.test.d.ts +2 -0
  21. package/dist/__tests__/rpc.test.d.ts.map +1 -0
  22. package/dist/__tests__/rpc.test.js +31 -0
  23. package/dist/__tests__/rpc.test.js.map +1 -0
  24. package/dist/__tests__/solana-encoder.test.d.ts +2 -0
  25. package/dist/__tests__/solana-encoder.test.d.ts.map +1 -0
  26. package/dist/__tests__/solana-encoder.test.js +85 -0
  27. package/dist/__tests__/solana-encoder.test.js.map +1 -0
  28. package/dist/__tests__/solana-watcher.test.d.ts +2 -0
  29. package/dist/__tests__/solana-watcher.test.d.ts.map +1 -0
  30. package/dist/__tests__/solana-watcher.test.js +281 -0
  31. package/dist/__tests__/solana-watcher.test.js.map +1 -0
  32. package/dist/__tests__/sweep-key-parity.test.d.ts +2 -0
  33. package/dist/__tests__/sweep-key-parity.test.d.ts.map +1 -0
  34. package/dist/__tests__/sweep-key-parity.test.js +236 -0
  35. package/dist/__tests__/sweep-key-parity.test.js.map +1 -0
  36. package/dist/__tests__/tron-encoder.test.d.ts +2 -0
  37. package/dist/__tests__/tron-encoder.test.d.ts.map +1 -0
  38. package/dist/__tests__/tron-encoder.test.js +93 -0
  39. package/dist/__tests__/tron-encoder.test.js.map +1 -0
  40. package/dist/__tests__/utxo-watcher.test.d.ts +2 -0
  41. package/dist/__tests__/utxo-watcher.test.d.ts.map +1 -0
  42. package/dist/__tests__/utxo-watcher.test.js +218 -0
  43. package/dist/__tests__/utxo-watcher.test.js.map +1 -0
  44. package/dist/bitcoin/encoder.d.ts +15 -0
  45. package/dist/bitcoin/encoder.d.ts.map +1 -0
  46. package/dist/bitcoin/encoder.js +286 -0
  47. package/dist/bitcoin/encoder.js.map +1 -0
  48. package/dist/bitcoin/index.d.ts +4 -0
  49. package/dist/bitcoin/index.d.ts.map +1 -0
  50. package/dist/bitcoin/index.js +20 -0
  51. package/dist/bitcoin/index.js.map +1 -0
  52. package/dist/dogecoin/encoder.d.ts +19 -0
  53. package/dist/dogecoin/encoder.d.ts.map +1 -0
  54. package/dist/dogecoin/encoder.js +145 -0
  55. package/dist/dogecoin/encoder.js.map +1 -0
  56. package/dist/dogecoin/index.d.ts +4 -0
  57. package/dist/dogecoin/index.d.ts.map +1 -0
  58. package/dist/dogecoin/index.js +20 -0
  59. package/dist/dogecoin/index.js.map +1 -0
  60. package/dist/evm/encoder.d.ts +7 -0
  61. package/dist/evm/encoder.d.ts.map +1 -0
  62. package/dist/evm/encoder.js +43 -0
  63. package/dist/evm/encoder.js.map +1 -0
  64. package/dist/evm/eth-watcher.d.ts +38 -0
  65. package/dist/evm/eth-watcher.d.ts.map +1 -0
  66. package/dist/evm/eth-watcher.js +138 -0
  67. package/dist/evm/eth-watcher.js.map +1 -0
  68. package/dist/evm/index.d.ts +16 -0
  69. package/dist/evm/index.d.ts.map +1 -0
  70. package/dist/evm/index.js +34 -0
  71. package/dist/evm/index.js.map +1 -0
  72. package/dist/evm/types.d.ts +43 -0
  73. package/dist/evm/types.d.ts.map +1 -0
  74. package/dist/evm/types.js +101 -0
  75. package/dist/evm/types.js.map +1 -0
  76. package/dist/evm/watcher.d.ts +42 -0
  77. package/dist/evm/watcher.d.ts.map +1 -0
  78. package/dist/evm/watcher.js +162 -0
  79. package/dist/evm/watcher.js.map +1 -0
  80. package/dist/index.d.ts +7 -0
  81. package/dist/index.d.ts.map +1 -0
  82. package/dist/index.js +7 -0
  83. package/dist/index.js.map +1 -0
  84. package/dist/litecoin/encoder.d.ts +8 -0
  85. package/dist/litecoin/encoder.d.ts.map +1 -0
  86. package/dist/litecoin/encoder.js +16 -0
  87. package/dist/litecoin/encoder.js.map +1 -0
  88. package/dist/litecoin/index.d.ts +4 -0
  89. package/dist/litecoin/index.d.ts.map +1 -0
  90. package/dist/litecoin/index.js +20 -0
  91. package/dist/litecoin/index.js.map +1 -0
  92. package/dist/shared/test-helpers/index.d.ts +9 -0
  93. package/dist/shared/test-helpers/index.d.ts.map +1 -0
  94. package/dist/shared/test-helpers/index.js +30 -0
  95. package/dist/shared/test-helpers/index.js.map +1 -0
  96. package/dist/shared/utxo/index.d.ts +5 -0
  97. package/dist/shared/utxo/index.d.ts.map +1 -0
  98. package/dist/shared/utxo/index.js +3 -0
  99. package/dist/shared/utxo/index.js.map +1 -0
  100. package/dist/shared/utxo/rpc.d.ts +24 -0
  101. package/dist/shared/utxo/rpc.d.ts.map +1 -0
  102. package/dist/shared/utxo/rpc.js +75 -0
  103. package/dist/shared/utxo/rpc.js.map +1 -0
  104. package/dist/shared/utxo/types.d.ts +40 -0
  105. package/dist/shared/utxo/types.d.ts.map +1 -0
  106. package/dist/shared/utxo/types.js +2 -0
  107. package/dist/shared/utxo/types.js.map +1 -0
  108. package/dist/shared/utxo/watcher.d.ts +55 -0
  109. package/dist/shared/utxo/watcher.d.ts.map +1 -0
  110. package/dist/shared/utxo/watcher.js +150 -0
  111. package/dist/shared/utxo/watcher.js.map +1 -0
  112. package/dist/solana/encoder.d.ts +13 -0
  113. package/dist/solana/encoder.d.ts.map +1 -0
  114. package/dist/solana/encoder.js +62 -0
  115. package/dist/solana/encoder.js.map +1 -0
  116. package/dist/solana/index.d.ts +17 -0
  117. package/dist/solana/index.d.ts.map +1 -0
  118. package/dist/solana/index.js +32 -0
  119. package/dist/solana/index.js.map +1 -0
  120. package/dist/solana/sweeper.d.ts +47 -0
  121. package/dist/solana/sweeper.d.ts.map +1 -0
  122. package/dist/solana/sweeper.js +151 -0
  123. package/dist/solana/sweeper.js.map +1 -0
  124. package/dist/solana/types.d.ts +49 -0
  125. package/dist/solana/types.d.ts.map +1 -0
  126. package/dist/solana/types.js +2 -0
  127. package/dist/solana/types.js.map +1 -0
  128. package/dist/solana/watcher.d.ts +59 -0
  129. package/dist/solana/watcher.d.ts.map +1 -0
  130. package/dist/solana/watcher.js +251 -0
  131. package/dist/solana/watcher.js.map +1 -0
  132. package/dist/sweep/evm-sweeper.d.ts +31 -0
  133. package/dist/sweep/evm-sweeper.d.ts.map +1 -0
  134. package/dist/sweep/evm-sweeper.js +229 -0
  135. package/dist/sweep/evm-sweeper.js.map +1 -0
  136. package/dist/sweep/index.d.ts +22 -0
  137. package/dist/sweep/index.d.ts.map +1 -0
  138. package/dist/sweep/index.js +290 -0
  139. package/dist/sweep/index.js.map +1 -0
  140. package/dist/sweep/tron-sweeper.d.ts +40 -0
  141. package/dist/sweep/tron-sweeper.d.ts.map +1 -0
  142. package/dist/sweep/tron-sweeper.js +363 -0
  143. package/dist/sweep/tron-sweeper.js.map +1 -0
  144. package/dist/sweep/utxo-sweeper.d.ts +14 -0
  145. package/dist/sweep/utxo-sweeper.d.ts.map +1 -0
  146. package/dist/sweep/utxo-sweeper.js +13 -0
  147. package/dist/sweep/utxo-sweeper.js.map +1 -0
  148. package/dist/tron/address-convert.d.ts +15 -0
  149. package/dist/tron/address-convert.d.ts.map +1 -0
  150. package/dist/tron/address-convert.js +95 -0
  151. package/dist/tron/address-convert.js.map +1 -0
  152. package/dist/tron/encoder.d.ts +20 -0
  153. package/dist/tron/encoder.d.ts.map +1 -0
  154. package/dist/tron/encoder.js +67 -0
  155. package/dist/tron/encoder.js.map +1 -0
  156. package/dist/tron/index.d.ts +6 -0
  157. package/dist/tron/index.d.ts.map +1 -0
  158. package/dist/tron/index.js +20 -0
  159. package/dist/tron/index.js.map +1 -0
  160. package/dist/tron/sha256.d.ts +6 -0
  161. package/dist/tron/sha256.d.ts.map +1 -0
  162. package/dist/tron/sha256.js +90 -0
  163. package/dist/tron/sha256.js.map +1 -0
  164. package/dist/tron/watcher.d.ts +42 -0
  165. package/dist/tron/watcher.d.ts.map +1 -0
  166. package/dist/tron/watcher.js +168 -0
  167. package/dist/tron/watcher.js.map +1 -0
  168. package/package.json +47 -0
  169. package/src/__tests__/bitcoin-encoder.test.ts +115 -0
  170. package/src/__tests__/dogecoin-encoder.test.ts +66 -0
  171. package/src/__tests__/litecoin-encoder.test.ts +51 -0
  172. package/src/__tests__/registry.test.ts +91 -0
  173. package/src/__tests__/rpc.test.ts +36 -0
  174. package/src/__tests__/solana-encoder.test.ts +103 -0
  175. package/src/__tests__/solana-watcher.test.ts +316 -0
  176. package/src/__tests__/sweep-key-parity.test.ts +302 -0
  177. package/src/__tests__/tron-encoder.test.ts +108 -0
  178. package/src/__tests__/utxo-watcher.test.ts +252 -0
  179. package/src/bitcoin/encoder.ts +320 -0
  180. package/src/bitcoin/index.ts +23 -0
  181. package/src/dogecoin/encoder.ts +161 -0
  182. package/src/dogecoin/index.ts +23 -0
  183. package/src/evm/encoder.ts +49 -0
  184. package/src/evm/eth-watcher.ts +168 -0
  185. package/src/evm/index.ts +46 -0
  186. package/src/evm/types.ts +146 -0
  187. package/src/evm/watcher.ts +189 -0
  188. package/src/index.ts +21 -0
  189. package/src/litecoin/encoder.ts +18 -0
  190. package/src/litecoin/index.ts +23 -0
  191. package/src/shared/test-helpers/index.ts +36 -0
  192. package/src/shared/utxo/index.ts +12 -0
  193. package/src/shared/utxo/rpc.ts +80 -0
  194. package/src/shared/utxo/types.ts +43 -0
  195. package/src/shared/utxo/watcher.ts +195 -0
  196. package/src/solana/encoder.ts +72 -0
  197. package/src/solana/index.ts +36 -0
  198. package/src/solana/sweeper.ts +196 -0
  199. package/src/solana/types.ts +52 -0
  200. package/src/solana/watcher.ts +282 -0
  201. package/src/sweep/evm-sweeper.ts +296 -0
  202. package/src/sweep/index.ts +353 -0
  203. package/src/sweep/tron-sweeper.ts +467 -0
  204. package/src/sweep/utxo-sweeper.ts +23 -0
  205. package/src/tron/address-convert.ts +91 -0
  206. package/src/tron/encoder.ts +74 -0
  207. package/src/tron/index.ts +23 -0
  208. package/src/tron/sha256.ts +100 -0
  209. package/src/tron/watcher.ts +208 -0
  210. package/tsconfig.json +17 -0
  211. package/vitest.config.ts +8 -0
@@ -0,0 +1,353 @@
1
+ #!/usr/bin/env node
2
+
3
+ /**
4
+ * Unified crypto sweep CLI -- consolidates deposits from all chains to treasury.
5
+ *
6
+ * Reads mnemonic from stdin, fetches chain config from chain server,
7
+ * dispatches to per-chain sweep strategies.
8
+ *
9
+ * Usage:
10
+ * openssl enc -aes-256-cbc -pbkdf2 -iter 100000 -d -pass pass:<passphrase> \
11
+ * -in "/mnt/g/My Drive/paperclip-wallet.enc" \
12
+ * | CRYPTO_SERVICE_URL=http://167.71.118.221:3100 \
13
+ * CRYPTO_SERVICE_KEY=sk-chain-2026 \
14
+ * crypto-sweep
15
+ *
16
+ * Env vars:
17
+ * CRYPTO_SERVICE_URL -- Chain server URL (required)
18
+ * CRYPTO_SERVICE_KEY -- Chain server auth key (optional)
19
+ * SWEEP_DRY_RUN -- set to "false" to actually broadcast (default: true)
20
+ * MAX_ADDRESSES -- how many deposit addresses to scan (default: 200)
21
+ */
22
+
23
+ import { secp256k1 } from "@noble/curves/secp256k1.js";
24
+ import { keccak_256 } from "@noble/hashes/sha3.js";
25
+ import { HDKey } from "@scure/bip32";
26
+ import * as bip39 from "@scure/bip39";
27
+ import { wordlist } from "@scure/bip39/wordlists/english.js";
28
+ import type { KeyPair, SweepResult } from "@wopr-network/platform-core/crypto-plugin";
29
+ import { privateKeyToAccount } from "viem/accounts";
30
+ import { sha256 } from "../tron/sha256.js";
31
+ import { EvmSweeper, type EvmToken } from "./evm-sweeper.js";
32
+ import { TronSweeper, type TronToken } from "./tron-sweeper.js";
33
+ import { UtxoSweeper } from "./utxo-sweeper.js";
34
+
35
+ // --- Config ---
36
+
37
+ const CRYPTO_SERVICE_URL = process.env.CRYPTO_SERVICE_URL;
38
+ const CRYPTO_SERVICE_KEY = process.env.CRYPTO_SERVICE_KEY;
39
+ const DRY_RUN = process.env.SWEEP_DRY_RUN !== "false";
40
+ const MAX_INDEX = Number(process.env.MAX_ADDRESSES ?? "200");
41
+
42
+ // --- Chain server types ---
43
+
44
+ interface ChainMethod {
45
+ id: string;
46
+ token: string;
47
+ chain: string;
48
+ decimals: number;
49
+ contractAddress: string | null;
50
+ displayName: string;
51
+ coin_type: number;
52
+ curve: string;
53
+ encoding: string;
54
+ encoding_params: Record<string, string>;
55
+ rpc_url?: string;
56
+ rpc_headers?: Record<string, string>;
57
+ }
58
+
59
+ // Coin type to BIP-44 path and chain family
60
+ const COIN_TYPE_FAMILIES: Record<number, string> = {
61
+ 0: "utxo", // BTC
62
+ 2: "utxo", // LTC
63
+ 3: "utxo", // DOGE
64
+ 60: "evm", // ETH / EVM chains
65
+ 195: "tron", // TRX
66
+ 501: "solana", // SOL
67
+ };
68
+
69
+ // --- Address derivation ---
70
+
71
+ // Base58 for Tron addresses
72
+ const BASE58_ALPHABET = "123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz";
73
+
74
+ function base58encode(data: Uint8Array): string {
75
+ let num = 0n;
76
+ for (const byte of data) num = num * 256n + BigInt(byte);
77
+ let encoded = "";
78
+ while (num > 0n) {
79
+ encoded = BASE58_ALPHABET[Number(num % 58n)] + encoded;
80
+ num = num / 58n;
81
+ }
82
+ for (const byte of data) {
83
+ if (byte !== 0) break;
84
+ encoded = `1${encoded}`;
85
+ }
86
+ return encoded;
87
+ }
88
+
89
+ // secp256k1.ProjectivePoint for point decompression
90
+ const ProjectivePoint = (
91
+ secp256k1 as unknown as {
92
+ ProjectivePoint: {
93
+ fromHex(hex: string): { toRawBytes(compressed: boolean): Uint8Array };
94
+ };
95
+ }
96
+ ).ProjectivePoint;
97
+
98
+ function toHex(data: Uint8Array): string {
99
+ return Array.from(data, (b) => b.toString(16).padStart(2, "0")).join("");
100
+ }
101
+
102
+ function pubkeyToTronAddress(pubkey: Uint8Array): string {
103
+ const uncompressed: Uint8Array = ProjectivePoint.fromHex(toHex(pubkey)).toRawBytes(false);
104
+ const hash = keccak_256(uncompressed.slice(1));
105
+ const addressBytes = hash.slice(-20);
106
+ const payload = new Uint8Array(21);
107
+ payload[0] = 0x41;
108
+ payload.set(addressBytes, 1);
109
+ const checksum = sha256(sha256(payload));
110
+ const full = new Uint8Array(25);
111
+ full.set(payload);
112
+ full.set(checksum.slice(0, 4), 21);
113
+ return base58encode(full);
114
+ }
115
+
116
+ function deriveKeyPairs(
117
+ master: HDKey,
118
+ coinType: number,
119
+ family: string,
120
+ maxIndex: number,
121
+ chainIndex: number,
122
+ ): KeyPair[] {
123
+ const account = master.derive(`m/44'/${coinType}'/0'`);
124
+ const chain = account.deriveChild(chainIndex);
125
+ const keys: KeyPair[] = [];
126
+
127
+ for (let i = 0; i < maxIndex; i++) {
128
+ const child = chain.deriveChild(i);
129
+ if (!child.privateKey || !child.publicKey) continue;
130
+
131
+ let address: string;
132
+ if (family === "evm") {
133
+ const privHex = `0x${toHex(child.privateKey)}` as `0x${string}`;
134
+ address = privateKeyToAccount(privHex).address;
135
+ } else if (family === "tron") {
136
+ address = pubkeyToTronAddress(child.publicKey);
137
+ } else {
138
+ // UTXO / other -- use hex pubkey as placeholder address
139
+ address = toHex(child.publicKey);
140
+ }
141
+
142
+ keys.push({
143
+ privateKey: child.privateKey,
144
+ publicKey: child.publicKey,
145
+ address,
146
+ index: i,
147
+ });
148
+ }
149
+
150
+ return keys;
151
+ }
152
+
153
+ function deriveTreasuryAddress(master: HDKey, coinType: number, family: string): KeyPair {
154
+ // Treasury = internal chain (1), index 0
155
+ const account = master.derive(`m/44'/${coinType}'/0'`);
156
+ const chain = account.deriveChild(1);
157
+ const child = chain.deriveChild(0);
158
+ if (!child.privateKey || !child.publicKey) throw new Error(`Cannot derive treasury key for coin_type ${coinType}`);
159
+
160
+ let address: string;
161
+ if (family === "evm") {
162
+ const privHex = `0x${toHex(child.privateKey)}` as `0x${string}`;
163
+ address = privateKeyToAccount(privHex).address;
164
+ } else if (family === "tron") {
165
+ address = pubkeyToTronAddress(child.publicKey);
166
+ } else {
167
+ address = toHex(child.publicKey);
168
+ }
169
+
170
+ return {
171
+ privateKey: child.privateKey,
172
+ publicKey: child.publicKey,
173
+ address,
174
+ index: 0,
175
+ };
176
+ }
177
+
178
+ // --- Main ---
179
+
180
+ async function main() {
181
+ // Validate required env
182
+ if (!CRYPTO_SERVICE_URL) {
183
+ console.error("CRYPTO_SERVICE_URL is required");
184
+ process.exit(1);
185
+ }
186
+
187
+ // Read mnemonic from stdin
188
+ const chunks: Buffer[] = [];
189
+ for await (const chunk of process.stdin) {
190
+ chunks.push(chunk as Buffer);
191
+ }
192
+ const mnemonic = Buffer.concat(chunks).toString("utf-8").trim();
193
+
194
+ if (!bip39.validateMnemonic(mnemonic, wordlist)) {
195
+ console.error("Invalid mnemonic");
196
+ process.exit(1);
197
+ }
198
+
199
+ // Fetch enabled payment methods from chain server
200
+ console.log(`Fetching chains from ${CRYPTO_SERVICE_URL}/chains...`);
201
+ const headers: Record<string, string> = {
202
+ "Content-Type": "application/json",
203
+ };
204
+ if (CRYPTO_SERVICE_KEY) headers.Authorization = `Bearer ${CRYPTO_SERVICE_KEY}`;
205
+
206
+ const res = await fetch(`${CRYPTO_SERVICE_URL}/chains`, { headers });
207
+ if (!res.ok) throw new Error(`Chain server returned ${res.status}`);
208
+
209
+ const methods: ChainMethod[] = await res.json();
210
+ console.log(`Found ${methods.length} payment methods\n`);
211
+
212
+ // Group by coin_type
213
+ const byCoinType = new Map<number, ChainMethod[]>();
214
+ for (const m of methods) {
215
+ const coinType = m.coin_type;
216
+ if (coinType === undefined || coinType === null) {
217
+ console.log(` Skipping ${m.token}/${m.chain} -- no coin_type`);
218
+ continue;
219
+ }
220
+ const group = byCoinType.get(coinType) ?? [];
221
+ group.push(m);
222
+ byCoinType.set(coinType, group);
223
+ }
224
+
225
+ // Derive master HD key
226
+ const seed = bip39.mnemonicToSeedSync(mnemonic);
227
+ const master = HDKey.fromMasterSeed(seed);
228
+
229
+ console.log(`Dry run: ${DRY_RUN}`);
230
+ console.log(`Max addresses: ${MAX_INDEX}\n`);
231
+
232
+ const allResults: SweepResult[] = [];
233
+
234
+ // Process each coin type group
235
+ for (const [coinType, group] of byCoinType) {
236
+ const family = COIN_TYPE_FAMILIES[coinType];
237
+ if (!family) {
238
+ console.log(`Skipping coin_type ${coinType} -- unsupported family`);
239
+ continue;
240
+ }
241
+
242
+ const chainNames = [...new Set(group.map((m) => m.chain))].join(", ");
243
+ console.log(`\n${"=".repeat(60)}\nCoin type ${coinType} (${family}) -- chains: ${chainNames}\n${"=".repeat(60)}`);
244
+
245
+ // Derive deposit keys at chain=0 (external)
246
+ const depositKeys = deriveKeyPairs(master, coinType, family, MAX_INDEX, 0);
247
+ const treasury = deriveTreasuryAddress(master, coinType, family);
248
+ console.log(`Treasury: ${treasury.address}`);
249
+ console.log(`Scanning ${MAX_INDEX} deposit addresses...\n`);
250
+
251
+ // Include treasury key in the key set so sweepers can use it for gas funding
252
+ const allKeys = [...depositKeys, treasury];
253
+
254
+ if (family === "utxo") {
255
+ const sweeper = new UtxoSweeper(chainNames);
256
+ try {
257
+ await sweeper.sweep(allKeys, treasury.address, DRY_RUN);
258
+ } catch (err) {
259
+ console.log(` ${(err as Error).message}`);
260
+ }
261
+ continue;
262
+ }
263
+
264
+ if (family === "evm") {
265
+ // Group EVM methods by chain (each chain has its own RPC)
266
+ const byChain = new Map<string, ChainMethod[]>();
267
+ for (const m of group) {
268
+ const chain = byChain.get(m.chain) ?? [];
269
+ chain.push(m);
270
+ byChain.set(m.chain, chain);
271
+ }
272
+
273
+ for (const [chainName, chainMethods] of byChain) {
274
+ const rpcUrl = chainMethods[0]?.rpc_url;
275
+ if (!rpcUrl) {
276
+ console.log(` Skipping ${chainName} -- no rpc_url in chain config`);
277
+ continue;
278
+ }
279
+
280
+ const tokens: EvmToken[] = chainMethods
281
+ .filter((m) => m.contractAddress)
282
+ .map((m) => ({
283
+ name: m.token,
284
+ address: m.contractAddress as `0x${string}`,
285
+ decimals: m.decimals,
286
+ }));
287
+
288
+ console.log(`\n--- ${chainName} (${tokens.length} ERC-20 tokens) ---`);
289
+ const sweeper = new EvmSweeper({
290
+ rpcUrl,
291
+ chainName,
292
+ tokens,
293
+ });
294
+ const results = await sweeper.sweep(allKeys, treasury.address, DRY_RUN);
295
+ allResults.push(...results);
296
+ }
297
+ continue;
298
+ }
299
+
300
+ if (family === "tron") {
301
+ const rpcUrl = group[0]?.rpc_url;
302
+ if (!rpcUrl) {
303
+ console.log(" Skipping tron -- no rpc_url in chain config");
304
+ continue;
305
+ }
306
+
307
+ const tokens: TronToken[] = group
308
+ .filter((m): m is ChainMethod & { contractAddress: string } => m.contractAddress != null)
309
+ .map((m) => ({
310
+ name: m.token,
311
+ contractAddress: m.contractAddress,
312
+ decimals: m.decimals,
313
+ }));
314
+
315
+ console.log(`\n--- Tron (${tokens.length} TRC-20 tokens) ---`);
316
+ const sweeper = new TronSweeper({
317
+ rpcUrl,
318
+ apiKey: group[0]?.rpc_headers?.["TRON-PRO-API-KEY"],
319
+ tokens,
320
+ });
321
+ const results = await sweeper.sweep(allKeys, treasury.address, DRY_RUN);
322
+ allResults.push(...results);
323
+ continue;
324
+ }
325
+
326
+ console.log(` Skipping ${family} -- no sweeper implemented`);
327
+ }
328
+
329
+ // Summary
330
+ console.log(`\n${"=".repeat(60)}`);
331
+ console.log("SWEEP SUMMARY");
332
+ console.log(`${"=".repeat(60)}`);
333
+
334
+ if (allResults.length === 0) {
335
+ if (DRY_RUN) {
336
+ console.log("Dry run complete. Set SWEEP_DRY_RUN=false to broadcast.");
337
+ } else {
338
+ console.log("No funds swept.");
339
+ }
340
+ } else {
341
+ for (const r of allResults) {
342
+ console.log(` [${r.index}] ${r.address}: ${r.amount} ${r.token} -> ${r.txHash}`);
343
+ }
344
+ console.log(`\nTotal: ${allResults.length} transactions`);
345
+ }
346
+
347
+ console.log("\nDone.");
348
+ }
349
+
350
+ main().catch((err) => {
351
+ console.error(err);
352
+ process.exit(1);
353
+ });