Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Update web3js #89

Open
wants to merge 2 commits into
base: main
Choose a base branch
from
Open
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
242 changes: 102 additions & 140 deletions src/mint/web3.js
Original file line number Diff line number Diff line change
@@ -1,182 +1,144 @@
import { getWalletAddressOrConnect, web3 } from "../wallet.js";
import { formatValue} from "../utils.js";
import { NFTContract } from "../contract.js"
import { formatValue } from "../utils.js";
import { NFTContract } from "../contract.js";
import { buildTx } from "../tx";
import { readOnlyWeb3 } from "../web3";

const findMethodByName = (methodName) =>
Object.keys(NFTContract.methods)
.find(key => key.toLowerCase() === methodName.toLowerCase())
.find(key => key.toLowerCase() === methodName.toLowerCase());

const getMethodWithCustomName = (methodName) => {
const method = window.DEFAULTS?.contractMethods ? window.DEFAULTS?.contractMethods[methodName] : undefined
const method = window.DEFAULTS?.contractMethods ? window.DEFAULTS?.contractMethods[methodName] : undefined;
if (method) {
console.log(`Using custom ${methodName} method name: `, method)
console.log(`Using custom ${methodName} method name: `, method);
if (NFTContract.methods[method]) {
return NFTContract.methods[method]
return NFTContract.methods[method];
} else {
alert(`Custom ${methodName} name isn't present in the ABI, using default name`)
console.log(`Custom ${methodName} name isn't present in the ABI, using default name`)
alert(`Custom ${methodName} name isn't present in the ABI, using default name`);
console.log(`Custom ${methodName} name isn't present in the ABI, using default name`);
}
}
return undefined
}
return undefined;
};

const getMintTx = ({ numberOfTokens }) => {
const customMintMethod = getMethodWithCustomName('mint')
if (customMintMethod)
return customMintMethod(numberOfTokens)
const customMintMethod = getMethodWithCustomName('mint');
if (customMintMethod) {
return customMintMethod(numberOfTokens);
}

console.log("Using hardcoded mint method detection")
const methodNameVariants = ['mint', 'publicMint', 'mintNFTs', 'mintPublic', 'mintSale']
const name = methodNameVariants.find(n => findMethodByName(n) !== undefined)
console.log("Using hardcoded mint method detection");
const methodNameVariants = ['mint', 'publicMint', 'mintNFTs', 'mintPublic', 'mintSale'];
const name = methodNameVariants.find(n => findMethodByName(n) !== undefined);
if (!name) {
alert("Buildship widget doesn't know how to mint from your contract. Contact https://buildship.xyz in Discord to resolve this.")
return undefined
alert("Buildship widget doesn't know how to mint from your contract. Contact https://buildship.xyz in Discord to resolve this.");
return undefined;
}
return NFTContract.methods[findMethodByName(name)](numberOfTokens);
}
};

const getMintPriceConstant = () => {
// for contracts without exported price variable or method
const defaultPrice = window.DEFAULTS?.publicMint?.price
const defaultPrice = window.DEFAULTS?.publicMint?.price;
if (defaultPrice) {
const priceNumber = typeof defaultPrice === "string" ? Number(defaultPrice) : defaultPrice
const priceNumber = typeof defaultPrice === "string" ? Number(defaultPrice) : defaultPrice;
if (isNaN(priceNumber)) {
alert("Wrong publicMintPrice format, should be a number in ETH (or native token)")
return undefined
alert("Wrong publicMintPrice format, should be a number in ETH (or native token)");
return undefined;
}
console.warn("Using DEFAULTS.publicMint.price as price not found in the smart-contract")
return (priceNumber * 1e18).toString()
console.warn("Using DEFAULTS.publicMint.price as price not found in the smart-contract");
return (priceNumber * 1e18).toString();
}
return undefined
}
return undefined;
};

const getMintPriceFromConfig = async () => {
const config = await NFTContract.methods.config().call();
return config.mintPrice;
};

