diff --git a/www/abi_v3.js b/www/abi_v3.js new file mode 100644 index 0000000..a97bb21 --- /dev/null +++ b/www/abi_v3.js @@ -0,0 +1 @@ +const CONTRACT_ABI = [{"inputs":[],"stateMutability":"nonpayable","type":"constructor"},{"inputs":[],"name":"ApprovalCallerNotOwnerNorApproved","type":"error"},{"inputs":[],"name":"ApprovalQueryForNonexistentToken","type":"error"},{"inputs":[],"name":"BalanceQueryForZeroAddress","type":"error"},{"inputs":[],"name":"MintERC2309QuantityExceedsLimit","type":"error"},{"inputs":[],"name":"MintToZeroAddress","type":"error"},{"inputs":[],"name":"MintZeroQuantity","type":"error"},{"inputs":[],"name":"OwnerQueryForNonexistentToken","type":"error"},{"inputs":[],"name":"OwnershipNotInitializedForExtraData","type":"error"},{"inputs":[],"name":"TransferCallerNotOwnerNorApproved","type":"error"},{"inputs":[],"name":"TransferFromIncorrectOwner","type":"error"},{"inputs":[],"name":"TransferToNonERC721ReceiverImplementer","type":"error"},{"inputs":[],"name":"TransferToZeroAddress","type":"error"},{"inputs":[],"name":"URIQueryForNonexistentToken","type":"error"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"owner","type":"address"},{"indexed":true,"internalType":"address","name":"approved","type":"address"},{"indexed":true,"internalType":"uint256","name":"tokenId","type":"uint256"}],"name":"Approval","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"owner","type":"address"},{"indexed":true,"internalType":"address","name":"operator","type":"address"},{"indexed":false,"internalType":"bool","name":"approved","type":"bool"}],"name":"ApprovalForAll","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"uint256","name":"fromTokenId","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"toTokenId","type":"uint256"},{"indexed":true,"internalType":"address","name":"from","type":"address"},{"indexed":true,"internalType":"address","name":"to","type":"address"}],"name":"ConsecutiveTransfer","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"previousOwner","type":"address"},{"indexed":true,"internalType":"address","name":"newOwner","type":"address"}],"name":"OwnershipTransferred","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"from","type":"address"},{"indexed":true,"internalType":"address","name":"to","type":"address"},{"indexed":true,"internalType":"uint256","name":"tokenId","type":"uint256"}],"name":"Transfer","type":"event"},{"inputs":[],"name":"MAX_MINT_PER_WALLET","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"MAX_SUPPLY","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"START_ID","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"quantity","type":"uint256"}],"name":"adminMint","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"to","type":"address"},{"internalType":"uint256","name":"tokenId","type":"uint256"}],"name":"approve","outputs":[],"stateMutability":"payable","type":"function"},{"inputs":[{"internalType":"address","name":"owner","type":"address"}],"name":"balanceOf","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"baseURI","outputs":[{"internalType":"string","name":"","type":"string"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"tokenId","type":"uint256"}],"name":"getApproved","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"owner","type":"address"},{"internalType":"address","name":"operator","type":"address"}],"name":"isApprovedForAll","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"masterTransfer","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"merkleRoot","outputs":[{"internalType":"bytes32","name":"","type":"bytes32"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"quantity","type":"uint256"},{"internalType":"bytes32[]","name":"_merkleProof","type":"bytes32[]"}],"name":"mint","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"mintEnabled","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"name","outputs":[{"internalType":"string","name":"","type":"string"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"owner","type":"address"}],"name":"numberMinted","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"owner","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"tokenId","type":"uint256"}],"name":"ownerOf","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"remainingSupply","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"renounceOwnership","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"from","type":"address"},{"internalType":"address","name":"to","type":"address"},{"internalType":"uint256","name":"tokenId","type":"uint256"}],"name":"safeTransferFrom","outputs":[],"stateMutability":"payable","type":"function"},{"inputs":[{"internalType":"address","name":"from","type":"address"},{"internalType":"address","name":"to","type":"address"},{"internalType":"uint256","name":"tokenId","type":"uint256"},{"internalType":"bytes","name":"_data","type":"bytes"}],"name":"safeTransferFrom","outputs":[],"stateMutability":"payable","type":"function"},{"inputs":[{"internalType":"address","name":"operator","type":"address"},{"internalType":"bool","name":"approved","type":"bool"}],"name":"setApprovalForAll","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"string","name":"_newBaseURI","type":"string"}],"name":"setBaseURI","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"bytes32","name":"_merkleRoot","type":"bytes32"}],"name":"setMerkleRoot","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"bytes4","name":"interfaceId","type":"bytes4"}],"name":"supportsInterface","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"symbol","outputs":[{"internalType":"string","name":"","type":"string"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"toggleRound","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"toggleSale","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"tokenId","type":"uint256"}],"name":"tokenURI","outputs":[{"internalType":"string","name":"","type":"string"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"totalSupply","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"from","type":"address"},{"internalType":"address","name":"to","type":"address"},{"internalType":"uint256","name":"tokenId","type":"uint256"}],"name":"transferFrom","outputs":[],"stateMutability":"payable","type":"function"},{"inputs":[{"internalType":"address","name":"newOwner","type":"address"}],"name":"transferOwnership","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"withdraw","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"wlRound","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"view","type":"function"}]; diff --git a/www/android-chrome-192x192.png b/www/android-chrome-192x192.png new file mode 100644 index 0000000..d47f17d Binary files /dev/null and b/www/android-chrome-192x192.png differ diff --git a/www/android-chrome-384x384.png b/www/android-chrome-384x384.png new file mode 100644 index 0000000..6ae48dc Binary files /dev/null and b/www/android-chrome-384x384.png differ diff --git a/www/app.css b/www/app.css new file mode 100644 index 0000000..22919b6 --- /dev/null +++ b/www/app.css @@ -0,0 +1,4 @@ +html, body { height: 100%; } +.links { margin: 10px 0px; } +.links img { height: 16px; } +p.card-text { max-height: 100px; overflow: scroll; } diff --git a/www/app_v3.js b/www/app_v3.js new file mode 100644 index 0000000..011853c --- /dev/null +++ b/www/app_v3.js @@ -0,0 +1,311 @@ +// https://docs.ethers.org/v6/getting-started/ +let provider = null; +let signer = null; +let wallet = null; +let contract = null; +let reader = new ethers.Contract(CONTRACT_ADDR, CONTRACT_ABI, new ethers.JsonRpcProvider(CHAIN_RPC)); +let rsupply = MAX_SUPPLY; +let minted_out = false; +let addr_proof = []; +let proof_cache = {}; +let raw_chain_id = null; + +// main +update_supply(); +let tweet_modal = new bootstrap.Modal($('.modal')[0]); +$('.btn-tweet').attr('href', 'https://twitter.com/intent/tweet?text=' + encodeURIComponent(TWEET_TEXT)); + +// enable tooltips +const tooltipTriggerList = document.querySelectorAll('[data-bs-toggle="tooltip"]'); +const tooltipList = [...tooltipTriggerList].map(tooltipTriggerEl => new bootstrap.Tooltip(tooltipTriggerEl)); + +// connect button +$('#connect').click(async _ => { + if (window.ethereum === undefined) { + alert('Please open by MetaMask browser'); + return; + } + + // press button effect + $('#connect').addClass('disabled'); + + // connect metamask + provider = new ethers.BrowserProvider(window.ethereum) + signer = await provider.getSigner(); + + // switch chain + let changed = await switch_chain(); + if (changed) return; + + console.log('💬', 'connecting wallet..'); + + // 1) check mint enabled + /* reduce page load + let mint_enabled = await reader.getFunction('mintEnabled').staticCall(); + if (!mint_enabled) { + show_mint_disabled(); + return; + } + */ + + // 2) check whitelist + if (PROOF_URL != null) { + let mm_addr = signer.address.toLowerCase(); + let c1 = mm_addr[2]; // 0x[_] + let proofs = proof_cache[c1]; + if (!proofs) { + let url = PROOF_URL + `${c1}.json?t=${+(new Date())}`; + try { + proofs = await $.get(url); + } + catch (err) { + console.error(err); + proofs = {}; + } + proof_cache[c1] = proofs; + } + addr_proof = proofs[mm_addr] || []; + let pass = addr_proof.length > 0; + console.log(addr_proof, proof_cache); + if (!pass) { + show_wl_only(); + return; + } + } + + // get remaining qty + let minted_qty = await reader.getFunction('numberMinted').staticCall(signer.address); + let remaining_qty = MINT_PER_WALLET - parseInt(minted_qty); + if (MAX_SUPPLY > 0) remaining_qty = Math.min(remaining_qty, rsupply); + + // update connect/disconnect buttons + hide_connect(); + show_disconnect(); + + // 1) mintable + if (remaining_qty > 0) { + $('#mint') + .text(`Mint x${remaining_qty} (Free)`) + .attr('qty', remaining_qty) + .removeClass('d-none'); + } + // 2) minted + else { + show_minted(); + } +}); +$('#disconnect').click(_ => { + $('#connect') + .removeClass('disabled') + .removeClass('d-none'); + $('#mint') + .removeClass('disabled') + .addClass('d-none'); + $('#minting').addClass('d-none'); + $('#msg').addClass('d-none'); + $('#disconnect').addClass('d-none'); + tweet_modal.hide(); +}); + +// mint button +$('#mint').click(async _ => { + $('#mint').addClass('d-none'); + $('#minting').removeClass('d-none'); + // recheck chain before mint + let [ok, msg] = await validate_chain(); + if (!ok) { + $('#mint').removeClass('d-none'); + $('#minting').addClass('d-none'); + alert(msg); + return; + } + // mint + let qty = +$('#mint').attr('qty'); + contract = new ethers.Contract(CONTRACT_ADDR, CONTRACT_ABI, signer); + mint_by_gas_rate(contract, qty, addr_proof, MINT_GAS_RATE) + .then(tx => { + console.log(tx); + return tx.wait(); + }) + .then(receipt => { // https://docs.ethers.org/v6/api/providers/#TransactionReceipt + console.log(receipt); + $('#minting').addClass('d-none'); + if (receipt.status != 1) { // 1 success, 0 revert + alert(JSON.stringify(receipt.toJSON())); + $('#mint').removeClass('d-none'); + return; + } + tweet_modal.show(); + play_party_effect(); + show_minted(); + }) + .catch(e => { + alert(e); + $('#mint').removeClass('d-none'); + $('#minting').addClass('d-none'); + }); +}); + +if (window.ethereum) { + // reconnect when switch account + window.ethereum.on('accountsChanged', function (accounts) { + if (minted_out) return; + console.log('💬', 'changed account'); + $('#disconnect').click(); + is_chain_ready(_ => $('#connect').click()); + }); + // disconnect when switch chain + window.ethereum.on('chainChanged', function (networkId) { + raw_chain_id = networkId; + if (minted_out) return; + console.log('💬', 'changed chain'); + $('#disconnect').click(); + is_chain_ready(_ => $('#connect').click()); + }); +} + +// web3 functions +function update_supply() { + $('#supply').html('Minted: ...'); + if (MAX_SUPPLY > 0) { + reader.getFunction('remainingSupply').staticCall().then(s => { + rsupply = parseInt(s); + let minted = MAX_SUPPLY - rsupply; + $('#supply').html(`Minted: ${minted}/${MAX_SUPPLY}`); + // minted out ? + minted_out = minted >= MAX_SUPPLY; + if (!minted_out) + $('#connect').removeClass('disabled'); + else + show_minted_out(); + }); + } + else { // open edition + reader.getFunction('totalSupply').staticCall().then(s => { + let minted = parseInt(s); + $('#supply').html(`Minted: ${minted}/∞`); + $('#connect').removeClass('disabled'); + }); + } +} +function is_chain_ready(callback) { + let ready = parseInt(raw_chain_id) == CHAIN_ID; + if (ready && callback) callback(); + return ready; +} +function handle_chain_exception(err) { + let msg = `Please change network to [${CHAIN_NAME}] before mint.`; + alert(`${msg}\n\n----- Error Info -----\n[${err.code}] ${err.message}`); + $('#connect').removeClass('disabled'); +} +async function validate_chain() { + // https://ethereum.stackexchange.com/questions/134610/metamask-detectethereumprovider-check-is-connected-to-specific-chain + let { chainId } = await provider.getNetwork(); + raw_chain_id = chainId; + let ok = is_chain_ready(); + let msg = ok ? null : `Please change network to [${CHAIN_NAME}] before mint.`; + return [ ok, msg ]; +} +async function switch_chain() { + // https://docs.metamask.io/wallet/reference/wallet_addethereumchain/ + let [ok, _] = await validate_chain(); + if (ok) return false; + // switch chain + try { + await window.ethereum.request({ + "method": "wallet_switchEthereumChain", + "params": [ + { + "chainId": "0x" + CHAIN_ID.toString(16), + } + ] + }); + return true; + } + // if chain not found, add chain + catch(error) { + if ([-32603, 4902].includes(error.code)) { // chain not added + try { + await window.ethereum.request({ + "method": "wallet_addEthereumChain", + "params": [ + { + "chainId": "0x" + CHAIN_ID.toString(16), + "chainName": CHAIN_NAME, + "rpcUrls": [ + CHAIN_RPC, + ], + //"iconUrls": [ + // "https://xdaichain.com/fake/example/url/xdai.svg", + // "https://xdaichain.com/fake/example/url/xdai.png" + //], + "nativeCurrency": { + "name": CHAIN_SYMBOL, + "symbol": CHAIN_SYMBOL, + "decimals": 18 + }, + "blockExplorerUrls": [ + CHAIN_EXPLORER, + ] + } + ] + }); + } + catch(error) { + handle_chain_exception(error); + } + } + else { + handle_chain_exception(error); + } + return true; + } +} +async function mint_by_gas_rate(contract, qty, proof, gas_rate=1) { + if (gas_rate == 1) { + return contract.getFunction('mint').send(qty, proof); + } + else { + let mint_fn = contract.getFunction('mint'); + let params = [ qty, proof ]; + let gas_limit = await mint_fn.estimateGas(...params); + gas_limit = Math.ceil(Number(gas_limit) * gas_rate); + return mint_fn.send(...params, { gasLimit: gas_limit }); + } +} +async function load_contract_obj() { // for console use + provider = new ethers.BrowserProvider(window.ethereum) + signer = await provider.getSigner(); + let [ok, msg] = await validate_chain(); + if (!ok) { console.warn(msg); return; } + contract = new ethers.Contract(CONTRACT_ADDR, CONTRACT_ABI, signer); + console.log('done'); +} + +// common +function short_addr(addr) { + return addr.substr(0, 5) + '...' + addr.slice(-4); +} +function play_party_effect() { + party.confetti(document.body, { + count: 120, + size: 2, + }); +} +function show_msg(msg, auto=false) { + hide_connect(); + $('#msg').text(msg).removeClass('d-none'); + if (auto) show_disconnect(); +} +function hide_connect() { + return $('#connect').addClass('d-none'); +} +function show_disconnect() { + let btn = $('#disconnect').removeClass('d-none'); + if (signer != null) btn.text(`Disconnect ${short_addr(signer.address)}`); + return btn; +} +let show_minted = _ => show_msg('Minted'); +let show_wl_only = _ => show_msg("You're not eligible", true); +let show_minted_out = _ => show_msg('Minted Out'); +let show_mint_disabled = _ => show_msg('Mint disabled'); diff --git a/www/app_v3_bundle.js b/www/app_v3_bundle.js new file mode 100644 index 0000000..9c63ba9 --- /dev/null +++ b/www/app_v3_bundle.js @@ -0,0 +1,11 @@ +function load_script(src, callback=null) { + var script = document.createElement('script'); + script.src = src; + if (callback) script.onload = callback; + document.body.appendChild(script); +} +load_script('./config.js?t=' + +(new Date()), _ => { // 1. load config (no cache) + load_script('../abi_v3.js', _ => { // 2. load abi + load_script('../app_v3.js?r=240309'); // 3. load app + }); +}); diff --git a/www/apple-touch-icon.png b/www/apple-touch-icon.png new file mode 100644 index 0000000..300af99 Binary files /dev/null and b/www/apple-touch-icon.png differ diff --git a/www/browserconfig.xml b/www/browserconfig.xml new file mode 100644 index 0000000..d416bc5 --- /dev/null +++ b/www/browserconfig.xml @@ -0,0 +1,9 @@ + + + + + + #ffffff + + + diff --git a/www/dbk/asset.png b/www/dbk/asset.png new file mode 100644 index 0000000..2db78b8 Binary files /dev/null and b/www/dbk/asset.png differ diff --git a/www/dbk/config.js b/www/dbk/config.js new file mode 100644 index 0000000..f436daf --- /dev/null +++ b/www/dbk/config.js @@ -0,0 +1,20 @@ +// chain +const CHAIN_NAME = "OP Mainnet"; +const CHAIN_RPC = "https://mainnet.optimism.io"; +const CHAIN_ID = 10; +const CHAIN_SYMBOL = "ETH"; +const CHAIN_EXPLORER = "https://optimistic.etherscan.io"; + +// contract +const MAX_SUPPLY = 0; // open edition +const MINT_PER_WALLET = 5; +const CONTRACT_ADDR = "0x128a1e35398c72A9A060f482963f594752BECcEf"; +const MINT_GAS_RATE = 1; + +// whitelist +const PROOF_URL = "https://bored-town.github.io/cdn/blobz/"; // null = public + +// twitter +const TWEET_TEXT = `Snagged my freemint Blobz NFT from @BoredTownNFT to celebrate the EIP-4844 'Dencun'! Gas fees were minimal thanks to the upgrade. + +Mint yours at https://boredtown.app/launch/blobz-op`; diff --git a/www/dbk/index.html b/www/dbk/index.html new file mode 100644 index 0000000..97a17c8 --- /dev/null +++ b/www/dbk/index.html @@ -0,0 +1,86 @@ + + + + + + + BLOBz Optimism + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ +
+ +
BLOBz Optimism
+
Minted: ...
+ + +

