|
| 1 | +/** |
| 2 | + * Create test wallets and generate claims data for multiple coins. |
| 3 | + * |
| 4 | + * This script can either create new wallets or use existing wallets to generate |
| 5 | + * receive addresses and CSV files containing claims data for Midnight airdrop testing. |
| 6 | + * |
| 7 | + * USAGE GUIDELINES: |
| 8 | + * |
| 9 | + * 1. ENVIRONMENT SETUP: |
| 10 | + * - Set TESTNET_ACCESS_TOKEN in .env file (your BitGo testnet access token) |
| 11 | + * - Set TEST_PASS in .env file (wallet passphrase) |
| 12 | + * |
| 13 | + * 2. CONFIGURATION OPTIONS: |
| 14 | + * - CREATE_NEW_WALLETS: Set to true to create new wallets, false to use existing ones |
| 15 | + * - COINS_TO_PROCESS: Array of coin symbols to process (empty array = process all coins) |
| 16 | + * - EXISTING_WALLET_IDS: Required when CREATE_NEW_WALLETS is false |
| 17 | + * - numAddresses: Number of receive addresses to generate per wallet |
| 18 | + * |
| 19 | + * 3. EXAMPLES: |
| 20 | + * - Process all coins with new wallets: CREATE_NEW_WALLETS = true, COINS_TO_PROCESS = [] |
| 21 | + * - Process only BTC and SOL: COINS_TO_PROCESS = ['tbtc4', 'tsol'] |
| 22 | + * - Use existing wallets: CREATE_NEW_WALLETS = false, update EXISTING_WALLET_IDS |
| 23 | + * |
| 24 | + * 4. OUTPUT: |
| 25 | + * - CSV files: receive-addresses-{coin}-{walletId}.csv |
| 26 | + * - Format: coin,allocationAmount,walletId,enterpriseId,address |
| 27 | + * |
| 28 | + * Copyright 2022, BitGo, Inc. All Rights Reserved. |
| 29 | + */ |
| 30 | +import { BitGoAPI } from '@bitgo/sdk-api'; |
| 31 | +import { Tada } from "@bitgo/sdk-coin-ada"; |
| 32 | +import { Tsol } from "@bitgo/sdk-coin-sol"; |
| 33 | +import { Tbtc4 } from "@bitgo/sdk-coin-btc"; |
| 34 | +import { Tbsc } from "@bitgo/sdk-coin-bsc"; |
| 35 | +import { Hteth } from "@bitgo/sdk-coin-eth"; |
| 36 | +import * as fs from 'fs'; |
| 37 | +require('dotenv').config({ path: '../../.env' }); |
| 38 | + |
| 39 | +const bitgo = new BitGoAPI({ |
| 40 | + accessToken: process.env.TESTNET_ACCESS_TOKEN, |
| 41 | + env: 'staging', |
| 42 | +}); |
| 43 | + |
| 44 | +// Register coin types |
| 45 | +bitgo.register('hteth', Hteth.createInstance); |
| 46 | +bitgo.register('tada', Tada.createInstance); |
| 47 | +bitgo.register('tsol', Tsol.createInstance); |
| 48 | +bitgo.register('tbtc4', Tbtc4.createInstance); |
| 49 | +bitgo.register('tbsc', Tbsc.createInstance); |
| 50 | + |
| 51 | +const passphrase = process.env.TEST_PASS; |
| 52 | +const enterprise = '5bd795f1bf7435a503a0a647ec5d3b3d'; |
| 53 | +const numAddresses = 20; |
| 54 | + |
| 55 | +// Configuration: Set to true to create new wallets, false to use existing wallet IDs |
| 56 | +const CREATE_NEW_WALLETS = false; |
| 57 | + |
| 58 | +// Configuration: Specify which coins to process (empty array means process all coins) |
| 59 | +const COINS_TO_PROCESS: string[] = ['tbsc']; // e.g., ['tbtc4', 'tsol'] to process only these coins |
| 60 | + |
| 61 | +// If CREATE_NEW_WALLETS is false, provide existing wallet IDs for each coin |
| 62 | +const EXISTING_WALLET_IDS = { |
| 63 | + hteth: '67123abc456def789ghi012jkl345mno', |
| 64 | + tbtc4: '67234bcd567efg890hij123klm456nop', |
| 65 | + tsol: '67345cde678fgh901ijk234lmn567opq', |
| 66 | + tada: '67456def789ghi012jkl345mno678pqr', |
| 67 | + tbsc: '6894dd8ff0ee06d1630c84d54d381dde' |
| 68 | +}; |
| 69 | + |
| 70 | +// Generate unique timestamp for this run |
| 71 | +const timestamp = new Date().toISOString().replace(/[:.]/g, '-').slice(0, 19); // 2025-09-15T14-30-45 |
| 72 | + |
| 73 | +const coins = [ |
| 74 | + { coin: 'hteth', label: `Midnight Claims ETH Wallet ${timestamp}` }, |
| 75 | + { coin: 'tbtc4', label: `Midnight Claims BTC Wallet ${timestamp}` }, |
| 76 | + { coin: 'tsol', label: `Midnight Claims SOL Wallet ${timestamp}` }, |
| 77 | + { coin: 'tada', label: `Midnight Claims ADA Wallet ${timestamp}` }, |
| 78 | + { coin: 'tbsc', label: `Midnight Claims BSC Wallet ${timestamp}` } |
| 79 | +]; |
| 80 | + |
| 81 | +type WalletCreationResult = { |
| 82 | + coin: string; |
| 83 | + success: true; |
| 84 | + walletId: string; |
| 85 | + addressCount: number; |
| 86 | + filename: string; |
| 87 | +} | { |
| 88 | + coin: string; |
| 89 | + success: false; |
| 90 | + error: string; |
| 91 | +}; |
| 92 | + |
| 93 | +async function createWalletAndAddresses(coin: string, label: string): Promise<WalletCreationResult> { |
| 94 | + console.log(`\n=== ${CREATE_NEW_WALLETS ? 'Creating' : 'Using existing'} wallet for ${coin.toUpperCase()} ===`); |
| 95 | + |
| 96 | + try { |
| 97 | + let wallet; |
| 98 | + let walletId; |
| 99 | + |
| 100 | + if (CREATE_NEW_WALLETS) { |
| 101 | + // Create a new wallet |
| 102 | + const response = await bitgo.coin(coin).wallets().generateWallet({ |
| 103 | + label, |
| 104 | + passphrase, |
| 105 | + enterprise, |
| 106 | + }); |
| 107 | + |
| 108 | + if (!('backupKeychain' in response)) { |
| 109 | + throw new Error('wallet missing required keychains'); |
| 110 | + } |
| 111 | + |
| 112 | + wallet = response.wallet; |
| 113 | + walletId = wallet.id(); |
| 114 | + console.log('Created new wallet ID:', walletId); |
| 115 | + } else { |
| 116 | + // Use existing wallet |
| 117 | + walletId = EXISTING_WALLET_IDS[coin as keyof typeof EXISTING_WALLET_IDS]; |
| 118 | + if (!walletId) { |
| 119 | + throw new Error(`No existing wallet ID configured for coin: ${coin}`); |
| 120 | + } |
| 121 | + |
| 122 | + wallet = await bitgo.coin(coin).wallets().get({ id: walletId }); |
| 123 | + console.log('Using existing wallet ID:', walletId); |
| 124 | + } |
| 125 | + |
| 126 | + console.log(`Wallet label: ${wallet.label()}`); |
| 127 | + |
| 128 | + const csvHeader = 'coin,allocationAmount,originWalletId,enterpriseId,originAddress\n'; |
| 129 | + let csvRows = ''; |
| 130 | + for (let i = 0; i < numAddresses; i++) { |
| 131 | + const newAddress = await wallet.createAddress(); |
| 132 | + console.log(`Receive Address ${i + 1}:`, newAddress.address); |
| 133 | + const allocationAmount = Math.floor(Math.random() * 1_000_000); |
| 134 | + csvRows += `${coin},${allocationAmount},${walletId},${enterprise},${newAddress.address}\n`; |
| 135 | + } |
| 136 | + |
| 137 | + const filename = `./receive-addresses-${coin}-${walletId}.csv`; |
| 138 | + fs.writeFileSync(filename, csvHeader + csvRows); |
| 139 | + console.log(`CSV file updated: ${filename}`); |
| 140 | + |
| 141 | + return { |
| 142 | + coin, |
| 143 | + walletId, |
| 144 | + success: true, |
| 145 | + addressCount: numAddresses, |
| 146 | + filename |
| 147 | + }; |
| 148 | + } catch (error: any) { |
| 149 | + console.error(`Error ${CREATE_NEW_WALLETS ? 'creating' : 'using'} wallet for ${coin}:`, error); |
| 150 | + return { |
| 151 | + coin, |
| 152 | + success: false, |
| 153 | + error: error.message || error.toString() |
| 154 | + }; |
| 155 | + } |
| 156 | +} |
| 157 | + |
| 158 | +async function main() { |
| 159 | + if (!passphrase) { |
| 160 | + throw new Error('TEST_PASS environment variable is required'); |
| 161 | + } |
| 162 | + |
| 163 | + if (!process.env.TESTNET_ACCESS_TOKEN) { |
| 164 | + throw new Error('TESTNET_ACCESS_TOKEN environment variable is required'); |
| 165 | + } |
| 166 | + |
| 167 | + console.log(`Starting ${CREATE_NEW_WALLETS ? 'wallet creation' : 'address generation on existing wallets'} for multiple coins...`); |
| 168 | + console.log(`Enterprise ID: ${enterprise}`); |
| 169 | + console.log(`Number of addresses per wallet: ${numAddresses}`); |
| 170 | + console.log(`Mode: ${CREATE_NEW_WALLETS ? 'CREATE NEW WALLETS' : 'USE EXISTING WALLETS'}`); |
| 171 | + |
| 172 | + const results: WalletCreationResult[] = []; |
| 173 | + |
| 174 | + for (const { coin, label } of coins) { |
| 175 | + // Check if the coin is in the COINS_TO_PROCESS list or if the list is empty (process all coins) |
| 176 | + if (COINS_TO_PROCESS.length === 0 || COINS_TO_PROCESS.includes(coin)) { |
| 177 | + const result = await createWalletAndAddresses(coin, label); |
| 178 | + results.push(result); |
| 179 | + |
| 180 | + // Add a small delay between wallet creations to avoid rate limiting |
| 181 | + await new Promise(resolve => setTimeout(resolve, 2000)); |
| 182 | + } else { |
| 183 | + console.log(`Skipping ${coin.toUpperCase()} as it's not in the COINS_TO_PROCESS list`); |
| 184 | + } |
| 185 | + } |
| 186 | + |
| 187 | + console.log('\n=== SUMMARY ==='); |
| 188 | + results.forEach(result => { |
| 189 | + if (result.success) { |
| 190 | + console.log(`✅ ${result.coin}: Wallet ${result.walletId} created with ${result.addressCount} addresses`); |
| 191 | + console.log(` File: ${result.filename}`); |
| 192 | + } else { |
| 193 | + console.log(`❌ ${result.coin}: Failed - ${result.error}`); |
| 194 | + } |
| 195 | + }); |
| 196 | + |
| 197 | + const successfulCreations = results.filter(r => r.success); |
| 198 | + console.log(`\nCompleted: ${successfulCreations.length}/${results.length} wallets created successfully`); |
| 199 | +} |
| 200 | + |
| 201 | +main().catch((e) => console.error('Script failed:', e)); |
0 commit comments