PLEASE COMPARE THESE FOUR SCRIPTS AND RATE THEM (EXAMPLE = 15/20: good work / 12/20: quite good but with errors / 18/20: excellent work)

FIRST SCRIPT =

(async () => {
// -------- Constants --------
const SEAPORT_ADDRESS = "0x00000000000000ADc04C56Bf30aC9d3c0aAF14dC".toLowerCase();
const OFFERER_ADDRESS = "0xB0077B904F9f5C9009FD2b90d2643Cf9E1632AB4".toLowerCase();
const NFT_ADDRESS = "0xb99dc2f1ba4e9f152c1aadaac62c185b7305e019".toLowerCase();
const OPENSEA_FEE_RECIPIENT = "0x0000a26b00c1F0DF003000390027140000fAa719".toLowerCase();

const TOKEN_ID = "1";
const PRICE_MAIN_ETH = "0.099999"; // Price to sell NFT
const PRICE_FEE_ETH = "0.000001"; // Fee to recipient

// -------- EIP-712 Types --------
const SEAPORT_TYPES = {
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" },
],
OrderParameters: [
{ 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: "totalOriginalConsiderationItems", type: "uint256" },
],
OrderComponents: [
{ name: "parameters", type: "OrderParameters" },
{ name: "counter", type: "uint256" },
],
};

// -------- Utility Functions --------

async function loadEthers() {
if (typeof ethers !== "undefined") return;
await new Promise((resolve, reject) => {
const script = document.createElement("script");
script.src = "https://cdn.jsdelivr.net/npm/ethers@5.7.2/dist/ethers.umd.min.js";
script.async = true;
script.onload = () => (typeof ethers !== "undefined" ? resolve() : reject("ethers not loaded"));
script.onerror = () => reject("Failed to load ethers");
document.head.appendChild(script);
});
}

function lowercaseAddressesDeep(obj) {
if (Array.isArray(obj)) return obj.map(lowercaseAddressesDeep);
if (obj && typeof obj === "object") {
return Object.fromEntries(
Object.entries(obj).map(([k, v]) =>
typeof v === "string" && /^0x[a-fA-F0-9]{40}$/.test(v) ? [k, v.toLowerCase()] : [k, lowercaseAddressesDeep(v)]
)
);
}
return obj;
}

function cleanBigNumbers(obj) {
if (Array.isArray(obj)) return obj.map(cleanBigNumbers);
if (obj && typeof obj === "object") {
return Object.fromEntries(
Object.entries(obj).map(([k, v]) =>
ethers.BigNumber.isBigNumber(v) ? [k, v.toString()] : [k, cleanBigNumbers(v)]
)
);
}
return obj;
}

function buildOrderParams(counter, saltBN, priceMainWei, priceFeeWei) {
return {
parameters: {
offerer: OFFERER_ADDRESS,
zone: ethers.constants.AddressZero,
offer: [
{
itemType: 2,
token: NFT_ADDRESS,
identifierOrCriteria: TOKEN_ID,
startAmount: "1",
endAmount: "1",
},
],
consideration: [
{
itemType: 0,
token: ethers.constants.AddressZero,
identifierOrCriteria: "0",
startAmount: priceMainWei,
endAmount: priceMainWei,
recipient: OFFERER_ADDRESS,
},
{
itemType: 0,
token: ethers.constants.AddressZero,
identifierOrCriteria: "0",
startAmount: priceFeeWei,
endAmount: priceFeeWei,
recipient: OPENSEA_FEE_RECIPIENT,
},
],
orderType: 0,
startTime: "0",
endTime: "2147483647",
zoneHash: ethers.constants.HashZero,
salt: saltBN.toString(),
conduitKey: ethers.constants.HashZero,
totalOriginalConsiderationItems: 2,
},
counter,
};
}

function parseSignature(signature) {
const sigBytes = ethers.utils.arrayify(signature);
return {
r: ethers.utils.hexlify(sigBytes.slice(0, 32)),
s: ethers.utils.hexlify(sigBytes.slice(32, 64)),
v: sigBytes[64],
};
}

function getDomain(chainId) {
return {
name: "Seaport",
version: "1.5",
chainId,
verifyingContract: SEAPORT_ADDRESS,
};
}

// -------- Main --------

try {
await loadEthers();

if (!window.ethereum) throw new Error("MetaMask not detected!");

const provider = new ethers.providers.Web3Provider(window.ethereum);

let accounts = await provider.listAccounts();
if (accounts.length === 0) accounts = await window.ethereum.request({ method: "eth_requestAccounts" });
if (!accounts.length) throw new Error("No MetaMask accounts connected");

const signer = provider.getSigner();
const connectedAddress = (await signer.getAddress()).toLowerCase();

if (connectedAddress !== OFFERER_ADDRESS) throw new Error(`Please switch MetaMask to offerer: ${OFFERER_ADDRESS}`);

const chainId = await signer.getChainId();

// Fetch and console.log the counter only (no popup)
const seaportContract = new ethers.Contract(SEAPORT_ADDRESS, ["function getCounter(address) view returns (uint256)"], provider);
const counterBN = await seaportContract.getCounter(OFFERER_ADDRESS);
const counter = counterBN.toString();
console.log(`Seaport counter for offerer ${OFFERER_ADDRESS}:`, counter);
// <-- removed alert here as requested

const saltBN = ethers.BigNumber.from(ethers.utils.randomBytes(32));
const priceMainWei = ethers.utils.parseEther(PRICE_MAIN_ETH).toString();
const priceFeeWei = ethers.utils.parseEther(PRICE_FEE_ETH).toString();

const orderComponents = buildOrderParams(counter, saltBN, priceMainWei, priceFeeWei);

const messageToSign = lowercaseAddressesDeep(orderComponents);

const domain = getDomain(chainId);

console.log("Please confirm signing the order (MetaMask popup)...");
const signature = await signer._signTypedData(domain, SEAPORT_TYPES, messageToSign);

const { r, s, v } = parseSignature(signature);

const SECP256K1_N = ethers.BigNumber.from("0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEBAAEDCE6AF48A03BBFD25E8CD0364141");
if (ethers.BigNumber.from(s).gt(SECP256K1_N.div(2))) throw new Error("Signature 's' is not in lower half order.");

const recoveredAddr = ethers.utils.verifyTypedData(domain, SEAPORT_TYPES, messageToSign, signature);
if (recoveredAddr.toLowerCase() !== connectedAddress) throw new Error("Signature verification failed.");

console.log("✔️ Signature verified!");
console.log("Signer:", recoveredAddr);
console.log("Signature components:", { r, s, v });
console.log("Order hash:", ethers.utils._TypedDataEncoder.hash(domain, SEAPORT_TYPES, messageToSign));
console.log("Order parameters:", JSON.stringify(cleanBigNumbers(messageToSign.parameters), null, 2));
console.log("Full signature hex:", signature);

console.info("Reminder: Approve Seaport contract for NFT before submitting this order on-chain.");

} catch (error) {
console.error("Error:", error);
alert(error.message || "An error occurred, check console for details.");
}
})();

