axie-tools
Version:
TypeScript SDK for building Axie Infinity trading bots and AI agents on Ronin network. Programmatic marketplace operations for Axies, Materials, and Consumables (buy/sell/delist), batch transfers, floor price detection, and wallet management. Includes int
3,383 lines • 106 kB
JavaScript
#!/usr/bin/env node
// cli.ts
import prompts2 from "prompts";
import { Wallet as Wallet2, parseEther as parseEther2, isHexString } from "ethers";
// lib/axie.ts
import { AbiCoder as AbiCoder2 } from "ethers";
// lib/contracts.ts
import { Interface, Contract } from "ethers";
import AXIE_PROXY from "@roninbuilders/contracts/axie_proxy";
import ERC721_BATCH_TRANSFER from "@roninbuilders/contracts/erc_721_batch_transfer";
import MARKETPLACE_GATEWAY_PROXY from "@roninbuilders/contracts/market_gateway_proxy";
import MATERIAL_ERC_1155_PROXY from "@roninbuilders/contracts/material_erc_1155_proxy";
import AXIE_CONSUMABLE_ERC_1155_PROXY from "@roninbuilders/contracts/axie_consumable_erc_1155_proxy";
import ERC_1155_EXCHANGE from "@roninbuilders/contracts/erc_1155_exchange_21a3764f";
import USD_COIN from "@roninbuilders/contracts/usd_coin";
import WRAPPED_ETHER from "@roninbuilders/contracts/wrapped_ether";
import MULTICALL_3 from "@roninbuilders/contracts/multicall_3";
function getAxieContract(signerOrProvider) {
const address = AXIE_PROXY.address;
const abi = new Interface(AXIE_PROXY.proxy_abi);
return new Contract(address, abi, signerOrProvider);
}
function getMarketplaceContract(signerOrProvider) {
const address = MARKETPLACE_GATEWAY_PROXY.address;
const abi = new Interface(MARKETPLACE_GATEWAY_PROXY.proxy_abi);
return new Contract(address, abi, signerOrProvider);
}
function getBatchTransferContract(signerOrProvider) {
const address = ERC721_BATCH_TRANSFER.address;
const abi = new Interface(ERC721_BATCH_TRANSFER.abi);
return new Contract(address, abi, signerOrProvider);
}
function getWETHContract(signerOrProvider) {
const address = WRAPPED_ETHER.address;
const abi = new Interface(WRAPPED_ETHER.abi);
return new Contract(address, abi, signerOrProvider);
}
function getMaterialContract(signerOrProvider) {
const address = MATERIAL_ERC_1155_PROXY.address;
const abi = new Interface(MATERIAL_ERC_1155_PROXY.proxy_abi);
return new Contract(address, abi, signerOrProvider);
}
function getConsumableContract(signerOrProvider) {
const address = AXIE_CONSUMABLE_ERC_1155_PROXY.address;
const abi = new Interface(AXIE_CONSUMABLE_ERC_1155_PROXY.proxy_abi);
return new Contract(address, abi, signerOrProvider);
}
function getERC1155ExchangeContract(signerOrProvider) {
const address = ERC_1155_EXCHANGE.address;
const abi = new Interface(ERC_1155_EXCHANGE.abi);
return new Contract(address, abi, signerOrProvider);
}
function getMulticall3Contract(signerOrProvider) {
const address = MULTICALL_3.address;
const abi = new Interface(MULTICALL_3.abi);
return new Contract(address, abi, signerOrProvider);
}
// lib/utils.ts
import prompts from "prompts";
import { JsonRpcProvider, formatEther, parseUnits } from "ethers";
// lib/material.ts
import { AbiCoder } from "ethers";
// lib/marketplace.ts
var ORDER_FRAGMENTS = `
fragment OrderInfo on Order {
...PartialOrderFields
makerProfile {
name
addresses {
ronin
__typename
}
__typename
}
assets {
...AssetInfo
availableQuantity
remainingQuantity
__typename
}
__typename
}
fragment PartialOrderFields on Order {
id
maker
kind
expiredAt
paymentToken
startedAt
basePrice
endedAt
endedPrice
expectedState
nonce
marketFeePercentage
signature
hash
duration
timeLeft
currentPrice
suggestedPrice
currentPriceUsd
status
__typename
}
fragment AssetInfo on Asset {
erc
address
id
quantity
orderId
__typename
}
`;
// lib/material.ts
var MATERIAL_QUERIES = {
GET_MATERIALS: `
query GetMaterials($owner: String, $includeMinPrice: Boolean = false, $includeQuantity: Boolean = false) {
erc1155Tokens(owner: $owner, tokenType: Material, from: 0, size: 32) {
total
results {
...Erc1155Metadata
quantity: total @include(if: $includeQuantity)
minPrice @include(if: $includeMinPrice)
orders(from: 0, size: 1) {
total
__typename
}
__typename
}
__typename
}
}
fragment Erc1155Metadata on Erc1155Token {
attributes
description
imageUrl
name
tokenAddress
tokenId
tokenType
__typename
}
`,
GET_MATERIAL_DETAIL: `
query GetMaterialDetail($tokenId: String) {
erc1155Token(tokenType: Material, tokenId: $tokenId) {
...Erc1155Metadata
minPrice
totalSupply: total
totalOwners
orders(from: 0, size: 1, sort: PriceAsc) {
totalListed: quantity
totalOrders: total
__typename
}
__typename
}
}
fragment Erc1155Metadata on Erc1155Token {
attributes
description
imageUrl
name
tokenAddress
tokenId
tokenType
__typename
}
`,
GET_MATERIAL_ORDERS: `
query GetBuyNowErc1155Orders($tokenType: Erc1155Type!, $tokenId: String, $from: Int!, $size: Int!, $sort: SortBy = PriceAsc) {
erc1155Token(tokenType: $tokenType, tokenId: $tokenId) {
tokenId
tokenType
total
minPrice
orders(from: $from, size: $size, sort: $sort) {
total
quantity
data {
...OrderInfo
__typename
}
__typename
}
__typename
}
}
${ORDER_FRAGMENTS}
`,
GET_MATERIAL_BY_OWNER: `
query GetErc1155DetailByOwner($tokenType: Erc1155Type!, $owner: String, $tokenId: String, $skipId: Boolean = false) {
erc1155ByOwner: erc1155Token(
tokenType: $tokenType
owner: $owner
tokenId: $tokenId
) {
id: tokenId @skip(if: $skipId)
tokenId
tokenType
totalOwned: total
orders(from: 0, size: 100, maker: $owner, sort: PriceAsc, includeInvalid: true) {
totalListed: quantity
totalOrders: total
data {
...OrderInfo
__typename
}
__typename
}
__typename
}
}
${ORDER_FRAGMENTS}
`,
GET_MATERIAL_OWNERSHIP: `
query GetErc1155Token($owner: String!, $tokenId: String!) {
erc1155Token(tokenType: Material, tokenId: $tokenId, owner: $owner) {
tokenId
tokenType
total
orders(from: 0, size: 100, sort: PriceAsc) {
quantity
total
data {
...OrderInfo
__typename
}
__typename
}
__typename
}
}
${ORDER_FRAGMENTS}
`,
CREATE_ORDER: `
mutation CreateOrder($order: InputOrder!, $signature: String!) {
createOrder(order: $order, signature: $signature) {
...OrderInfo
__typename
}
}
${ORDER_FRAGMENTS}
`
};
async function checkMaterialOwnership(materialId, address, skyMavisApiKey, accessToken) {
const { graphqlUrl, headers } = getMarketplaceApi(skyMavisApiKey);
const variables = {
tokenId: materialId,
owner: address
};
const apiHeaders = {
...headers,
authorization: `Bearer ${accessToken}`
};
try {
const result = await apiRequest(
graphqlUrl,
JSON.stringify({
operationName: "GetErc1155Token",
query: MATERIAL_QUERIES.GET_MATERIAL_OWNERSHIP,
variables
}),
apiHeaders
);
const token = result.data?.erc1155Token;
console.log(`\u{1F50D} Ownership query result:`, JSON.stringify(token, null, 2));
if (token && token.total === 0) {
console.log(`\u2139\uFE0F User owns 0 of this material`);
return token;
}
return token;
} catch (error) {
console.log(`\u274C Error in checkMaterialOwnership:`, error);
return null;
}
}
async function getUserMaterials(address, skyMavisApiKey) {
const query = MATERIAL_QUERIES.GET_MATERIALS;
const variables = {
owner: address,
includeMinPrice: false,
includeQuantity: true
};
const { graphqlUrl, headers } = getMarketplaceApi(skyMavisApiKey);
try {
const results = await apiRequest(
graphqlUrl,
JSON.stringify({ operationName: "GetMaterials", query, variables }),
headers
);
return results.data?.erc1155Tokens?.results || [];
} catch (error) {
return [];
}
}
async function validateMaterialToken(tokenId, skyMavisApiKey) {
const variables = { tokenId };
const { graphqlUrl, headers } = getMarketplaceApi(skyMavisApiKey);
try {
const result = await apiRequest(
graphqlUrl,
JSON.stringify({
operationName: "GetMaterialDetail",
query: MATERIAL_QUERIES.GET_MATERIAL_DETAIL,
variables
}),
headers
);
if (result?.data?.erc1155Token) {
return result.data.erc1155Token;
}
return null;
} catch (error) {
return null;
}
}
async function getMaterialFloorPrice(materialId, skyMavisApiKey, requestedQuantity) {
const { graphqlUrl, headers } = getMarketplaceApi(skyMavisApiKey);
try {
const response = await fetch(graphqlUrl, {
method: "POST",
headers: { "Content-Type": "application/json", ...headers },
body: JSON.stringify({
query: `query GetErc1155Orders($tokenType: Erc1155Type!, $tokenId: String, $sort: SortBy = PriceAsc) {
erc1155Token(tokenType: $tokenType, tokenId: $tokenId) {
orders(from: 0, size: 50, sort: $sort) {
data { currentPrice expiredAt assets { availableQuantity } }
}
}
}`,
variables: {
tokenType: "Material",
tokenId: materialId,
sort: "PriceAsc"
}
})
});
const { data } = await response.json();
const orders = data?.erc1155Token?.orders?.data || [];
const validOrders = orders.filter(
(order) => order.expiredAt * 1e3 > Date.now() && order.assets?.[0]?.availableQuantity && parseInt(order.assets[0].availableQuantity) > 0
);
if (validOrders.length === 0) {
return null;
}
if (!requestedQuantity || requestedQuantity <= 0) {
const cheapestOrder = validOrders[0];
return (Number(cheapestOrder.currentPrice) / 1e18).toFixed(6);
}
let remainingQuantity = requestedQuantity;
let totalCost = 0;
let ordersUsed = 0;
for (const order of validOrders) {
const availableQuantity = parseInt(order.assets[0].availableQuantity);
const quantityToUse = Math.min(remainingQuantity, availableQuantity);
const orderPrice = Number(order.currentPrice) / 1e18;
totalCost += quantityToUse * orderPrice;
remainingQuantity -= quantityToUse;
ordersUsed++;
if (remainingQuantity <= 0) {
const averagePrice = totalCost / requestedQuantity;
return averagePrice.toFixed(6);
}
}
return null;
} catch (error) {
return null;
}
}
function encodeMaterialOrderData(order) {
const orderTypes = [
"(address,uint8,(uint8,address,uint256,uint256),uint256,address,uint256,uint256,uint256,uint256,uint256,uint256)"
];
const orderData = [
order.maker,
1,
// kind: sell order
[
2,
// ERC1155 type
order.assets[0].address,
parseInt(order.assets[0].id),
parseInt(order.assets[0].quantity)
],
order.expiredAt,
order.paymentToken,
order.startedAt,
order.basePrice,
order.endedAt,
order.endedPrice,
order.expectedState || 0,
order.nonce
];
return AbiCoder.defaultAbiCoder().encode(orderTypes, [orderData]);
}
// lib/utils.ts
async function getGasPrice(signerOrProvider, options) {
if (options?.gasPrice !== void 0) {
return options.gasPrice;
}
let provider;
if ("provider" in signerOrProvider && signerOrProvider.provider) {
provider = signerOrProvider.provider;
} else {
provider = signerOrProvider;
}
try {
const feeData = await provider.getFeeData();
if (feeData.gasPrice) {
return feeData.gasPrice;
}
} catch (error) {
console.warn(
"Could not fetch gas price from network, using fallback.",
error
);
}
return parseUnits("26", "gwei");
}
var DEFAULT_RONIN_RPC_URL = "https://api.roninchain.com/rpc";
function createProvider(_skyMavisApiKey, rpcUrl = process.env.RONIN_RPC_URL ?? DEFAULT_RONIN_RPC_URL) {
return new JsonRpcProvider(rpcUrl);
}
function getMarketplaceApi(skyMavisApiKey) {
const graphqlUrl = "https://api-gateway.skymavis.com/graphql/axie-marketplace";
const headers = {
"x-api-key": skyMavisApiKey
};
return {
graphqlUrl,
headers
};
}
async function getAccountInfo(address, provider, skyMavisApiKey) {
const axieIds = await getAxieIdsFromAccount(address, provider);
const wethContract = getWETHContract(provider);
const marketplaceContract = getMarketplaceContract(provider);
const marketplaceAddress = await marketplaceContract.getAddress();
const balance = await provider.getBalance(address);
const wethBalance = await wethContract.balanceOf(address);
const allowance = await wethContract.allowance(address, marketplaceAddress);
const axieContract = getAxieContract(provider);
const isApprovedForAll = await axieContract.isApprovedForAll(
address,
marketplaceAddress
);
const materialContract = getMaterialContract(provider);
const isMaterialApprovedForAll = await materialContract.isApprovedForAll(
address,
marketplaceAddress
);
const materials = await getUserMaterials(address, skyMavisApiKey);
return {
address,
ronBalance: formatEther(balance),
wethBalance: formatEther(wethBalance),
allowance,
isApprovedForAll,
isMaterialApprovedForAll,
axieIds,
materials
};
}
async function apiRequest(url, body = null, headers = {}, method = "POST") {
if (method === "POST" && body) {
try {
const parsedBody = JSON.parse(body);
if (parsedBody.query) {
if (parsedBody.variables) {
}
}
} catch (e) {
console.error("Failed to parse GraphQL request body:", e);
}
}
const response = await fetch(url, {
method,
headers: {
...headers,
"Content-Type": "application/json"
},
...method === "GET" ? {} : { body }
});
if (!response.ok) {
const errorText = await response.text();
console.timeEnd("\u{1F680} Fetch");
throw new Error(`HTTP ${response.status}: ${errorText}`);
}
const responseText = await response.text();
console.timeEnd("\u{1F680} Fetch");
try {
const res = JSON.parse(responseText);
return res;
} catch (error) {
throw new Error(`Failed to parse JSON response: ${responseText}`);
}
}
var askToContinue = async () => {
const response = await prompts({
type: "confirm",
name: "continue",
message: "\u{1F504} Would you like to do something else?"
});
if (!response.continue) {
console.log("\u{1F44B} Goodbye!");
process.exit(0);
}
};
async function ensureMarketplaceToken() {
if (!process.env.MARKETPLACE_ACCESS_TOKEN) {
const response = await prompts({
type: "password",
name: "token",
message: "\u{1F511} Enter your Marketplace access token:",
validate: (value) => value !== void 0 && value !== ""
});
if (!response.token) {
process.exit(1);
}
process.env.MARKETPLACE_ACCESS_TOKEN = response.token;
}
return process.env.MARKETPLACE_ACCESS_TOKEN;
}
// lib/axie.ts
var AXIE_FRAGMENTS = `
fragment AxieDetail on Axie {
id
order {
...OrderInfo
__typename
}
__typename
}
`;
var AXIE_QUERIES = {
GET_AXIE_DETAIL: `
query GetAxieDetail($axieId: ID!) {
axie(axieId: $axieId) {
...AxieDetail
__typename
}
}
${AXIE_FRAGMENTS}
${ORDER_FRAGMENTS}
`
};
async function getAxieIdsFromAccount(address, provider) {
const axieContract = getAxieContract(provider);
const multicall3Contract = getMulticall3Contract(provider);
const axiesBalance = await axieContract.balanceOf(address);
if (axiesBalance === 0n) {
return [];
}
const balanceNum = Number(axiesBalance);
const axieContractAddress = await axieContract.getAddress();
const calls = Array.from({ length: balanceNum }, (_, i) => ({
target: axieContractAddress,
callData: axieContract.interface.encodeFunctionData("tokenOfOwnerByIndex", [
address,
i
])
}));
const results = await multicall3Contract.tryAggregate.staticCall(
false,
calls
);
const axieIds = [];
for (const result of results) {
if (result.success) {
try {
const decoded = axieContract.interface.decodeFunctionResult(
"tokenOfOwnerByIndex",
result.returnData
);
axieIds.push(Number(decoded[0]));
} catch (error) {
}
}
}
return axieIds;
}
async function getAxieDetails(axieId, accessToken, skyMavisApiKey) {
const variables = { axieId };
const { graphqlUrl, headers: apiHeaders } = getMarketplaceApi(skyMavisApiKey);
const headers = {
...apiHeaders,
authorization: `Bearer ${accessToken}`
};
try {
const results = await apiRequest(
graphqlUrl,
JSON.stringify({
query: AXIE_QUERIES.GET_AXIE_DETAIL,
variables
}),
headers
);
return results.data?.axie.order || null;
} catch (error) {
console.error("Error fetching axie details:", error);
return null;
}
}
function encodeAxieOrderData(order) {
const orderTypes = [
"(address maker, uint8 kind, (uint8 erc,address addr,uint256 id,uint256 quantity)[] assets, uint256 expiredAt, address paymentToken, uint256 startedAt, uint256 basePrice, uint256 endedAt, uint256 endedPrice, uint256 expectedState, uint256 nonce, uint256 marketFeePercentage)"
];
const orderData = [
order.maker,
1,
// kind: sell order
[
[
1,
// ERC721 type
order.assets[0].address,
parseInt(order.assets[0].id),
parseInt(order.assets[0].quantity)
]
],
order.expiredAt,
order.paymentToken,
order.startedAt,
order.basePrice,
order.endedAt,
order.endedPrice,
order.expectedState || "0",
order.nonce,
order.marketFeePercentage
];
return AbiCoder2.defaultAbiCoder().encode(orderTypes, [orderData]);
}
async function getAxieFloorPrice(skyMavisApiKey) {
const { graphqlUrl, headers } = getMarketplaceApi(skyMavisApiKey);
try {
const response = await fetch(graphqlUrl, {
method: "POST",
headers: { "Content-Type": "application/json", ...headers },
body: JSON.stringify({
query: `query GetAxieLatest($from: Int!, $size: Int!, $sort: SortBy, $auctionType: AuctionType) {
axies(from: $from, size: $size, sort: $sort, auctionType: $auctionType) {
total
results {
id
order {
id
currentPrice
expiredAt
__typename
}
__typename
}
__typename
}
}`,
variables: {
from: 0,
size: 20,
sort: "PriceAsc",
auctionType: "Sale"
}
})
});
const { data } = await response.json();
const axies = data?.axies?.results || [];
const validAxies = axies.filter(
(axie) => axie.order && axie.order.currentPrice && axie.order.expiredAt * 1e3 > Date.now()
);
if (validAxies.length === 0) {
return null;
}
const cheapestAxie = validAxies[0];
return (Number(cheapestAxie.order.currentPrice) / 1e18).toFixed(6);
} catch (error) {
return null;
}
}
// lib/marketplace/access-token.ts
var AUTH_TOKEN_REFRESH_URL = "https://athena.skymavis.com/v2/public/auth/token/refresh";
var decodeJWT = (token) => {
const parts = token.split(".");
if (parts.length !== 3) throw new Error("Invalid JWT format");
const payload = parts[1];
const decoded = Buffer.from(payload, "base64").toString("utf-8");
return JSON.parse(decoded);
};
var getTokenExpirationInfo = (token) => {
const payload = decodeJWT(token);
if (!payload.exp) throw new Error("Token missing exp claim");
const expiresAt = new Date(payload.exp * 1e3);
const now = Date.now();
const expiresInMs = expiresAt.getTime() - now;
const expiresInSeconds = Math.floor(expiresInMs / 1e3);
const isExpired = expiresInSeconds <= 0;
let humanReadable;
if (isExpired) {
const pastSeconds = Math.abs(expiresInSeconds);
const hours = Math.floor(pastSeconds / 3600);
const minutes = Math.floor(pastSeconds % 3600 / 60);
humanReadable = hours > 0 ? `expired ${hours}h ${minutes}m ago` : `expired ${minutes}m ago`;
} else {
const hours = Math.floor(expiresInSeconds / 3600);
const minutes = Math.floor(expiresInSeconds % 3600 / 60);
humanReadable = hours > 0 ? `expires in ${hours}h ${minutes}m` : `expires in ${minutes}m`;
}
return {
expiresAt,
expiresIn: expiresInSeconds,
isExpired,
humanReadable
};
};
var refreshToken = async (refreshToken2) => {
const data = await apiRequest(
AUTH_TOKEN_REFRESH_URL,
JSON.stringify({ refreshToken: refreshToken2 })
);
const newAccessToken = data.accessToken;
const newRefreshToken = data.refreshToken;
if (!newAccessToken || !newRefreshToken) {
throw new Error(
"Error refreshing token, API response: " + JSON.stringify(data)
);
}
const expirationInfo = getTokenExpirationInfo(newAccessToken);
return {
newAccessToken,
newRefreshToken,
expirationInfo
};
};
// lib/marketplace/approve.ts
async function approveMarketplaceContract(signer, options) {
const axieContract = getAxieContract(signer);
const marketplaceContract = getMarketplaceContract();
const address = await signer.getAddress();
const marketplaceAddress = await marketplaceContract.getAddress();
let isApproved = await axieContract.isApprovedForAll(
address,
marketplaceAddress
);
if (!isApproved) {
const gasPrice = await getGasPrice(signer, options);
const tx = await axieContract.setApprovalForAll(marketplaceAddress, true, {
gasPrice
});
const receipt = await tx.wait();
}
return isApproved;
}
async function approveWETH(signer, options) {
const address = await signer.getAddress();
const wethContract = getWETHContract(signer);
const marketplaceContract = getMarketplaceContract();
const marketplaceAddress = await marketplaceContract.getAddress();
const currentAllowance = await wethContract.allowance(
address,
marketplaceAddress
);
if (currentAllowance === 0n) {
const amountToApprove = "115792089237316195423570985008687907853269984665640564039457584007913129639935";
const gasPrice = await getGasPrice(signer, options);
const txApproveWETH = await wethContract.approve(
marketplaceAddress,
amountToApprove,
{
gasPrice
}
);
const txApproveReceipt = await txApproveWETH.wait();
}
return currentAllowance;
}
async function approveBatchTransfer(signer, batchTransferAddress, options) {
const address = await signer.getAddress();
const axieContract = getAxieContract(signer);
const isApproved = await axieContract.isApprovedForAll(
address,
batchTransferAddress
);
if (!isApproved) {
const gasPrice = await getGasPrice(signer, options);
const approveTx = await axieContract.setApprovalForAll(
batchTransferAddress,
true,
{
gasPrice
}
);
await approveTx.wait();
}
}
async function approveMaterialMarketplace(signer, options) {
const materialContract = getMaterialContract(signer);
const marketplaceContract = getMarketplaceContract();
const address = await signer.getAddress();
const marketplaceAddress = await marketplaceContract.getAddress();
let isApproved = await materialContract.isApprovedForAll(
address,
marketplaceAddress
);
if (!isApproved) {
const gasPrice = await getGasPrice(signer, options);
const tx = await materialContract.setApprovalForAll(
marketplaceAddress,
true,
{
gasPrice
}
);
const receipt = await tx.wait();
}
return isApproved;
}
async function approveConsumableMarketplace(signer, options) {
const consumableContract = getConsumableContract(signer);
const marketplaceContract = getMarketplaceContract();
const address = await signer.getAddress();
const marketplaceAddress = await marketplaceContract.getAddress();
let isApproved = await consumableContract.isApprovedForAll(
address,
marketplaceAddress
);
if (!isApproved) {
const gasPrice = await getGasPrice(signer, options);
const tx = await consumableContract.setApprovalForAll(
marketplaceAddress,
true,
{
gasPrice
}
);
await tx.wait();
isApproved = true;
}
return isApproved;
}
// lib/transfers.ts
async function transferAxie(signer, addressTo, axieId, options) {
const addressFrom = await signer.getAddress();
const writeAxieContract = getAxieContract(signer);
const formattedAxieId = typeof axieId === "string" ? axieId : axieId.toString();
const gasPrice = await getGasPrice(signer, options);
const tx = await writeAxieContract["safeTransferFrom(address,address,uint256)"](
addressFrom,
addressTo.replace("ronin:", "0x").toLowerCase(),
formattedAxieId,
{ gasPrice }
);
const receipt = await tx.wait();
return receipt;
}
async function batchTransferAxies(signer, addressTo, axieIds, options) {
const writeBatchTransferContract = getBatchTransferContract(signer);
const writeAxieContract = getAxieContract(signer);
const batchTransferAddress = await writeBatchTransferContract.getAddress();
const axieContractAddress = await writeAxieContract.getAddress();
await approveBatchTransfer(signer, batchTransferAddress, options);
const addressFrom = await signer.getAddress();
const axies = axieIds.map((axieId) => {
return typeof axieId === "string" ? axieId : axieId.toString();
});
if (axies.length === 0) {
throw new Error("You must provide at least one axie ID");
}
const normalizedAddressTo = addressTo.replace("ronin:", "").toLowerCase();
const finalAddressTo = normalizedAddressTo.startsWith("0x") ? normalizedAddressTo : `0x${normalizedAddressTo}`;
const gasPrice = await getGasPrice(signer, options);
const tx = await writeBatchTransferContract["safeBatchTransfer(address,uint256[],address)"](axieContractAddress, axies, finalAddressTo, {
gasPrice
});
const receipt = await tx.wait();
return receipt;
}
// lib/consumable.ts
import { AbiCoder as AbiCoder3 } from "ethers";
var CONSUMABLE_QUERIES = {
GET_CONSUMABLES: `
query GetConsumables($owner: String, $includeMinPrice: Boolean = false, $includeQuantity: Boolean = false) {
erc1155Tokens(owner: $owner, tokenType: Consumable, from: 0, size: 32) {
total
results {
...Erc1155Metadata
quantity: total @include(if: $includeQuantity)
minPrice @include(if: $includeMinPrice)
orders(from: 0, size: 1) {
total
__typename
}
__typename
}
__typename
}
}
fragment Erc1155Metadata on Erc1155Token {
attributes
description
imageUrl
name
tokenAddress
tokenId
tokenType
__typename
}
`,
GET_CONSUMABLE_DETAIL: `
query GetConsumableDetail($tokenId: String) {
erc1155Token(tokenType: Consumable, tokenId: $tokenId) {
...Erc1155Metadata
minPrice
totalSupply: total
totalOwners
orders(from: 0, size: 1, sort: PriceAsc) {
totalListed: quantity
totalOrders: total
__typename
}
__typename
}
}
fragment Erc1155Metadata on Erc1155Token {
attributes
description
imageUrl
name
tokenAddress
tokenId
tokenType
__typename
}
`,
GET_CONSUMABLE_ORDERS: `
query GetBuyNowErc1155Orders($tokenType: Erc1155Type!, $tokenId: String, $from: Int!, $size: Int!, $sort: SortBy = PriceAsc) {
erc1155Token(tokenType: $tokenType, tokenId: $tokenId) {
tokenId
tokenType
total
minPrice
orders(from: $from, size: $size, sort: $sort) {
total
quantity
data {
...OrderInfo
__typename
}
__typename
}
__typename
}
}
${ORDER_FRAGMENTS}
`,
GET_CONSUMABLE_BY_OWNER: `
query GetErc1155DetailByOwner($tokenType: Erc1155Type!, $owner: String, $tokenId: String, $skipId: Boolean = false) {
erc1155ByOwner: erc1155Token(
tokenType: $tokenType
owner: $owner
tokenId: $tokenId
) {
id: tokenId @skip(if: $skipId)
tokenId
tokenType
totalOwned: total
orders(from: 0, size: 100, maker: $owner, sort: PriceAsc, includeInvalid: true) {
totalListed: quantity
totalOrders: total
data {
...OrderInfo
__typename
}
__typename
}
__typename
}
}
${ORDER_FRAGMENTS}
`,
GET_CONSUMABLE_OWNERSHIP: `
query GetErc1155Token($owner: String!, $tokenId: String!) {
erc1155Token(tokenType: Consumable, tokenId: $tokenId, owner: $owner) {
tokenId
tokenType
total
orders(from: 0, size: 100, sort: PriceAsc) {
quantity
total
data {
...OrderInfo
__typename
}
__typename
}
__typename
}
}
${ORDER_FRAGMENTS}
`,
CREATE_ORDER: `
mutation CreateOrder($order: InputOrder!, $signature: String!) {
createOrder(order: $order, signature: $signature) {
...OrderInfo
__typename
}
}
${ORDER_FRAGMENTS}
`
};
async function checkConsumableOwnership(consumableId, address, skyMavisApiKey, accessToken) {
const { graphqlUrl, headers } = getMarketplaceApi(skyMavisApiKey);
const variables = {
tokenId: consumableId,
owner: address
};
const apiHeaders = {
...headers,
authorization: `Bearer ${accessToken}`
};
try {
const result = await apiRequest(
graphqlUrl,
JSON.stringify({
operationName: "GetErc1155Token",
query: CONSUMABLE_QUERIES.GET_CONSUMABLE_OWNERSHIP,
variables
}),
apiHeaders
);
const token = result.data?.erc1155Token;
console.log(`\u{1F50D} Ownership query result:`, JSON.stringify(token, null, 2));
if (token && token.total === 0) {
console.log(`\u2139\uFE0F User owns 0 of this consumable`);
return token;
}
return token;
} catch (error) {
console.log(`\u274C Error in checkConsumableOwnership:`, error);
return null;
}
}
async function validateConsumableToken(tokenId, skyMavisApiKey) {
const variables = { tokenId };
const { graphqlUrl, headers } = getMarketplaceApi(skyMavisApiKey);
try {
const result = await apiRequest(
graphqlUrl,
JSON.stringify({
operationName: "GetConsumableDetail",
query: CONSUMABLE_QUERIES.GET_CONSUMABLE_DETAIL,
variables
}),
headers
);
if (result?.data?.erc1155Token) {
return result.data.erc1155Token;
}
return null;
} catch (error) {
return null;
}
}
async function getConsumableFloorPrice(consumableId, skyMavisApiKey, requestedQuantity) {
const { graphqlUrl, headers } = getMarketplaceApi(skyMavisApiKey);
try {
const pageSize = 50;
let from = 0;
let totalOrders = Number.POSITIVE_INFINITY;
const quantityNeeded = requestedQuantity ?? 0;
let remainingQuantity = quantityNeeded;
let totalCost = 0;
while (from < totalOrders) {
const response = await fetch(graphqlUrl, {
method: "POST",
headers: { "Content-Type": "application/json", ...headers },
body: JSON.stringify({
query: `query GetErc1155Orders($tokenType: Erc1155Type!, $tokenId: String, $from: Int!, $size: Int!, $sort: SortBy = PriceAsc) {
erc1155Token(tokenType: $tokenType, tokenId: $tokenId) {
orders(from: $from, size: $size, sort: $sort) {
total
data { currentPrice expiredAt assets { availableQuantity } }
}
}
}`,
variables: {
tokenType: "Consumable",
tokenId: consumableId,
from,
size: pageSize,
sort: "PriceAsc"
}
})
});
const { data } = await response.json();
const orders = data?.erc1155Token?.orders?.data || [];
totalOrders = data?.erc1155Token?.orders?.total ?? from + orders.length;
const validOrders = orders.filter(
(order) => order.expiredAt * 1e3 > Date.now() && order.assets?.[0]?.availableQuantity && parseInt(order.assets[0].availableQuantity) > 0
);
if (quantityNeeded <= 0) {
const cheapestOrder = validOrders[0];
if (cheapestOrder) {
return (Number(cheapestOrder.currentPrice) / 1e18).toFixed(6);
}
}
for (const order of validOrders) {
const availableQuantity = parseInt(order.assets[0].availableQuantity);
const quantityToUse = Math.min(remainingQuantity, availableQuantity);
const orderPrice = Number(order.currentPrice) / 1e18;
totalCost += quantityToUse * orderPrice;
remainingQuantity -= quantityToUse;
if (remainingQuantity <= 0) {
const averagePrice = totalCost / quantityNeeded;
return averagePrice.toFixed(6);
}
}
if (orders.length < pageSize) {
break;
}
from += orders.length;
}
return null;
} catch (error) {
return null;
}
}
function encodeConsumableOrderData(order) {
const orderTypes = [
"(address,uint8,(uint8,address,uint256,uint256),uint256,address,uint256,uint256,uint256,uint256,uint256,uint256)"
];
const orderData = [
order.maker,
1,
// kind: sell order
[
2,
// ERC1155 type
order.assets[0].address,
parseInt(order.assets[0].id),
parseInt(order.assets[0].quantity)
],
order.expiredAt,
order.paymentToken,
order.startedAt,
order.basePrice,
order.endedAt,
order.endedPrice,
order.expectedState || 0,
order.nonce
];
return AbiCoder3.defaultAbiCoder().encode(orderTypes, [orderData]);
}
// lib/marketplace/cancel-order.ts
import { AbiCoder as AbiCoder4, Interface as Interface2 } from "ethers";
import APP_AXIE_ORDER_EXCHANGE from "@roninbuilders/contracts/app_axie_order_exchange";
async function cancelMarketplaceOrder(axieId, signer, skyMavisApiKey, options) {
let orderToCancel = options?.order;
if (!orderToCancel) {
const query = `
query GetAxieDetail($axieId: ID!) {
axie(axieId: $axieId) {
id
order {
... on Order {
id
maker
kind
assets {
... on Asset {
erc
address
id
quantity
orderId
}
}
expiredAt
paymentToken
startedAt
basePrice
endedAt
endedPrice
expectedState
nonce
marketFeePercentage
signature
hash
duration
timeLeft
currentPrice
suggestedPrice
currentPriceUsd
}
}
}
}
`;
const variables = {
axieId
};
const { graphqlUrl, headers } = getMarketplaceApi(skyMavisApiKey);
const result = await apiRequest(
graphqlUrl,
JSON.stringify({ query, variables }),
headers
);
if (result === null || result.data === void 0 || result.data.axie.order == null) {
throw new Error(`Could not find an active order for Axie ID: ${axieId}`);
}
orderToCancel = result.data.axie.order;
}
const orderData = [
orderToCancel.maker,
orderToCancel.kind === "Sell" ? 1 : 0,
[
[
orderToCancel.assets[0].erc === "Erc721" ? 1 : 0,
orderToCancel.assets[0].address,
+orderToCancel.assets[0].id,
+orderToCancel.assets[0].quantity
]
],
orderToCancel.expiredAt,
orderToCancel.paymentToken,
orderToCancel.startedAt,
orderToCancel.basePrice,
orderToCancel.endedAt,
orderToCancel.endedPrice,
orderToCancel.expectedState || 0,
orderToCancel.nonce,
orderToCancel.marketFeePercentage
];
const encodedOrderData = AbiCoder4.defaultAbiCoder().encode(
[
"(address maker, uint8 kind, (uint8 erc,address addr,uint256 id,uint256 quantity)[] assets, uint256 expiredAt, address paymentToken, uint256 startedAt, uint256 basePrice, uint256 endedAt, uint256 endedPrice, uint256 expectedState, uint256 nonce, uint256 marketFeePercentage)"
],
[orderData]
);
const axieOrderExchangeInterface = new Interface2(APP_AXIE_ORDER_EXCHANGE.abi);
const orderExchangePayload = axieOrderExchangeInterface.encodeFunctionData(
"cancelOrder",
[encodedOrderData]
);
const marketGatewayContract = getMarketplaceContract(signer);
const gasPrice = await getGasPrice(signer, options);
console.time("Transaction Send and Wait");
const tx = await marketGatewayContract.interactWith(
"ORDER_EXCHANGE",
orderExchangePayload,
{
gasPrice
}
);
const receipt = await tx.wait();
console.timeEnd("Transaction Send and Wait");
return receipt;
}
// lib/marketplace/cancel-material-order.ts
async function cancelMaterialOrder(materialId, signer, skyMavisApiKey, options) {
const userAddress = await signer.getAddress();
const { graphqlUrl, headers } = getMarketplaceApi(skyMavisApiKey);
const variables = {
tokenType: "Material",
tokenId: materialId,
owner: userAddress,
skipId: true
};
const result = await apiRequest(
graphqlUrl,
JSON.stringify({
operationName: "GetErc1155DetailByOwner",
query: MATERIAL_QUERIES.GET_MATERIAL_BY_OWNER,
variables
}),
headers
);
if (!result.data?.erc1155ByOwner?.orders?.data) {
return {
totalOrders: 0,
canceled: 0,
failed: 0,
canceledOrders: [],
failedCancellations: [],
message: "No orders found to cancel"
};
}
const userOrders = result.data.erc1155ByOwner.orders.data;
const canceledOrders = [];
const failedCancellations = [];
for (const order of userOrders) {
try {
const encodedOrderData = encodeMaterialOrderData(order);
const marketGatewayContract = getMarketplaceContract(signer);
const ERC1155_EXCHANGE_CONTRACT = getERC1155ExchangeContract();
const cancelOrderPayload = ERC1155_EXCHANGE_CONTRACT.interface.encodeFunctionData("cancelOrder", [
encodedOrderData
]);
const gasPrice = await getGasPrice(signer, options);
const tx = await marketGatewayContract.interactWith(
"ERC1155_EXCHANGE",
cancelOrderPayload,
{
gasPrice,
gasLimit: 11e4
}
);
const receipt = await tx.wait();
canceledOrders.push({
orderId: order.id,
transactionHash: receipt.hash,
quantity: order.assets[0]?.quantity || "0",
price: order.currentPrice
});
} catch (error) {
failedCancellations.push({
orderId: order.id,
error: error.message
});
}
}
const summary = {
totalOrders: userOrders.length,
canceled: canceledOrders.length,
failed: failedCancellations.length,
canceledOrders,
failedCancellations
};
return summary;
}
// lib/marketplace/cancel-consumable-order.ts
async function cancelConsumableOrder(consumableId, signer, skyMavisApiKey, options) {
const userAddress = await signer.getAddress();
const { graphqlUrl, headers } = getMarketplaceApi(skyMavisApiKey);
const variables = {
tokenType: "Consumable",
tokenId: consumableId,
owner: userAddress,
skipId: true
};
const result = await apiRequest(
graphqlUrl,
JSON.stringify({
operationName: "GetErc1155DetailByOwner",
query: CONSUMABLE_QUERIES.GET_CONSUMABLE_BY_OWNER,
variables
}),
headers
);
if (!result.data?.erc1155ByOwner?.orders?.data) {
return {
totalOrders: 0,
canceled: 0,
failed: 0,
canceledOrders: [],
failedCancellations: [],
message: "No orders found to cancel"
};
}
const userOrders = result.data.erc1155ByOwner.orders.data;
const canceledOrders = [];
const failedCancellations = [];
for (const order of userOrders) {
try {
const encodedOrderData = encodeConsumableOrderData(order);
const marketGatewayContract = getMarketplaceContract(signer);
const ERC1155_EXCHANGE_CONTRACT = getERC1155ExchangeContract();
const cancelOrderPayload = ERC1155_EXCHANGE_CONTRACT.interface.encodeFunctionData("cancelOrder", [
encodedOrderData
]);
const gasPrice = await getGasPrice(signer, options);
const tx = await marketGatewayContract.interactWith(
"ERC1155_EXCHANGE",
cancelOrderPayload,
{
gasPrice,
gasLimit: 11e4
}
);
const receipt = await tx.wait();
canceledOrders.push({
orderId: order.id,
transactionHash: receipt.hash,
quantity: order.assets[0]?.quantity || "0",
price: order.currentPrice
});
} catch (error) {
failedCancellations.push({
orderId: order.id,
error: error.message
});
}
}
const summary = {
totalOrders: userOrders.length,
canceled: canceledOrders.length,
failed: failedCancellations.length,
canceledOrders,
failedCancellations
};
return summary;
}
// lib/marketplace/create-order.ts
var axieOrderTypes = {
Asset: [
{ name: "erc", type: "uint8" },
{ name: "addr", type: "address" },
{ name: "id", type: "uint256" },
{ name: "quantity", type: "uint256" }
],
Order: [
{ name: "maker", type: "address" },
{ name: "kind", type: "uint8" },
{ name: "assets", type: "Asset[]" },
{ name: "expiredAt", type: "uint256" },
{ name: "paymentToken", type: "address" },
{ name: "startedAt", type: "uint256" },
{ name: "basePrice", type: "uint256" },
{ name: "endedAt", type: "uint256" },
{ name: "endedPrice", type: "uint256" },
{ name: "expectedState", type: "uint256" },
{ name: "nonce", type: "uint256" },
{ name: "marketFeePercentage", type: "uint256" }
]
};
async function createMarketplaceOrder(orderData, accessToken, signer, skyMavisApiKey) {
const {
address,
axieId,
basePrice,
endedPrice,
startedAt,
endedAt,
expiredAt
} = orderData;
const AXIE_CONTRACT_ADDRESS = await getAxieContract().getAddress();
const WETH_CONTRACT_ADDRESS = await getWETHContract().getAddress();
const MARKETPLACE_CONTRACT_ADDRESS = await getMarketplaceContract().getAddress();
const domain = {
name: "MarketGateway",
version: "1",
chainId: "2020",
// ✅ CRITICAL FIX: Use string "2020" not number 2020
verifyingContract: MARKETPLACE_CONTRACT_ADDRESS
// 0x3b3adf1422f84254b7fbb0e7ca62bd0865133fe3
};
const marketGatewayContract = getMarketplaceContract(signer);
const nonce = await marketGatewayContract.makerNonce(address);
const orderToSign = {
maker: address,
kind: "1",
// Sell order
assets: [
{
erc: "1",
// ERC721
addr: AXIE_CONTRACT_ADDRESS,
id: axieId,
quantity: "0"
// ERC721 quantity is 0 for API compatibility
}
],
expiredAt: expiredAt.toString(),
paymentToken: WETH_CONTRACT_ADDRESS,
startedAt: startedAt.toString(),
basePrice,
endedAt: endedAt.toString(),
endedPrice,
expectedState: "0",
nonce: nonce.toString(),
marketFeePercentage: "425"
// Standard 4.25% fee
};
const signature = await signer.signTypedData(
domain,
axieOrderTypes,
orderToSign
);
const query = `
mutation CreateOrder($order: InputOrder!, $signature: String!) {
createOrder(order: $order, signature: $signature) {
...OrderInfo
__typename
}
}
fragment OrderInfo on Order {
...PartialOrderFields
makerProfile {
name
addresses {
ronin
__typename
}
__typename
}
assets {
erc
address
id
quantity
orderId
__typename
}
__typename
}
fragment PartialOrderFields on Order {
id
maker
kind
expiredAt
paymentToken
startedAt
basePrice
endedAt
endedPrice
expectedState
nonce
marketFeePercentage
signature
hash
duration
timeLeft
currentPrice
suggestedPrice
currentPriceUsd
assets {
erc
address
id
quantity
orderId
__typename
}
__typename
}
`;
const variables = {
order: {
maker: address,
nonce: Number(nonce),
assets: [
{
id: axieId,
address: AXIE_CONTRACT_ADDRESS,
erc: "Erc721",
quantity: "0"
// API expects "0" for ERC721
}
],
kind: "Sell",
expectedState: "",
basePrice,
endedPrice,
startedAt,
endedAt,
expiredAt
},
signature
};
const { graphqlUrl, headers: apiHeaders } = getMarketplaceApi(skyMavisApiKey);
const headers = {
...apiHeaders,
authorization: `Bearer ${accessToken}`
};
const result = await apiRequest(
graphqlUrl,
JSON.stringify({
operationName: "CreateOrder",
query,
variables
}),
headers
);
return result;
}
// lib/marketplace/create-material-order.ts
var materialOrderTypes = {
Asset: [
{ name: "erc", type: "uint8" },
{ name: "addr", type: "address" },
{ name: "id", type: "uint256" },
{ name: "quantity", type: "uint256" }
],
ERC1155Order: [
{ name: "maker", type: "address" },
{ name: "kind", type: "uint8" },
{ name: "asset", type: "Asset" },
{ name: "expiredAt", type: "uint256" },
{ name: "paymentToken", type: "address" },
{ name: "startedAt", type: "uint256" },
{ name: "unitPrice", type: "uint256" },
{ name: "endedAt", type: "uint256" },
{ name: "endedUnitPrice", type: "uint256" },
{ name: "expectedState", type: "uint256" },
{ name: "nonce", type: "uint256" }
]
};
async function createMaterialMarketplaceOrder(orderData, accessToken, signer, skyMavisApiKey, options) {
const {
address,
materialId,
quantity: inputQuantity,
unitPrice,
endedUnitPrice,
startedAt,
endedAt,
expiredAt
} = orderData;
const MATERIAL_CONTRACT_ADDRESS = await getMaterialContract().getAddress();
const WETH_CONTRACT_ADDRESS = await getWETHContract().getAddress();
const MARKETPLACE_CONTRACT_ADDRESS = await getMarketplaceContract().getAddress();
const domain = {
name: "MarketGateway",
version: "1",
chainId: "2020",
verifyingContract: MARKETPLACE_CONTRACT_ADDRESS
};
console.log(`\u{1F50D} Checking ownership for address: ${address}`);
const ownership = await checkMaterialOwnership(
materialId,
address,
skyMavisApiKey,
accessToken
);
if (!ownership) {
throw new Error(
`\u274C Unable to verify ownership of material ${materialId}. Please check if you own this material.`
);
}
const ownedQuantity = ownership.total;
if (ownedQuantity === 0) {
throw new Error(
`\u274C You don't own any of material ${materialId}. You need to own at least 1 to create an order.`
);
}
let quantity;
if (!inputQuantity) {
quantity = ownedQuantity.toString();
} else {
quantity = inputQuantity;
if (parseInt(quantity) > ownedQuantity) {
throw new Error(
`\u274C Insufficient quantity!
\u2022 Requested: ${quantity}
\u2022 Owned: ${ownedQuantity}
\u2022 Cannot list more materials than you own.`
);
}
}
const orderToSign = {
maker: address.replace("ronin:", "0x"),
// Use Ethereum address format for signing
kind: "1",
// Sell order
asset: {
// Note: 'asset' (singular) like working example
erc: "2",
// ERC1155
addr: MATERIAL_CONTRACT_ADDRESS,
id: materialId,
quantity
},
expiredAt: expiredAt.toString(),
paymentToken: WETH_CONTRACT_ADDRESS,
startedAt: startedAt.toString(),
unitPrice,
// Use unitPrice like working example
endedAt: "0",
// Fixed price listing
endedUnitPrice: "0",
expectedState: "0",
nonce: options?.nonce || "0"
};
const provider = createProvider(skyMavisApiKey);
const signerWithProvider = signer.connect(provider);
const signature = await signerWithProvider.signTypedData(
domain,
materialOrderTypes,
orderToSign
);
const query = `
mutation CreateOrder($order: InputOrder!, $signature: String!) {
createOrder(order: $order, signature: $signature) {
...OrderInfo
__typename
}
}
fragment OrderInfo on Order {
...PartialOrderFields
makerProfile {
name
addresses {
ronin
__typename
}
__typename
}
assets {
erc
address
id
quantity
orderId
__typename
}
__typename
}
fragment PartialOrderFields on Order {
id
maker
kind
expiredAt
paymentToken
startedAt
basePrice
endedAt
endedPrice
expectedState
nonce
marketFeePercentage
signature
hash
duration
timeLeft
currentPrice
suggestedPrice
currentPriceUsd
assets {
erc
address
id
quantity
orderId
__typename
}
__typename
}
`;
const variables = {
order: {
maker: address.replace("ronin:", "0x"),
// Use 0x format for API
nonce: 0,
// Hardcoded as per working example
assets: [
{
id: materialId,
address: MATERIAL_CONTRACT_ADDRESS,
erc: "Erc1155",
quantity
}
],
kind: "Sell",
expectedState: "",
basePrice: unitPrice,
// API uses basePrice
endedPrice: "0",
// Fixed price listing
startedAt,
endedAt: 0,
// Fixed price listing
expiredAt
},
signature
};
const { graphqlUrl, headers: apiHeaders } = getMarketplaceApi(skyMavisApiKey);
const headers = {
...apiHeaders,
authorization: `Bearer ${accessToken}`
};
const result = await apiRequest(
graphqlUrl,
JSON.stringify({
operationName: "CreateOrder",
query,
variables
}),
headers
);
return result;
}
// lib/marketplace/create-consumable-order.ts
var consumableOrderTypes = {
Asset: [
{ name: "erc", type: "uint8" },
{ name: "addr", type: "address" },
{ name: "id", type: "uint256" },
{ name: "quantity", type: "uint256" }
],
ERC1155Order: [
{ name: "maker", type: "address" },
{ name: "kind", type: "uint8" },
{ name: "asset", type: "Asset" },
{ name: "expiredAt", type: "uint256" },
{ name: "paymentToken", type: "address" },
{ name: "startedAt", type: "uint256" },
{ name: "unitPrice", type: "uint256" },
{ name: "endedAt", type: "uint256" },
{ name: "endedUnitPrice", type: "uint256" },
{ name: "expectedState", type: "uint256" },
{ name: "nonce", type: "uint256" }
]
};
async function createConsumableMarketplaceOrder(orderData, accessToken, signer, skyMavisApiKey, options) {
const {
address,
consumableId,
quantity: inputQuantity,
unitPrice,
startedAt,
expiredAt
} = orderData;
const makerAddress = address.replace("ronin:", "0x");
const provider = createProvider(skyMavisApiKey);
const signerWithProvider = signer.connect(provider);
const CONSUMABLE_CONTRACT_ADDRESS = await getConsumableContract().getAddress();
const WETH_CONTRACT_ADDRESS = await getWETHContract().getAddress();
const MARKETPLACE_CONTRACT_ADDRESS = await getMarketplaceContract().getAddress();
const domain = {
name: "MarketGateway",
version: "1",
chainId: "2020",
verifyingContract: MARKETPLACE_CONTRACT_ADDRESS
};
const marketGatewayContract = getMarketplaceContract(signerWithProvider);
const currentNonce = options?.nonce ?? await marketGatewayContract.makerNonce(makerAddress);
const nonce = currentNonce.toString();
console.log(`\u{1F50D} Checking ownership for address: ${makerAddress}`);
const ownership = await checkConsumableOwnership(
consumableId,
makerAddress,
skyMavisApiKey,
accessToken
);
if (!ownership) {
throw new Error(
`\u274C Unable to verify ownership of consumable ${consumableId}. Please check if you own this consumable.`
);
}
const ownedQuantity = ownership.total;
if (ownedQuantity === 0) {
throw new Error(
`\u274C You don't own any of consumable ${consumableId}. You need to own at least 1 to create an order.`
);
}
let quantity;
if (!inputQuantity) {
quantity = ownedQuantity.toString();
} else {
quantity = inputQuantity;
if (parseInt(quantity) > ownedQuantity) {
throw new Error(
`\u274C Insufficient quantity!
\u2022 Requested: ${quantity}
\u2022 Owned: ${ownedQuantity}
\u2022 Cannot list more consumables than you own.`
);
}
}
const orderToSign = {
maker: makerAddress,
// Use Ethereum address format for signing
kind: "1",
// Sell order
asset: {
// Note: 'asset' (singular) like working example
erc: "2",
// ERC1155
addr: CONSUMABLE_CONTRACT_ADDRESS,
id: consumableId,
quantity
},
expiredAt: expiredAt.toString(),
paymentToken: WETH_CONTRACT_ADDRESS,
startedAt: startedAt.toString(),
unitPrice,
// Use unitPrice like working example
endedAt: "0",
// Fixed price listing
endedUnitPrice: "0",
expectedState: "0",
nonce
};
const signature = await signerWithProvider.signTypedData(
domain,
consumableOrderTypes,
orderToSign
);
const query = `
mutation CreateOrder($order: InputOrder!, $signature: String!) {
createOrder(order: $order, signature: $signature) {
...OrderInfo
__typename
}
}
fragment OrderInfo on Order {
...PartialOrderFields
makerProfile {
name
addresses {
ronin
__typename
}
__typename
}
assets {
erc
address
id
quantity
orderId
__typename
}
__typename
}
fragment PartialOrderFields on Order {
id
maker
kind
expiredAt
paymentToken
startedAt
basePrice
endedAt
endedPrice
expectedState
nonce
marketFeePercentage
signature
hash
duration
timeLeft
currentPrice
suggestedPrice
currentPriceUsd
assets {
erc
address
id
quantity
orderId
__typename
}
__typename
}
`;
const variables = {
order: {
maker: makerAddress,
// Use 0x format for API
nonce: Number(nonce),
assets: [
{
id: consumableId,
address: CONSUMABLE_CONTRACT_ADDRESS,
erc: "Erc1155",
quantity
}
],
kind: "Sell",
expectedState: "",
basePrice: unitPrice,
// API uses basePrice
endedPrice: "0",
// Fixed price listing
startedAt,
endedAt: 0,
// Fixed price listing
expiredAt
},
signature
};
const { graphqlUrl, headers: apiHeaders } = getMarketplaceApi(skyMavisApiKey);
const headers = {
...apiHeaders,
authorization: `Bearer ${accessToken}`
};
const result = await apiRequest(
graphqlUrl,
JSON.stringify({
operationName: "CreateOrder",
query,
variables
}),
headers
);
return result;
}
// lib/marketplace/settle-order.ts
import { Interface as Interface3 } from "ethers";
import APP_AXIE_ORDER_EXCHANGE2 from "@roninbuilders/contracts/app_axie_order_exchange";
async function buyMarketplaceOrder(axieId, signer, accessToken, skyMavisApiKey, options) {
try {
let order = options?.existingOrder;
if (!order) {
order = await getAxieDetails(axieId, accessToken, skyMavisApiKey);
}
if (!order) {
console.error("No order found for axie", axieId);
return false;
}
const address = await signer.getAddress();
const wethContract = getWETHContract(signer);
const wethBalance = await wethContract.balanceOf(address);
if (BigInt(wethBalance) < BigInt(order.currentPrice)) {
console.error("Insufficient WETH balance");
return false;
}
const contract = getMarketplaceContract(signer);
const encodedOrderData = encodeAxieOrderData(order);
const referralAddr = "0xa7d8ca624656922c633732fa2f327f504678d132";
const settleInfo = {
orderData: encodedOrderData,
signature: order.signature,
referralAddr,
expectedState: BigInt(0),
recipient: address,
refunder: address
};
const axieOrderExchangeInterface = new Interface3(
APP_AXIE_ORDER_EXCHANGE2.abi
);
const orderExchangeData = axieOrderExchangeInterface.encodeFunctionData(
"settleOrder",
[settleInfo, BigInt(order.currentPrice)]
);
const gasPrice = await getGasPrice(signer, options);
const txBuyAxie = await contract.interactWith(
"ORDER_EXCHANGE",
orderExchangeData,
{
gasPrice,
gasLimit: 1e6
}
);
const receipt = await txBuyAxie.wait();
if (receipt?.status === 0) {
return false;
}
return receipt;
} catch (error) {
console.error("Error settling order:", error);
return false;
}
}
// lib/marketplace/settle-material-order.ts
import { AbiCoder as AbiCoder5 } from "ethers";
async function buyMaterialOrder(materialId, quantity, signer, accessToken, skyMavisApiKey, options) {
const query = MATERIAL_QUERIES.GET_MATERIAL_ORDERS;
const variables = {
tokenType: "Material",
tokenId: materialId,
from: 0,
size: 50,
sort: "PriceAsc"
};
const { graphqlUrl, headers: apiHeaders } = getMarketplaceApi(skyMavisApiKey);
const headers = {
...apiHeaders,
authorization: `Bearer ${accessToken}`
};
try {
const results = await apiRequest(
graphqlUrl,
JSON.stringify({
operationName: "GetBuyNowErc1155Orders",
query,
variables
}),
headers
);
const orders = results.data?.erc1155Token?.orders?.data || [];
if (!orders || orders.length === 0) {
return false;
}
const address = await signer.getAddress();
const validOrders = orders.filter((order2) => {
const isExpired = order2.expiredAt * 1e3 <= Date.now();
const hasQuantity = order2.assets?.[0]?.availableQuantity && parseInt(order2.assets[0].availableQuantity) > 0;
const isSelfOrder = order2.maker.toLowerCase() === address.toLowerCase();
return !isExpired && hasQuantity && !isSelfOrder;
});
if (validOrders.length === 0) {
return false;
}
const sortedValidOrders = validOrders.sort((a, b) => {
const priceA = parseFloat(a.currentPrice);
const priceB = parseFloat(b.currentPrice);
return priceA - priceB;
});
const ordersToTry = sortedValidOrders.slice(3);
let order = null;
for (const candidateOrder of ordersToTry) {
const availableQuantity = parseInt(
candidateOrder.assets[0].availableQuantity
);
if (quantity <= availableQuantity) {
order = candidateOrder;
break;
}
}
if (!order) {
return false;
}
const wethContract = getWETHContract(signer);
const wethBalance = await wethContract.balanceOf(address);
const totalCost = BigInt(order.currentPrice) * BigInt(quantity);
if (BigInt(wethBalance) < totalCost) {
return false;
}
const erc1155ExchangeAddress = "0xb36c9027ed4353fdd7a59d8c40e0df5221a3764f";
const allowance = await wethContract.allowance(
address,
erc1155ExchangeAddress
);
const gasPrice = await getGasPrice(signer, options);
if (BigInt(allowance) < totalCost) {
const approveTx = await wethContract.approve(
erc1155ExchangeAddress,
totalCost,
{ gasPrice }
);
await approveTx.wait();
}
const orderTypes = [
"(address,uint8,uint8,address,uint256,uint256,uint256,address,uint256,uint256,uint256,uint256,uint256,uint256,uint256)"
];
const orderData = [
[
order.maker,
// address maker
1,
// uint8 kind (sell = 1)
2,
// uint8 erc type (ERC1155 = 2)
order.assets[0].address,
// address asset contract
BigInt(order.assets[0].id),
// uint256 asset id
BigInt(order.assets[0].quantity),
// uint256 original order quantity
BigInt(order.expiredAt),
// uint256 expiredAt
order.paymentToken,
// address paymentToken
BigInt(order.startedAt),
// uint256 startedAt
BigInt(order.basePrice),
// uint256 basePrice
BigInt(order.endedAt || 0),
// uint256 endedAt
BigInt(order.endedPrice || 0),
// uint256 endedPrice
BigInt(order.expectedState || 0),
// uint256 expectedState
BigInt(order.nonce || 0),
// uint256 nonce
BigInt(order.marketFeePercentage || 425)
// uint256 marketFeePercentage
]
];
const encodedOrderData = AbiCoder5.defaultAbiCoder().encode(
orderTypes,
orderData
);
const referralAddr = "0xa7d8ca624656922c633732fa2f327f504678d132";
const parsedExpectedState = order.expectedState && order.expectedState !== "" ? BigInt(order.expectedState) : 0n;
const settleInfo = {
orderData: encodedOrderData,
signature: order.signature,
referralAddr,
expectedState: parsedExpectedState,
recipient: address,
refunder: address
};
try {
const ERC1155_EXCHANGE_CONTRACT = getERC1155ExchangeContract();
const settleInfoTuple = [
settleInfo.orderData,
settleInfo.signature,
settleInfo.referralAddr,
settleInfo.expectedState,
settleInfo.recipient,
settleInfo.refunder
];
const totalSettlePrice = BigInt(order.currentPrice) * BigInt(quantity);
const orderExchangeData = ERC1155_EXCHANGE_CONTRACT.interface.encodeFunctionData("settleOrder", [
settleInfoTuple,
BigInt(quantity),
totalSettlePrice
]);
const marketplaceContract = getMarketplaceContract(signer);
const gatewayGasEstimate = await marketplaceContract.interactWith.estimateGas(
"ERC1155_EXCHANGE",
orderExchangeData,
{
gasPrice
}
);
const gatewayTx = await marketplaceContract.interactWith(
"ERC1155_EXCHANGE",
orderExchangeData,
{
gasPrice,
gasLimit: Math.min(Number(gatewayGasEstimate) + 5e4, 6e5)
}
);
const gatewayReceipt = await gatewayTx.wait();
if (gatewayReceipt?.status === 1) {
return gatewayReceipt;
} else {
return false;
}
} catch (gatewayError) {
return false;
}
} catch (error) {
return false;
}
}
// lib/marketplace/settle-consumable-order.ts
import { AbiCoder as AbiCoder6 } from "ethers";
async function buyConsumableOrder(consumableId, quantity, signer, accessToken, skyMavisApiKey, options) {
const query = CONSUMABLE_QUERIES.GET_CONSUMABLE_ORDERS;
const { graphqlUrl, headers: apiHeaders } = getMarketplaceApi(skyMavisApiKey);
const headers = {
...apiHeaders,
authorization: `Bearer ${accessToken}`
};
try {
const address = await signer.getAddress();
const pageSize = 50;
let from = 0;
let totalOrders = Number.POSITIVE_INFINITY;
let order = null;
while (from < totalOrders) {
const results = await apiRequest(
graphqlUrl,
JSON.stringify({
operationName: "GetBuyNowErc1155Orders",
query,
variables: {
tokenType: "Consumable",
tokenId: consumableId,
from,
size: pageSize,
sort: "PriceAsc"
}
}),
headers
);
const orders = results.data?.erc1155Token?.orders?.data || [];
totalOrders = results.data?.erc1155Token?.orders?.total ?? from + orders.length;
if (!orders || orders.length === 0) {
break;
}
const validOrders = orders.filter((order2) => {
const isExpired = order2.expiredAt * 1e3 <= Date.now();
const hasQuantity = order2.assets?.[0]?.availableQuantity && parseInt(order2.assets[0].availableQuantity) > 0;
const isSelfOrder = order2.maker.toLowerCase() === address.toLowerCase();
return !isExpired && hasQuantity && !isSelfOrder;
});
const sortedValidOrders = validOrders.sort((a, b) => {
const priceA = parseFloat(a.currentPrice);
const priceB = parseFloat(b.currentPrice);
return priceA - priceB;
});
for (const candidateOrder of sortedValidOrders) {
const availableQuantity = parseInt(
candidateOrder.assets[0].availableQuantity
);
if (quantity <= availableQuantity) {
order = candidateOrder;
break;
}
}
if (order || orders.length < pageSize) {
break;
}
from += orders.length;
}
if (!order) {
return false;
}
const wethContract = getWETHContract(signer);
const wethBalance = await wethContract.balanceOf(address);
const totalCost = BigInt(order.currentPrice) * BigInt(quantity);
if (BigInt(wethBalance) < totalCost) {
return false;
}
const erc1155ExchangeContract = getERC1155ExchangeContract();
const erc1155ExchangeAddress = await erc1155ExchangeContract.getAddress();
const allowance = await wethContract.allowance(
address,
erc1155ExchangeAddress
);
const gasPrice = await getGasPrice(signer, options);
if (BigInt(allowance) < totalCost) {
const approveTx = await wethContract.approve(
erc1155ExchangeAddress,
totalCost,
{ gasPrice }
);
await approveTx.wait();
}
const orderTypes = [
"(address,uint8,uint8,address,uint256,uint256,uint256,address,uint256,uint256,uint256,uint256,uint256,uint256,uint256)"
];
const orderData = [
[
order.maker,
// address maker
1,
// uint8 kind (sell = 1)
2,
// uint8 erc type (ERC1155 = 2)
order.assets[0].address,
// address asset contract
BigInt(order.assets[0].id),
// uint256 asset id
BigInt(order.assets[0].quantity),
// uint256 original order quantity
BigInt(order.expiredAt),
// uint256 expiredAt
order.paymentToken,
// address paymentToken
BigInt(order.startedAt),
// uint256 startedAt
BigInt(order.basePrice),
// uint256 basePrice
BigInt(order.endedAt || 0),
// uint256 endedAt
BigInt(order.endedPrice || 0),
// uint256 endedPrice
BigInt(order.expectedState || 0),
// uint256 expectedState
BigInt(order.nonce || 0),
// uint256 nonce
BigInt(order.marketFeePercentage || 425)
// uint256 marketFeePercentage
]
];
const encodedOrderData = AbiCoder6.defaultAbiCoder().encode(
orderTypes,
orderData
);
const referralAddr = "0xa7d8ca624656922c633732fa2f327f504678d132";
const parsedExpectedState = order.expectedState && order.expectedState !== "" ? BigInt(order.expectedState) : 0n;
const settleInfo = {
orderData: encodedOrderData,
signature: order.signature,
referralAddr,
expectedState: parsedExpectedState,
recipient: address,
refunder: address
};
try {
const ERC1155_EXCHANGE_CONTRACT = getERC1155ExchangeContract();
const marketplaceContract = getMarketplaceContract(signer);
const settleInfoTuple = [
settleInfo.orderData,
settleInfo.signature,
settleInfo.referralAddr,
settleInfo.expectedState,
settleInfo.recipient,
settleInfo.refunder
];
const totalSettlePrice = BigInt(order.currentPrice) * BigInt(quantity);
const orderExchangeData = ERC1155_EXCHANGE_CONTRACT.interface.encodeFunctionData("settleOrder", [
settleInfoTuple,
BigInt(quantity),
totalSettlePrice
]);
const gatewayGasEstimate = await marketplaceContract.interactWith.estimateGas(
"ERC1155_EXCHANGE",
orderExchangeData,
{
gasPrice
}
);
const gatewayTx = await marketplaceContract.interactWith(
"ERC1155_EXCHANGE",
orderExchangeData,
{
gasPrice,
gasLimit: Math.min(Number(gatewayGasEstimate) + 5e4, 6e5)
}
);
const gatewayReceipt = await gatewayTx.wait();
if (gatewayReceipt?.status === 1) {
return gatewayReceipt;
} else {
return false;
}
} catch (gatewayError) {
return false;
}
} catch (error) {
return false;
}
}
// index.ts
import { Wallet, parseEther, parseUnits as parseUnits2, formatEther as formatEther2 } from "ethers";
// cli.ts
import "dotenv/config";
var getAxieId = async () => {
const response = await prompts2({
type: "number",
name: "axieId",
message: "\u{1F194} Enter Axie ID:",
validate: (value) => value !== void 0 && !isNaN(value)
});
if (response.axieId === void 0) {
console.log("\u274C Invalid Axie ID!");
return null;
}
return response.axieId;
};
var getMaterialId = async (skyMavisApiKey) => {
const response = await prompts2({
type: "text",
name: "materialId",
message: "\u{1F194} Enter Material ID:",
validate: (value) => value !== void 0 && value.length > 0
});
if (response.materialId === void 0) {
console.log("\u274C Invalid Material ID!");
return null;
}
console.log("\u{1F50D} Validating material token...");
const materialInfo = await validateMaterialToken(
response.materialId,
skyMavisApiKey
);
if (!materialInfo) {
console.log("\u274C Material ID not found or invalid!");
return null;
}
console.log(`\u2705 Found material: ${materialInfo.name}`);
console.log(`\u{1F4C4} Description: ${materialInfo.description}`);
console.log(`\u{1F4E6} Total Supply: ${materialInfo.totalSupply}`);
console.log(`\u{1F465} Total Owners: ${materialInfo.totalOwners}`);
if (materialInfo.minPrice) {
console.log(
`\u{1F4B0} Min Price: ${(Number(materialInfo.minPrice) / 1e18).toFixed(6)} WETH`
);
}
if (materialInfo.orders) {
console.log(`\u{1F6D2} Listed Quantity: ${materialInfo.orders.totalListed}`);
console.log(`\u{1F4CB} Total Orders: ${materialInfo.orders.totalOrders}`);
}
return response.materialId;
};
var getConsumableId = async (skyMavisApiKey) => {
const response = await prompts2({
type: "text",
name: "consumableId",
message: "\u{1F194} Enter Consumable ID:",
validate: (value) => value !== void 0 && value.length > 0
});
if (response.consumableId === void 0) {
console.log("\u274C Invalid Consumable ID!");
return null;
}
console.log("\u{1F50D} Validating consumable token...");
const consumableInfo = await validateConsumableToken(
response.consumableId,
skyMavisApiKey
);
if (!consumableInfo) {
console.log("\u274C Consumable ID not found or invalid!");
return null;
}
console.log(`\u2705 Found consumable: ${consumableInfo.name}`);
console.log(`\u{1F4C4} Description: ${consumableInfo.description}`);
console.log(`\u{1F4E6} Total Supply: ${consumableInfo.totalSupply}`);
console.log(`\u{1F465} Total Owners: ${consumableInfo.totalOwners}`);
if (consumableInfo.minPrice) {
console.log(
`\u{1F4B0} Min Price: ${(Number(consumableInfo.minPrice) / 1e18).toFixed(6)} WETH`
);
}
if (consumableInfo.orders) {
console.log(`\u{1F6D2} Listed Quantity: ${consumableInfo.orders.totalListed}`);
console.log(`\u{1F4CB} Total Orders: ${consumableInfo.orders.totalOrders}`);
}
return response.consumableId;
};
var getQuantity = async (optional = false) => {
const message = optional ? "\u{1F4E6} Enter Quantity (leave empty to use all available):" : "\u{1F4E6} Enter Quantity:";
const response = await prompts2({
type: "number",
name: "quantity",
message,
validate: (value) => optional || !isNaN(value) && value > 0
});
if (!optional && !response.quantity) {
console.log("\u274C Invalid Quantity!");
return null;
}
return response.quantity || null;
};
var getPrice = async (optional = false, materialId, skyMavisApiKey, quantity, isAxie = false, consumableIdOrMessage) => {
const customMessage = isAxie ? consumableIdOrMessage : void 0;
const consumableId = !isAxie ? consumableIdOrMessage : void 0;
const message = customMessage ? customMessage : optional ? "\u{1F4B0} Enter Price (in WETH, leave empty to use floor price):" : "\u{1F4B0} Enter Price (in WETH):";
const response = await prompts2({
type: "text",
name: "price",
message,
validate: (value) => optional ? true : value && value.length > 0 && !isNaN(parseFloat(value))
});
if (!optional && !response.price) {
console.log("\u274C Invalid Price!");
return null;
}
if (optional && !response.price?.trim() && skyMavisApiKey) {
console.log("\u{1F50D} Getting floor price from marketplace...");
let floorPrice = null;
if (isAxie) {
floorPrice = await getAxieFloorPrice(skyMavisApiKey);
} else if (materialId) {
floorPrice = await getMaterialFloorPrice(
materialId,
skyMavisApiKey,
quantity
);
} else if (consumableId) {
floorPrice = await getConsumableFloorPrice(
consumableId,
skyMavisApiKey,
quantity
);
}
if (!floorPrice) {
console.log(
"\u274C Could not determine floor price. Please enter a price manually."
);
return null;
}
console.log(`\u{1F4B0} Using floor price: ${floorPrice} WETH`);
return floorPrice;
}
return response.price || null;
};
async function main() {
let skyMavisApiKey = process.env.SKYMAVIS_API_KEY;
if (!skyMavisApiKey) {
const response = await prompts2({
type: "text",
name: "apiKey",
message: "\u{1F511} Enter your Skymavis project API key (get from https://developers.roninchain.com/console/applications/):",
validate: (value) => value !== void 0 && value !== ""
});
skyMavisApiKey = response.apiKey;
if (!skyMavisApiKey) {
console.log("\u274C API key is required");
process.exit(1);
}
}
let privateKey = process.env.PRIVATE_KEY;
if (!privateKey) {
const response = await prompts2({
type: "password",
name: "privateKey",
message: "\u{1F510} Enter your private key:",
validate: (value) => {
if (!value) return false;
return isHexString(value, 32) || isHexString(`0x${value}`, 32);
}
});
privateKey = response.privateKey;
if (!privateKey) {
console.log("\u274C Private key is required");
process.exit(1);
}
privateKey = privateKey.startsWith("0x") ? privateKey : `0x${privateKey}`;
}
const provider = createProvider(skyMavisApiKey);
const wallet = new Wallet2(privateKey, provider);
const address = await wallet.getAddress();
while (true) {
try {
const response = await prompts2({
type: "select",
name: "action",
message: "What would you like to do?",
choices: [
{ title: "Get account info", value: "account" },
{ title: "Refresh access token", value: "refresh-token" },
{ title: "Approve WETH", value: "approve-weth" },
{ title: "Approve Axie marketplace", value: "approve-marketplace" },
{
title: "Approve Material marketplace",
value: "approve-material-marketplace"
},
{
title: "Approve Consumable marketplace",
value: "approve-consumable-marketplace"
},
{ title: "Settle axie order (buy axie)", value: "settle" },
{
title: "Settle material order (buy material)",
value: "settle-material"
},
{
title: "Settle consumable order (buy consumable)",
value: "settle-consumable"
},
{ title: "Cancel axie order (delist axie)", value: "cancel" },
{
title: "Cancel material order (delist materials)",
value: "cancel-material"
},
{
title: "Cancel consumable order (delist consumables)",
value: "cancel-consumable"
},
{
title: "Cancel all axie orders (delist all axies)",
value: "cancel-all"
},
{ title: "Create axie order (list axie)", value: "create" },
{
title: "Create material order (list material)",
value: "create-material"
},
{
title: "Create consumable order (list consumable)",
value: "create-consumable"
},
{
title: "Create axie auction (list axie for auction)",
value: "create-auction"
},
{
title: "Create orders for all axies (list all)",
value: "create-all"
},
{
title: "Create auction orders for all axies (list all as auctions)",
value: "create-auction-all"
},
{ title: "Transfer axie", value: "transfer" },
{ title: "Transfer all axies", value: "transfer-all" },
{ title: "List all axies (comma-separated)", value: "list-all" }
]
});
const action = response.action;
if (!action) {
console.log("\u274C Action selection cancelled");
break;
}
switch (action) {
case "account": {
const info = await getAccountInfo(address, provider, skyMavisApiKey);
console.log(`\u{1F4EC} Address: ${info.address}`);
console.log("\u{1F4B0} RON Balance:", info.ronBalance);
console.log("\u{1F4B0} WETH Balance:", info.wethBalance);
console.log(
"\u{1F6D2} Marketplace WETH allowance:",
info.allowance !== 0n ? "\u2705 Granted" : "\u274C Not granted"
);
console.log(
"\u{1F510} Marketplace approval for Axies:",
info.isApprovedForAll ? "\u2705 Approved" : "\u274C Not approved"
);
console.log(
"\u{1F510} Marketplace approval for Materials:",
info.isMaterialApprovedForAll ? "\u2705 Approved" : "\u274C Not approved"
);
const token = process.env.MARKETPLACE_ACCESS_TOKEN;
if (token) {
const expInfo = getTokenExpirationInfo(token);
console.log(
"\u{1F3AB} Access token:",
expInfo.isExpired ? "\u274C" : "\u2705",
expInfo.humanReadable
);
}
console.log(`\u{1F43E} Number of Axies: ${info.axieIds.length}`);
if (info.axieIds.length > 0) {
console.log(`\u{1F194} Axie IDs: ${info.axieIds.join(", ")}`);
}
console.log(`\u{1F9EA} Number of Materials: ${info.materials.length}`);
if (info.materials.length > 0) {
console.log("\u{1F4CB} Materials:");
info.materials.forEach((material) => {
console.log(
` \u2022 ${material.name} (ID: ${material.tokenId})${material.quantity ? ` - Qty: ${material.quantity}` : ""}${material.orders?.total ? ` - Listed: ${material.orders.total}` : ""}`
);
});
}
break;
}
case "refresh-token": {
let refreshTokenValue = process.env.MARKETPLACE_REFRESH_TOKEN;
if (!refreshTokenValue) {
const response2 = await prompts2({
type: "text",
name: "refreshToken",
message: "Enter refresh token",
validate: (value) => value.length > 0
});
refreshTokenValue = response2.refreshToken;
if (!refreshTokenValue) {
console.log("\u274C Refresh token is required");
break;
}
}
const result = await refreshToken(refreshTokenValue);
console.log("\u2705 Token refreshed!");
console.log(
"\u{1F3AB} Access token:",
result.expirationInfo.isExpired ? "\u274C" : "\u2705",
result.expirationInfo.humanReadable
);
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 "approve-material-marketplace": {
await approveMaterialMarketplace(wallet);
break;
}
case "approve-consumable-marketplace": {
await approveConsumableMarketplace(wallet);
break;
}
case "settle": {
const token = await ensureMarketplaceToken();
const axieId = await getAxieId();
if (!axieId) break;
await approveWETH(wallet);
const receipt = await buyMarketplaceOrder(
axieId,
wallet,
token,
skyMavisApiKey
);
if (receipt) {
console.log("\u{1F680} Transaction successful! Hash:", receipt.hash);
console.log(
"\u{1F517} View transaction: https://app.roninchain.com/tx/" + receipt.hash
);
}
break;
}
case "settle-material": {
const token = await ensureMarketplaceToken();
const materialId = await getMaterialId(skyMavisApiKey);
if (!materialId) break;
const quantity = await getQuantity();
if (!quantity) break;
await approveWETH(wallet);
const receipt = await buyMaterialOrder(
materialId,
quantity,
wallet,
token,
skyMavisApiKey
);
if (receipt) {
console.log("\u{1F680} Transaction successful! Hash:", receipt.hash);
console.log(
"\u{1F517} View transaction: https://app.roninchain.com/tx/" + receipt.hash
);
}
break;
}
case "settle-consumable": {
const token = await ensureMarketplaceToken();
const consumableId = await getConsumableId(skyMavisApiKey);
if (!consumableId) break;
const quantity = await getQuantity();
if (!quantity) break;
await approveWETH(wallet);
const receipt = await buyConsumableOrder(
consumableId,
quantity,
wallet,
token,
skyMavisApiKey
);
if (receipt) {
console.log("\u{1F680} Transaction successful! Hash:", receipt.hash);
console.log(
"\u{1F517} View transaction: https://app.roninchain.com/tx/" + receipt.hash
);
}
break;
}
case "create": {
const axieId = await getAxieId();
if (!axieId) break;
const token = await ensureMarketplaceToken();
const basePrice = await getPrice(
true,
void 0,
skyMavisApiKey,
void 0,
true
);
if (!basePrice) break;
await approveMarketplaceContract(wallet);
const currentBlock = await provider.getBlock("latest");
const startedAt = currentBlock.timestamp;
const expiredAt = startedAt + 15634800;
const orderData = {
address,
axieId: axieId.toString(),
basePrice: parseEther2(basePrice).toString(),
endedPrice: "0",
startedAt,
endedAt: 0,
expiredAt
};
const result = await createMarketplaceOrder(
orderData,
token,
wallet,
skyMavisApiKey
);
if (result === null || result.errors || !result.data) {
console.error(
"\u274C Error:",
result?.errors?.[0]?.message || "Unknown error"
);
break;
}
console.log(
`\u2705 Created order for Axie ${axieId}! Current price in USD: ${result.data.createOrder.currentPriceUsd}`
);
break;
}
case "create-material": {
const materialId = await getMaterialId(skyMavisApiKey);
if (!materialId) break;
const quantity = await getQuantity(true);
const price = await getPrice(
true,
materialId,
skyMavisApiKey,
quantity
);
if (!price) break;
const token = await ensureMarketplaceToken();
await approveMaterialMarketplace(wallet);
const currentBlock = await provider.getBlock("latest");
const startedAt = currentBlock.timestamp;
const expiredAt = startedAt + 15634800;
const orderData = {
address,
materialId: materialId.toString(),
quantity: quantity ? quantity.toString() : void 0,
unitPrice: parseEther2(price).toString(),
endedUnitPrice: "0",
startedAt,
endedAt: 0,
expiredAt
};
const result = await createMaterialMarketplaceOrder(
orderData,
token,
wallet,
skyMavisApiKey
);
if (result === null || result.errors || !result.data) {
console.error(
"\u274C Error:",
result?.errors?.[0]?.message || "Unknown error"
);
break;
}
console.log(
`\u2705 Created material order for Material ${materialId}${quantity ? ` (qty: ${quantity})` : " (all available)"}! Current price in USD: ${result.data.createOrder.currentPriceUsd}`
);
break;
}
case "create-consumable": {
const consumableId = await getConsumableId(skyMavisApiKey);
if (!consumableId) break;
const quantity = await getQuantity(true);
const price = await getPrice(
true,
void 0,
skyMavisApiKey,
quantity,
false,
consumableId
);
if (!price) break;
const token = await ensureMarketplaceToken();
await approveConsumableMarketplace(wallet);
const currentBlock = await provider.getBlock("latest");
const startedAt = currentBlock.timestamp;
const expiredAt = startedAt + 15634800;
const orderData = {
address,
consumableId: consumableId.toString(),
quantity: quantity ? quantity.toString() : void 0,
unitPrice: parseEther2(price).toString(),
endedUnitPrice: "0",
startedAt,
endedAt: 0,
expiredAt
};
const result = await createConsumableMarketplaceOrder(
orderData,
token,
wallet,
skyMavisApiKey
);
if (result === null || result.errors || !result.data) {
console.error(
"\u274C Error:",
result?.errors?.[0]?.message || "Unknown error"
);
break;
}
console.log(
`\u2705 Created consumable order for Consumable ${consumableId}${quantity ? ` (qty: ${quantity})` : " (all available)"}! Current price in USD: ${result.data.createOrder.currentPriceUsd}`
);
break;
}
case "create-auction": {
const axieId = await getAxieId();
if (!axieId) break;
const token = await ensureMarketplaceToken();
const startPrice = await getPrice(
false,
// required, not optional
void 0,
skyMavisApiKey,
void 0,
true,
"\u{1F4B0} Enter starting price (in WETH):"
);
if (!startPrice) break;
const endPrice = await getPrice(
true,
void 0,
skyMavisApiKey,
void 0,
true,
"\u{1F3C1} Enter ending price (in WETH, leave empty to use floor price):"
);
if (!endPrice) break;
const durationResponse = await prompts2({
type: "number",
name: "duration",
message: "Enter auction duration in hours (1-168)",
validate: (value) => value >= 1 && value <= 168
});
const durationHours = durationResponse.duration;
if (!durationHours) {
console.log("\u274C Duration is required");
break;
}
await approveMarketplaceContract(wallet);
const currentBlock = await provider.getBlock("latest");
const startedAt = currentBlock.timestamp;
const endedAt = startedAt + durationHours * 3600;
const expiredAt = startedAt + 15634800;
const orderData = {
address,
axieId: axieId.toString(),
basePrice: parseEther2(startPrice).toString(),
endedPrice: parseEther2(endPrice).toString(),
startedAt,
endedAt,
expiredAt
};
const result = await createMarketplaceOrder(
orderData,
token,
wallet,
skyMavisApiKey
);
if (result === null || result.errors || !result.data) {
console.error(
"\u274C Error:",
result?.errors?.[0]?.message || "Unknown error"
);
break;
}
console.log(`\u2705 Created auction for Axie ${axieId}!`);
console.log(`Start price: ${startPrice} WETH`);
console.log(`End price: ${endPrice} WETH`);
console.log(`Duration: ${durationHours} hours`);
console.log(
`Current price in USD: ${result.data.createOrder.currentPriceUsd}`
);
break;
}
case "create-all": {
const token = await ensureMarketplaceToken();
const basePrice = await getPrice(
true,
void 0,
skyMavisApiKey,
void 0,
true
);
if (!basePrice) break;
await approveMarketplaceContract(wallet);
let axieIds = await getAxieIdsFromAccount(address, provider);
if (axieIds.length > 100) {
console.log(
"\u26A0\uFE0F 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;
for (const axieId of axieIds) {
const orderData = {
address,
axieId: axieId.toString(),
basePrice: parseEther2(basePrice).toString(),
endedPrice: "0",
startedAt,
endedAt: 0,
expiredAt
};
const result = await createMarketplaceOrder(
orderData,
token,
wallet,
skyMavisApiKey
);
if (result === null || result.errors || !result.data) {
console.error(
`\u274C Error creating order for Axie ${axieId}:`,
result?.errors?.[0]?.message || "Unknown error"
);
continue;
}
console.log(
`\u2705 Created order for Axie ${axieId}! Current price in USD: ${result.data.createOrder.currentPriceUsd}`
);
}
break;
}
case "create-auction-all": {
const token = await ensureMarketplaceToken();
const startPrice = await getPrice(
false,
// required, not optional
void 0,
skyMavisApiKey,
void 0,
true,
"\u{1F4B0} Enter starting price for all auctions (in WETH):"
);
if (!startPrice) break;
const endPrice = await getPrice(
true,
void 0,
skyMavisApiKey,
void 0,
true,
"\u{1F3C1} Enter ending price for all auctions (in WETH, leave empty to use floor price):"
);
if (!endPrice) break;
const durationResponse = await prompts2({
type: "number",
name: "duration",
message: "Enter auction duration in hours for all auctions (1-168):",
validate: (value) => value >= 1 && value <= 168
});
const durationHours = durationResponse.duration;
if (!durationHours) {
console.log("\u274C Duration is required");
break;
}
await approveMarketplaceContract(wallet);
let axieIds = await getAxieIdsFromAccount(address, provider);
if (axieIds.length > 100) {
console.log(
"\u26A0\uFE0F 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 endedAt = startedAt + durationHours * 3600;
const expiredAt = startedAt + 15634800;
console.log(`\u{1F680} Creating auctions for ${axieIds.length} Axies...`);
console.log(`Start price: ${startPrice} WETH`);
console.log(`End price: ${endPrice} WETH`);
console.log(`Duration: ${durationHours} hours`);
let successCount = 0;
let errorCount = 0;
for (const axieId of axieIds) {
const orderData = {
address,
axieId: axieId.toString(),
basePrice: parseEther2(startPrice).toString(),
endedPrice: parseEther2(endPrice).toString(),
startedAt,
endedAt,
expiredAt
};
const result = await createMarketplaceOrder(
orderData,
token,
wallet,
skyMavisApiKey
);
if (result === null || result.errors || !result.data) {
console.error(
`\u274C Error creating auction for Axie ${axieId}:`,
result?.errors?.[0]?.message || "Unknown error"
);
errorCount++;
continue;
}
console.log(
`\u2705 Created auction for Axie ${axieId}! Current price in USD: ${result.data.createOrder.currentPriceUsd}`
);
successCount++;
}
console.log(
`\u{1F3C1} Summary: ${successCount} auctions created successfully, ${errorCount} errors`
);
break;
}
case "cancel": {
const axieId = await getAxieId();
if (!axieId) break;
const receipt = await cancelMarketplaceOrder(
axieId,
wallet,
skyMavisApiKey
);
if (receipt) {
console.log("\u2705 Order cancelled! Transaction hash:", receipt.hash);
console.log(
"\u{1F517} View transaction: https://app.roninchain.com/tx/" + receipt.hash
);
}
break;
}
case "cancel-material": {
const materialId = await getMaterialId(skyMavisApiKey);
if (!materialId) break;
const result = await cancelMaterialOrder(
materialId,
wallet,
skyMavisApiKey
);
if (result && "canceled" in result && result.canceled > 0) {
console.log(
`\u2705 Successfully cancelled ${result.canceled} material order(s)!`
);
if (result.canceledOrders.length > 0) {
result.canceledOrders.forEach((order) => {
console.log(
"\u{1F517} View transaction: https://app.roninchain.com/tx/" + order.transactionHash
);
});
}
} else if (result && "message" in result) {
console.log("\u274C", result.message);
}
break;
}
case "cancel-consumable": {
const consumableId = await getConsumableId(skyMavisApiKey);
if (!consumableId) break;
const result = await cancelConsumableOrder(
consumableId,
wallet,
skyMavisApiKey
);
if (result && "canceled" in result && result.canceled > 0) {
console.log(
`\u2705 Successfully cancelled ${result.canceled} consumable order(s)!`
);
if (result.canceledOrders.length > 0) {
result.canceledOrders.forEach((order) => {
console.log(
"\u{1F517} View transaction: https://app.roninchain.com/tx/" + order.transactionHash
);
});
}
} else if (result && "message" in result) {
console.log("\u274C", result.message);
}
break;
}
case "cancel-all": {
const fromAddress = await wallet.getAddress();
let axieIds = await getAxieIdsFromAccount(fromAddress, provider);
if (axieIds.length === 0) {
console.log("\u274C No Axies found in your account");
break;
}
if (axieIds.length > 100) {
console.log(
"\u26A0\uFE0F Warning: Can only cancel up to 100 orders at once, only cancelling the first 100"
);
axieIds = axieIds.slice(0, 100);
}
console.log(
`\u{1F4E6} Cancelling orders for ${axieIds.length} Axies using batch transfer...`
);
try {
const receipt = await batchTransferAxies(
wallet,
fromAddress,
axieIds
);
if (receipt) {
console.log(
`\u2705 Successfully cancelled all orders for ${axieIds.length} Axies in one transaction!`
);
console.log("\u{1F680} Transaction hash:", receipt.hash);
console.log(
"\u{1F517} View transaction: https://app.roninchain.com/tx/" + receipt.hash
);
}
} catch (error) {
console.log(
`\u274C Error cancelling orders: ${error instanceof Error ? error.message : error}`
);
}
break;
}
case "transfer": {
const axieId = await getAxieId();
if (!axieId) break;
const response2 = await prompts2({
type: "text",
name: "address",
message: "Enter recipient address",
validate: (value) => value.length > 0
});
const address2 = response2.address;
if (!address2) {
console.log("\u274C Recipient address is required");
break;
}
const receipt = await transferAxie(wallet, address2, axieId);
if (receipt) {
console.log("\u2705 Axie transferred! Transaction hash:", receipt.hash);
console.log(
"\u{1F517} View transaction: https://app.roninchain.com/tx/" + receipt.hash
);
}
break;
}
case "transfer-all": {
const response2 = await prompts2({
type: "text",
name: "address",
message: "Enter recipient address",
validate: (value) => value.length > 0
});
const address2 = response2.address;
if (!address2) {
console.log("\u274C Recipient address is required");
break;
}
const fromAddress = await wallet.getAddress();
let axieIds = await getAxieIdsFromAccount(fromAddress, provider);
if (axieIds.length > 100) {
console.log(
"\u26A0\uFE0F 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, address2, axieIds);
if (receipt) {
console.log(
"\u2705 Axies transferred! Transaction hash:",
receipt.hash
);
console.log(
"\u{1F517} View transaction: https://app.roninchain.com/tx/" + receipt.hash
);
}
break;
}
case "list-all": {
const addressResponse = await prompts2({
type: "text",
name: "queryAddress",
message: "Enter address to export axies from (leave empty for your own):",
validate: (value) => !value || value.startsWith("0x")
});
const queryAddress = addressResponse.queryAddress || address;
console.log(`\u{1F50D} Fetching all axies for ${queryAddress}...`);
const axieIds = await getAxieIdsFromAccount(queryAddress, provider);
if (axieIds.length === 0) {
console.log("\u274C No Axies found");
break;
}
const csvResponse = await prompts2({
type: "select",
name: "format",
message: "How would you like to export?",
choices: [
{ title: "Print to screen", value: "screen" },
{ title: "Save as CSV file", value: "csv" }
]
});
const commaSeparated = axieIds.join(",");
if (csvResponse.format === "screen") {
console.log(`
\u{1F4CB} Axies (${axieIds.length} total):`);
console.log(commaSeparated);
} else if (csvResponse.format === "csv") {
const filenameResponse = await prompts2({
type: "text",
name: "filename",
message: "Enter filename (without .csv extension):",
validate: (value) => value.length > 0
});
if (!filenameResponse.filename) {
console.log("\u274C Filename is required");
break;
}
const fs = await import("fs/promises");
const filename = `${filenameResponse.filename}.csv`;
await fs.writeFile(filename, commaSeparated);
console.log(`\u2705 Saved ${axieIds.length} axie IDs to ${filename}`);
}
break;
}
}
} catch (error) {
if (error instanceof Error) {
console.error("\u274C Error:", error.message);
} else {
console.error("\u274C Error:", error);
}
} finally {
await askToContinue();
}
}
}
main();