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.

302 lines (267 loc) 9.77 kB
import { type JsonRpcProvider, parseUnits, type Signer, toBigInt } from "ethers"; import { getAxieContract, getAxieDelegationContract } from "./contracts"; // Delegation context hash const ctxHash = "0xfa4914198408a3840a0a751c221614143f885428607953c71fa37ad0dd52f940"; async function approveAxieDelegation(signer: Signer) { const axieContract = getAxieContract(signer); const delegationContract = getAxieDelegationContract(signer); const delegationAddress = await delegationContract.getAddress(); // Check if already approved const isApproved = await axieContract.isApprovedForAll( await signer.getAddress(), delegationAddress ); if (!isApproved) { console.log("Approving Axie delegation contract..."); const tx = await axieContract.setApprovalForAll(delegationAddress, true); await tx.wait(); console.log("✅ Delegation contract approved"); } } export async function getAxieIdsFromAccount( address: string, provider: JsonRpcProvider, ) { // get axie contract const axieContract = getAxieContract(provider); // get axies balance for the address const axiesBalance = await axieContract.balanceOf(address); // get axie ids const axieIds: number[] = []; for (let i = 0; i < axiesBalance; i++) { try { const axieId = await axieContract.tokenOfOwnerByIndex(address, i); axieIds.push(Number(axieId)); } catch (error: unknown) { const errorMessage = error instanceof Error ? error.message : "Unknown error"; console.error(`Error fetching axie id at index ${i}: ${errorMessage}`); } } return axieIds; } export async function delegateAxie( signer: Signer, tokenId: string | number, delegatee: string, ) { const axieContract = getAxieContract(signer); const delegationContract = getAxieDelegationContract(signer); // Format token ID const formattedTokenId = typeof tokenId === "string" ? tokenId : tokenId.toString(); try { // Check if context is already attached const context = await axieContract.contextOf(formattedTokenId); if (context === ctxHash) { console.log(`ℹ️ Context already attached for Axie ${formattedTokenId}`); } else { // If context exists but is different, we need to attach the correct one console.log(`Attaching context for Axie ${formattedTokenId}`); const tx1 = await axieContract.attachContext( ctxHash, formattedTokenId, "0x", { gasPrice: parseUnits("20", "gwei"), } ); await tx1.wait(); console.log(`✅ Context attached for Axie ${formattedTokenId}`); } } catch (error) { // If error contains "execution reverted", context is already attached if (error instanceof Error && error.message.includes('execution reverted')) { console.log(`ℹ️ Context already attached for Axie ${formattedTokenId}`); } else { // If any other error, try to attach context try { console.log(`Attaching context for Axie ${formattedTokenId}`); const tx1 = await axieContract.attachContext( ctxHash, formattedTokenId, "0x", { gasPrice: parseUnits("20", "gwei"), } ); await tx1.wait(); console.log(`✅ Context attached for Axie ${formattedTokenId}`); } catch (attachError) { if (attachError instanceof Error && attachError.message.includes('execution reverted')) { console.log(`ℹ️ Context already attached for Axie ${formattedTokenId}`); } else { throw attachError; } } } } // Calculate expiry timestamp (1 year from now) const oneYearFromNow = Math.floor(Date.now() / 1000) + (365 * 24 * 60 * 60); // Format the delegatee address const cleanDelegatee = delegatee.replace("ronin:", "0x").toLowerCase(); console.log(`Delegating Axie ${formattedTokenId} to ${cleanDelegatee}`); try { // Call delegate function with standard method call const tx2 = await delegationContract.delegate( formattedTokenId, cleanDelegatee, oneYearFromNow, 1, // permissionBitMap as uint64 { gasPrice: parseUnits("20", "gwei"), } ); const receipt = await tx2.wait(); return receipt; } catch (error) { console.error("Delegation error:", error); throw error; } } export async function batchDelegateAxies( signer: Signer, axieIds: Array<string | number>, delegatee: string, ) { const axieContract = getAxieContract(signer); const delegationContract = getAxieDelegationContract(signer); // Format token IDs and delegatee address const tokenIds = axieIds.map(id => { try { return BigInt(id.toString().replace(/[^0-9]/g, '')); } catch (e) { throw new Error(`Invalid Axie ID format: ${id}`); } }); // Format the delegatee address const formattedDelegatee = delegatee.replace("ronin:", "0x").toLowerCase(); // Check and attach contexts individually console.log(`Checking/attaching context for ${tokenIds.length} Axies`); for (const tokenId of tokenIds) { try { // Check if context is already attached const context = await axieContract.contextOf(tokenId.toString()); if (context === ctxHash) { console.log(`ℹ️ Context already attached for Axie ${tokenId}`); } else { console.log(`- Attaching context for Axie ${tokenId}`); const tx1 = await axieContract.attachContext(ctxHash, tokenId.toString(), "0x"); await tx1.wait(); console.log(`✅ Context attached for Axie ${tokenId}`); } } catch (error) { // If error contains "execution reverted", context is already attached if (error instanceof Error && error.message.includes('execution reverted')) { console.log(`ℹ️ Context already attached for Axie ${tokenId}`); } else { try { console.log(`- Attaching context for Axie ${tokenId}`); const tx1 = await axieContract.attachContext(ctxHash, tokenId.toString(), "0x"); await tx1.wait(); console.log(`✅ Context attached for Axie ${tokenId}`); } catch (attachError) { if (attachError instanceof Error && attachError.message.includes('execution reverted')) { console.log(`ℹ️ Context already attached for Axie ${tokenId}`); } else { console.log(`❌ Failed to attach context for Axie ${tokenId}`, attachError); throw attachError; } } } } } // Calculate expiry timestamp (1 year from now) const oneYearFromNow = BigInt(Math.floor(Date.now() / 1000) + (365 * 24 * 60 * 60)); const permissionBitMap = 1n; // Now do the bulk delegation console.log(`\nDelegating ${tokenIds.length} Axies to ${delegatee}`); try { const tx2 = await delegationContract.bulkDelegate( tokenIds, Array(tokenIds.length).fill(formattedDelegatee), Array(tokenIds.length).fill(oneYearFromNow), Array(tokenIds.length).fill(permissionBitMap) ); const receipt = await tx2.wait(); return receipt; } catch (error: unknown) { // If bulk delegation fails, try individual delegations console.log("\n⚠️ Bulk delegation failed:", error); console.log("Falling back to individual delegations...\n"); for (const tokenId of tokenIds) { try { const tx = await delegationContract.delegate( tokenId, formattedDelegatee, oneYearFromNow, permissionBitMap ); await tx.wait(); console.log(`✅ Successfully delegated Axie ${tokenId}`); } catch (err: unknown) { console.log(`❌ Failed to delegate Axie ${tokenId}`, err); } } } } export async function batchRevokeDelegations( signer: Signer, axieIds: Array<string | number>, ) { const delegationContract = getAxieDelegationContract(signer); // Format token IDs - ensure they are clean numeric strings const tokenIds = axieIds.map(id => id.toString().replace(/[^0-9]/g, '')); // Then do the bulk revocation console.log(`\nRevoking delegations for ${tokenIds.length} Axies`); try { const tx2 = await delegationContract.bulkRevokeDelegations( tokenIds ); const receipt = await tx2.wait(); return receipt; } catch (error: unknown) { console.log("\n⚠️ Bulk revocation failed:", error); console.log("Falling back to individual revocations...\n"); for (const tokenId of tokenIds) { try { const tx = await delegationContract.revokeDelegation( tokenId ); await tx.wait(); console.log(`✅ Successfully revoked delegation for Axie ${tokenId}`); } catch (err: unknown) { console.log(`❌ Failed to revoke delegation for Axie ${tokenId}`, err); } } } } export async function revokeDelegation( signer: Signer, tokenId: string | number, ) { // // First approve the delegation contract if needed // await approveAxieDelegation(signer); // const axieContract = getAxieContract(signer); const delegationContract = getAxieDelegationContract(signer); // // First request detachment // console.log(`Requesting detachment for Axie ${Number(tokenId)}...`); // const tx1 = await axieContract.requestDetachContext( // ctxHash, // Number(tokenId), // "0x", // { // gasPrice: parseUnits("20", "gwei"), // } // ); // await tx1.wait(); // Then revoke the delegation console.log(`Revoking delegation for Axie ${Number(tokenId)}`); const tx2 = await delegationContract.revokeDelegation( Number(tokenId), { gasPrice: parseUnits("20", "gwei"), } ); const receipt = await tx2.wait(); return receipt; }