UNPKG

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

2,469 lines 72 kB
// 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 getUSDCContract(signerOrProvider) {
  const address = USD_COIN.address;
  const abi = new Interface(USD_COIN.proxy_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";
export {
  Wallet,
  approveBatchTransfer,
  approveConsumableMarketplace,
  approveMarketplaceContract,
  approveMaterialMarketplace,
  approveWETH,
  askToContinue,
  batchTransferAxies,
  buyConsumableOrder,
  buyMarketplaceOrder,
  buyMaterialOrder,
  cancelConsumableOrder,
  cancelMarketplaceOrder,
  cancelMaterialOrder,
  createConsumableMarketplaceOrder,
  createMarketplaceOrder,
  createMaterialMarketplaceOrder,
  createProvider,
  ensureMarketplaceToken,
  formatEther2 as formatEther,
  getAccountInfo,
  getAxieContract,
  getAxieFloorPrice,
  getAxieIdsFromAccount,
  getConsumableFloorPrice,
  getGasPrice,
  getMaterialFloorPrice,
  getTokenExpirationInfo,
  getUSDCContract,
  getWETHContract,
  parseEther,
  parseUnits2 as parseUnits,
  refreshToken,
  transferAxie,
  validateConsumableToken,
  validateMaterialToken
};