craft-nft

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

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 }