qrread.js (33141B)
1 var state = 0; 2 const STATE = { 3 WALLET_SETTINGS: 1, 4 CHAIN_SETTINGS: 2, 5 CONTRACT_SETTINGS: 4, 6 MINT: 8, 7 SCAN_START: 16, 8 SCAN_RESULT: 32, 9 SCAN_STOP: 64, 10 SCAN_CONFIRM: 128, 11 SCAN_DONE: 256, 12 AIEE: 512, 13 WALLET_GENERATED: 1024, 14 WALLET_FAIL: 2048, 15 }; 16 17 var settings = { 18 keyFile: undefined, 19 privateKey: undefined, 20 tokenAddress: undefined, 21 tokenId: undefined, 22 fungibleTokenAddress: undefined, 23 batchUnitValue: 1, 24 batchNumber: undefined, 25 provider: undefined, 26 wallet: undefined, 27 chainId: undefined, 28 dataPost: undefined, 29 metaInterface: undefined, 30 metaCanWrite: false, 31 mintAmount: 1, 32 minedAmount: 0, 33 failedAmount: 0, 34 recipient: undefined, 35 tokenName: undefined, 36 tokenSymbol: undefined, 37 voucherName: undefined, 38 voucherSymbol: undefined, 39 voucherDecimals: undefined, 40 voucherExpire: undefined, 41 voucherTransferAmount: 0, 42 }; 43 44 const txBase = { 45 to: undefined, 46 gasLimit: 200000, 47 gasPrice: 1, 48 data: "0xd824ee4f", // mintFromBatchTo(address,bytes32,uint16) 49 value: 0, 50 nonce: -1, 51 chainId: 5050, 52 }; 53 54 const txBaseERC20 = { 55 to: undefined, 56 gasLimit: 100000, 57 gasPrice: 1, 58 data: "0xa9059cbb", // transfer(address,uint256) 59 value: 0, 60 nonce: -1, 61 chainId: 5050, 62 } 63 64 function checkAddress(addr) { 65 if (addr.length < 40) { 66 throw 'invalid ethereum address (too short): ' + addr; 67 } 68 if (addr.substring(0, 9) == "ethereum:") { // metamask qr 69 addr = addr.substring(9); 70 } 71 if (addr.substring(0, 2) == "0x") { 72 addr = addr.substring(2); 73 } 74 const re = new RegExp("^[0-9a-fA-F]{40}$"); 75 const m = addr.match(re); 76 if (m === null) { 77 throw 'invalid ethereum address (invalid hex or too long): ' + addr; 78 } 79 return addr; 80 } 81 82 function checkState(stateCheck, exact) { 83 masked = state & stateCheck; 84 if (exact) { 85 if (masked != stateCheck) { 86 console.error('fail exact state', state, stateCheck); 87 throw 'fail state transition check (exact)'; 88 } 89 } 90 if (masked == 0) { 91 console.error('fail contains state', state, stateCheck); 92 throw 'fail state transition check (partial)'; 93 } 94 } 95 96 let actSerial = 0; 97 98 function actLoad() { 99 const v = localStorage.getItem("craftNftQr.act.serial"); 100 actSerial = parseInt(v); 101 if (isNaN(actSerial)) { 102 actSerial = 0; 103 localStorage.setItem("craftNftQr.act.serial", actSerial); 104 } 105 console.log('actserial', actSerial); 106 } 107 108 function actRegister(address, tokenId, voucherValue) { 109 const o = { 110 recipient: address, 111 tokenId: tokenId, 112 voucherValue: voucherValue, 113 serial: actSerial, 114 dateCreated: Math.floor(Date.now() / 1000), 115 dateUpdated: Math.floor(Date.now() / 1000), 116 state: 0, 117 } 118 const j = JSON.stringify(o); 119 localStorage.setItem("craftNftQr.act." + actSerial, j); 120 const r = actSerial; 121 actSerial++; 122 localStorage.setItem("craftNftQr.act.serial", actSerial); 123 return r; 124 } 125 126 function actUpdate(serial, success) { 127 let j = localStorage.getItem("craftNftQr.act." + serial); 128 let o = JSON.parse(j); 129 if (o.state != 0) { 130 console.error("update on final act state " + state + ", serial " + serial); 131 return; 132 } 133 if (success) { 134 o.state = 1; 135 } else { 136 o.state = -1; 137 } 138 o.dateUpdated = Math.floor(Date.now() / 1000); 139 j = JSON.stringify(o); 140 localStorage.setItem("craftNftQr.act." + serial, j); 141 } 142 143 async function signAndSend() { 144 let serials = []; 145 for (let i = 0; i < settings.mintAmount; i++) { 146 const serial = actRegister(settings.recipient, settings.tokenId, settings.batchUnitValue); 147 serials.push(serial); 148 } 149 150 let addr = settings.recipient; 151 console.info('found recipient address', addr); 152 let tx = txBase; 153 tx.to = settings.tokenAddress; 154 if (tx.to.substring(0, 2) != '0x') { 155 tx.to = '0x' + tx.to; 156 } 157 158 let nonce = await settings.wallet.getTransactionCount(); 159 addr = addressPrePad + addr; 160 tx.data += addr; 161 tx.data += settings.dataPost; 162 tx.value = 0; 163 164 for (let i = 0; i < settings.mintAmount; i++) { 165 setStatus('signing and sending transaction ' + (i + 1) + ' of ' + settings.mintAmount + '...', STATUS_BUSY); 166 let txCopy = tx; 167 txCopy.nonce = nonce; 168 const txSigned = await settings.wallet.signTransaction(txCopy); 169 console.log(txSigned); 170 console.log(txCopy) 171 let txr = undefined; 172 try { 173 throw 'skip'; 174 txr = await settings.wallet.sendTransaction(txCopy); 175 } catch(e) { 176 console.error('mint NFT transaction failed, trying hack method', e); 177 const txHack = { 178 jsonrpc: "2.0", 179 id: 222, 180 method: "eth_sendRawTransaction", 181 params: [txSigned], 182 }; 183 const rsp = await fetch(settings.provider.connection.url, { 184 method: "POST", 185 headers: { 186 "Content-Type": "application/json", 187 }, 188 body: JSON.stringify(txHack), 189 }); 190 const o = await rsp.json(); 191 txr = o.result; 192 } 193 setStatus('sent transaction ' + (i + 1) + ' of ' + settings.mintAmount, STATUS_OK); 194 const e = new CustomEvent('tx', { 195 detail: { 196 settings: settings, 197 tx: txr, 198 mintAmount: settings.mintAmount, 199 serial: serials.shift(), 200 }, 201 bubbles: true, 202 cancelable: true, 203 composed: false, 204 }); 205 window.dispatchEvent(e); 206 console.debug(txr); 207 nonce++; 208 } 209 210 const value = settings.voucherTransferAmount; 211 setStatus('signing and sending fungible token transaction of value ' + (value / (10 ** settings.voucherDecimals)) + '...', STATUS_BUSY); 212 let txVoucher = txBaseERC20; 213 txVoucher.to = settings.voucherAddress; 214 if (txVoucher.to.substring(0, 2) != '0x') { 215 txVoucher.to = '0x' + txVoucher.to; 216 } 217 txVoucher.nonce = nonce; 218 txVoucher.data += addr; 219 let valueHex = "0000000000000000000000000000000000000000000000000000000000000000" + value.toString(16); 220 valueHex = valueHex.slice(-64); 221 txVoucher.data += valueHex; 222 const txSigned = await settings.wallet.signTransaction(txVoucher); 223 console.log(txSigned); 224 let txr = undefined; 225 try { 226 throw 'skip'; 227 txr = await settings.wallet.sendTransaction(txVoucher); 228 } catch(e) { 229 console.error('send voucher transaction failed, trying hack method', e); 230 const txHack = { 231 jsonrpc: "2.0", 232 id: 333, 233 method: "eth_sendRawTransaction", 234 params: [txSigned], 235 }; 236 const rsp = await fetch(settings.provider.connection.url, { 237 method: "POST", 238 headers: { 239 "Content-Type": "application/json", 240 }, 241 body: JSON.stringify(txHack), 242 }); 243 const o = await rsp.json(); 244 console.log('rsp', o); 245 txr = o.result; 246 247 } 248 setStatus('sent fungible transaction of value ' + value, STATUS_OK); 249 const e = new CustomEvent('tx', { 250 detail: { 251 settings: settings, 252 tx: txr, 253 mintAmount: settings.mintAmount, 254 }, 255 bubbles: true, 256 cancelable: true, 257 composed: false, 258 }); 259 window.dispatchEvent(e); 260 console.debug(txr); 261 nonce++; 262 263 setStatus('verifying transactions...', STATUS_BUSY); 264 } 265 266 function unlockWalletProgress(v) { 267 setStatus("unlocking wallet: " + parseInt(v * 100) + "%", STATUS_BUSY); 268 } 269 270 async function keyFileHandler(v, passphrase) { 271 setStatus('unlocking keyfile...', STATUS_BUSY); 272 console.debug('wallet', settings.wallet); 273 // make sure dom updates are executed before unlock 274 setTimeout(async () => { 275 try { 276 //settings.wallet = await ethers.Wallet.fromEncryptedJson(v, passphrase, unlockWalletProgress); 277 settings.wallet = ethers.Wallet.fromEncryptedJsonSync(v, passphrase); 278 } catch(e) { 279 state |= STATE.WALLET_SETTINGS; 280 const ev = new CustomEvent('uistate', { 281 detail: { 282 delta: STATE.WALLET_FAIL, 283 settings: settings, 284 }, 285 bubbles: true, 286 cancelable: true, 287 composed: false, 288 }); 289 window.dispatchEvent(ev); 290 setStatus('keyfile unlock fail. wrong passphrase?', STATUS_ERROR); 291 console.error(e); 292 return; 293 } 294 state |= STATE.WALLET_SETTINGS; 295 const e = new CustomEvent('uistate', { 296 detail: { 297 delta: STATE.WALLET_SETTINGS, 298 settings: settings, 299 }, 300 bubbles: true, 301 cancelable: true, 302 composed: false, 303 }); 304 window.dispatchEvent(e); 305 setStatus('keyfile unlocked', STATUS_OK); 306 }, 0); 307 return true; 308 } 309 310 async function chainHandler(rpc, chainId) { 311 setStatus('connecting to network', STATUS_BUSY); 312 setTimeout(async () => { 313 providerString = document.getElementById('chainRpcUrl').value; 314 settings.provider = new ethers.providers.JsonRpcProvider(providerString); 315 settings.wallet = settings.wallet.connect(settings.provider); 316 const network = await settings.provider.getNetwork(); 317 console.debug('connected to network', network, settings.provider); 318 if (network.chainId != chainId) { 319 throw 'chainId mismatch, requested ' + chainId + ', got ' + network.chainId; 320 } 321 // TODO: get chainid for txbase from settings directly 322 settings.chainId = parseInt(chainId); 323 txBase.chainId = settings.chainId; 324 txBaseERC20.chainId = settings.chainId; 325 326 const gasPrice = await settings.provider.getGasPrice(); 327 txBase.gasPrice = parseInt(gasPrice); 328 txBaseERC20.gasPrice = parseInt(gasPrice); 329 330 state |= STATE.CHAIN_SETTINGS; 331 const e = new CustomEvent('uistate', { 332 detail: { 333 delta: STATE.CHAIN_SETTINGS, 334 settings: settings, 335 }, 336 bubbles: true, 337 cancelable: true, 338 composed: false, 339 }); 340 window.dispatchEvent(e); 341 setStatus('connected to network', STATUS_OK); 342 }, 0); 343 return true; 344 } 345 346 async function checkContractOwner(contractAddress, voucherAddress) { 347 const contract = new ethers.Contract(contractAddress, nftAbi, settings.provider); 348 const voucher = new ethers.Contract(voucherAddress, erc20Abi, settings.provider); 349 const r = await contract.isWriter(settings.wallet.address); 350 const rr = true; 351 if (!(r && rr)) { 352 setStatus('address ' + settings.wallet.address + ' does not have mint access to contracts. plesae start over.', STATUS_ERROR); 353 const e = new CustomEvent('uistate', { 354 detail: { 355 delta: STATE.AIEE, 356 settings: settings, 357 }, 358 bubbles: true, 359 cancelable: true, 360 composed: false, 361 }); 362 window.dispatchEvent(e); 363 return; 364 } 365 setStatus('scanning contract...', STATUS_BUSY); 366 setTimeout(scanContract, 0, contractAddress, voucherAddress); 367 } 368 369 async function scanContract(contractAddress, voucherAddress) { 370 const contract = new ethers.Contract(contractAddress, nftAbi, settings.provider); 371 const voucher = new ethers.Contract(voucherAddress, erc20Abi, settings.provider); 372 settings.tokenName = await contract.name(); 373 settings.tokenSymbol = await contract.symbol(); 374 settings.voucherName = await voucher.name(); 375 settings.voucherSymbol = await voucher.symbol(); 376 const decimals = await voucher.decimals(); 377 settings.voucherDecimals = decimals.toNumber(); 378 setStatus('scanning contract for tokens...', STATUS_BUSY); 379 setTimeout(scanContractTokens, 0, contractAddress, voucherAddress); 380 } 381 382 async function contractHandler(contractAddress, voucherAddress) { 383 checkState(STATE.WALLET_SETTINGS | STATE.NETWORK_SETTINGS, true); 384 setStatus('check if wallet can mint...', STATUS_BUSY); 385 setTimeout(checkContractOwner, 0, contractAddress, voucherAddress); 386 } 387 388 async function metaHandler(url) { 389 settings.metaInterface = new Wala(url); 390 try { 391 settings.metaInterface.put('foo'); 392 settings.metaCanWrite = true; 393 } catch { 394 console.warn('cannot write to data URL', url); 395 } 396 //try { 397 // settings.metaInterface.get(''); 398 //} catch(e) { 399 // console.warn('cannot read from data URL', url, e); 400 // settings.metaInterace = undefined; 401 //} 402 } 403 404 async function scanContractTokens(contractAddress, voucherAddress) { 405 const contract = new ethers.Contract(contractAddress, nftAbi, settings.provider); 406 let i = 0; 407 let tokens = []; 408 while (true) { 409 try { 410 const tokenId = await contract.tokens(i); 411 tokens.push(tokenId); 412 } catch(e) { 413 break; 414 } 415 i++; 416 } 417 418 let c = 0; 419 let z = 0; 420 for (let i = 0; i < tokens.length; i++) { 421 const tokenId = tokens[i]; 422 const uri = await contract.tokenURI(ethers.BigNumber.from(tokenId)); 423 console.log('uri', uri); 424 let j = 0; 425 while (true) { 426 let batch = undefined; 427 try { 428 batch = await contract.token(tokenId, j); 429 } catch(e) { 430 console.error('cant get token', tokenId, j, e); 431 break; 432 } 433 if (batch.count == 0 && batch.capped) { 434 console.debug('skipping unique token', tokenId); 435 break; 436 } else if (batch.sparse) { 437 console.debug('skip sparse token', tokenId); 438 j++; 439 continue; 440 } 441 let nice = null; 442 try { 443 const tokenMeta = await fetch(uri); 444 const o = await tokenMeta.json(); 445 console.debug('token metadata retrieved', tokenId, o); 446 nice = o.name; 447 } catch(e) { 448 console.warn('metadata lookup fail', e); 449 } 450 console.debug('count cursor', (batch.count - batch.cursor), settings.batchUnitValue); 451 z += (batch.count - batch.cursor) 452 const e = new CustomEvent('token', { 453 detail: { 454 tokenId: tokenId, 455 batch: j, 456 nice: nice, 457 }, 458 bubbles: true, 459 cancelable: true, 460 composed: false, 461 }); 462 window.dispatchEvent(e); 463 c++; 464 j++; 465 } 466 } 467 if (c == 0) { 468 setStatus('no NFTs found. please fix and start over.', STATUS_ERROR); 469 throw 'missing at least one available NFT'; 470 } 471 setStatus('found ' + c + ' available token batches in contract', STATUS_OK); 472 settings.tokenAddress = contractAddress; 473 setStatus('check fungible token coverage...', STATUS_BUSY); 474 checkVoucherBalance(voucherAddress, z) 475 } 476 477 async function checkVoucherBalance(addr, unitCount) { 478 const voucher = new ethers.Contract(addr, erc20Abi, settings.provider); 479 const balance = await voucher.balanceOf(settings.wallet.address); 480 const target = unitCount * settings.batchUnitValue; 481 if (balance.lt(target)) { 482 console.warn('insufficient funds to cover all batch token units. need ' + target + ', have ' + balance); 483 setStatus('watch out; insufficient fungible token coverage for batch token units.', STATUS_ERROR); 484 } else { 485 setStatus('fungible token balance ' + (balance / (10 ** settings.voucherDecimals)) , STATUS_OK); 486 } 487 488 settings.voucherAddress = addr; 489 state |= STATE.CONTRACT_SETTINGS; 490 const e = new CustomEvent('uistate', { 491 detail: { 492 delta: STATE.CONTRACT_SETTINGS, 493 settings: settings, 494 }, 495 bubbles: true, 496 cancelable: true, 497 composed: false, 498 }); 499 window.dispatchEvent(e); 500 } 501 502 async function scanTokenMetadata(tokenId) { 503 if (settings.metaInterface === undefined) { 504 console.debug('skip metadata lookup since interface is not available'); 505 return; 506 } 507 setStatus('scan token metadata...', STATUS_BUSY); 508 if (tokenId.length < 64) { 509 setStatus('invalid token id length', STATUS_ERROR); 510 throw 'invalid token id length'; 511 } else if (tokenId.substring(0, 2) == '0x') { 512 tokenId = tokenId.substring(2); 513 } 514 515 const imgWrap = document.getElementById('scanTokenMetaImage'); 516 let img = imgWrap.lastChild; 517 if (img !== null) { 518 imgWrap.removeChild(img); 519 } 520 521 let r = undefined; 522 try { 523 r = await settings.metaInterface.get(tokenId); 524 } catch(e) { 525 setStatus('metadata lookup failed', STATUS_ERROR); 526 document.getElementById('scanTokenMetaName').innerHTML = '(unavailable)'; 527 document.getElementById('scanTokenMetaDescription').innerHTML = '(unavailable)'; 528 document.getElementById('scanTokenMetaImage').innerHTML = '(unavailable)'; 529 return; 530 } 531 532 const o = JSON.parse(r); 533 setStatus('found token metadata', STATUS_OK); 534 console.debug('metadata token', tokenId, o); 535 document.getElementById('scanTokenMetaName').innerHTML = o['name']; 536 document.getElementById('scanTokenMetaDescription').innerHTML = o['description']; 537 538 img = document.createElement('img'); 539 img.src = o['image']; 540 img.style.height = '200px'; 541 imgWrap.appendChild(img); 542 } 543 544 async function manualConfirmHandler(addr) { 545 try { 546 settings.recipient = checkAddress(addr); 547 } catch(e) { 548 console.error(e); 549 return; 550 } 551 const e = new CustomEvent('uistate', { 552 detail: { 553 delta: STATE.SCAN_STOP, 554 settings: settings, 555 }, 556 bubbles: true, 557 cancelable: true, 558 composed: false, 559 }); 560 window.dispatchEvent(e); 561 const ee = new CustomEvent('uistate', { 562 detail: { 563 delta: STATE.SCAN_CONFIRM, 564 settings: settings, 565 }, 566 bubbles: true, 567 cancelable: true, 568 composed: false, 569 }); 570 window.dispatchEvent(ee); 571 } 572 573 async function requestHandler(tokenBatch, amount) { 574 const v = tokenBatch.split('.'); 575 let batchNumberHex = "0000000000000000000000000000000000000000000000000000000000000000" + v[1].toString(16); 576 batchNumberHex = batchNumberHex.slice(-64); 577 let tokenId = v[0].substring(2); 578 await scanTokenMetadata(tokenId); 579 setStatus('scan QR code or manually enter address...', STATUS_BUSY); 580 settings.dataPost = tokenId + batchNumberHex; 581 settings.tokenId = tokenId; 582 settings.batchNumber = v[1]; 583 //settings.mintAmount = amount; 584 settings.mintAmount = 1; 585 //settings.voucherTransferAmount = (settings.mintAmount * settings.batchUnitValue) * (10 ** settings.voucherDecimals); 586 settings.voucherTransferAmount = amount * (10 ** settings.voucherDecimals); 587 const e = new CustomEvent('uistate', { 588 detail: { 589 delta: STATE.MINT, 590 settings: settings, 591 }, 592 bubbles: true, 593 cancelable: true, 594 composed: false, 595 }); 596 window.dispatchEvent(e); 597 } 598 599 function generateWalletProgress(v) { 600 setStatus('encrypting wallet for ' + settings.wallet.address + ": " + parseInt(v * 100) + "%", STATUS_BUSY); 601 } 602 603 async function generateWallet(passphrase) { 604 setStatus('generating new wallet', STATUS_BUSY); 605 const mn = await ethers.Wallet.createRandom(); 606 const wallet = new ethers.Wallet(mn.privateKey); 607 settings.wallet = wallet; 608 const keyfile = await wallet.encrypt(passphrase, {}, generateWalletProgress); 609 settings.keyFile = keyfile; 610 console.debug('settings now', settings); 611 setStatus('generated new wallet: ' + settings.wallet.address + ". <blink>REMEMBER TO COPY AND STORE!</blink>", STATUS_OK); 612 const e = new CustomEvent('uistate', { 613 detail: { 614 delta: STATE.WALLET_GENERATED, 615 settings: settings, 616 }, 617 bubbles: true, 618 cancelable: true, 619 composed: false, 620 }); 621 window.dispatchEvent(e); 622 } 623 624 625 const nftAbi = [{"inputs":[{"internalType":"string","name":"_name","type":"string"},{"internalType":"string","name":"_symbol","type":"string"}],"stateMutability":"nonpayable","type":"constructor"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"_minter","type":"address"},{"indexed":true,"internalType":"uint48","name":"_count","type":"uint48"},{"indexed":true,"internalType":"bool","name":"_capped","type":"bool"},{"indexed":false,"internalType":"bytes32","name":"_tokenId","type":"bytes32"}],"name":"Allocate","type":"event"},{"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":"address","name":"_minter","type":"address"},{"indexed":true,"internalType":"address","name":"_beneficiary","type":"address"},{"indexed":false,"internalType":"uint256","name":"_value","type":"uint256"}],"name":"Mint","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"bytes","name":"_multiHash","type":"bytes"}],"name":"Msg","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"},{"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"},{"indexed":false,"internalType":"bytes32","name":"_data","type":"bytes32"}],"name":"TransferWithData","type":"event"},{"inputs":[{"internalType":"uint8","name":"_length","type":"uint8"},{"internalType":"uint64","name":"_codecId","type":"uint64"},{"internalType":"string","name":"_uriPrefix","type":"string"}],"name":"addCodec","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"_writer","type":"address"}],"name":"addWriter","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"bytes32","name":"content","type":"bytes32"},{"internalType":"int48","name":"count","type":"int48"}],"name":"allocate","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"_owner","type":"address"}],"name":"balanceOf","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"baseURL","outputs":[{"internalType":"bytes","name":"","type":"bytes"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"_writer","type":"address"}],"name":"deleteWriter","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"_tokenId","type":"uint256"}],"name":"getApproved","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bytes32","name":"_truncatedId","type":"bytes32"}],"name":"getDigest","outputs":[{"internalType":"bytes32","name":"","type":"bytes32"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bytes","name":"_data","type":"bytes"}],"name":"getDigestHex","outputs":[{"internalType":"bytes","name":"","type":"bytes"}],"stateMutability":"pure","type":"function"},{"inputs":[],"name":"getMsg","outputs":[{"internalType":"bytes","name":"","type":"bytes"}],"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":[{"internalType":"address","name":"_writer","type":"address"}],"name":"isWriter","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"_recipient","type":"address"},{"internalType":"bytes32","name":"_content","type":"bytes32"},{"internalType":"uint16","name":"_batch","type":"uint16"},{"internalType":"uint48","name":"_index","type":"uint48"}],"name":"mintExactFromBatchTo","outputs":[{"internalType":"bytes32","name":"","type":"bytes32"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"_recipient","type":"address"},{"internalType":"bytes32","name":"_content","type":"bytes32"},{"internalType":"uint16","name":"_batch","type":"uint16"}],"name":"mintFromBatchTo","outputs":[{"internalType":"bytes32","name":"","type":"bytes32"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"_recipient","type":"address"},{"internalType":"bytes32","name":"_content","type":"bytes32"}],"name":"mintTo","outputs":[{"internalType":"bytes32","name":"","type":"bytes32"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"bytes32","name":"","type":"bytes32"}],"name":"mintedToken","outputs":[{"internalType":"bytes32","name":"","type":"bytes32"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"}],"name":"multiCodecs","outputs":[{"internalType":"uint8","name":"l","type":"uint8"},{"internalType":"uint8","name":"codecRLength","type":"uint8"},{"internalType":"uint8","name":"prefixRLength","type":"uint8"},{"internalType":"bytes16","name":"prefix","type":"bytes16"},{"internalType":"bytes8","name":"codec","type":"bytes8"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"name","outputs":[{"internalType":"string","name":"","type":"string"}],"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":[{"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":"_baseString","type":"string"}],"name":"setBaseURL","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"bytes32","name":"_content","type":"bytes32"},{"internalType":"uint16","name":"_batch","type":"uint16"},{"internalType":"uint48","name":"_cap","type":"uint48"}],"name":"setCap","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"bytes","name":"_digest","type":"bytes"}],"name":"setMsg","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"_codec","type":"uint256"}],"name":"setMsgCodec","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"bytes4","name":"interfaceID","type":"bytes4"}],"name":"supportsInterface","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"pure","type":"function"},{"inputs":[],"name":"symbol","outputs":[{"internalType":"string","name":"","type":"string"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bytes","name":"_digest","type":"bytes"}],"name":"toHash","outputs":[{"internalType":"bytes","name":"","type":"bytes"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bytes","name":"_data","type":"bytes"}],"name":"toHex","outputs":[{"internalType":"bytes","name":"","type":"bytes"}],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bytes","name":"_digest","type":"bytes"}],"name":"toURI","outputs":[{"internalType":"string","name":"","type":"string"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bytes","name":"_data","type":"bytes"}],"name":"toURL","outputs":[{"internalType":"string","name":"","type":"string"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bytes32","name":"","type":"bytes32"},{"internalType":"uint256","name":"","type":"uint256"}],"name":"token","outputs":[{"internalType":"uint48","name":"count","type":"uint48"},{"internalType":"uint48","name":"cursor","type":"uint48"},{"internalType":"bool","name":"sparse","type":"bool"},{"internalType":"bool","name":"capped","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"}],"name":"tokenByIndex","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"uint256","name":"","type":"uint256"}],"name":"tokenOfOwnerByIndex","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"_tokenId","type":"uint256"}],"name":"tokenURI","outputs":[{"internalType":"string","name":"","type":"string"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"}],"name":"tokens","outputs":[{"internalType":"bytes32","name":"","type":"bytes32"}],"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":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"nonpayable","type":"function"}]; 626 627 const erc20Abi = [{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"_owner","type":"address"},{"indexed":true,"internalType":"address","name":"_spender","type":"address"},{"indexed":false,"internalType":"uint256","name":"_value","type":"uint256"}],"name":"Approval","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"_from","type":"address"},{"indexed":true,"internalType":"address","name":"_to","type":"address"},{"indexed":false,"internalType":"uint256","name":"_value","type":"uint256"}],"name":"Transfer","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"_from","type":"address"},{"indexed":true,"internalType":"address","name":"_to","type":"address"},{"indexed":true,"internalType":"address","name":"_spender","type":"address"},{"indexed":false,"internalType":"uint256","name":"_value","type":"uint256"}],"name":"TransferFrom","type":"event"},{"inputs":[{"internalType":"address","name":"_owner","type":"address"},{"internalType":"address","name":"_spender","type":"address"}],"name":"allowance","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"_spender","type":"address"},{"internalType":"uint256","name":"_value","type":"uint256"}],"name":"approve","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"_holder","type":"address"}],"name":"balanceOf","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"decimals","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"name","outputs":[{"internalType":"string","name":"","type":"string"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"symbol","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":"_to","type":"address"},{"internalType":"uint256","name":"_value","type":"uint256"}],"name":"transfer","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"_from","type":"address"},{"internalType":"address","name":"_to","type":"address"},{"internalType":"uint256","name":"_value","type":"uint256"}],"name":"transferFrom","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"nonpayable","type":"function"}]; 628 629 const writerAbi = [{"anonymous":false,"inputs":[{"indexed":false,"internalType":"address","name":"_writer","type":"address"}],"name":"WriterAdded","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"address","name":"_writer","type":"address"}],"name":"WriterRemoved","type":"event"},{"inputs":[{"internalType":"address","name":"_writer","type":"address"}],"name":"addWriter","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"_writer","type":"address"}],"name":"deleteWriter","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"_writer","type":"address"}],"name":"isWriter","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"_idx","type":"uint256"}],"name":"writers","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"}]; 630 631 const expireAbi = [{"anonymous":false,"inputs":[{"indexed":false,"internalType":"uint256","name":"_timestamp","type":"uint256"}],"name":"Expired","type":"event"},{"inputs":[],"name":"applyExpiry","outputs":[{"internalType":"uint8","name":"","type":"uint8"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"expires","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"nonpayable","type":"function"}];