commit 368075314cff0fcf6c9b0f1f342b6689e08cb264
parent 4d3568c4b5b9c68562fcdad8ec767abb6da9ef9d
Author: lash <dev@holbrook.no>
Date: Tue, 21 Feb 2023 20:20:49 +0000
Include fungible token issuance in qr read
Diffstat:
3 files changed, 104 insertions(+), 18 deletions(-)
diff --git a/js/qrread.html b/js/qrread.html
@@ -10,8 +10,10 @@
<script>
const MAX_MINT = 4;
+const BATCH_UNIT_VALUE = 500;
window.addEventListener('load', () => {
+ settings.batchUnitValue = BATCH_UNIT_VALUE;
for (let i = 1; i <= MAX_MINT; i<<=1) {
const opt = document.createElement('option');
opt.setAttribute('value', i);
@@ -38,7 +40,8 @@ window.addEventListener('load', () => {
});
document.getElementById('contractSubmit').addEventListener("click", (o) => {
const tokenAddress = document.getElementById("contractAddress").value;
- return contractHandler(tokenAddress);
+ const voucherAddress = document.getElementById("voucherAddress").value;
+ return contractHandler(tokenAddress, voucherAddress);
});
document.getElementById('requestSubmit').addEventListener("click", (o) => {
let tokenBatch = undefined;
@@ -111,12 +114,15 @@ window.addEventListener('load', () => {
<button id="chainSubmit">connect to network</button>
</div>
<div class="pane" id="contract">
- <label for="contractAddress">Contract address</label>
+ <label for="contractAddress">NFT Contract address</label>
<input type="text" id="contractAddress" size="42" />
+ <label for="voucherAddress">Voucher address</label>
+ <input type="text" id="voucherAddress" size="42" />
+
<button id="contractSubmit">connect to contract</button>
</div>
<div class="pane" id="product">
- <label for="requestTokenChooser">Choose token</label>
+ <label for="requestTokenChooser">Choose NFT token</label>
<div id="tokenChooser"></div>
<label for="requestAmount">Choose mint amount</label>
<select id="requestAmount"></select>
diff --git a/js/qrread.js b/js/qrread.js
@@ -16,6 +16,8 @@ var settings = {
privateKey: undefined,
tokenAddress: undefined,
tokenId: undefined,
+ fungibleTokenAddress: undefined,
+ batchUnitValue: 1,
batchNumber: undefined,
provider: undefined,
wallet: undefined,
@@ -27,6 +29,12 @@ var settings = {
minedAmount: 0,
failedAmount: 0,
recipient: undefined,
+ tokenName: undefined,
+ tokenSymbol: undefined,
+ voucherName: undefined,
+ voucherSymbol: undefined,
+ voucherDecimals: undefined,
+ voucherExpire: undefined,
};
const txBase = {
@@ -39,6 +47,16 @@ const txBase = {
chainId: 5050,
};
+const txBaseERC20 = {
+ to: undefined,
+ gasLimit: 100000,
+ gasPrice: 1,
+ data: "0xa9059cbb", // transfer(address,uint256)
+ value: 0,
+ nonce: -1,
+ chainId: 5050,
+}
+
function checkState(stateCheck, exact) {
masked = state & stateCheck;
if (exact) {
@@ -88,6 +106,37 @@ async function signAndSend() {
console.debug(txr);
nonce++;
}
+
+ const value = (settings.mintAmount * settings.batchUnitValue) * (10 ** settings.voucherDecimals);
+ setStatus('signing and sending fungible token transaction of value ' + value + '...', STATUS_BUSY);
+ let txVoucher = txBaseERC20;
+ txVoucher.to = settings.voucherAddress;
+ if (txVoucher.to.substring(0, 2) != '0x') {
+ txVoucher.to = '0x' + txVoucher.to;
+ }
+ txVoucher.nonce = nonce;
+ txVoucher.data += addr;
+ let valueHex = "0000000000000000000000000000000000000000000000000000000000000000" + value.toString(16);
+ valueHex = valueHex.slice(-64);
+ txVoucher.data += valueHex;
+ const txSigned = await settings.wallet.signTransaction(txVoucher);
+ console.log(txSigned);
+ const txr = await settings.wallet.sendTransaction(txVoucher);
+ setStatus('sent fungible transaction of value ' + value, STATUS_OK);
+ const e = new CustomEvent('tx', {
+ detail: {
+ settings: settings,
+ tx: txr,
+ mintAmount: settings.mintAmount,
+ },
+ bubbles: true,
+ cancelable: true,
+ composed: false,
+ });
+ window.dispatchEvent(e);
+ console.debug(txr);
+ nonce++;
+
setStatus('verifying transactions...', STATUS_BUSY);
}
@@ -140,11 +189,14 @@ async function chainHandler(rpc, chainId) {
return true;
}
-async function checkContractOwner(addr) {
- const contract = new ethers.Contract(addr, nftAbi, settings.provider);
+async function checkContractOwner(contractAddress, voucherAddress) {
+ const contract = new ethers.Contract(contractAddress, nftAbi, settings.provider);
+ const voucher = new ethers.Contract(voucherAddress, erc20Abi, settings.provider);
const r = await contract.isWriter(settings.wallet.address);
- if (!r) {
- setStatus('address ' + settings.wallet.address + ' does not have mint access to contract. plesae start over.', STATUS_ERROR);
+ const rr = true;
+ //const rr = await voucher.isWriter(settings.wallet.address);
+ if (!(r && rr)) {
+ setStatus('address ' + settings.wallet.address + ' does not have mint access to contracts. plesae start over.', STATUS_ERROR);
const e = new CustomEvent('uistate', {
detail: {
delta: STATE.AIEE,
@@ -158,22 +210,25 @@ async function checkContractOwner(addr) {
return;
}
setStatus('scanning contract...', STATUS_BUSY);
- setTimeout(scanContract, 0, addr);
+ setTimeout(scanContract, 0, contractAddress, voucherAddress);
}
-async function scanContract(addr) {
- const contract = new ethers.Contract(addr, nftAbi, settings.provider);
+async function scanContract(contractAddress, voucherAddress) {
+ const contract = new ethers.Contract(contractAddress, nftAbi, settings.provider);
+ const voucher = new ethers.Contract(voucherAddress, erc20Abi, settings.provider);
settings.tokenName = await contract.name();
settings.tokenSymbol = await contract.symbol();
+ settings.voucherName = await voucher.name();
+ settings.voucherSymbol = await voucher.symbol();
+ settings.voucherDecimals = await voucher.decimals();
setStatus('scanning contract for tokens...', STATUS_BUSY);
- setTimeout(scanContractTokens, 0, addr);
+ setTimeout(scanContractTokens, 0, contractAddress, voucherAddress);
}
-async function contractHandler(addr) {
+async function contractHandler(contractAddress, voucherAddress) {
checkState(STATE.WALLET_SETTINGS | STATE.NETWORK_SETTINGS, true);
setStatus('check if wallet can mint...', STATUS_BUSY);
- setTimeout(checkContractOwner, 0, addr);
-
+ setTimeout(checkContractOwner, 0, contractAddress, voucherAddress);
}
async function metaHandler(url) {
@@ -192,7 +247,7 @@ async function metaHandler(url) {
//}
}
-async function scanContractTokens(contractAddress) {
+async function scanContractTokens(contractAddress, voucherAddress) {
const contract = new ethers.Contract(contractAddress, nftAbi, settings.provider);
let i = 0;
let tokens = [];
@@ -207,6 +262,7 @@ async function scanContractTokens(contractAddress) {
}
let c = 0;
+ let z = 0;
for (let i = 0; i < tokens.length; i++) {
const tokenId = tokens[i];
const uri = await contract.tokenURI(ethers.BigNumber.from(tokenId));
@@ -222,6 +278,8 @@ async function scanContractTokens(contractAddress) {
j++;
continue;
}
+ console.debug('count cursor', (batch.count - batch.cursor), settings.batchUnitValue);
+ z += (batch.count - batch.cursor)
const e = new CustomEvent('token', {
detail: {
tokenId: tokenId,
@@ -239,8 +297,24 @@ async function scanContractTokens(contractAddress) {
j++;
}
}
-
+ setStatus('found ' + c + ' available token batches in contract', STATUS_OK);
settings.tokenAddress = contractAddress;
+ setStatus('check fungible token coverage...', STATUS_BUSY);
+ checkVoucherBalance(voucherAddress, z)
+}
+
+async function checkVoucherBalance(addr, unitCount) {
+ const voucher = new ethers.Contract(addr, erc20Abi, settings.provider);
+ const balance = await voucher.balanceOf(settings.wallet.address);
+ const target = unitCount * settings.batchUnitValue;
+ if (balance.lt(target)) {
+ console.warn('insufficient funds to cover all batch token units. need ' + target + ', have ' + balance);
+ setStatus('watch out; insufficient fungible token coverage for batch token units.', STATUS_ERROR);
+ } else {
+ setStatus('fungible token balance ' + balance, STATUS_OK);
+ }
+
+ settings.voucherAddress = addr;
state |= STATE.CONTRACT_SETTINGS;
const e = new CustomEvent('uistate', {
detail: {
@@ -252,7 +326,6 @@ async function scanContractTokens(contractAddress) {
composed: false,
});
window.dispatchEvent(e);
- setStatus('found ' + c + ' available token batches in contract', STATUS_OK);
}
async function scanTokenMetadata(tokenId) {
@@ -307,5 +380,8 @@ async function requestHandler(tokenBatch, amount) {
}
-
const nftAbi = [{"inputs":[{"internalType":"string","name":"_name","type":"string"},{"internalType":"string","name":"_symbol","type":"string"},{"internalType":"bytes32","name":"_declaration","type":"bytes32"}],"stateMutability":"nonpayable","type":"constructor"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"_minter","type":"address"},{"indexed":true,"internalType":"uint48","name":"_count","type":"uint48"},{"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":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":"address","name":"_writer","type":"address"}],"name":"addWriter","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"bytes32","name":"content","type":"bytes32"},{"internalType":"uint48","name":"count","type":"uint48"}],"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":[],"name":"declaration","outputs":[{"internalType":"bytes32","name":"","type":"bytes32"}],"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":"bytes32","name":"_data","type":"bytes32"}],"name":"getDigestHex","outputs":[{"internalType":"bytes","name":"","type":"bytes"}],"stateMutability":"pure","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":[],"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":"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":"bytes32","name":"_data","type":"bytes32"}],"name":"toURI","outputs":[{"internalType":"string","name":"","type":"string"}],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"bytes32","name":"_data","type":"bytes32"}],"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"}],"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"},{"internalType":"bool","name":"_final","type":"bool"}],"name":"transferOwnership","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"nonpayable","type":"function"}];
+
+
+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"}];
+
diff --git a/js/qrread_ui.js b/js/qrread_ui.js
@@ -54,6 +54,10 @@ window.addEventListener('uistate', (e) => {
updateSettingsView('NFT contract address', e.detail.settings.tokenAddress);
updateSettingsView('NFT name', e.detail.settings.tokenName);
updateSettingsView('NFT symbol', e.detail.settings.tokenSymbol);
+ updateSettingsView('Voucher contract address', e.detail.settings.voucherAddress);
+ updateSettingsView('Voucher name', e.detail.settings.voucherName);
+ updateSettingsView('Voucher symbol', e.detail.settings.voucherSymbol);
+ updateSettingsView('Voucher decimals', e.detail.settings.voucherDecimals);
document.getElementById("contract").style.display = "none";
document.getElementById("product").style.display = "block";
break;