Calling all Optimism Stack dwellers! Dive into the deep end of the Dencun upgrade with the Blobz NFT collection!

These free-to-mint, unlimited-edition cuties aren't just adorable blobfish; they're a tribute to the power of Blobs (Binary Large Objects), the digital treasure chests hidden within databases, holding all sorts of goodies and data.

Grab your free Blobz on the OP Stack (Optimism, Base, Zora, Mode), celebrate Dencun's arrival, and represent the wonders of storing anything and everything in the vast digital ocean!

+ Connect Wallet + Mint + + + +
+ +
+ + + + + + + + + diff --git a/www/favicon-16x16.png b/www/favicon-16x16.png new file mode 100644 index 0000000..bc24447 Binary files /dev/null and b/www/favicon-16x16.png differ diff --git a/www/favicon-32x32.png b/www/favicon-32x32.png new file mode 100644 index 0000000..bdf4d97 Binary files /dev/null and b/www/favicon-32x32.png differ diff --git a/www/favicon.ico b/www/favicon.ico new file mode 100644 index 0000000..94d4540 Binary files /dev/null and b/www/favicon.ico differ diff --git a/www/index.html b/www/index.html new file mode 100644 index 0000000..45974a6 --- /dev/null +++ b/www/index.html @@ -0,0 +1 @@ +thoon diff --git a/www/mstile-150x150.png b/www/mstile-150x150.png new file mode 100644 index 0000000..575e667 Binary files /dev/null and b/www/mstile-150x150.png differ diff --git a/www/safari-pinned-tab.svg b/www/safari-pinned-tab.svg new file mode 100644 index 0000000..3dd5aa3 --- /dev/null +++ b/www/safari-pinned-tab.svg @@ -0,0 +1,797 @@ + + + + +Created by potrace 1.14, written by Peter Selinger 2001-2017 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/www/site.webmanifest b/www/site.webmanifest new file mode 100644 index 0000000..a1553eb --- /dev/null +++ b/www/site.webmanifest @@ -0,0 +1,19 @@ +{ + "name": "", + "short_name": "", + "icons": [ + { + "src": "/android-chrome-192x192.png", + "sizes": "192x192", + "type": "image/png" + }, + { + "src": "/android-chrome-384x384.png", + "sizes": "384x384", + "type": "image/png" + } + ], + "theme_color": "#ffffff", + "background_color": "#ffffff", + "display": "standalone" +}