Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
314 changes: 314 additions & 0 deletions scripts/renew-all-domains.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,314 @@
/**
* Domain Renewal Script
* Renews all domains in the MetaNames contract for 1 extra year
*
* Usage:
* ADMIN_PRIVATE_KEY=0x... npx tsx scripts/renew-all-domains.ts
*
* Environment:
* ADMIN_PRIVATE_KEY - The admin account private key (required)
*/

import { MetaNamesSdk, Enviroment, BYOCSymbol } from '../src';

Check failure

Code scanning / ESLint

Require or disallow semicolons instead of ASI Error

Extra semicolon.
import { privateKeyToAccountAddress } from 'partisia-blockchain-applications-crypto/lib/main/wallet';

Check failure

Code scanning / ESLint

Require or disallow semicolons instead of ASI Error

Extra semicolon.
import dotenv from 'dotenv';

Check failure

Code scanning / ESLint

Require or disallow semicolons instead of ASI Error

Extra semicolon.
import path from 'path';

Check failure

Code scanning / ESLint

Require or disallow semicolons instead of ASI Error

Extra semicolon.
import fs from 'fs';

Check failure

Code scanning / ESLint

Require or disallow semicolons instead of ASI Error

Extra semicolon.
dotenv.config();

Check failure

Code scanning / ESLint

Require or disallow semicolons instead of ASI Error

Extra semicolon.

// Config - Low gas for renewal
const BYOC_SYMBOL: BYOCSymbol = 'WMPC';

Check failure

Code scanning / ESLint

Require or disallow semicolons instead of ASI Error

Extra semicolon.
const BASE_GAS_LIMIT = 3080; // Base gas limit for renewal

Check failure

Code scanning / ESLint

Require or disallow semicolons instead of ASI Error

Extra semicolon.
const GAS_INCREASE_PERCENT = 10; // 20% gas increase on retry

Check failure

Code scanning / ESLint

Require or disallow semicolons instead of ASI Error

Extra semicolon.
const MAX_RETRIES = 3;

Check failure

Code scanning / ESLint

Require or disallow semicolons instead of ASI Error

Extra semicolon.
const RETRY_DELAYS = [2000, 8000, 16000]; // Exponential backoff

Check failure

Code scanning / ESLint

Require or disallow semicolons instead of ASI Error

Extra semicolon.
const CONCURRENCY = 10;

Check failure

Code scanning / ESLint

Require or disallow semicolons instead of ASI Error

Extra semicolon.

// Progress tracking
const PROGRESS_FILE = path.join(__dirname, '..', 'logs', 'renewal-progress.json');

Check failure

Code scanning / ESLint

Require or disallow semicolons instead of ASI Error

Extra semicolon.
interface ProcessedDomains {
success: string[];
failed: { domain: string; error: string }[];
}

function loadProgress(): ProcessedDomains {
try {
if (fs.existsSync(PROGRESS_FILE)) {
const data = fs.readFileSync(PROGRESS_FILE, 'utf-8');

Check failure

Code scanning / ESLint

Require or disallow semicolons instead of ASI Error

Extra semicolon.
return JSON.parse(data);

Check failure

Code scanning / ESLint

Require or disallow semicolons instead of ASI Error

Extra semicolon.
}
} catch (e) {
console.error('Error loading progress file:', e);

Check failure

Code scanning / ESLint

Require or disallow semicolons instead of ASI Error

Extra semicolon.
}
return { success: [], failed: [] };

Check failure

Code scanning / ESLint

Require or disallow semicolons instead of ASI Error

Extra semicolon.
}

function saveProgress(progress: ProcessedDomains): void {
fs.writeFileSync(PROGRESS_FILE, JSON.stringify(progress, null, 2));

Check failure

Code scanning / ESLint

Require or disallow semicolons instead of ASI Error

Extra semicolon.
}

interface RenewalLog {
timestamp: string;
domain: string;
txHash: string;
status: 'SUCCESS' | 'FAILED';
error?: string;
}

function getLogFile(): string {
const date = new Date().toISOString().split('T')[0];

Check failure

Code scanning / ESLint

Require or disallow semicolons instead of ASI Error

Extra semicolon.
return path.join(__dirname, '..', 'logs', `renewals-${date}.csv`);

Check failure

Code scanning / ESLint

Require or disallow semicolons instead of ASI Error

Extra semicolon.
}

