const fetch = require('node-fetch');
const { ethers } = require('ethers');
// .env must contain:
// - OWNER_ADDRESS
// - OWNER_PRIVATE_KEY
// - POLYGON_ALCHEMY_KEY
// - OPENSEA_API_KEY
require('dotenv').config();
const OPENSEA_CONDUIT_KEY = '0x0000007b02230091a7ed01230072f7006a004d60a8d4e71d599b8104250f0000';
const CROSS_CHAIN_SEAPORT_ADDRESS = '0x00000000006c3852cbef3e08e8df289169ede581';
const SEAPORT_CONTRACT_NAME = 'Seaport';
const SEAPORT_CONTRACT_VERSION = '1.1';
const EIP_712_ORDER_TYPE = {
OrderComponents: [
{ name: 'offerer', type: 'address' },
{ name: 'zone', type: 'address' },
{ name: 'offer', type: 'OfferItem[]' },
{ name: 'consideration', type: 'ConsiderationItem[]' },
{ name: 'orderType', type: 'uint8' },
{ name: 'startTime', type: 'uint256' },
{ name: 'endTime', type: 'uint256' },
{ name: 'zoneHash', type: 'bytes32' },
{ name: 'salt', type: 'uint256' },
{ name: 'conduitKey', type: 'bytes32' },
{ name: 'counter', type: 'uint256' },
],
OfferItem: [
{ name: 'itemType', type: 'uint8' },
{ name: 'token', type: 'address' },
{ name: 'identifierOrCriteria', type: 'uint256' },
{ name: 'startAmount', type: 'uint256' },
{ name: 'endAmount', type: 'uint256' },
],
ConsiderationItem: [
{ name: 'itemType', type: 'uint8' },
{ name: 'token', type: 'address' },
{ name: 'identifierOrCriteria', type: 'uint256' },
{ name: 'startAmount', type: 'uint256' },
{ name: 'endAmount', type: 'uint256' },
{ name: 'recipient', type: 'address' },
],
};
const ITEM_TYPE = {
NATIVE: 0,
ERC20: 1,
ERC721: 2,
ERC1155: 3,
ERC721_WITH_CRITERIA: 4,
ERC1155_WITH_CRITERIA: 5,
}
const POLYGON_ALCHEMY_URL = 'https://polygon-mainnet.g.alchemy.com/v2/' + process.env.POLYGON_ALCHEMY_KEY;
const POLYGON_CHAIN_ID = 137
const OPENSEA_API_URL = 'https://api.opensea.io/v2/orders/matic/seaport/listings';
//struct OrderParameters {
// address offerer;
// address zone;
// struct OfferItem[] offer;
// struct ConsiderationItem[] consideration;
// enum OrderType orderType;
// uint256 startTime;
// uint256 endTime;
// bytes32 zoneHash;
// uint256 salt;
// bytes32 conduitKey;
// uint256 totalOriginalConsiderationItems;
//}
/**
* Submits a request to your provider to sign the order.
* @param {Object} signer
* @param {Object} orderParameters - standard OrderParameters struct (see above)
* @param {number} counter - counter of the offerer
* @returns {string} the EIP-2098 order signature
*/
async function signOrder(signer, orderParameters, counter)
{
const domainData = {
name: SEAPORT_CONTRACT_NAME,
version: SEAPORT_CONTRACT_VERSION,
chainId: POLYGON_CHAIN_ID,
verifyingContract: CROSS_CHAIN_SEAPORT_ADDRESS,
};
const orderComponents = {
...orderParameters,
counter,
};
const signature = await signer._signTypedData(
domainData,
EIP_712_ORDER_TYPE,
orderComponents
);
// Use EIP-2098 compact signatures to save gas. https://eips.ethereum.org/EIPS/eip-2098
return ethers.utils.splitSignature(signature).compact;
}
/**
* Retrieves listings on OpenSea
* See https://docs.opensea.io/v2.0/reference/retrieve-listings
* @param {string} nftContractAddress
* @param {string} nftTokenId
* @returns {Object} API result (Promise)
*/
function getPolygonNFTListings(nftContractAddress, nftTokenId)
{
return fetch(`${OPENSEA_API_URL}?asset_contract_address=${nftContractAddress}&token_ids=${nftTokenId}`, {
method: 'GET',
headers: {'X-API-KEY': process.env.OPENSEA_API_KEY},
})
.then((data) => data.json());
}
/**
* Creates new listing on OpenSea
* See https://docs.opensea.io/v2.0/reference/create-an-order
* @param {Object} signer
* @param {string} nftContractAddress
* @param {string} nftTokenId
* @param {number} nftItemType - 2 for ERC721, 3 for ERC1155
* @param {string} nftPriceEth
* @param {number} startTime - Unix timestamp, in seconds
* @param {number} endTime - Unix timestamp, in seconds
* @returns {Object} API result (Promise)
*/
async function listPolygonNFT(signer, nftContractAddress, nftTokenId, nftItemType, nftPriceEth, startTime, endTime)
{
// 2.5 % fee
const priceNet = (ethers.utils.parseEther(nftPriceEth) * .975).toString();
const priceFee = (ethers.utils.parseEther(nftPriceEth) * .025).toString();
const orderParameters = {
offerer: process.env.OWNER_ADDRESS,
zone: '0x0000000000000000000000000000000000000000',
offer: [
{
itemType: nftItemType,
token: nftContractAddress,
identifierOrCriteria: nftTokenId,
startAmount: '1',
endAmount: '1',
},
],
consideration: [
{
itemType: 1,
token: '0x7ceB23fD6bC0adD59E62ac25578270cFf1b9f619',
identifierOrCriteria: '0',
startAmount: priceNet,
endAmount: priceNet,
recipient: process.env.OWNER_ADDRESS,
},
{
itemType: 1,
token: '0x7ceB23fD6bC0adD59E62ac25578270cFf1b9f619',
identifierOrCriteria: '0',
startAmount: priceFee,
endAmount: priceFee,
recipient: '0x0000a26b00c1F0DF003000390027140000fAa719',
},
],
orderType: 1,
startTime,
endTime,
zoneHash: '0x0000000000000000000000000000000000000000000000000000000000000000',
salt: 0,
conduitKey: OPENSEA_CONDUIT_KEY,
totalOriginalConsiderationItems: 2,
};
const counter = 0;
const signature = await signOrder(
signer,
orderParameters,
counter,
);
const order = {
parameters: { ...orderParameters, counter },
signature,
};
return fetch(OPENSEA_API_URL, {
method: 'POST',
headers: {
'X-API-KEY': process.env.OPENSEA_API_KEY,
'Accept': 'application/json',
'Content-Type': 'application/json',
},
body: JSON.stringify(order),
})
.then((data) => data.json());
}
//######################################
// Example:
// list some Polygon ERC721 NFT for 0.005 ETH on OpenSea, starting right now, and ending in 7 days
//######################################
const provider = new ethers.providers.JsonRpcProvider(POLYGON_ALCHEMY_URL);
const signer = new ethers.Wallet(process.env.OWNER_PRIVATE_KEY, provider);
const nftContractAddress = '0x........................................';
const nftTokenId = '1';
const nftItemType = ITEM_TYPE.ERC721;
const nftPriceEth = '0.005';
const startTime = Math.floor(new Date().getTime() / 1000);
const endTime = startTime + 7 * 24 * 3600;
listPolygonNFT(signer, nftContractAddress, nftTokenId, nftItemType, nftPriceEth, startTime, endTime)
.then(result => {
console.log(result);
})
.catch((error) => {
console.error(error);
});
//######################################
// Example:
// get existing listings for some Polygon NFT on OpenSea
//######################################
//getPolygonNFTListings(nftContractAddress, nftTokenId)
//.then(result => {
// console.log(result);
//})
//.catch((error) => {
// console.error(error);
//});