@wopr-network/crypto-plugins 1.0.1 → 1.1.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 (74) hide show
  1. package/.github/workflows/ci.yml +0 -2
  2. package/dist/bitcoin/encoder.d.ts +1 -1
  3. package/dist/bitcoin/encoder.d.ts.map +1 -1
  4. package/dist/bitcoin/index.d.ts +1 -1
  5. package/dist/bitcoin/index.d.ts.map +1 -1
  6. package/dist/dogecoin/encoder.d.ts +1 -1
  7. package/dist/dogecoin/encoder.d.ts.map +1 -1
  8. package/dist/dogecoin/index.d.ts +1 -1
  9. package/dist/dogecoin/index.d.ts.map +1 -1
  10. package/dist/evm/encoder.d.ts +1 -1
  11. package/dist/evm/encoder.d.ts.map +1 -1
  12. package/dist/evm/eth-watcher.d.ts +1 -1
  13. package/dist/evm/eth-watcher.d.ts.map +1 -1
  14. package/dist/evm/index.d.ts +1 -1
  15. package/dist/evm/index.d.ts.map +1 -1
  16. package/dist/evm/watcher.d.ts +1 -1
  17. package/dist/evm/watcher.d.ts.map +1 -1
  18. package/dist/litecoin/encoder.d.ts +1 -1
  19. package/dist/litecoin/encoder.d.ts.map +1 -1
  20. package/dist/litecoin/index.d.ts +1 -1
  21. package/dist/litecoin/index.d.ts.map +1 -1
  22. package/dist/shared/test-helpers/index.d.ts +1 -1
  23. package/dist/shared/test-helpers/index.d.ts.map +1 -1
  24. package/dist/shared/utxo/watcher.d.ts +1 -1
  25. package/dist/shared/utxo/watcher.d.ts.map +1 -1
  26. package/dist/solana/encoder.d.ts +1 -1
  27. package/dist/solana/encoder.d.ts.map +1 -1
  28. package/dist/solana/index.d.ts +1 -1
  29. package/dist/solana/index.d.ts.map +1 -1
  30. package/dist/solana/sweeper.d.ts +5 -6
  31. package/dist/solana/sweeper.d.ts.map +1 -1
  32. package/dist/solana/sweeper.js +230 -10
  33. package/dist/solana/sweeper.js.map +1 -1
  34. package/dist/solana/watcher.d.ts +1 -1
  35. package/dist/solana/watcher.d.ts.map +1 -1
  36. package/dist/sweep/evm-sweeper.d.ts +1 -1
  37. package/dist/sweep/evm-sweeper.d.ts.map +1 -1
  38. package/dist/sweep/index.js +90 -4
  39. package/dist/sweep/index.js.map +1 -1
  40. package/dist/sweep/tron-sweeper.d.ts +1 -1
  41. package/dist/sweep/tron-sweeper.d.ts.map +1 -1
  42. package/dist/sweep/utxo-sweeper.d.ts +1 -1
  43. package/dist/sweep/utxo-sweeper.d.ts.map +1 -1
  44. package/dist/tron/encoder.d.ts +1 -1
  45. package/dist/tron/encoder.d.ts.map +1 -1
  46. package/dist/tron/index.d.ts +1 -1
  47. package/dist/tron/index.d.ts.map +1 -1
  48. package/dist/tron/watcher.d.ts +1 -1
  49. package/dist/tron/watcher.d.ts.map +1 -1
  50. package/package.json +3 -3
  51. package/src/__tests__/registry.test.ts +1 -1
  52. package/src/bitcoin/encoder.ts +1 -1
  53. package/src/bitcoin/index.ts +1 -1
  54. package/src/dogecoin/encoder.ts +1 -1
  55. package/src/dogecoin/index.ts +1 -1
  56. package/src/evm/encoder.ts +1 -1
  57. package/src/evm/eth-watcher.ts +1 -1
  58. package/src/evm/index.ts +1 -1
  59. package/src/evm/watcher.ts +1 -1
  60. package/src/litecoin/encoder.ts +1 -1
  61. package/src/litecoin/index.ts +1 -1
  62. package/src/shared/test-helpers/index.ts +1 -1
  63. package/src/shared/utxo/watcher.ts +1 -1
  64. package/src/solana/encoder.ts +1 -1
  65. package/src/solana/index.ts +1 -1
  66. package/src/solana/sweeper.ts +270 -15
  67. package/src/solana/watcher.ts +1 -1
  68. package/src/sweep/evm-sweeper.ts +1 -1
  69. package/src/sweep/index.ts +103 -5
  70. package/src/sweep/tron-sweeper.ts +1 -1
  71. package/src/sweep/utxo-sweeper.ts +1 -1
  72. package/src/tron/encoder.ts +1 -1
  73. package/src/tron/index.ts +1 -1
  74. package/src/tron/watcher.ts +1 -1
