diff --git a/README.md b/README.md index 4576ded..9f78ac4 100644 --- a/README.md +++ b/README.md @@ -1,13 +1,305 @@ -# Example Extension - [LNbits](https://github.com/lnbits/lnbits) extension +# SiLNt — Silent Payments Wallet Extension for LNbits -For more about LNBits extension check [this tutorial](https://github.com/lnbits/lnbits/wiki/LNbits-Extensions) +A [LNbits](https://lnbits.com) extension for managing [Silent Payment](https://silentpayments.xyz) Bitcoin wallets, with blockchain scanning powered by a self-hosted [BlindBit Oracle](https://github.com/ponthief/blindbit-oracle). -
+## Features
-curl -H "Content-type: application/json" -X POST https://YOUR-LNBITS/YOUR-EXTENSION/api/v1/EXAMPLE -d '{"amount":"100","memo":"example"}' -H "X-Api-Key: YOUR_WALLET-ADMIN/INVOICE-KEY"
+---
+
+## Requirements
+
+- LNbits instance (self-hosted)
+- Python dependencies: `embit`, `httpx`, `coincurve`, `cryptography`, `dnspython`, `ecdsa`
+- A running [blindbit-oracle](https://github.com/ponthief/blindbit-oracle) instance for blockchain scanning
+
+---
+
+## Installation
+
+1. As Admin user, navigate to **Settings → Extensions** and add Source:
+ [Ponthief-Extensions](https://raw.githubusercontent.com/ponthief/lnbits-extensions/extensions/extensions.json)
+2. Install/Enable the extension from the LNbits admin panel under **Extensions**.
+3. Database migrations run automatically on first load.
+
+---
+
+## Configuration
+
+### BlindBit Oracle Connection
+
+Before scanning, an admin must configure the BlindBit Oracle connection via the **Settings** button (⚙️) in the extension UI, or via the API:
+
+```bash
+curl -X PUT https://Thrilla is a self-custodial friends-and-family wallet built on LNbits. Inside your circle you transact with collaborative PayJoin; to the outside world you receive with maximum-privacy Silent Payments and share a human-readable BitMail name. Powered by a BlindBit Oracle and a Fulcrum electrum server — your keys stay on your device and never reach the server.
+ +Thrilla is designed for a known circle — friends, family, a small community — sharing one LNbits instance. Within that circle there's an expected level of trust, so members can transact collaboratively via PayJoin: two people co-sign a single transaction that improves privacy and consolidates coins, without a middleman.
+Toward the outside world, you present your Silent Payment address — reusable, unlinkable, and safe to publish — so the public ledger never becomes a map of who pays you. Friendly trust on the inside, maximum privacy on the outside.
+Your keys are your responsibility. Thrilla is self-custodial: every member generates and holds their own keys on their own device. No one — not other members, not the server operator — can move your Silent Payment funds for you, and no one can recover them if you lose your backup.
+Traditional Bitcoin forces a trade-off: reuse one address and leak your whole history, or generate endless new ones and drown in bookkeeping. Silent Payments end that compromise — and collaborative PayJoin lets your circle transact together with better privacy.
+Publish a single static address. Senders derive a unique on-chain output for each payment using shared-secret cryptography, so nothing on the blockchain links your payments together — yet only you can find and spend them.
+ Reusable & unlinkable +Pay another member of your instance with a transaction you both contribute inputs to. This breaks the common "all inputs share one owner" assumption that chain-analysis relies on, and can consolidate UTXOs at the same time. Designed for the trusted circle — both sides co-sign one transaction, no third party in between.
+ Within your circle +Long payment codes are hard to share. BitMail maps a friendly name@domain to your Silent Payment address over DNS, so you can hand someone alice@mydomain.net instead of a 116-character string.
+ DNS-backed +Each account will include a built-in Lightning wallet for instant, low-fee spending — the natural pairing with Silent Payments for private receiving. Arriving when Thrilla launches on mainnet; the current Signet release focuses on Silent Payments and PayJoin.
+ Coming at mainnet launch +Move Silent Payment funds into Lightning without an exchange, via non-custodial submarine swaps through Boltz. This will become available once Thrilla launches on mainnet alongside the Lightning wallet.
+ Coming at mainnet launch +One deployment serves a circle of independent users, each with their own wallet, addresses, and device trust. Thrilla rides on LNbits' account model, so onboarding a new person is just another login.
+ One instance, many wallets +Thrilla ties each wallet to securely authenticated devices. Creating an account is what makes that possible — and it only takes a moment.
+To use Thrilla you must create an account. This is what lets the app securely authenticate your device: each new device is confirmed via an email link before it can access a wallet, so a password alone is never enough to reach your funds from an unknown machine.
+The confirmation email comes from . If you don't see it within a minute, check your spam or junk folder and mark it as "not spam" so future messages arrive in your inbox.
+Signet coins have no monetary value — they exist purely for testing. Grab some from a faucet and send your first Silent Payment in minutes.
+Sign up, then click the confirmation link emailed from (check spam if needed). Generate a Silent Payment wallet and copy your sp1q… address.
+Paste your address into the faucet and request test coins. They'll arrive on-chain shortly — scan your wallet to detect them.
+ + Open the Signet faucet → + +Try a payment, claim a BitMail name, do a collaborative PayJoin with another member, and watch unlinkable outputs land — all risk-free with worthless coins.
+Create an account, generate a Silent Payment address, claim a BitMail name, and try a collaborative PayJoin with a friend — all with worthless test coins. No risk, no real funds.
+ Open signet.thrilla.me → + + +Every Bitcoin transaction is permanent and public. The way you receive funds decides whether that ledger becomes a map of your financial life — or stays just noise to everyone but you.
+Share one static code for years. Each sender derives a different on-chain destination, so reuse of the public address never becomes reuse on the chain.
+Two payments to the same Silent Payment address produce outputs that look entirely unrelated to any third party — there's no common key or address joining them.
+When members pay each other, both contribute inputs to one transaction. That defeats the "single owner of all inputs" heuristic chain-analysis leans on — privacy that comes from cooperation, not just cryptography.
+You don't have to interact with the sender or hand out a new address each time. No address-request dance, no leaking which invoice belongs to whom.
+Put your address — or your BitMail name — on a profile, an invoice, or a donation page. Public exposure no longer means a public spending history.
+Clustering heuristics rely on address reuse and linkable, single-owner inputs. Silent Payments and PayJoin remove both signals, breaking the usual surveillance assumptions.
+Thrilla is the interface. The cryptography, scanning, and collaborative transactions are handled by open components underneath it.
+A fast web app that builds and signs Silent Payment and PayJoin transactions locally, manages your BitMail address, and shows balances and activity. All signing happens in your browser; the server only ever sees what it strictly needs to.
+Thrilla runs as an LNbits extension. LNbits provides the multi-user account system, authentication, and device confirmation, letting a single self-hosted instance serve a whole circle securely and independently. (The built-in Lightning wallet activates at mainnet launch.)
+Finding your Silent Payment outputs means scanning the chain with your scan key. A BlindBit Oracle precomputes the tweak and UTXO indexes so your wallet can detect incoming payments quickly, and a Fulcrum electrum server supplies fast address and transaction lookups — all without your spend key ever leaving your device.
+Once Thrilla launches on mainnet, swaps will talk directly to a Boltz submarine-swap backend: you fund a lockup address from your SP wallet, Boltz pays your Lightning invoice, and the swap stays non-custodial — a failed swap is refundable on-chain with a key only your wallet holds.
+Self-custody isn't a slogan here — it's enforced by where the secrets physically live. Thrilla is built so that a full server compromise still can't move your Silent Payment coins. Those keys are yours to hold and yours to back up.
+Your mnemonic and the keys derived from it are generated and held client-side, encrypted in your own browser. The server derives keys transiently during setup and discards them — there is no seed column in the database to leak. Backing up your seed is your responsibility.
+Using Thrilla requires creating an account. New devices must then be explicitly confirmed via an email link (sent from — check spam) before they can access a wallet, so a stolen password alone can't reach your funds from an unknown machine.
+Transactions — including collaborative PayJoins — are constructed and signed in your browser using keys that never travel over the wire. The backend only broadcasts the finished, signed transaction — it can't author one on your behalf.
+A PayJoin between members is co-signed by both parties on a single transaction. Each side signs locally with their own keys; neither the other member nor the server can spend on your behalf.
+Run your own LNbits, BlindBit Oracle, and Fulcrum server (and, at mainnet, a Boltz backend) and the entire stack is yours — no third party sits between you and the chain, and no analytics company profiles your spending.
+Thrilla, LNbits, BlindBit, and Fulcrum are open and inspectable. The cryptography follows the open Silent Payments standard rather than a proprietary scheme you'd have to take on trust.
+${keysJson}`,
+ ok: {
+ label: 'I have saved them',
+ color: 'positive',
+ },
+ persistent: true,
+ })
+
+ Quasar.copyToClipboard(keysJson)
+ this.$q.notify({
+ type: 'positive',
+ message: 'Wallet keys copied to clipboard. Save them somewhere safe.',
+ timeout: 10000,
+ })
+ } else {
+ this.$q.notify({
+ type: 'positive',
+ message: 'Silent Payment Wallet created.',
+ timeout: 5000,
+ })
+ }
+ } catch (error) {
+ LNbits.utils.notifyApiError(error)
+ }
+ },
+ updateWalletDialog(walletAccountId) {
+ var wallet = _.findWhere(this.walletAccounts, {id: walletAccountId})
+ this.updateDialog.data = _.clone(wallet)
+ this.updateDialog.hr_address = this.updateDialog.data.hr_address
+ this.updateDialog.last_height = this.updateDialog.data.last_height
+ this.updateDialog.title = this.updateDialog.data.title
+ this.updateDialog.show = true
+ this.bip353Valid = true
+ },
+ updateWalletDetails: async function (data) {
+ try {
+ const response = await LNbits.api.request(
+ 'PUT',
+ '/siLNt/api/v1/wallet/' + data.id,
+ this.inkey,
+ data
+ )
+ this.walletAccounts.push(mapWalletAccount(response.data))
+ this.updateDialog.show = false
+ await this.refreshWalletAccounts()
+ Quasar.Notify.create({
+ type: 'positive',
+ message: 'Wallet updated.'
+ })
+ } catch (error) {
+ LNbits.utils.notifyApiError(error)
+ }
+ },
+ deleteWalletDialog: function (walletAccountId) {
+ LNbits.utils
+ .confirmDialog(
+ 'Are you sure you want to delete this wallet?'
+ )
+ .onOk(async () => {
+ try {
+ await LNbits.api.request(
+ 'DELETE',
+ '/siLNt/api/v1/wallet/' + walletAccountId,
+ this.inkey
+ )
+ this.walletAccounts = _.reject(this.walletAccounts, function (obj) {
+ return obj.id === walletAccountId
+ })
+ await this.refreshWalletAccounts()
+ this.$emit('clear-utxos',walletAccountId)
+ } catch (error) {
+ this.$q.notify({
+ type: 'warning',
+ message: 'Error while deleting wallet account. Please try again.',
+ timeout: 10000
+ })
+ }
+ })
+ },
+ getsiLNtWallets: async function () {
+ try {
+ const {data} = await LNbits.api.request(
+ 'GET',
+ `/siLNt/api/v1/wallet`,
+ this.inkey
+ )
+ return data
+ } catch (error) {
+ this.$q.notify({
+ type: 'warning',
+ message: 'Failed to fetch wallets.',
+ timeout: 10000
+ })
+ LNbits.utils.notifyApiError(error)
+ }
+ return []
+ },
+ refreshWalletAccounts: async function () {
+ this.walletAccounts = []
+ const wallets = await this.getsiLNtWallets()
+ // Load addresses for each wallet in parallel
+ const walletsWithAddresses = await Promise.all(
+ wallets.map(async w => {
+ let addresses = []
+ try {
+ const {data} = await LNbits.api.request(
+ 'GET',
+ `/siLNt/api/v1/wallet/${w.id}/addresses`,
+ this.inkey
+ )
+ addresses = data.addresses || []
+ } catch (e) {
+ // ignore — wallet still loads without addresses
+ }
+ return {
+ ...mapWalletAccount(w),
+ expanded: false,
+ addresses: addresses,
+ loadingAddresses: false
+ }
+ })
+ )
+ this.walletAccounts = walletsWithAddresses
+ this.$emit('accounts-update', this.walletAccounts)
+ },
+ updateWalletBalance: async function (walletId, balance) {
+ try {
+ await LNbits.api.request(
+ 'PUT',
+ `/siLNt/api/v1/wallet/${walletId}`,
+ this.inkey,
+ { balance }
+ )
+ } catch (error) {
+ LNbits.utils.notifyApiError(error)
+ }
+ },
+ validateBip353: async function (address) {
+ // Basic email format check first
+ const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/
+ if (!emailRegex.test(address)) {
+ this.$q.notify({
+ type: 'warning',
+ message: 'BIP353 Address must be in email format (e.g. alice@domain.com)',
+ timeout: 8000
+ })
+ // this.bip353Valid = false
+ return false
+ }
+ try {
+ await LNbits.api.request(
+ 'GET',
+ `/siLNt/api/v1/bip353/resolve?address=${encodeURIComponent(address)}`,
+ this.inkey
+ )
+ this.$q.notify({
+ type: 'positive',
+ message: `BIP353 address verified: ${address}`,
+ timeout: 5000
+ })
+ this.bip353Valid = true
+ return true
+ } catch (error) {
+ this.$q.notify({
+ type: 'negative',
+ message: `BIP353 resolution failed for ${address} — check the address and try again`,
+ timeout: 8000
+ })
+ // this.bip353Valid = false
+ return false
+ }
+ },
+ toggleAddresses: async function (wallet) {
+ wallet.expanded = !wallet.expanded
+ if (wallet.expanded && !wallet.addresses.length && !wallet.loadingAddresses) {
+ wallet.loadingAddresses = true
+ try {
+ const {data} = await LNbits.api.request(
+ 'GET',
+ `/siLNt/api/v1/wallet/${wallet.id}/addresses`,
+ this.inkey
+ )
+ wallet.addresses = data.addresses || []
+ } catch (error) {
+ LNbits.utils.notifyApiError(error)
+ } finally {
+ wallet.loadingAddresses = false
+ }
+ }
+ },
+ generateAddress: async function (wallet) {
+ this.$q.notify({
+ type: 'info',
+ message: 'Open this wallet in the Thrilla app to generate a labeled address.',
+ caption: 'LNbits doesn\'t store wallet keys for security reasons.',
+ timeout: 7000,
+ actions: [{
+ label: 'Open Thrilla',
+ color: 'white',
+ handler: () => window.open(this.thrillaUrl, '_blank'),
+ }],
+ })
+ },
+
+ saveAddress: async function (wallet, addr) {
+ const rw = this.walletAccounts.find(w => w.id === wallet.id)
+ if (!rw) return
+ try {
+ const {data} = await LNbits.api.request('POST', `/siLNt/api/v1/wallet/${rw.id}/addresses`, this.inkey, {
+ sp_address: addr.sp_address,
+ label_index: addr.label_index
+ })
+ const idx = rw.addresses.findIndex(a => a._tempId === addr._tempId)
+ if (idx !== -1) rw.addresses.splice(idx, 1, { ...data, saved: true })
+ this.$emit('addresses-changed')
+ Quasar.Notify.create({ type: 'positive', message: 'Address saved.' })
+ } catch (error) {
+ LNbits.utils.notifyApiError(error)
+ }
+ },
+
+ deleteAddress: async function (wallet, addr) {
+ const rw = this.walletAccounts.find(w => w.id === wallet.id)
+ if (!rw) return
+ if (!addr.id) {
+ rw.addresses = rw.addresses.filter(a => a._tempId !== addr._tempId)
+ return
+ }
+ LNbits.utils.confirmDialog('Delete this labeled address?').onOk(async () => {
+ try {
+ await LNbits.api.request('DELETE', `/siLNt/api/v1/wallet/${rw.id}/addresses/${addr.id}`, this.inkey)
+ rw.addresses = rw.addresses.filter(a => a.id !== addr.id)
+ Quasar.Notify.create({ type: 'positive', message: 'Address deleted.' })
+ } catch (error) {
+ LNbits.utils.notifyApiError(error)
+ }
+ })
+ },
+ created: async function () {
+ if (this.inkey) {
+ await this.refreshWalletAccounts()
+ }
+ },
+ showQrCodeAddress: function (address) {
+ this.qrDialog.address = address
+ this.qrDialog.show = true
+ },
+ closeFormDialog: function () {
+ this.formDialog.data = {
+ hr_address: '', // ← empty string not null
+ last_height: '',
+ mnemonic: '',
+ network: 'mainnet',
+ is_unique: false
+ }
+ },
+ closeUpdateDialog: function () {
+ this.updateDialog.data = {
+ is_unique: false
+ }
+ },
+ getAccountDescription: function (accountType) {
+ return getAccountDescription(accountType)
+ },
+
+ showAddAccountDialog: function () {
+ this.formDialog.data = {
+ hr_address: '',
+ last_height: '',
+ mnemonic: '',
+ network: 'mainnet'
+ }
+ this.formDialog.show = true
+ },
+ showUpdateWalletDialog: function () {
+ this.updateDialog.show = true
+ },
+ // todo: bad. base.js not present in custom components
+ copyText: function (text, message, position) {
+ var notify = this.$q.notify
+ Quasar.copyToClipboard(text).then(function () {
+ notify({
+ message: message || 'Copied to clipboard!',
+ position: position || 'bottom'
+ })
+ })
+ },
+ showQrCode: function (wallet) {
+ this.qrDialog.address = wallet.sp_address || ''
+ this.qrDialog.show = true
+ },
+ },
+ created: async function () {
+ if (this.inkey) {
+ await this.refreshWalletAccounts()
+ }
+ }
+})
diff --git a/static/conversion-example.png b/static/conversion-example.png
deleted file mode 100644
index 544e633..0000000
Binary files a/static/conversion-example.png and /dev/null differ
diff --git a/static/conversion-example2.png b/static/conversion-example2.png
deleted file mode 100644
index 7543fdc..0000000
Binary files a/static/conversion-example2.png and /dev/null differ
diff --git a/static/fastapi-example.png b/static/fastapi-example.png
deleted file mode 100644
index ed309bb..0000000
Binary files a/static/fastapi-example.png and /dev/null differ
diff --git a/static/fastapi-example2.png b/static/fastapi-example2.png
deleted file mode 100644
index ceacf49..0000000
Binary files a/static/fastapi-example2.png and /dev/null differ
diff --git a/static/fastapi-framework.png b/static/fastapi-framework.png
deleted file mode 100644
index b446ce2..0000000
Binary files a/static/fastapi-framework.png and /dev/null differ
diff --git a/static/fastapilogo.png b/static/fastapilogo.png
deleted file mode 100644
index 57eb6a8..0000000
Binary files a/static/fastapilogo.png and /dev/null differ
diff --git a/static/js/bip39-word-list.js b/static/js/bip39-word-list.js
new file mode 100644
index 0000000..c0a5eac
--- /dev/null
+++ b/static/js/bip39-word-list.js
@@ -0,0 +1,2050 @@
+const bip39WordList = Object.freeze([
+ 'abandon',
+ 'ability',
+ 'able',
+ 'about',
+ 'above',
+ 'absent',
+ 'absorb',
+ 'abstract',
+ 'absurd',
+ 'abuse',
+ 'access',
+ 'accident',
+ 'account',
+ 'accuse',
+ 'achieve',
+ 'acid',
+ 'acoustic',
+ 'acquire',
+ 'across',
+ 'act',
+ 'action',
+ 'actor',
+ 'actress',
+ 'actual',
+ 'adapt',
+ 'add',
+ 'addict',
+ 'address',
+ 'adjust',
+ 'admit',
+ 'adult',
+ 'advance',
+ 'advice',
+ 'aerobic',
+ 'affair',
+ 'afford',
+ 'afraid',
+ 'again',
+ 'age',
+ 'agent',
+ 'agree',
+ 'ahead',
+ 'aim',
+ 'air',
+ 'airport',
+ 'aisle',
+ 'alarm',
+ 'album',
+ 'alcohol',
+ 'alert',
+ 'alien',
+ 'all',
+ 'alley',
+ 'allow',
+ 'almost',
+ 'alone',
+ 'alpha',
+ 'already',
+ 'also',
+ 'alter',
+ 'always',
+ 'amateur',
+ 'amazing',
+ 'among',
+ 'amount',
+ 'amused',
+ 'analyst',
+ 'anchor',
+ 'ancient',
+ 'anger',
+ 'angle',
+ 'angry',
+ 'animal',
+ 'ankle',
+ 'announce',
+ 'annual',
+ 'another',
+ 'answer',
+ 'antenna',
+ 'antique',
+ 'anxiety',
+ 'any',
+ 'apart',
+ 'apology',
+ 'appear',
+ 'apple',
+ 'approve',
+ 'april',
+ 'arch',
+ 'arctic',
+ 'area',
+ 'arena',
+ 'argue',
+ 'arm',
+ 'armed',
+ 'armor',
+ 'army',
+ 'around',
+ 'arrange',
+ 'arrest',
+ 'arrive',
+ 'arrow',
+ 'art',
+ 'artefact',
+ 'artist',
+ 'artwork',
+ 'ask',
+ 'aspect',
+ 'assault',
+ 'asset',
+ 'assist',
+ 'assume',
+ 'asthma',
+ 'athlete',
+ 'atom',
+ 'attack',
+ 'attend',
+ 'attitude',
+ 'attract',
+ 'auction',
+ 'audit',
+ 'august',
+ 'aunt',
+ 'author',
+ 'auto',
+ 'autumn',
+ 'average',
+ 'avocado',
+ 'avoid',
+ 'awake',
+ 'aware',
+ 'away',
+ 'awesome',
+ 'awful',
+ 'awkward',
+ 'axis',
+ 'baby',
+ 'bachelor',
+ 'bacon',
+ 'badge',
+ 'bag',
+ 'balance',
+ 'balcony',
+ 'ball',
+ 'bamboo',
+ 'banana',
+ 'banner',
+ 'bar',
+ 'barely',
+ 'bargain',
+ 'barrel',
+ 'base',
+ 'basic',
+ 'basket',
+ 'battle',
+ 'beach',
+ 'bean',
+ 'beauty',
+ 'because',
+ 'become',
+ 'beef',
+ 'before',
+ 'begin',
+ 'behave',
+ 'behind',
+ 'believe',
+ 'below',
+ 'belt',
+ 'bench',
+ 'benefit',
+ 'best',
+ 'betray',
+ 'better',
+ 'between',
+ 'beyond',
+ 'bicycle',
+ 'bid',
+ 'bike',
+ 'bind',
+ 'biology',
+ 'bird',
+ 'birth',
+ 'bitter',
+ 'black',
+ 'blade',
+ 'blame',
+ 'blanket',
+ 'blast',
+ 'bleak',
+ 'bless',
+ 'blind',
+ 'blood',
+ 'blossom',
+ 'blouse',
+ 'blue',
+ 'blur',
+ 'blush',
+ 'board',
+ 'boat',
+ 'body',
+ 'boil',
+ 'bomb',
+ 'bone',
+ 'bonus',
+ 'book',
+ 'boost',
+ 'border',
+ 'boring',
+ 'borrow',
+ 'boss',
+ 'bottom',
+ 'bounce',
+ 'box',
+ 'boy',
+ 'bracket',
+ 'brain',
+ 'brand',
+ 'brass',
+ 'brave',
+ 'bread',
+ 'breeze',
+ 'brick',
+ 'bridge',
+ 'brief',
+ 'bright',
+ 'bring',
+ 'brisk',
+ 'broccoli',
+ 'broken',
+ 'bronze',
+ 'broom',
+ 'brother',
+ 'brown',
+ 'brush',
+ 'bubble',
+ 'buddy',
+ 'budget',
+ 'buffalo',
+ 'build',
+ 'bulb',
+ 'bulk',
+ 'bullet',
+ 'bundle',
+ 'bunker',
+ 'burden',
+ 'burger',
+ 'burst',
+ 'bus',
+ 'business',
+ 'busy',
+ 'butter',
+ 'buyer',
+ 'buzz',
+ 'cabbage',
+ 'cabin',
+ 'cable',
+ 'cactus',
+ 'cage',
+ 'cake',
+ 'call',
+ 'calm',
+ 'camera',
+ 'camp',
+ 'can',
+ 'canal',
+ 'cancel',
+ 'candy',
+ 'cannon',
+ 'canoe',
+ 'canvas',
+ 'canyon',
+ 'capable',
+ 'capital',
+ 'captain',
+ 'car',
+ 'carbon',
+ 'card',
+ 'cargo',
+ 'carpet',
+ 'carry',
+ 'cart',
+ 'case',
+ 'cash',
+ 'casino',
+ 'castle',
+ 'casual',
+ 'cat',
+ 'catalog',
+ 'catch',
+ 'category',
+ 'cattle',
+ 'caught',
+ 'cause',
+ 'caution',
+ 'cave',
+ 'ceiling',
+ 'celery',
+ 'cement',
+ 'census',
+ 'century',
+ 'cereal',
+ 'certain',
+ 'chair',
+ 'chalk',
+ 'champion',
+ 'change',
+ 'chaos',
+ 'chapter',
+ 'charge',
+ 'chase',
+ 'chat',
+ 'cheap',
+ 'check',
+ 'cheese',
+ 'chef',
+ 'cherry',
+ 'chest',
+ 'chicken',
+ 'chief',
+ 'child',
+ 'chimney',
+ 'choice',
+ 'choose',
+ 'chronic',
+ 'chuckle',
+ 'chunk',
+ 'churn',
+ 'cigar',
+ 'cinnamon',
+ 'circle',
+ 'citizen',
+ 'city',
+ 'civil',
+ 'claim',
+ 'clap',
+ 'clarify',
+ 'claw',
+ 'clay',
+ 'clean',
+ 'clerk',
+ 'clever',
+ 'click',
+ 'client',
+ 'cliff',
+ 'climb',
+ 'clinic',
+ 'clip',
+ 'clock',
+ 'clog',
+ 'close',
+ 'cloth',
+ 'cloud',
+ 'clown',
+ 'club',
+ 'clump',
+ 'cluster',
+ 'clutch',
+ 'coach',
+ 'coast',
+ 'coconut',
+ 'code',
+ 'coffee',
+ 'coil',
+ 'coin',
+ 'collect',
+ 'color',
+ 'column',
+ 'combine',
+ 'come',
+ 'comfort',
+ 'comic',
+ 'common',
+ 'company',
+ 'concert',
+ 'conduct',
+ 'confirm',
+ 'congress',
+ 'connect',
+ 'consider',
+ 'control',
+ 'convince',
+ 'cook',
+ 'cool',
+ 'copper',
+ 'copy',
+ 'coral',
+ 'core',
+ 'corn',
+ 'correct',
+ 'cost',
+ 'cotton',
+ 'couch',
+ 'country',
+ 'couple',
+ 'course',
+ 'cousin',
+ 'cover',
+ 'coyote',
+ 'crack',
+ 'cradle',
+ 'craft',
+ 'cram',
+ 'crane',
+ 'crash',
+ 'crater',
+ 'crawl',
+ 'crazy',
+ 'cream',
+ 'credit',
+ 'creek',
+ 'crew',
+ 'cricket',
+ 'crime',
+ 'crisp',
+ 'critic',
+ 'crop',
+ 'cross',
+ 'crouch',
+ 'crowd',
+ 'crucial',
+ 'cruel',
+ 'cruise',
+ 'crumble',
+ 'crunch',
+ 'crush',
+ 'cry',
+ 'crystal',
+ 'cube',
+ 'culture',
+ 'cup',
+ 'cupboard',
+ 'curious',
+ 'current',
+ 'curtain',
+ 'curve',
+ 'cushion',
+ 'custom',
+ 'cute',
+ 'cycle',
+ 'dad',
+ 'damage',
+ 'damp',
+ 'dance',
+ 'danger',
+ 'daring',
+ 'dash',
+ 'daughter',
+ 'dawn',
+ 'day',
+ 'deal',
+ 'debate',
+ 'debris',
+ 'decade',
+ 'december',
+ 'decide',
+ 'decline',
+ 'decorate',
+ 'decrease',
+ 'deer',
+ 'defense',
+ 'define',
+ 'defy',
+ 'degree',
+ 'delay',
+ 'deliver',
+ 'demand',
+ 'demise',
+ 'denial',
+ 'dentist',
+ 'deny',
+ 'depart',
+ 'depend',
+ 'deposit',
+ 'depth',
+ 'deputy',
+ 'derive',
+ 'describe',
+ 'desert',
+ 'design',
+ 'desk',
+ 'despair',
+ 'destroy',
+ 'detail',
+ 'detect',
+ 'develop',
+ 'device',
+ 'devote',
+ 'diagram',
+ 'dial',
+ 'diamond',
+ 'diary',
+ 'dice',
+ 'diesel',
+ 'diet',
+ 'differ',
+ 'digital',
+ 'dignity',
+ 'dilemma',
+ 'dinner',
+ 'dinosaur',
+ 'direct',
+ 'dirt',
+ 'disagree',
+ 'discover',
+ 'disease',
+ 'dish',
+ 'dismiss',
+ 'disorder',
+ 'display',
+ 'distance',
+ 'divert',
+ 'divide',
+ 'divorce',
+ 'dizzy',
+ 'doctor',
+ 'document',
+ 'dog',
+ 'doll',
+ 'dolphin',
+ 'domain',
+ 'donate',
+ 'donkey',
+ 'donor',
+ 'door',
+ 'dose',
+ 'double',
+ 'dove',
+ 'draft',
+ 'dragon',
+ 'drama',
+ 'drastic',
+ 'draw',
+ 'dream',
+ 'dress',
+ 'drift',
+ 'drill',
+ 'drink',
+ 'drip',
+ 'drive',
+ 'drop',
+ 'drum',
+ 'dry',
+ 'duck',
+ 'dumb',
+ 'dune',
+ 'during',
+ 'dust',
+ 'dutch',
+ 'duty',
+ 'dwarf',
+ 'dynamic',
+ 'eager',
+ 'eagle',
+ 'early',
+ 'earn',
+ 'earth',
+ 'easily',
+ 'east',
+ 'easy',
+ 'echo',
+ 'ecology',
+ 'economy',
+ 'edge',
+ 'edit',
+ 'educate',
+ 'effort',
+ 'egg',
+ 'eight',
+ 'either',
+ 'elbow',
+ 'elder',
+ 'electric',
+ 'elegant',
+ 'element',
+ 'elephant',
+ 'elevator',
+ 'elite',
+ 'else',
+ 'embark',
+ 'embody',
+ 'embrace',
+ 'emerge',
+ 'emotion',
+ 'employ',
+ 'empower',
+ 'empty',
+ 'enable',
+ 'enact',
+ 'end',
+ 'endless',
+ 'endorse',
+ 'enemy',
+ 'energy',
+ 'enforce',
+ 'engage',
+ 'engine',
+ 'enhance',
+ 'enjoy',
+ 'enlist',
+ 'enough',
+ 'enrich',
+ 'enroll',
+ 'ensure',
+ 'enter',
+ 'entire',
+ 'entry',
+ 'envelope',
+ 'episode',
+ 'equal',
+ 'equip',
+ 'era',
+ 'erase',
+ 'erode',
+ 'erosion',
+ 'error',
+ 'erupt',
+ 'escape',
+ 'essay',
+ 'essence',
+ 'estate',
+ 'eternal',
+ 'ethics',
+ 'evidence',
+ 'evil',
+ 'evoke',
+ 'evolve',
+ 'exact',
+ 'example',
+ 'excess',
+ 'exchange',
+ 'excite',
+ 'exclude',
+ 'excuse',
+ 'execute',
+ 'exercise',
+ 'exhaust',
+ 'exhibit',
+ 'exile',
+ 'exist',
+ 'exit',
+ 'exotic',
+ 'expand',
+ 'expect',
+ 'expire',
+ 'explain',
+ 'expose',
+ 'express',
+ 'extend',
+ 'extra',
+ 'eye',
+ 'eyebrow',
+ 'fabric',
+ 'face',
+ 'faculty',
+ 'fade',
+ 'faint',
+ 'faith',
+ 'fall',
+ 'false',
+ 'fame',
+ 'family',
+ 'famous',
+ 'fan',
+ 'fancy',
+ 'fantasy',
+ 'farm',
+ 'fashion',
+ 'fat',
+ 'fatal',
+ 'father',
+ 'fatigue',
+ 'fault',
+ 'favorite',
+ 'feature',
+ 'february',
+ 'federal',
+ 'fee',
+ 'feed',
+ 'feel',
+ 'female',
+ 'fence',
+ 'festival',
+ 'fetch',
+ 'fever',
+ 'few',
+ 'fiber',
+ 'fiction',
+ 'field',
+ 'figure',
+ 'file',
+ 'film',
+ 'filter',
+ 'final',
+ 'find',
+ 'fine',
+ 'finger',
+ 'finish',
+ 'fire',
+ 'firm',
+ 'first',
+ 'fiscal',
+ 'fish',
+ 'fit',
+ 'fitness',
+ 'fix',
+ 'flag',
+ 'flame',
+ 'flash',
+ 'flat',
+ 'flavor',
+ 'flee',
+ 'flight',
+ 'flip',
+ 'float',
+ 'flock',
+ 'floor',
+ 'flower',
+ 'fluid',
+ 'flush',
+ 'fly',
+ 'foam',
+ 'focus',
+ 'fog',
+ 'foil',
+ 'fold',
+ 'follow',
+ 'food',
+ 'foot',
+ 'force',
+ 'forest',
+ 'forget',
+ 'fork',
+ 'fortune',
+ 'forum',
+ 'forward',
+ 'fossil',
+ 'foster',
+ 'found',
+ 'fox',
+ 'fragile',
+ 'frame',
+ 'frequent',
+ 'fresh',
+ 'friend',
+ 'fringe',
+ 'frog',
+ 'front',
+ 'frost',
+ 'frown',
+ 'frozen',
+ 'fruit',
+ 'fuel',
+ 'fun',
+ 'funny',
+ 'furnace',
+ 'fury',
+ 'future',
+ 'gadget',
+ 'gain',
+ 'galaxy',
+ 'gallery',
+ 'game',
+ 'gap',
+ 'garage',
+ 'garbage',
+ 'garden',
+ 'garlic',
+ 'garment',
+ 'gas',
+ 'gasp',
+ 'gate',
+ 'gather',
+ 'gauge',
+ 'gaze',
+ 'general',
+ 'genius',
+ 'genre',
+ 'gentle',
+ 'genuine',
+ 'gesture',
+ 'ghost',
+ 'giant',
+ 'gift',
+ 'giggle',
+ 'ginger',
+ 'giraffe',
+ 'girl',
+ 'give',
+ 'glad',
+ 'glance',
+ 'glare',
+ 'glass',
+ 'glide',
+ 'glimpse',
+ 'globe',
+ 'gloom',
+ 'glory',
+ 'glove',
+ 'glow',
+ 'glue',
+ 'goat',
+ 'goddess',
+ 'gold',
+ 'good',
+ 'goose',
+ 'gorilla',
+ 'gospel',
+ 'gossip',
+ 'govern',
+ 'gown',
+ 'grab',
+ 'grace',
+ 'grain',
+ 'grant',
+ 'grape',
+ 'grass',
+ 'gravity',
+ 'great',
+ 'green',
+ 'grid',
+ 'grief',
+ 'grit',
+ 'grocery',
+ 'group',
+ 'grow',
+ 'grunt',
+ 'guard',
+ 'guess',
+ 'guide',
+ 'guilt',
+ 'guitar',
+ 'gun',
+ 'gym',
+ 'habit',
+ 'hair',
+ 'half',
+ 'hammer',
+ 'hamster',
+ 'hand',
+ 'happy',
+ 'harbor',
+ 'hard',
+ 'harsh',
+ 'harvest',
+ 'hat',
+ 'have',
+ 'hawk',
+ 'hazard',
+ 'head',
+ 'health',
+ 'heart',
+ 'heavy',
+ 'hedgehog',
+ 'height',
+ 'hello',
+ 'helmet',
+ 'help',
+ 'hen',
+ 'hero',
+ 'hidden',
+ 'high',
+ 'hill',
+ 'hint',
+ 'hip',
+ 'hire',
+ 'history',
+ 'hobby',
+ 'hockey',
+ 'hold',
+ 'hole',
+ 'holiday',
+ 'hollow',
+ 'home',
+ 'honey',
+ 'hood',
+ 'hope',
+ 'horn',
+ 'horror',
+ 'horse',
+ 'hospital',
+ 'host',
+ 'hotel',
+ 'hour',
+ 'hover',
+ 'hub',
+ 'huge',
+ 'human',
+ 'humble',
+ 'humor',
+ 'hundred',
+ 'hungry',
+ 'hunt',
+ 'hurdle',
+ 'hurry',
+ 'hurt',
+ 'husband',
+ 'hybrid',
+ 'ice',
+ 'icon',
+ 'idea',
+ 'identify',
+ 'idle',
+ 'ignore',
+ 'ill',
+ 'illegal',
+ 'illness',
+ 'image',
+ 'imitate',
+ 'immense',
+ 'immune',
+ 'impact',
+ 'impose',
+ 'improve',
+ 'impulse',
+ 'inch',
+ 'include',
+ 'income',
+ 'increase',
+ 'index',
+ 'indicate',
+ 'indoor',
+ 'industry',
+ 'infant',
+ 'inflict',
+ 'inform',
+ 'inhale',
+ 'inherit',
+ 'initial',
+ 'inject',
+ 'injury',
+ 'inmate',
+ 'inner',
+ 'innocent',
+ 'input',
+ 'inquiry',
+ 'insane',
+ 'insect',
+ 'inside',
+ 'inspire',
+ 'install',
+ 'intact',
+ 'interest',
+ 'into',
+ 'invest',
+ 'invite',
+ 'involve',
+ 'iron',
+ 'island',
+ 'isolate',
+ 'issue',
+ 'item',
+ 'ivory',
+ 'jacket',
+ 'jaguar',
+ 'jar',
+ 'jazz',
+ 'jealous',
+ 'jeans',
+ 'jelly',
+ 'jewel',
+ 'job',
+ 'join',
+ 'joke',
+ 'journey',
+ 'joy',
+ 'judge',
+ 'juice',
+ 'jump',
+ 'jungle',
+ 'junior',
+ 'junk',
+ 'just',
+ 'kangaroo',
+ 'keen',
+ 'keep',
+ 'ketchup',
+ 'key',
+ 'kick',
+ 'kid',
+ 'kidney',
+ 'kind',
+ 'kingdom',
+ 'kiss',
+ 'kit',
+ 'kitchen',
+ 'kite',
+ 'kitten',
+ 'kiwi',
+ 'knee',
+ 'knife',
+ 'knock',
+ 'know',
+ 'lab',
+ 'label',
+ 'labor',
+ 'ladder',
+ 'lady',
+ 'lake',
+ 'lamp',
+ 'language',
+ 'laptop',
+ 'large',
+ 'later',
+ 'latin',
+ 'laugh',
+ 'laundry',
+ 'lava',
+ 'law',
+ 'lawn',
+ 'lawsuit',
+ 'layer',
+ 'lazy',
+ 'leader',
+ 'leaf',
+ 'learn',
+ 'leave',
+ 'lecture',
+ 'left',
+ 'leg',
+ 'legal',
+ 'legend',
+ 'leisure',
+ 'lemon',
+ 'lend',
+ 'length',
+ 'lens',
+ 'leopard',
+ 'lesson',
+ 'letter',
+ 'level',
+ 'liar',
+ 'liberty',
+ 'library',
+ 'license',
+ 'life',
+ 'lift',
+ 'light',
+ 'like',
+ 'limb',
+ 'limit',
+ 'link',
+ 'lion',
+ 'liquid',
+ 'list',
+ 'little',
+ 'live',
+ 'lizard',
+ 'load',
+ 'loan',
+ 'lobster',
+ 'local',
+ 'lock',
+ 'logic',
+ 'lonely',
+ 'long',
+ 'loop',
+ 'lottery',
+ 'loud',
+ 'lounge',
+ 'love',
+ 'loyal',
+ 'lucky',
+ 'luggage',
+ 'lumber',
+ 'lunar',
+ 'lunch',
+ 'luxury',
+ 'lyrics',
+ 'machine',
+ 'mad',
+ 'magic',
+ 'magnet',
+ 'maid',
+ 'mail',
+ 'main',
+ 'major',
+ 'make',
+ 'mammal',
+ 'man',
+ 'manage',
+ 'mandate',
+ 'mango',
+ 'mansion',
+ 'manual',
+ 'maple',
+ 'marble',
+ 'march',
+ 'margin',
+ 'marine',
+ 'market',
+ 'marriage',
+ 'mask',
+ 'mass',
+ 'master',
+ 'match',
+ 'material',
+ 'math',
+ 'matrix',
+ 'matter',
+ 'maximum',
+ 'maze',
+ 'meadow',
+ 'mean',
+ 'measure',
+ 'meat',
+ 'mechanic',
+ 'medal',
+ 'media',
+ 'melody',
+ 'melt',
+ 'member',
+ 'memory',
+ 'mention',
+ 'menu',
+ 'mercy',
+ 'merge',
+ 'merit',
+ 'merry',
+ 'mesh',
+ 'message',
+ 'metal',
+ 'method',
+ 'middle',
+ 'midnight',
+ 'milk',
+ 'million',
+ 'mimic',
+ 'mind',
+ 'minimum',
+ 'minor',
+ 'minute',
+ 'miracle',
+ 'mirror',
+ 'misery',
+ 'miss',
+ 'mistake',
+ 'mix',
+ 'mixed',
+ 'mixture',
+ 'mobile',
+ 'model',
+ 'modify',
+ 'mom',
+ 'moment',
+ 'monitor',
+ 'monkey',
+ 'monster',
+ 'month',
+ 'moon',
+ 'moral',
+ 'more',
+ 'morning',
+ 'mosquito',
+ 'mother',
+ 'motion',
+ 'motor',
+ 'mountain',
+ 'mouse',
+ 'move',
+ 'movie',
+ 'much',
+ 'muffin',
+ 'mule',
+ 'multiply',
+ 'muscle',
+ 'museum',
+ 'mushroom',
+ 'music',
+ 'must',
+ 'mutual',
+ 'myself',
+ 'mystery',
+ 'myth',
+ 'naive',
+ 'name',
+ 'napkin',
+ 'narrow',
+ 'nasty',
+ 'nation',
+ 'nature',
+ 'near',
+ 'neck',
+ 'need',
+ 'negative',
+ 'neglect',
+ 'neither',
+ 'nephew',
+ 'nerve',
+ 'nest',
+ 'net',
+ 'network',
+ 'neutral',
+ 'never',
+ 'news',
+ 'next',
+ 'nice',
+ 'night',
+ 'noble',
+ 'noise',
+ 'nominee',
+ 'noodle',
+ 'normal',
+ 'north',
+ 'nose',
+ 'notable',
+ 'note',
+ 'nothing',
+ 'notice',
+ 'novel',
+ 'now',
+ 'nuclear',
+ 'number',
+ 'nurse',
+ 'nut',
+ 'oak',
+ 'obey',
+ 'object',
+ 'oblige',
+ 'obscure',
+ 'observe',
+ 'obtain',
+ 'obvious',
+ 'occur',
+ 'ocean',
+ 'october',
+ 'odor',
+ 'off',
+ 'offer',
+ 'office',
+ 'often',
+ 'oil',
+ 'okay',
+ 'old',
+ 'olive',
+ 'olympic',
+ 'omit',
+ 'once',
+ 'one',
+ 'onion',
+ 'online',
+ 'only',
+ 'open',
+ 'opera',
+ 'opinion',
+ 'oppose',
+ 'option',
+ 'orange',
+ 'orbit',
+ 'orchard',
+ 'order',
+ 'ordinary',
+ 'organ',
+ 'orient',
+ 'original',
+ 'orphan',
+ 'ostrich',
+ 'other',
+ 'outdoor',
+ 'outer',
+ 'output',
+ 'outside',
+ 'oval',
+ 'oven',
+ 'over',
+ 'own',
+ 'owner',
+ 'oxygen',
+ 'oyster',
+ 'ozone',
+ 'pact',
+ 'paddle',
+ 'page',
+ 'pair',
+ 'palace',
+ 'palm',
+ 'panda',
+ 'panel',
+ 'panic',
+ 'panther',
+ 'paper',
+ 'parade',
+ 'parent',
+ 'park',
+ 'parrot',
+ 'party',
+ 'pass',
+ 'patch',
+ 'path',
+ 'patient',
+ 'patrol',
+ 'pattern',
+ 'pause',
+ 'pave',
+ 'payment',
+ 'peace',
+ 'peanut',
+ 'pear',
+ 'peasant',
+ 'pelican',
+ 'pen',
+ 'penalty',
+ 'pencil',
+ 'people',
+ 'pepper',
+ 'perfect',
+ 'permit',
+ 'person',
+ 'pet',
+ 'phone',
+ 'photo',
+ 'phrase',
+ 'physical',
+ 'piano',
+ 'picnic',
+ 'picture',
+ 'piece',
+ 'pig',
+ 'pigeon',
+ 'pill',
+ 'pilot',
+ 'pink',
+ 'pioneer',
+ 'pipe',
+ 'pistol',
+ 'pitch',
+ 'pizza',
+ 'place',
+ 'planet',
+ 'plastic',
+ 'plate',
+ 'play',
+ 'please',
+ 'pledge',
+ 'pluck',
+ 'plug',
+ 'plunge',
+ 'poem',
+ 'poet',
+ 'point',
+ 'polar',
+ 'pole',
+ 'police',
+ 'pond',
+ 'pony',
+ 'pool',
+ 'popular',
+ 'portion',
+ 'position',
+ 'possible',
+ 'post',
+ 'potato',
+ 'pottery',
+ 'poverty',
+ 'powder',
+ 'power',
+ 'practice',
+ 'praise',
+ 'predict',
+ 'prefer',
+ 'prepare',
+ 'present',
+ 'pretty',
+ 'prevent',
+ 'price',
+ 'pride',
+ 'primary',
+ 'print',
+ 'priority',
+ 'prison',
+ 'private',
+ 'prize',
+ 'problem',
+ 'process',
+ 'produce',
+ 'profit',
+ 'program',
+ 'project',
+ 'promote',
+ 'proof',
+ 'property',
+ 'prosper',
+ 'protect',
+ 'proud',
+ 'provide',
+ 'public',
+ 'pudding',
+ 'pull',
+ 'pulp',
+ 'pulse',
+ 'pumpkin',
+ 'punch',
+ 'pupil',
+ 'puppy',
+ 'purchase',
+ 'purity',
+ 'purpose',
+ 'purse',
+ 'push',
+ 'put',
+ 'puzzle',
+ 'pyramid',
+ 'quality',
+ 'quantum',
+ 'quarter',
+ 'question',
+ 'quick',
+ 'quit',
+ 'quiz',
+ 'quote',
+ 'rabbit',
+ 'raccoon',
+ 'race',
+ 'rack',
+ 'radar',
+ 'radio',
+ 'rail',
+ 'rain',
+ 'raise',
+ 'rally',
+ 'ramp',
+ 'ranch',
+ 'random',
+ 'range',
+ 'rapid',
+ 'rare',
+ 'rate',
+ 'rather',
+ 'raven',
+ 'raw',
+ 'razor',
+ 'ready',
+ 'real',
+ 'reason',
+ 'rebel',
+ 'rebuild',
+ 'recall',
+ 'receive',
+ 'recipe',
+ 'record',
+ 'recycle',
+ 'reduce',
+ 'reflect',
+ 'reform',
+ 'refuse',
+ 'region',
+ 'regret',
+ 'regular',
+ 'reject',
+ 'relax',
+ 'release',
+ 'relief',
+ 'rely',
+ 'remain',
+ 'remember',
+ 'remind',
+ 'remove',
+ 'render',
+ 'renew',
+ 'rent',
+ 'reopen',
+ 'repair',
+ 'repeat',
+ 'replace',
+ 'report',
+ 'require',
+ 'rescue',
+ 'resemble',
+ 'resist',
+ 'resource',
+ 'response',
+ 'result',
+ 'retire',
+ 'retreat',
+ 'return',
+ 'reunion',
+ 'reveal',
+ 'review',
+ 'reward',
+ 'rhythm',
+ 'rib',
+ 'ribbon',
+ 'rice',
+ 'rich',
+ 'ride',
+ 'ridge',
+ 'rifle',
+ 'right',
+ 'rigid',
+ 'ring',
+ 'riot',
+ 'ripple',
+ 'risk',
+ 'ritual',
+ 'rival',
+ 'river',
+ 'road',
+ 'roast',
+ 'robot',
+ 'robust',
+ 'rocket',
+ 'romance',
+ 'roof',
+ 'rookie',
+ 'room',
+ 'rose',
+ 'rotate',
+ 'rough',
+ 'round',
+ 'route',
+ 'royal',
+ 'rubber',
+ 'rude',
+ 'rug',
+ 'rule',
+ 'run',
+ 'runway',
+ 'rural',
+ 'sad',
+ 'saddle',
+ 'sadness',
+ 'safe',
+ 'sail',
+ 'salad',
+ 'salmon',
+ 'salon',
+ 'salt',
+ 'salute',
+ 'same',
+ 'sample',
+ 'sand',
+ 'satisfy',
+ 'satoshi',
+ 'sauce',
+ 'sausage',
+ 'save',
+ 'say',
+ 'scale',
+ 'scan',
+ 'scare',
+ 'scatter',
+ 'scene',
+ 'scheme',
+ 'school',
+ 'science',
+ 'scissors',
+ 'scorpion',
+ 'scout',
+ 'scrap',
+ 'screen',
+ 'script',
+ 'scrub',
+ 'sea',
+ 'search',
+ 'season',
+ 'seat',
+ 'second',
+ 'secret',
+ 'section',
+ 'security',
+ 'seed',
+ 'seek',
+ 'segment',
+ 'select',
+ 'sell',
+ 'seminar',
+ 'senior',
+ 'sense',
+ 'sentence',
+ 'series',
+ 'service',
+ 'session',
+ 'settle',
+ 'setup',
+ 'seven',
+ 'shadow',
+ 'shaft',
+ 'shallow',
+ 'share',
+ 'shed',
+ 'shell',
+ 'sheriff',
+ 'shield',
+ 'shift',
+ 'shine',
+ 'ship',
+ 'shiver',
+ 'shock',
+ 'shoe',
+ 'shoot',
+ 'shop',
+ 'short',
+ 'shoulder',
+ 'shove',
+ 'shrimp',
+ 'shrug',
+ 'shuffle',
+ 'shy',
+ 'sibling',
+ 'sick',
+ 'side',
+ 'siege',
+ 'sight',
+ 'sign',
+ 'silent',
+ 'silk',
+ 'silly',
+ 'silver',
+ 'similar',
+ 'simple',
+ 'since',
+ 'sing',
+ 'siren',
+ 'sister',
+ 'situate',
+ 'six',
+ 'size',
+ 'skate',
+ 'sketch',
+ 'ski',
+ 'skill',
+ 'skin',
+ 'skirt',
+ 'skull',
+ 'slab',
+ 'slam',
+ 'sleep',
+ 'slender',
+ 'slice',
+ 'slide',
+ 'slight',
+ 'slim',
+ 'slogan',
+ 'slot',
+ 'slow',
+ 'slush',
+ 'small',
+ 'smart',
+ 'smile',
+ 'smoke',
+ 'smooth',
+ 'snack',
+ 'snake',
+ 'snap',
+ 'sniff',
+ 'snow',
+ 'soap',
+ 'soccer',
+ 'social',
+ 'sock',
+ 'soda',
+ 'soft',
+ 'solar',
+ 'soldier',
+ 'solid',
+ 'solution',
+ 'solve',
+ 'someone',
+ 'song',
+ 'soon',
+ 'sorry',
+ 'sort',
+ 'soul',
+ 'sound',
+ 'soup',
+ 'source',
+ 'south',
+ 'space',
+ 'spare',
+ 'spatial',
+ 'spawn',
+ 'speak',
+ 'special',
+ 'speed',
+ 'spell',
+ 'spend',
+ 'sphere',
+ 'spice',
+ 'spider',
+ 'spike',
+ 'spin',
+ 'spirit',
+ 'split',
+ 'spoil',
+ 'sponsor',
+ 'spoon',
+ 'sport',
+ 'spot',
+ 'spray',
+ 'spread',
+ 'spring',
+ 'spy',
+ 'square',
+ 'squeeze',
+ 'squirrel',
+ 'stable',
+ 'stadium',
+ 'staff',
+ 'stage',
+ 'stairs',
+ 'stamp',
+ 'stand',
+ 'start',
+ 'state',
+ 'stay',
+ 'steak',
+ 'steel',
+ 'stem',
+ 'step',
+ 'stereo',
+ 'stick',
+ 'still',
+ 'sting',
+ 'stock',
+ 'stomach',
+ 'stone',
+ 'stool',
+ 'story',
+ 'stove',
+ 'strategy',
+ 'street',
+ 'strike',
+ 'strong',
+ 'struggle',
+ 'student',
+ 'stuff',
+ 'stumble',
+ 'style',
+ 'subject',
+ 'submit',
+ 'subway',
+ 'success',
+ 'such',
+ 'sudden',
+ 'suffer',
+ 'sugar',
+ 'suggest',
+ 'suit',
+ 'summer',
+ 'sun',
+ 'sunny',
+ 'sunset',
+ 'super',
+ 'supply',
+ 'supreme',
+ 'sure',
+ 'surface',
+ 'surge',
+ 'surprise',
+ 'surround',
+ 'survey',
+ 'suspect',
+ 'sustain',
+ 'swallow',
+ 'swamp',
+ 'swap',
+ 'swarm',
+ 'swear',
+ 'sweet',
+ 'swift',
+ 'swim',
+ 'swing',
+ 'switch',
+ 'sword',
+ 'symbol',
+ 'symptom',
+ 'syrup',
+ 'system',
+ 'table',
+ 'tackle',
+ 'tag',
+ 'tail',
+ 'talent',
+ 'talk',
+ 'tank',
+ 'tape',
+ 'target',
+ 'task',
+ 'taste',
+ 'tattoo',
+ 'taxi',
+ 'teach',
+ 'team',
+ 'tell',
+ 'ten',
+ 'tenant',
+ 'tennis',
+ 'tent',
+ 'term',
+ 'test',
+ 'text',
+ 'thank',
+ 'that',
+ 'theme',
+ 'then',
+ 'theory',
+ 'there',
+ 'they',
+ 'thing',
+ 'this',
+ 'thought',
+ 'three',
+ 'thrive',
+ 'throw',
+ 'thumb',
+ 'thunder',
+ 'ticket',
+ 'tide',
+ 'tiger',
+ 'tilt',
+ 'timber',
+ 'time',
+ 'tiny',
+ 'tip',
+ 'tired',
+ 'tissue',
+ 'title',
+ 'toast',
+ 'tobacco',
+ 'today',
+ 'toddler',
+ 'toe',
+ 'together',
+ 'toilet',
+ 'token',
+ 'tomato',
+ 'tomorrow',
+ 'tone',
+ 'tongue',
+ 'tonight',
+ 'tool',
+ 'tooth',
+ 'top',
+ 'topic',
+ 'topple',
+ 'torch',
+ 'tornado',
+ 'tortoise',
+ 'toss',
+ 'total',
+ 'tourist',
+ 'toward',
+ 'tower',
+ 'town',
+ 'toy',
+ 'track',
+ 'trade',
+ 'traffic',
+ 'tragic',
+ 'train',
+ 'transfer',
+ 'trap',
+ 'trash',
+ 'travel',
+ 'tray',
+ 'treat',
+ 'tree',
+ 'trend',
+ 'trial',
+ 'tribe',
+ 'trick',
+ 'trigger',
+ 'trim',
+ 'trip',
+ 'trophy',
+ 'trouble',
+ 'truck',
+ 'true',
+ 'truly',
+ 'trumpet',
+ 'trust',
+ 'truth',
+ 'try',
+ 'tube',
+ 'tuition',
+ 'tumble',
+ 'tuna',
+ 'tunnel',
+ 'turkey',
+ 'turn',
+ 'turtle',
+ 'twelve',
+ 'twenty',
+ 'twice',
+ 'twin',
+ 'twist',
+ 'two',
+ 'type',
+ 'typical',
+ 'ugly',
+ 'umbrella',
+ 'unable',
+ 'unaware',
+ 'uncle',
+ 'uncover',
+ 'under',
+ 'undo',
+ 'unfair',
+ 'unfold',
+ 'unhappy',
+ 'uniform',
+ 'unique',
+ 'unit',
+ 'universe',
+ 'unknown',
+ 'unlock',
+ 'until',
+ 'unusual',
+ 'unveil',
+ 'update',
+ 'upgrade',
+ 'uphold',
+ 'upon',
+ 'upper',
+ 'upset',
+ 'urban',
+ 'urge',
+ 'usage',
+ 'use',
+ 'used',
+ 'useful',
+ 'useless',
+ 'usual',
+ 'utility',
+ 'vacant',
+ 'vacuum',
+ 'vague',
+ 'valid',
+ 'valley',
+ 'valve',
+ 'van',
+ 'vanish',
+ 'vapor',
+ 'various',
+ 'vast',
+ 'vault',
+ 'vehicle',
+ 'velvet',
+ 'vendor',
+ 'venture',
+ 'venue',
+ 'verb',
+ 'verify',
+ 'version',
+ 'very',
+ 'vessel',
+ 'veteran',
+ 'viable',
+ 'vibrant',
+ 'vicious',
+ 'victory',
+ 'video',
+ 'view',
+ 'village',
+ 'vintage',
+ 'violin',
+ 'virtual',
+ 'virus',
+ 'visa',
+ 'visit',
+ 'visual',
+ 'vital',
+ 'vivid',
+ 'vocal',
+ 'voice',
+ 'void',
+ 'volcano',
+ 'volume',
+ 'vote',
+ 'voyage',
+ 'wage',
+ 'wagon',
+ 'wait',
+ 'walk',
+ 'wall',
+ 'walnut',
+ 'want',
+ 'warfare',
+ 'warm',
+ 'warrior',
+ 'wash',
+ 'wasp',
+ 'waste',
+ 'water',
+ 'wave',
+ 'way',
+ 'wealth',
+ 'weapon',
+ 'wear',
+ 'weasel',
+ 'weather',
+ 'web',
+ 'wedding',
+ 'weekend',
+ 'weird',
+ 'welcome',
+ 'west',
+ 'wet',
+ 'whale',
+ 'what',
+ 'wheat',
+ 'wheel',
+ 'when',
+ 'where',
+ 'whip',
+ 'whisper',
+ 'wide',
+ 'width',
+ 'wife',
+ 'wild',
+ 'will',
+ 'win',
+ 'window',
+ 'wine',
+ 'wing',
+ 'wink',
+ 'winner',
+ 'winter',
+ 'wire',
+ 'wisdom',
+ 'wise',
+ 'wish',
+ 'witness',
+ 'wolf',
+ 'woman',
+ 'wonder',
+ 'wood',
+ 'wool',
+ 'word',
+ 'work',
+ 'world',
+ 'worry',
+ 'worth',
+ 'wrap',
+ 'wreck',
+ 'wrestle',
+ 'wrist',
+ 'write',
+ 'wrong',
+ 'yard',
+ 'year',
+ 'yellow',
+ 'you',
+ 'young',
+ 'youth',
+ 'zebra',
+ 'zero',
+ 'zone',
+ 'zoo'
+])
diff --git a/static/js/crypto/aes.js b/static/js/crypto/aes.js
new file mode 100644
index 0000000..7395d50
--- /dev/null
+++ b/static/js/crypto/aes.js
@@ -0,0 +1,1413 @@
+/*! MIT License. Copyright 2015-2018 Richard Moore
- LNbits API is built using
- FastAPI, a high-performance, easy to code API framework.
- FastAPI auto-generates swagger UI docs for testing endpoints
- /docs
-
views.py is used for
- setting application routes:
- views_api.py is used for
- setting application API endpoints:
- - LNbits uses - Quasar Framework - for frontend deisgn elements. Quasar Framework is an open-source - Vue.js based framework for building apps. -
- - - TIP: Look through -/template files in
- other extensions for examples of Quasar elements being used.
- In the below example we make a dialogue popup box (box can be
- triggered
-
- LNbits uses - Vue - for best-in-class, responsive and high-performance components. -
- -Typical example of Vue components in a frontend script:
-
- Content can be conditionally rendered using Vue's
- v-if:
-
- A magical "g" (ie
- this.g.user.wallets[0].inkey) is always available, with info about the user, wallets and
- extensions:
-
{% raw %}{{ g }}{% endraw %}
- - LNbits includes a handy - exchange rate function, that streams rates from 6 different sources. -
- Exchange rate API:from lnbits.utils.exchange_rates import
- fiat_amount_as_satoshis:For most purposes use Quasar's inbuilt VueQrcode library:
-- LNbits does also include a handy - - QR code enpoint -
- {% raw %} You can use via - {{protocol + location}}{% endraw - %}/api/v1/qrcode/some-data-you-want-in-a-qrcode:- Fastapi includes a great - websocket tool -
- {% raw %} -
- A few LNbits extensions also make use of a weird and useful
- websocket/GET tool built into LNbits, such as extensions
- Copilot and LNURLDevices
- You can subscribe to websocket with
- wss:{{location}}/api/v1/ws/{SOME-ID}
- You can post to any clients subscribed to the endpoint with
- {{protocol +
- location}}/api/v1/ws/{SOME-ID}/{THE-DATA-YOU-WANT-TO-POST}
-
-
- Function used in this demo:
-
GET /siLNt/api/v1/wallet
+ {"X-Api-Key": <invoice_key>}network=mainnet | testnet (default: mainnet)
+ [<wallet_account_object>, ...]
+ curl -X GET {{ request.base_url }}siLNt/api/v1/wallet -H
+ "X-Api-Key: <invoice_key>"
+
+ GET /siLNt/api/v1/wallet/{wallet_id}
+ {"X-Api-Key": <invoice_key>}{wallet_account_object}
+ curl -X GET {{ request.base_url }}siLNt/api/v1/wallet/<wallet_id> -H
+ "X-Api-Key: <invoice_key>"
+
+ POST /siLNt/api/v1/wallet
+ {"X-Api-Key": <invoice_key>}{"mnemonic": <encrypted_string>, "title": <string>,
+ "hr_address": <string>, "last_height": <string>,
+ "network": "mainnet" | "testnet"}
+ If hr_address is provided, it must resolve via BIP353 DNS to this wallet's SP address.
+ ""
+ curl -X POST {{ request.base_url }}siLNt/api/v1/wallet -H
+ "X-Api-Key: <invoice_key>" -H "Content-Type: application/json" -d
+ '{"mnemonic": "...", "last_height": "840000", "network": "mainnet"}'
+
+ PUT /siLNt/api/v1/wallet/{wallet_id}
+ {"X-Api-Key": <invoice_key>}{"hr_address": <string>, "last_height": <string>,
+ "title": <string>, "balance": <integer>}
+ If hr_address is updated, it must resolve via BIP353 DNS to this wallet's SP address.
+ ""
+ curl -X PUT {{ request.base_url }}siLNt/api/v1/wallet/<wallet_id> -H
+ "X-Api-Key: <invoice_key>" -H "Content-Type: application/json" -d
+ '{"hr_address": "alice@domain.com"}'
+
+ DELETE /siLNt/api/v1/wallet/{wallet_id}
+ {"X-Api-Key": <invoice_key>}Also deletes all UTXOs and labeled SP addresses for this wallet.
+ curl -X DELETE {{ request.base_url }}siLNt/api/v1/wallet/<wallet_id> -H
+ "X-Api-Key: <invoice_key>"
+
+ GET /siLNt/api/v1/wallet/{wallet_id}/addresses
+ {"X-Api-Key": <invoice_key>}{"addresses": [<address_object>, ...], "max": 10}
+ curl -X GET {{ request.base_url }}siLNt/api/v1/wallet/<wallet_id>/addresses -H
+ "X-Api-Key: <invoice_key>"
+
+ POST /siLNt/api/v1/wallet/{wallet_id}/addresses/preview
+ {"X-Api-Key": <invoice_key>}{"label_index": <integer>}
+ Derives a BIP352 labeled SP address without saving to DB. label_index starts at 1 (0 is reserved for change).
+ {"sp_address": <string>, "label_index": <integer>, "wallet_id": <string>}
+ curl -X POST {{ request.base_url }}siLNt/api/v1/wallet/<wallet_id>/addresses/preview -H
+ "X-Api-Key: <invoice_key>" -H "Content-Type: application/json" -d
+ '{"label_index": 1}'
+
+ POST /siLNt/api/v1/wallet/{wallet_id}/addresses
+ {"X-Api-Key": <invoice_key>}{"sp_address": <string>, "label_index": <integer>}
+ Persists a previously previewed labeled SP address to the DB. Maximum 10 per wallet.
+ {"sp_address": <string>, "label_index": <integer>, "wallet_id": <string>, "id": <string>}
+ curl -X POST {{ request.base_url }}siLNt/api/v1/wallet/<wallet_id>/addresses -H
+ "X-Api-Key: <invoice_key>" -H "Content-Type: application/json" -d
+ '{"sp_address": "sp1qq...", "label_index": 1}'
+
+ DELETE /siLNt/api/v1/wallet/{wallet_id}/addresses/{address_id}
+ {"X-Api-Key": <invoice_key>}curl -X DELETE {{ request.base_url }}siLNt/api/v1/wallet/<wallet_id>/addresses/<address_id> -H
+ "X-Api-Key: <invoice_key>"
+
+ POST /siLNt/api/v1/wallet/{wallet_id}/scan
+ {"X-Api-Key": <invoice_key>}{"from_height": <integer>, "to_height": <integer>}
+ {"utxos_found": <integer>, "blocks_scanned": <integer>,
+ "final_height": <integer>, "balance": <integer>}
+ curl -X POST {{ request.base_url }}siLNt/api/v1/wallet/<wallet_id>/scan -H
+ "X-Api-Key: <invoice_key>" -H "Content-Type: application/json" -d
+ '{"from_height": 840000, "to_height": 850000}'
+
+ POST /siLNt/api/v1/wallet/{wallet_id}/scan/stop
+ {"X-Api-Key": <invoice_key>}{"status": "stop requested"}
+ curl -X POST {{ request.base_url }}siLNt/api/v1/wallet/<wallet_id>/scan/stop -H
+ "X-Api-Key: <invoice_key>"
+
+ GET /siLNt/api/v1/wallet/{wallet_id}/scan/progress
+ {"X-Api-Key": <invoice_key>}{"active": <boolean>, "current": <integer>,
+ "total": <integer>, "found": <integer>}
+ curl -X GET {{ request.base_url }}siLNt/api/v1/wallet/<wallet_id>/scan/progress -H
+ "X-Api-Key: <invoice_key>"
+
+ GET /siLNt/api/v1/utxos
+ {"X-Api-Key": <invoice_key>}wallet_id=<string> (required)
+ {"utxos": [<utxo_object>, ...]}
+ curl -X GET "{{ request.base_url }}siLNt/api/v1/utxos?wallet_id=<wallet_id>" -H
+ "X-Api-Key: <invoice_key>"
+
+ GET /siLNt/api/v1/backend/config
+ {"X-Api-Key": <invoice_key>}{"blindbit_url": <string>, "blindbit_user": <string>,
+ "blindbit_pass": <string>, "mempool_url": <string>}
+ curl -X GET {{ request.base_url }}siLNt/api/v1/backend/config -H
+ "X-Api-Key: <invoice_key>"
+
+ PUT /siLNt/api/v1/backend/config
+ {"X-Api-Key": <admin_key>}{"blindbit_url": <string>, "blindbit_user": <string>,
+ "blindbit_pass": <string>, "mempool_url": <string>}
+ mempool_url defaults to https://mempool.space if not provided. Supports both http and https.
+ {"blindbit_url": <string>, "blindbit_user": <string>,
+ "blindbit_pass": <string>, "mempool_url": <string>}
+ curl -X PUT {{ request.base_url }}siLNt/api/v1/backend/config -H
+ "X-Api-Key: <admin_key>" -H "Content-Type: application/json" -d
+ '{"blindbit_url": "http://localhost:8001", "mempool_url": "http://localhost:8080"}'
+
+ GET /siLNt/api/v1/oracle/tip
+ {"X-Api-Key": <invoice_key>}{"height": <integer>}
+ curl -X GET {{ request.base_url }}siLNt/api/v1/oracle/tip -H
+ "X-Api-Key: <invoice_key>"
+
+ GET /siLNt/api/v1/bip353/resolve
+ {"X-Api-Key": <invoice_key>}address=<email_format> e.g. alice@domain.com (required)
+ {"address": <string>, "dns_domain": <string>,
+ "result": <string>}
+ curl -X GET "{{ request.base_url }}siLNt/api/v1/bip353/resolve?address=alice@domain.com"
+ -H "X-Api-Key: <invoice_key>"
+
+ POST /siLNt/api/v1/tx/build
+ {"X-Api-Key": <admin_key>}{"wallet_id": <string>, "recipient": <string>,
+ "amount": <integer>, "fee_rate": <integer>,
+ "memo": <string>, "utxos": [{"txid": <string>,
+ "amount": <integer>, "vout": <integer>,
+ "priv_key_tweak": <string>, "pub_key": <string>}]}
+ recipient can be a Silent Payment address (sp1...), on-chain address, or BIP353 email address. BIP353 addresses are auto-resolved server-side.
+ {"tx_hex": <string>, "fee": <integer>,
+ "amount": <integer>, "change": <integer>,
+ "total_input": <integer>, "recipient": <string>}
+ curl -X POST {{ request.base_url }}siLNt/api/v1/tx/build -H
+ "X-Api-Key: <admin_key>" -H "Content-Type: application/json" -d
+ '{"wallet_id": "...", "recipient": "sp1qq...", "amount": 1000,
+ "fee_rate": 1, "utxos": [...]}'
+
+ POST /siLNt/api/v1/tx/broadcast
+ {"X-Api-Key": <admin_key>}{"tx_hex": <string>, "wallet_id": <string>,
+ "spent_txids": [<string>, ...]}
+ Broadcasts to configured Mempool URL. spent_txids are marked as unconfirmed_spent in the DB.
+ {"txid": <string>}
+ curl -X POST {{ request.base_url }}siLNt/api/v1/tx/broadcast -H
+ "X-Api-Key: <admin_key>" -H "Content-Type: application/json" -d
+ '{"tx_hex": "0200000001...", "wallet_id": "...", "spent_txids": ["..."]}'
+
+ GET /siLNt/api/v1/config
+ {"X-Api-Key": <invoice_key>}Returns mempool_endpoint from BlindBit config (falls back to https://mempool.space).
+ {"mempool_endpoint": <string>, "sats_denominated": <boolean>,
+ "network": <string>}
+ curl -X GET {{ request.base_url }}siLNt/api/v1/config -H
+ "X-Api-Key: <invoice_key>"
+
+ + This will scan every block from your wallet's last scan height to the + current chain tip. Scanning may take several minutes depending on the + range. +
+ +