craft-nft

A standalone NFT implementation for real-world arts and crafts assets
Info | Log | Files | Refs | README

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"}];