@@ -1,16 +1,206 @@
1
+ import { ed25519 } from "@noble/curves/ed25519.js";
1
2
  import type {
2
3
  DepositInfo,
3
4
  ISweepStrategy,
4
5
  KeyPair,
5
6
  SweeperOpts,
6
7
  SweepResult,
7
- } from "@wopr-network/platform-core/crypto-plugin";
8
+ } from "@wopr-network/platform-crypto-server/plugin";
8
9
  import type { SolanaRpcCall } from "./types.js";
9
10
  import { createSolanaRpcCaller } from "./watcher.js";
10
11
 
11
12
  /** Transaction fee estimate (in lamports). */
12
13
  const TX_FEE = 5_000n;
13
14
 
15
+ /** SPL Token program ID. */
16
+ const TOKEN_PROGRAM_ID = "TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA";
17
+
18
+ /** System program ID. */
19
+ const SYSTEM_PROGRAM_ID = "11111111111111111111111111111111";
20
+
21
+ // --- Base58 for Solana ---
22
+
23
+ const BASE58_ALPHABET = "123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz";
24
+
25
+ function base58decode(str: string): Uint8Array {
26
+ let num = 0n;
27
+ for (const ch of str) {
28
+ const idx = BASE58_ALPHABET.indexOf(ch);
29
+ if (idx < 0) throw new Error(`Invalid Base58 char: ${ch}`);
30
+ num = num * 58n + BigInt(idx);
31
+ }
32
+ const bytes: number[] = [];
33
+ while (num > 0n) {
34
+ bytes.unshift(Number(num % 256n));
35
+ num = num / 256n;
36
+ }
37
+ let leadingZeros = 0;
38
+ for (const ch of str) {
39
+ if (ch !== "1") break;
40
+ leadingZeros++;
41
+ }
42
+ return new Uint8Array([...new Array(leadingZeros).fill(0), ...bytes]);
43
+ }
44
+
45
+ function base58encode(data: Uint8Array): string {
46
+ let num = 0n;
47
+ for (const byte of data) num = num * 256n + BigInt(byte);
48
+ let encoded = "";
49
+ while (num > 0n) {
50
+ encoded = BASE58_ALPHABET[Number(num % 58n)] + encoded;
51
+ num = num / 58n;
52
+ }
53
+ for (const byte of data) {
54
+ if (byte !== 0) break;
55
+ encoded = `1${encoded}`;
56
+ }
57
+ return encoded || "1";
58
+ }
59
+
60
+ // --- Solana transaction helpers ---
61
+
62
+ /** Compact-u16 encoding used by Solana for array lengths. */
63
+ function encodeCompactU16(value: number): Uint8Array {
64
+ if (value < 0x80) return new Uint8Array([value]);
65
+ if (value < 0x4000) return new Uint8Array([(value & 0x7f) | 0x80, value >> 7]);
66
+ return new Uint8Array([(value & 0x7f) | 0x80, ((value >> 7) & 0x7f) | 0x80, value >> 14]);
67
+ }
68
+
69
+ /** Write a u32 as little-endian bytes. */
70
+ function writeU32LE(value: number): Uint8Array {
71
+ const buf = new Uint8Array(4);
72
+ buf[0] = value & 0xff;
73
+ buf[1] = (value >> 8) & 0xff;
74
+ buf[2] = (value >> 16) & 0xff;
75
+ buf[3] = (value >> 24) & 0xff;
76
+ return buf;
77
+ }
78
+
79
+ /** Write a u64 as little-endian bytes. */
80
+ function writeU64LE(value: bigint): Uint8Array {
81
+ const buf = new Uint8Array(8);
82
+ for (let i = 0; i < 8; i++) {
83
+ buf[i] = Number(value & 0xffn);
84
+ value = value >> 8n;
85
+ }
86
+ return buf;
87
+ }
88
+
89
+ /** Concatenate multiple Uint8Arrays. */
90
+ function concatBytes(...arrays: Uint8Array[]): Uint8Array {
91
+ const total = arrays.reduce((sum, a) => sum + a.length, 0);
92
+ const result = new Uint8Array(total);
93
+ let offset = 0;
94
+ for (const a of arrays) {
95
+ result.set(a, offset);
96
+ offset += a.length;
97
+ }
98
+ return result;
99
+ }
100
+
101
+ /**
102
+ * Build a Solana transaction message (v0 legacy format).
103
+ * Returns the serialized message bytes for signing.
104
+ */
105
+ function buildTransactionMessage(
106
+ recentBlockhash: string,
107
+ feePayer: Uint8Array,
108
+ instructions: Array<{
109
+ programId: Uint8Array;
110
+ accounts: Array<{ pubkey: Uint8Array; isSigner: boolean; isWritable: boolean }>;
111
+ data: Uint8Array;
112
+ }>,
113
+ ): Uint8Array {
114
+ // Collect all unique accounts
115
+ const accountMap = new Map<string, { pubkey: Uint8Array; isSigner: boolean; isWritable: boolean }>();
116
+ const feePayerKey = base58encode(feePayer);
117
+ accountMap.set(feePayerKey, { pubkey: feePayer, isSigner: true, isWritable: true });
118
+
119
+ for (const ix of instructions) {
120
+ const progKey = base58encode(ix.programId);
121
+ if (!accountMap.has(progKey)) {
122
+ accountMap.set(progKey, { pubkey: ix.programId, isSigner: false, isWritable: false });
123
+ }
124
+ for (const acc of ix.accounts) {
125
+ const key = base58encode(acc.pubkey);
126
+ const existing = accountMap.get(key);
127
+ if (existing) {
128
+ existing.isSigner = existing.isSigner || acc.isSigner;
129
+ existing.isWritable = existing.isWritable || acc.isWritable;
130
+ } else {
131
+ accountMap.set(key, { ...acc });
132
+ }
133
+ }
134
+ }
135
+
136
+ // Sort: signers+writable first, then signers+readonly, then non-signers+writable, then non-signers+readonly
137
+ // Fee payer is always first
138
+ const accounts = [...accountMap.values()];
139
+ accounts.sort((a, b) => {
140
+ const aKey = base58encode(a.pubkey);
141
+ const bKey = base58encode(b.pubkey);
142
+ if (aKey === feePayerKey) return -1;
143
+ if (bKey === feePayerKey) return 1;
144
+ if (a.isSigner !== b.isSigner) return a.isSigner ? -1 : 1;
145
+ if (a.isWritable !== b.isWritable) return a.isWritable ? -1 : 1;
146
+ return 0;
147
+ });
148
+
149
+ const numRequiredSignatures = accounts.filter((a) => a.isSigner).length;
150
+ const numReadonlySignedAccounts = accounts.filter((a) => a.isSigner && !a.isWritable).length;
151
+ const numReadonlyUnsignedAccounts = accounts.filter((a) => !a.isSigner && !a.isWritable).length;
152
+
153
+ const accountKeyIndex = new Map<string, number>();
154
+ for (let i = 0; i < accounts.length; i++) {
155
+ accountKeyIndex.set(base58encode(accounts[i].pubkey), i);
156
+ }
157
+
158
+ // Serialize message
159
+ const header = new Uint8Array([numRequiredSignatures, numReadonlySignedAccounts, numReadonlyUnsignedAccounts]);
160
+ const accountKeysLen = encodeCompactU16(accounts.length);
161
+ const accountKeysData = concatBytes(...accounts.map((a) => a.pubkey));
162
+ const blockhashBytes = base58decode(recentBlockhash);
163
+ const instructionsLen = encodeCompactU16(instructions.length);
164
+
165
+ const ixData: Uint8Array[] = [];
166
+ for (const ix of instructions) {
167
+ const progIdx = accountKeyIndex.get(base58encode(ix.programId));
168
+ if (progIdx === undefined) throw new Error("Program ID not in account list");
169
+ const accountIdxs = ix.accounts.map((a) => {
170
+ const idx = accountKeyIndex.get(base58encode(a.pubkey));
171
+ if (idx === undefined) throw new Error("Account not in account list");
172
+ return idx;
173
+ });
174
+ ixData.push(
175
+ concatBytes(
176
+ new Uint8Array([progIdx]),
177
+ encodeCompactU16(accountIdxs.length),
178
+ new Uint8Array(accountIdxs),
179
+ encodeCompactU16(ix.data.length),
180
+ ix.data,
181
+ ),
182
+ );
183
+ }
184
+
185
+ return concatBytes(header, accountKeysLen, accountKeysData, blockhashBytes, instructionsLen, ...ixData);
186
+ }
187
+
188
+ /**
189
+ * Sign and serialize a full Solana transaction (legacy format).
190
+ * Returns base58-encoded wire format ready for sendTransaction.
191
+ */
192
+ function signAndSerialize(messageBytes: Uint8Array, signers: Uint8Array[]): string {
193
+ const signatures: Uint8Array[] = [];
194
+ for (const secretKey of signers) {
195
+ const sig = ed25519.sign(messageBytes, secretKey.slice(0, 32));
196
+ signatures.push(sig);
197
+ }
198
+
199
+ const numSigs = encodeCompactU16(signatures.length);
200
+ const wire = concatBytes(numSigs, ...signatures, messageBytes);
201
+ return base58encode(wire);
202
+ }
203
+
14
204
  /**
15
205
  * Solana sweep strategy.
16
206
  *
@@ -164,33 +354,98 @@ export class SolanaSweeper implements ISweepStrategy {
164
354
  }
165
355
 
166
356
  /**
167
- * Submit a native SOL transfer.
357
+ * Submit a native SOL transfer via SystemProgram.transfer.
168
358
  *
169
- * Builds a SystemProgram.transfer instruction, signs with the deposit keypair,
170
- * and submits via sendTransaction.
359
+ * Builds the instruction manually, signs with Ed25519, and submits via sendTransaction.
171
360
  */
