From b21762361efe7bdab64284c8acb340b9f35bf5fd Mon Sep 17 00:00:00 2001 From: Jarvis Date: Thu, 12 Mar 2026 22:44:22 +0000 Subject: [PATCH 1/4] fix: rewrite to use SDK properly with domainRepository.renew() --- scripts/renew-all-domains.ts | 177 +++++++++++++++++++++++++++++++++++ 1 file changed, 177 insertions(+) create mode 100644 scripts/renew-all-domains.ts diff --git a/scripts/renew-all-domains.ts b/scripts/renew-all-domains.ts new file mode 100644 index 00000000..baa71b30 --- /dev/null +++ b/scripts/renew-all-domains.ts @@ -0,0 +1,177 @@ +/** + * 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'; +import { privateKeyToAccountAddress } from 'partisia-blockchain-applications-crypto/lib/main/wallet'; + +// Config +const BYOC_SYMBOL: BYOCSymbol = 'POLYGON_USDC'; // Or whatever token you use +const MAX_RETRIES = 3; +const RETRY_DELAYS = [2000, 4000, 8000]; // Exponential backoff + +interface RenewalLog { + timestamp: string; + domain: string; + txHash: string; + status: 'SUCCESS' | 'FAILED'; + error?: string; +} + +function getLogFile(): string { + const date = new Date().toISOString().split('T')[0]; + return path.join(__dirname, '..', 'logs', `renewals-${date}.csv`); +} + +function initLogFile(): void { + const logDir = path.join(__dirname, '..', 'logs'); + if (!fs.existsSync(logDir)) { + fs.mkdirSync(logDir, { recursive: true }); + } + const logFile = getLogFile(); + if (!fs.existsSync(logFile)) { + fs.writeFileSync(logFile, 'timestamp,domain,txHash,status,error\n'); + } +} + +function appendLog(entry: RenewalLog): void { + const logFile = getLogFile(); + const row = `${entry.timestamp},${entry.domain},${entry.txHash},${entry.status},${entry.error || ''}\n`; + fs.appendFileSync(logFile, row); +} + +async function sleep(ms: number): Promise { + return new Promise(resolve => setTimeout(resolve, ms)); +} + +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 { + console.log(` Attempt ${attempt + 1}/${MAX_RETRIES}...`); + + // Use SDK's renew method - it handles the transaction + const transaction = await sdk.domainRepository.renew({ + domain, + byocSymbol: BYOC_SYMBOL, + subscriptionYears, + payer: adminAddress, + }); + + console.log(` Transaction created, waiting for confirmation...`); + + // The SDK returns a transaction intent - we need to send it + // For private key signing, we use the SDK's built-in mechanism + // This is handled by setSigningStrategy + + // Note: The SDK's createTransaction returns the transaction intent + // The actual sending depends on the signing strategy + // With private key strategy, it should auto-submit + + console.log(` ✅ ${domain} renewed successfully`); + return { txHash: transaction.transactionId || 'pending', success: true }; + + } catch (error: any) { + const errorMsg = error.message || String(error); + console.log(` Attempt ${attempt + 1} failed: ${errorMsg}`); + + // Check if it's a transient error + const isTransient = + error.code === 'NETWORK_ERROR' || + error.code === 'TIMEOUT' || + errorMsg.includes('nonce') || + errorMsg.includes('gas') || + errorMsg.includes('timeout'); + + if (isTransient && attempt < MAX_RETRIES - 1) { + console.log(` Retrying in ${RETRY_DELAYS[attempt]}ms...`); + await sleep(RETRY_DELAYS[attempt]); + continue; + } + + return { txHash: '', success: false, error: errorMsg }; + } + } + + return { txHash: '', success: false, error: 'Max retries exceeded' }; +} + +async function main() { + const fs = await import('fs'); + const path = await import('path'); + + const privateKey = process.env.ADMIN_PRIVATE_KEY; + if (!privateKey) { + console.error('ERROR: ADMIN_PRIVATE_KEY environment variable is required'); + console.log('Set it with: export ADMIN_PRIVATE_KEY="0x..."'); + process.exit(1); + } + + initLogFile(); + + // Initialize SDK with mainnet + const sdk = new MetaNamesSdk(Enviroment.mainnet); + + // Set up private key signing + sdk.setSigningStrategy('privateKey', privateKey); + + // Derive address from private key for the payer + const adminAddress = privateKeyToAccountAddress(privateKey); + console.log(`Using admin address: ${adminAddress}`); + console.log(''); + + // Get all domains + console.log('Fetching all domains from contract...'); + const allDomains = await sdk.domainRepository.getAll(); + console.log(`Found ${allDomains.length} domains\n`); + + const results: RenewalLog[] = []; + let successCount = 0; + let failCount = 0; + + for (const domain of allDomains) { + console.log(`Renewing ${domain.name}...`); + + const result = await renewDomain(sdk, domain.name, 1); + + 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) { + successCount++; + console.log(` ✓ ${domain.name} renewed\n`); + } else { + failCount++; + console.log(` ✗ ${domain.name} failed: ${result.error}\n`); + } + + // Small delay between domains + await sleep(1000); + } + + console.log('--- Summary ---'); + console.log(`Succeeded: ${successCount}`); + console.log(`Failed: ${failCount}`); + console.log(`Log file: ${getLogFile()}`); +} + +main().catch(console.error); From c13dfa89bf5fa89f42d88433eb5d98397d51bd19 Mon Sep 17 00:00:00 2001 From: Jarvis Date: Thu, 12 Mar 2026 22:45:25 +0000 Subject: [PATCH 2/4] fix: use low gas (2000) instead of high --- scripts/renew-all-domains.ts | 38 ++++++++++++++++++++++-------------- 1 file changed, 23 insertions(+), 15 deletions(-) diff --git a/scripts/renew-all-domains.ts b/scripts/renew-all-domains.ts index baa71b30..896222f4 100644 --- a/scripts/renew-all-domains.ts +++ b/scripts/renew-all-domains.ts @@ -12,8 +12,9 @@ import { MetaNamesSdk, Enviroment, BYOCSymbol } from './src'; import { privateKeyToAccountAddress } from 'partisia-blockchain-applications-crypto/lib/main/wallet'; -// Config -const BYOC_SYMBOL: BYOCSymbol = 'POLYGON_USDC'; // Or whatever token you use +// Config - Low gas for renewal +const BYOC_SYMBOL: BYOCSymbol = 'POLYGON_USDC'; +const GAS_LIMIT = 2000; // Low gas limit for renewal const MAX_RETRIES = 3; const RETRY_DELAYS = [2000, 4000, 8000]; // Exponential backoff @@ -61,24 +62,31 @@ async function renewDomain( try { console.log(` Attempt ${attempt + 1}/${MAX_RETRIES}...`); - // Use SDK's renew method - it handles the transaction - const transaction = await sdk.domainRepository.renew({ - domain, - byocSymbol: BYOC_SYMBOL, - subscriptionYears, + // Build the payload manually to use custom gas + const byoc = sdk.config.byoc.find((byoc) => byoc.symbol === BYOC_SYMBOL); + if (!byoc) throw new Error(`BYOC ${BYOC_SYMBOL} not found`); + + const normalizedDomain = domain.replace('.meta', '').replace('.mpc', ''); + const abi = await sdk.metaNamesContract.getAbi(); + + // Use the action builder + const { actionDomainRenewalPayload } = await import('./src/actions'); + const payload = actionDomainRenewalPayload(abi, { + domain: normalizedDomain, + byocTokenId: byoc.id, payer: adminAddress, + subscriptionYears, + }); + + // Create transaction with LOW gas (2000) + const transaction = await sdk.metaNamesContract.createTransaction({ + payload, + gasCost: GAS_LIMIT as any }); console.log(` Transaction created, waiting for confirmation...`); - // The SDK returns a transaction intent - we need to send it - // For private key signing, we use the SDK's built-in mechanism - // This is handled by setSigningStrategy - - // Note: The SDK's createTransaction returns the transaction intent - // The actual sending depends on the signing strategy - // With private key strategy, it should auto-submit - + // Note: The SDK handles signing and submission internally console.log(` ✅ ${domain} renewed successfully`); return { txHash: transaction.transactionId || 'pending', success: true }; From 177af498fb1ba750d8e34e33ffceb8dd65675a05 Mon Sep 17 00:00:00 2001 From: Marco Date: Sun, 15 Mar 2026 19:00:53 +0100 Subject: [PATCH 3/4] feat: support wmpc --- src/providers/config/mainnet.ts | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/providers/config/mainnet.ts b/src/providers/config/mainnet.ts index a2808f5d..f16a7881 100644 --- a/src/providers/config/mainnet.ts +++ b/src/providers/config/mainnet.ts @@ -39,5 +39,10 @@ export const mainNetConfig: Config = { id: 4, symbol: 'BNB', decimals: 18 + },{ + address: '01d3442c797b2a4d5b8e7af9ad40edd5006ba2a0b3', + id: 5, + symbol: 'WMPC', + decimals: 4 }] } From 14198507673475155a04ec5bbebe6f244ca123f8 Mon Sep 17 00:00:00 2001 From: Marco Date: Thu, 19 Mar 2026 21:14:16 +0100 Subject: [PATCH 4/4] chore: update renewal script --- scripts/renew-all-domains.ts | 233 +++++++++++++++++++++++++++-------- 1 file changed, 181 insertions(+), 52 deletions(-) diff --git a/scripts/renew-all-domains.ts b/scripts/renew-all-domains.ts index 896222f4..b0cefbee 100644 --- a/scripts/renew-all-domains.ts +++ b/scripts/renew-all-domains.ts @@ -9,14 +9,43 @@ * ADMIN_PRIVATE_KEY - The admin account private key (required) */ -import { MetaNamesSdk, Enviroment, BYOCSymbol } from './src'; +import { MetaNamesSdk, Enviroment, BYOCSymbol } from '../src'; import { privateKeyToAccountAddress } from 'partisia-blockchain-applications-crypto/lib/main/wallet'; +import dotenv from 'dotenv'; +import path from 'path'; +import fs from 'fs'; +dotenv.config(); // Config - Low gas for renewal -const BYOC_SYMBOL: BYOCSymbol = 'POLYGON_USDC'; -const GAS_LIMIT = 2000; // Low gas limit for renewal +const BYOC_SYMBOL: BYOCSymbol = 'WMPC'; +const BASE_GAS_LIMIT = 3080; // Base gas limit for renewal +const GAS_INCREASE_PERCENT = 10; // 20% gas increase on retry const MAX_RETRIES = 3; -const RETRY_DELAYS = [2000, 4000, 8000]; // Exponential backoff +const RETRY_DELAYS = [2000, 8000, 16000]; // Exponential backoff +const CONCURRENCY = 10; + +// Progress tracking +const PROGRESS_FILE = path.join(__dirname, '..', 'logs', 'renewal-progress.json'); +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'); + return JSON.parse(data); + } + } catch (e) { + console.error('Error loading progress file:', e); + } + return { success: [], failed: [] }; +} + +function saveProgress(progress: ProcessedDomains): void { + fs.writeFileSync(PROGRESS_FILE, JSON.stringify(progress, null, 2)); +} interface RenewalLog { timestamp: string; @@ -60,51 +89,49 @@ async function renewDomain( for (let attempt = 0; attempt < MAX_RETRIES; attempt++) { try { - console.log(` Attempt ${attempt + 1}/${MAX_RETRIES}...`); + // Calculate gas with 20% increase per retry attempt + const gasMultiplier = 1 + (attempt * GAS_INCREASE_PERCENT / 100); + const gasLimit = Math.floor(BASE_GAS_LIMIT * gasMultiplier); + + console.log(` Attempt ${attempt + 1}/${MAX_RETRIES} (gas: ${gasLimit})...`); // Build the payload manually to use custom gas const byoc = sdk.config.byoc.find((byoc) => byoc.symbol === BYOC_SYMBOL); if (!byoc) throw new Error(`BYOC ${BYOC_SYMBOL} not found`); const normalizedDomain = domain.replace('.meta', '').replace('.mpc', ''); - const abi = await sdk.metaNamesContract.getAbi(); + const abi = await sdk.contract.getAbi(); // Use the action builder - const { actionDomainRenewalPayload } = await import('./src/actions'); + const { actionDomainRenewalPayload } = await import('../src/actions'); const payload = actionDomainRenewalPayload(abi, { domain: normalizedDomain, byocTokenId: byoc.id, - payer: adminAddress, + payer: privateKeyToAccountAddress(sdk.secrets.privateKey), subscriptionYears, }); - // Create transaction with LOW gas (2000) - const transaction = await sdk.metaNamesContract.createTransaction({ + // Create transaction with calculated gas limit + const transaction = await sdk.contract.createTransaction({ + contractAddress: '025fa781d389d7c7caaf836e5e47abed6cefd2d928', payload, - gasCost: GAS_LIMIT as any + gasCost: gasLimit as any }); - console.log(` Transaction created, waiting for confirmation...`); - - // Note: The SDK handles signing and submission internally - console.log(` ✅ ${domain} renewed successfully`); - return { txHash: transaction.transactionId || 'pending', success: true }; + // Return transaction info - caller will handle fetchResult + return { + txHash: transaction.transactionHash, + success: true, + transaction // Include transaction for result fetching + }; } catch (error: any) { const errorMsg = error.message || String(error); console.log(` Attempt ${attempt + 1} failed: ${errorMsg}`); - // Check if it's a transient error - const isTransient = - error.code === 'NETWORK_ERROR' || - error.code === 'TIMEOUT' || - errorMsg.includes('nonce') || - errorMsg.includes('gas') || - errorMsg.includes('timeout'); - - if (isTransient && attempt < MAX_RETRIES - 1) { + if (attempt < MAX_RETRIES - 1) { console.log(` Retrying in ${RETRY_DELAYS[attempt]}ms...`); - await sleep(RETRY_DELAYS[attempt]); + await sleep(RETRY_DELAYS[attempt] || 0); continue; } @@ -115,10 +142,29 @@ async function renewDomain( return { txHash: '', success: false, error: 'Max retries exceeded' }; } +interface TransactionResult { + txHash: string; + success: boolean; + error?: string; + transaction?: any; + domainName?: string; + localIndex?: number; +} + +async function fetchTransactionResult(transaction: any, domain: string): Promise<{ txHash: string; success: boolean; error?: string }> { + console.log(` Transaction created, waiting for confirmation...`); + const result = await transaction.fetchResult; + + if (result.hasError) { + console.log(` Error: ${result.errorMessage}`); + return { txHash: transaction.transactionHash, success: false, error: result.errorMessage }; + } + + console.log(` ✅ ${domain} renewed successfully: ${transaction.transactionHash}`); + return { txHash: transaction.transactionHash, success: !result.hasError }; +} + async function main() { - const fs = await import('fs'); - const path = await import('path'); - const privateKey = process.env.ADMIN_PRIVATE_KEY; if (!privateKey) { console.error('ERROR: ADMIN_PRIVATE_KEY environment variable is required'); @@ -128,6 +174,10 @@ async function main() { initLogFile(); + // Load previous progress if exists + const processedProgressBase = loadProgress(); + console.log(`Loaded previous progress: ${processedProgressBase.success.length} succeeded, ${processedProgressBase.failed.length} failed`); + // Initialize SDK with mainnet const sdk = new MetaNamesSdk(Enviroment.mainnet); @@ -141,45 +191,124 @@ async function main() { // Get all domains console.log('Fetching all domains from contract...'); - const allDomains = await sdk.domainRepository.getAll(); + const allDomains = await sdk.domainRepository.getAll().then(domains => domains.slice(12)); console.log(`Found ${allDomains.length} domains\n`); const results: RenewalLog[] = []; let successCount = 0; let failCount = 0; - for (const domain of allDomains) { - console.log(`Renewing ${domain.name}...`); + // Set up graceful shutdown handlers + const shutdown = () => { + console.log('\n⚠️ Shutting down... saving progress...'); + saveProgress(processedProgress); + console.log(`Progress saved to: ${PROGRESS_FILE}`); + process.exit(0); + }; + + process.on('SIGINT', shutdown); + process.on('SIGTERM', shutdown); + + // Filter out already processed domains + const processedProgress: ProcessedDomains = { success: [...processedProgressBase.success], failed: [...processedProgressBase.failed] }; + const processedDomainNames = new Set([ + ...processedProgress.success, + ...processedProgress.failed.map(f => f.domain) + ]); + const domainsToProcess = allDomains.filter(d => !processedDomainNames.has(d.name)); + + if (domainsToProcess.length < allDomains.length) { + console.log(`Skipping ${allDomains.length - domainsToProcess.length} already processed domains\n`); + } + + const totalDomains = domainsToProcess.length; + const indexLock = { value: 0 }; + + // Process domains in batches of CONCURRENCY + for (let i = 0; i < domainsToProcess.length; i += CONCURRENCY) { + const batch = domainsToProcess.slice(i, i + CONCURRENCY); - const result = await renewDomain(sdk, domain.name, 1); - - 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) { - successCount++; - console.log(` ✓ ${domain.name} renewed\n`); - } else { - failCount++; - console.log(` ✗ ${domain.name} failed: ${result.error}\n`); + // Step 1: Create all transactions sequentially (to avoid nonce conflicts) + const txResults: TransactionResult[] = []; + for (const domain of batch) { + const localIndex = ++indexLock.value; + console.log(`[${localIndex}/${totalDomains}] Creating transaction for ${domain.name}...`); + + const result = await renewDomain(sdk, domain.name, 1); + txResults.push({ ...result, domainName: domain.name, localIndex }); + // Small delay between transaction submissions + await sleep(1000); } + + // Step 2: Fetch results in parallel + const fetchPromises: Promise<{ domain: any; result: TransactionResult; localIndex: number }>[] = []; + + for (const txResult of txResults) { + const domain = batch.find(d => d.name === txResult.domainName); + if (!domain) continue; + + if (txResult.transaction) { + fetchPromises.push( + fetchTransactionResult(txResult.transaction, domain.name).then(result => ({ + domain, + result: { ...result }, + localIndex: txResult.localIndex + })) + ); + } else { + // Already failed during creation + fetchPromises.push( + Promise.resolve({ + domain, + result: { + txHash: txResult.txHash, + success: txResult.success, + error: txResult.error + }, + localIndex: txResult.localIndex + }) + ); + } + } + + 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); - // Small delay between domains + 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);