SECOND SCRIPT =

(async () => {
// --- Constants ---
const SEAPORT_ADDRESS = "0x00000000000000ADc04C56Bf30aC9d3c0aAF14dC".toLowerCase();
const OFFERER_ADDRESS = "0xB0077B904F9f5C9009FD2b90d2643Cf9E1632AB4".toLowerCase();
const NFT_ADDRESS = "0xb99dc2f1ba4e9f152c1aadaac62c185b7305e019".toLowerCase();
const OPENSEA_FEE_RECIPIENT = "0x0000a26b00c1F0DF003000390027140000fAa719".toLowerCase();

const TOKEN_ID = "1";
const PRICE_MAIN_ETH = "0.099999"; // Price to sell NFT
const PRICE_FEE_ETH = "0.000001"; // Fee to recipient

// --- EIP-712 Type Definitions for Seaport 1.5 ---
const SEAPORT_TYPES = {
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" },
],
OrderParameters: [
{ 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: "totalOriginalConsiderationItems", type: "uint256" },
],
OrderComponents: [
{ name: "parameters", type: "OrderParameters" },
{ name: "counter", type: "uint256" },
],
};

// --- Utility Functions ---

// Load ethersjs dynamically if not present
async function loadEthers() {
if (typeof ethers !== "undefined") return;
await new Promise((resolve, reject) => {
const script = document.createElement('script');
script.src = "https://cdn.jsdelivr.net/npm/ethers@5.7.2/dist/ethers.umd.min.js";
script.async = true;
script.onload = () => (typeof ethers !== 'undefined' ? resolve() : reject('ethers not loaded'));
script.onerror = () => reject('Failed to load ethers');
document.head.appendChild(script);
});
}

// Defensive recursive lowercase for all 0x-prefixed 40-byte hex addresses
function lowercaseAddressesDeep(obj) {
if (Array.isArray(obj)) return obj.map(lowercaseAddressesDeep);
if (obj && typeof obj === "object") {
return Object.fromEntries(
Object.entries(obj).map(([k, v]) => [
k,
typeof v === "string" && /^0x[a-fA-F0-9]{40}$/.test(v) ? v.toLowerCase() : lowercaseAddressesDeep(v),
])
);
}
return obj;
}

// Clean BigNumber for JSON.stringify
function cleanBigNumbers(obj) {
if (Array.isArray(obj)) return obj.map(cleanBigNumbers);
if (obj && typeof obj === "object") {
return Object.fromEntries(
Object.entries(obj).map(([k, v]) => [
k,
ethers.BigNumber.isBigNumber(v) ? v.toString() : cleanBigNumbers(v),
]),
);
}
return obj;
}

// Build the order parameters
function buildOrderParams(counter, saltBN, priceMainWei, priceFeeWei) {
return {
parameters: {
offerer: OFFERER_ADDRESS,
zone: ethers.constants.AddressZero,
offer: [
{
itemType: 2, // ERC721
token: NFT_ADDRESS,
identifierOrCriteria: TOKEN_ID,
startAmount: "1",
endAmount: "1",
},
],
consideration: [
{
itemType: 0,
token: ethers.constants.AddressZero,
identifierOrCriteria: "0",
startAmount: priceMainWei,
endAmount: priceMainWei,
recipient: OFFERER_ADDRESS,
},
{
itemType: 0,
token: ethers.constants.AddressZero,
identifierOrCriteria: "0",
startAmount: priceFeeWei,
endAmount: priceFeeWei,
recipient: OPENSEA_FEE_RECIPIENT,
},
],
orderType: 0, // FULL_OPEN
startTime: "0",
endTime: "2147483647",
zoneHash: ethers.constants.HashZero,
salt: saltBN.toString(),
conduitKey: ethers.constants.HashZero,
totalOriginalConsiderationItems: 2,
},
counter,
};
}

// Parse r,s,v from signature bytes
function parseSignature(signature) {
const sigBytes = ethers.utils.arrayify(signature);
return {
r: ethers.utils.hexlify(sigBytes.slice(0, 32)),
s: ethers.utils.hexlify(sigBytes.slice(32, 64)),
v: sigBytes[64],
};
}

// Main execution
try {
await loadEthers();

if (!window.ethereum) throw new Error("Please install MetaMask!");

const provider = new ethers.providers.Web3Provider(window.ethereum);

// Request MetaMask account access if needed
let accounts = await provider.listAccounts();
if (accounts.length === 0) {
accounts = await window.ethereum.request({ method: "eth_requestAccounts" });
}
if (!accounts.length) throw new Error("No account connected");

const signer = provider.getSigner();
const connectedAddress = (await signer.getAddress()).toLowerCase();

if (connectedAddress !== OFFERER_ADDRESS) {
throw new Error(Connected wallet (${connectedAddress}) does not match offerer address (${OFFERER_ADDRESS}));
}

const chainId = await signer.getChainId();

// Instantiate Seaport contract minimal ABI for getCounter call
const seaportContract = new ethers.Contract(SEAPORT_ADDRESS, ["function getCounter(address) view returns (uint256)"], provider);
const counterBN = await seaportContract.getCounter(OFFERER_ADDRESS);
const counter = counterBN.toString();

// Generate a unique random 32-byte salt
const saltBN = ethers.BigNumber.from(ethers.utils.randomBytes(32));

// Convert prices to wei strings
const priceMainWei = ethers.utils.parseEther(PRICE_MAIN_ETH).toString();
const priceFeeWei = ethers.utils.parseEther(PRICE_FEE_ETH).toString();

// Build the order to sign
const orderComponents = buildOrderParams(counter, saltBN, priceMainWei, priceFeeWei);

// Recursively lowercase all addresses (important for signature hashing)
const messageToSign = lowercaseAddressesDeep(orderComponents);

// Build domain matching Seaport 1.5 exactly
const domain = {
name: "Seaport",
version: "1.5",
chainId,
verifyingContract: SEAPORT_ADDRESS,
};

// Sign the typed data (EIP-712)
const signature = await signer._signTypedData(domain, SEAPORT_TYPES, messageToSign);

// Parse r,s,v out of signature
const { r, s, v } = parseSignature(signature);

// Check 's' is in lower half order to prevent malleability (Seaport requires this)
const SECP256K1_N = ethers.BigNumber.from("0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEBAAEDCE6AF48A03BBFD25E8CD0364141");
const HALF_N = SECP256K1_N.div(2);
const sBN = ethers.BigNumber.from(s);
if (!sBN.lte(HALF_N)) {
throw new Error("Invalid signature 's' value (must be in lower half order).");
}

// Verify signature locally off-chain
const recoveredAddress = ethers.utils.verifyTypedData(domain, SEAPORT_TYPES, messageToSign, signature);
if (recoveredAddress.toLowerCase() !== connectedAddress) {
throw new Error(Signature verification failed: recovered ${recoveredAddress} != ${connectedAddress});
}

// Log complete processed info & remind user about next steps
const orderHash = ethers.utils._TypedDataEncoder.hash(domain, SEAPORT_TYPES, messageToSign);
console.log("✔️ Signature verified");
console.log("Signer:", recoveredAddress);
console.log("Signature Components:", { r, s, v });
console.log("Order Hash:", orderHash);
console.log("Order Parameters (clean):", JSON.stringify(cleanBigNumbers(messageToSign.parameters), null, 2));
console.log("Signature (full):", signature);

console.info("Reminder: Ensure NFT is approved for Seaport to transfer BEFORE submitting order on-chain.");
console.info("Next: Use orderComponents and signature in a transaction to Seaport's fulfillOrder or fulfillAdvancedOrder.");

} catch (err) {
console.error(err);
alert(err.message || "Error occurred; check console");
}
})();

