@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.
- package/.github/workflows/ci.yml +33 -0
- package/.github/workflows/publish.yml +12 -0
- package/biome.json +23 -0
- package/dist/__tests__/bitcoin-encoder.test.d.ts +2 -0
- package/dist/__tests__/bitcoin-encoder.test.d.ts.map +1 -0
- package/dist/__tests__/bitcoin-encoder.test.js +97 -0
- package/dist/__tests__/bitcoin-encoder.test.js.map +1 -0
- package/dist/__tests__/dogecoin-encoder.test.d.ts +2 -0
- package/dist/__tests__/dogecoin-encoder.test.d.ts.map +1 -0
- package/dist/__tests__/dogecoin-encoder.test.js +57 -0
- package/dist/__tests__/dogecoin-encoder.test.js.map +1 -0
- package/dist/__tests__/litecoin-encoder.test.d.ts +2 -0
- package/dist/__tests__/litecoin-encoder.test.d.ts.map +1 -0
- package/dist/__tests__/litecoin-encoder.test.js +44 -0
- package/dist/__tests__/litecoin-encoder.test.js.map +1 -0
- package/dist/__tests__/registry.test.d.ts +2 -0
- package/dist/__tests__/registry.test.d.ts.map +1 -0
- package/dist/__tests__/registry.test.js +75 -0
- package/dist/__tests__/registry.test.js.map +1 -0
- package/dist/__tests__/rpc.test.d.ts +2 -0
- package/dist/__tests__/rpc.test.d.ts.map +1 -0
- package/dist/__tests__/rpc.test.js +31 -0
- package/dist/__tests__/rpc.test.js.map +1 -0
- package/dist/__tests__/solana-encoder.test.d.ts +2 -0
- package/dist/__tests__/solana-encoder.test.d.ts.map +1 -0
- package/dist/__tests__/solana-encoder.test.js +85 -0
- package/dist/__tests__/solana-encoder.test.js.map +1 -0
- package/dist/__tests__/solana-watcher.test.d.ts +2 -0
- package/dist/__tests__/solana-watcher.test.d.ts.map +1 -0
- package/dist/__tests__/solana-watcher.test.js +281 -0
- package/dist/__tests__/solana-watcher.test.js.map +1 -0
- package/dist/__tests__/sweep-key-parity.test.d.ts +2 -0
- package/dist/__tests__/sweep-key-parity.test.d.ts.map +1 -0
- package/dist/__tests__/sweep-key-parity.test.js +236 -0
- package/dist/__tests__/sweep-key-parity.test.js.map +1 -0
- package/dist/__tests__/tron-encoder.test.d.ts +2 -0
- package/dist/__tests__/tron-encoder.test.d.ts.map +1 -0
- package/dist/__tests__/tron-encoder.test.js +93 -0
- package/dist/__tests__/tron-encoder.test.js.map +1 -0
- package/dist/__tests__/utxo-watcher.test.d.ts +2 -0
- package/dist/__tests__/utxo-watcher.test.d.ts.map +1 -0
- package/dist/__tests__/utxo-watcher.test.js +218 -0
- package/dist/__tests__/utxo-watcher.test.js.map +1 -0
- package/dist/bitcoin/encoder.d.ts +15 -0
- package/dist/bitcoin/encoder.d.ts.map +1 -0
- package/dist/bitcoin/encoder.js +286 -0
- package/dist/bitcoin/encoder.js.map +1 -0
- package/dist/bitcoin/index.d.ts +4 -0
- package/dist/bitcoin/index.d.ts.map +1 -0
- package/dist/bitcoin/index.js +20 -0
- package/dist/bitcoin/index.js.map +1 -0
- package/dist/dogecoin/encoder.d.ts +19 -0
- package/dist/dogecoin/encoder.d.ts.map +1 -0
- package/dist/dogecoin/encoder.js +145 -0
- package/dist/dogecoin/encoder.js.map +1 -0
- package/dist/dogecoin/index.d.ts +4 -0
- package/dist/dogecoin/index.d.ts.map +1 -0
- package/dist/dogecoin/index.js +20 -0
- package/dist/dogecoin/index.js.map +1 -0
- package/dist/evm/encoder.d.ts +7 -0
- package/dist/evm/encoder.d.ts.map +1 -0
- package/dist/evm/encoder.js +43 -0
- package/dist/evm/encoder.js.map +1 -0
- package/dist/evm/eth-watcher.d.ts +38 -0
- package/dist/evm/eth-watcher.d.ts.map +1 -0
- package/dist/evm/eth-watcher.js +138 -0
- package/dist/evm/eth-watcher.js.map +1 -0
- package/dist/evm/index.d.ts +16 -0
- package/dist/evm/index.d.ts.map +1 -0
- package/dist/evm/index.js +34 -0
- package/dist/evm/index.js.map +1 -0
- package/dist/evm/types.d.ts +43 -0
- package/dist/evm/types.d.ts.map +1 -0
- package/dist/evm/types.js +101 -0
- package/dist/evm/types.js.map +1 -0
- package/dist/evm/watcher.d.ts +42 -0
- package/dist/evm/watcher.d.ts.map +1 -0
- package/dist/evm/watcher.js +162 -0
- package/dist/evm/watcher.js.map +1 -0
- package/dist/index.d.ts +7 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +7 -0
- package/dist/index.js.map +1 -0
- package/dist/litecoin/encoder.d.ts +8 -0
- package/dist/litecoin/encoder.d.ts.map +1 -0
- package/dist/litecoin/encoder.js +16 -0
- package/dist/litecoin/encoder.js.map +1 -0
- package/dist/litecoin/index.d.ts +4 -0
- package/dist/litecoin/index.d.ts.map +1 -0
- package/dist/litecoin/index.js +20 -0
- package/dist/litecoin/index.js.map +1 -0
- package/dist/shared/test-helpers/index.d.ts +9 -0
- package/dist/shared/test-helpers/index.d.ts.map +1 -0
- package/dist/shared/test-helpers/index.js +30 -0
- package/dist/shared/test-helpers/index.js.map +1 -0
- package/dist/shared/utxo/index.d.ts +5 -0
- package/dist/shared/utxo/index.d.ts.map +1 -0
- package/dist/shared/utxo/index.js +3 -0
- package/dist/shared/utxo/index.js.map +1 -0
- package/dist/shared/utxo/rpc.d.ts +24 -0
- package/dist/shared/utxo/rpc.d.ts.map +1 -0
- package/dist/shared/utxo/rpc.js +75 -0
- package/dist/shared/utxo/rpc.js.map +1 -0
- package/dist/shared/utxo/types.d.ts +40 -0
- package/dist/shared/utxo/types.d.ts.map +1 -0
- package/dist/shared/utxo/types.js +2 -0
- package/dist/shared/utxo/types.js.map +1 -0
- package/dist/shared/utxo/watcher.d.ts +55 -0
- package/dist/shared/utxo/watcher.d.ts.map +1 -0
- package/dist/shared/utxo/watcher.js +150 -0
- package/dist/shared/utxo/watcher.js.map +1 -0
- package/dist/solana/encoder.d.ts +13 -0
- package/dist/solana/encoder.d.ts.map +1 -0
- package/dist/solana/encoder.js +62 -0
- package/dist/solana/encoder.js.map +1 -0
- package/dist/solana/index.d.ts +17 -0
- package/dist/solana/index.d.ts.map +1 -0
- package/dist/solana/index.js +32 -0
- package/dist/solana/index.js.map +1 -0
- package/dist/solana/sweeper.d.ts +47 -0
- package/dist/solana/sweeper.d.ts.map +1 -0
- package/dist/solana/sweeper.js +151 -0
- package/dist/solana/sweeper.js.map +1 -0
- package/dist/solana/types.d.ts +49 -0
- package/dist/solana/types.d.ts.map +1 -0
- package/dist/solana/types.js +2 -0
- package/dist/solana/types.js.map +1 -0
- package/dist/solana/watcher.d.ts +59 -0
- package/dist/solana/watcher.d.ts.map +1 -0
- package/dist/solana/watcher.js +251 -0
- package/dist/solana/watcher.js.map +1 -0
- package/dist/sweep/evm-sweeper.d.ts +31 -0
- package/dist/sweep/evm-sweeper.d.ts.map +1 -0
- package/dist/sweep/evm-sweeper.js +229 -0
- package/dist/sweep/evm-sweeper.js.map +1 -0
- package/dist/sweep/index.d.ts +22 -0
- package/dist/sweep/index.d.ts.map +1 -0
- package/dist/sweep/index.js +290 -0
- package/dist/sweep/index.js.map +1 -0
- package/dist/sweep/tron-sweeper.d.ts +40 -0
- package/dist/sweep/tron-sweeper.d.ts.map +1 -0
- package/dist/sweep/tron-sweeper.js +363 -0
- package/dist/sweep/tron-sweeper.js.map +1 -0
- package/dist/sweep/utxo-sweeper.d.ts +14 -0
- package/dist/sweep/utxo-sweeper.d.ts.map +1 -0
- package/dist/sweep/utxo-sweeper.js +13 -0
- package/dist/sweep/utxo-sweeper.js.map +1 -0
- package/dist/tron/address-convert.d.ts +15 -0
- package/dist/tron/address-convert.d.ts.map +1 -0
- package/dist/tron/address-convert.js +95 -0
- package/dist/tron/address-convert.js.map +1 -0
- package/dist/tron/encoder.d.ts +20 -0
- package/dist/tron/encoder.d.ts.map +1 -0
- package/dist/tron/encoder.js +67 -0
- package/dist/tron/encoder.js.map +1 -0
- package/dist/tron/index.d.ts +6 -0
- package/dist/tron/index.d.ts.map +1 -0
- package/dist/tron/index.js +20 -0
- package/dist/tron/index.js.map +1 -0
- package/dist/tron/sha256.d.ts +6 -0
- package/dist/tron/sha256.d.ts.map +1 -0
- package/dist/tron/sha256.js +90 -0
- package/dist/tron/sha256.js.map +1 -0
- package/dist/tron/watcher.d.ts +42 -0
- package/dist/tron/watcher.d.ts.map +1 -0
- package/dist/tron/watcher.js +168 -0
- package/dist/tron/watcher.js.map +1 -0
- package/package.json +47 -0
- package/src/__tests__/bitcoin-encoder.test.ts +115 -0
- package/src/__tests__/dogecoin-encoder.test.ts +66 -0
- package/src/__tests__/litecoin-encoder.test.ts +51 -0
- package/src/__tests__/registry.test.ts +91 -0
- package/src/__tests__/rpc.test.ts +36 -0
- package/src/__tests__/solana-encoder.test.ts +103 -0
- package/src/__tests__/solana-watcher.test.ts +316 -0
- package/src/__tests__/sweep-key-parity.test.ts +302 -0
- package/src/__tests__/tron-encoder.test.ts +108 -0
- package/src/__tests__/utxo-watcher.test.ts +252 -0
- package/src/bitcoin/encoder.ts +320 -0
- package/src/bitcoin/index.ts +23 -0
- package/src/dogecoin/encoder.ts +161 -0
- package/src/dogecoin/index.ts +23 -0
- package/src/evm/encoder.ts +49 -0
- package/src/evm/eth-watcher.ts +168 -0
- package/src/evm/index.ts +46 -0
- package/src/evm/types.ts +146 -0
- package/src/evm/watcher.ts +189 -0
- package/src/index.ts +21 -0
- package/src/litecoin/encoder.ts +18 -0
- package/src/litecoin/index.ts +23 -0
- package/src/shared/test-helpers/index.ts +36 -0
- package/src/shared/utxo/index.ts +12 -0
- package/src/shared/utxo/rpc.ts +80 -0
- package/src/shared/utxo/types.ts +43 -0
- package/src/shared/utxo/watcher.ts +195 -0
- package/src/solana/encoder.ts +72 -0
- package/src/solana/index.ts +36 -0
- package/src/solana/sweeper.ts +196 -0
- package/src/solana/types.ts +52 -0
- package/src/solana/watcher.ts +282 -0
- package/src/sweep/evm-sweeper.ts +296 -0
- package/src/sweep/index.ts +353 -0
- package/src/sweep/tron-sweeper.ts +467 -0
- package/src/sweep/utxo-sweeper.ts +23 -0
- package/src/tron/address-convert.ts +91 -0
- package/src/tron/encoder.ts +74 -0
- package/src/tron/index.ts +23 -0
- package/src/tron/sha256.ts +100 -0
- package/src/tron/watcher.ts +208 -0
- package/tsconfig.json +17 -0
- 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
|
+
});
|