172
361
  private async submitSolTransfer(key: KeyPair, treasury: string, lamports: bigint): Promise<string> {
173
- // Get recent blockhash
174
362
  const blockhashResult = (await this.rpc("getLatestBlockhash", [{ commitment: "finalized" }])) as {
175
363
  value: { blockhash: string; lastValidBlockHeight: number };
176
364
  };
177
365
 
178
- // In production, build + sign the transaction using @solana/web3.js or manual serialization.
179
- // For Phase 1, we use a simplified approach:
180
- throw new Error(
181
- `SOL transfer not yet implemented — would send ${lamports} lamports from ${key.address} to ${treasury} with blockhash ${blockhashResult.value.blockhash}`,
182
- );
366
+ const fromPubkey = base58decode(key.address);
367
+ const toPubkey = base58decode(treasury);
368
+ const systemProgramId = base58decode(SYSTEM_PROGRAM_ID);
369
+
370
+ // SystemProgram.Transfer instruction: index 2, followed by u64 lamports
371
+ const ixData = concatBytes(writeU32LE(2), writeU64LE(lamports));
372
+
373
+ const message = buildTransactionMessage(blockhashResult.value.blockhash, fromPubkey, [
374
+ {
375
+ programId: systemProgramId,
376
+ accounts: [
377
+ { pubkey: fromPubkey, isSigner: true, isWritable: true },
378
+ { pubkey: toPubkey, isSigner: false, isWritable: true },
379
+ ],
380
+ data: ixData,
381
+ },
382
+ ]);
383
+
384
+ // Ed25519 signing requires the 64-byte secret key (32-byte seed + 32-byte pubkey)
385
+ const secretKey = concatBytes(key.privateKey, fromPubkey);
386
+ const serialized = signAndSerialize(message, [secretKey]);
387
+
388
+ const result = (await this.rpc("sendTransaction", [serialized, { encoding: "base58" }])) as string;
389
+ return result;
183
390
  }