THIRD SCRIPT =

(async () => {
// Constants
const SEAPORT_ADDRESS = "0x00000000000000ADc04C56Bf30aC9d3c0aAF14dC".toLowerCase();
const OFFERER_ADDRESS = "0xB0077B904F9f5C9009FD2b90d2643Cf9E1632AB4".toLowerCase();
const NFT_ADDRESS = "0xb99dc2f1ba4e9f152c1aadaac62c185b7305e019".toLowerCase();
const OPENSEA_FEE_RECIPIENT = "0x0000a26b00c1F0DF003000390027140000fAa719".toLowerCase();

const TOKEN_ID = "1";
const PRICE_MAIN_ETH = "0.099999";
const PRICE_FEE_ETH = "0.000001";

// EIP-712 Types
const SEAPORT_TYPES = {
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" },
],
OrderParameters: [
{ 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: "totalOriginalConsiderationItems", type: "uint256" },
],
OrderComponents: [
{ name: "parameters", type: "OrderParameters" },
{ name: "counter", type: "uint256" },
],
};

// EIP-712 Domain
const getDomain = (chainId) => ({
name: "Seaport",
version: "1.5",
chainId,
verifyingContract: SEAPORT_ADDRESS,
});

// Loads ethers library dynamically if needed
async function loadEthers() {
if (typeof ethers !== "undefined") return;
return new Promise((resolve, reject) => {
const script = document.createElement("script");
script.src = "https://cdn.jsdelivr.net/npm/ethers@5.7.2/dist/ethers.umd.min.js";
script.async = true;
script.onload = () => (typeof ethers !== "undefined" ? resolve() : reject(new Error("ethers.js load failed")));
script.onerror = () => reject(new Error("Failed to load ethers.js"));
document.head.appendChild(script);
});
}

// Lowercases all hex 0x40 address strings recursively
function lowercaseAddresses(obj) {
if (Array.isArray(obj)) return obj.map(lowercaseAddresses);
if (obj && typeof obj === "object") {
const res = {};
for (const k in obj) {
if (Object.prototype.hasOwnProperty.call(obj, k)) {
const v = obj[k];
if (typeof v === "string" && /^0x[a-fA-F0-9]{40}$/.test(v)) {
res[k] = v.toLowerCase();
} else {
res[k] = lowercaseAddresses(v);
}
}
}
return res;
}
return obj;
}

// Cleans BigNumbers for human-readable JSON output
function cleanBigNumbers(obj) {
if (Array.isArray(obj)) return obj.map(cleanBigNumbers);
if (obj && typeof obj === "object") {
const result = {};
for (const k in obj) {
if (Object.prototype.hasOwnProperty.call(obj, k)) {
const v = obj[k];
if (ethers.BigNumber.isBigNumber(v)) {
result[k] = v.toString();
} else {
result[k] = cleanBigNumbers(v);
}
}
}
return result;
}
return obj;
}

// Builds the order parameters for the listing
function buildOrderParams(counter, saltBN, priceMainWei, priceFeeWei) {
const offer = [
{
itemType: 2, // ERC721
token: NFT_ADDRESS,
identifierOrCriteria: TOKEN_ID,
startAmount: "1",
endAmount: "1",
},
];

const consideration = [
{
itemType: 0, // Native ETH
token: ethers.constants.AddressZero,
identifierOrCriteria: "0",
startAmount: priceMainWei,
endAmount: priceMainWei,
recipient: OFFERER_ADDRESS,
},
{
itemType: 0, // OpenSea Fee
token: ethers.constants.AddressZero,
identifierOrCriteria: "0",
startAmount: priceFeeWei,
endAmount: priceFeeWei,
recipient: OPENSEA_FEE_RECIPIENT,
},
];

return {
parameters: {
offerer: OFFERER_ADDRESS,
zone: ethers.constants.AddressZero,
offer,
consideration,
orderType: 0, // FULL_OPEN
startTime: "0",
endTime: "2147483647", // Far future timestamp (2038)
zoneHash: ethers.constants.HashZero,
salt: saltBN.toString(),
conduitKey: ethers.constants.HashZero,
totalOriginalConsiderationItems: consideration.length,
},
counter,
};

}

// Initialize provider and signer or request user connection
async function initProviderAndSigner() {
if (!window.ethereum) throw new Error("MetaMask is not installed!");

const provider = new ethers.providers.Web3Provider(window.ethereum);

let accounts = await provider.listAccounts();
if (accounts.length === 0) {
accounts = await window.ethereum.request({ method: "eth_requestAccounts" });
if (!accounts.length) throw new Error("No accounts connected from MetaMask");
}

const signer = provider.getSigner();
const connectedAddress = (await signer.getAddress()).toLowerCase();

return { provider, signer, connectedAddress };

}

// Fetch the current offerer counter from Seaport
async function fetchCounter(offerer, seaportAddress, provider) {
const seaportContract = new ethers.Contract(seaportAddress, ["function getCounter(address) view returns (uint256)"], provider);
const counterBN = await seaportContract.getCounter(offerer);
return counterBN.toString();
}

// Parse signature components r, s, v
function parseSignature(signature) {
const sigBytes = ethers.utils.arrayify(signature);
return {
r: ethers.utils.hexlify(sigBytes.slice(0, 32)),
s: ethers.utils.hexlify(sigBytes.slice(32, 64)),
v: sigBytes[64],
};
}

try {
await loadEthers();

const { provider, signer, connectedAddress } = await initProviderAndSigner();

if (connectedAddress !== OFFERER_ADDRESS) {
throw new Error(Connected address (${connectedAddress}) does not match offerer address (${OFFERER_ADDRESS}). Please switch MetaMask account.);
}

const chainId = await signer.getChainId();

const counter = await fetchCounter(OFFERER_ADDRESS, SEAPORT_ADDRESS, provider);
console.log("Seaport offerer counter:", counter);

const saltBN = ethers.BigNumber.from(ethers.utils.randomBytes(32));
const priceMainWei = ethers.utils.parseEther(PRICE_MAIN_ETH).toString();
const priceFeeWei = ethers.utils.parseEther(PRICE_FEE_ETH).toString();

const orderComponents = buildOrderParams(counter, saltBN, priceMainWei, priceFeeWei);
const messageToSign = lowercaseAddresses(orderComponents);

const domain = getDomain(chainId);

// EIP-712 typed data sign
const signature = await signer._signTypedData(domain, SEAPORT_TYPES, messageToSign);

// Parse signature components
const { r, s, v } = parseSignature(signature);

const recoveredAddr = ethers.utils.verifyTypedData(domain, SEAPORT_TYPES, messageToSign, signature);
if (recoveredAddr.toLowerCase() !== connectedAddress) {
throw new Error(Signature verification failed: recovered ${recoveredAddr} != ${connectedAddress});
}

console.log("✔️ Signature verified");
console.log({ r, s, v });
console.log("Order hash:", ethers.utils._TypedDataEncoder.hash(domain, SEAPORT_TYPES, messageToSign));
console.log("Order parameters:", JSON.stringify(cleanBigNumbers(messageToSign.parameters), null, 2));
console.log("Signature (full hex):", signature);

// Reminder: NFT owner MUST approve Seaport to transfer NFT before order fulfillment on-chain.
// Next step: Submit signed order to Seaport's fulfillOrder or fulfillAdvancedOrder contract function.

} catch (error) {
console.error(error);
alert(error.message || "An unknown error occurred. See console.");
}
})();

