8000 Support SLIP-39 by sectore · Pull Request #3 · sectore/wordlist · GitHub
[go: up one dir, main page]
More Web Proxy on the site http://driver.im/
Skip to content

Support SLIP-39 #3

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 7 commits into from
Jul 21, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 4 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,14 +1,14 @@
# [BIP-39](https://github.com/bitcoin/bips/blob/master/bip-0039/bip-0039-wordlists.md) | [SLIP-39](https://github.com/satoshilabs/slips/blob/master/slip-0039.md) word lists
# [BIP-39](https://github.com/bitcoin/bips/blob/master/bip-0039/bip-0039-wordlists.md) | [SLIP-39](https://github.com/satoshilabs/slips/blob/master/slip-0039.md) word list

Explore [BIP-39](https://github.com/bitcoin/bips/blob/master/bip-0039/bip-0039-wordlists.md) word lists in 10 languages or [SLIP-39](https://github.com/satoshilabs/slips/blob/master/slip-0039.md) in English. Filter words or search for word positions.
Explore [BIP-39](https://github.com/bitcoin/bips/blob/master/bip-0039/bip-0039-wordlists.md) and [SLIP-39](https://github.com/satoshilabs/slips/blob/master/slip-0039.md) word list in up to 10 languages. Filter words or search for word positions.

## Live

https://bip39.bitcoinbeachtravemuende.de
https://wordlist.bitcoinbeachtravemuende.de

## Preview

https://github.com/BitcoinBeachTravemuende/bip39-word-list/assets/47693/d03fa943-cb90-4064-8b3e-9577f4d0b2e1
https://github.com/user-attachments/assets/9b394691-a757-4fa8-a805-84cbfe523067

## Develop (locally)

Expand Down Expand Up @@ -49,7 +49,6 @@ bun run import:slip39
- [Svelte](https://svelte.dev/)
- [daisyUI](https://daisyui.com/)
- [Effect](https://effect.website/)
- [bitcoinjs/bip39](https://github.com/bitcoinjs/bip39)
- [svelte-typewriter](https://github.com/satohshi/svelte-typewriter)
- [lucide-svelte](https://lucide.dev/)

Expand Down
Binary file modified bun.lockb
Binary file not shown.
2 changes: 1 addition & 1 deletion flake.nix
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
{
description = "bip39 word list";
description = "word list";

inputs = {
nixpkgs.url = "github:NixOS/nixpkgs/nixos-unstable";
Expand Down
5 changes: 3 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
@@ -1,10 +1,11 @@
{
"name": "bip39-slip39-wordlist",
"name": "wordlist",
"version": "0.0.1",
"devDependencies": {
"@effect/platform": "^0.59.2",
"@effect/platform-bun": "^0.39.2",
"@effect/platform-browser": "^0.38.3",
"@effect/platform-bun": "^0.39.2",
"@effect/schema": "^0.68.26",
"@sveltejs/adapter-auto": "^3.2.2",
"@sveltejs/adapter-static": "^3.0.2",
"@sveltejs/kit": "^2.5.18",
Expand Down
2 changes: 1 addition & 1 deletion src/Footer.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
<aside class="flex flex-col items-center gap-2 text-xs">
<p>
Open Source at <a
href="https://github.com/BitcoinBeachTravemuende/bip39-word-list"
href="https://github.com/BitcoinBeachTravemuende/wordlist"
class="ml-1 inline-block underline hover:text-gray-600 dark:hover:text-gray-200"
>
Github</a
Expand Down
83 changes: 52 additions & 31 deletions src/Header.svelte
8000
Original file line number Diff line number Diff line change
@@ -1,61 +1,60 @@
<script lang="ts">
import { page } from '$app/stores';
import { AlignJustify, LayoutGrid, Moon, Repeat2, Sun } from 'lucide-svelte';
import store, { PATHS as P } from '$lib/store.svelte';
import { languages, type LANG } from '$lib/types';
import { SUB_PATH } from '$lib/const';
import store from '$lib/store.svelte';
import { type LANG } from '$lib/types';
import { getFullPath } from '$lib/utils';
import T from '$lib/theme.svelte';

const isPath = (path: string) => $page.url.pathname === path;
</script>
const isSubPath = (subPath: SUB_PATH) => $page.url.pathname.endsWith(subPath);

{#snippet headline(clazz)}
<h1 class={`flex-1 justify-center text-4xl uppercase text-gray-700 dark:text-gray-300 ${clazz}`}>
<a
class="mr-2 font-bold text-gray-600 hover:text-gray-800 dark:text-gray-200 dark:hover:text-gray-50"
href="https://github.com/bitcoin/bips/blob/master/bip-0039/bip-0039-wordlists.md">BIP-39</a
> word list
</h1>
{/snippet}
const isBip39 = $derived(store.wordlistType === 'bip39');
const isSlip39 = $derived(store.wordlistType === 'slip39');

const langLabel = $derived.by(() => {
const l = store.languages.length;
return `${l} language${l > 1 ? 's' : ''}`;
});
</script>

<header class="flex w-full items-center justify-center md:flex-row md:justify-normal">
<div class="flex gap-2 text-gray-600 dark:text-gray-300">
<div class="flex w-full gap-2 text-gray-600 dark:text-gray-300">
<a
href={P.HOME}
class="rounded-md p-2 hover:bg-gray-100 hover:text-gray-800 dark:hover:bg-gray-800 dark:hover:text-gray-50 {isPath(
P.HOME
href={getFullPath(store.wordlistType, SUB_PATH.HOME)}
class="rounded-md p-2 hover:bg-gray-100 hover:text-gray-800 dark:hover:bg-gray-800 dark:hover:text-gray-50 {isSubPath(
SUB_PATH.HOME
)
? 'cursor-default !bg-gray-200 text-gray-900 dark:!bg-gray-600 dark:text-gray-100'
: ''}"
title="typewriter"
>
<Repeat2 strokeWidth={isPath(P.HOME) ? 1.2 : 1} />
<Repeat2 strokeWidth={isSubPath(SUB_PATH.HOME) ? 1.2 : 1} />
</a>
<a
href={P.GRID}
class="rounded-md p-2 hover:bg-gray-100 hover:text-gray-800 dark:hover:bg-gray-800 dark:hover:text-gray-50 {isPath(
P.GRID
href={getFullPath(store.wordlistType, SUB_PATH.GRID)}
class="rounded-md p-2 hover:bg-gray-100 hover:text-gray-800 dark:hover:bg-gray-800 dark:hover:text-gray-50 {isSubPath(
SUB_PATH.GRID
)
? 'cursor-default !bg-gray-200 text-gray-900 dark:!bg-gray-600 dark:text-gray-100'
: ''}"
title="grid view"
>
<LayoutGrid strokeWidth={isPath(P.GRID) ? 1.2 : 1} />
<LayoutGrid strokeWidth={isSubPath(SUB_PATH.GRID) ? 1.2 : 1} />
</a>
<a
href={P.LIST}
class="rounded-md p-2 hover:bg-gray-100 hover:text-gray-800 dark:hover:bg-gray-800 dark:hover:text-gray-100 {isPath(
P.LIST
href={getFullPath(store.wordlistType, SUB_PATH.LIST)}
class="rounded-md p-2 hover:bg-gray-100 hover:text-gray-800 dark:hover:bg-gray-800 dark:hover:text-gray-100 {isSubPath(
SUB_PATH.LIST
)
? 'cursor-default !bg-gray-200 text-gray-900 dark:!bg-gray-600 dark:text-gray-100'
: ''}"
title="list view"
>
<AlignJustify strokeWidth={isPath(P.LIST) ? 1.2 : 1} />
<AlignJustify strokeWidth={isSubPath(SUB_PATH.LIST) ? 1.2 : 1} />
</a>
</div>

{@render headline('hidden md:flex')}

<div
class="ml-2 flex-none cursor-pointer rounded-md p-2 text-gray-600 hover:bg-gray-100 hover:text-gray-800 dark:text-gray-300 dark:hover:bg-gray-800 dark:hover:text-gray-100 md:ml-20"
>
Expand All @@ -66,17 +65,39 @@
{/if}
</div>
</header>
<div class="mt-5 flex flex-col items-center justify-center gap-1 text-gray-500 dark:text-gray-400">
{@render headline('flex md:hidden pt-10')}
<div
class="mt-5 flex flex-col items-center justify-center gap-1 pt-10 text-gray-500 dark:text-gray-400"
>
<h1
class={`mb-10 flex justify-center text-3xl uppercase text-gray-700 dark:text-gray-300 md:text-4xl`}
>
<a
class="ease mr-4 border-b-8 border-transparent font-bold text-gray-400 hover:border-gray-800 hover:text-gray-800 dark:text-gray-400 dark:hover:border-gray-50 dark:hover:text-gray-50 {isBip39
? ' !border-gray-800 text-gray-600 dark:!border-gray-50 dark:text-gray-50'
: ''}"
class:dark:text-gray-100={isBip39}
class:border-gray-800={isBip39}
class:dark:border-gray-100={isBip39}
class:border-gray-600={isBip39}
href={$page.url.pathname.replace('slip39', 'bip39')}>BIP-39</a
>

<a
class="ease ml-4 border-b-8 border-transparent font-bold text-gray-400 hover:border-gray-800 hover:text-gray-800 dark:text-gray-400 dark:hover:border-gray-50 dark:hover:text-gray-50 {isSlip39
? ' !border-gray-800 text-gray-600 dark:!border-gray-50 dark:text-gray-50'
: ''}"
href={$page.url.pathname.replace('bip39', 'slip39')}>SLIP-39</a
>
</h1>
<p class="text-xs uppercase">
{languages.length} languages
{langLabel}
</p>
<select
class="select select-ghost btn-sm select-xs bg-gray-200 text-gray-600 dark:bg-gray-600 dark:text-gray-400"
Event & { currentTarget: HTMLSelectElement }) =>
(store.selectedLang = e.currentTarget.value as LANG)}
>
{#each languages as l (l)}
{#each store.languages as l (l)}
<option class="text-gray-600 dark:text-gray-400" selected={store.selectedLang === l} value={l}
>{l.toUpperCase()}</option
>
Expand Down
6 changes: 6 additions & 0 deletions src/lib/const.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,11 @@
import type { LANG, WordListType } from './types';

export enum SUB_PATH {
HOME = '/',
LIST = '/list',
GRID = '/grid'
}

const bip39Url = (file: string) => `https://github.com/bitcoin/bips/blob/master/bip-0039/${file}`;

type WordListUrls = Record<LANG, string>;
Expand Down
102 changes: 77 additions & 25 deletions src/lib/store.svelte.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,10 @@
import { pipe } from 'effect';
import { Effect, pipe } from 'effect';
import * as O from 'effect/Option';
import * as A from 'effect/Array';
import { validPosition } from '$lib/utils';
import type { LANG, WordList } from './types';
import * as R from 'effect/Record';

import { getLocalStorage, setLocalStorage, validPosition } from '$lib/utils';
import { WordListTypeSchema, type LANG, type WordList, type WordListType } from './types';
import bip39en from './wordlists/bip39-en';
import bip39cz from './wordlists/bip39-cz';
import bip39zhHans from './wordlists/bip39-zh-Hans';
Expand All @@ -13,41 +15,91 @@ import bip39jp from './wordlists/bip39-jp';
import bip39kr from './wordlists/bip39-kr';
import bip39pt from './wordlists/bip39-pt';
import bip39es from './wordlists/bip39-es';
import slip39es from './wordlists/slip39-en';

export enum PATHS {
HOME = '/',
LIST = '/list',
GRID = '/grid'
}

// bip30 wordlists
// https://github.com/bitcoinjs/bip39/tree/master/src/wordlists
const WORDLISTS: Record<LANG, string[]> = {
en: bip39en,
cz: bip39cz,
'zh-Hans': bip39zhHans,
'zh-Hant': bip39zhHant,
fr: bip39fr,
it: bip39it,
jp: bip39jp,
kr: bip39kr,
pt: bip39pt,
es: bip39es
const WORD_LIST_MAP: Record<WordListType, Record<LANG, O.Option<string[]>>> = {
// https://github.com/bitcoinjs/bip39/tree/master/src/wordlists
bip39: {
en: O.some(bip39en),
cz: O.some(bip39cz),
'zh-Hans': O.some(bip39zhHans),
'zh-Hant': O.some(bip39zhHant),
fr: O.some(bip39fr),
it: O.some(bip39it),
jp: O.some(bip39jp),
kr: O.some(bip39kr),
pt: O.some(bip39pt),
es: O.some(bip39es)
},
slip39: {
en: O.some(slip39es),
cz: O.none(),
'zh-Hans': O.none(),
'zh-Hant': O.none(),
fr: O.none(),
it: O.none(),
jp: O.none(),
kr: O.none(),
pt: O.none(),
es: O.none()
}
};

const KEY_WORD_LIST_TYPE = 'type';
const DEFAULT_WORD_LIST_TYPE: WordListType = 'bip39';

class Store {
selectedLang = $state<LANG>('en');

// @private
#wordlistType = $state<WordListType>(DEFAULT_WORD_LIST_TYPE);

constructor() {
// check stored `WordlistType` at start
this.checkWordlistType();
}

// getter
wordlistType = $derived(this.#wordlistType);

setWordlistType = (t: WordListType) =>
pipe(
setLocalStorage(t, KEY_WORD_LIST_TYPE, WordListTypeSchema),
Effect.tap(() => (this.#wordlistType = t)),
Effect.runSync
);

checkWordlistType = () =>
pipe(
getLocalStorage(KEY_WORD_LIST_TYPE, WordListTypeSchema),
Effect.orElse(() => Effect.succeed(DEFAULT_WORD_LIST_TYPE)),
Effect.tap((t) => (this.#wordlistType = t)),
Effect.runSync
);

randomize = $state(false);
// @private
#wordlist: WordList = $derived.by(() =>
pipe(
WORDLISTS[this.selectedLang],
A.map((word, i) => ({ pos: i + 1, word }))
WORD_LIST_MAP[this.#wordlistType][this.selectedLang],
// transform `string` -> `WordListItem`
O.map(A.map((word, i) => ({ pos: i + 1, word }))),
O.getOrElse(() => [])
)
);
// Make wordlist readable only
// getter
wordlist = $derived(this.#wordlist);

languages = $derived.by(() =>
pipe(
WORD_LIST_MAP[this.#wordlistType],
// filter out `none` values
R.getSomes,
// get languages (keys)
R.keys
)
);

filter = $state<O.Option<string>>(O.none());
wordlistFiltered = $derived.by(() =>
pipe(
Expand Down
10 changes: 9 additions & 1 deletion src/lib/types.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
import * as S from '@effect/schema/Schema';

export const languages = [
'en',
'fr',
Expand All @@ -13,7 +15,13 @@ export const languages = [

export type LANG = (typeof languages)[number];

export type WordListType = 'bip39' | 'slip39';
// Schema for routes (`string`)
export const WordListTypeLiteralSchema = S.Literal('bip39', 'slip39');
// Schema for LocalStorage (JSON)
export const WordListTypeSchema = S.parseJson(S.Literal('bip39', 'slip39'));

export type WordListType = typeof WordListTypeSchema.Type;
export type WordListTypencoded = typeof WordListTypeSchema.Encoded;

export type WordListItem = {
pos: number;
Expand Down
5 changes: 4 additions & 1 deletion src/lib/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,8 @@ import * as A from 'effect/Array';
import * as SC from '@effect/schema/Schema';
import * as KeyValueStore from '@effect/platform/KeyValueStore';
import { BrowserKeyValueStore } from '@effect/platform-browser';
import type { WordList, WordListItem } from './types';
import type { WordList, WordListItem, WordListType } from './types';
import type { SUB_PATH } from './const';

export const validPosition: (n: string) => O.Option<number> = flow(
N.parse,
Expand Down Expand Up @@ -46,3 +47,5 @@ export const setLocalStorage = <A>(value: A, key: string, schema: SC.Schema<A, s
);
return Effect.provide(effect, BrowserKeyValueStore.layerLocalStorage);
};

export const getFullPath = (type: WordListType, subPath: SUB_PATH) => `/${type}${subPath}`;
20 changes: 19 additions & 1 deletion src/routes/+layout.svelte
Original file line number Diff line number Diff line change
@@ -1,9 +1,27 @@
<script>
<script lang="ts">
import { onNavigate } from '$app/navigation';
import { WordListTypeLiteralSchema } from '$lib/types';
import Footer from '../Footer.svelte';
import Header from '../Header.svelte';
import store from '$lib/store.svelte';
import '../app.css';
import * as SC from '@effect/schema/Schema';

let { children } = $props();

// Handle changes of `slug` (`WordListType`)
onNavigate(async (nav) => {
const slugTo = nav?.to?.params?.['slug'];
const slugFrom = nav?.from?.params?.['slug'];

if (slugTo !== slugFrom) {
// Supported numbers of languages are different for different `WordListType` -> switch always back to `en`
store.selectedLang = 'en';
// (unsafe) transform `slugTo` to `WordListType`
const slug = SC.decodeUnknownSync(WordListTypeLiteralSchema)(slugTo);
store.setWordlistType(slug);
}
});
</script>

<div class="container mx-auto flex min-h-screen flex-col px-4 pt-4 text-gray-600">
Expand Down
Loading
Loading
0