184
391
 
185
392
  /**
186
393
  * Submit an SPL token transfer.
187
394
  *
188
- * Builds a TokenProgram.transfer instruction for the given mint,
189
- * signs with the deposit keypair, and submits via sendTransaction.
395
+ * Finds or derives the associated token accounts for sender and receiver,
396
+ * then builds a TokenProgram.Transfer instruction.
190
397
  */
191
398
  private async submitSplTransfer(key: KeyPair, treasury: string, mint: string, amount: bigint): Promise<string> {
192
- throw new Error(
193
- `SPL transfer not yet implemented — would send ${amount} of ${mint} from ${key.address} to ${treasury}`,
194
- );
399
+ const blockhashResult = (await this.rpc("getLatestBlockhash", [{ commitment: "finalized" }])) as {
400
+ value: { blockhash: string; lastValidBlockHeight: number };
401
+ };
402
+
403
+ const fromPubkey = base58decode(key.address);
404
+ const tokenProgramId = base58decode(TOKEN_PROGRAM_ID);
405
+
406
+ // Find sender's token account for this mint
407
+ const senderAccounts = (await this.rpc("getTokenAccountsByOwner", [
408
+ key.address,
409
+ { mint },
410
+ { encoding: "jsonParsed" },
411
+ ])) as { value: Array<{ pubkey: string }> };
412
+
413
+ if (senderAccounts.value.length === 0) {
414
+ throw new Error(`No token account found for ${key.address} mint ${mint}`);
415
+ }
416
+ const senderTokenAccount = base58decode(senderAccounts.value[0].pubkey);
417
+
418
+ // Find receiver's token account for this mint
419
+ const receiverAccounts = (await this.rpc("getTokenAccountsByOwner", [
420
+ treasury,
421
+ { mint },
422
+ { encoding: "jsonParsed" },
423
+ ])) as { value: Array<{ pubkey: string }> };
424
+
425
+ if (receiverAccounts.value.length === 0) {
426
+ throw new Error(`No token account found for treasury ${treasury} mint ${mint} — create ATA first`);
427
+ }
428
+ const receiverTokenAccount = base58decode(receiverAccounts.value[0].pubkey);
429
+
430
+ // SPL Token Transfer instruction: index 3, followed by u64 amount
431
+ const ixData = concatBytes(new Uint8Array([3]), writeU64LE(amount));
432
+
433
+ const message = buildTransactionMessage(blockhashResult.value.blockhash, fromPubkey, [
434
+ {
435
+ programId: tokenProgramId,
436
+ accounts: [
437
+ { pubkey: senderTokenAccount, isSigner: false, isWritable: true },
438
+ { pubkey: receiverTokenAccount, isSigner: false, isWritable: true },
439
+ { pubkey: fromPubkey, isSigner: true, isWritable: false },
440
+ ],
441
+ data: ixData,
442
+ },
443
+ ]);
444
+
445
+ const secretKey = concatBytes(key.privateKey, fromPubkey);
446
+ const serialized = signAndSerialize(message, [secretKey]);
447
+
448
+ const result = (await this.rpc("sendTransaction", [serialized, { encoding: "base58" }])) as string;
449
+ return result;
195
450
  }
