#!/usr/bin/env node import { select, input, confirm, password } from '@inquirer/prompts'; import * as dotenv from 'dotenv' import { ethers, utils } from "ethers"; import { getAxieIdsFromAccount } from "./lib/axie"; import { refreshToken } from "./lib/marketplace/access-token"; import buyMarketplaceOrder from "./lib/marketplace/buy-order"; import cancelMarketplaceOrder from "./lib/marketplace/cancel-order"; import createMarketplaceOrder from "./lib/marketplace/create-order"; import { batchTransferAxies, transferAxie } from "./lib/transfers"; import { getAxieContract, getWETHContract } from "./lib/contracts" import { MARKETPLACE_GATEWAY_V2 } from "@roninbuilders/contracts" import { askToContinue, getAxieId } from './lib/utils'; dotenv.config() async function ensureMarketplaceToken(): Promise { if (!process.env.MARKETPLACE_ACCESS_TOKEN) { const token = await input({ message: '🔑 Enter your Marketplace access token:', validate: (value) => value !== undefined && value !== '' }); process.env.MARKETPLACE_ACCESS_TOKEN = token; } return process.env.MARKETPLACE_ACCESS_TOKEN!; } async function main() { // Ask for Skymavis API key at startup if not in env (optional) let skyMavisApiKey = process.env.SKYMAVIS_DAPP_KEY; if (!skyMavisApiKey) { const shouldProvideKey = await confirm({ message: '🔑 Would you like to provide a Skymavis DApp API key? (Recommended for better rate limits)', default: false }); if (shouldProvideKey) { skyMavisApiKey = await input({ message: '🔑 Enter your Skymavis DApp API key:', validate: (value) => value !== undefined && value !== '' }); } } // Check for PRIVATE_KEY before the main loop let privateKey = process.env.PRIVATE_KEY; if (!privateKey) { privateKey = await password({ message: '🔐 Enter your private key:', validate: (value) => { if (!value) return false; // Private keys are 32 bytes (64 characters) + optional "0x" prefix return ethers.utils.isHexString(value, 32) || ethers.utils.isHexString(`0x${value}`, 32); } }); // Ensure "0x" prefix privateKey = privateKey.startsWith('0x') ? privateKey : `0x${privateKey}`; } while (true) { // Initialize provider with the API key from above const provider = new ethers.providers.JsonRpcProvider( skyMavisApiKey ? `https://api-gateway.skymavis.com/rpc?apikey=${skyMavisApiKey}` : "https://api.roninchain.com/rpc" ); // Initialize wallet with the previously obtained private key const wallet = new ethers.Wallet(privateKey, provider); const address = await wallet.getAddress(); try { const action = await select({ message: 'What would you like to do?', choices: [ { name: 'Get account info', value: 'account' }, { name: 'Refresh access token', value: 'refresh-token' }, { name: 'Approve WETH', value: 'approve-weth' }, { name: 'Approve marketplace', value: 'approve-marketplace' }, { name: 'Buy axie', value: 'buy' }, { name: 'Delist axie', value: 'delist' }, { name: 'Delist all axies', value: 'delist-all' }, { name: 'List axie', value: 'list' }, { name: 'List all axies', value: 'list-all' }, { name: 'Transfer axie', value: 'transfer' }, { name: 'Transfer all axies', value: 'transfer-all' }, ] }); switch (action) { case 'account': { const axieIds = await getAxieIdsFromAccount(address, provider); console.log(`📬 Address: ${address}`); const wethContract = getWETHContract(wallet) // Get RON balance const balance = await provider.getBalance(address); const balanceInEther = utils.formatEther(balance); console.log('💰 RON Balance:', balanceInEther); // Get WETH balance const wethBalance = await wethContract.balanceOf(address); const wethBalanceInEther = utils.formatEther(wethBalance); console.log('💰 WETH Balance:', wethBalanceInEther); // WETH allowance const allowance = await wethContract.allowance(address, MARKETPLACE_GATEWAY_V2.address); console.log('🛒 Marketplace WETH allowance:', !allowance.eq(0) ? '✅ Granted' : '❌ Not granted') // get axie contract const axieContract = getAxieContract(provider) // check if marketplace is approved for axies const isApprovedForAll = await axieContract.isApprovedForAll(address, MARKETPLACE_GATEWAY_V2.address) console.log('🔐 Marketplace approval for Axies:', isApprovedForAll ? '✅ Approved' : '❌ Not approved') console.log(`🐾 Number of Axies: ${axieIds.length}`); if (axieIds.length > 0) { console.log(`🆔 Axie IDs: ${axieIds.join(', ')}`); } break; } case 'refresh-token': { let refreshTokenValue = process.env.MARKETPLACE_REFRESH_TOKEN; if (!refreshTokenValue) { refreshTokenValue = await input({ message: 'Enter refresh token', validate: (value) => value.length > 0 }); } const result = await refreshToken(refreshTokenValue); console.log('New access token:', result.newAccessToken); console.log('New refresh token:', result.newRefreshToken); process.env.MARKETPLACE_ACCESS_TOKEN = result.newAccessToken; process.env.MARKETPLACE_REFRESH_TOKEN = result.newRefreshToken; break; } case 'approve-weth': { const wethContract = getWETHContract(wallet); const amountToApprove = '115792089237316195423570985008687907853269984665640564039457584007913129639935'; const address = await wallet.getAddress(); console.log(`Approving Marketplace (${MARKETPLACE_GATEWAY_V2.address}) to spend ${amountToApprove} WETH for the address ${address}`); const txApproveWETH = await wethContract.approve(MARKETPLACE_GATEWAY_V2.address, amountToApprove, { gasPrice: utils.parseUnits('20', 'gwei') }); const txApproveReceipt = await txApproveWETH.wait(); console.log('✅ WETH approved! Transaction hash:', txApproveReceipt.transactionHash); break; } case 'approve-marketplace': { const axieContract = getAxieContract(wallet); const address = await wallet.getAddress(); console.log(`Approving Marketplace (${MARKETPLACE_GATEWAY_V2.address}) to handle Axies for ${address}`); const tx = await axieContract.setApprovalForAll(MARKETPLACE_GATEWAY_V2.address, true, { gasPrice: utils.parseUnits('20', 'gwei') }); const receipt = await tx.wait(); console.log('✅ Marketplace approved! Transaction hash:', receipt.transactionHash); break; } case 'buy': { const token = await ensureMarketplaceToken(); const axieId = await getAxieId(); if (!axieId) break; const receipt = await buyMarketplaceOrder( axieId, wallet, token, process.env.SKYMAVIS_DAPP_KEY // This can be undefined ); if (receipt) { console.log('🚀 Transaction successful! Hash:', receipt.transactionHash); console.log('🔗 View transaction: https://app.roninchain.com/tx/' + receipt.transactionHash); } break; } case 'delist': { const axieId = await getAxieId(); if (!axieId) break; const receipt = await cancelMarketplaceOrder( axieId, wallet, process.env.SKYMAVIS_DAPP_KEY // This can be undefined ); if (receipt) { console.log('Transaction hash:', receipt.transactionHash); } break; } case 'delist-all': { const fromAddress = await wallet.getAddress(); let axieIds = await getAxieIdsFromAccount(fromAddress, provider); if (axieIds.length > 100) { console.log('⚠️ Warning: Can only transfer up to 100 Axies at once, only delisting the first 100'); axieIds = axieIds.slice(0, 100); } const receipt = await batchTransferAxies(wallet, fromAddress, axieIds); if (receipt) { console.log('✅ Axies delisted! Transaction hash:', receipt.transactionHash); } break; } case 'list': { const axieId = await getAxieId(); if (!axieId) break; const token = await ensureMarketplaceToken(); const basePrice = await input({ message: 'Enter base price in ETH', validate: (value) => utils.parseEther(value).gt(0) }); const address = await wallet.getAddress(); const currentBlock = await provider.getBlock('latest'); const startedAt = currentBlock.timestamp; const expiredAt = startedAt + 15634800; // ~6 months const orderData = { address, axieId: axieId.toString(), basePrice: ethers.utils.parseEther(basePrice).toString(), endedPrice: '0', startedAt, endedAt: 0, expiredAt, }; const result = await createMarketplaceOrder( orderData, token, wallet, process.env.SKYMAVIS_DAPP_KEY // This can be undefined ); if (result === null || result.errors || !result.data) { console.error('❌ Error:', result?.errors?.[0]?.message || 'Unknown error'); break; } console.log('✅ Order created! Order hash:', result.data.createOrder); break; } case 'list-all': { const token = await ensureMarketplaceToken(); const basePrice = await input({ message: 'Enter base price in ETH (for all Axies)', validate: (value) => utils.parseEther(value).gt(0) }); const address = await wallet.getAddress(); let axieIds = await getAxieIdsFromAccount(address, provider); if (axieIds.length > 100) { console.log('⚠️ Warning: Can only list up to 100 Axies at once, only listing the first 100'); axieIds = axieIds.slice(0, 100); } const currentBlock = await provider.getBlock('latest'); const startedAt = currentBlock.timestamp; const expiredAt = startedAt + 15634800; // ~6 months for (const axieId of axieIds) { const orderData = { address, axieId: axieId.toString(), basePrice: ethers.utils.parseEther(basePrice).toString(), endedPrice: '0', startedAt, endedAt: 0, expiredAt, }; const result = await createMarketplaceOrder( orderData, token, wallet, process.env.SKYMAVIS_DAPP_KEY ); if (result === null || result.errors || !result.data) { console.error(`❌ Error listing Axie ${axieId}:`, result?.errors?.[0]?.message || 'Unknown error'); continue; } console.log(`✅ Listed Axie ${axieId}! Order hash:`, result.data.createOrder); } break; } case 'transfer': { const axieId = await getAxieId(); if (!axieId) break; const address = await input({ message: 'Enter recipient address', validate: (value) => value.length > 0 }); const receipt = await transferAxie(wallet, address, axieId); if (receipt) { console.log('✅ Axie transferred! Transaction hash:', receipt.transactionHash); } break; } case 'transfer-all': { const address = await input({ message: 'Enter recipient address', validate: (value) => value.length > 0 }); const fromAddress = await wallet.getAddress(); let axieIds = await getAxieIdsFromAccount(fromAddress, provider); if (axieIds.length > 100) { console.log('⚠️ Warning: Can only transfer up to 100 Axies at once, only transfering the first 100'); axieIds = axieIds.slice(0, 100); } const receipt = await batchTransferAxies(wallet, address, axieIds); if (receipt) { console.log('✅ Axies transferred! Transaction hash:', receipt.transactionHash); } break; } } } catch (error) { if (error instanceof Error) { console.error('❌ Error:', error.message); } else { console.error('❌ Error:', error); } } finally { await askToContinue(); } } } main();