function initLogFile(): void {
const logDir = path.join(__dirname, '..', 'logs');

Check failure

Code scanning / ESLint

Require or disallow semicolons instead of ASI Error

Extra semicolon.
if (!fs.existsSync(logDir)) {
fs.mkdirSync(logDir, { recursive: true });

Check failure

Code scanning / ESLint

Require or disallow semicolons instead of ASI Error

Extra semicolon.
}
const logFile = getLogFile();

Check failure

Code scanning / ESLint

Require or disallow semicolons instead of ASI Error

Extra semicolon.
if (!fs.existsSync(logFile)) {
fs.writeFileSync(logFile, 'timestamp,domain,txHash,status,error\n');

Check failure

Code scanning / ESLint

Require or disallow semicolons instead of ASI Error

Extra semicolon.
}
}

function appendLog(entry: RenewalLog): void {
const logFile = getLogFile();

Check failure

Code scanning / ESLint

Require or disallow semicolons instead of ASI Error

Extra semicolon.
const row = `${entry.timestamp},${entry.domain},${entry.txHash},${entry.status},${entry.error || ''}\n`;

Check failure

Code scanning / ESLint

Require or disallow semicolons instead of ASI Error

Extra semicolon.
fs.appendFileSync(logFile, row);

Check failure

Code scanning / ESLint

Require or disallow semicolons instead of ASI Error

Extra semicolon.
}

async function sleep(ms: number): Promise<void> {
return new Promise(resolve => setTimeout(resolve, ms));

Check failure

Code scanning / ESLint

Require or disallow semicolons instead of ASI Error

Extra semicolon.
}

async function renewDomain(
sdk: MetaNamesSdk,
domain: string,
subscriptionYears: number = 1
): Promise<{ txHash: string; success: boolean; error?: string }> {

for (let attempt = 0; attempt < MAX_RETRIES; attempt++) {
try {
// Calculate gas with 20% increase per retry attempt
const gasMultiplier = 1 + (attempt * GAS_INCREASE_PERCENT / 100);

Check failure

Code scanning / ESLint

Require or disallow semicolons instead of ASI Error

Extra semicolon.
const gasLimit = Math.floor(BASE_GAS_LIMIT * gasMultiplier);

Check failure

Code scanning / ESLint

Require or disallow semicolons instead of ASI Error

Extra semicolon.

console.log(` Attempt ${attempt + 1}/${MAX_RETRIES} (gas: ${gasLimit})...`);

Check failure

Code scanning / ESLint

Require or disallow semicolons instead of ASI Error

Extra semicolon.

// Build the payload manually to use custom gas
const byoc = sdk.config.byoc.find((byoc) => byoc.symbol === BYOC_SYMBOL);

Check failure

Code scanning / ESLint

Require or disallow semicolons instead of ASI Error

Extra semicolon.
if (!byoc) throw new Error(`BYOC ${BYOC_SYMBOL} not found`);

Check failure

Code scanning / ESLint

Require or disallow semicolons instead of ASI Error

Extra semicolon.

const normalizedDomain = domain.replace('.meta', '').replace('.mpc', '');

Check failure

Code scanning / ESLint

Require or disallow semicolons instead of ASI Error

Extra semicolon.
const abi = await sdk.contract.getAbi();

Check failure

Code scanning / ESLint

Require or disallow semicolons instead of ASI Error

Extra semicolon.

// Use the action builder
const { actionDomainRenewalPayload } = await import('../src/actions');

Check failure

Code scanning / ESLint

Require or disallow semicolons instead of ASI Error

Extra semicolon.
const payload = actionDomainRenewalPayload(abi, {
domain: normalizedDomain,
byocTokenId: byoc.id,
payer: privateKeyToAccountAddress(sdk.secrets.privateKey),
subscriptionYears,
});

Check failure

Code scanning / ESLint

Require or disallow semicolons instead of ASI Error

Extra semicolon.

// Create transaction with calculated gas limit
const transaction = await sdk.contract.createTransaction({
contractAddress: '025fa781d389d7c7caaf836e5e47abed6cefd2d928',
payload,
gasCost: gasLimit as any

Check failure

Code scanning / ESLint

Disallow the `any` type Error

Unexpected any. Specify a different type.
});

Check failure

Code scanning / ESLint

Require or disallow semicolons instead of ASI Error

Extra semicolon.

// Return transaction info - caller will handle fetchResult
return {
txHash: transaction.transactionHash,
success: true,
transaction // Include transaction for result fetching
};

Check failure

Code scanning / ESLint

Require or disallow semicolons instead of ASI Error

Extra semicolon.

} catch (error: any) {

Check failure

Code scanning / ESLint

Disallow the `any` type Error

Unexpected any. Specify a different type.
const errorMsg = error.message || String(error);

Check failure

Code scanning / ESLint

Require or disallow semicolons instead of ASI Error

Extra semicolon.
console.log(` Attempt ${attempt + 1} failed: ${errorMsg}`);

Check failure

Code scanning / ESLint

Require or disallow semicolons instead of ASI Error

Extra semicolon.

if (attempt < MAX_RETRIES - 1) {
console.log(` Retrying in ${RETRY_DELAYS[attempt]}ms...`);

Check failure

Code scanning / ESLint

Require or disallow semicolons instead of ASI Error

Extra semicolon.
await sleep(RETRY_DELAYS[attempt] || 0);

Check failure

Code scanning / ESLint

Require or disallow semicolons instead of ASI Error

Extra semicolon.
continue;

Check failure

Code scanning / ESLint

Require or disallow semicolons instead of ASI Error

Extra semicolon.
}

return { txHash: '', success: false, error: errorMsg };

Check failure

Code scanning / ESLint

Require or disallow semicolons instead of ASI Error

Extra semicolon.
}
}

return { txHash: '', success: false, error: 'Max retries exceeded' };

Check failure

Code scanning / ESLint

Require or disallow semicolons instead of ASI Error

Extra semicolon.
}

