Skip to content

Commit

Permalink
Official sales (part 1) (#12)
Browse files Browse the repository at this point in the history
* 404 fix, display official sales, list help pages

* include official sales in header, minor improvements

plus very minor outlining for official sales

* send instructions, work on official sales, crypto
  • Loading branch information
stjet authored Mar 18, 2024
1 parent 921e63f commit ea79ec4
Show file tree
Hide file tree
Showing 19 changed files with 704 additions and 29 deletions.
21 changes: 9 additions & 12 deletions src/lib/components/Header.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -29,32 +29,29 @@
</li>
<li class="tooltip tooltip-bottom" data-tip="Market" aria-current={$page.url.pathname === '/market' ? 'page' : undefined}>
<a class="btn btn-ghost btn-circle" href="/market">
<svg xmlns="http://www.w3.org/2000/svg" class="h-5 w-5" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" d="M12 21v-8.25M15.75 21v-8.25M8.25 21v-8.25M3 9l9-6 9 6m-1.5 12V10.332A48.36 48.36 0 0012 9.75c-2.551 0-5.056.2-7.5.582V21M3 21h18M12 6.75h.008v.008H12V6.75z" />
</svg>
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" class="w-5 h-5">
<path stroke-linecap="round" stroke-linejoin="round" d="M13.5 21v-7.5a.75.75 0 0 1 .75-.75h3a.75.75 0 0 1 .75.75V21m-4.5 0H2.36m11.14 0H18m0 0h3.64m-1.39 0V9.349M3.75 21V9.349m0 0a3.001 3.001 0 0 0 3.75-.615A2.993 2.993 0 0 0 9.75 9.75c.896 0 1.7-.393 2.25-1.016a2.993 2.993 0 0 0 2.25 1.016c.896 0 1.7-.393 2.25-1.015a3.001 3.001 0 0 0 3.75.614m-16.5 0a3.004 3.004 0 0 1-.621-4.72l1.189-1.19A1.5 1.5 0 0 1 5.378 3h13.243a1.5 1.5 0 0 1 1.06.44l1.19 1.189a3 3 0 0 1-.621 4.72M6.75 18h3.75a.75.75 0 0 0 .75-.75V13.5a.75.75 0 0 0-.75-.75H6.75a.75.75 0 0 0-.75.75v3.75c0 .414.336.75.75.75Z" />
</svg>

</a>
</li>
<li class="tooltip tooltip-bottom" data-tip="Trending" aria-current={$page.url.pathname === '/trending' ? 'page' : undefined}>
<li class="tooltip tooltip-bottom" data-tip="Minters" aria-current={$page.url.pathname === '/trending' ? 'page' : undefined}>
<div class="dropdown dropdown-end">
<label tabindex="0" class="btn btn-ghost btn-circle">
<div class="indicator">
<svg xmlns="http://www.w3.org/2000/svg" class="h-5 w-5" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" d="M15.362 5.214A8.252 8.252 0 0112 21 8.25 8.25 0 016.038 7.048 8.287 8.287 0 009 9.6a8.983 8.983 0 013.361-6.867 8.21 8.21 0 003 2.48z" />
<path stroke-linecap="round" stroke-linejoin="round" d="M12 18a3.75 3.75 0 00.495-7.467 5.99 5.99 0 00-1.925 3.546 5.974 5.974 0 01-2.133-1A3.75 3.75 0 0012 18z" />
<path stroke-linecap="round" stroke-linejoin="round" d="M12 21v-8.25M15.75 21v-8.25M8.25 21v-8.25M3 9l9-6 9 6m-1.5 12V10.332A48.36 48.36 0 0012 9.75c-2.551 0-5.056.2-7.5.582V21M3 21h18M12 6.75h.008v.008H12V6.75z" />
</svg>

<span class="badge badge-sm indicator-item">0</span>
<!--<span class="badge badge-sm indicator-item">0</span>-->
</div>
</label>
<div tabindex="0" class="mt-3 z-[1] card card-compact dropdown-content w-52 bg-base-100 shadow">
<div class="card-body">
<span class="font-bold text-lg">Trending NFTs</span>
<span class="text-info">...</span>
<div class="card-actions">
<a class="btn btn-primary btn-block" href="/explorer/minters">All Minters</a>
<a class="btn btn-primary btn-block" href="/market/official_sales">Official Sales</a>
</div>
<div class="card-actions">
<a class="btn btn-primary btn-block" href="/market">View Market</a>
<a class="btn btn-primary btn-block" href="/explorer/minters">All Minters</a>
</div>
</div>
</div>
Expand Down
77 changes: 77 additions & 0 deletions src/lib/components/OfficialSaleCard.svelte
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
<script>
import { IPFS_GATEWAY } from '$lib/config/constants.js';
import { accountToIpfsCid } from '$lib/utils/ipfs.js';
import { getCurrencyString } from '$lib/services/price.js';
/*
official_sale is actually
{
sale_info,
supply,
}
*/
export let official_sale;
/*
//Official Sale schema
official_sale: {
supply_hash: string, //info like supply, name, image and whatnot can be looked up from this
minter_seed: string, //minter seed
sale_seed_index: number, //index of SALE_SEED (.env constant) that receives the payments
sale_price: number,
start_amount: number, //amount of nfts minted before the official sale
total_sales: number, //cannot exceed max supply - start amount (unless max supply is 0 [infinite]), and starts at 0
completed: boolean, //manually set to hide (in future, maybe have previously completed official sales viewable somewhere?),
collection: string,
}
*/
let metadata;
async function getMetadata() {
metadata = await (await fetch(`${IPFS_GATEWAY}/${accountToIpfsCid(official_sale.supply.metadata_representative)}`)).json();
}
getMetadata();
</script>


<div class="group">
<div class="card card-compact w-100 bg-base-100 shadow-xl">
{#if metadata}
<div class="aspect-h-1 aspect-w-1 w-full overflow-hidden rounded-md bg-gray-200 lg:aspect-none lg:h-80">
{#if metadata.animation_url}
<!-- svelte-ignore a11y-media-has-caption -->
<video class="w-5/6" controls>
<source src="{IPFS_GATEWAY}/{metadata.animation_url}#x-ipfs-companion-no-redirect">
</video>
{:else}
<img class="h-full w-full object-cover object-center lg:h-full lg:w-full" src="{IPFS_GATEWAY}/{metadata.image}" alt="{metadata.name} Image" />
{/if}
</div>
{/if}
<div class="card-body">
{#if metadata}
<h2 class="card-title">{official_sale.supply.nft_metadata.name}</h2>
<h3 class="card-title">From {official_sale.sale_info.collection}</h3>
<div class="flex flex-col my-2">
<div class="flex justify-between">
<h3 class="text-sm text-gray-400">Ask Price</h3>
<p class="text-end dark:text-yellow-200 font-bold">{official_sale.sale_info.sale_price} BAN</p>
</div>
<div class="flex justify-between">
<p class="text-xs text-end dark:text-yellow-200">{getCurrencyString(official_sale.sale_info.sale_price)}</p>
</div>
</div>
<div class="card-actions justify-end">
<a href="/market/official_sales/listings?supply_hash={official_sale.sale_info.supply_hash}" class="btn btn-primary btn-sm btn-block">Buy</a>
</div>
{:else}
<div class="flex justify-center">
<span class="loading loading-spinner loading-lg"></span>
</div>
{/if}
</div>
</div>
</div>
17 changes: 15 additions & 2 deletions src/lib/components/OwnedMintedCard.svelte
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
<script>
import { IPFS_GATEWAY } from '$lib/config/constants.js';
import { accountToIpfsCid } from '$lib/utils/ipfs.js';
import { getAccount } from '$lib/services/banano.js';
export let mintedNft;
export let owned;
Expand Down Expand Up @@ -31,9 +32,10 @@
<div class="card-body">
{#if metadata}
<h2 class="card-title"><a class="link" href="/explorer/assets?mint_hash={mintedNft.mint_hash}">{metadata.name}</a></h2>
<div class="card-actions justify-end">
<div class="card-action{ owned ? " grid grid-cols-2 gap-1" : "" }">
{#if owned}
<a href="/market/listings/create?mint_hash={mintedNft.mint_hash}" class="btn btn-primary btn-sm btn-block">Create Listing</a>
<a href="/market/listings/create?mint_hash={mintedNft.mint_hash}" class="btn btn-primary btn-sm w-full">Create Listing</a>
<button class="btn btn-primary btn-sm w-full" onclick="document.getElementById('send-modal-{mintedNft.mint_hash}').showModal()">How to Send</button>
{:else}
<a href="/market/listings/create?mint_hash={mintedNft.mint_hash}" class="btn btn-primary btn-sm btn-block" disabled>Create Listing</a>
{/if}
Expand All @@ -45,4 +47,15 @@
{/if}
</div>
</div>
<dialog id="send-modal-{mintedNft.mint_hash}" class="modal">
<div class="modal-box">
<h3 class="font-bold text-lg">Sending Instructions</h3>
<p class="py-4">Send a transaction to your intended recepient while your representative is set to {getAccount(mintedNft.mint_hash)}.</p>
<div class="modal-action">
<form method="dialog">
<button class="btn">x</button>
</form>
</div>
</div>
</dialog>
</div>
1 change: 1 addition & 0 deletions src/lib/services/banano.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import { BANANO_PREFIX, RPC_URL } from '$lib/config/constants.js';

import bananojs from '@bananocoin/bananojs';

//can be used to get asset rep if mint hash is the public key
export function getAccount(publicKey) {
return bananojs.BananoUtil.getAccount(publicKey, BANANO_PREFIX);
}
Expand Down
12 changes: 12 additions & 0 deletions src/routes/+error.svelte
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
<script>
import { page } from '$app/stores';
</script>

<svelte:head>
<title>{$page.status}</title>
</svelte:head>

<div class="w-full text-center mt-[22vh]">
<h1 class="text-9xl">{$page.status}</h1>
<p>{$page.error?.message}</p>
</div>
1 change: 0 additions & 1 deletion src/routes/404/+page.js

This file was deleted.

10 changes: 0 additions & 10 deletions src/routes/404/+page.svelte

This file was deleted.

2 changes: 1 addition & 1 deletion src/routes/account/+page.js
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ export const ssr = false;
export async function load({ url, fetch }) {
const address = get(sessionAddress);

const addressValid = verifyAddress(address, true)
const addressValid = verifyAddress(address, true);
if (!addressValid) {
return {
accountAddress: address,
Expand Down
1 change: 1 addition & 0 deletions src/routes/crypto_dvoaplpomy/+page.js
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export const ssr = false;
155 changes: 155 additions & 0 deletions src/routes/crypto_dvoaplpomy/+page.svelte
Original file line number Diff line number Diff line change
@@ -0,0 +1,155 @@
<script>
import { onMount } from 'svelte';
//https://developer.mozilla.org/en-US/docs/Web/API/SubtleCrypto/encrypt#rsa-oaep
//https://developer.mozilla.org/en-US/docs/Web/API/SubtleCrypto/encrypt#rsa-oaep
const hex_chars = ['0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E', 'F'];
function uint8_to_hex(uint8) {
let hex = "";
for (let i=0; i < uint8.length; i++) {
hex += hex_chars[Math.floor(uint8[i]/16)];
hex += hex_chars[uint8[i] % 16];
}
return hex;
}
function hex_to_uint8(hex) {
hex = hex.toUpperCase();
let uint8 = new Uint8Array(Math.floor(hex.length/2));
for (let i=0; i < Math.floor(hex.length/2); i++) {
uint8[i] = hex_chars.indexOf(hex[i*2])*16;
uint8[i] += hex_chars.indexOf(hex[i*2+1]);
}
return uint8;
}
//convert from Ed25519 (blake2b) to x25519
/*deleted user on the nano discord:
@IPDetective I have great news for you. I was looking more closely into your idea of using nano keys for asymmetric encryption cause it interested me. Remember the libsodium functions to convert ed25519 keys to curve25519? I said that you might have to edit them because nano uses blake2b with ed25519 instead of sha512.
But then I looked at the code for the functions. The one to convert public keys is super complicated, but it doesn't use the hash function. The one to convert private keys is much less complicated and does use the hash.
It's trivially easy to write your own code to convert the private key, replacing sha512 with blake2b
It's trivially easy to write your own code to convert the private key, replacing sha512 with blake2b
Here it is in python
```
import hashlib
def sk_to_curve25519(sk):
h = bytearray(hashlib.blake2b(sk, digest_size=64).digest())
h[0] &= 248
h[31] &= 127
h[31] |= 64
return bytes(h[:32]) # Return the upper 32 bytes of the 64 byte array
```
I did some testing. Since only the function to convert the private key uses the hash, you don't need to worry about the complex public key function. You can use libsodium to convert the public key, and just write your own function to convert the private key, and it'll work for encryption/decryption
*/
//Thank you so much random deleted user on the nano discord!!
function blake2bConvertSecretKey(secret_key) {
const context = blakejs.blake2bInit(64);
blakejs.blake2bUpdate(context, secret_key);
const h = blakejs.blake2bFinal(context);
h[0] &= 248
h[31] &= 127
h[31] |= 64
return h.slice(0, 32);
}
let asymc_address, asymc_message, asymc_seed, asymc_encrypted, asymc_nonce, asymc_pk, asymc_pubk;
let asymc_index = 0;
let asymc_encrypt_output = "";
let asymc_decrypt_output = "";
let asymc_gen_output = "";
//32 bytes
function gen_random_box_key_pair() {
let key_pair = nacl.box.keyPair();
asymc_gen_output = `Public key: ${uint8_to_hex(key_pair.publicKey)}, Private key: ${uint8_to_hex(key_pair.secretKey)}`;
}
onMount(() => {
document.getElementById("asymc-encrypt").onclick = async () => {
let public_key = window.bananocoinBananojs.getAccountPublicKey(asymc_address.trim());
let nonce = new Uint8Array(24);
window.crypto.getRandomValues(nonce);
public_key = ed2curve.convertPublicKey(hex_to_uint8(public_key));
console.log(public_key)
let encrypted = nacl.box((new TextEncoder()).encode(asymc_message), nonce, public_key, hex_to_uint8(asymc_pk.trim()));
asymc_encrypt_output = `${uint8_to_hex(encrypted)}, nonce: ${uint8_to_hex(nonce)}`;
};
document.getElementById("asymc-decrypt").onclick = async () => {
let private_key = window.bananocoinBananojs.getPrivateKey(asymc_seed.trim(), asymc_index);
//private_key = ed2curve.convertSecretKey(hex_to_uint8(private_key));
private_key = blake2bConvertSecretKey(hex_to_uint8(private_key));
console.log(nacl.box.keyPair.fromSecretKey(private_key))
//console.log(ed2curve.convertKeyPair({ publicKey: hex_to_uint8("D1BAB4851289F4B5972F4650D914B9A01D43C8A51B40EFB0AE16FB55FDEA8352"), secretKey: hex_to_uint8("8E1236E83B88446ECE38854EC9E9A15570F7298096CA718712D54FD8A74678C7") }))
let decrypted = nacl.box.open(hex_to_uint8(asymc_encrypted.trim()), hex_to_uint8(asymc_nonce.trim()), hex_to_uint8(asymc_pubk.trim()), private_key);
console.log(decrypted)
if (decrypted === null) {
asymc_decrypt_output = "DECRYPTION FAILED";
} else {
asymc_decrypt_output = (new TextDecoder()).decode(decrypted.buffer);
}
};
});
</script>

<svelte:head>
<title>Cryptography Tools</title>
<script src="/modules/banano.js"></script>
<script src="/modules/ed2curve.js"></script>
</svelte:head>

<div>
<p>USE THIS SITE WHILE OFFLINE! Inputting seed, etc, on a web page is generally not a good idea. Disconnecting from the internet is a good bare minimum security precaution.</p>
<hr/>
<h2 class="text-xl">Generate Random Asymmetric Keypair</h2>
<button id="asymc-gen" class="btn btn-primary" on:click={gen_random_box_key_pair}>Generate</button>
<p>{asymc_gen_output}</p>
<hr/>
<div>
<h2 class="text-xl">x25519-xsalsa20-poly1305 (tweetnacl) Asymmetrically Encrypt with own Private Key, and peer's address</h2>
<label for="asymc-address">Address of recepient:</label><input id="asymc-address" type="text" class="input input-bordered input-sm w-full max-w-xs" bind:value={asymc_address}/>
<br>
<label for="asymc-pk">Own private key:</label><input id="asymc-pk" type="text" class="input input-bordered input-sm w-full max-w-xs" bind:value={asymc_pk}/>
<br>
<label for="asymc-message">Message:</label><input id="asymc-message" type="text" class="input input-bordered input-sm w-full max-w-xs" bind:value={asymc_message}/>
<br>
<button id="asymc-encrypt" class="btn btn-primary">Encrypt</button>
<p>{asymc_encrypt_output}</p>
</div>
<hr/>
<div>
<h2 class="text-xl">x25519-xsalsa20-poly1305 (tweetnacl) Asymmetrically Decrypt with own seed, nonce, and peer's Public Key</h2>
<label for="asymc-seed">Your seed:</label><input id="asymc-seed" type="text" class="input input-bordered input-sm w-full max-w-xs" bind:value={asymc_seed}/>
<br>
<label for="asymc-index">Your seed index:</label><input id="asymc-index" type="number" min=0 class="input input-bordered input-sm w-full max-w-xs" bind:value={asymc_index}/>
<br>
<label for="asymc-nonce">Nonce:</label><input id="asymc-nonce" type="text" class="input input-bordered input-sm w-full max-w-xs" bind:value={asymc_nonce}/>
<br>
<label for="asymc-pubk">Peer's public key:</label><input id="asymc-pubk" type="text" class="input input-bordered input-sm w-full max-w-xs" bind:value={asymc_pubk}/>
<br>
<label for="asymc-encrypted">Encrypted:</label><input id="asymc-encrypted" type="text" class="input input-bordered input-sm w-full max-w-xs"bind:value={asymc_encrypted}/>
<br>
<button id="asymc-decrypt" class="btn btn-primary">Decrypt</button>
<p>{asymc_decrypt_output}</p>
</div>
</div>

<style>
label {
margin-right: 5px;
}
input {
margin-bottom: 5px;
}
hr {
margin-top: 15px;
margin-bottom: 15px;
}
</style>
22 changes: 22 additions & 0 deletions src/routes/help/+page.server.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
/*
{
'./+layout.svelte': [Function: ./+layout.svelte],
'./+page.svelte': [Function: ./+page.svelte],
'./message-signing/+page.svelte': [Function: ./message-signing/+page.svelte],
'./minting/+page.svelte': [Function: ./minting/+page.svelte],
'./usage/+page.svelte': [Function: ./usage/+page.svelte]
}
*/
//import.meta.glob gets something like the above ^
//get the subdirectories
const help_pages = Object.keys(import.meta.glob("./**")).map((p) => p.split("/")[1]).filter((p) => !p.includes("."));

//ssr is on
export const ssr = true;

/** @type {import('./$types').PageLoad} */
export async function load() {
return {
help_pages,
};
}
11 changes: 11 additions & 0 deletions src/routes/help/+page.svelte
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
<script>
export let data;
</script>

<p>A quick list of all help pages:</p>

<ul class="list-disc pl-4">
{#each data.help_pages as help_page}
<li><a class="link" href="/help/{help_page}">{help_page}</a></li>
{/each}
</ul>
Loading

0 comments on commit ea79ec4

Please sign in to comment.