// lib/wdk-integration.cjs - WDK Integration Layer
// CRITICAL: Load bare-node-runtime FIRST before any other requires
require('bare-node-runtime/global')
const fs = require('bare-fs')
const path = require('bare-path')
const os = require('bare-os')
// Import WDK packages directly
// bare-node-runtime/global provides Node.js compatibility for Bare runtime
const WDK = require('@tetherto/wdk')
const WalletManagerBtc = require('@tetherto/wdk-wallet-btc')
const WalletManagerEvm = require('@tetherto/wdk-wallet-evm')
// Import encryption module
const { encrypt, decrypt, isValidFormat } = require('./encryption.cjs')
// Storage paths
const STORAGE_DIR = path.join(os.homedir(), '.hyper-wallet')
const WALLET_MNEMONIC_FILE = path.join(STORAGE_DIR, '.wallet-mnemonic')
/**
* Generate new wallet mnemonic (12 words)
* Uses WDK.getRandomSeedPhrase() - official WDK method
* @returns {string} - 12-word BIP-39 mnemonic
*/
function generateWalletMnemonic() {
return WDK.default.getRandomSeedPhrase()
}
/**
* Save wallet mnemonic to file (with password-based encryption)
* Stored separately from identity mnemonic for security separation
* @param {string} mnemonic - 12-word mnemonic
* @param {string} password - Encryption password (min 8 chars)
*/
function saveWalletMnemonic(mnemonic, password) {
if (!fs.existsSync(STORAGE_DIR)) {
fs.mkdirSync(STORAGE_DIR, { recursive: true, mode: 0o700 })
}
// Require password for new mnemonics
if (!password || password.length < 8) {
throw new Error('Password must be at least 8 characters')
}
// Encrypt mnemonic before saving
const encrypted = encrypt(mnemonic, password)
fs.writeFileSync(WALLET_MNEMONIC_FILE, encrypted, { mode: 0o600 })
}
/**
* Load wallet mnemonic from file (with password-based decryption)
* @param {string} password - Decryption password
* @returns {string|null} - 12-word mnemonic or null if not exists
*/
function loadWalletMnemonic(password) {
if (!fs.existsSync(WALLET_MNEMONIC_FILE)) {
return null
}
const encrypted = fs.readFileSync(WALLET_MNEMONIC_FILE, 'utf8').trim()
// Check if this is plaintext (legacy format) - contains spaces and 12 words
if (encrypted.includes(' ') && encrypted.split(' ').length === 12) {
console.warn('⚠️ WARNING: Wallet mnemonic is stored in plaintext (legacy format)')
console.warn('⚠️ Please set a password to encrypt your mnemonics')
return encrypted
}
// Decrypt with password
if (!password) {
throw new Error('Password required to decrypt wallet mnemonic')
}
try {
return decrypt(encrypted, password)
} catch (err) {
throw new Error('Invalid password or corrupted mnemonic data')
}
}
/**
* Initialize WDK with wallet mnemonic
* Creates WDK instance and registers Bitcoin + Ethereum wallets
* @param {string} walletMnemonic - 12-word BIP-39 mnemonic
* @param {Object} config - Configuration { testnet: boolean }
* @returns {Object} - { wdk } - WDK instance with registered wallets
*/
async function initializeWDK(walletMnemonic, config = { testnet: true, testnet4: false }) {
try {
// Initialize WDK with mnemonic and register wallets (chainable)
const wdk = new WDK.default(walletMnemonic)
.registerWallet('bitcoin', WalletManagerBtc.default, {
network: config.testnet4 ? 'testnet4' : (config.testnet ? 'testnet' : 'bitcoin'),
client: {
type: 'electrum',
clientConfig: {
host: config.testnet4 ? 'blackie.c3-soft.com' : 'electrum.blockstream.info',
port: config.testnet4 ? 50001 : (config.testnet ? 60001 : 50001)
}
}
})
.registerWallet('ethereum', WalletManagerEvm.default, {
provider: config.testnet
? 'https://ethereum-sepolia-rpc.publicnode.com'
: 'https://eth.drpc.org'
})
return { wdk }
} catch (err) {
// Graceful error handling with clear user feedback
if (err.message.includes('network') || err.message.includes('provider')) {
throw new Error('Cannot connect to blockchain network. Check internet connection.')
} else if (err.message.includes('mnemonic') || err.message.includes('seed')) {
throw new Error('Invalid wallet mnemonic format.')
} else {
throw new Error(`WDK initialization failed: ${err.message}`)
}
}
}
/**
* Get first address for testing/display
* Uses account index 0, address index 0
* @param {Object} wdk - WDK instance
* @param {string} chain - 'bitcoin' or 'ethereum'
* @returns {string} - First derived address
*/
async function getFirstAddress(wdk, chain = 'bitcoin') {
try {
const account = await wdk.getAccount(chain, 0)
return await account.getAddress()
} catch (err) {
throw new Error(`Failed to derive ${chain} address: ${err.message}`)
}
}
/**
* Check if wallet mnemonic exists
* @returns {boolean}
*/
function walletMnemonicExists() {
return fs.existsSync(WALLET_MNEMONIC_FILE)
}
/**
* Token contract addresses for ERC-20 tokens
* Format: { 'chain-token': { mainnet: 'address', testnet: 'address' } }
*/
const TOKEN_CONTRACTS = {
'ethereum-usdt': {
mainnet: '0xdAC17F958D2ee523a2206206994597C13D831ec7', // Checksummed address (important!)
testnet: '0x7169D38820dfd117C3FA1f22a697dBA58d90BA06' // Sepolia USDT
},
'ethereum-usdc': {
mainnet: '0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48',
testnet: '0x1c7D4B196Cb0C7B01d743Fbc6116a902379C7238' // Sepolia USDC
}
}
/**
* Check if a chain parameter refers to a token (e.g., 'ethereum-usdt')
* @param {string} chain - Chain identifier
* @returns {boolean}
*/
function isTokenChain(chain) {
return chain.includes('-') && chain !== 'testnet' && chain !== 'mainnet'
}
/**
* Get base chain from token chain identifier
* @param {string} chain - Chain identifier (e.g., 'ethereum-usdt' or 'ethereum')
* @returns {string} - Base chain (e.g., 'ethereum')
*/
function getBaseChain(chain) {
if (!isTokenChain(chain)) {
return chain
}
// Extract base chain from 'ethereum-usdt' -> 'ethereum'
return chain.split('-')[0]
}
/**
* Get token contract information
* @param {string} chain - Token chain identifier (e.g., 'ethereum-usdt')
* @param {boolean} isTestnet - Whether to use testnet contracts
* @returns {Object} - Token info { contractAddress, symbol, decimals }
*/
function getTokenInfo(chain, isTestnet = true) {
const tokenContracts = TOKEN_CONTRACTS[chain]
if (!tokenContracts) {
throw new Error(`Unsupported token: ${chain}`)
}
const contractAddress = isTestnet ? tokenContracts.testnet : tokenContracts.mainnet
// Extract token symbol from chain (ethereum-usdt -> USDT)
const symbol = chain.split('-')[1]?.toUpperCase() || 'TOKEN'
return {
contractAddress, // Changed from 'contract' to match usage in wallet-server.cjs
symbol,
decimals: 6 // USDT/USDC use 6 decimals
}
}
module.exports = {
generateWalletMnemonic,
saveWalletMnemonic,
loadWalletMnemonic,
initializeWDK,
getFirstAddress,
walletMnemonicExists,
isTokenChain,
getBaseChain,
getTokenInfo,
TOKEN_CONTRACTS
}