196
451
  }
@@ -3,7 +3,7 @@ import type {
3
3
  IWatcherCursorStore,
4
4
  PaymentEvent,
5
5
  WatcherOpts,
6
- } from "@wopr-network/platform-core/crypto-plugin";
6
+ } from "@wopr-network/platform-crypto-server/plugin";
7
7
  import type { SignatureInfo, SolanaRpcCall, SolanaTransaction } from "./types.js";
8
8
 
9
9
  /** Microdollars per cent. Used for oracle price conversion. */
@@ -6,7 +6,7 @@
6
6
  * 2. Fund gas -- treasury sends ETH to ERC-20 deposit addresses
7
7
  * 3. Sweep ERC-20s -- deposit addresses send all tokens to treasury
8
8
  */
9
- import type { DepositInfo, ISweepStrategy, KeyPair, SweepResult } from "@wopr-network/platform-core/crypto-plugin";
9
+ import type { DepositInfo, ISweepStrategy, KeyPair, SweepResult } from "@wopr-network/platform-crypto-server/plugin";
10
10
  import {
11
11
  type Address,
12
12
  type Chain,
@@ -25,8 +25,9 @@ import { keccak_256 } from "@noble/hashes/sha3.js";
25
25
  import { HDKey } from "@scure/bip32";
26
26
  import * as bip39 from "@scure/bip39";
27
27
  import { wordlist } from "@scure/bip39/wordlists/english.js";
28
- import type { KeyPair, SweepResult } from "@wopr-network/platform-core/crypto-plugin";
28
+ import type { KeyPair, SweepResult } from "@wopr-network/platform-crypto-server/plugin";
29
29
  import { privateKeyToAccount } from "viem/accounts";
30
+ import { SolanaSweeper } from "../solana/sweeper.js";
30
31
  import { sha256 } from "../tron/sha256.js";
31
32
  import { EvmSweeper, type EvmToken } from "./evm-sweeper.js";
32
33
  import { TronSweeper, type TronToken } from "./tron-sweeper.js";
@@ -38,6 +39,7 @@ const CRYPTO_SERVICE_URL = process.env.CRYPTO_SERVICE_URL;
38
39
  const CRYPTO_SERVICE_KEY = process.env.CRYPTO_SERVICE_KEY;
39
40
  const DRY_RUN = process.env.SWEEP_DRY_RUN !== "false";
40
41
  const MAX_INDEX = Number(process.env.MAX_ADDRESSES ?? "200");
42
+ const SUBCOMMAND = process.argv[2]; // "sweep" (default) or "pool-replenish"
41
43
 
42
44
  // --- Chain server types ---
43
45
 
@@ -323,6 +325,47 @@ async function main() {
323
325
  continue;
324
326
  }
325
327
 
328
+ if (family === "solana") {
329
+ // Solana uses Ed25519 — pre-derived pool keys come from the chain server
330
+ const rpcUrl = group[0]?.rpc_url;
331
+ if (!rpcUrl) {
332
+ console.log(" Skipping solana -- no rpc_url in chain config");
333
+ continue;
334
+ }
335
+
336
+ // For Solana, fetch pre-derived pool addresses from the chain server
337
+ console.log(`\n--- Solana (${group.length} tokens) ---`);
338
+ console.log(" Note: Solana uses Ed25519 pre-derived keys -- sweep requires pool keys from chain server");
339
+
340
+ for (const method of group) {
341
+ const sweeper = new SolanaSweeper({
342
+ rpcUrl,
343
+ rpcHeaders: method.rpc_headers ?? {},
344
+ chain: method.chain,
345
+ token: method.token,
346
+ decimals: method.decimals,
347
+ contractAddress: method.contractAddress ?? undefined,
348
+ });
349
+
350
+ // For Solana, we can't derive keys from the secp256k1 master — skip if no pool keys.
351
+ // In production, pool keys would be fetched from the chain server's admin API.
352
+ console.log(` ${method.token}: scan-only (pool key sweep requires admin/pool/export endpoint)`);
353
+ try {
354
+ const deposits = await sweeper.scan([], treasury.address);
355
+ if (deposits.length > 0) {
356
+ for (const d of deposits) {
357
+ console.log(` [${d.index}] ${d.address}: ${d.nativeBalance} lamports`);
358
+ }
359
+ } else {
360
+ console.log(" No deposits found");
361
+ }
362
+ } catch (err) {
363
+ console.log(` Scan error: ${(err as Error).message}`);
364
+ }
365
+ }
366
+ continue;
367
+ }
368
+
326
369
  console.log(` Skipping ${family} -- no sweeper implemented`);
327
370
  }
