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
text/typescript
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;
}