UNPKG

axie-tools

Version:

TypeScript library and CLI tool for interacting with Axie Infinity marketplace and NFTs on Ronin network. Features marketplace operations (buy/sell/delist), batch transfers, and wallet information.

600 lines (529 loc) • 21.6 kB
#!/usr/bin/env node import { select, input, confirm, password } from "@inquirer/prompts"; import { JsonRpcProvider, Wallet, parseEther, isHexString, isAddress } from "ethers"; import { getAxieIdsFromAccount, delegateAxie, batchDelegateAxies, batchRevokeDelegations, revokeDelegation } from "./lib/axie"; import { refreshToken } from "./lib/marketplace/access-token"; import { batchTransferAxies, transferAxie } from "./lib/transfers"; import { askToContinue, ensureMarketplaceToken, getAccountInfo, getAxieId, } from "./lib/utils"; import { approveMarketplaceContract, approveWETH, } from "./lib/marketplace/approve"; import buyMarketplaceOrder from "./lib/marketplace/settle-order"; import cancelMarketplaceOrder from "./lib/marketplace/cancel-order"; import createMarketplaceOrder from "./lib/marketplace/create-order"; import { checkBlessings, isActivated } from "./lib/atia"; import { signDelegation } from "./lib/atia/delegation"; import { delegateAtiaBlessing, getAtiaBlessingDelegations, revokeAtiaBlessingDelegation } from "./lib/atia/graphql"; import "dotenv/config"; async function main() { // 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 isHexString(value, 32) || isHexString(`0x${value}`, 32); }, }); // Ensure "0x" prefix privateKey = privateKey.startsWith("0x") ? privateKey : `0x${privateKey}`; } // Initialize provider with a reliable RPC endpoint const provider = new JsonRpcProvider("https://api.roninchain.com/rpc"); // Initialize wallet with the previously obtained private key const wallet = new Wallet(privateKey, provider); const address = await wallet.getAddress(); while (true) { 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 multiple axies", value: "delist-all" }, { name: "List axie", value: "list" }, { name: "List multiple axies", value: "list-all" }, { name: "Transfer axie", value: "transfer" }, { name: "Transfer multiple axies", value: "transfer-all" }, { name: "Delegate axie", value: "delegate-axie" }, { name: "Revoke axie delegation", value: "revoke-delegation-axie" }, { name: "Delegate multiple axies", value: "batch-delegate" }, { name: "Revoke multiple delegations", value: "batch-revoke" }, { name: "Check Atia blessing status", value: "check-blessing" }, { name: "Pray for Atia blessing", value: "pray-blessing" }, { name: "Create Atia Blessing delegation", value: "delegate" }, { name: "List Atia Blessing delegations", value: "list-delegations" }, { name: "Revoke Atia Blessing delegation", value: "revoke-delegation" }, ], }); switch (action) { case "account": { const info = await getAccountInfo(address, provider); console.log(`šŸ“¬ Address: ${info.address}`); console.log("šŸ’° RON Balance:", info.ronBalance); console.log("šŸ’° WETH Balance:", info.wethBalance); console.log("šŸ’° USDC Balance:", info.usdcBalance); console.log( "šŸ›’ Marketplace WETH allowance:", info.allowance !== 0n ? "āœ… Granted" : "āŒ Not granted", ); console.log( "šŸ” Marketplace approval for Axies:", info.isApprovedForAll ? "āœ… Approved" : "āŒ Not approved", ); console.log(`🐾 Number of Axies: ${info.axieIds.length}`); if (info.axieIds.length > 0) { console.log(`šŸ†” Axie IDs: ${info.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": { await approveWETH(wallet); break; } case "approve-marketplace": { await approveMarketplaceContract(wallet); break; } case "buy": { const token = await ensureMarketplaceToken(); const axieId = await getAxieId(); if (!axieId) break; await approveWETH(wallet); const receipt = await buyMarketplaceOrder( axieId, wallet, token ); if (receipt) { console.log("šŸš€ Transaction successful! Hash:", receipt.hash); console.log( `šŸ”— View transaction: https://app.roninchain.com/tx/${receipt.hash}`, ); } 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) => parseEther(value) > 0n, }); await approveMarketplaceContract(wallet); const currentBlock = await provider.getBlock("latest"); const startedAt = currentBlock?.timestamp ?? Math.floor(Date.now() / 1000); const expiredAt = startedAt + 15634800; // ~6 months const orderData = { address, axieId: axieId.toString(), basePrice: parseEther(basePrice).toString(), endedPrice: "0", startedAt, endedAt: 0, expiredAt, }; const result = await createMarketplaceOrder( orderData, token, wallet ); if (result === null || result.errors || !result.data) { console.error( "āŒ Error:", result?.errors?.[0]?.message || "Unknown error", ); break; } console.log( `āœ… Listed Axie ${axieId}! Current price in USD: ${result.data.createOrder.currentPriceUsd}`, ); break; } case "list-all": { const token = await ensureMarketplaceToken(); const basePrice = await input({ message: "Enter base price in ETH (for all Axies)", validate: (value) => parseEther(value) > 0n, }); await approveMarketplaceContract(wallet); 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 ?? Math.floor(Date.now() / 1000); const expiredAt = startedAt + 15634800; // ~6 months for (const axieId of axieIds) { const orderData = { address, axieId: axieId.toString(), basePrice: parseEther(basePrice).toString(), endedPrice: "0", startedAt, endedAt: 0, expiredAt, }; const result = await createMarketplaceOrder( orderData, token, wallet, ); 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}! Current price in USD: ${result.data.createOrder.currentPriceUsd}`, ); } break; } case "delist": { const axieId = await getAxieId(); if (!axieId) break; const receipt = await cancelMarketplaceOrder( axieId, wallet ); if (receipt) { console.log("āœ… Axie delisted! Transaction hash:", receipt.hash); console.log( `šŸ”— View transaction: https://app.roninchain.com/tx/${receipt.hash}`, ); } break; } case "delist-all": { const axieIdsInput = await input({ message: "Enter comma-separated Axie IDs to delist (e.g. 123, 456, 789)", validate: (value) => { const ids = value.split(",").map(id => id.trim()); return ids.length > 0 && ids.every(id => !Number.isNaN(Number(id))); }, }); let axieIds = axieIdsInput.split(",").map(id => id.trim()); if (axieIds.length > 100) { console.log( "āš ļø Warning: Can only delist up to 100 Axies at once, only delisting the first 100", ); axieIds = axieIds.slice(0, 100); } const receipt = await batchTransferAxies( wallet, await wallet.getAddress(), axieIds, ); if (receipt) { console.log("āœ… Axies delisted! Transaction hash:", receipt.hash); console.log( `šŸ”— View transaction: https://app.roninchain.com/tx/${receipt.hash}`, ); } else { console.log("āŒ Error delisting Axies"); } 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.hash); } break; } case "transfer-all": { const axieIdsInput = await input({ message: "Enter comma-separated Axie IDs (e.g. 123, 456, 789)", validate: (value) => { const ids = value.split(",").map(id => id.trim()); return ids.length > 0 && ids.every(id => !Number.isNaN(Number(id))); }, }); let axieIds = axieIdsInput.split(",").map(id => id.trim()); if (axieIds.length > 100) { console.log( "āš ļø Warning: Can only transfer up to 100 Axies at once, only transferring the first 100", ); axieIds = axieIds.slice(0, 100); } const address = await input({ message: "Enter recipient address", validate: (value) => value.length > 0, }); const receipt = await batchTransferAxies(wallet, address, axieIds); if (receipt) { console.log( "āœ… Axies transferred! Transaction hash:", receipt.hash, ); } break; } case "delegate-axie": { const axieId = await getAxieId(); if (!axieId) break; const address = await input({ message: "Enter delegatee address", validate: (value) => value.length > 0, }); const receipt = await delegateAxie(wallet, axieId, address); if (receipt) { console.log("āœ… Axie delegated! Transaction hash:", receipt.hash); console.log( `šŸ”— View transaction: https://app.roninchain.com/tx/${receipt.hash}`, ); } break; } case "revoke-delegation-axie": { const axieId = await getAxieId(); if (!axieId) break; console.log("šŸ”„ Starting delegation revocation process (this requires multiple transactions)..."); const receipt = await revokeDelegation(wallet, axieId); if (receipt) { console.log("āœ… Axie delegation fully revoked! Final transaction hash:", receipt.hash); console.log( `šŸ”— View transaction: https://app.roninchain.com/tx/${receipt.hash}`, ); } break; } case "batch-delegate": { const axieIdsInput = await input({ message: "Enter comma-separated Axie IDs to delegate (e.g. 123, 456, 789)", validate: (value) => { const ids = value.split(",").map(id => id.trim()); return ids.length > 0 && ids.every(id => !Number.isNaN(Number(id))); }, }); let axieIds = axieIdsInput.split(",").map(id => id.trim()); if (axieIds.length === 0) { console.log("āŒ No Axie IDs provided"); break; } if (axieIds.length > 100) { console.log( "āš ļø Warning: Can only delegate up to 100 Axies at once, only delegating the first 100", ); axieIds = axieIds.slice(0, 100); } const address = await input({ message: "Enter delegatee address", validate: (value) => value.length > 0, }); console.log("\nšŸ”„ Starting batch delegation process..."); try { const receipt = await batchDelegateAxies(wallet, axieIds, address); if (receipt) { console.log("\nāœ… Batch operation completed! Final transaction hash:", receipt.hash); console.log( `šŸ”— View transaction: https://app.roninchain.com/tx/${receipt.hash}`, ); } } catch (error: unknown) { console.log("\nāŒ Some operations failed. Check the logs above for details."); } break; } case "batch-revoke": { const axieIdsInput = await input({ message: "Enter comma-separated Axie IDs to revoke delegation (e.g. 123, 456, 789)", validate: (value) => { const ids = value.split(",").map(id => id.trim()); return ids.length > 0 && ids.every(id => !Number.isNaN(Number(id))); }, }); let axieIds = axieIdsInput.split(",").map(id => id.trim()); if (axieIds.length === 0) { console.log("āŒ No Axie IDs provided"); break; } if (axieIds.length > 100) { console.log( "āš ļø Warning: Can only revoke up to 100 Axies at once, only revoking the first 100", ); axieIds = axieIds.slice(0, 100); } console.log("\nšŸ”„ Starting batch revocation process..."); console.log("This will:"); console.log("1. Check and approve delegation contract if needed"); console.log("2. Request detachment for each Axie"); console.log("3. Perform the revocation\n"); try { const receipt = await batchRevokeDelegations(wallet, axieIds); if (receipt) { console.log("\nāœ… Batch operation completed! Final transaction hash:", receipt.hash); console.log( `šŸ”— View transaction: https://app.roninchain.com/tx/${receipt.hash}`, ); } } catch (error: unknown) { console.log("\nāŒ Some operations failed. Check the logs above for details."); } break; } case "check-blessing": { const address = await input({ message: "Enter address to check blessing status (press Enter to use your address)", validate: () => true, }); const targetAddress = address || wallet.address; const { status, streak } = await isActivated(targetAddress, provider); if (status) { console.log(`āœ… Address ${targetAddress.slice(-4)} has already prayed today (Current streak: ${streak})`); } else { console.log(`āŒ Address ${targetAddress.slice(-4)} has not prayed today (Current streak: ${streak})`); } break; } case "pray-blessing": { const token = await ensureMarketplaceToken(); await checkBlessings(wallet, token); break; } case "delegate": { const token = await ensureMarketplaceToken(); if (!token) { console.log("āŒ No marketplace token available. Please refresh token first."); break; } const toAddress = await input({ message: "Enter delegatee address:", validate: (value) => isAddress(value), }); const percentage = await input({ message: "Enter delegator slips percentage (default: 100):", validate: (value) => { if (value === "") return true; const num = Number.parseInt(value); return !Number.isNaN(num) && num > 0 && num <= 100; }, default: "100" }); const delegatedAt = Math.floor(Date.now() / 1000); console.log("\nšŸ”„ Creating delegation signature..."); const { message, signature } = await signDelegation( wallet, toAddress, delegatedAt, Number.parseInt(percentage) ); console.log("āœ… Signature created!"); console.log("\nšŸ“ Message:", message); console.log("āœļø Signature:", signature); console.log("\nšŸ”„ Submitting delegation..."); const success = await delegateAtiaBlessing( token, signature, wallet.address, toAddress, delegatedAt, Number.parseInt(percentage) ); if (success) { console.log("āœ… Delegation submitted successfully!"); } else { console.log("āŒ Failed to submit delegation"); } break; } case "revoke-delegation": { const token = await ensureMarketplaceToken(); if (!token) { console.log("āŒ No marketplace token available. Please refresh token first."); break; } const toAddress = await input({ message: "Enter delegatee address to revoke:", validate: (value) => isAddress(value), }); console.log("\nšŸ”„ Revoking delegation..."); const success = await revokeAtiaBlessingDelegation( token, wallet.address, // userAddress toAddress, wallet.address // fromAddress ); if (success) { console.log("āœ… Delegation revoked successfully!"); } else { console.log("āŒ Failed to revoke delegation"); } break; } case "list-delegations": { const token = await ensureMarketplaceToken(); if (!token) { console.log("āŒ No marketplace token available. Please refresh token first."); break; } console.log("\nšŸ“‹ Checking delegations..."); const delegations = await getAtiaBlessingDelegations(token, wallet.address); if (delegations.length === 0) { console.log("ā„¹ļø No delegations found"); break; } console.log("\nšŸ“‹ Delegations:"); for (const delegation of delegations) { const fromName = delegation.fromProfile?.name || delegation.fromAddress.slice(-4); const toName = delegation.toProfile?.name || delegation.toAddress.slice(-4); console.log(`\nšŸ”„ From: ${fromName} (${delegation.fromAddress})`); console.log(` To: ${toName} (${delegation.toAddress})`); console.log(` Percentage: ${delegation.delegatorSlipsPercent}%`); console.log(` Last Prayed: ${delegation.lastPrayedAt ? new Date(delegation.lastPrayedAt).toLocaleString() : 'Never'}`); console.log(` Delegated At: ${new Date(delegation.delegatedAt * 1000).toLocaleString()}`); } break; } } } catch (error) { if (error instanceof Error) { console.error("āŒ Error:", error.message); } else { console.error("āŒ Error:", error); } } finally { await askToContinue(); } } } main();