328
371
 
@@ -347,7 +390,62 @@ async function main() {
347
390
  console.log("\nDone.");
348
391
  }
349
392
 
350
- main().catch((err) => {
351
- console.error(err);
352
- process.exit(1);
353
- });
393
+ // --- Pool replenish subcommand ---
394
+
395
+ async function poolReplenish() {
396
+ if (!CRYPTO_SERVICE_URL) {
397
+ console.error("CRYPTO_SERVICE_URL is required");
398
+ process.exit(1);
399
+ }
400
+
401
+ const count = Number(process.env.POOL_COUNT ?? "100");
402
+ const chain = process.env.POOL_CHAIN;
403
+
404
+ if (!chain) {
405
+ console.error("POOL_CHAIN is required (e.g. 'solana')");
406
+ process.exit(1);
407
+ }
408
+
409
+ console.log(`Replenishing ${count} addresses for chain "${chain}"...`);
410
+
411
+ const headers: Record<string, string> = { "Content-Type": "application/json" };
412
+ if (CRYPTO_SERVICE_KEY) headers.Authorization = `Bearer ${CRYPTO_SERVICE_KEY}`;
413
+
414
+ const res = await fetch(`${CRYPTO_SERVICE_URL}/admin/pool/replenish`, {
415
+ method: "POST",
416
+ headers,
417
+ body: JSON.stringify({ chain, count }),
418
+ });
419
+
420
+ if (!res.ok) {
421
+ const body = await res.text();
422
+ console.error(`Pool replenish failed: ${res.status} ${body}`);
423
+ process.exit(1);
424
+ }
425
+
426
+ const result = (await res.json()) as { added: number; total: number };
427
+ console.log(`Added ${result.added} addresses (total pool: ${result.total})`);
428
+
429
+ // Check pool status
430
+ const statusRes = await fetch(`${CRYPTO_SERVICE_URL}/admin/pool/status?chain=${chain}`, { headers });
431
+ if (statusRes.ok) {
432
+ const status = (await statusRes.json()) as { available: number; claimed: number; total: number };
433
+ console.log(`Pool status: ${status.available} available, ${status.claimed} claimed, ${status.total} total`);
434
+ }
435
+
436
+ console.log("Done.");
437
+ }
438
+
439
+ // --- Entry point ---
440
+
441
+ if (SUBCOMMAND === "pool-replenish") {
442
+ poolReplenish().catch((err) => {
443
+ console.error(err);
444
+ process.exit(1);
445
+ });
446
+ } else {
447
+ main().catch((err) => {
448
+ console.error(err);
449
+ process.exit(1);
450
+ });
451
+ }
@@ -11,7 +11,7 @@
11
11
 