export const getMintPrice = async () => {
const customMintPriceMethod = getMethodWithCustomName('price')
const customMintPriceMethod = getMethodWithCustomName('price');
if (customMintPriceMethod) {
return customMintPriceMethod().call()
return customMintPriceMethod().call();
}

const mintPriceConstant = getMintPriceConstant()
const mintPriceConstant = getMintPriceConstant();
if (mintPriceConstant !== undefined) {
console.log("Using constant mint price specified in DEFAULTS")
return mintPriceConstant
console.log("Using constant mint price specified in DEFAULTS");
return mintPriceConstant;
}

const matches = Object.keys(NFTContract.methods).filter(key =>
!key.includes("()") && (key.toLowerCase().includes('price') || key.toLowerCase().includes('cost'))
)
switch (matches.length) {
// Use auto-detection only when sure
// Otherwise this code might accidentally use presale price instead of public minting price
case 1:
console.log("Using price method auto-detection")
return NFTContract.methods[matches[0]]().call()
default:
console.log("Using hardcoded price detection")
const methodNameVariants = ['price', 'cost', 'public_sale_price', 'getPrice', 'salePrice']
const name = methodNameVariants.find(n => findMethodByName(n) !== undefined)
if (!name) {
alert("Buildship widget doesn't know how to fetch price from your contract. Contact https://buildship.xyz in Discord to resolve this.")
return undefined
}
return NFTContract.methods[findMethodByName(name)]().call();
// Use the custom function to get the mint price from the contract's config
const mintPriceFromConfig = await getMintPriceFromConfig();
if (mintPriceFromConfig) {
console.log("Using custom mint price from config");
return mintPriceFromConfig;
}
}

export const getMintedNumber = async () => {
if (!NFTContract)
return undefined

const customTotalSupplyMethod = getMethodWithCustomName('totalSupply')
if (customTotalSupplyMethod)
return await customTotalSupplyMethod().call()

if (NFTContract.methods.totalSupply)
return await NFTContract.methods.totalSupply().call()
// temporary solution, works only for buildship.xyz contracts
// totalSupply was removed to save gas when minting
// but number minted still accessible in the contract as a private variable
// TODO: remove this in NFTFactory v1.1
const minted = await readOnlyWeb3.eth.getStorageAt(
NFTContract._address,
'0x00000000000000000000000000000000000000000000000000000000000000fb'
)
return readOnlyWeb3.utils.hexToNumber(minted)
const matches = Object.keys(NFTContract.methods).filter((key) =>
!key.includes("()") &&
(key.toLowerCase().includes("price") || key.toLowerCase().includes("cost"))
);
switch (matches.length) { case 0:
console.warn("Couldn't find the mint price function. Please contact https://buildship.xyz in Discord to resolve this.");
alert("Couldn't find the mint price function. Please contact https://buildship.xyz in Discord to resolve this.");
return undefined;
case 1:
console.log("Using the first price function found in the ABI");
return NFTContract.methods[matches[0]]().call();
default:
console.warn("Multiple price functions found. Using the first one. Contact https://buildship.xyz in Discord if this doesn't work.");
return NFTContract.methods[matches[0]]().call();
}
};

export const getMaxSupply = async () => {
if (!NFTContract)
return undefined

const customMaxSupplyMethod = getMethodWithCustomName('maxSupply')
if (customMaxSupplyMethod)
return await customMaxSupplyMethod().call()

if (NFTContract.methods.maxSupply)
return await NFTContract.methods.maxSupply().call()
if (NFTContract.methods.MAX_SUPPLY)
return await NFTContract.methods.MAX_SUPPLY().call()
alert("Widget doesn't know how to fetch maxSupply from your contract. Contact https://buildship.xyz to resolve this.")
return undefined
const updateMintPrice = async () => {
const price = await getMintPrice();
if (price) {
document.getElementById("mint-price").innerText = formatValue(price);
}

export const getDefaultMaxTokensPerMint = () => {
const defaultMaxPerMintConfig = window.DEFAULTS?.publicMint?.maxPerMint || window.MAX_PER_MINT
if (!defaultMaxPerMintConfig || isNaN(Number(defaultMaxPerMintConfig))) {
console.error("Can't read maxPerMint from your contract & config, using default value: 10")
return 10
}
return Number(defaultMaxPerMintConfig)
};

const updateTotalSupply = async () => {
const totalSupply = await NFTContract.methods.totalSupply().call();
document.getElementById("total-supply").innerText = totalSupply;
};

const mint = async (numberOfTokens) => {
const walletAddress = await getWalletAddressOrConnect();
if (!walletAddress) {
console.error("No wallet connected");
return;
}

export const getMaxTokensPerMint = async () => {
const customMaxPerMintMethod = getMethodWithCustomName('maxPerMint')
if (customMaxPerMintMethod)
return await customMaxPerMintMethod().call().then(Number)

if (NFTContract?.methods?.maxPerMint) {
return Number(await NFTContract.methods.maxPerMint().call())
}
if (NFTContract?.methods?.maxMintAmount) {
return Number(await NFTContract.methods.maxMintAmount().call())
}
if (NFTContract?.methods?.MAX_TOKENS_PER_MINT) {
return Number(await NFTContract.methods.MAX_TOKENS_PER_MINT().call())
}
return getDefaultMaxTokensPerMint()
const mintTx = getMintTx({ numberOfTokens });
if (!mintTx) {
console.error("No mint method found");
return;
}

export const mint = async (nTokens) => {
const wallet = await getWalletAddressOrConnect(true);
if (!wallet) {
return { tx: undefined }
}
const numberOfTokens = nTokens ?? 1;
const mintPrice = await getMintPrice();
if (mintPrice === undefined)
return { tx: undefined }

const txParams = {
from: wallet,
value: formatValue(Number(mintPrice) * numberOfTokens),
}
const mintTx = await getMintTx({ numberOfTokens })
if (!mintTx) {
return { tx: undefined }
}
const txBuilder = await buildTx(
mintTx,
txParams,
// TODO: use different limits for ERC721A / ERC721
window.DEFAULTS?.publicMint?.defaultGasLimit ?? (100000 * numberOfTokens),
window.DEFAULTS?.publicMint?.gasLimitSlippage ?? 5000
)
if (!txBuilder) {
return { tx: undefined }
}
const tx = mintTx.send(txBuilder)
// https://github.com/ChainSafe/web3.js/issues/1547
return Promise.resolve({ tx })
const price = await getMintPrice();
if (!price) {
console.error("No mint price found");
return;
}
const txOptions = {
value: BigInt(price) * BigInt(numberOfTokens),
};
const tx = await buildTx(mintTx, txOptions);
await tx.send();
};

document.getElementById("mint-button").addEventListener("click", async () => {
const numberOfTokens = parseInt(document.getElementById("number-of-tokens").value);
if (isNaN(numberOfTokens) || numberOfTokens <= 0) {
alert("Please enter a valid number of tokens to mint");
return;
}
await mint(numberOfTokens);
});

updateMintPrice();
updateTotalSupply();
setInterval(updateMintPrice, 30 * 1000); // Update mint price every 30 seconds
setInterval(updateTotalSupply, 30 * 1000); // Update total supply every 30 seconds