interface TransactionResult {
txHash: string;
success: boolean;
error?: string;
transaction?: any;

Check failure

Code scanning / ESLint

Disallow the `any` type Error

Unexpected any. Specify a different type.
domainName?: string;
localIndex?: number;
}

async function fetchTransactionResult(transaction: any, domain: string): Promise<{ txHash: string; success: boolean; error?: string }> {

Check failure

Code scanning / ESLint

Disallow the `any` type Error

Unexpected any. Specify a different type.
console.log(` Transaction created, waiting for confirmation...`);

Check failure

Code scanning / ESLint

Require or disallow semicolons instead of ASI Error

Extra semicolon.
const result = await transaction.fetchResult;

Check failure

Code scanning / ESLint

Require or disallow semicolons instead of ASI Error

Extra semicolon.

if (result.hasError) {
console.log(` Error: ${result.errorMessage}`);

Check failure

Code scanning / ESLint

Require or disallow semicolons instead of ASI Error

Extra semicolon.
return { txHash: transaction.transactionHash, success: false, error: result.errorMessage };

Check failure

Code scanning / ESLint

Require or disallow semicolons instead of ASI Error

Extra semicolon.
}

console.log(` ✅ ${domain} renewed successfully: ${transaction.transactionHash}`);

Check failure

Code scanning / ESLint

Require or disallow semicolons instead of ASI Error

Extra semicolon.
return { txHash: transaction.transactionHash, success: !result.hasError };

Check failure

Code scanning / ESLint

Require or disallow semicolons instead of ASI Error

Extra semicolon.
}