12
12
  import { secp256k1 } from "@noble/curves/secp256k1.js";
13
13
  import { keccak_256 } from "@noble/hashes/sha3.js";
14
- import type { DepositInfo, ISweepStrategy, KeyPair, SweepResult } from "@wopr-network/platform-core/crypto-plugin";
14
+ import type { DepositInfo, ISweepStrategy, KeyPair, SweepResult } from "@wopr-network/platform-crypto-server/plugin";
15
15
  import { sha256 } from "../tron/sha256.js";
16
16
 
17
17
  // TRX has 6 decimals (1 TRX = 1,000,000 SUN)
@@ -4,7 +4,7 @@
4
4
  * UTXO chains require coin selection, fee estimation, and input signing
5
5
  * that is best handled by dedicated wallet software (Electrum, Sparrow, etc.).
6
6
  */
7
- import type { DepositInfo, ISweepStrategy, KeyPair, SweepResult } from "@wopr-network/platform-core/crypto-plugin";
7
+ import type { DepositInfo, ISweepStrategy, KeyPair, SweepResult } from "@wopr-network/platform-crypto-server/plugin";
8
8
 
9
9
  export class UtxoSweeper implements ISweepStrategy {
10
10
  private readonly chain: string;
@@ -1,6 +1,6 @@
1
1
  import { secp256k1 } from "@noble/curves/secp256k1.js";
2
2
  import { keccak_256 } from "@noble/hashes/sha3.js";
3
- import type { EncodingParams, IAddressEncoder } from "@wopr-network/platform-core/crypto-plugin";
3
+ import type { EncodingParams, IAddressEncoder } from "@wopr-network/platform-crypto-server/plugin";
4
4
 
5
5
  import { sha256 } from "./sha256.js";
6
6
 
package/src/tron/index.ts CHANGED
@@ -1,4 +1,4 @@
1
- import type { IChainPlugin, WatcherOpts } from "@wopr-network/platform-core/crypto-plugin";
1
+ import type { IChainPlugin, WatcherOpts } from "@wopr-network/platform-crypto-server/plugin";
2
2
 
3
3
  import { keccakB58Encoder } from "./encoder.js";
4
4
  import { TronEvmWatcher } from "./watcher.js";
@@ -3,7 +3,7 @@ import type {
3
3
  IWatcherCursorStore,
4
4
  PaymentEvent,
5
5
  WatcherOpts,
6
- } from "@wopr-network/platform-core/crypto-plugin";
6
+ } from "@wopr-network/platform-crypto-server/plugin";
7
7
 
8
8
  import { hexToTron, tronToHex } from "./address-convert.js";
9
9