craft-nft

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

commit e122f1e40c89bf8a5887b1dd5f7f8849f44d99f1
parent 2d68d177b264a321919a0e8992b2317857999751
Author: lash <dev@holbrook.no>
Date:   Sat, 17 Dec 2022 19:45:57 +0000

Add token details view, minting view

Diffstat:
Mjs/index.html | 22+++++++++++++++++++++-
Mjs/manual_test_browser.js | 95++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++-----
Mjs/src/common.js | 10+++++++++-
Mjs/src/engine.js | 46++++++++++++++++++++++++++++++++++++++++++++++
4 files changed, 166 insertions(+), 7 deletions(-)

diff --git a/js/index.html b/js/index.html @@ -10,6 +10,9 @@ #detail { visibility: hidden; } +#mint { + visibility: hidden; +} a { text-decoration: underline; color: #0000ff; @@ -39,7 +42,7 @@ a:hover { </form> </div> <div id="detail"> - <a id="back_link" onClick="listTokens();">back</a> + <a id="back_link" onClick="uiCreateToken();">back</a> <dl> <dt>token id</dt> <dd id="token_id"></dd> @@ -47,8 +50,25 @@ a:hover { <dd id="token_name"></dd> <dt>description</dt> <dd id="token_description"></dd> + <dt class="token_batch_list">batches</dt> + <dd class="token_batch_list"> + <ol id="token_batches"></ol> + </dd> </dl> </div> + <div id="mint"> + <a id="back_link" onClick="uiCreateToken();">back</a> + <dl> + <dt>token id</dt> + <dd id="token_mint_id"></dd> + <dt>batch</dt> + <dd id="token_mint_batch"></dd> + </dl> + <form> + <input type="text" id="token_mint_recipient" /> + <button id="mint_submit">mint</button> + </form> + </div> <h2>Tokens</h2> <ul id="token_list"></ul> diff --git a/js/manual_test_browser.js b/js/manual_test_browser.js @@ -1,13 +1,36 @@ window.addEventListener('token', (e) => { const li = document.createElement('li'); const a = document.createElement('a'); - a.setAttribute('onClick', 'viewToken("' + e.detail.tokenId + '")'); + a.setAttribute('onClick', 'uiViewToken("' + e.detail.tokenId + '")'); li.id = 'token_' + e.detail.tokenId; a.textContent = e.detail.tokenId; li.appendChild(a); document.getElementById('token_list').appendChild(li); }); +window.addEventListener('tokenBatch', (e) => { + let currentTokenId = document.getElementById('token_id').innerHTML; + if (currentTokenId.substring(0, 2) == '0x') { + currentTokenId = currentTokenId.substring(2); + } + if (e.detail.tokenId != currentTokenId) { + throw 'batch event without matching token ' + tokenId + ' in view'; + } + const li = document.createElement('li'); + const span = document.createElement('span'); + li.setAttribute('id', 'token_' + e.detail.tokenId + ' _batch_' + e.detail.batch); + span.innerHTML = 'used ' + e.detail.cursor + ' of ' + e.detail.count; + li.appendChild(span); + if (window.craftnft.isMintAvailable(e.detail.tokenId, e.detail.batch)) { + const a = document.createElement('a'); + a.setAttribute('onClick', 'uiMintToken("' + e.detail.tokenId + '", ' + e.detail.batch + ')'); + a.innerHTML = 'mint'; + li.appendChild(a); + } + const batchList = document.getElementById('token_batches') + batchList.appendChild(li); +}); + async function generatePayload() { let tokenData = { @@ -47,20 +70,81 @@ async function generatePayload() { window.dispatchEvent(tokenRequestEvent); } -async function viewToken(tokenId) { +async function generateMint() { + const tokenId = document.getElementById('token_mint_id').innerHTML; + let batch = document.getElementById('token_mint_batch').innerHTML; + batch = parseInt(batch, 10); + const recipient = document.getElementById('token_mint_recipient').value; + window.craftnft.mintToken(session, tokenId, batch, recipient); + uiViewToken(tokenId); +} + +async function uiMintToken(tokenId, batch) { + document.getElementById('token_mint_id').innerHTML = tokenId; + document.getElementById('token_mint_batch').innerHTML = batch; + + document.getElementById('interactive').style.visibility = 'hidden'; + document.getElementById('detail').style.visibility = 'hidden'; + document.getElementById('mint').style.visibility = 'visible' +} + +async function uiViewTokenSingle(tokenId) { + if (!await window.craftnft.isMintAvailable(session, tokenId, 0)) { + console.debug('token ' + tokenId + ' is already minted'); + return; + } + const li = document.createElement('li'); + li.setAttribute('id', 'token_' + tokenId + '_single'); + + let a = document.createElement('a'); + a.setAttribute('onClick', 'uiMintToken("' + tokenId + '", ' + 0 + ')'); + a.innerHTML = 'mint'; + li.appendChild(a); + + const batch = document.getElementById('token_batches'); + batch.appendChild(li); + +} + +async function uiViewToken(tokenId) { const r = await session.contentGateway.get(tokenId); const tokenData = JSON.parse(r); + const batch_shit = document.getElementById('token_batches'); + while (batch_shit.lastChild) { + batch_shit.removeChild(batch_shit.lastChild); + } + document.getElementById('token_id').innerHTML = tokenId; document.getElementById('token_name').innerHTML = tokenData.name; document.getElementById('token_description').innerHTML = tokenData.description; + window.craftnft.getBatches(session, tokenId, (batch, count, cursor) => { + if (batch == -1) { + uiViewTokenSingle(tokenId); + return; + } + const e = new CustomEvent('tokenBatch', { + detail: { + tokenId: tokenId, + batch: batch, + count: count, + cursor: cursor, + }, + bubbles: true, + cancelable: true, + composed: false, + }); + window.dispatchEvent(e); + }); document.getElementById('interactive').style.visibility = 'hidden'; document.getElementById('detail').style.visibility = 'visible'; + document.getElementById('mint').style.visibility = 'hidden'; } -async function listTokens() { +async function uiCreateToken() { document.getElementById('interactive').style.visibility = 'visible'; document.getElementById('detail').style.visibility = 'hidden'; + document.getElementById('mint').style.visibility = 'hidden'; } function run(w3, generated_session) { @@ -69,11 +153,12 @@ function run(w3, generated_session) { document.getElementById('data_account').innerHTML = session.account; document.getElementById('data_contract').innerHTML = session.contractAddress; document.getElementById('panel_submit').addEventListener('click', generatePayload); + document.getElementById('mint_submit').addEventListener('click', generateMint); window.craftnft.getTokens(w3, session, (tokenId) => { if (tokenId.substring(0, 2) == '0x') { tokenId = tokenId.substring(2); } - const tokenEvent = new CustomEvent('token', { + const e = new CustomEvent('token', { detail: { tokenId: tokenId, }, @@ -81,6 +166,6 @@ function run(w3, generated_session) { cancelable: true, composed: false, }); - window.dispatchEvent(tokenEvent); + window.dispatchEvent(e); }); } diff --git a/js/src/common.js b/js/src/common.js @@ -10,6 +10,13 @@ var session = { window.addEventListener('load', async () => { const provider = window.craftnft.loadProvider(); const conn = window.craftnft.loadConn(provider); + // none of the suggestions tried so far worked (accountsChanged on provider, update on conn.provider publicconfigstore ... +// window.addEventListener('update', async (e) => { +// const oldAccount = session.account; +// const newAccount = await conn.eth.getAccounts(); +// session.account = newAccount[0]; +// console.log('account changed from ' + oldACcount + ' to ' + session.account); +// }); let config = undefined; let rs = await fetch('settings.json'); @@ -34,5 +41,6 @@ window.addEventListener('load', async () => { }); window.addEventListener('tokenRequest', async(e) => { - window.craftnft.allocateToken(session, e.detail.digest, e.detail.amount); + window.craftnft.allocateToken(session, e.detail.digest, e.detail.tokenData.amount); }); + diff --git a/js/src/engine.js b/js/src/engine.js @@ -51,10 +51,56 @@ async function allocateToken(session, tokenId, amount) { }); } +async function mintToken(session, tokenId, batch, recipient) { + const w3 = new Web3(); + const address = await w3.utils.toChecksumAddress(recipient); + console.log('address', address, recipient); + session.contract.methods.mintFromBatchTo(address, '0x' + tokenId, batch).send({ + from: session.account, + value: 0, + }); +} + +async function isMintAvailable(session, tokenId, batch) { + let token = await session.contract.methods.token('0x' + tokenId, batch).call({from: session.account}); + if (batch == 0) { + if (token.count == 0) { + return token.cursor == 0; + } + } + if (token.cursor < token.count) { + return true; + } + return false; +} + +async function getBatches(session, tokenId, callback) { + let token = await session.contract.methods.token('0x' + tokenId, 0).call({from: session.account}); + if (token.count == 0) { + callback(-1); + return; + } + + callback(0, token.count, token.cursor); + let i = 1; + while (true) { + try { + token = await session.contract.methods.token('0x' + tokenId, 1).call({from: session.account}); + } catch(e) { + break; + } + callback(i, token.count, token.cursor); + i++; + } +} + module.exports = { loadProvider: loadProvider, loadConn: loadConn, startSession: startSession, getTokens: getTokens, + getBatches: getBatches, allocateToken: allocateToken, + mintToken: mintToken, + isMintAvailable: isMintAvailable, };