working
This commit is contained in:
21
.gitignore
vendored
Normal file
21
.gitignore
vendored
Normal file
@@ -0,0 +1,21 @@
|
||||
node_modules
|
||||
|
||||
# Output
|
||||
.output
|
||||
.vercel
|
||||
/.svelte-kit
|
||||
/build
|
||||
|
||||
# OS
|
||||
.DS_Store
|
||||
Thumbs.db
|
||||
|
||||
# Env
|
||||
.env
|
||||
.env.*
|
||||
!.env.example
|
||||
!.env.test
|
||||
|
||||
# Vite
|
||||
vite.config.js.timestamp-*
|
||||
vite.config.ts.timestamp-*
|
||||
38
README.md
Normal file
38
README.md
Normal file
@@ -0,0 +1,38 @@
|
||||
# create-svelte
|
||||
|
||||
Everything you need to build a Svelte project, powered by [`create-svelte`](https://github.com/sveltejs/kit/tree/main/packages/create-svelte).
|
||||
|
||||
## Creating a project
|
||||
|
||||
If you're seeing this, you've probably already done this step. Congrats!
|
||||
|
||||
```bash
|
||||
# create a new project in the current directory
|
||||
npx sv create
|
||||
|
||||
# create a new project in my-app
|
||||
npx sv create my-app
|
||||
```
|
||||
|
||||
## Developing
|
||||
|
||||
Once you've created a project and installed dependencies with `npm install` (or `pnpm install` or `yarn`), start a development server:
|
||||
|
||||
```bash
|
||||
npm run dev
|
||||
|
||||
# or start the server and open the app in a new browser tab
|
||||
npm run dev -- --open
|
||||
```
|
||||
|
||||
## Building
|
||||
|
||||
To create a production version of your app:
|
||||
|
||||
```bash
|
||||
npm run build
|
||||
```
|
||||
|
||||
You can preview the production build with `npm run preview`.
|
||||
|
||||
> To deploy your app, you may need to install an [adapter](https://svelte.dev/docs/kit/adapters) for your target environment.
|
||||
3484
package-lock.json
generated
Normal file
3484
package-lock.json
generated
Normal file
File diff suppressed because it is too large
Load Diff
28
package.json
Normal file
28
package.json
Normal file
@@ -0,0 +1,28 @@
|
||||
{
|
||||
"name": "test",
|
||||
"version": "0.0.1",
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"dev": "vite dev",
|
||||
"build": "vite build",
|
||||
"preview": "vite preview",
|
||||
"check": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json",
|
||||
"check:watch": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json --watch"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@sveltejs/adapter-auto": "^3.0.0",
|
||||
"@sveltejs/adapter-cloudflare": "^4.7.4",
|
||||
"@sveltejs/kit": "^2.0.0",
|
||||
"@sveltejs/vite-plugin-svelte": "^4.0.0",
|
||||
"svelte": "^5.0.0",
|
||||
"svelte-check": "^4.0.0",
|
||||
"typescript": "^5.0.0",
|
||||
"vite": "^5.0.3"
|
||||
},
|
||||
"dependencies": {
|
||||
"banani": "^1.0.3",
|
||||
"banani-bns": "^0.0.6",
|
||||
"mongodb": "^6.10.0",
|
||||
"qrcode": "^1.5.4"
|
||||
}
|
||||
}
|
||||
13
src/app.d.ts
vendored
Normal file
13
src/app.d.ts
vendored
Normal file
@@ -0,0 +1,13 @@
|
||||
// See https://svelte.dev/docs/kit/types#app
|
||||
// for information about these interfaces
|
||||
declare global {
|
||||
namespace App {
|
||||
// interface Error {}
|
||||
// interface Locals {}
|
||||
// interface PageData {}
|
||||
// interface PageState {}
|
||||
// interface Platform {}
|
||||
}
|
||||
}
|
||||
|
||||
export {};
|
||||
14
src/app.html
Normal file
14
src/app.html
Normal file
@@ -0,0 +1,14 @@
|
||||
<!doctype html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<link rel="icon" href="%sveltekit.assets%/favicon.png" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||
<script src="/bns-browser.js"></script>
|
||||
<link rel="stylesheet" href="/global.css">
|
||||
%sveltekit.head%
|
||||
</head>
|
||||
<body data-sveltekit-preload-data="hover">
|
||||
<div style="display: contents">%sveltekit.body%</div>
|
||||
</body>
|
||||
</html>
|
||||
42
src/lib/Declare.svelte
Normal file
42
src/lib/Declare.svelte
Normal file
@@ -0,0 +1,42 @@
|
||||
<script lang="ts">
|
||||
import { browser } from "$app/environment";
|
||||
|
||||
import { Progress } from "$lib/types";
|
||||
|
||||
let { seed, bsf_seed, domain, progress = $bindable() } = $props();
|
||||
|
||||
let resolve_to: String = $state("");
|
||||
|
||||
async function declare() {
|
||||
resolve_to = resolve_to.trim();
|
||||
if (browser && resolve_to.startsWith("ban_") && resolve_to.length === 64) {
|
||||
const rpc = new window.bns.banani.RPC("https://kaliumapi.appditto.com/api");
|
||||
const wallet = new window.bns.banani.Wallet(rpc, seed);
|
||||
await wallet.receive_all();
|
||||
const domain_manager = new window.bns.DomainAccountManager(rpc, wallet);
|
||||
await domain_manager.declare_domain_resolve_to(resolve_to);
|
||||
progress = Progress.Done;
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<p>One last step! Just enter your main Banano address below, so anyone sending to {domain}.ban can know to send it to you.</p>
|
||||
|
||||
<div>
|
||||
<input bind:value={resolve_to} type="text" placeholder="ban_..."/>
|
||||
<br><br>
|
||||
<button class="button" onclick={declare}>Declare Address to Resolve to</button>
|
||||
</div>
|
||||
|
||||
<style>
|
||||
input {
|
||||
border: 1px solid gray;
|
||||
padding: 5px;
|
||||
min-width: 40%;
|
||||
border-radius: 10px;
|
||||
}
|
||||
div {
|
||||
margin: 15px 0;
|
||||
}
|
||||
</style>
|
||||
|
||||
65
src/lib/Payment.svelte
Normal file
65
src/lib/Payment.svelte
Normal file
@@ -0,0 +1,65 @@
|
||||
<script lang="ts">
|
||||
import { browser } from "$app/environment";
|
||||
|
||||
import { toDataURL } from "qrcode";
|
||||
|
||||
import { Progress } from "$lib/types";
|
||||
import { get_price, seconds_to_time, ALLOWED } from "$lib/utils";
|
||||
|
||||
let { domain, payment_address, send_to_pub_key, progress = $bindable() } = $props();
|
||||
|
||||
let time_left: number = $state(5 * 60);
|
||||
let payment_qr_promise: Promise<String> = $derived(toDataURL(`ban:${payment_address}?amount=${String(window.bns.banani.whole_to_raw(String(price)))}`));
|
||||
let error: String = $state(undefined);
|
||||
|
||||
let price = $state(undefined);
|
||||
|
||||
if (domain.length > 3 && domain.split("").every((c) => ALLOWED.includes(c)) && domain !== undefined) {
|
||||
price = get_price(domain.length);
|
||||
}
|
||||
|
||||
setInterval(() => time_left -= 1, 1000);
|
||||
|
||||
async function check_for_payment() {
|
||||
let { send_hash, message } = await (await fetch("/api/check_payment", {
|
||||
method: "POST",
|
||||
body: JSON.stringify({
|
||||
domain,
|
||||
send_to_pub_key,
|
||||
}),
|
||||
})).json();
|
||||
if (!send_hash) {
|
||||
error = "Have you sent the payment?";
|
||||
} else {
|
||||
console.log(send_hash);
|
||||
progress = Progress.Declare;
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
{#if error}
|
||||
<span class="error">{error}</span>
|
||||
{/if}
|
||||
{#if isNaN(price)}
|
||||
<p>Invalid domain name (too short / disallowed characters)</p>
|
||||
{:else}
|
||||
<p>Send {price} BAN to <code>{payment_address}</code></p>
|
||||
<p><a href="https://thebananostand.com?request=send&address={payment_address}&amount={price}" target="_blank">Open in Bananostand</a></p>
|
||||
<p><a href="ban:{payment_address}?amount={String(window.bns.banani.whole_to_raw(String(price)))}" target="_blank">Open in Kalium</a></p>
|
||||
{#await payment_qr_promise}
|
||||
<p>Loading QR...</p>
|
||||
{:then payment_qr}
|
||||
<img alt="Payment QR code" src="{payment_qr}">
|
||||
{/await}
|
||||
<br>
|
||||
<p>{seconds_to_time(time_left)}</p>
|
||||
<br>
|
||||
<button onclick={check_for_payment} class="button">I've sent the payment</button>
|
||||
{/if}
|
||||
|
||||
<style>
|
||||
p {
|
||||
margin-bottom: 15px;
|
||||
}
|
||||
</style>
|
||||
|
||||
21
src/lib/Seed.svelte
Normal file
21
src/lib/Seed.svelte
Normal file
@@ -0,0 +1,21 @@
|
||||
<script lang="ts">
|
||||
import { Progress } from "$lib/types";
|
||||
|
||||
let { bsf_seed, progress = $bindable() } = $props();
|
||||
|
||||
function continue_to_payment() {
|
||||
progress = Progress.Payment;
|
||||
}
|
||||
</script>
|
||||
|
||||
<p>Save the seed and continue.</p>
|
||||
<p>Seed (in BNS Seed Format): <code>{bsf_seed}</code></p>
|
||||
<button class="button" onclick={continue_to_payment}>Continue</button>
|
||||
|
||||
<style>
|
||||
p {
|
||||
margin-bottom: 15px;
|
||||
}
|
||||
</style>
|
||||
|
||||
|
||||
65
src/lib/db.ts
Normal file
65
src/lib/db.ts
Normal file
@@ -0,0 +1,65 @@
|
||||
import type { MongoClient } from "mongodb";
|
||||
|
||||
import type { Path } from "$lib/types";
|
||||
import { get_price } from "$lib/utils";
|
||||
|
||||
export async function is_domain_already_issued(db: MongoClient, domain: string): Promise<boolean> {
|
||||
const issued = db.db("bns_backend").collection("issued");
|
||||
if (await issued.findOne({ domain })) {
|
||||
return true;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
export async function add_domain_to_issued(db: MongoClient, domain: string, issued_hash: string, price: number) {
|
||||
const issued = db.db("bns_backend").collection("issued");
|
||||
await issued.insertOne({
|
||||
domain,
|
||||
issued_hash,
|
||||
price, //cause price may change in future, record price at time of sale
|
||||
});
|
||||
}
|
||||
|
||||
//in the last 5 minutes
|
||||
export async function payment_already_pending(db: MongoClient, domain: string): Promise<boolean> {
|
||||
const payments = db.db("bns_backend").collection("payments");
|
||||
if (await payments.findOne({
|
||||
domain,
|
||||
timestamp: {
|
||||
$gt: Date.now() - 5 * 60 * 1000,
|
||||
},
|
||||
})) {
|
||||
return true;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
//todo: return an actual type
|
||||
export async function find_payment(db: MongoClient, domain: string, send_to: string): Promise<any> {
|
||||
const payments = db.db("bns_backend").collection("payments");
|
||||
return await payments.findOne({
|
||||
domain,
|
||||
send_to,
|
||||
timestamp: {
|
||||
$gt: Date.now() - 5 * 60 * 1000,
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
//todo: technically possible for there to be race condition with payment_already_pending
|
||||
export async function create_payment(db: MongoClient, domain: string, send_to: string, receive_seed: string) {
|
||||
const price = get_price(domain.length);
|
||||
const payments = db.db("bns_backend").collection("payments");
|
||||
await payments.insertOne({
|
||||
domain,
|
||||
receive_seed, //seed to receive payment from
|
||||
send_to, //Domain Address (banano address) to send domain to after payment received
|
||||
price,
|
||||
timestamp: Date.now(),
|
||||
});
|
||||
}
|
||||
|
||||
//
|
||||
|
||||
1
src/lib/index.ts
Normal file
1
src/lib/index.ts
Normal file
@@ -0,0 +1 @@
|
||||
// place files you want to import through the `$lib` alias in this folder.
|
||||
10
src/lib/mongo.ts
Normal file
10
src/lib/mongo.ts
Normal file
@@ -0,0 +1,10 @@
|
||||
import { MongoClient } from "mongodb";
|
||||
|
||||
import { MONGODB_URI } from "$env/static/private";
|
||||
|
||||
//const MONGODB_URI = process.env["MONGODB_URI"];
|
||||
|
||||
const client = new MongoClient(MONGODB_URI);
|
||||
|
||||
export const client_promise = client.connect();
|
||||
|
||||
8
src/lib/types.ts
Normal file
8
src/lib/types.ts
Normal file
@@ -0,0 +1,8 @@
|
||||
export enum Progress {
|
||||
Seed = "seed",
|
||||
Payment = "payment",
|
||||
Declare = "declare",
|
||||
Done = "done",
|
||||
Failed = "failed",
|
||||
}
|
||||
|
||||
37
src/lib/utils.ts
Normal file
37
src/lib/utils.ts
Normal file
@@ -0,0 +1,37 @@
|
||||
export const ALLOWED = ["a", "b", "c", "d", "e", "f", "g", "h", "i", "j", "k", "l", "m", "n", "o", "p", "q", "r", "s", "t", "u", "v", "w", "x", "y", "z", "0", "1", "2", "3", "4", "5", "6", "7", "8", "9", "_", "backspace", "arrowleft", "arrowtop", "arrowbottom", "arrowright"];
|
||||
|
||||
export function get_price(dl: number): number {
|
||||
if (dl === 4) {
|
||||
return 4200;
|
||||
} else if (dl === 5) {
|
||||
return 1900;
|
||||
} else if (dl === 6) {
|
||||
return 1200;
|
||||
} else if (dl === 7) {
|
||||
return 900;
|
||||
} else if (dl === 8) {
|
||||
return 690;
|
||||
} else if (dl === 9) {
|
||||
return 420;
|
||||
} else if (dl > 9) {
|
||||
return 1;//190;
|
||||
} else {
|
||||
return 99999; //currently <4 length domains not buyable, but just in case...
|
||||
}
|
||||
}
|
||||
|
||||
export function is_domain_name_allowed(domain: string): boolean {
|
||||
return domain.split("").every((c) => ALLOWED.includes(c));
|
||||
}
|
||||
|
||||
const HEX_CHARS = ["0", "1", "2", "3", "4", "5", "6", "7", "8", "9", "A", "B", "C", "D", "E", "F"];
|
||||
|
||||
export function is_valid_public_key(public_key: string): boolean {
|
||||
return public_key.length === 64 && public_key.split("").every((c) => HEX_CHARS.includes(c));
|
||||
}
|
||||
|
||||
export function seconds_to_time(seconds: number): string {
|
||||
if (seconds < 0) return "0:00";
|
||||
return `${Math.floor(seconds / 60)}:${String(seconds % 60).padStart(2, "0")}`;
|
||||
}
|
||||
|
||||
221
src/routes/+page.svelte
Normal file
221
src/routes/+page.svelte
Normal file
@@ -0,0 +1,221 @@
|
||||
<script lang="ts">
|
||||
import { goto } from "$app/navigation";
|
||||
|
||||
import { get_price, ALLOWED } from "$lib/utils";
|
||||
|
||||
let domain_content: String = $state("");
|
||||
let error: String = $state("");
|
||||
let price: String = $state("");
|
||||
|
||||
function domain_keydown(event: KeyboardEvent) {
|
||||
let key = event.key.toLowerCase();
|
||||
if (!ALLOWED.includes(key)) {
|
||||
event.preventDefault();
|
||||
} else {
|
||||
price = "";
|
||||
if (domain_content.length > 3) {
|
||||
price = `Price: ${get_price(domain_content.length)} BAN`;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function domain_keyup() {
|
||||
domain_content = domain_content.toLowerCase();
|
||||
}
|
||||
|
||||
async function domain_next() {
|
||||
domain_content = domain_content.toLowerCase();
|
||||
if (domain_content.length < 4) {
|
||||
error = "Domain name must be more than 3 characters";
|
||||
} else {
|
||||
const resp = await (await fetch("/api/domain_issued?domain=" + domain_content)).json();
|
||||
if (resp.issued) {
|
||||
error = "Domain name already issued, choose another one";
|
||||
} else {
|
||||
error = "";
|
||||
goto("/register?domain=" + domain_content);
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<svelte:head>
|
||||
<title>Get your .ban</title>
|
||||
</svelte:head>
|
||||
|
||||
<div>
|
||||
<div id="front">
|
||||
<div id="pitch" class="half">
|
||||
<h1>.ban: It's Healthy</h1>
|
||||
<div id="nutrition">
|
||||
<h2><b>Nutrition Facts</b></h2>
|
||||
<hr>
|
||||
<span>1 serving per domain</span>
|
||||
<br>
|
||||
<span><b>Serving size</b></span> <span class="right"><b>starting at 190 $BAN (or 1 kg of bananas)</b></span>
|
||||
<hr class="thickest">
|
||||
<span><b>Amount per serving</b></span>
|
||||
<br>
|
||||
<span class="calories"><b>Calories</b></span> <span class="right calories"><b>0</b></span>
|
||||
<hr class="thick">
|
||||
<span> </span> <span class="right"><b><small>% Daily Value</small></b></span>
|
||||
<hr>
|
||||
<span><b>Decentralisation</b> 25g</span> <span class="right"><b>100%</b></span>
|
||||
<hr>
|
||||
<span class="stagger">On-chain?</span> <span class="right"><b>Yep</b></span>
|
||||
<hr>
|
||||
<span class="stagger">Censorable?</span> <span class="right"><b>Nope</b></span>
|
||||
<hr>
|
||||
<span class="stagger">Revocable?</span> <span class="right"><b>Hell no</b></span>
|
||||
<hr>
|
||||
<span><b>Utility</b> 17g</span> <span class="right"><b>100%</b></span>
|
||||
<hr>
|
||||
<span class="stagger">Fast and Feeless?</span> <span class="right"><b>Duh</b></span>
|
||||
<hr>
|
||||
<span class="stagger">Wait, no renewal fees?</span> <span class="right"><b>Yesss</b></span>
|
||||
<hr>
|
||||
<span class="stagger">Mine, forever?</span> <span class="right"><b>Bingo</b></span>
|
||||
<hr>
|
||||
<span class="stagger">Transferable?</span> <span class="right"><b>Si</b></span>
|
||||
<hr>
|
||||
<span class="stagger">Resolves to Banano address?</span> <span class="right"><b>Correct</b></span>
|
||||
<hr>
|
||||
<span class="stagger">Can be associated with arbitrary metadata?</span> <span class="right"><b>Yeah...</b></span>
|
||||
<hr class="thickest">
|
||||
|
||||
<span>Potassium 190g 55882%</span> - <span class="right">Thorium 0g 0%</span>
|
||||
<hr>
|
||||
<span>Unicorn Horn powder 10mg 41%</span> - <span class="right">Typescript 1kg 1%</span>
|
||||
<hr class="thick">
|
||||
</div>
|
||||
<p id="bottom-1">Have <code>yourname.ban</code> resolve to <code>ban_1area11yrea11yrea11y1ongdifficu1ttorememberaddress11hcd8a7c9</code>! Other usecases like decentralised websites coming soon™</p>
|
||||
</div>
|
||||
<div id="get" class="half">
|
||||
<div id="get-child">
|
||||
<div id="input-container">
|
||||
<input placeholder="Get your .ban on" maxlength="48" onkeydown={domain_keydown} onkeyup={domain_keyup} bind:value={domain_content} type="text"/><span>.ban</span><input onclick={domain_next} type="button" value="-->"/>
|
||||
</div>
|
||||
<span id="price">{price}</span>
|
||||
<span class="error">{error}</span>
|
||||
<div>
|
||||
<h2>Supported by:</h2>
|
||||
<div>
|
||||
<span>Bananostand</span>
|
||||
</div>
|
||||
</div>
|
||||
<p id="bottom-2">.ban is the first publicly available top level domain (TLD) for the <a href="https://github.com/stjet/bns/blob/master/bns_protocol.md">Banano Name Service protocol (BNS)</a></p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<style>
|
||||
#front {
|
||||
display: grid;
|
||||
grid-template-columns: 50vw 50vw;
|
||||
height: 100vh;
|
||||
}
|
||||
|
||||
#pitch {
|
||||
background-color: var(--green);
|
||||
}
|
||||
|
||||
.half {
|
||||
padding: 2em;
|
||||
}
|
||||
|
||||
.right {
|
||||
float: right;
|
||||
}
|
||||
|
||||
.stagger {
|
||||
margin-left: 2em;
|
||||
}
|
||||
|
||||
h1 {
|
||||
font-size: xxx-large;
|
||||
}
|
||||
|
||||
h2 {
|
||||
font-size: xx-large;
|
||||
}
|
||||
|
||||
.calories {
|
||||
font-size: x-large;
|
||||
}
|
||||
|
||||
#bottom-1 {
|
||||
word-break: break-all;
|
||||
}
|
||||
|
||||
hr {
|
||||
margin: 1px 0;
|
||||
}
|
||||
|
||||
.thickest {
|
||||
border-width: 7px;
|
||||
}
|
||||
|
||||
.thick {
|
||||
border-width: 4px;
|
||||
}
|
||||
|
||||
#get {
|
||||
background-color: var(--yellow);
|
||||
}
|
||||
|
||||
#get-child {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
#bottom-2 {
|
||||
position: absolute;
|
||||
bottom: 0;
|
||||
}
|
||||
|
||||
#input-container {
|
||||
width: 100%;
|
||||
height: 30px;
|
||||
border: 1px solid gray;
|
||||
border-radius: 10px;
|
||||
margin: auto;
|
||||
background-color: white;
|
||||
}
|
||||
|
||||
#input-container input {
|
||||
border: none;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
#input-container input[type="text"] {
|
||||
padding-left: 3px;
|
||||
width: calc(65% - 3px);
|
||||
border-top-left-radius: 10px;
|
||||
border-bottom-left-radius: 10px;
|
||||
}
|
||||
|
||||
#input-container span {
|
||||
display: inline-block;
|
||||
width: 10%;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
#input-container input[type="button"] {
|
||||
width: 25%;
|
||||
border-top-right-radius: 10px;
|
||||
border-bottom-right-radius: 10px;
|
||||
cursor: pointer;
|
||||
color: white;
|
||||
background-color: var(--grey2);
|
||||
}
|
||||
|
||||
#logo {
|
||||
height: 16px;
|
||||
width: 16px;
|
||||
margin-top: 2px;
|
||||
}
|
||||
</style>
|
||||
45
src/routes/api/check_payment/+server.ts
Normal file
45
src/routes/api/check_payment/+server.ts
Normal file
@@ -0,0 +1,45 @@
|
||||
import { error, json } from "@sveltejs/kit";
|
||||
import type { RequestHandler } from "./$types";
|
||||
|
||||
import { Wallet, RPC, get_address_from_public_key, raw_to_whole } from "banani";
|
||||
import { TLDAccountManager } from "banani-bns";
|
||||
|
||||
import { client_promise } from "$lib/mongo";
|
||||
import { add_domain_to_issued, is_domain_already_issued, find_payment } from "$lib/db";
|
||||
import { is_valid_public_key } from "$lib/utils";
|
||||
import { TLD_SEED, STORAGE_ADDRESS } from "$env/static/private";
|
||||
|
||||
const rpc = new RPC("https://kaliumapi.appditto.com/api");
|
||||
const sleep = ms => new Promise(r => setTimeout(r, ms));
|
||||
|
||||
export const POST: RequestHandler = async ({ request }) => {
|
||||
const { domain, send_to_pub_key } = await request.json();
|
||||
if (!domain || !send_to_pub_key) {
|
||||
return error(400, "Missing one or more of the required fields `domain` and `send_to_pub_key`");
|
||||
} else if (!is_valid_public_key(send_to_pub_key)) {
|
||||
return error(400, "`send_to_pub_key` is invalid");
|
||||
}
|
||||
const db = await client_promise;
|
||||
if (await is_domain_already_issued(db, domain)) {
|
||||
return error(500, "Domain already issued");
|
||||
}
|
||||
const found = await find_payment(db, domain, get_address_from_public_key(send_to_pub_key));
|
||||
if (!found) {
|
||||
return error(500, "Payment request expired or never made");
|
||||
}
|
||||
const receive_wallet = new Wallet(rpc, found.receive_seed);
|
||||
await receive_wallet.receive_all();
|
||||
await sleep(1500);
|
||||
const balance = Number(raw_to_whole((await rpc.get_account_balance(receive_wallet.address)).balance));
|
||||
if (balance < found.price) {
|
||||
return error(500, `Need to be sent ${found.price}, only got ${balance}`);
|
||||
}
|
||||
receive_wallet.send_all(STORAGE_ADDRESS);
|
||||
const tld_manager = new TLDAccountManager(rpc, new Wallet(rpc, TLD_SEED));
|
||||
const send_hash = await tld_manager.issue_domain_name(domain, found.send_to);
|
||||
await add_domain_to_issued(db, domain, send_hash, found.price);
|
||||
return json({
|
||||
send_hash,
|
||||
});
|
||||
};
|
||||
|
||||
17
src/routes/api/domain_issued/+server.ts
Normal file
17
src/routes/api/domain_issued/+server.ts
Normal file
@@ -0,0 +1,17 @@
|
||||
import { error, json } from '@sveltejs/kit';
|
||||
import type { RequestHandler } from "./$types";
|
||||
|
||||
import { client_promise } from "$lib/mongo";
|
||||
import { is_domain_already_issued } from "$lib/db";
|
||||
|
||||
export const GET: RequestHandler = async ({ url }) => {
|
||||
const domain = url.searchParams.get("domain");
|
||||
if (!domain) {
|
||||
return error(400, "Missing URL query param `domain`");
|
||||
}
|
||||
let db = await client_promise;
|
||||
return json({
|
||||
issued: await is_domain_already_issued(db, domain),
|
||||
});
|
||||
}
|
||||
|
||||
31
src/routes/api/start_payment/+server.ts
Normal file
31
src/routes/api/start_payment/+server.ts
Normal file
@@ -0,0 +1,31 @@
|
||||
import { error, json } from "@sveltejs/kit";
|
||||
import type { RequestHandler } from "./$types";
|
||||
|
||||
import { Wallet, get_address_from_public_key } from "banani";
|
||||
|
||||
import { client_promise } from "$lib/mongo";
|
||||
import { is_domain_already_issued, payment_already_pending, create_payment } from "$lib/db";
|
||||
import { is_domain_name_allowed, is_valid_public_key } from "$lib/utils";
|
||||
|
||||
export const POST: RequestHandler = async ({ request }) => {
|
||||
const { domain, send_to_pub_key } = await request.json();
|
||||
if (!domain || !send_to_pub_key) {
|
||||
return error(400, "Missing one or more of the required fields `domain` and `send_to_pub_key`");
|
||||
} else if (!is_domain_name_allowed(domain) || domain.length < 4) {
|
||||
return error(400, "Domain name has disallowed characters or is shorter than 4 characters");
|
||||
} else if (!is_valid_public_key(send_to_pub_key)) {
|
||||
return error(400, "`send_to_pub_key` is invalid");
|
||||
}
|
||||
const db = await client_promise;
|
||||
if (await is_domain_already_issued(db, domain)) {
|
||||
return error(500, "Domain already issued");
|
||||
} else if (await payment_already_pending(db, domain)) {
|
||||
return error(500, "Payment for domain already pending, wait 5 minutes or so");
|
||||
}
|
||||
const payment_wallet = Wallet.gen_random_wallet();
|
||||
await create_payment(db, domain, get_address_from_public_key(send_to_pub_key), payment_wallet.seed);
|
||||
return json({
|
||||
payment_address: payment_wallet.address,
|
||||
});
|
||||
};
|
||||
|
||||
64
src/routes/register/+page.svelte
Normal file
64
src/routes/register/+page.svelte
Normal file
@@ -0,0 +1,64 @@
|
||||
<script lang="ts">
|
||||
import { page } from "$app/stores";
|
||||
import { browser } from "$app/environment";
|
||||
|
||||
import { Progress } from "$lib/types";
|
||||
import Seed from "$lib/Seed.svelte";
|
||||
import Payment from "$lib/Payment.svelte";
|
||||
import Declare from "$lib/Declare.svelte";
|
||||
|
||||
let progress: Progress = $state(Progress.Seed);
|
||||
let message: String = $state(undefined); //error
|
||||
let payment_address: String = $state(undefined);
|
||||
|
||||
let domain = $page.url.searchParams.get("domain");
|
||||
|
||||
let wallet, send_to_pub_key, bsf_seed;
|
||||
if (browser) {
|
||||
wallet = window.bns.banani.Wallet.gen_random_wallet();
|
||||
send_to_pub_key = wallet.public_key;
|
||||
bsf_seed = window.bns.hex_to_bns_seed_format(wallet.seed);
|
||||
}
|
||||
|
||||
$effect(async () => {
|
||||
if (progress === Progress.Payment) {
|
||||
({ payment_address, message } = await (await fetch("/api/start_payment", {
|
||||
method: "POST",
|
||||
body: JSON.stringify({
|
||||
domain,
|
||||
send_to_pub_key,
|
||||
}),
|
||||
})).json());
|
||||
if (!payment_address) {
|
||||
progress = Progress.Failed;
|
||||
}
|
||||
}
|
||||
});
|
||||
</script>
|
||||
|
||||
<div id="main">
|
||||
<div class="middle">
|
||||
{#if progress === Progress.Seed}
|
||||
<Seed bind:progress {bsf_seed}/>
|
||||
{:else if progress === Progress.Payment}
|
||||
<Payment bind:progress {domain} {payment_address} {send_to_pub_key}/>
|
||||
{:else if progress === Progress.Declare}
|
||||
<Declare bind:progress {domain} seed={wallet.seed} {bsf_seed}/>
|
||||
{:else if progress === Progress.Done}
|
||||
<p>You are all set! Try your new domain out by sending yourself a Banano or two in Bananostand at {domain}.ban!</p>
|
||||
<p>You can make further changes to your BNS domain by entering the seed and clicking the "Create Domain Account" in the <a href="https://bns.prussia.dev/browser_test">BNS Web Wallet</a>.</p>
|
||||
<p>As a reminder, the seed you need to save is <code>{bsf_seed}</code></p>
|
||||
{:else if progress === Progress.Failed}
|
||||
<span class="error">{message}</span>
|
||||
{/if}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<style>
|
||||
#main {
|
||||
height: 100vh;
|
||||
box-sizing: border-box;
|
||||
padding: 10px;
|
||||
background-color: var(--green);
|
||||
}
|
||||
</style>
|
||||
11
static/bns-browser.js
Normal file
11
static/bns-browser.js
Normal file
File diff suppressed because one or more lines are too long
BIN
static/favicon.png
Normal file
BIN
static/favicon.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 5.2 KiB |
BIN
static/fonts/Arimo.ttf
Normal file
BIN
static/fonts/Arimo.ttf
Normal file
Binary file not shown.
40
static/global.css
Normal file
40
static/global.css
Normal file
@@ -0,0 +1,40 @@
|
||||
@font-face {
|
||||
font-family: Arimo;
|
||||
src: local("Arimo"), url("/fonts/Arimo.ttf");
|
||||
}
|
||||
|
||||
:root {
|
||||
--yellow: #FBDD11;
|
||||
--green: #4CBF4B;
|
||||
--grey1: #2A2A2E;
|
||||
--grey2: #212124;
|
||||
}
|
||||
|
||||
*:not(code) {
|
||||
font-family: Arimo;
|
||||
margin: 0;
|
||||
color: var(--grey1);
|
||||
}
|
||||
|
||||
.error {
|
||||
color: red;
|
||||
background-color: var(--yellow);
|
||||
}
|
||||
|
||||
.middle {
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.button {
|
||||
background-color: var(--yellow);
|
||||
border: 1px solid black;
|
||||
border-radius: 15px;
|
||||
padding: 15px;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.button:hover {
|
||||
cursor: pointer;
|
||||
background-color: #eacf1e;
|
||||
}
|
||||
|
||||
19
svelte.config.js
Normal file
19
svelte.config.js
Normal file
@@ -0,0 +1,19 @@
|
||||
/*
|
||||
import adapter from '@sveltejs/adapter-auto';
|
||||
*/
|
||||
import { vitePreprocess } from '@sveltejs/vite-plugin-svelte';
|
||||
import adapter from '@sveltejs/adapter-cloudflare';
|
||||
|
||||
export default {
|
||||
// Consult https://svelte.dev/docs/kit/integrations#preprocessors
|
||||
// for more information about preprocessors
|
||||
preprocess: vitePreprocess(),
|
||||
|
||||
kit: {
|
||||
// adapter-auto only supports some environments, see https://svelte.dev/docs/kit/adapter-auto for a list.
|
||||
// If your environment is not supported, or you settled on a specific environment, switch out the adapter.
|
||||
// See https://svelte.dev/docs/kit/adapters for more information about adapters.
|
||||
adapter: adapter(),
|
||||
}
|
||||
};
|
||||
|
||||
19
tsconfig.json
Normal file
19
tsconfig.json
Normal file
@@ -0,0 +1,19 @@
|
||||
{
|
||||
"extends": "./.svelte-kit/tsconfig.json",
|
||||
"compilerOptions": {
|
||||
"allowJs": true,
|
||||
"checkJs": true,
|
||||
"esModuleInterop": true,
|
||||
"forceConsistentCasingInFileNames": true,
|
||||
"resolveJsonModule": true,
|
||||
"skipLibCheck": true,
|
||||
"sourceMap": true,
|
||||
"strict": true,
|
||||
"moduleResolution": "bundler"
|
||||
}
|
||||
// Path aliases are handled by https://svelte.dev/docs/kit/configuration#alias
|
||||
// except $lib which is handled by https://svelte.dev/docs/kit/configuration#files
|
||||
//
|
||||
// If you want to overwrite includes/excludes, make sure to copy over the relevant includes/excludes
|
||||
// from the referenced tsconfig.json - TypeScript does not merge them in
|
||||
}
|
||||
6
vite.config.ts
Normal file
6
vite.config.ts
Normal file
@@ -0,0 +1,6 @@
|
||||
import { sveltekit } from '@sveltejs/kit/vite';
|
||||
import { defineConfig } from 'vite';
|
||||
|
||||
export default defineConfig({
|
||||
plugins: [sveltekit()]
|
||||
});
|
||||
Reference in New Issue
Block a user