manual_test_browser.js (11826B)
1 // This file contains a rought-edged example implementation of a user interface for reading and writing Craft NFT tokens. 2 3 let tokenListCache = []; 4 5 /** 6 * Emitted when a new token has been found. 7 */ 8 window.addEventListener('token', (e) => { 9 for (let i = tokenListCache.length - 1; i > -1; i--) { 10 if (tokenListCache[i] == e.detail.tokenId) { 11 return; 12 } 13 } 14 tokenListCache.push(e.detail.tokenId); 15 const li = document.createElement('li'); 16 const a = document.createElement('a'); 17 a.setAttribute('onClick', 'uiViewToken("' + e.detail.tokenId + '")'); 18 li.id = 'token_' + e.detail.tokenId; 19 a.textContent = e.detail.tokenId; 20 li.appendChild(a); 21 document.getElementById('token_list').appendChild(li); 22 }); 23 24 25 /** 26 * Emitted when a new batch allocation of a token has been found. 27 */ 28 window.addEventListener('tokenBatch', async (e) => { 29 let currentTokenId = document.getElementById('token_id').innerHTML; 30 if (currentTokenId.substring(0, 2) == '0x') { 31 currentTokenId = currentTokenId.substring(2); 32 } 33 if (e.detail.tokenId != currentTokenId) { 34 throw 'batch event without matching token ' + tokenId + ' in view'; 35 } 36 37 const li = document.createElement('li'); 38 const span = document.createElement('span'); 39 li.setAttribute('id', 'token_' + e.detail.tokenId + ' _batch_' + e.detail.batch); 40 if (parseInt(e.detail.cursor, 10) >= parseInt(e.detail.count, 10)) { 41 span.innerHTML = 'all minted (' + e.detail.cursor + ' of ' + e.detail.count + ')'; 42 } else { 43 span.innerHTML = 'minted ' + e.detail.cursor + ' of ' + e.detail.count + ' '; 44 45 } 46 li.appendChild(span); 47 48 const mintedTokenData = await window.craftnft.getMintedToken(session, e.detail.tokenId, e.detail.batch); 49 console.debug('retrieved minted token data', mintedTokenData); 50 51 if (mintedTokenData.mintable) { 52 const a = document.createElement('a'); 53 a.setAttribute('onClick', 'uiMintToken("' + e.detail.tokenId + '", ' + e.detail.batch + ')'); 54 a.innerHTML = 'mint'; 55 li.appendChild(a); 56 } 57 const batchList = document.getElementById('token_batches') 58 batchList.appendChild(li); 59 }); 60 61 62 /** 63 * Request creation of a new token allocation. 64 * 65 * Parameters for the allocation are read directly from the DOM. 66 * 67 * Interpreted parameters are emitted with the tokenRequest event. 68 */ 69 async function generateAllocation() { 70 let tokenData_ERC721 = { 71 name: undefined, 72 description: undefined, 73 image: undefined, 74 }; 75 let tokenData_openSea = { 76 attributes: [], 77 }; 78 let tokenData_native = { 79 amount: 0, 80 attachments: [], 81 }; 82 83 let amount = document.getElementById('panel_amount').value; 84 if (amount === '') { 85 amount = '0'; 86 } 87 tokenData_native.amount = parseInt(amount, 10); 88 if (isNaN(tokenData_native.amount)) { 89 throw 'amount must be numeric'; 90 } 91 tokenData_ERC721.name = document.getElementById('panel_title').value; 92 tokenData_ERC721.description = document.getElementById('panel_description').value; 93 94 const attachments = document.getElementById('panel_attachment_list').children; 95 for (let i = 0; i < attachments.length; i++) { 96 if (tokenData_ERC721.image === undefined) { 97 tokenData_ERC721.image = attachments[i].innerHTML; 98 } 99 tokenData_native.attachments.push(attachments[i].innerHTML); 100 } 101 102 tokenData = Object.assign(tokenData_ERC721, tokenData_native, tokenData_openSea); 103 const s = JSON.stringify(tokenData); 104 105 const sha_raw = new jsSHA("SHA-256", "TEXT", { encoding: "UTF8" }); 106 sha_raw.update(s); 107 const digest = sha_raw.getHash("HEX"); 108 109 if (session.contentGateway !== undefined) { 110 try { 111 let r = await session.contentGateway.put(s); 112 if (r != digest) { 113 throw 'digest mismatch (' + r + ' != ' + digest + ')'; 114 } 115 } catch(e) { 116 console.error('failed to upload token data:', e); 117 } 118 } 119 120 const tokenRequestEvent = new CustomEvent('tokenRequest', { 121 detail: { 122 digest: digest, 123 tokenData: tokenData, 124 }, 125 bubbles: true, 126 cancelable: true, 127 composed: false, 128 }); 129 window.dispatchEvent(tokenRequestEvent); 130 } 131 132 133 /** 134 * Request minting of a new token from an existing allocation. 135 * 136 * Parameters for the minting are read directly from the DOM. 137 * 138 * Interpreted parameters are emitted with the tokenMint event. 139 */ 140 async function generateMint() { 141 const tokenId = document.getElementById('token_mint_id').innerHTML; 142 143 let batch = document.getElementById('token_mint_batch').innerHTML; 144 batch = parseInt(batch, 10); 145 146 const recipient = document.getElementById('token_mint_recipient').value; 147 148 let index = undefined; 149 if (document.getElementById('token_mint_typ').value === 'batched') { 150 index = parseInt(document.getElementById('token_mint_index').value, 10); 151 } 152 153 const tokenRequestEvent = new CustomEvent('tokenMint', { 154 detail: { 155 recipient: recipient, 156 digest: tokenId, 157 batch: batch, 158 index: index, 159 }, 160 bubbles: true, 161 cancelable: true, 162 composed: false, 163 }); 164 window.dispatchEvent(tokenRequestEvent); 165 uiViewToken(tokenId); 166 } 167 168 169 /** 170 * Render the mint token view. 171 */ 172 async function uiMintToken(tokenId, batch) { 173 document.getElementById('token_mint_id').innerHTML = tokenId; 174 document.getElementById('token_mint_batch').innerHTML = batch; 175 176 document.getElementById('interactive').style.display = 'none'; 177 document.getElementById('detail').style.display = 'none'; 178 document.getElementById('mint').style.display = 'block'; 179 180 } 181 182 183 /** 184 * Render the Unique Token part of the allocated token view. 185 */ 186 async function uiViewTokenSingle(tokenId) { 187 let li = document.createElement('li'); 188 li.setAttribute('id', 'token_' + tokenId + '_single'); 189 190 const mintedTokenData = await window.craftnft.getMintedToken(session, tokenId, 0); 191 console.debug('retrieved minted single token data', mintedTokenData); 192 193 if (!mintedTokenData.mintable) { 194 console.debug('token ' + tokenId + ' is already minted'); 195 li.innerHTML = '(already minted)'; 196 } else { 197 let a = document.createElement('a'); 198 a.setAttribute('onClick', 'uiMintToken("' + tokenId + '", ' + 0 + ')'); 199 a.innerHTML = 'mint'; 200 li.appendChild(a); 201 } 202 203 const batch = document.getElementById('token_batches'); 204 batch.appendChild(li); 205 206 } 207 208 209 /** 210 * Render the allocated token view. 211 */ 212 async function uiViewToken(tokenId) { 213 let tokenData = { 214 name: '(unavailable)', 215 description: '(unavailable)', 216 }; 217 if (session.contentGateway !== undefined) { 218 try { 219 const r = await session.contentGateway.get(tokenId); 220 tokenData = JSON.parse(r); 221 } catch(e) { 222 tokenData.name = '(failed)'; 223 tokenData.description = '(failed)'; 224 console.error('could not fetch token content:', e); 225 } 226 } 227 228 const image_display = document.getElementById('token_image'); 229 if (image_display.lastChild !== null) { 230 image_display.removeChild(image_display.lastChild); 231 } 232 const batch_shit = document.getElementById('token_batches'); 233 while (batch_shit.lastChild) { 234 batch_shit.removeChild(batch_shit.lastChild); 235 } 236 237 const tokenChainData = await window.craftnft.getTokenChainData(session, tokenId); 238 console.debug('retrieved token chain data', tokenChainData); 239 240 console.debug('getting image hash', tokenData.image); 241 if (tokenData.image !== undefined) { 242 image_hash = tokenData.image.substring(7); 243 session.contentGateway.get(image_hash, true).then((v) => { 244 let img = document.createElement('img'); 245 console.debug('img img', img); 246 img.src = URL.createObjectURL(v); 247 document.getElementById('token_image').appendChild(img); 248 }); 249 } 250 document.getElementById('token_id').innerHTML = tokenId; 251 document.getElementById('token_name').innerHTML = tokenData.name; 252 document.getElementById('token_description').innerHTML = tokenData.description; 253 document.getElementById('token_cap').innerHTML = tokenChainData.issue.cap; 254 if (tokenChainData.issue.cap == 0) { 255 document.getElementById('token_mint_typ').value = 'unique'; 256 document.getElementById('token_mint_index').value = ''; 257 document.getElementById('mint_index').style.visibility = 'hidden'; 258 } else { 259 document.getElementById('token_mint_typ').value = 'batched'; 260 document.getElementById('mint_index').style.visibility = 'inherit'; 261 } 262 263 window.craftnft.getBatches(session, tokenId, (batch, count, cursor) => { 264 if (batch == -1) { 265 uiViewTokenSingle(tokenId); 266 return; 267 } 268 const e = new CustomEvent('tokenBatch', { 269 detail: { 270 tokenId: tokenId, 271 batch: batch, 272 count: count, 273 cursor: cursor, 274 }, 275 bubbles: true, 276 cancelable: true, 277 composed: false, 278 }); 279 window.dispatchEvent(e); 280 }); 281 document.getElementById('interactive').style.display = 'none'; 282 document.getElementById('detail').style.display = 'block'; 283 document.getElementById('mint').style.display = 'none'; 284 285 } 286 287 288 /** 289 * Render the create token allocation view. 290 */ 291 async function uiCreateToken() { 292 document.getElementById('interactive').style.display ='block'; 293 document.getElementById('detail').style.display = 'none'; 294 document.getElementById('mint').style.display = 'none'; 295 296 } 297 298 299 /** 300 * UI entry point. 301 */ 302 async function run(w3, generated_session) { 303 session = generated_session; 304 console.debug('running with session', session); 305 306 if (session.contentGatewayUrl !== undefined) { 307 session.contentGateway = new Wala('http://localhost:8001'); 308 } 309 const account = document.getElementById('data_account'); 310 let s = document.createElement('span'); 311 s.innerHTML = session.account; 312 account.append(s); 313 314 let f = document.createElement('font'); 315 if (await window.craftnft.isOwner(session, session.account)) { 316 f.setAttribute('color', 'green'); 317 f.innerHTML += ' (contract owner)'; 318 account.append(s); 319 } else { 320 f.setAttribute('color', 'red'); 321 f.innerHTML = ' (not contract owner!)'; 322 } 323 account.append(f); 324 325 document.getElementById('data_contract').innerHTML = session.contractAddress; 326 document.getElementById('data_name').innerHTML = session.name; 327 document.getElementById('data_symbol').innerHTML = session.symbol; 328 document.getElementById('data_supply').innerHTML = session.supply; 329 document.getElementById('panel_submit').addEventListener('click', () => { 330 generateAllocation(); 331 return false; 332 }); 333 document.getElementById('mint_submit').addEventListener('click', () => { 334 generateMint(); 335 return false; 336 }); 337 338 // if (session.contentGateway !== undefined) { 339 // declarationUrl = session.contentGateway.url(session.declarationHash); 340 // let a = document.createElement('a') 341 // a.setAttribute('href', declarationUrl); 342 // a.innerHTML = declarationUrl; 343 // document.getElementById('data_declaration').appendChild(a); 344 // } else { 345 // document.getElementById('data_declaration').innerHTML = 'sha256:' + session.declarationHash; 346 // } 347 348 window.craftnft.getTokens(w3, session, (tokenId) => { 349 if (tokenId.substring(0, 2) == '0x') { 350 tokenId = tokenId.substring(2); 351 } 352 const e = new CustomEvent('token', { 353 detail: { 354 tokenId: tokenId, 355 }, 356 bubbles: true, 357 cancelable: true, 358 composed: false, 359 }); 360 window.dispatchEvent(e); 361 }); 362 } 363 364 365 async function renderFile(contents, filename) { 366 //const sha_raw = new jsSHA("SHA-256", "BINARY", { encoding: "UTF8" }); 367 const sha_raw = new jsSHA("SHA-256", "ARRAYBUFFER"); 368 sha_raw.update(contents); 369 const digest = sha_raw.getHash("HEX"); 370 let li = document.createElement('li'); 371 li.innerHTML = 'sha256:' + digest; 372 document.getElementById('panel_attachment_list').appendChild(li); 373 return digest; 374 } 375 376 377 async function uploadFile(contents, filename) { 378 if (session.contentGateway !== undefined) { 379 try { 380 let r = await session.contentGateway.put(contents, filename); 381 //if (r != digest) { 382 // throw 'digest mismatch (' + r + ' != ' + digest + ')'; 383 //} 384 } catch(e) { 385 console.error('failed to upload token attachment data:', e); 386 } 387 } 388 } 389 390 391 async function fileChange(e) { 392 let fileButton = document.getElementById("panel_thumb") 393 let file = fileButton.files[0]; 394 if (file) { 395 let f = new FileReader(); 396 f.onloadend = async (r) => { 397 renderFile(r.target.result, file.name); 398 uploadFile(r.target.result, file.name); 399 }; 400 f.readAsArrayBuffer(file); 401 } 402 }