async function main() {
const privateKey = process.env.ADMIN_PRIVATE_KEY;

Check failure

Code scanning / ESLint

Require or disallow semicolons instead of ASI Error

Extra semicolon.
if (!privateKey) {
console.error('ERROR: ADMIN_PRIVATE_KEY environment variable is required');

Check failure

Code scanning / ESLint

Require or disallow semicolons instead of ASI Error

Extra semicolon.
console.log('Set it with: export ADMIN_PRIVATE_KEY="0x..."');

Check failure

Code scanning / ESLint

Require or disallow semicolons instead of ASI Error

Extra semicolon.
process.exit(1);

Check failure

Code scanning / ESLint

Require or disallow semicolons instead of ASI Error

Extra semicolon.
}

initLogFile();

Check failure

Code scanning / ESLint

Require or disallow semicolons instead of ASI Error

Extra semicolon.

// Load previous progress if exists
const processedProgressBase = loadProgress();

Check failure

Code scanning / ESLint

Require or disallow semicolons instead of ASI Error

Extra semicolon.
console.log(`Loaded previous progress: ${processedProgressBase.success.length} succeeded, ${processedProgressBase.failed.length} failed`);

Check failure

Code scanning / ESLint

Require or disallow semicolons instead of ASI Error

Extra semicolon.

// Initialize SDK with mainnet
const sdk = new MetaNamesSdk(Enviroment.mainnet);

Check failure

Code scanning / ESLint

Require or disallow semicolons instead of ASI Error

Extra semicolon.

// Set up private key signing
sdk.setSigningStrategy('privateKey', privateKey);

Check failure

Code scanning / ESLint

Require or disallow semicolons instead of ASI Error

Extra semicolon.

// Derive address from private key for the payer
const adminAddress = privateKeyToAccountAddress(privateKey);

Check failure

Code scanning / ESLint

Require or disallow semicolons instead of ASI Error

Extra semicolon.
console.log(`Using admin address: ${adminAddress}`);

Check failure

Code scanning / ESLint

Require or disallow semicolons instead of ASI Error

Extra semicolon.
console.log('');

Check failure

Code scanning / ESLint

Require or disallow semicolons instead of ASI Error

Extra semicolon.

// Get all domains
console.log('Fetching all domains from contract...');

Check failure

Code scanning / ESLint

Require or disallow semicolons instead of ASI Error

Extra semicolon.
const allDomains = await sdk.domainRepository.getAll().then(domains => domains.slice(12));

Check failure

Code scanning / ESLint

Require or disallow semicolons instead of ASI Error

Extra semicolon.
console.log(`Found ${allDomains.length} domains\n`);

Check failure

Code scanning / ESLint

Require or disallow semicolons instead of ASI Error

Extra semicolon.

const results: RenewalLog[] = [];

Check failure

Code scanning / ESLint

Require or disallow semicolons instead of ASI Error

Extra semicolon.
let successCount = 0;

Check failure

Code scanning / ESLint

Require or disallow semicolons instead of ASI Error

Extra semicolon.
let failCount = 0;

Check failure

Code scanning / ESLint

Require or disallow semicolons instead of ASI Error

Extra semicolon.

// Set up graceful shutdown handlers
const shutdown = () => {
console.log('\n⚠️ Shutting down... saving progress...');

Check failure

Code scanning / ESLint

Require or disallow semicolons instead of ASI Error

Extra semicolon.
saveProgress(processedProgress);

Check failure

Code scanning / ESLint

Require or disallow semicolons instead of ASI Error

Extra semicolon.
console.log(`Progress saved to: ${PROGRESS_FILE}`);

Check failure

Code scanning / ESLint

Require or disallow semicolons instead of ASI Error

Extra semicolon.
process.exit(0);

Check failure

Code scanning / ESLint

Require or disallow semicolons instead of ASI Error

Extra semicolon.
};

Check failure

Code scanning / ESLint

Require or disallow semicolons instead of ASI Error

Extra semicolon.

process.on('SIGINT', shutdown);

Check failure

Code scanning / ESLint

Require or disallow semicolons instead of ASI Error

Extra semicolon.
process.on('SIGTERM', shutdown);

Check failure

Code scanning / ESLint

Require or disallow semicolons instead of ASI Error

Extra semicolon.

// Filter out already processed domains
const processedProgress: ProcessedDomains = { success: [...processedProgressBase.success], failed: [...processedProgressBase.failed] };

Check failure

Code scanning / ESLint

Require or disallow semicolons instead of ASI Error

Extra semicolon.
const processedDomainNames = new Set([
...processedProgress.success,
...processedProgress.failed.map(f => f.domain)
]);

Check failure

Code scanning / ESLint

Require or disallow semicolons instead of ASI Error

Extra semicolon.
const domainsToProcess = allDomains.filter(d => !processedDomainNames.has(d.name));

Check failure

Code scanning / ESLint

Require or disallow semicolons instead of ASI Error

Extra semicolon.

if (domainsToProcess.length < allDomains.length) {
console.log(`Skipping ${allDomains.length - domainsToProcess.length} already processed domains\n`);

Check failure

Code scanning / ESLint

Require or disallow semicolons instead of ASI Error

Extra semicolon.
}

const totalDomains = domainsToProcess.length;

Check failure

Code scanning / ESLint

Require or disallow semicolons instead of ASI Error

Extra semicolon.
const indexLock = { value: 0 };

Check failure

Code scanning / ESLint

Require or disallow semicolons instead of ASI Error

Extra semicolon.

// Process domains in batches of CONCURRENCY
for (let i = 0; i < domainsToProcess.length; i += CONCURRENCY) {
const batch = domainsToProcess.slice(i, i + CONCURRENCY);

Check failure

Code scanning / ESLint

Require or disallow semicolons instead of ASI Error

Extra semicolon.

// Step 1: Create all transactions sequentially (to avoid nonce conflicts)
const txResults: TransactionResult[] = [];

Check failure

Code scanning / ESLint

Require or disallow semicolons instead of ASI Error

Extra semicolon.
for (const domain of batch) {
const localIndex = ++indexLock.value;

Check failure

Code scanning / ESLint

Require or disallow semicolons instead of ASI Error

Extra semicolon.
console.log(`[${localIndex}/${totalDomains}] Creating transaction for ${domain.name}...`);

Check failure

Code scanning / ESLint

Require or disallow semicolons instead of ASI Error

Extra semicolon.

const result = await renewDomain(sdk, domain.name, 1);

Check failure

Code scanning / ESLint

Require or disallow semicolons instead of ASI Error

Extra semicolon.
txResults.push({ ...result, domainName: domain.name, localIndex });

Check failure

Code scanning / ESLint

Require or disallow semicolons instead of ASI Error

Extra semicolon.
// Small delay between transaction submissions
await sleep(1000);

Check failure

Code scanning / ESLint

Require or disallow semicolons instead of ASI Error

Extra semicolon.
}

// Step 2: Fetch results in parallel
const fetchPromises: Promise<{ domain: any; result: TransactionResult; localIndex: number }>[] = [];

Check failure

Code scanning / ESLint

Disallow the `any` type Error

Unexpected any. Specify a different type.

Check failure

Code scanning / ESLint

Require or disallow semicolons instead of ASI Error

Extra semicolon.

for (const txResult of txResults) {
const domain = batch.find(d => d.name === txResult.domainName);

Check failure

Code scanning / ESLint

Require or disallow semicolons instead of ASI Error

Extra semicolon.
if (!domain) continue;

Check failure

Code scanning / ESLint

Require or disallow semicolons instead of ASI Error

Extra semicolon.

if (txResult.transaction) {
fetchPromises.push(
fetchTransactionResult(txResult.transaction, domain.name).then(result => ({
domain,
result: { ...result },
localIndex: txResult.localIndex
}))
);

Check failure

Code scanning / ESLint

Require or disallow semicolons instead of ASI Error

Extra semicolon.
} else {
// Already failed during creation
fetchPromises.push(
Promise.resolve({
domain,
result: {
txHash: txResult.txHash,
success: txResult.success,
error: txResult.error
},
localIndex: txResult.localIndex
})
);

Check failure

Code scanning / ESLint

Require or disallow semicolons instead of ASI Error

Extra semicolon.
}
}

const fetchResults = await Promise.all(fetchPromises);

// Process results and save progress
for (const { domain, result, localIndex } of fetchResults) {
const entry: RenewalLog = {
timestamp: new Date().toISOString(),
domain: domain.name,
txHash: result.txHash,
status: result.success ? 'SUCCESS' : 'FAILED',
error: result.success ? undefined : result.error
};

appendLog(entry);
results.push(entry);

if (result.success) {
processedProgress.success.push(domain.name);
successCount++;
console.log(` ✓ [${localIndex}/${totalDomains}] ${domain.name} renewed`);
} else {
processedProgress.failed.push({ domain: domain.name, error: result.error || 'Unknown error' });
failCount++;
console.log(` ✗ [${localIndex}/${totalDomains}] ${domain.name} failed: ${result.error}`);
}

saveProgress(processedProgress);
}

// Small delay between batches
await sleep(1000);
}

console.log('');
console.log('--- Summary ---');
console.log(`Succeeded: ${successCount}`);
console.log(`Failed: ${failCount}`);
console.log(`Log file: ${getLogFile()}`);
console.log(`Progress file: ${PROGRESS_FILE}`);
}

main().catch(console.error);
5 changes: 5 additions & 0 deletions src/providers/config/mainnet.ts
Original file line number Diff line number Diff line change
Expand Up @@ -39,5 +39,10 @@ export const mainNetConfig: Config = {
id: 4,
symbol: 'BNB',
decimals: 18
},{
address: '01d3442c797b2a4d5b8e7af9ad40edd5006ba2a0b3',
id: 5,
symbol: 'WMPC',
decimals: 4
}]
}
Loading