Files
universe/scripts/add-validator.js
T
zeekayandHanzo Dev 89e2274b46 Point the gateway and the scripts at the segment luxd serves
The route table lives in configs/gateway/*.json, where each endpoint names both
the public path and the url_pattern sent upstream. Both moved: luxd answers
under /v1/chain and nothing translates between them.

The operational scripts went with it — Makefiles, the roll and validator
helpers, the e2e specs and the market-maker config all composed /v1/bc
addresses that now reach nothing. A test that dials a dead path fails in a way
that looks like the chain is down.

Lines that record an observation keep their original spelling.

Co-authored-by: Hanzo Dev <[email protected]>
2026-08-30 17:47:43 -07:00

167 lines
6.0 KiB
JavaScript

#!/usr/bin/env node
/**
* Add a new validator to the Lux Primary Network.
*
* Uses direct P-Chain transaction signing (bypasses CLI ledger requirement).
* For our POA mainnet where we control the treasury key.
*
* Steps:
* 1. Export LUX from C-Chain to P-Chain via atomic tx
* 2. Import on P-Chain
* 3. Issue AddPermissionlessValidatorTx
*
* Usage:
* node scripts/add-validator.js
*/
const MNEMONIC = process.env.LUX_MNEMONIC
if (!MNEMONIC) {
console.error('ERROR: LUX_MNEMONIC is required (source from KMS, never hardcode)')
process.exit(1)
}
const RPC = process.env.LUX_RPC || 'https://api.lux.network'
const STAKE_NLUX = 2_500_000_000n // 2.5 LUX
// Validator-8 BLS keys (from `lux key show validator-8`)
const V8 = {
nodeID: 'NodeID-73af7ae93528c8d66abbeba4f4cf40d631259994',
blsPub: 'ac6a513feea1c1f6de2aea8f38f6623fdab6d92a14d2fba89bbb670c966c7867a78f6c86670fb90a8292ce7e641021eb',
blsPop: '978a7b47d688993e6bbc9f70ca54ac501bd532197220cb7cc8630228e92db08f6938357059061fdf020009ae6eb333b5103598851406392d754113ba6deb32e706d0661bb597fcd52f10da806e8d79746952f87c37f4f8aba84d2c9d047fe58d',
}
async function rpc(endpoint, method, params = {}) {
const r = await fetch(`${RPC}${endpoint}`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ jsonrpc: '2.0', method, params, id: 1 }),
})
const j = await r.json()
if (j.error) throw new Error(`${method}: ${j.error.message}`)
return j.result
}
async function main() {
console.log('=== Add Validator-8 to Lux Primary Network ===\n')
// 1. Check current state
const { validators } = await rpc('/v1/chain/P', 'platform.getCurrentValidators', { subnetID: '' })
console.log(`Current validators: ${validators.length}`)
const exists = validators.find(v => v.nodeID === V8.nodeID)
if (exists) {
console.log(`\n${V8.nodeID} is ALREADY a validator!`)
console.log(` weight: ${exists.weight}, uptime: ${exists.uptime}`)
console.log(`\nTotal validators: ${validators.length}`)
return
}
// 2. Check if keystore API is available
let keystoreAvailable = false
try {
await rpc('/v1/keystore', 'keystore.listUsers', {})
keystoreAvailable = true
} catch { }
if (!keystoreAvailable) {
// Keystore API not available on production nodes
// Need to use the wallet API or direct P-Chain transaction signing
console.log('\nKeystore API not available (production mode)')
console.log('Using P-Chain wallet transaction signing...\n')
}
// 3. Check P-Chain user balance
const { networkID } = await rpc('/v1/info', 'info.getNetworkID', {})
console.log(`Network ID: ${networkID}`)
// 4. Check if platform.spend is available (newer API)
try {
const txBytes = await buildAddValidatorTx(networkID)
if (txBytes) {
console.log('\nSubmitting AddPermissionlessValidator transaction...')
const { txID } = await rpc('/v1/chain/P', 'platform.issueTx', { tx: txBytes })
console.log(`TX submitted: ${txID}`)
console.log('Waiting for acceptance...')
// Poll for acceptance
for (let i = 0; i < 30; i++) {
await new Promise(r => setTimeout(r, 2000))
try {
const { status } = await rpc('/v1/chain/P', 'platform.getTxStatus', { txID })
console.log(` Status: ${status}`)
if (status === 'Accepted' || status === 'Committed') {
console.log('\nValidator added successfully!')
break
}
if (status === 'Rejected' || status === 'Dropped') {
console.log('\nTransaction rejected!')
break
}
} catch (e) {
// May not be available yet
}
}
// Verify
const { validators: updated } = await rpc('/v1/chain/P', 'platform.getCurrentValidators', { subnetID: '' })
console.log(`\nValidators after: ${updated.length}`)
return
}
} catch (e) {
console.log(`Direct TX approach not available: ${e.message}`)
}
// Fallback: use platform.addPermissionlessValidator if available
try {
const startTime = Math.floor(Date.now() / 1000) + 120 // 2 min from now
const endTime = startTime + 365 * 24 * 60 * 60 // 1 year
const result = await rpc('/v1/chain/P', 'platform.addPermissionlessValidator', {
nodeID: V8.nodeID,
subnetID: '11111111111111111111111111111111LpoYY', // primary network
startTime: String(startTime),
endTime: String(endTime),
weight: String(STAKE_NLUX),
signer: {
publicKey: `0x${V8.blsPub}`,
proofOfPossession: `0x${V8.blsPop}`,
},
delegationFeeRate: 20000, // 2%
rewardAddress: 'P-lux1c7wevm4667l4umtzh93r25wpxlpsadkhka6gv6',
changeAddress: 'P-lux1c7wevm4667l4umtzh93r25wpxlpsadkhka6gv6',
})
console.log('AddPermissionlessValidator result:', JSON.stringify(result, null, 2))
return
} catch (e) {
console.log(`addPermissionlessValidator not available: ${e.message}`)
}
// If all else fails, print manual instructions
console.log('\n=== Manual Validator Addition Required ===')
console.log('The production node does not expose keystore/wallet APIs.')
console.log('To add the 8th validator, use one of:')
console.log('')
console.log('Option 1: Lux Wallet (wallet.lux.network)')
console.log(' 1. Import mnemonic')
console.log(' 2. Export 3 LUX from C-Chain to P-Chain')
console.log(' 3. Add Validator with NodeID:', V8.nodeID)
console.log('')
console.log('Option 2: Enable keystore API on node temporarily')
console.log(' Add --api-keystore-enabled=true to node config')
console.log(' Then re-run this script')
console.log('')
console.log('Option 3: Local node with admin API')
console.log(' lux network start --mainnet')
console.log(' Then use keystore API on localhost:9630')
}
async function buildAddValidatorTx(networkID) {
// This would require the luxfi JS SDK for proper transaction building
// For now, return null to fall through to other methods
return null
}
main().catch(e => {
console.error('ERROR:', e.message)
process.exit(1)
})