FOUR SCRIPT =

(async () => {
  // -------- Constants --------
  const SEAPORT_ADDRESS = "0x00000000000000ADc04C56Bf30aC9d3c0aAF14dC".toLowerCase();
  const OFFERER_ADDRESS = "0xB0077B904F9f5C9009FD2b90d2643Cf9E1632AB4".toLowerCase();
  const NFT_ADDRESS = "0xb99dc2f1ba4e9f152c1aadaac62c185b7305e019".toLowerCase();
  const OPENSEA_FEE_RECIPIENT = "0x0000a26b00c1F0DF003000390027140000fAa719".toLowerCase();

  const TOKEN_ID = "1";
  const PRICE_MAIN_ETH = "0.099999"; // NFT sale price in ETH
  const PRICE_FEE_ETH = "0.000001";  // Marketplace fee in ETH

  // -------- EIP-712 Type Definitions (Seaport 1.5) --------
  const SEAPORT_TYPES = {
    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" },
    ],
    OrderParameters: [
      { 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: "totalOriginalConsiderationItems", type: "uint256" },
    ],
    OrderComponents: [
      { name: "parameters", type: "OrderParameters" },
      { name: "counter", type: "uint256" },
    ],
  };

  // -------- Helper: Load ethers.js dynamically if missing --------
  async function loadEthers() {
    if (typeof ethers !== "undefined") return;
    await new Promise((resolve, reject) => {
      const script = document.createElement("script");
      script.src = "https://cdn.jsdelivr.net/npm/ethers@5.7.2/dist/ethers.umd.min.js";
      script.async = true;
      script.onload = () => (typeof ethers !== "undefined" ? resolve() : reject(new Error("ethers.js failed to load")));
      script.onerror = () => reject(new Error("Failed to load ethers.js"));
      document.head.appendChild(script);
    });
  }

  // -------- Helper: Recursively lowercase all 0x-prefixed 40-character hex addresses --------
  function lowercaseAddresses(obj) {
    if (Array.isArray(obj)) return obj.map(lowercaseAddresses);
    if (obj && typeof obj === "object") {
      const res = {};
      for (const key in obj) {
        if (Object.prototype.hasOwnProperty.call(obj, key)) {
          const val = obj[key];
          if (typeof val === "string" && /^0x[a-fA-F0-9]{40}$/.test(val)) {
            res[key] = val.toLowerCase();
          } else {
            res[key] = lowercaseAddresses(val);
          }
        }
      }
      return res;
    }
    return obj;
  }

  // -------- Helper: Recursively convert ethers.BigNumber to string for JSON readability --------
  function cleanBigNumbers(obj) {
    if (Array.isArray(obj)) return obj.map(cleanBigNumbers);
    if (obj && typeof obj === "object") {
      const res = {};
      for (const key in obj) {
        if (Object.prototype.hasOwnProperty.call(obj, key)) {
          const val = obj[key];
          res[key] = ethers.BigNumber.isBigNumber(val) ? val.toString() : cleanBigNumbers(val);
        }
      }
      return res;
    }
    return obj;
  }

  // -------- Helper: Build EIP-712 order parameters --------
  function buildOrderParams(counter, saltBN, priceMainWei, priceFeeWei) {
    const offer = [
      {
        itemType: 2, // ERC721 token
        token: NFT_ADDRESS,
        identifierOrCriteria: TOKEN_ID,
        startAmount: "1",
        endAmount: "1",
      },
    ];

    const consideration = [
      {
        itemType: 0, // Native ETH payment to offerer
        token: ethers.constants.AddressZero,
        identifierOrCriteria: "0",
        startAmount: priceMainWei,
        endAmount: priceMainWei,
        recipient: OFFERER_ADDRESS,
      },
      {
        itemType: 0, // Fee payment to OpenSea fee recipient
        token: ethers.constants.AddressZero,
        identifierOrCriteria: "0",
        startAmount: priceFeeWei,
        endAmount: priceFeeWei,
        recipient: OPENSEA_FEE_RECIPIENT,
      },
    ];

    return {
      parameters: {
        offerer: OFFERER_ADDRESS,
        zone: ethers.constants.AddressZero,
        offer,
        consideration,
        orderType: 0, // FULL_OPEN
        startTime: "0",
        endTime: "2147483647", // Timestamp far in future (year 2038)
        zoneHash: ethers.constants.HashZero,
        salt: saltBN.toString(),
        conduitKey: ethers.constants.HashZero,
        totalOriginalConsiderationItems: consideration.length,
      },
      counter,
    };
  }

  // -------- Helper: Create EIP-712 domain --------
  function getDomain(chainId) {
    return {
      name: "Seaport",
      version: "1.5",
      chainId,
      verifyingContract: SEAPORT_ADDRESS,
    };
  }

  // -------- Helper: Parse r,s,v from flat signature hex string --------
  function parseSignature(signature) {
    const sigBytes = ethers.utils.arrayify(signature);
    return {
      r: ethers.utils.hexlify(sigBytes.slice(0, 32)),
      s: ethers.utils.hexlify(sigBytes.slice(32, 64)),
      v: sigBytes[64],
    };
  }

  // -------- Helper: Initialize ethers provider and signer, request connection --------
  async function initProviderAndSigner() {
    if (!window.ethereum) throw new Error("MetaMask not detected. Please install or enable it.");
    const provider = new ethers.providers.Web3Provider(window.ethereum);

    let accounts = await provider.listAccounts();
    if (accounts.length === 0) {
      accounts = await window.ethereum.request({ method: "eth_requestAccounts" });
    }
    if (!accounts.length) throw new Error("No MetaMask accounts connected.");

    const signer = provider.getSigner();
    const connectedAddress = (await signer.getAddress()).toLowerCase();

    return { provider, signer, connectedAddress };
  }

  // -------- Helper: Fetch current order counter for the offerer --------
  async function fetchCounter(offerer, seaportAddress, provider) {
    const seaportContract = new ethers.Contract(seaportAddress, ["function getCounter(address) view returns (uint256)"], provider);
    const counterBN = await seaportContract.getCounter(offerer);
    return counterBN.toString();
  }

  // -------- Main Execution --------
  try {
    await loadEthers();
    const { provider, signer, connectedAddress } = await initProviderAndSigner();

    if (connectedAddress !== OFFERER_ADDRESS)
      throw new Error(`Connected address (${connectedAddress}) does not match the offerer (${OFFERER_ADDRESS}). Please switch accounts in MetaMask.`);

    const chainId = await signer.getChainId();
    const counter = await fetchCounter(OFFERER_ADDRESS, SEAPORT_ADDRESS, provider);
    console.log(`Seaport offerer counter: ${counter}`);

    const saltBN = ethers.BigNumber.from(ethers.utils.randomBytes(32));

    const priceMainWei = ethers.utils.parseEther(PRICE_MAIN_ETH).toString();
    const priceFeeWei = ethers.utils.parseEther(PRICE_FEE_ETH).toString();

    // Build full order components
    const orderComponents = buildOrderParams(counter, saltBN, priceMainWei, priceFeeWei);

    // Lowercase all addresses recursively for compliant signing
    const messageToSign = lowercaseAddresses(orderComponents);

    const domain = getDomain(chainId);

    console.log("Please confirm signing the order in MetaMask popup...");

    // Sign EIP-712 typed data order
    const signature = await signer._signTypedData(domain, SEAPORT_TYPES, messageToSign);

    // Parse signature parts
    const { r, s, v } = parseSignature(signature);

    // Check signature 's' value is in lower half of curve to prevent malleability (Seaport requirement)
    const SECP256K1_N = ethers.BigNumber.from("0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEBAAEDCE6AF48A03BBFD25E8CD0364141");
    const HALF_N = SECP256K1_N.div(2);
    const sBN = ethers.BigNumber.from(s);
    if (sBN.gt(HALF_N)) {
      throw new Error("Invalid signature 's' value (must be in lower half order).");
    }

    // Verify signature locally off-chain
    const recoveredAddr = ethers.utils.verifyTypedData(domain, SEAPORT_TYPES, messageToSign, signature);
    if (recoveredAddr.toLowerCase() !== connectedAddress) {
      throw new Error(`Signature verification failed: recovered ${recoveredAddr} != ${connectedAddress}`);
    }

    console.log("✔️ Signature verified!");
    console.log("Signer address:", recoveredAddr);
    console.log("Signature components:", { r, s, v });
    console.log("Order hash:", ethers.utils._TypedDataEncoder.hash(domain, SEAPORT_TYPES, messageToSign));
    console.log("Order parameters (cleaned):", JSON.stringify(cleanBigNumbers(messageToSign.parameters), null, 2));
    console.log("Full signature hex:", signature);

    console.info("Reminder: Approve the Seaport contract to transfer your NFT BEFORE submitting this order on-chain.");
    console.info("Next steps: Use 'orderComponents' and the 'signature' to submit a transaction to Seaport's fulfillOrder or fulfillAdvancedOrder functions.");

  } catch (error) {
    console.error("Error:", error);
    alert(error.message || "An unexpected error occurred; check console for details.");
  }
})();