diff --git a/package-lock.json b/package-lock.json
index 096adb4..6f0e9ce 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -11,10 +11,11 @@
"dependencies": {
"@dashincubator/base58check": "^1.4.1",
"@dashincubator/ripemd160": "^3.0.0",
- "@dashincubator/secp256k1": "^1.7.1-5",
+ "@dashincubator/secp256k1": "dashhive/secp256k1.js#sync-with-upstream",
"@zxing/library": "^0.21.2",
+ "crowdnode": "dashhive/crowdnode.js#ref-dashtx",
"crypticstorage": "^0.0.2",
- "dashhd": "^3.3.3",
+ "dashhd": "dashhive/DashHD.js#ref-secp256k1-2.1.0-compat",
"dashkeys": "^1.1.4",
"dashphrase": "^1.4.0",
"dashsight": "dashhive/DashSight.js#feat/bulk-txs",
@@ -33,6 +34,7 @@
"version": "1.4.1",
"resolved": "https://registry.npmjs.org/@dashincubator/base58check/-/base58check-1.4.1.tgz",
"integrity": "sha512-JSAO+viM3pVgM93XSBAUhLvK18BlRIsmYU4eGuyrj1lz3Xa1oplSel94oG0SI5d2qTxdy0NfGT3vEFbiKpfj5Q==",
+ "license": "SEE LICENSE IN LICENSE",
"bin": {
"base58check": "bin/base58check.js"
}
@@ -40,17 +42,25 @@
"node_modules/@dashincubator/ripemd160": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/@dashincubator/ripemd160/-/ripemd160-3.0.0.tgz",
- "integrity": "sha512-EbdXcceP2mW76NchCKp8UYNbZgWkLuV4Mbi30G82xRED32ljJzXsKaaVdzU0oVo2fVzPRXF1GhSF6Lq9beTVvA=="
+ "integrity": "sha512-EbdXcceP2mW76NchCKp8UYNbZgWkLuV4Mbi30G82xRED32ljJzXsKaaVdzU0oVo2fVzPRXF1GhSF6Lq9beTVvA==",
+ "license": "MIT"
},
"node_modules/@dashincubator/secp256k1": {
- "version": "1.7.1-5",
- "resolved": "https://registry.npmjs.org/@dashincubator/secp256k1/-/secp256k1-1.7.1-5.tgz",
- "integrity": "sha512-3iA+RDZrJsRFPpWhlYkp3EdoFAlKjdqkNFiRwajMrzcpA/G/IBX0AnC1pwRLkTrM+tUowcyGrkJfT03U4ETZeg=="
+ "version": "2.1.0",
+ "resolved": "git+ssh://git@github.com/dashhive/secp256k1.js.git#d587c1a24ca7d5c47f7cb519ca70a54920da3245",
+ "license": "MIT"
+ },
+ "node_modules/@root/request": {
+ "version": "1.9.2",
+ "resolved": "https://registry.npmjs.org/@root/request/-/request-1.9.2.tgz",
+ "integrity": "sha512-wVaL9yVV9oDR9UNbPZa20qgY+4Ch6YN8JUkaE4el/uuS5dmhD8Lusm/ku8qJVNtmQA56XLzEDCRS6/vfpiHK2A==",
+ "license": "(MIT OR Apache-2.0)",
+ "optional": true
},
"node_modules/@types/node": {
- "version": "20.14.11",
- "resolved": "https://registry.npmjs.org/@types/node/-/node-20.14.11.tgz",
- "integrity": "sha512-kprQpL8MMeszbz6ojB5/tU8PLN4kesnN8Gjzw349rDlNgsSzg90lAVj3llK99Dh7JON+t9AuscPPFW6mPbTnSA==",
+ "version": "20.14.12",
+ "resolved": "https://registry.npmjs.org/@types/node/-/node-20.14.12.tgz",
+ "integrity": "sha512-r7wNXakLeSsGT0H1AU863vS2wa5wBOK4bWMjZz2wj+8nBx+m5PeIn0k8AloSLpRuiwdRQZwarZqHE4FNArPuJQ==",
"dev": true,
"license": "MIT",
"dependencies": {
@@ -76,17 +86,40 @@
"version": "0.9.0",
"resolved": "https://registry.npmjs.org/@zxing/text-encoding/-/text-encoding-0.9.0.tgz",
"integrity": "sha512-U/4aVJ2mxI0aDNI8Uq0wEhMgY+u4CNtEb0om3+y3+niDAsoTCOB33UF0sxpzqzdqXLqmvc+vZyAt4O8pPdfkwA==",
+ "license": "(Unlicense OR Apache-2.0)",
"optional": true
},
+ "node_modules/crowdnode": {
+ "version": "1.8.0",
+ "resolved": "git+ssh://git@github.com/dashhive/crowdnode.js.git#2cfe200186a3d3ed7d1fb29edac0657825ddf7d0",
+ "license": "SEE LICENSE IN LICENSE",
+ "bin": {
+ "crowdnode": "bin/crowdnode.js"
+ },
+ "optionalDependencies": {
+ "@dashincubator/base58check": "^1.4.1",
+ "@dashincubator/ripemd160": "^3.0.0",
+ "@dashincubator/secp256k1": "dashhive/secp256k1.js#sync-with-upstream",
+ "@root/request": "^1.9.2",
+ "dashhd": "dashhive/DashHD.js#ref-secp256k1-2.1.0-compat",
+ "dashkeys": "^1.1.4",
+ "dashsight": "^1.6.1",
+ "dashtx": "^0.16.0",
+ "dotenv": "^16.4.5",
+ "qrcode-svg": "^1.1.0",
+ "tough-cookie": "^4.1.4",
+ "ws": "^8.18.0"
+ }
+ },
"node_modules/crypticstorage": {
"version": "0.0.2",
"resolved": "https://registry.npmjs.org/crypticstorage/-/crypticstorage-0.0.2.tgz",
- "integrity": "sha512-PyiKq03ekJU1AwzYVchlm+LaUvDy939S1smmCD0fa60HLKVM0m6zEj4J/MXcm1BLrwdaZk9eRdJcau77nIOPAw=="
+ "integrity": "sha512-PyiKq03ekJU1AwzYVchlm+LaUvDy939S1smmCD0fa60HLKVM0m6zEj4J/MXcm1BLrwdaZk9eRdJcau77nIOPAw==",
+ "license": "MIT"
},
"node_modules/dashhd": {
"version": "3.3.3",
- "resolved": "https://registry.npmjs.org/dashhd/-/dashhd-3.3.3.tgz",
- "integrity": "sha512-sbhLV8EtmebnlIdx/d1hcbnxdfka/0rcLx+UO5y44kZdu5tyJ5ftBFbhhIb38vd+T+Xfcwpeo0z+0ZDznRkfaw==",
+ "resolved": "git+ssh://git@github.com/dashhive/DashHD.js.git#92cf91acf5633ed2ebed3b4f39d0d0f1d830679e",
"license": "SEE LICENSE IN LICENSE"
},
"node_modules/dashkeys": {
@@ -98,11 +131,13 @@
"node_modules/dashphrase": {
"version": "1.4.0",
"resolved": "https://registry.npmjs.org/dashphrase/-/dashphrase-1.4.0.tgz",
- "integrity": "sha512-o+LdiPkiYmg07kXBE+2bbcJzBmeTQVPn1GS2XlQeo8lene+KknAprSyiYi5XtqV/QVgNjvzOV7qBst2MijSPAA=="
+ "integrity": "sha512-o+LdiPkiYmg07kXBE+2bbcJzBmeTQVPn1GS2XlQeo8lene+KknAprSyiYi5XtqV/QVgNjvzOV7qBst2MijSPAA==",
+ "license": "SEE LICENSE IN LICENSE"
},
"node_modules/dashsight": {
"version": "1.6.1",
"resolved": "git+ssh://git@github.com/dashhive/DashSight.js.git#11c1a740057ba646f89e8da2d85cb35dc67085f3",
+ "license": "SEE LICENSE IN LICENSE",
"dependencies": {
"dashtx": "^0.9.0-3"
},
@@ -121,6 +156,7 @@
"version": "0.9.0",
"resolved": "https://registry.npmjs.org/dashtx/-/dashtx-0.9.0.tgz",
"integrity": "sha512-DDbH5vPChUpOrYMOoM+6g/Iy99KqG4nkJ6f8TphnGibzAY7mitjMgtFSc62YzbZdoPGYeSPm8N4jmz+Mbwm7Eg==",
+ "license": "SEE LICENSE IN LICENSE",
"bin": {
"dashtx-inspect": "bin/inspect.js"
}
@@ -156,36 +192,41 @@
}
},
"node_modules/dotenv": {
- "version": "16.3.1",
- "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-16.3.1.tgz",
- "integrity": "sha512-IPzF4w4/Rd94bA9imS68tZBaYyBWSCE47V1RGuMrB94iyTOIEwRmVL2x/4An+6mETpLrKJ5hQkB8W4kFAadeIQ==",
+ "version": "16.4.5",
+ "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-16.4.5.tgz",
+ "integrity": "sha512-ZmdL2rui+eB2YwhsWzjInR8LldtZHGDoQ1ugH85ppHKwpUHL7j7rN0Ti9NCnGiQbhaZ11FpR+7ao1dNsmduNUg==",
+ "license": "BSD-2-Clause",
"optional": true,
"engines": {
"node": ">=12"
},
"funding": {
- "url": "https://github.com/motdotla/dotenv?sponsor=1"
+ "url": "https://dotenvx.com"
}
},
"node_modules/html5-qrcode": {
"version": "2.3.8",
"resolved": "https://registry.npmjs.org/html5-qrcode/-/html5-qrcode-2.3.8.tgz",
- "integrity": "sha512-jsr4vafJhwoLVEDW3n1KvPnCCXWaQfRng0/EEYk1vNcQGcG/htAdhJX0be8YyqMoSz7+hZvOZSTAepsabiuhiQ=="
+ "integrity": "sha512-jsr4vafJhwoLVEDW3n1KvPnCCXWaQfRng0/EEYk1vNcQGcG/htAdhJX0be8YyqMoSz7+hZvOZSTAepsabiuhiQ==",
+ "license": "Apache-2.0"
},
"node_modules/idb": {
"version": "8.0.0",
"resolved": "https://registry.npmjs.org/idb/-/idb-8.0.0.tgz",
- "integrity": "sha512-l//qvlAKGmQO31Qn7xdzagVPPaHTxXx199MhrAFuVBTPqydcPYBWjkrbv4Y0ktB+GmWOiwHl237UUOrLmQxLvw=="
+ "integrity": "sha512-l//qvlAKGmQO31Qn7xdzagVPPaHTxXx199MhrAFuVBTPqydcPYBWjkrbv4Y0ktB+GmWOiwHl237UUOrLmQxLvw==",
+ "license": "ISC"
},
"node_modules/immediate": {
"version": "3.0.6",
"resolved": "https://registry.npmjs.org/immediate/-/immediate-3.0.6.tgz",
- "integrity": "sha512-XXOFtyqDjNDAQxVfYxuF7g9Il/IbWmmlQg2MYKOH8ExIT1qg6xc4zyS3HaEEATgs1btfzxq15ciUiY7gjSXRGQ=="
+ "integrity": "sha512-XXOFtyqDjNDAQxVfYxuF7g9Il/IbWmmlQg2MYKOH8ExIT1qg6xc4zyS3HaEEATgs1btfzxq15ciUiY7gjSXRGQ==",
+ "license": "MIT"
},
"node_modules/lie": {
"version": "3.1.1",
"resolved": "https://registry.npmjs.org/lie/-/lie-3.1.1.tgz",
"integrity": "sha512-RiNhHysUjhrDQntfYSfY4MU24coXXdEOgw9WGcKHNeEwffDYbF//u87M1EWaMGzuFoSbqW0C9C6lEEhDOAswfw==",
+ "license": "MIT",
"dependencies": {
"immediate": "~3.0.5"
}
@@ -194,22 +235,72 @@
"version": "1.10.0",
"resolved": "https://registry.npmjs.org/localforage/-/localforage-1.10.0.tgz",
"integrity": "sha512-14/H1aX7hzBBmmh7sGPd+AOMkkIrHM3Z1PAyGgZigA1H1p5O5ANnMyWzvpAETtG68/dC4pC0ncy3+PPGzXZHPg==",
+ "license": "Apache-2.0",
"dependencies": {
"lie": "3.1.1"
}
},
+ "node_modules/psl": {
+ "version": "1.9.0",
+ "resolved": "https://registry.npmjs.org/psl/-/psl-1.9.0.tgz",
+ "integrity": "sha512-E/ZsdU4HLs/68gYzgGTkMicWTLPdAftJLfJFlLUAAKZGkStNU72sZjT66SnMDVOfOWY/YAoiD7Jxa9iHvngcag==",
+ "license": "MIT",
+ "optional": true
+ },
+ "node_modules/punycode": {
+ "version": "2.3.1",
+ "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz",
+ "integrity": "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==",
+ "license": "MIT",
+ "optional": true,
+ "engines": {
+ "node": ">=6"
+ }
+ },
"node_modules/qrcode-svg": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/qrcode-svg/-/qrcode-svg-1.1.0.tgz",
"integrity": "sha512-XyQCIXux1zEIA3NPb0AeR8UMYvXZzWEhgdBgBjH9gO7M48H9uoHzviNz8pXw3UzrAcxRRRn9gxHewAVK7bn9qw==",
+ "license": "MIT",
"bin": {
"qrcode-svg": "bin/qrcode-svg.js"
}
},
+ "node_modules/querystringify": {
+ "version": "2.2.0",
+ "resolved": "https://registry.npmjs.org/querystringify/-/querystringify-2.2.0.tgz",
+ "integrity": "sha512-FIqgj2EUvTa7R50u0rGsyTftzjYmv/a3hO345bZNrqabNqjtgiDMgmo4mkUjd+nzU5oF3dClKqFIPUKybUyqoQ==",
+ "license": "MIT",
+ "optional": true
+ },
+ "node_modules/requires-port": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/requires-port/-/requires-port-1.0.0.tgz",
+ "integrity": "sha512-KigOCHcocU3XODJxsu8i/j8T9tzT4adHiecwORRQ0ZZFcp7ahwXuRU1m+yuO90C5ZUyGeGfocHDI14M3L3yDAQ==",
+ "license": "MIT",
+ "optional": true
+ },
+ "node_modules/tough-cookie": {
+ "version": "4.1.4",
+ "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-4.1.4.tgz",
+ "integrity": "sha512-Loo5UUvLD9ScZ6jh8beX1T6sO1w2/MpCRpEP7V280GKMVUQ0Jzar2U3UJPsrdbziLEMMhu3Ujnq//rhiFuIeag==",
+ "license": "BSD-3-Clause",
+ "optional": true,
+ "dependencies": {
+ "psl": "^1.1.33",
+ "punycode": "^2.1.1",
+ "universalify": "^0.2.0",
+ "url-parse": "^1.5.3"
+ },
+ "engines": {
+ "node": ">=6"
+ }
+ },
"node_modules/ts-custom-error": {
"version": "3.3.1",
"resolved": "https://registry.npmjs.org/ts-custom-error/-/ts-custom-error-3.3.1.tgz",
"integrity": "sha512-5OX1tzOjxWEgsr/YEUWSuPrQ00deKLh6D7OTWcvNHm12/7QPyRh8SYpyWvA4IZv8H/+GQWQEh/kwo95Q9OVW1A==",
+ "license": "MIT",
"engines": {
"node": ">=14.0.0"
}
@@ -218,7 +309,51 @@
"version": "5.26.5",
"resolved": "https://registry.npmjs.org/undici-types/-/undici-types-5.26.5.tgz",
"integrity": "sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA==",
- "dev": true
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/universalify": {
+ "version": "0.2.0",
+ "resolved": "https://registry.npmjs.org/universalify/-/universalify-0.2.0.tgz",
+ "integrity": "sha512-CJ1QgKmNg3CwvAv/kOFmtnEN05f0D/cn9QntgNOQlQF9dgvVTHj3t+8JPdjqawCHk7V/KA+fbUqzZ9XWhcqPUg==",
+ "license": "MIT",
+ "optional": true,
+ "engines": {
+ "node": ">= 4.0.0"
+ }
+ },
+ "node_modules/url-parse": {
+ "version": "1.5.10",
+ "resolved": "https://registry.npmjs.org/url-parse/-/url-parse-1.5.10.tgz",
+ "integrity": "sha512-WypcfiRhfeUP9vvF0j6rw0J3hrWrw6iZv3+22h6iRMJ/8z1Tj6XfLP4DsUix5MhMPnXpiHDoKyoZ/bdCkwBCiQ==",
+ "license": "MIT",
+ "optional": true,
+ "dependencies": {
+ "querystringify": "^2.1.1",
+ "requires-port": "^1.0.0"
+ }
+ },
+ "node_modules/ws": {
+ "version": "8.18.0",
+ "resolved": "https://registry.npmjs.org/ws/-/ws-8.18.0.tgz",
+ "integrity": "sha512-8VbfWfHLbbwu3+N6OKsOMpBdT4kXPDDB9cJk2bJ6mh9ucxdlnNvH1e+roYkKmN9Nxw2yjz7VzeO9oOz2zJ04Pw==",
+ "license": "MIT",
+ "optional": true,
+ "engines": {
+ "node": ">=10.0.0"
+ },
+ "peerDependencies": {
+ "bufferutil": "^4.0.1",
+ "utf-8-validate": ">=5.0.2"
+ },
+ "peerDependenciesMeta": {
+ "bufferutil": {
+ "optional": true
+ },
+ "utf-8-validate": {
+ "optional": true
+ }
+ }
}
}
}
diff --git a/package.json b/package.json
index e44fb16..e904035 100644
--- a/package.json
+++ b/package.json
@@ -36,11 +36,11 @@
"dependencies": {
"@dashincubator/base58check": "^1.4.1",
"@dashincubator/ripemd160": "^3.0.0",
- "@dashincubator/secp256k1": "^1.7.1-5",
+ "@dashincubator/secp256k1": "dashhive/secp256k1.js#sync-with-upstream",
"@zxing/library": "^0.21.2",
- "crowdnode": "^1.8.0",
+ "crowdnode": "dashhive/crowdnode.js#ref-dashtx",
"crypticstorage": "^0.0.2",
- "dashhd": "^3.3.3",
+ "dashhd": "dashhive/DashHD.js#ref-secp256k1-2.1.0-compat",
"dashkeys": "^1.1.4",
"dashphrase": "^1.4.0",
"dashsight": "dashhive/DashSight.js#feat/bulk-txs",
diff --git a/src/components/balance.js b/src/components/balance.js
index 9077705..7075f5c 100644
--- a/src/components/balance.js
+++ b/src/components/balance.js
@@ -1,6 +1,9 @@
-import { lit as html } from '../helpers/lit.js'
-import { envoy, formatDash, } from '../helpers/utils.js'
-import { updateAllFunds, } from '../helpers/wallet.js'
+import {
+ lit as html,
+} from '../utils/generic.js'
+import { envoy, } from '../utils/retort.js'
+import { formatDash, } from '../utils/dash/local.js'
+import { updateAllFunds, } from '../utils/dash/network.js'
const initialState = {
id: 'Balance',
@@ -68,7 +71,7 @@ const initialState = {
// )
if (state?.wallet && state.walletFunds) {
- updateAllFunds(state.wallet, state.walletFunds)
+ updateAllFunds(state.wallet)
.then(balance => {
console.log(
'Update Balance',
diff --git a/src/components/contacts-list.js b/src/components/contacts-list.js
index 16a112d..87a1bfc 100644
--- a/src/components/contacts-list.js
+++ b/src/components/contacts-list.js
@@ -1,13 +1,14 @@
-import { lit as html } from '../helpers/lit.js'
import {
- envoy,
- restate,
+ lit as html,
+ timeago,
+ getAvatar,
+} from '../utils/generic.js'
+import { envoy, restate, } from '../utils/retort.js'
+import {
sortContactsByAlias,
filterPairedContacts,
filterUnpairedContacts,
- timeago,
- getAvatar,
-} from '../helpers/utils.js'
+} from '../utils/dash/local.js'
let _handlers = []
diff --git a/src/components/crowdnode-card.js b/src/components/crowdnode-card.js
new file mode 100644
index 0000000..b8df62a
--- /dev/null
+++ b/src/components/crowdnode-card.js
@@ -0,0 +1,226 @@
+import {
+ lit as html,
+ formDataEntries,
+} from '../utils/generic.js'
+
+import {
+ createSignal,
+} from '../utils/retort.js'
+
+import {
+ CrowdNode,
+} from '../imports.js'
+
+export const CrowdNodeCard = (() => {
+ console.log('CrowdNodeCard', CrowdNode)
+ const initCfg = {
+ state: {},
+ slugs: {},
+ events: {},
+ elements: {},
+ markup: {},
+ }
+ const initialState = {
+ id: 'Card',
+ name: 'Card',
+ withdrawTxt: 'Withdraw',
+ withdrawAlt: 'Withdraw from Crowdnode',
+ depositTxt: 'Deposit',
+ depositAlt: `Deposit to Crowdnode`,
+ signupTxt: 'Signup',
+ signupAlt: `Signup for Crowdnode`,
+ placement: 'center',
+ rendered: null,
+ responsive: true,
+ }
+
+ const cn = function Crowdnode(
+ config = {}
+ ) {
+ config = {
+ ...initCfg,
+ ...config,
+ }
+
+ this.appElement = document.body
+
+ this.api = createSignal({})
+
+ this.state = {
+ ...initialState,
+ ...config.state,
+ }
+
+ this.slugs = {
+ form: this.state.name?.toLowerCase().replaceAll(' ', '_'),
+ ...config.slugs,
+ }
+
+ this.elements = {
+ ...config.elements,
+ }
+
+ this.markup = {}
+ this.markup.content = () => html`
+
+
+
+ ${!this.api.value?.acceptedToS && html`
+ Start earning interest by staking your Dash at CrowdNode
+ `}
+ ${this.api.value?.acceptedToS && html`
+
+ Balance: Ð ${this.api.value?.balance}
+
+ `}
+ ${this.api.value?.acceptedToS && html`
+
+ Earned: Ð ${this.api.value?.earned}
+
+ `}
+
+
+
+ ${!this.api.value?.acceptedToS && html`
+
+ ${this.state.signupTxt}
+
+ `}
+ ${this.api.value?.acceptedToS && html`
+
+ ${this.state.depositTxt}
+
+ `}
+ ${this.api.value?.acceptedToS && this.api.value?.balance > 0 && html`
+
+ ${this.state.withdrawTxt}
+
+ `}
+
+ `
+ this.markup = {
+ ...this.markup,
+ ...config.markup,
+ }
+
+ this.events = {
+ submit: event => {
+ event.preventDefault()
+ event.stopPropagation()
+
+ this.elements.form?.removeEventListener('submit', this.events.submit)
+
+ let fde = formDataEntries(event)
+
+ console.log(
+ `${this.slugs.form} submit`,
+ {event, fde},
+ )
+ },
+ ...config.events,
+ }
+
+ const $d = document
+
+ const form = $d.createElement('form')
+
+ this.elements.form = form
+
+ form.name = `${this.slugs.form}`
+ form.classList.add('flex', 'col', 'card')
+ form.innerHTML = this.markup.content()
+
+ this.api.on((apiChange) => {
+ console.log('CN Card API Change', apiChange)
+ this.render?.({})
+ })
+
+ /**
+ * Update the config of the CN Card
+ * @function
+ */
+ this.updateConfig = (config = {}) => {
+ console.log('CN Card updateConfig TOP', config)
+
+ for (let param in config) {
+ this[param] = {
+ ...this[param],
+ ...(config[param] || {}),
+ }
+ }
+
+ console.log('CN Card updateConfig BOT', this)
+ }
+
+ /**
+ * Trigger the rendering of the CN Card
+ * @function
+ */
+ this.render = ({
+ cfg = {},
+ position = 'afterend',
+ el = this.appElement,
+ }) => {
+ console.log('crowdnode render', this)
+
+ this.elements.form?.removeEventListener?.(
+ 'submit',
+ this.events.submit,
+ )
+
+ if (el !== this.appElement) {
+ this.appElement = el
+ }
+
+ this.updateConfig(cfg)
+
+ this.elements.form.name = this.slugs.form
+ this.elements.form.innerHTML = this.markup.content()
+
+ this.elements.form.addEventListener(
+ 'submit',
+ this.events.submit,
+ )
+
+ console.log('CARD RENDER', this, cfg)
+
+ if (!this.state.rendered) {
+ el.insertAdjacentElement(position, this.elements.form)
+ this.state.rendered = this.elements.form
+ }
+
+ this.events?.render?.(this)
+ }
+
+ return this
+ }
+
+ return cn
+})();
+
+export default CrowdNodeCard
diff --git a/src/components/dialog.js b/src/components/dialog.js
index b133a09..a1fe8d5 100644
--- a/src/components/dialog.js
+++ b/src/components/dialog.js
@@ -1,8 +1,15 @@
-import { lit as html } from '../helpers/lit.js'
import {
+ DIALOG_STATUS,
+} from '../utils/constants.js'
+
+import {
+ lit as html,
formDataEntries,
+} from '../utils/generic.js'
+
+import {
envoy,
-} from '../helpers/utils.js'
+} from '../utils/retort.js'
let modal = envoy(
{
@@ -26,7 +33,8 @@ const initialState = {
rendered: null,
responsive: true,
delay: 500,
- async render () {},
+ status: DIALOG_STATUS.NOT_LOADING,
+ async render () { return this },
addListener () {},
addListeners () {},
removeAllListeners (targets) {},
@@ -210,6 +218,9 @@ const initialState = {
// event.target === state.elements.dialog,
// state.elements.dialog.returnValue
// )
+ state.status = DIALOG_STATUS.NOT_LOADING
+ state.elements.progress?.remove()
+ state.elements.progress = null
if (state.elements.dialog.returnValue !== 'cancel') {
resolve(state.elements.dialog.returnValue)
@@ -298,30 +309,30 @@ export async function setupDialog(
.replaceAll(/[^a-zA-Z _]/g, '')
.replaceAll(' ', '_')
- const dialog = document.createElement('dialog')
- const form = document.createElement('form')
- const progress = document.createElement('progress')
+ const dialogElement = document.createElement('dialog')
+ const formElement = document.createElement('form')
+ const progressElement = document.createElement('progress')
- state.elements.dialog = dialog
- state.elements.form = form
- state.elements.progress = progress
+ state.elements.dialog = dialogElement
+ state.elements.form = formElement
+ state.elements.progress = progressElement
- progress.classList.add('pending')
+ progressElement.classList.add('pending')
- dialog.innerHTML = ``
- dialog.id = state.slugs.dialog
+ dialogElement.innerHTML = ``
+ dialogElement.id = state.slugs.dialog
if (state.responsive) {
- dialog.classList.add('responsive')
+ dialogElement.classList.add('responsive')
}
- dialog.classList.add(state.placement)
+ dialogElement.classList.add(...(state.placement.split(' ')))
- form.name = `${state.slugs.form}`
- form.method = 'dialog'
- form.innerHTML = await state.content(state)
+ formElement.name = `${state.slugs.form}`
+ formElement.method = 'dialog'
+ formElement.innerHTML = await state.content(state)
- dialog.insertAdjacentElement(
+ dialogElement.insertAdjacentElement(
'afterbegin',
- form
+ formElement
)
function addListener(
@@ -340,74 +351,74 @@ export async function setupDialog(
) {
if (resolve && reject) {
addListener(
- dialog,
+ dialogElement,
'close',
state.events.handleClose(state, resolve, reject),
)
addListener(
- dialog,
+ dialogElement,
'click',
state.events.handleClick(state),
)
}
addListener(
- form,
+ formElement,
'blur',
state.events.handleBlur(state),
)
addListener(
- form,
+ formElement,
'focusout',
state.events.handleFocusOut(state),
)
addListener(
- form,
+ formElement,
'focusin',
state.events.handleFocusIn(state),
)
addListener(
- form,
+ formElement,
'change',
state.events.handleChange(state),
)
- // let updrop = form.querySelector('.updrop')
+ // let updrop = formElement.querySelector('.updrop')
// state.elements.updrop = updrop
// if (updrop) {
addListener(
- form,
+ formElement,
'drop',
state.events.handleDrop(state),
)
addListener(
- form,
+ formElement,
'dragover',
state.events.handleDragOver(state),
)
addListener(
- form,
+ formElement,
'dragend',
state.events.handleDragEnd(state),
)
addListener(
- form,
+ formElement,
'dragleave',
state.events.handleDragLeave(state),
)
// }
addListener(
- form,
+ formElement,
'input',
state.events.handleInput(state),
)
addListener(
- form,
+ formElement,
'reset',
state.events.handleReset(state),
)
addListener(
- form,
+ formElement,
'submit',
state.events.handleSubmit(state),
)
@@ -416,7 +427,7 @@ export async function setupDialog(
state.addListeners = addListeners
function removeAllListeners(
- targets = [dialog,form],
+ targets = [dialogElement,formElement],
) {
if (state.elements.updrop) {
targets.push(state.elements.updrop)
@@ -456,29 +467,56 @@ export async function setupDialog(
}
}
- dialog.id = state.slugs.dialog
- form.name = `${state.slugs.form}`
- form.innerHTML = await state.content(state)
+ dialogElement.id = state.slugs.dialog
+ formElement.name = `${state.slugs.form}`
+ formElement.innerHTML = await state.content(state)
// console.log('DIALOG RENDER', state, position, state.slugs.dialog, modal.rendered)
+ if (
+ state.status === DIALOG_STATUS.LOADING &&
+ state.elements.progress
+ ) {
+ state.elements.form.insertAdjacentElement(
+ 'beforebegin',
+ state.elements.progress,
+ )
+
+ // document.body.insertAdjacentHTML(
+ // 'afterbegin',
+ // ` `,
+ // )
+ }
+
+ if (
+ state.status === DIALOG_STATUS.SUCCESS ||
+ state.status === DIALOG_STATUS.ERROR
+ ) {
+ // document.getElementById('pageLoader')?.remove()
+ state.elements.progress?.remove()
+ }
+
if (!modal.rendered[state.slugs.dialog]) {
- el.insertAdjacentElement(position, dialog)
- modal.rendered[state.slugs.dialog] = dialog
+ el.insertAdjacentElement(position, dialogElement)
+ modal.rendered[state.slugs.dialog] = dialogElement
}
state.events.handleRender(state)
+
+ return state
}
state.render = render
return {
- element: dialog,
+ element: dialogElement,
+ state,
+ elements: state.elements,
show: (callback) => new Promise((resolve, reject) => {
removeAllListeners()
addListeners(resolve, reject)
- // console.log('dialog show', dialog)
- dialog.show()
+ // console.log('dialog show', dialogElement)
+ dialogElement.show()
state.events.handleShow?.(state)
callback?.()
}),
@@ -486,11 +524,11 @@ export async function setupDialog(
removeAllListeners()
addListeners(resolve, reject)
// console.log('dialog showModal', dialog)
- dialog.showModal()
+ dialogElement.showModal()
state.events.handleShow?.(state)
callback?.()
}),
- close: returnVal => dialog.close(returnVal),
+ close: returnVal => dialogElement.close(returnVal),
render,
}
}
diff --git a/src/components/main-footer.js b/src/components/main-footer.js
index f419d49..4620ea5 100644
--- a/src/components/main-footer.js
+++ b/src/components/main-footer.js
@@ -1,4 +1,6 @@
-import { lit as html } from '../helpers/lit.js'
+import {
+ lit as html,
+} from '../utils/generic.js'
const initialState = {
rendered: null,
diff --git a/src/components/modal.js b/src/components/modal.js
new file mode 100644
index 0000000..14f673f
--- /dev/null
+++ b/src/components/modal.js
@@ -0,0 +1,397 @@
+import {
+ DIALOG_STATUS,
+} from '../utils/constants.js'
+
+import {
+ lit as html,
+ formDataEntries,
+ toSlug,
+ // addListener,
+ addListeners,
+ removeAllListeners,
+} from '../utils/generic.js'
+
+import {
+ createSignal,
+ effect,
+} from '../utils/retort.js'
+
+/**
+ * Create a new HTML Dialog
+ *
+ * @param {Object} config
+ */
+export function DialogContructor (
+ config = {}
+) {
+ const initCfg = {
+ state: {},
+ slugs: {},
+ events: {},
+ elements: {},
+ templates: {},
+ }
+ const initialState = {
+ id: 'Dialog',
+ name: 'Dialog',
+ submitTxt: 'Submit',
+ submitAlt: 'Submit Form',
+ cancelTxt: 'Cancel',
+ cancelAlt: `Cancel Form`,
+ closeTxt: 'X',
+ closeAlt: `Close`,
+ placement: 'center',
+ rendered: null,
+ responsive: true,
+ delay: 500,
+ status: DIALOG_STATUS.NOT_LOADING,
+ }
+
+ config = {
+ ...initCfg,
+ ...config,
+ }
+
+ this.eventHandlers = []
+
+ this.state = createSignal({
+ ...initialState,
+ ...config.state,
+ })
+
+ this.slugs = {
+ dialog: toSlug(this.state.value.name, this.state.value.id),
+ form: toSlug(this.state.value.id, this.state.value.name),
+ ...config.slugs,
+ }
+
+ this.appElement = document.body
+
+ this.elements = {
+ ...config.elements,
+ }
+
+ this.markup = {}
+
+ effect(() => {
+ this.markup.header = html`
+
+ ${this.state.value.name}
+ ${
+ this.state.value.closeTxt && html`${this.state.value.closeTxt} `
+ }
+
+ `
+ this.markup.footer = html`
+
+
+ ${this.state.value.cancelTxt}
+
+
+ ${this.state.value.submitTxt}
+
+
+ `
+ })
+
+ this.markup.fields = html`
+
+ Thing
+
+
+
+
Some instructions
+ `
+
+ this.markup.content = () => html`
+ ${this.markup.header}
+
+
+ ${this.markup.fields}
+
+
+
+
+ ${this.markup.footer}
+ `
+
+ this.markup = {
+ ...this.markup,
+ ...config.markup,
+ }
+
+ this.events = {
+ input: event => {
+ },
+ change: event => {
+ },
+ blur: event => {
+ // event.preventDefault()
+ if (
+ event?.target?.validity?.patternMismatch &&
+ event?.target?.type !== 'checkbox'
+ ) {
+ event.preventDefault()
+ let label = event.target?.previousElementSibling?.textContent?.trim()
+ if (label) {
+ event.target.setCustomValidity(`Invalid ${label}`)
+ }
+ } else {
+ event.target.setCustomValidity('')
+ }
+ event.target.reportValidity()
+ },
+ focusout: event => {
+ // event.preventDefault()
+ },
+ focusin: event => {
+ // event.preventDefault()
+ },
+ drop: event => {
+ event.preventDefault()
+ },
+ dragover: event => {
+ event.preventDefault()
+ },
+ dragend: event => {
+ event.preventDefault()
+ },
+ dragleave: event => {
+ event.preventDefault()
+ },
+ render: (
+ state,
+ ) => {
+ },
+ show: (
+ state,
+ ) => {
+ // focus first input
+ this.elements.form.querySelector(
+ 'input'
+ )?.focus()
+ },
+ close: (
+ resolve = res=>{},
+ reject = res=>{},
+ ) => async event => {
+ event.preventDefault()
+ removeAllListeners.bind(this)
+
+ this.state.value.status = DIALOG_STATUS.NOT_LOADING
+ this.elements.dialog?.querySelector('progress')?.remove()
+
+ if (this.elements.dialog.returnValue !== 'cancel') {
+ resolve(this.elements.dialog.returnValue)
+ } else {
+ resolve('cancel')
+ }
+
+ setTimeout(t => {
+ this.state.value.rendered = null
+ event?.target?.remove()
+ }, this.state.value.delay)
+ },
+ submit: event => {
+ event.preventDefault()
+
+ let fde = formDataEntries(event)
+
+ this.elements.dialog.returnValue = String(fde.intent)
+
+ this.elements.dialog.close(String(fde.intent))
+ },
+ reset: event => {
+ event.preventDefault()
+ this.elements.form?.removeEventListener(
+ 'close',
+ this.events.reset
+ )
+ this.elements.dialog.close('cancel')
+ },
+ click: event => {
+ if (event.target === this.elements.dialog) {
+ this.elements.dialog.close('cancel')
+ }
+ },
+ }
+
+ const dialogElement = document.createElement('dialog')
+ const formElement = document.createElement('form')
+ const progressElement = document.createElement('progress')
+
+ this.elements.dialog = dialogElement
+ this.elements.form = formElement
+ this.elements.progress = progressElement
+
+ this.element = this.elements.dialog
+
+ progressElement.classList.add('pending')
+
+ dialogElement.innerHTML = ``
+ dialogElement.id = this.slugs.dialog
+ if (this.state.value.responsive) {
+ dialogElement.classList.add('responsive')
+ }
+ dialogElement.classList.add(...(this.state.value.placement.split(' ')))
+
+ formElement.name = `${this.slugs.form}`
+ formElement.method = 'dialog'
+ formElement.innerHTML = this.markup.content()
+
+ dialogElement.insertAdjacentElement(
+ 'afterbegin',
+ formElement
+ )
+
+ /**
+ * Show the dialog
+ * @function
+ */
+ this.show = (callback, el = this.appElement) => new Promise((resolve, reject) => {
+ removeAllListeners.call(this)
+ addListeners.call(this, resolve, reject)
+ console.log('modal.js dialog show', this.elements.dialog)
+
+ this.render({
+ el,
+ position: 'afterend'
+ })
+
+ this.elements.dialog.show()
+ this.events.show?.(this)
+ callback?.()
+ })
+
+ /**
+ * Show the Modal form of the dialog
+ * @function
+ */
+ this.showModal = (callback, el = this.appElement) => new Promise((resolve, reject) => {
+ removeAllListeners.call(this)
+ addListeners.call(this, resolve, reject)
+ console.log('modal.js dialog showModal', this, this.elements.dialog)
+
+ this.render({
+ el,
+ // cfg: config,
+ position: 'afterend'
+ })
+
+ this.elements.dialog.showModal()
+ this.events.show?.(this)
+ callback?.()
+ })
+
+ /**
+ * Close the dialog
+ * @function
+ */
+ this.close = returnVal => this.elements.dialog.close(returnVal)
+
+ /**
+ * Update the config of the dialog
+ * @function
+ */
+ this.updateConfig = (config = {}) => {
+ console.log('Dialog updateConfig TOP', config)
+
+ for (let param in config) {
+ if ('value' in this[param]) {
+ this[param].value = {
+ ...this[param].value,
+ ...(config[param] || {}),
+ }
+ } else {
+ this[param] = {
+ ...this[param],
+ ...(config[param] || {}),
+ }
+ }
+ }
+
+ console.log('Dialog updateConfig BOT', this)
+ }
+
+ /**
+ * Trigger the rendering of the dialog
+ * @function
+ */
+ this.render = ({
+ cfg = {},
+ position = 'afterend',
+ el = this.appElement,
+ }) => {
+ console.log('dialog render', this)
+
+ if (el !== this.appElement) {
+ this.appElement = el
+ }
+
+ this.updateConfig(cfg)
+
+ console.log('DIALOG elements', this, this.elements)
+
+ this.elements.dialog.id = this.slugs.dialog
+ this.elements.form.name = this.slugs.form
+ this.elements.form.innerHTML = this.markup.content()
+
+ console.log('DIALOG RENDER STATE', this.state.value, cfg)
+
+ console.log('DIALOG RENDER', position, this.slugs.dialog)
+
+ if (
+ this.state.value.status === DIALOG_STATUS.LOADING
+ ) {
+ this.elements.form.insertAdjacentElement(
+ 'beforebegin',
+ this.elements.progress,
+ )
+ }
+
+ if (
+ this.state.value.status === DIALOG_STATUS.SUCCESS ||
+ this.state.value.status === DIALOG_STATUS.ERROR
+ ) {
+ this.elements.dialog.querySelector('progress')?.remove()
+ }
+
+ if (!this.state.value.rendered) {
+ console.log('!this.state.rendered el', el, this.appElement, this.elements.dialog)
+ // @ts-ignore
+ el?.insertAdjacentElement(position, this.elements.dialog)
+ this.state.value.rendered = this.elements.dialog
+
+ this.elements.dialog.addEventListener(
+ 'close',
+ this.events.close
+ )
+ }
+
+ this.events.render(this.state)
+
+ return this
+ }
+}
+
+export default DialogContructor
diff --git a/src/components/nav.js b/src/components/nav.js
index 1b25755..dbe7b9b 100644
--- a/src/components/nav.js
+++ b/src/components/nav.js
@@ -1,4 +1,6 @@
-import { lit as html } from '../helpers/lit.js'
+import {
+ lit as html,
+} from '../utils/generic.js'
const initialState = {
data: {
diff --git a/src/components/send-request-btns.js b/src/components/send-request-btns.js
index 413a3ed..81c2560 100644
--- a/src/components/send-request-btns.js
+++ b/src/components/send-request-btns.js
@@ -1,4 +1,6 @@
-import { lit as html } from '../helpers/lit.js'
+import {
+ lit as html,
+} from '../utils/generic.js'
const initialState = {
rendered: null,
diff --git a/src/components/svg-sprite.js b/src/components/svg-sprite.js
index 3fa2055..74b3c74 100644
--- a/src/components/svg-sprite.js
+++ b/src/components/svg-sprite.js
@@ -1,4 +1,6 @@
-import { lit as svg } from '../helpers/lit.js'
+import {
+ lit as svg,
+} from '../utils/generic.js'
const initialState = {
rendered: null,
@@ -7,6 +9,10 @@ const initialState = {
+
+
+
+
diff --git a/src/components/transactions-list.js b/src/components/transactions-list.js
index 07aadae..f45ce9e 100644
--- a/src/components/transactions-list.js
+++ b/src/components/transactions-list.js
@@ -1,11 +1,15 @@
-import { lit as html } from '../helpers/lit.js'
+import {
+ lit as html,
+ timeago,
+ getAvatar,
+} from '../utils/generic.js'
import {
envoy,
restate,
+} from '../utils/retort.js'
+import {
sortTransactionsByTime,
- timeago,
- getAvatar,
-} from '../helpers/utils.js'
+} from '../utils/dash/local.js'
let _handlers = []
diff --git a/src/helpers/lit.js b/src/helpers/lit.js
deleted file mode 100644
index f976e5a..0000000
--- a/src/helpers/lit.js
+++ /dev/null
@@ -1,8 +0,0 @@
-/**
- *
- * @param {TemplateStringsArray} s
- * @param {...any} v
- *
- * @returns {string}
- */
-export const lit = (s, ...v) => String.raw({ raw: s }, ...v)
diff --git a/src/helpers/utils.js b/src/helpers/utils.js
deleted file mode 100644
index 493808f..0000000
--- a/src/helpers/utils.js
+++ /dev/null
@@ -1,1215 +0,0 @@
-import {
- DashHd,
- DashPhrase,
-} from '../imports.js'
-
-import {
- DUFFS,
- DASH_URI_REGEX,
- OIDC_CLAIMS,
- SUPPORTED_CLAIMS,
- TIMEAGO_LOCALE_EN,
- MOMENT, MOMENTS, NEVER,
- SECONDS, MINUTE, HOUR, DAY, WEEK, MONTH, YEAR,
-} from './constants.js'
-
-/**
- *
- * @param {String} [phraseOrXkey]
- * @param {Number} [accountIndex]
- * @param {Number} [addressIndex]
- * @param {Number} [usageIndex]
- *
- * @returns {Promise}
- */
-export async function deriveWalletData(
- phraseOrXkey,
- accountIndex = 0,
- addressIndex = 0,
- usageIndex = DashHd.RECEIVE,
-) {
- if (!phraseOrXkey) {
- throw new Error('Seed phrase or xkey value empty or invalid')
- }
-
- let recoveryPhrase
- let seed, derivedWallet, wpub, id, account
- let xkey, xprv, xpub, xkeyId
- let addressKey, addressKeyId, address
- let secretSalt = ''; // "TREZOR";
- let recoveryPhraseArr = phraseOrXkey.trim().split(' ')
-
- if (recoveryPhraseArr?.length >= 12) {
- recoveryPhrase = phraseOrXkey;
- }
-
- if (
- ['xprv', 'xpub'].includes(
- phraseOrXkey?.substring(0,4) || ''
- )
- ) {
- xkey = await DashHd.fromXKey(phraseOrXkey);
- } else {
- seed = await DashPhrase.toSeed(recoveryPhrase, secretSalt);
- derivedWallet = await DashHd.fromSeed(seed);
- wpub = await DashHd.toXPub(derivedWallet);
- id = await DashHd.toId(derivedWallet);
- account = await derivedWallet.deriveAccount(accountIndex);
- xkey = await account.deriveXKey(usageIndex);
- xprv = await DashHd.toXPrv(xkey);
- }
-
- xkeyId = await DashHd.toId(xkey);
- xpub = await DashHd.toXPub(xkey);
- addressKey = await xkey.deriveAddress(addressIndex);
- addressKeyId = await DashHd.toId(addressKey);
- address = await DashHd.toAddr(addressKey.publicKey);
-
- return {
- id,
- accountIndex,
- usageIndex,
- addressIndex,
- addressKeyId,
- addressKey,
- address,
- xkeyId,
- xkey,
- xprv,
- xpub,
- seed,
- wpub,
- account,
- derivedWallet,
- recoveryPhrase,
- }
-}
-
-/**
- *
- * @param {Number} [accountIndex]
- * @param {Number} [addressIndex]
- * @param {Number} [use]
- *
- * @returns {Promise}
- */
-export async function generateWalletData(
- accountIndex = 0,
- addressIndex = 0,
- use = DashHd.RECEIVE
-) {
- let targetBitEntropy = 128;
- let recoveryPhrase = await DashPhrase.generate(targetBitEntropy);
-
- return await deriveWalletData(
- recoveryPhrase,
- accountIndex,
- addressIndex,
- use
- )
-}
-
-/**
- *
- * @example
- * let acct = deriveAccountData(wallet, 0, 0, 0)
- *
- * @param {HDWallet} wallet
- * @param {Number} [accountIndex]
- * @param {Number} [addressIndex]
- * @param {Number} [use]
- *
- * @returns
- */
-export async function deriveAccountData(
- wallet,
- accountIndex = 0,
- addressIndex = 0,
- use = DashHd.RECEIVE,
-) {
- let account = await wallet.deriveAccount(accountIndex);
- let xkey = await account.deriveXKey(use);
- let xkeyId = await DashHd.toId(xkey);
- let xprv = await DashHd.toXPrv(xkey);
- let xpub = await DashHd.toXPub(xkey);
- let xpubKey = await DashHd.fromXKey(xpub);
- let xpubId = await DashHd.toId(xpubKey);
- let key = await xkey.deriveAddress(addressIndex);
- let address = await DashHd.toAddr(key.publicKey);
-
- return {
- account,
- xkeyId,
- xkey,
- xprv,
- xpub,
- xpubKey,
- xpubId,
- key,
- address
- }
-}
-
-/**
- *
- * @example
- * let addr = deriveAddressData(wallet, 0, 0, 0)
- *
- * @param {HDWallet} wallet
- * @param {Number} [accountIndex]
- * @param {Number} [addressIndex]
- * @param {Number} [use]
- *
- * @returns
- */
-export async function deriveAddressData(
- wallet,
- accountIndex = 0,
- addressIndex = 0,
- use = DashHd.RECEIVE,
-) {
- let account = await wallet.deriveAccount(accountIndex);
- let xkey = await account.deriveXKey(use);
- let key = await xkey.deriveAddress(addressIndex);
- let address = await DashHd.toAddr(key.publicKey);
-
- return address
-}
-
-// export async function batchAddressGenerate(
-// wallet,
-// accountIndex = 0,
-// addressIndex = 0,
-// use = DashHd.RECEIVE,
-// batchSize = 20
-// ) {
-// let batchLimit = addressIndex + batchSize
-// let addresses = []
-
-// let account = await wallet.deriveAccount(accountIndex);
-// let xkey = await account.deriveXKey(use);
-
-// for (;addressIndex < batchLimit; addressIndex++) {
-// let key = await xkey.deriveAddress(addressIndex);
-// let address = await DashHd.toAddr(key.publicKey);
-// addresses.push({
-// address,
-// addressIndex,
-// accountIndex,
-// })
-// }
-
-// return {
-// addresses,
-// finalAddressIndex: addressIndex,
-// }
-// }
-
-export function phraseToEl(phrase, el = 'span', cls = 'tag') {
- let words = phrase?.split(' ')
- return words?.map(
- w => `<${el} class="${cls}">${w}${el}>`
- )?.join(' ')
-}
-
-/**
- * @param {Number} duffs - ex: 00000000
- * @param {Number} [fix] - value for toFixed - ex: 8
- */
-export function toDash(duffs, fix = 8) {
- return (duffs / DUFFS).toFixed(fix);
-}
-
-/**
- * @param {String} dash - ex: 0.00000000
- */
-export function toDashStr(dash, pad = 12) {
- return `Đ ` + `${dash}`.padStart(pad, " ");
-}
-
-/**
- * Based on https://stackoverflow.com/a/48100007
- *
- * @param {Number} dash - ex: 0.00000000
- * @param {Number} [fix] - value for toFixed - ex: 8
- */
-export function fixedDash(dash, fix = 8) {
- return (
- Math.trunc(dash * Math.pow(10, fix)) / Math.pow(10, fix)
- )
- .toFixed(fix);
-}
-
-// https://stackoverflow.com/a/27946310
-export function roundUsing(func, number, prec = 8) {
- var tempnumber = number * Math.pow(10, prec);
- tempnumber = func(tempnumber);
- return tempnumber / Math.pow(10, prec);
-}
-
-/**
- * @param {Number} duffs - ex: 00000000
- */
-export function toDASH(duffs) {
- let dash = toDash(duffs / DUFFS);
- return toDashStr(dash);
-}
-
-/**
- * @param {Number} dash - ex: 0.00000000
- * @param {Number} [fix] - value for toFixed - ex: 8
- */
-export function fixedDASH(dash, fix = 8) {
- return toDashStr(fixedDash(dash, fix));
-}
-
-/**
- * @param {String} dash - ex: 0.00000000
- */
-export function toDuff(dash) {
- return Math.round(parseFloat(dash) * DUFFS);
-}
-
-export function formatDash(
- unformattedBalance,
- options = {},
-) {
- let opts = {
- maxlen: 10,
- fract: 8,
- sigsplit: 3,
- ...options,
- }
- let funds = 0
- let balance = `${funds}`
-
- if (unformattedBalance) {
- funds += unformattedBalance
- balance = fixedDash(funds, opts.fract)
- // TODO FIX: does not support large balances
-
- // console.log('balance fixedDash', balance, balance.length)
-
- let [fundsInt,fundsFract] = balance.split('.')
- opts.maxlen -= fundsInt.length
-
- let fundsFraction = fundsFract?.substring(
- 0, Math.min(Math.max(0, opts.maxlen), opts.sigsplit)
- )
-
- let fundsRemainder = fundsFract?.substring(
- fundsFraction.length,
- Math.max(0, opts.maxlen)
- )
-
- balance = `${
- fundsInt
- }.${
- fundsFraction
- } ${
- fundsRemainder
- } `
- }
-
- return balance
-}
-
-export function formDataEntries(event) {
- let fd = new FormData(
- event.target,
- event.submitter
- )
-
- return Object.fromEntries(fd.entries())
-}
-
-export function copyToClipboard(target) {
- target.select();
- document.execCommand("copy");
-}
-
-export function setClipboard(event) {
- event.preventDefault()
- let el = event.target?.previousElementSibling
- let val = el.textContent?.trim()
- if (el.nodeName === 'INPUT') {
- val = el.value?.trim()
- }
- const type = "text/plain";
- const blob = new Blob([val], { type });
-
- if (
- "clipboard" in navigator &&
- typeof navigator.clipboard.write === "function"
- ) {
- const data = [new ClipboardItem({ [type]: blob })];
-
- navigator.clipboard.write(data).then(
- cv => {
- console.log('setClipboard', cv)
- },
- ce => {
- console.error('[fail] setClipboard', ce)
- }
- );
- } else {
- copyToClipboard(el)
- }
-}
-
-export function openBlobSVG(target) {
- const svgStr = new XMLSerializer().serializeToString(target);
- const svgBlob = new Blob([svgStr], { type: "image/svg+xml" });
- const url = URL.createObjectURL(svgBlob);
- const win = open(url);
- win.onload = (evt) => URL.revokeObjectURL(url);
-}
-
-/**
- * Creates a `Proxy` wrapped object with optional listeners
- * that react to changes
- *
- * @example
- * let fooHistory = []
- *
- * let kung = envoy(
- * { foo: 'bar' },
- * function firstListener(state, oldState) {
- * if (state.foo !== oldState.foo) {
- * localStorage.foo = state.foo
- * },
- * },
- * async function secondListener(state, oldState) {
- * if (state.foo !== oldState.foo) {
- * fooHistory.push(oldState.foo)
- * }
- * }
- * )
- * kung.foo = 'baz'
- * console.log(localStorage.foo) // 'baz'
- * kung.foo = 'boo'
- * console.log(fooHistory) // ['bar','baz']
- *
- * @param {Object} obj
- * @param {...(
- * state: any, oldState: any, prop: string | symbol
- * ) => void | Promise?} [initListeners]
- *
- * @returns {obj}
- */
-export function envoy(obj, ...initListeners) {
- let _listeners = [...initListeners]
- return new Proxy(obj, {
- get(obj, prop, receiver) {
- if (prop === '_listeners') {
- return _listeners
- }
- return Reflect.get(obj, prop, receiver)
- },
- set(obj, prop, value) {
- if (
- prop === '_listeners' &&
- Array.isArray(value)
- ) {
- _listeners = value
- }
-
- _listeners.forEach(
- fn => fn(
- {...obj, [prop]: value},
- obj,
- prop
- )
- )
-
- obj[prop] = value
-
- return true
- }
- })
-}
-
-/**
- * Creates a reactive signal
- *
- * Inspired By
- * {@link https://gist.github.com/developit/a0430c500f5559b715c2dddf9c40948d Valoo} &
- * {@link https://dev.to/ratiu5/implementing-signals-from-scratch-3e4c Signals from Scratch}
- *
- * @example
- * let count = createSignal(0)
- * console.log(count.value) // 0
- * count.value = 2
- * console.log(count.value) // 2
- *
- * let off = count.on((value) => {
- * document.querySelector("body").innerHTML = value;
- * });
- *
- * off(); // unsubscribe
- *
- * @param {Object} initialValue inital value
-*/
-export function createSignal(initialValue) {
- let _value = initialValue;
- let _last = _value;
- const subs = [];
-
- function pub() {
- for (let s of subs) {
- s && s(_value, _last);
- }
- }
-
- return {
- get value() { return _value; },
- set value(v) {
- _last = _value
- _value = v;
- pub();
- },
- on: s => {
- const i = subs.push(s)-1;
- return () => { subs[i] = 0; };
- }
- }
-}
-
-export async function restate(
- state = {},
- renderState = {},
-) {
- let renderKeys = Object.keys(renderState)
-
- for await (let prop of renderKeys) {
- state[prop] = renderState[prop]
- }
-
- return state
-}
-
-export function filterPairedContacts(contact) {
- let outLen = Object.keys(contact.outgoing || {}).length
- return outLen > 0 // && !!contact.alias
-}
-
-export function filterUnpairedContacts(contact) {
- return !filterPairedContacts(contact)
-}
-
-export function sortContactsByAlias(a, b) {
- const aliasA = a.alias || a.info?.preferred_username?.toUpperCase() || 'zzz';
- const aliasB = b.alias || b.info?.preferred_username?.toUpperCase() || 'zzz';
-
- if (aliasA < aliasB) {
- return -1;
- }
- if (aliasA > aliasB) {
- return 1;
- }
- return 0;
-}
-
-export function sortContactsByName(a, b) {
- const nameA = a.info?.name?.toUpperCase();
- const nameB = b.info?.name?.toUpperCase();
-
- if (nameA < nameB) {
- return -1;
- }
- if (nameA > nameB) {
- return 1;
- }
- return 0;
-}
-
-export function sortTransactionsByTime(a, b) {
- const timeA = a.time;
- const timeB = b.time;
-
- if (timeA > timeB) {
- return -1;
- }
- if (timeA < timeB) {
- return 1;
- }
- return 0;
-}
-
-export function DashURLSearchParams(params) {
- let searchParams
- let qry = {}
-
- Object.defineProperty(this, "entries", {
- enumerable: false,
- configurable: false,
- writable: false,
- value: () => Object.entries(qry),
- });
- Object.defineProperty(this, "toString", {
- enumerable: false,
- configurable: false,
- writable: false,
- value: () => this.entries().map(p => p.join('=')).join('&'),
- });
- Object.defineProperty(this, "size", {
- get() { return this.entries().length },
- enumerable: false,
- configurable: false,
- });
-
- if (typeof params === 'string' && params !== '') {
- searchParams = params.split('&')
- searchParams.forEach(q => {
- let [prop,val] = q.split('=')
- qry[prop] = val
- })
- }
-
- if(Array.isArray(params) && params.length > 0) {
- params.forEach(q => {
- let [prop,val] = q
- qry[prop] = val
- })
- }
-
- // console.log('DashURLSearchParams', {
- // params, searchParams, qry,
- // qryStr: this.toString(),
- // })
-}
-
-export function parseDashURI(uri) {
- let result = {}
- let parsedUri = [
- ...uri.matchAll(DASH_URI_REGEX)
- ]?.[0]?.groups || {}
- // let searchParams = new URLSearchParams(parsedUri?.params || '')
- let searchParams = new DashURLSearchParams(parsedUri?.params || '')
-
- console.log(
- 'parseDashURI',
- parsedUri,
- searchParams
- )
-
- if (parsedUri?.address) {
- result.address = parsedUri?.address
- }
-
- if (searchParams?.size > 0) {
- let claims = Object.fromEntries(
- searchParams?.entries()
- )
-
- for (let c in claims) {
- if (SUPPORTED_CLAIMS.includes(c)) {
- result[c] = claims[c]
- }
- }
- }
-
- return result
-}
-
-export function parseAddressField(uri) {
- /* @type {Record} */
- let result = {}
-
- if (uri.includes(':')) {
- let [protocol] = uri.split(':')
- if (protocol.includes('dash')) {
- // @ts-ignore
- result = parseDashURI(uri)
- }
- } else if (
- 'xprv' === uri?.substring(0,4)
- ) {
- result.xprv = uri
- } else if (
- 'xpub' === uri?.substring(0,4)
- ) {
- result.xpub = uri
- } else {
- result.address = uri
- }
-
- return result
-}
-
-export function isEmpty(value) {
- if (value === null) {
- return true
- }
- // if (typeof value === 'boolean' && value === false) {
- // return true
- // }
- if (typeof value === 'string' && value?.length === 0) {
- return true
- }
- if (typeof value === 'object' && Object.keys(value)?.length === 0) {
- return true
- }
- if (Array.isArray(value) && value.length === 0) {
- return true
- }
- return false;
-}
-
-export function generateContactPairingURI(
- state,
- protocol = 'dash', // 'web+dash'
- joiner = ':'
-) {
- let addr = state.wallet?.address || ''
- let claims = [
- ["xpub", state.wallet?.xpub || ''],
- ["sub", state.wallet?.xkeyId || ''],
- ]
-
- if (state.userInfo) {
- let filteredInfo = Array.from(
- Object.entries(state.userInfo)
- ).filter(p => {
- let [key, val] = p
- if (
- ![
- // 'updated_at',
- 'email_verified',
- 'phone_number_verified',
- ].includes(key) &&
- !isEmpty(val)
- ) {
- return true
- }
- })
-
- claims = [
- ...claims,
- ...filteredInfo,
- ]
- }
-
- let scope = claims.map(p => p[0]).join(',')
- // let searchParams = new URLSearchParams([
- // ...claims,
- // ['scope', scope]
- // ])
- let searchParams = new DashURLSearchParams([
- ...claims,
- ['scope', scope]
- ])
-
- console.log(
- 'Generate Dash URI claims',
- claims, scope, searchParams,
- searchParams.size,
- searchParams.entries(),
- )
-
- let res = `${protocol}${joiner}${addr}`
-
- if (searchParams.size > 0) {
- res += `?${searchParams.toString()}`
- }
-
- return res
-}
-
-export function generatePaymentRequestURI(
- state,
- protocol = 'dash',
- joiner = ':'
-) {
- let addr = state.wallet?.address || ''
- let claims = []
-
- if (state.userInfo) {
- let filteredInfo = Array.from(
- Object.entries(state.userInfo)
- ).filter(p => {
- let [key, val] = p
- if (
- ![
- 'updated_at',
- 'email_verified',
- 'phone_number_verified',
- ].includes(key) &&
- !isEmpty(val)
- ) {
- return true
- }
- })
-
- claims = [
- ...filteredInfo,
- ]
- }
-
- if (state.amount > 0) {
- claims.push(
- ["amount", state.amount],
- )
- }
-
- if (state.label) {
- claims.push(
- ["label", state.label],
- )
- }
-
- if (state.message) {
- claims.push(
- ["message", state.message],
- )
- }
-
- // let searchParams = new URLSearchParams([
- // ...claims,
- // ])
- let searchParams = new DashURLSearchParams([
- ...claims,
- ])
-
- let res = `${protocol}${joiner}${addr}`
-
- if (searchParams.size > 0) {
- res += `?${searchParams.toString()}`
- }
-
- return res
-}
-
-export async function getStoreData(
- store,
- callback,
- iterableCallback = res => async (v, k, i) => res.push(v)
-) {
- let result = []
-
- return await store.keys().then(async function(keys) {
- for (let k of keys) {
- let v = await store.getItem(k)
- await iterableCallback(result)(v, k)
- }
-
- callback?.(result)
-
- return result
- }).catch(function(err) {
- console.error('getStoreData', err)
- return null
- });
-}
-
-export async function loadStore(
- store,
- callback,
- iterableCallback = res => v => res.push(v)
-) {
- let result = []
-
- return await store.iterate(iterableCallback(result))
- .then(() => callback?.(result))
- .catch(err => {
- console.error('loadStore', err)
- return null
- });
-}
-
-export async function loadStoreObject(store, callback) {
- // let storeLen = await store.length()
- let result = {}
-
- return await store.iterate((v, k, i) => {
- result[k] = v
-
- // if (i === storeLen) {
- // return result
- // }
- })
- .then(() => callback?.(result))
- .then(() => result)
- .catch(err => {
- console.error('loadStoreObject', err)
- return null
- });
-}
-
-/**
- * promise debounce changes
- *
- * https://www.freecodecamp.org/news/javascript-debounce-example/
- *
- * @example
- * const change = debounce((a) => console.log('Saving data', a));
- * change('b');change('c');change('d');
- * 'Saving data d'
- *
- * @param {(...args) => void} callback
- * @param {number} [delay]
-*
-* @returns {Promise}
-*/
-export async function debouncePromise(callback, delay = 300) {
- let timer
-
- return await new Promise(resolve => async (...args) => {
- clearTimeout(timer)
-
- timer = setTimeout(() => {
- resolve(callback.apply(this, args))
- }, delay)
- })
-
- // return async (...args) => {
- // clearTimeout(timer)
-
- // timer = resolve => setTimeout(() => {
- // resolve(callback.apply(this, args))
- // }, delay)
-
- // return await new Promise(timer)
- // }
-}
-
-/**
- * debounce changes
- *
- * https://www.freecodecamp.org/news/javascript-debounce-example/
- *
- * @example
- * const change = debounce((a) => console.log('Saving data', a));
- * change('b');change('c');change('d');
- * 'Saving data d'
- *
- * @param {(...args) => void} callback
- * @param {number} [delay]
-*
-* @returns {(...args) => void}
-*/
-export function debounce(callback, delay = 300) {
- let timer
-
- return (...args) => {
- clearTimeout(timer)
-
- timer = setTimeout(() => {
- return callback.apply(this, args)
- }, delay)
-
- return timer
- }
-}
-
-/**
- * debounce that immediately triggers and black holes any extra
- * executions within the time delay
- *
- * https://www.freecodecamp.org/news/javascript-debounce-example/
- *
- * @example
- * const dry = nobounce((a) => console.log('Saving data', a));
- * dry('b');dry('c');dry('d');
- * 'Saving data b'
- *
- * @param {(...args) => void} callback
- * @param {number} [delay]
-*
-* @returns {(...args) => void}
-*/
-export function nobounce(callback, delay = 300) {
- let timer
-
- return (...args) => {
- if (!timer) {
- callback.apply(this, args)
- }
-
- clearTimeout(timer)
-
- timer = setTimeout(() => {
- timer = undefined
- }, delay)
- }
-}
-
-export function timeago(ms, locale = TIMEAGO_LOCALE_EN) {
- var ago = Math.floor(ms / 1000);
- var part = 0;
-
- if (ago < MOMENTS) { return locale.moment; }
- if (ago < SECONDS) { return locale.moments; }
- if (ago < MINUTE) { return locale.seconds.replace(/%\w?/, `${ago}`); }
-
- if (ago < (2 * MINUTE)) { return locale.minute; }
- if (ago < HOUR) {
- while (ago >= MINUTE) { ago -= MINUTE; part += 1; }
- return locale.minutes.replace(/%\w?/, `${part}`);
- }
-
- if (ago < (2 * HOUR)) { return locale.hour; }
- if (ago < DAY) {
- while (ago >= HOUR) { ago -= HOUR; part += 1; }
- return locale.hours.replace(/%\w?/, `${part}`);
- }
-
- if (ago < (2 * DAY)) { return locale.day; }
- if (ago < WEEK) {
- while (ago >= DAY) { ago -= DAY; part += 1; }
- return locale.days.replace(/%\w?/, `${part}`);
- }
-
- if (ago < (2 * WEEK)) { return locale.week; }
- if (ago < MONTH) {
- while (ago >= WEEK) { ago -= WEEK; part += 1; }
- return locale.weeks.replace(/%\w?/, `${part}`);
- }
-
- if (ago < (2 * MONTH)) { return locale.month; }
- if (ago < YEAR) { // 45 years, approximately the epoch
- while (ago >= MONTH) { ago -= MONTH; part += 1; }
- return locale.months.replace(/%\w?/, `${part}`);
- }
-
- if (ago < NEVER) {
- return locale.years;
- }
-
- return locale.never;
-}
-
-export async function sha256(str) {
- const buf = await crypto.subtle.digest(
- "SHA-256", new TextEncoder().encode(str)
- );
- return Array.prototype.map.call(
- new Uint8Array(buf),
- x => (('00' + x.toString(16)).slice(-2))
- ).join('');
-}
-
-// https://stackoverflow.com/a/66494926
-export function getBackgroundColor(stringInput) {
- let stringUniqueHash = [...stringInput].reduce((acc, char) => {
- return char.charCodeAt(0) + ((acc << 5) - acc);
- }, 0);
- return `hsl(${stringUniqueHash % 360}, 100%, 67%)`;
-}
-
-export async function getAvatarUrl(
- email,
- size = 48,
- rating = 'pg',
- srv = 'gravatar',
-) {
- let emailSHA = await sha256(email || '')
-
- if (srv === 'gravatar') {
- return `https://gravatar.com/avatar/${
- emailSHA
- }?s=${size}&r=${rating}&d=retro`
- }
- if (srv === 'libravatar') {
- return `https://seccdn.libravatar.org/avatar/${
- emailSHA
- }?s=${size}&r=${rating}&d=retro`
- }
-
- return ''
-}
-
-export async function getAvatar(c) {
- let initials = c?.info?.name?.
- split(' ').map(n => n[0]).slice(0,3).join('') || ''
- let nameOrAlias = c?.info?.name || c?.alias || c?.info?.preferred_username
-
- if (!initials) {
- initials = (c?.alias || c?.info?.preferred_username)?.[0] || ''
- }
-
- let avStr = `${initials}
`
-}
-
-export function fileIsSubType(file, type) {
- const fileType = file?.type?.split('/')?.[1]
-
- if (!fileType) {
- return false
- }
-
- return fileType === type
-}
-
-// fileInTypes({type:'application/json'}, ['image/png'])
-export function fileInMIMETypes(file, types = []) {
- const fileType = file?.type
-
- if (!fileType) {
- return false
- }
-
- return types.includes(fileType)
-}
-
-export function fileTypeInTypes(file, types = []) {
- const fileType = file?.type?.split('/')?.[0]
-
- if (!fileType) {
- return false
- }
-
- return types.includes(fileType)
-}
-
-export function fileTypeInSubtype(file, subtypes = []) {
- const fileSubType = file?.type?.split('/')?.[1]
-
- if (!fileSubType) {
- return false
- }
-
- return subtypes.includes(fileSubType)
-}
-
-export function readFile(file, options) {
- let opts = {
- expectedFileType: 'json',
- denyFileTypes: ['audio','video','image','font','model'],
- denyFileSubTypes: ['msword','xml'],
- callback: () => {},
- errorCallback: () => {},
- ...options,
- }
- let reader = new FileReader();
- let result
-
- reader.addEventListener('load', () => {
- if (
- fileTypeInTypes(
- file,
- opts.denyFileTypes,
- ) || fileTypeInSubtype(
- file,
- opts.denyFileSubTypes,
- )
- ) {
- return opts.errorCallback?.({
- err: `Wrong file type: ${file.type}. Expected: ${opts.expectedFileType}.`,
- file,
- })
- }
-
- try {
- // @ts-ignore
- result = JSON.parse(reader?.result || '{}');
-
- // console.log('parse loaded json', result);
-
- opts.callback?.(result, file)
-
- // state[key] = result
- } catch(err) {
- opts.errorCallback?.({
- err,
- file,
- })
-
- throw new Error(`failed to parse JSON data from ${file.name}`)
- }
- });
-
- reader.readAsText(file);
-}
-
-export async function getRandomWords(len = 32) {
- return await DashPhrase.generate(len)
-}
-
-export async function verifyPhrase(phrase) {
- return await DashPhrase.verify(phrase).catch(_ => false)
-}
-
-export function isUniqueAlias(aliases, preferredAlias) {
- return !aliases[preferredAlias]
-}
-
-export async function getUniqueAlias(aliases, preferredAlias) {
- let uniqueAlias = preferredAlias
- let notUnique = !isUniqueAlias(aliases, uniqueAlias)
-
- if (notUnique) {
- let aliasArr = uniqueAlias.split('_')
- let randomWords = (await getRandomWords()).split(' ')
-
- if (aliasArr.length > 1) {
- let lastWord = aliasArr.pop()
- let index = DashPhrase.base2048.indexOf(lastWord);
-
- if (index < 0) {
- aliasArr.push(lastWord)
- } else {
- aliasArr.push(randomWords[0])
- }
- } else {
- aliasArr.push(randomWords[0])
- }
-
- uniqueAlias = aliasArr.join('_')
-
- return await getUniqueAlias(aliases, uniqueAlias)
- }
-
- return uniqueAlias
-}
-
-export function getPartialHDPath(wallet) {
- return [
- wallet.accountIndex,
- wallet.usageIndex,
- wallet.addressIndex,
- ].join('/')
-}
-
-export function getAddressIndexFromUsage(wallet, account, usageIdx) {
- let usageIndex = usageIdx ?? wallet?.usageIndex ?? 0
- let addressIndex = account.usage?.[usageIndex] ?? account.addressIndex ?? 0
- let usage = account.usage ?? [
- account.addressIndex ?? 0,
- 0
- ]
-
- // console.log(
- // 'getAddressIndexFromUsage',
- // usageIndex,
- // addressIndex,
- // account,
- // usage,
- // )
-
- return {
- ...account,
- usage,
- usageIndex,
- addressIndex,
- }
-}
diff --git a/src/helpers/wallet.js b/src/helpers/wallet.js
deleted file mode 100644
index 78de19f..0000000
--- a/src/helpers/wallet.js
+++ /dev/null
@@ -1,2032 +0,0 @@
-import {
- DashWallet,
- DashTx,
- DashHd,
- DashSight,
- DashSocket,
- Cryptic,
-} from '../imports.js'
-import {
- DatabaseSetup,
-} from './db.js'
-import {
- deriveWalletData,
- getAddressIndexFromUsage,
- loadStoreObject,
-} from './utils.js'
-import {
- STOREAGE_SALT, OIDC_CLAIMS,
- KS_CIPHER, KS_PRF, USAGE,
-} from './constants.js'
-import {
- walletFunds,
-} from '../state/index.js'
-
-// @ts-ignore
-import blake from 'blakejs'
-// @ts-ignore
-import { keccak_256 } from '@noble/hashes/sha3'
-
-// @ts-ignore
-export const dashsight = DashSight.create({
- baseUrl: 'https://insight.dash.org',
- // baseUrl: 'https://dashsight.dashincubator.dev',
-});
-
-let defaultSocketEvents = {
- onClose: async (e) => console.log('onClose', e),
- onError: async (e) => console.log('onError', e),
- onMessage: async (e, data) => console.log('onMessage', e, data),
-}
-
-// Cryptic.setConfig({
-// // cipherAlgorithm: 'AES-GCM',
-// // cipherLength: 256,
-// // hashingAlgorithm: 'SHA-256',
-// // derivationAlgorithm: 'PBKDF2',
-// iterations: 1000,
-// })
-
-export async function initDashSocket(
- events = {}
-) {
- // @ts-ignore
- let dashsocket = DashSocket.create({
- dashsocketBaseUrl: 'https://insight.dash.org/socket.io',
- cookieStore: null,
- debug: true,
- ...defaultSocketEvents,
- ...events,
- })
-
- await dashsocket.init()
- .catch((e) => console.log('dashsocket catch err', e));
-
- // setTimeout(() => {
- // dashsocket.close()
- // }, 15*60*1000);
-
- return dashsocket
-}
-
-export const store = await DatabaseSetup()
-
-export async function getStoredItems(targStore) {
- let result = {}
- let storeLen = await targStore.length()
-
- return await targStore.iterate((
- value, key, iterationNumber
- ) => {
- result[key] = value
-
- if (iterationNumber === storeLen) {
- return result
- }
- })
-}
-
-export async function getFilteredStoreLength(targStore, query = {}) {
- let resLength = 0
- let storeLen = await targStore.length()
- let qs = Object.entries(query)
-
- // console.log('getFilteredStoreLength qs', {
- // storeName: targStore?._config?.storeName,
- // storeLen,
- // qs,
- // })
-
- if (storeLen === 0) {
- return 0
- }
-
- return await targStore.iterate((
- value, key, iterationNumber
- ) => {
- let res = true
-
- // console.log('getFilteredStoreLength qs before each', key, res)
-
- qs.forEach(([k,v]) => {
- // console.log('getFilteredStoreLength qs each', k, v, value[k])
- if (k === 'key' && key !== v || value[k] !== v) {
- res = undefined
- }
- })
-
- // console.log('getFilteredStoreLength qs after each', key, res)
-
- if (res) {
- resLength += 1
- }
-
- if (iterationNumber === storeLen) {
- return resLength
- }
- })
-}
-
-export async function findInStore(targStore, query = {}) {
- let result = {}
- let storeLen = await targStore.length()
- let qs = Object.entries(query)
- // console.log('findInStore qs', qs)
-
- return await targStore.iterate((
- value, key, iterationNumber
- ) => {
- let res = value
-
- // console.log('findInStore qs before each', key, res)
-
- qs.forEach(([k,v]) => {
- // console.log('findInStore qs each', k, v, value[k])
- if (k === 'key' && key !== v || value[k] !== v) {
- res = undefined
- }
- })
-
- // console.log('findInStore qs after each', key, res)
-
- if (res) {
- result[key] = res
- }
-
- if (iterationNumber === storeLen) {
- return result
- }
- })
-}
-
-export async function findOneInStore(targStore, query = {}) {
- let storeLen = await targStore.length()
- let qs = Object.entries(query)
-
- return await targStore.iterate((
- value, key, iterationNumber
- ) => {
- let res = value
-
- qs.forEach(([k,v]) => {
- if (k === 'key' && key !== v || value[k] !== v) {
- res = undefined
- }
- })
-
- if (res) {
- return res
- }
-
- if (iterationNumber === storeLen) {
- return undefined
- }
- })
-}
-
-export async function getUnusedChangeAddress(account) {
- let filterQuery = {
- xkeyId: account.xkeyId,
- usageIndex: DashHd.CHANGE,
- }
-
- let foundAddrs = await findInStore(store.addresses, filterQuery)
-
- for (let [fkey,fval] of Object.entries(foundAddrs)) {
- if (!fval.insight?.balance) {
- return fkey
- }
- }
-
- // return foundAddr.address
- return null
-}
-
-export async function loadWalletsForAlias($alias) {
- $alias.$wallets = {}
-
- if ($alias?.wallets) {
- for (let w of $alias.wallets) {
- let wallet = await store.wallets.getItem(w)
- $alias.$wallets[w] = wallet
- }
- }
-
- return $alias
-}
-
-export async function initWalletsInfo(
- info = {},
-) {
- let wallets = await getStoredItems(store.wallets)
-
- info = {
- ...OIDC_CLAIMS,
- ...info,
- }
-
- let alias = info.preferred_username
-
- wallets = Object.values(wallets || {})
- wallets = wallets
- .filter(w => w.alias === alias)
- .map(w => w.id)
-
- return {
- alias,
- wallets,
- info,
- }
-}
-
-export async function decryptWallet(
- decryptPass,
- decryptIV,
- decryptSalt,
- ciphertext,
-) {
- const cryptic = Cryptic.create(
- decryptPass,
- decryptSalt,
- // Cryptic.bufferToHex(
- // Cryptic.stringToBuffer(decryptSalt)
- // )
- )
-
- return await cryptic.decrypt(ciphertext, decryptIV);
-}
-
-export function blake256(data) {
- if ('string' === typeof data) {
- data = Cryptic.hexToBuffer(data)
- }
- const context = blake.blake2bInit(32, null);
- blake.blake2bUpdate(context, data);
- return Cryptic.toHex(blake.blake2bFinal(context));
-}
-
-export function getKeystoreData(keystore) {
- const {
- ciphertext,
- cipher,
- mac,
- } = keystore.crypto
- const [
- cipherAlgorithm,
- cipherLength,
- ] = KS_CIPHER[cipher]
-
- const derivationAlgorithm = keystore.crypto.kdf.toUpperCase()
- const hashingAlgorithm = KS_PRF[keystore.crypto.kdfparams.prf]
- const derivedKeyLength = keystore?.crypto?.kdfparams?.dklen
- const iterations = keystore.crypto.kdfparams.c
- const iv = keystore.crypto.cipherparams.iv
- const ivBuffer = Cryptic.hexToBuffer(iv)
- const salt = keystore.crypto.kdfparams.salt
- const saltBuffer = Cryptic.hexToBuffer(salt)
-
- const keyLength = derivedKeyLength / 2
- const numBits = (keyLength + iv.length) * 8
-
- return {
- cipher,
- cipherAlgorithm,
- cipherLength,
- ciphertext,
- mac,
- derivationAlgorithm,
- hashingAlgorithm,
- derivedKeyLength,
- iterations,
- iv,
- ivBuffer,
- salt,
- saltBuffer,
- keyLength,
- numBits,
- }
-}
-
-export async function setupCryptic(
- encryptionPassword,
- keystore,
-) {
- const ks = getKeystoreData(keystore)
- const {
- cipherLength, cipherAlgorithm,
- derivationAlgorithm, hashingAlgorithm, iv,
- iterations, salt,
- } = ks
-
- Cryptic.setConfig({
- cipherAlgorithm,
- cipherLength,
- hashingAlgorithm,
- derivationAlgorithm,
- iterations,
- })
-
- const cryptic = Cryptic.create(
- encryptionPassword,
- salt,
- );
-
- return {
- Cryptic,
- cryptic,
- ks,
- }
-}
-
-export async function encryptData(
- encryptionPassword,
- keystore,
- data,
-) {
- const { cryptic, ks } = await setupCryptic(
- encryptionPassword,
- keystore,
- )
-
- return await cryptic.encrypt(data, ks.iv);
-}
-
-export async function decryptData(
- encryptionPassword,
- keystore,
- data,
-) {
- const { cryptic, ks } = await setupCryptic(
- encryptionPassword,
- keystore,
- )
-
- return await cryptic.decrypt(data, ks.iv)
-}
-
-export function storedData(
- encryptionPassword,
- keystore,
-) {
- const SD = {}
-
- SD.decryptData = async function(data) {
- if (data && 'string' === typeof data && data.length > 0) {
- data = JSON.parse(await decryptData(
- encryptionPassword,
- keystore,
- data
- ))
- }
-
- return data
- }
-
- SD.decryptItem = async function(targetStore, item,) {
- let data = await targetStore.getItem(
- item,
- )
-
- data = await SD.decryptData(data)
-
- return data
- }
-
- /**
- *
- * @param {*} targetStore
- * @param {*} item
- * @param {*} data
- * @param {*} extend
- * @returns {Promise<[String,Object]>}
- */
- SD.encryptData = async function(
- targetStore, item, data = {}, extend = true
- ) {
- let encryptedData = ''
- let storedData = {}
- let jsonData = {}
- if (extend) {
- // storedData = await targetStore.getItem(
- // item,
- // )
- storedData = await SD.decryptItem(
- targetStore,
- item
- )
- }
-
- if (data) {
- jsonData = {
- ...storedData,
- ...data,
- }
- encryptedData = await encryptData(
- encryptionPassword,
- keystore,
- JSON.stringify(jsonData)
- )
- }
-
- return [
- encryptedData,
- jsonData,
- ]
- }
-
- SD.encryptItem = async function(
- targetStore, item, data = {}, extend = true
- ) {
- let encryptedData = ''
- let encryptedResult = ''
- let result = {}
-
- if (data || extend) {
- let d = await SD.encryptData(targetStore, item, data, extend)
- encryptedResult = d[0]
- result = d[1]
- encryptedData = await targetStore.setItem(
- item,
- encryptedResult
- )
- }
-
- return result || data || encryptedData
- // return encryptedData
- }
-
- return SD
-}
-
-export async function decryptKeystore(
- encryptionPassword,
- keystore,
-) {
- const { Cryptic, cryptic, ks } = await setupCryptic(
- encryptionPassword,
- keystore,
- )
-
- const derivedBytes = await cryptic.deriveBits(ks.numBits, ks.salt)
-
- const bMAC = blake256([
- ...new Uint8Array(derivedBytes.slice(16, 32)),
- ...Cryptic.toBytes(ks.ciphertext),
- ])
- const kMAC = Cryptic.toHex(keccak_256(new Uint8Array([
- ...new Uint8Array(derivedBytes.slice(16, 32)),
- ...Cryptic.toBytes(ks.ciphertext),
- ])));
-
- if (ks.mac && ![bMAC, kMAC].includes(ks.mac)) {
- throw new Error('Invalid password')
- }
-
- return await cryptic.decrypt(ks.ciphertext, ks.iv)
-}
-
-export function genKeystore(
- // aes-256-gcm
- cipher = 'aes-128-ctr',
- salt = Cryptic.randomBytes(32),
- iv = Cryptic.randomBytes(16),
- iterations = 262144,
- id = crypto.randomUUID(),
-) {
- return {
- crypto: {
- cipher,
- ciphertext: '',
- cipherparams: {
- iv: Cryptic.bufferToHex(iv),
- },
- kdf: "pbkdf2",
- kdfparams: {
- c: iterations,
- dklen: 32,
- prf: "hmac-sha256",
- salt: Cryptic.bufferToHex(salt),
- },
- mac: '',
- },
- id,
- meta: 'dash-incubator-keystore',
- version: 3,
- }
-}
-
-export async function encryptKeystore(
- encryptionPassword,
- recoveryPhrase,
-) {
- let keystore = genKeystore()
- const { Cryptic, cryptic, ks } = await setupCryptic(
- encryptionPassword,
- keystore,
- )
-
- const derivedBytes = await cryptic.deriveBits(ks.numBits, ks.salt)
- const encryptedPhrase = await cryptic.encrypt(recoveryPhrase, ks.iv);
-
- keystore.crypto.ciphertext = encryptedPhrase
-
- const bMAC = blake256([
- ...new Uint8Array(derivedBytes.slice(16, 32)),
- ...Cryptic.toBytes(keystore.crypto.ciphertext),
- ])
- const kMAC = Cryptic.toHex(keccak_256(new Uint8Array([
- ...new Uint8Array(derivedBytes.slice(16, 32)),
- ...Cryptic.toBytes(keystore.crypto.ciphertext),
- ])));
-
- keystore.crypto.mac = bMAC
-
- // console.log(
- // 'encrypted keystore',
- // ks,
- // {
- // encryptedPhrase,
- // // keyMaterial,
- // // derivedKey,
- // // derivedBytes,
- // },
- // {
- // bMAC,
- // kMAC,
- // },
- // )
-
- return keystore
-}
-
-export async function generateAddressIterator(
- xkey,
- xkeyId,
- addressIndex,
-) {
- let key = await xkey.deriveAddress(addressIndex);
- let address = await DashHd.toAddr(key.publicKey);
-
- return {
- address,
- addressIndex,
- usageIndex: xkey.index,
- xkeyId,
- }
-}
-
-export async function generateAndStoreAddressIterator(
- xkey,
- xkeyId,
- walletId,
- accountIndex,
- addressIndex,
- usageIndex = DashHd.RECEIVE,
-) {
- let { address } = await generateAddressIterator(
- xkey,
- xkeyId,
- addressIndex,
- )
-
- // console.log(
- // 'generateAddressIterator',
- // {xkey, xkeyId, key, address, accountIndex, addressIndex},
- // )
-
- store.addresses.getItem(address)
- .then(a => {
- let $addr = a || {}
- // console.log(
- // 'generateAddressIterator store.addresses.getItem',
- // {address, $addr},
- // )
-
- store.addresses.setItem(
- address,
- {
- ...$addr,
- updatedAt: Date.now(),
- walletId,
- xkeyId,
- accountIndex,
- addressIndex,
- usageIndex,
- },
- )
- })
-
- return {
- address,
- addressIndex,
- accountIndex,
- usageIndex: xkey.index,
- xkeyId,
- }
-}
-
-export async function batchXkeyAddressGenerate(
- wallet,
- addressIndex = 0,
- batchSize = 20,
-) {
- let batchLimit = addressIndex + batchSize
- let addresses = []
-
- for (let addrIdx = addressIndex; addrIdx < batchLimit; addrIdx++) {
- addresses.push(
- await generateAddressIterator(
- wallet.xkey,
- wallet.xkeyId,
- addrIdx,
- )
- )
- }
-
- return {
- addresses,
- finalAddressIndex: batchLimit,
- }
-}
-
-export async function batchAddressGenerate(
- wallet,
- accountIndex = 0,
- addressIndex = 0,
- usageIndex = DashHd.RECEIVE,
- batchSize = 20,
-) {
- // let hdpath = `m/44'/5'/${accountIndex}'/${usageIndex}/${addressIndex}`,
- let batchLimit = addressIndex + batchSize
- let addresses = []
-
- let account = await wallet.derivedWallet.deriveAccount(accountIndex);
- let xkey = await account.deriveXKey(usageIndex);
- let xkeyId = await DashHd.toId(xkey);
-
- if (usageIndex !== DashHd.RECEIVE) {
- let xkeyReceive = await account.deriveXKey(DashHd.RECEIVE);
- xkeyId = await DashHd.toId(xkeyReceive);
- }
-
- for (let addrIdx = addressIndex; addrIdx < batchLimit; addrIdx++) {
- addresses.push(
- await generateAndStoreAddressIterator(
- xkey,
- xkeyId,
- wallet.id,
- accountIndex,
- addrIdx,
- usageIndex,
- )
- )
- }
-
- return {
- addresses,
- finalAddressIndex: batchLimit,
- }
-}
-
-export async function batchAddressUsageGenerate(
- wallet,
- accountIndex = 0,
- addressIndex = 0,
- batchSize = 20,
-) {
- // let hdpath = `m/44'/5'/${accountIndex}'/${usageIndex}/${addressIndex}`,
- let batchLimit = addressIndex + batchSize
- let addresses = []
-
- let account = await wallet.derivedWallet.deriveAccount(accountIndex);
- let xkeyReceive = await account.deriveXKey(DashHd.RECEIVE);
- let xkeyChange = await account.deriveXKey(DashHd.CHANGE);
- let xkeyId = await DashHd.toId(xkeyReceive);
-
- console.log(
- 'batchAddressUsageGenerate',
- {batchLimit, account, xkeyReceive, xkeyChange},
- )
-
- for (let addrIdx = addressIndex; addrIdx < batchLimit; addrIdx++) {
- addresses.push(
- await generateAndStoreAddressIterator(
- xkeyReceive,
- xkeyId,
- wallet.id,
- accountIndex,
- addrIdx,
- DashHd.RECEIVE,
- )
- )
- addresses.push(
- await generateAndStoreAddressIterator(
- xkeyChange,
- xkeyId,
- wallet.id,
- accountIndex,
- addrIdx,
- DashHd.CHANGE,
- )
- )
- }
-
- return {
- addresses,
- finalAddressIndex: batchLimit,
- }
-}
-
-export async function initWallet(
- encryptionPassword,
- wallet,
- keystore,
- accountIndex = 0,
- addressIndex = 0,
- infoOverride = {},
-) {
- let {
- alias,
- wallets,
- info,
- } = await initWalletsInfo(infoOverride)
-
- let { id, recoveryPhrase } = wallet
-
- // console.log(
- // 'initWallet wallets',
- // wallets,
- // info,
- // )
-
- if (!wallets.includes(id)) {
- wallets.push(id)
- }
-
- let addrs = await batchAddressUsageGenerate(
- wallet,
- accountIndex,
- addressIndex,
- )
-
- console.log('init wallet batchAddressUsageGenerate', addrs)
-
- for (let a of addrs.addresses) {
- store.addresses.setItem(
- a.address,
- {
- updatedAt: Date.now(),
- walletId: wallet.id,
- accountIndex: a.accountIndex,
- addressIndex: a.addressIndex,
- usageIndex: a.usageIndex,
- xkeyId: a.xkeyId,
- }
- )
- }
-
- let storeWallet = await store.wallets.setItem(
- `${id}`,
- {
- id,
- updatedAt: Date.now(),
- accountIndex,
- addressIndex: addrs?.finalAddressIndex || addressIndex,
- keystore: keystore || await encryptKeystore(
- encryptionPassword,
- recoveryPhrase
- ),
- }
- )
-
- let storedAlias = await store.aliases.setItem(
- `${alias}`,
- await encryptData(
- encryptionPassword,
- storeWallet.keystore,
- JSON.stringify({
- wallets,
- info,
- })
- )
- )
-
- // console.log(
- // 'initWallet stored values',
- // storeWallet,
- // storedAlias,
- // )
-
- let contacts = '{}'
-
- return {
- wallets,
- contacts,
- }
-}
-
-// export async function checkWalletFunds(addr, wallet = {}) {
-// const HOUR = 1000 * 60 * 60;
-
-// let {
-// address,
-// accountIndex,
-// addressIndex,
-// usageIndex,
-// } = addr
-// let updatedAt = Date.now()
-// let $addr = await store.addresses.getItem(address) || {}
-
-// $addr = {
-// walletId: wallet.id,
-// accountIndex,
-// addressIndex,
-// usageIndex,
-// ...$addr,
-// }
-// // console.log('checkWalletFunds $addr', $addr)
-// let walletFunds = $addr?.insight
-
-// if (
-// !walletFunds?.updatedAt ||
-// updatedAt - walletFunds?.updatedAt > HOUR
-// ) {
-// // console.info('check insight api for addr', addr)
-
-// let insightRes = await dashsight.getInstantBalance(address)
-
-// if (insightRes) {
-// let { addrStr, ...res } = insightRes
-// walletFunds = res
-
-// $addr.insight = {
-// ...walletFunds,
-// updatedAt,
-// }
-
-// store.addresses.setItem(
-// address,
-// $addr,
-// )
-// }
-// }
-
-// // console.info('check addr funds', addr, walletFunds)
-
-// return $addr
-// }
-
-export async function updateAddrFunds(
- wallet, insightRes,
-) {
- let updatedAt = Date.now()
- let { addrStr, ...res } = insightRes
- let $addr = await store.addresses.getItem(addrStr) || {}
- let {
- walletId,
- xkeyId,
- } = $addr
-
- // console.log(
- // 'checkWalletFunds $addr',
- // $addr,
- // walletId,
- // wallet?.id,
- // walletId === wallet?.id
- // )
-
- if (walletId && walletId === wallet?.id) {
- let storedWallet = await store.wallets.getItem(walletId) || {}
- let storedAccount = await store.accounts.getItem(xkeyId) || {}
-
- $addr.insight = {
- ...res,
- updatedAt,
- }
-
- store.addresses.setItem(
- addrStr,
- $addr,
- )
-
- if (storedAccount.usage[$addr.usageIndex] < $addr.addressIndex) {
- storedAccount.usage[$addr.usageIndex] = $addr.addressIndex
- store.accounts.setItem(
- xkeyId,
- storedAccount
- )
- }
- if (storedWallet.accountIndex < $addr.accountIndex) {
- store.wallets.setItem(
- walletId,
- {
- ...storedWallet,
- accountIndex: $addr.accountIndex,
- }
- )
- let storeAcctLen = (await store.accounts.length())-1
-
- console.log('updateAddrFunds', {
- acctIdx: $addr.accountIndex,
- storeAcctLen,
- sameOrBigger: $addr.accountIndex >= storeAcctLen,
- })
-
- if ($addr.accountIndex >= storeAcctLen) {
- batchGenAccts(wallet.recoveryPhrase, $addr.accountIndex)
- .then(() => {
- // updateAllFunds(wallet)
- batchGenAcctsAddrs(wallet)
- .then(accts => {
- console.log('batchGenAcctsAddrs', { accts })
-
- updateAllFunds(wallet)
- .then(funds => {
- console.log('updateAllFunds then funds', funds)
- })
- .catch(err => console.error('catch updateAllFunds', err, wallet))
- })
- })
- }
- }
-
- return res
- }
-
- return { balance: 0 }
-}
-
-export async function updateAllFunds(wallet) {
- let funds = 0
- let addrKeys = await store.addresses.keys()
-
- if (addrKeys.length === 0) {
- walletFunds.balance = funds
- return funds
- }
-
- console.log(
- 'updateAllFunds getInstantBalances for',
- {addrKeys},
- addrKeys.length,
- )
-
- let balances = await dashsight.getInstantBalances(addrKeys)
- // let txs = await dashsight.getAllTxs(
- // addrKeys
- // )
-
- // console.log('getAllTxs', txs)
-
- if (balances.length >= 0) {
- walletFunds.balance = funds
- }
-
- // add insight balances to address
- for (const insightRes of balances) {
- let { addrStr } = insightRes
- let addrIdx = addrKeys.indexOf(addrStr)
- if (addrIdx > -1) {
- addrKeys.splice(addrIdx, 1)
- }
- funds += (await updateAddrFunds(wallet, insightRes))?.balance || 0
- walletFunds.balance = funds
- }
-
- // remove insight balances from address
- for (const addr of addrKeys) {
- let { insight, ...$addr } = await store.addresses.getItem(addr) || {}
-
- // walletFunds.balance = funds - (_insight?.balance || 0)
-
- store.addresses.setItem(
- addr,
- $addr,
- )
- }
-
- console.log('updateAllFunds funds', {balances, funds})
-
- return funds
-}
-
-export async function getTotalFunds(wallet) {
- let funds = 0
- let result = {}
- let addrsLen = await store.addresses.length()
-
- return await store.addresses.iterate((
- value, key, iterationNumber
- ) => {
- if (value?.walletId === wallet?.id) {
- result[key] = value
- funds += value?.insight?.balance || 0
- }
-
- if (iterationNumber === addrsLen) {
- return funds
- }
- })
-}
-
-export async function getAddrsWithFunds(wallet) {
- let result = {}
- let addrsLen = await store.addresses.length()
-
- return await store.addresses.iterate((
- value, key, iterationNumber
- ) => {
- if (
- value?.walletId === wallet?.id &&
- value?.insight?.balance > 0
- ) {
- result[key] = {
- ...value,
- address: key,
- }
- }
-
- if (iterationNumber === addrsLen) {
- return result
- }
- })
-}
-
-export async function batchGenAccts(
- phrase,
- accountIndex = 0,
- batchSize = 5,
-) {
- let $accts = await getStoredItems(store.accounts)
- // let $acctsArr = Object.values($accts)
- let accts = {}
- let batch = batchSize + accountIndex
-
- console.log(
- 'BATCH GENERATED ACCOUNTS START',
- {
- $accts,
- // $acctsArr,
- accountIndex,
- batch,
- }
- )
-
- for (let i = accountIndex; i < batch; i++) {
- let acctWallet = await deriveWalletData(
- phrase,
- i,
- )
-
- if (!$accts[acctWallet.xkeyId]) {
- let newAccount = await store.accounts.setItem(
- acctWallet.xkeyId,
- {
- createdAt: (new Date()).toISOString(),
- updatedAt: (new Date()).toISOString(),
- accountIndex: i,
- usage: [0,0],
- walletId: acctWallet.id,
- xkeyId: acctWallet.xkeyId,
- addressKeyId: acctWallet.addressKeyId,
- address: acctWallet.address,
- }
- )
-
- accts[`acct__${i}`] = [ acctWallet, newAccount ]
- }
-
- // accts[`acct__${i}`] = batchGenAcctAddrs(
- // acctWallet,
- // newAccount,
- // )
- }
-
- // let allBatches = Promise.allSettled(Object.values(accts))
-
- return accts
-}
-
-export async function batchGenAcctAddrs(
- wallet,
- account,
- usageIndex = -1,
- batchSize = 20,
-) {
- // console.log('batchGenAcctAddrs account', account, usageIndex)
-
- let filterQuery = {
- accountIndex: account.accountIndex,
- }
-
- if (usageIndex >= 0) {
- filterQuery.usageIndex = usageIndex
- }
-
- let acctAddrsLen = await getFilteredStoreLength(
- store.addresses,
- filterQuery,
- )
-
- // console.log('getFilteredStoreLength res', acctAddrsLen)
-
- let addrUsageIdx = account.usage?.[usageIndex] || 0
- let addrIdx = addrUsageIdx
- let batSize = batchSize
-
- if (acctAddrsLen === 0) {
- addrIdx = 0
- batSize = addrUsageIdx + batchSize
- }
-
- if (acctAddrsLen <= addrUsageIdx + (batchSize / 2)) {
- if (usageIndex >= 0) {
- return await batchAddressGenerate(
- wallet,
- account.accountIndex,
- account.usage[usageIndex],
- usageIndex,
- batSize,
- )
- } else {
- return await batchAddressUsageGenerate(
- wallet,
- account.accountIndex,
- addrIdx,
- batSize,
- )
- }
- }
-
- return null
-}
-
-export async function batchGenAcctsAddrs(
- wallet,
- usageIndex = -1,
- batchSize = 20,
-) {
- let $accts = await getStoredItems(store.accounts)
- let $acctsArr = Object.values($accts)
- let accts = {}
-
- if ($acctsArr.length > 0) {
- for (let $a of $acctsArr) {
- accts[`bat__${$a.accountIndex}`] = await batchGenAcctAddrs(
- wallet,
- $a,
- usageIndex,
- batchSize,
- )
- }
-
- // console.warn(
- // 'BATCH GENERATED ACCOUNTS',
- // accts,
- // )
- }
-
- return accts
-}
-
-export async function getAccountWallet(wallet, phrase) {
- let acctFromStore = await store.accounts.getItem(
- wallet.xkeyId,
- ) || {}
- let acctFromStoreWallet = getAddressIndexFromUsage(
- wallet,
- acctFromStore,
- )
-
- if (acctFromStoreWallet?.addressIndex > 0) {
- return {
- wallet: await deriveWalletData(
- phrase,
- acctFromStoreWallet.accountIndex,
- acctFromStoreWallet.addressIndex,
- acctFromStoreWallet?.usageIndex ?? USAGE.RECEIVE,
- ),
- account: acctFromStore,
- }
- }
-
- return {
- wallet,
- account: acctFromStore,
- }
-}
-
-export async function forceInsightUpdateForAddress(addr) {
- let currentAddr = await store.addresses.getItem(
- addr
- )
- await store.addresses.setItem(
- addr,
- {
- ...currentAddr,
- insight: {
- ...currentAddr.insight,
- updatedAt: 0
- }
- }
- )
-}
-
-export function sortAddrs(a, b) {
- // Ascending Lexicographical on TxId (prev-hash) in-memory (not wire) byte order
- if (a.accountIndex > b.accountIndex) {
- return 1;
- }
- if (a.accountIndex < b.accountIndex) {
- return -1;
- }
- // addressIndex
- // Ascending Vout (Numerical)
- let indexDiff = a.addressIndex - b.addressIndex;
- return indexDiff;
-}
-
-export function getBalance(utxos) {
- return utxos.reduce(function (total, utxo) {
- return total + utxo.satoshis;
- }, 0);
-}
-
-export function selectOptimalUtxos(utxos, output) {
- let balance = getBalance(utxos);
- let fees = DashTx.appraise({
- //@ts-ignore
- inputs: [{}],
- //@ts-ignore
- outputs: [{}],
- });
-
- let fullSats = output + fees.min;
-
- if (balance < fullSats) {
- return [];
- }
-
- // from largest to smallest
- utxos.sort(function (a, b) {
- return b.satoshis - a.satoshis;
- });
-
- // /** @type Array */
- let included = [];
- let total = 0;
-
- // try to get just one
- utxos.every(function (utxo) {
- if (utxo.satoshis > fullSats) {
- included[0] = utxo;
- total = utxo.satoshis;
- return true;
- }
- return false;
- });
- if (total) {
- return included;
- }
-
- // try to use as few coins as possible
- utxos.some(function (utxo, i) {
- included.push(utxo);
- total += utxo.satoshis;
- if (total >= fullSats) {
- return true;
- }
-
- // it quickly becomes astronomically unlikely to hit the one
- // exact possibility that least to paying the absolute minimum,
- // but remains about 75% likely to hit any of the mid value
- // possibilities
- if (i < 2) {
- // 1 input 25% chance of minimum (needs ~2 tries)
- // 2 inputs 6.25% chance of minimum (needs ~8 tries)
- fullSats = fullSats + DashTx.MIN_INPUT_SIZE;
- return false;
- }
- // but by 3 inputs... 1.56% chance of minimum (needs ~32 tries)
- // by 10 inputs... 0.00953674316% chance (needs ~524288 tries)
- fullSats = fullSats + DashTx.MIN_INPUT_SIZE + 1;
- });
- return included;
-}
-
-export async function deriveTxWallet(
- fromWallet,
- fundAddrs,
-) {
- let cachedAddrs = {}
- let privateKeys = {}
- let coreUtxos
- // let transactions
- let tmpWallet
-
- if (Array.isArray(fundAddrs) && fundAddrs.length > 0) {
- fundAddrs.sort(sortAddrs)
-
- for (let w of fundAddrs) {
- tmpWallet = await deriveWalletData(
- fromWallet.recoveryPhrase,
- w.accountIndex,
- w.addressIndex,
- w.usageIndex,
- )
- privateKeys[tmpWallet.address] = tmpWallet.addressKey.privateKey
- cachedAddrs[w.address] = {
- checked_at: w.updatedAt,
- hdpath: `m/44'/${DashWallet.COIN_TYPE}'/${w.accountIndex}'/${w.usageIndex}`,
- index: w.addressIndex,
- wallet: w.walletId, // maybe `selectedAlias`?
- txs: [],
- utxos: [],
- }
- }
-
- coreUtxos = await dashsight.getMultiCoreUtxos(
- Object.keys(privateKeys)
- )
- // transactions = await dashsight.getAllTxs(
- // Object.keys(privateKeys)
- // )
- } else {
- tmpWallet = await deriveWalletData(
- fromWallet.recoveryPhrase,
- fundAddrs.accountIndex,
- fundAddrs.addressIndex,
- fundAddrs.usageIndex,
- )
- privateKeys[tmpWallet.address] = tmpWallet.addressKey.privateKey
- cachedAddrs[fundAddrs.address] = {
- checked_at: fundAddrs.updatedAt,
- hdpath: `m/44'/${DashWallet.COIN_TYPE}'/${fundAddrs.accountIndex}'/${fundAddrs.usageIndex}`,
- index: fundAddrs.addressIndex,
- wallet: fundAddrs.walletId, // maybe `selectedAlias`?
- txs: [],
- utxos: [],
- }
- coreUtxos = await dashsight.getCoreUtxos(
- tmpWallet.address
- )
- // transactions = await dashsight.getAllTxs(
- // [tmpWallet.address]
- // )
- }
-
- // console.log('getAllTxs', transactions)
-
- return {
- privateKeys,
- cachedAddrs,
- coreUtxos,
- // transactions,
- }
-}
-
-export async function createOptimalTx(
- fromWallet,
- fundAddrs,
- changeAddrs,
- recipient,
- amount,
-) {
- const MIN_FEE = 191;
- const DUST = 2000;
- const amountSats = DashTx.toSats(amount)
-
- console.log('amount to send', {
- amount,
- amountSats,
- fundAddrs,
- })
-
- let changeAddr = changeAddrs[0]
-
- let {
- privateKeys,
- coreUtxos,
- } = await deriveTxWallet(fromWallet, fundAddrs)
-
- let optimalUtxos = selectOptimalUtxos(
- coreUtxos,
- amountSats,
- )
-
- console.log('utxos', {
- core: coreUtxos,
- optimal: optimalUtxos,
- })
-
- let recipientAddr = recipient?.address || recipient
-
- let payments = [
- {
- address: recipientAddr,
- satoshis: amountSats,
- },
- ]
-
- let spendableDuffs = optimalUtxos.reduce(function (total, utxo) {
- return total + utxo.satoshis;
- }, 0)
- let spentDuffs = payments.reduce(function (total, output) {
- return total + output.satoshis;
- }, 0)
- let unspentDuffs = spendableDuffs - spentDuffs
-
- let txInfo = {
- inputs: optimalUtxos,
- outputs: payments,
- }
-
- let sizes = DashTx.appraise(txInfo)
- let midFee = sizes.mid
-
- if (unspentDuffs < MIN_FEE) {
- throw new Error(
- `overspend: inputs total '${spendableDuffs}', but outputs total '${spentDuffs}', which leaves no way to pay the fee of '${sizes.mid}'`,
- )
- }
-
- txInfo.inputs.sort(DashTx.sortInputs)
-
- let outputs = txInfo.outputs.slice(0)
- let change
-
- change = unspentDuffs - (midFee + DashTx.OUTPUT_SIZE)
- if (change < DUST) {
- change = 0
- }
- if (change) {
- txInfo.outputs = outputs.slice(0);
- txInfo.outputs.push({
- address: changeAddr,
- satoshis: change,
- })
- }
-
- txInfo.outputs.sort(DashTx.sortOutputs)
-
- let keys = optimalUtxos.map(
- utxo => privateKeys[utxo.address]
- )
-
- return [
- txInfo,
- keys,
- changeAddr,
- ]
-}
-
-export async function createStandardTx(
- fromWallet,
- fundAddrs,
- changeAddrs,
- recipient,
- amount,
- fullTransfer = false,
-) {
- const amountSats = DashTx.toSats(amount)
-
- console.log('amount to send', {
- amount,
- amountSats,
- })
-
- let selection
- let receiverOutput
- let outputs = []
- let {
- privateKeys,
- coreUtxos,
- cachedAddrs,
- } = await deriveTxWallet(fromWallet, fundAddrs)
- let changeAddr = changeAddrs[0]
-
- let recipientAddr = recipient?.address || recipient
-
- // @ts-ignore
- let dashwallet = await DashWallet.create({
- safe: {
- cache: {
- addresses: cachedAddrs
- }
- },
- store: {
- save: data => console.log('dashwallet.store.save', {data})
- },
- dashsight,
- })
-
- receiverOutput = DashWallet._parseSendInfo(dashwallet, amountSats);
-
- selection = dashwallet.useMatchingCoins({
- output: receiverOutput,
- utxos: coreUtxos,
- breakChange: false,
- })
-
- console.log('coreUtxos', {
- coreUtxos,
- selection,
- amount,
- amountSats,
- fullTransfer,
- })
-
- let stampVal = dashwallet.__STAMP__ * selection.output.stampsPerCoin
- let receiverDenoms = receiverOutput?.denoms.slice(0)
-
- for (let denom of selection.output.denoms) {
- let address = '';
- let matchingDenomIndex = receiverDenoms.indexOf(denom)
- if (matchingDenomIndex >= 0) {
- void receiverDenoms.splice(matchingDenomIndex, 1)
- address = recipientAddr
- } else {
- address = changeAddr
- }
-
- let coreOutput = {
- address,
- // address: addrsInfo.addresses.pop(),
- satoshis: denom + stampVal,
- faceValue: denom,
- stamps: selection.output.stampsPerCoin,
- }
-
- outputs.push(coreOutput)
- }
-
- let txInfo = {
- inputs: selection.inputs,
- outputs: outputs,
- };
-
- txInfo.outputs.sort(DashTx.sortOutputs)
- txInfo.inputs.sort(DashTx.sortInputs)
-
- let keys = txInfo.inputs.map(
- utxo => privateKeys[utxo.address]
- )
-
- return [
- txInfo,
- keys,
- changeAddr,
- ]
-}
-
-export async function createDraftTx(
- fromWallet,
- fundAddrs,
- changeAddrs,
- recipient,
- amount,
- fullTransfer = false,
-) {
- const amountSats = DashTx.toSats(amount)
-
- console.log('amount to send', {
- amount,
- amountSats,
- })
-
- let {
- privateKeys,
- coreUtxos,
- cachedAddrs,
- } = await deriveTxWallet(fromWallet, fundAddrs)
- let changeAddr = changeAddrs[0]
-
- let recipientAddr = recipient?.address || recipient
-
- // @ts-ignore
- let dashwallet = await DashWallet.create({
- safe: {
- cache: {
- addresses: cachedAddrs
- }
- },
- store: {
- save: data => console.log('dashwallet.store.save', {data})
- },
- dashsight,
- })
-
- let utxos = null;
- let inputs = null;
- let output = {
- address: recipientAddr,
- satoshis: amountSats
- };
-
- if (coreUtxos) {
- inputs = coreUtxos;
- if (fullTransfer) {
- output.satoshis = null;
- }
- } else {
- utxos = coreUtxos;
- }
-
- let txDraft = await dashwallet.legacy.draftTx({
- utxos,
- inputs,
- output,
- feeSize: 'max',
- })
-
- if (txDraft.change) {
- txDraft.change.address = changeAddr;
- }
-
- let keys = txDraft.inputs.map(
- utxo => privateKeys[utxo.address]
- )
-
- let txSummary = await dashwallet.legacy.finalizeAndSignTx(txDraft, keys);
-
- return {
- ...txSummary,
- changeAddr,
- }
-}
-
-export async function createTx(
- fromWallet,
- fundAddrs,
- changeAddrs,
- recipient,
- amount,
- fullTransfer = false,
- mode = null,
-) {
- let tmpTx
- let dashTx = DashTx.create({
- // @ts-ignore
- version: 3,
- });
-
- if (fullTransfer) {
- let tx = await createDraftTx(
- fromWallet,
- fundAddrs,
- changeAddrs,
- recipient,
- amount,
- fullTransfer,
- )
-
- console.log('fullTransfer tx', tx);
-
- return {
- tx,
- changeAddr: tx.changeAddr,
- fee: tx.fee,
- // fee: inFee - outFee,
- }
- } else if (mode === 'cash') {
- // Denominated TX
- tmpTx = await createStandardTx(
- fromWallet,
- fundAddrs,
- changeAddrs,
- recipient,
- amount,
- fullTransfer,
- )
- } else {
- // Non-Denominated TX
- tmpTx = await createOptimalTx(
- fromWallet,
- fundAddrs,
- changeAddrs,
- recipient,
- amount,
- )
- }
-
- let [txInfo, keys, changeAddr] = tmpTx
-
- let inFee = txInfo.inputs.reduce((acc, cur) => acc + cur.satoshis, 0)
- let outFee = txInfo.outputs.reduce((acc, cur) => acc + cur.satoshis, 0)
-
- console.log('txInfo', {
- txInfo,
- calcFee: {
- in: inFee,
- out: outFee,
- fee: inFee - outFee,
- },
- });
-
- let tx = await dashTx.hashAndSignAll(txInfo, keys);
-
- console.log('tx', tx);
-
- return {
- tx,
- changeAddr,
- fee: inFee - outFee,
- }
-}
-
-export async function sendTx(
- tx,
-) {
- let txHex = tx.transaction;
-
- console.log('txHex', [txHex]);
-
- let result = await dashsight.instantSend(txHex);
-
- console.log('instantSend result', result);
-
- return result
-}
-
-
-export function processInOut({
- conAddr, tx, addr, dir, sentAmount = null, receivedAmount = null,
- byAlias = {}, byAddress = {}, byTx = {},
-}) {
- let alias = byTx?.[tx.txid]?.alias || conAddr.alias
- byAlias[conAddr.alias] = {
- ...(byAlias[conAddr.alias] || []),
- [tx.txid]: {
- addr,
- dir,
- sentAmount,
- receivedAmount,
- ...tx,
- ...conAddr,
- alias,
- }
- }
- byAddress[addr] = [
- ...(byAddress[addr] || []),
- {
- receivedAmount,
- sentAmount,
- dir,
- ...tx,
- ...conAddr,
- alias,
- }
- ]
- byTx[tx.txid] = {
- receivedAmount,
- sentAmount,
- dir,
- ...tx,
- ...conAddr,
- alias,
- }
-
- // console.log(
- // 'processInOut',
- // conAddr.alias, conAddr.xkeyId, tx,
- // )
-
- return {
- byAlias,
- byAddress,
- byTx,
- }
-}
-
-export async function getAddrsTransactions({
- appState, addrs, contactAddrs = {},
- txs = [],
-}) {
- let storeAddrs = await loadStoreObject(store.addresses)
- if (txs.length === 0) {
- txs = await dashsight.getAllTxs(addrs)
- }
- let byAddress = {}
- let byAlias = {}
- let byTx = {}
-
- // console.log('getAddrsTransactions', {
- // txs, addrs, contactAddrs, appT: appState.transactions
- // })
-
- for await (let tx of txs) {
- let dir = 'received'
- let conAddr
- let sentAmount = 0
- let receivedAmount = 0
-
- for await (let vin of tx.vin) {
- let addr = vin.addr
- conAddr = contactAddrs[addr]
-
- if(storeAddrs[addr]) {
- dir = 'sent'
- sentAmount += Number(vin.value)
- }
-
- if (conAddr) {
- processInOut({
- tx, addr, conAddr, dir, sentAmount,
- byAlias, byAddress, byTx,
- })
- }
- }
-
- for await (let vout of tx.vout) {
- if (vout?.scriptPubKey?.addresses) {
- for await (let addr of vout.scriptPubKey.addresses) {
- // let addr = vout.scriptPubKey.addresses[0]
- conAddr = contactAddrs[addr]
-
- if(storeAddrs[addr]) {
- receivedAmount += Number(vout.value)
- } else {
- // sentAmount -= Number(vout.value)
- }
-
- if (conAddr) {
- processInOut({
- tx, addr, conAddr, dir, receivedAmount,
- byAlias, byAddress, byTx,
- })
- }
- }
- }
- }
-
- byTx[tx.txid] = {
- ...byTx[tx.txid],
- receivedAmount,
- sentAmount,
- }
-
- if (!appState.transactions[tx.txid]?.vin) {
- store.transactions.setItem(
- tx.txid,
- {
- ...(appState.transactions[tx.txid] || {}),
- ...tx,
- dir,
- sentAmount,
- receivedAmount,
- },
- )
- }
- }
-
- // console.log('getAddrsTransactions by Tx', byTx)
- // console.log('getAddrsTransactions by Alias', byAlias)
- // console.log('getAddrsTransactions by Address', byAddress)
-
- return {
- byAddress,
- byAlias,
- byTx,
- }
-}
-
-export async function getContactsByXkeyId(
- appState,
-) {
- let contactsXkeys = {}
-
- for await (let c of appState.contacts) {
- let og = Object.values(c.outgoing || {})?.[0]
- let ic = Object.values(c.incoming || {})?.[0]
-
- if (og) {
- contactsXkeys[og.xkeyId] = {
- ...c,
- dir: 'outgoing',
- }
- }
- if (ic) {
- contactsXkeys[ic.xkeyId] = {
- ...c,
- dir: 'incoming',
- }
- }
- }
-
- return contactsXkeys
-}
-
-export async function getContactsFromAddrs(
- appState,
-) {
- let accts = await loadStoreObject(store.accounts)
- let addrs = await loadStoreObject(store.addresses)
- let contactAddrs = await getContactsByXkeyId(appState)
- let contacts = {}
-
- for await (let [ck,cv] of Object.entries(addrs)) {
- let contact = contactAddrs[cv.xkeyId]
- let acct = accts[cv.xkeyId]
- if (contact) {
- contacts[ck] = contact
- } else if (acct) {
- contacts[ck] = {
- ...acct,
- alias: null,
- }
- }
- }
-
- return contacts
-}
-
-export async function deriveContactAddrs(
- appState, dir = 'outgoing',
-) {
- let addrs = {}
-
- for await (let c of appState.contacts) {
- let og = Object.values(c[dir] || {})?.[0]
- let xkey = og?.xpub || og?.xprv
-
- if (xkey) {
- let contactWallet = await deriveWalletData(
- xkey,
- )
- let contactAddrs = await batchXkeyAddressGenerate(
- contactWallet,
- contactWallet.addressIndex,
- )
-
- contactAddrs.addresses.forEach(g => {
- addrs[g.address] = {
- alias: c.alias,
- xkeyId: contactWallet.xkeyId,
- dir,
- }
- })
- }
- }
-
- // console.log('deriveContactAddrs', {
- // asc: appState.contacts,
- // addrs,
- // })
-
- return addrs
-}
-
-export async function getTxs(appState, transactions = []) {
- let contactAddrs = await getContactsFromAddrs(appState)
- let contactOutAddrs = await deriveContactAddrs(appState)
-
- contactAddrs = {
- ...contactAddrs,
- ...contactOutAddrs,
- }
-
- let addrs = Object.keys(contactAddrs)
-
- if (addrs.length === 0) {
- return
- }
-
- // let TxStore = await store.transactions.keys()
- // let txs = await dashsight.getAllTxs(addrs)
-
- let txs = await getAddrsTransactions({
- appState, addrs, contactAddrs,
- txs: transactions,
- })
-
- // console.log('getTxs', {
- // txs, contactAddrs, contactOutAddrs, addrs
- // })
-
- return txs
-}
-
-export function getTransactionsByContactAlias(appState) {
- return async res => {
- if (!res) {
- return []
- }
-
- appState.contacts = res
-
- // let contactAddrs = await deriveContactAddrs(appState) || {}
- // let addrs = Object.keys(contactAddrs)
-
- // console.log('contactAddrs', addrs)
-
- // if (addrs?.length) {
- // getAddrsTransactions({
- // appState, addrs, contactAddrs
- // })
- // }
-
- // console.log('contacts', res, contactAddrs)
-
- return res
- }
-}
diff --git a/src/imports.js b/src/imports.js
index 9057285..985bf1c 100644
--- a/src/imports.js
+++ b/src/imports.js
@@ -11,6 +11,7 @@
*
* See https://github.com/jojobyte/browser-import-rabbit-hole
*/
+import './secp.js';
import '../node_modules/dashtx/dashtx.js';
import '../node_modules/dashkeys/dashkeys.js';
@@ -20,10 +21,13 @@ import '../node_modules/dashsight/dashsight.js';
import '../node_modules/dashsight/dashsocket.js';
import '../node_modules/@dashincubator/base58check/base58check.js';
import '../node_modules/@dashincubator/ripemd160/ripemd160.js';
-import '../node_modules/@dashincubator/secp256k1/secp256k1.js';
+// import '../node_modules/@dashincubator/secp256k1/secp256k1.js';
import '../node_modules/crypticstorage/cryptic.js';
import '../node_modules/dashwallet/dashwallet.js';
import '../node_modules/localforage/dist/localforage.js';
+import '../node_modules/crowdnode/dashcore-lit.js';
+import '../node_modules/crowdnode/dashapi.js';
+import '../node_modules/crowdnode/crowdnode.js';
import * as DashTxTypes from '../node_modules/dashtx/dashtx.js';
import * as DashKeysTypes from '../node_modules/dashkeys/dashkeys.js';
@@ -37,6 +41,9 @@ import * as Secp256k1Types from '../node_modules/@dashincubator/secp256k1/secp25
import * as CrypticTypes from '../node_modules/crypticstorage/cryptic.js';
import * as CrypticStorageTypes from '../node_modules/crypticstorage/storage.js';
import * as DashWalletTypes from '../node_modules/dashwallet/dashwallet.js';
+import * as DashCoreTypes from '../node_modules/crowdnode/dashcore-lit.js';
+import * as DashApiTypes from '../node_modules/crowdnode/dashapi.js';
+import * as CrowdNodeTypes from '../node_modules/crowdnode/crowdnode.js';
// import * as LocalForageTypes from '../node_modules/localforage/dist/localforage.js';
/** @type {DashTxTypes} */
@@ -56,7 +63,10 @@ export let Base58Check = window?.Base58Check || globalThis?.Base58Check;
/** @type {RIPEMD160Types} */
export let RIPEMD160 = window?.RIPEMD160 || globalThis?.RIPEMD160;
/** @type {Secp256k1Types} */
-export let Secp256k1 = window?.nobleSecp256k1 || globalThis?.nobleSecp256k1;
+export let Secp256k1 = (
+ window?.nobleSecp256k1 || globalThis?.nobleSecp256k1 ||
+ window?.Secp256k1 || globalThis?.Secp256k1
+);
/** @type {CrypticTypes} */
export let Cryptic =
window?.Cryptic || globalThis?.Cryptic;
@@ -65,11 +75,17 @@ export let CrypticStorage =
window?.CrypticStorage || globalThis?.CrypticStorage;
/** @type {DashWalletTypes} */
export let DashWallet = window?.Wallet || globalThis?.Wallet;
+/** @type {CrowdNodeTypes} */
+export let CrowdNode = window?.CrowdNode || globalThis?.CrowdNode
export let localforage =
window?.localforage || globalThis?.localforage;
export default {
+ Base58Check,
+ CrowdNode,
+ Cryptic,
+ CrypticStorage,
DashWallet,
DashTx,
DashKeys,
@@ -77,10 +93,7 @@ export default {
DashPhrase,
DashSight,
DashSocket,
- Base58Check,
+ localforage,
RIPEMD160,
Secp256k1,
- Cryptic,
- CrypticStorage,
- localforage,
};
diff --git a/src/index.css b/src/index.css
index afa2fa0..11382a1 100644
--- a/src/index.css
+++ b/src/index.css
@@ -96,16 +96,35 @@ figure h4 {
font-size: 2rem;
}
-.mh-25 {
+.min-h-auto {
+ min-height: auto;
+}
+.min-h-0 {
+ min-height: 0;
+}
+.min-h-25 {
+ min-height: 25%;
+}
+.min-h-50 {
+ min-height: 50%;
+}
+.min-h-75 {
+ min-height: 75%;
+}
+.min-h-100 {
+ min-height: 100%;
+}
+
+.max-h-25 {
max-height: 25%;
}
-.mh-50 {
+.max-h-50 {
max-height: 50%;
}
-.mh-75 {
+.max-h-75 {
max-height: 75%;
}
-.mh-100 {
+.max-h-100 {
max-height: 100%;
}
@@ -167,9 +186,24 @@ figure h4 {
.jc-start {
justify-content: start;
}
+.jc-center {
+ justify-content: center;
+}
+.jc-right {
+ justify-content: right;
+}
.jc-end {
justify-content: end;
}
+.jc-around {
+ justify-content: space-around;
+}
+.jc-between {
+ justify-content: space-between;
+}
+.jc-evenly {
+ justify-content: space-evenly;
+}
.as-start {
align-self: start;
}
@@ -320,6 +354,28 @@ figure h4 {
font-size: 1.5rem;
}
+.p-0 {
+ padding: 0;
+}
+.p-1 {
+ padding: .25rem;
+}
+.p-2 {
+ padding: .75rem;
+}
+.p-3 {
+ padding: 1rem;
+}
+.p-4 {
+ padding: 1.5rem;
+}
+.p-5 {
+ padding: 2rem;
+}
+.p-6 {
+ padding: 2.5rem;
+}
+
.px-0 {
padding-left: 0;
padding-right: 0;
diff --git a/src/index.html b/src/index.html
index 9722a8f..71a1ac2 100644
--- a/src/index.html
+++ b/src/index.html
@@ -47,6 +47,7 @@
}
}
+
diff --git a/src/libs/path-to-regexp.js b/src/libs/path-to-regexp.js
deleted file mode 100644
index 6394b64..0000000
--- a/src/libs/path-to-regexp.js
+++ /dev/null
@@ -1,490 +0,0 @@
-// @ts-nocheck
-
-/**
- * https://unpkg.com/path-to-regexp@6.2.1/dist/index.js
- * https://github.com/pillarjs/path-to-regexp
- * https://github.com/expressjs/express/blob/master/lib/router/layer.js
- */
-
-/**
- * Tokenize input string.
- */
-function lexer(str) {
- var tokens = [];
- var i = 0;
- while (i < str.length) {
- var char = str[i];
- if (char === '*' || char === '+' || char === '?') {
- tokens.push({ type: 'MODIFIER', index: i, value: str[i++] });
- continue;
- }
- if (char === '\\') {
- tokens.push({ type: 'ESCAPED_CHAR', index: i++, value: str[i++] });
- continue;
- }
- if (char === '{') {
- tokens.push({ type: 'OPEN', index: i, value: str[i++] });
- continue;
- }
- if (char === '}') {
- tokens.push({ type: 'CLOSE', index: i, value: str[i++] });
- continue;
- }
- if (char === ':') {
- var name = '';
- var j = i + 1;
- while (j < str.length) {
- var code = str.charCodeAt(j);
- if (
- // `0-9`
- (code >= 48 && code <= 57) ||
- // `A-Z`
- (code >= 65 && code <= 90) ||
- // `a-z`
- (code >= 97 && code <= 122) ||
- // `_`
- code === 95
- ) {
- name += str[j++];
- continue;
- }
- break;
- }
- if (!name) throw new TypeError('Missing parameter name at '.concat(i));
- tokens.push({ type: 'NAME', index: i, value: name });
- i = j;
- continue;
- }
- if (char === '(') {
- var count = 1;
- var pattern = '';
- var j = i + 1;
- if (str[j] === '?') {
- throw new TypeError('Pattern cannot start with "?" at '.concat(j));
- }
- while (j < str.length) {
- if (str[j] === '\\') {
- pattern += str[j++] + str[j++];
- continue;
- }
- if (str[j] === ')') {
- count--;
- if (count === 0) {
- j++;
- break;
- }
- } else if (str[j] === '(') {
- count++;
- if (str[j + 1] !== '?') {
- throw new TypeError(
- 'Capturing groups are not allowed at '.concat(j),
- );
- }
- }
- pattern += str[j++];
- }
- if (count) throw new TypeError('Unbalanced pattern at '.concat(i));
- if (!pattern) throw new TypeError('Missing pattern at '.concat(i));
- tokens.push({ type: 'PATTERN', index: i, value: pattern });
- i = j;
- continue;
- }
- tokens.push({ type: 'CHAR', index: i, value: str[i++] });
- }
- tokens.push({ type: 'END', index: i, value: '' });
- return tokens;
-}
-
-/**
- * Parse a string for the raw tokens.
- */
-export function parse(str, options) {
- if (options === void 0) {
- options = {};
- }
- var tokens = lexer(str);
- var _a = options.prefixes,
- prefixes = _a === void 0 ? './' : _a;
- var defaultPattern = '[^'.concat(
- escapeString(options.delimiter || '/#?'),
- ']+?',
- );
- var result = [];
- var key = 0;
- var i = 0;
- var path = '';
- var tryConsume = function (type) {
- if (i < tokens.length && tokens[i].type === type) return tokens[i++].value;
- };
- var mustConsume = function (type) {
- var value = tryConsume(type);
- if (value !== undefined) return value;
- var _a = tokens[i],
- nextType = _a.type,
- index = _a.index;
- throw new TypeError(
- 'Unexpected '
- .concat(nextType, ' at ')
- .concat(index, ', expected ')
- .concat(type),
- );
- };
- var consumeText = function () {
- var result = '';
- var value;
- while ((value = tryConsume('CHAR') || tryConsume('ESCAPED_CHAR'))) {
- result += value;
- }
- return result;
- };
- while (i < tokens.length) {
- var char = tryConsume('CHAR');
- var name = tryConsume('NAME');
- var pattern = tryConsume('PATTERN');
- if (name || pattern) {
- var prefix = char || '';
- if (prefixes.indexOf(prefix) === -1) {
- path += prefix;
- prefix = '';
- }
- if (path) {
- result.push(path);
- path = '';
- }
- result.push({
- name: name || key++,
- prefix: prefix,
- suffix: '',
- pattern: pattern || defaultPattern,
- modifier: tryConsume('MODIFIER') || '',
- });
- continue;
- }
- var value = char || tryConsume('ESCAPED_CHAR');
- if (value) {
- path += value;
- continue;
- }
- if (path) {
- result.push(path);
- path = '';
- }
- var open = tryConsume('OPEN');
- if (open) {
- var prefix = consumeText();
- var name_1 = tryConsume('NAME') || '';
- var pattern_1 = tryConsume('PATTERN') || '';
- var suffix = consumeText();
- mustConsume('CLOSE');
- result.push({
- name: name_1 || (pattern_1 ? key++ : ''),
- pattern: name_1 && !pattern_1 ? defaultPattern : pattern_1,
- prefix: prefix,
- suffix: suffix,
- modifier: tryConsume('MODIFIER') || '',
- });
- continue;
- }
- mustConsume('END');
- }
- return result;
-}
-
-/**
- * Compile a string to a template function for the path.
- */
-export function compile(str, options) {
- return tokensToFunction(parse(str, options), options);
-}
-
-/**
- * Expose a method for transforming tokens into the path function.
- */
-export function tokensToFunction(tokens, options) {
- if (options === void 0) {
- options = {};
- }
- var reFlags = flags(options);
- var _a = options.encode,
- encode =
- _a === void 0
- ? function (x) {
- return x;
- }
- : _a,
- _b = options.validate,
- validate = _b === void 0 ? true : _b;
- // Compile all the tokens into regexps.
- var matches = tokens.map(function (token) {
- if (typeof token === 'object') {
- return new RegExp('^(?:'.concat(token.pattern, ')$'), reFlags);
- }
- });
- return function (data) {
- var path = '';
- for (var i = 0; i < tokens.length; i++) {
- var token = tokens[i];
- if (typeof token === 'string') {
- path += token;
- continue;
- }
- var value = data ? data[token.name] : undefined;
- var optional = token.modifier === '?' || token.modifier === '*';
- var repeat = token.modifier === '*' || token.modifier === '+';
- if (Array.isArray(value)) {
- if (!repeat) {
- throw new TypeError(
- 'Expected "'.concat(
- token.name,
- '" to not repeat, but got an array',
- ),
- );
- }
- if (value.length === 0) {
- if (optional) continue;
- throw new TypeError(
- 'Expected "'.concat(token.name, '" to not be empty'),
- );
- }
- for (var j = 0; j < value.length; j++) {
- var segment = encode(value[j], token);
- if (validate && !matches[i].test(segment)) {
- throw new TypeError(
- 'Expected all "'
- .concat(token.name, '" to match "')
- .concat(token.pattern, '", but got "')
- .concat(segment, '"'),
- );
- }
- path += token.prefix + segment + token.suffix;
- }
- continue;
- }
- if (typeof value === 'string' || typeof value === 'number') {
- var segment = encode(String(value), token);
- if (validate && !matches[i].test(segment)) {
- throw new TypeError(
- 'Expected "'
- .concat(token.name, '" to match "')
- .concat(token.pattern, '", but got "')
- .concat(segment, '"'),
- );
- }
- path += token.prefix + segment + token.suffix;
- continue;
- }
- if (optional) continue;
- var typeOfMessage = repeat ? 'an array' : 'a string';
- throw new TypeError(
- 'Expected "'.concat(token.name, '" to be ').concat(typeOfMessage),
- );
- }
- return path;
- };
-}
-
-/**
- * Create path match function from `path-to-regexp` spec.
- */
-export function match(str, options) {
- var keys = [];
- var re = pathToRegexp(str, keys, options);
- return regexpToFunction(re, keys, options);
-}
-
-/**
- * Create a path match function from `path-to-regexp` output.
- */
-export function regexpToFunction(re, keys, options) {
- if (options === void 0) {
- options = {};
- }
- var _a = options.decode,
- decode =
- _a === void 0
- ? function (x) {
- return x;
- }
- : _a;
- return function (pathname) {
- var m = re.exec(pathname);
- if (!m) return false;
- var path = m[0],
- index = m.index;
- var params = Object.create(null);
- var _loop_1 = function (i) {
- if (m[i] === undefined) return 'continue';
- var key = keys[i - 1];
- if (key.modifier === '*' || key.modifier === '+') {
- params[key.name] = m[i]
- .split(key.prefix + key.suffix)
- .map(function (value) {
- return decode(value, key);
- });
- } else {
- params[key.name] = decode(m[i], key);
- }
- };
- for (var i = 1; i < m.length; i++) {
- _loop_1(i);
- }
- return { path: path, index: index, params: params };
- };
-}
-
-/**
- * Escape a regular expression string.
- */
-function escapeString(str) {
- return str.replace(/([.+*?=^!:${}()[\]|/\\])/g, '\\$1');
-}
-
-/**
- * Get the flags for a regexp from the options.
- */
-function flags(options) {
- return options && options.sensitive ? '' : 'i';
-}
-
-/**
- * Pull out keys from a regexp.
- */
-function regexpToRegexp(path, keys) {
- if (!keys) return path;
- var groupsRegex = /\((?:\?<(.*?)>)?(?!\?)/g;
- var index = 0;
- var execResult = groupsRegex.exec(path.source);
- while (execResult) {
- keys.push({
- // Use parenthesized substring match if available, index otherwise
- name: execResult[1] || index++,
- prefix: '',
- suffix: '',
- modifier: '',
- pattern: '',
- });
- execResult = groupsRegex.exec(path.source);
- }
- return path;
-}
-
-/**
- * Transform an array into a regexp.
- */
-function arrayToRegexp(paths, keys, options) {
- var parts = paths.map(function (path) {
- return pathToRegexp(path, keys, options).source;
- });
- return new RegExp('(?:'.concat(parts.join('|'), ')'), flags(options));
-}
-
-/**
- * Create a path regexp from string input.
- */
-function stringToRegexp(path, keys, options) {
- return tokensToRegexp(parse(path, options), keys, options);
-}
-
-/**
- * Expose a function for taking tokens and returning a RegExp.
- */
-export function tokensToRegexp(tokens, keys, options) {
- if (options === void 0) {
- options = {};
- }
- var _a = options.strict,
- strict = _a === void 0 ? false : _a,
- _b = options.start,
- start = _b === void 0 ? true : _b,
- _c = options.end,
- end = _c === void 0 ? true : _c,
- _d = options.encode,
- encode =
- _d === void 0
- ? function (x) {
- return x;
- }
- : _d,
- _e = options.delimiter,
- delimiter = _e === void 0 ? '/#?' : _e,
- _f = options.endsWith,
- endsWith = _f === void 0 ? '' : _f;
- var endsWithRe = '['.concat(escapeString(endsWith), ']|$');
- var delimiterRe = '['.concat(escapeString(delimiter), ']');
- var route = start ? '^' : '';
- // Iterate over the tokens and create our regexp string.
- for (var _i = 0, tokens_1 = tokens; _i < tokens_1.length; _i++) {
- var token = tokens_1[_i];
- if (typeof token === 'string') {
- route += escapeString(encode(token));
- } else {
- var prefix = escapeString(encode(token.prefix));
- var suffix = escapeString(encode(token.suffix));
- if (token.pattern) {
- if (keys) keys.push(token);
- if (prefix || suffix) {
- if (token.modifier === '+' || token.modifier === '*') {
- var mod = token.modifier === '*' ? '?' : '';
- route += '(?:'
- .concat(prefix, '((?:')
- .concat(token.pattern, ')(?:')
- .concat(suffix)
- .concat(prefix, '(?:')
- .concat(token.pattern, '))*)')
- .concat(suffix, ')')
- .concat(mod);
- } else {
- route += '(?:'
- .concat(prefix, '(')
- .concat(token.pattern, ')')
- .concat(suffix, ')')
- .concat(token.modifier);
- }
- } else {
- if (token.modifier === '+' || token.modifier === '*') {
- route += '((?:'
- .concat(token.pattern, ')')
- .concat(token.modifier, ')');
- } else {
- route += '('.concat(token.pattern, ')').concat(token.modifier);
- }
- }
- } else {
- route += '(?:'
- .concat(prefix)
- .concat(suffix, ')')
- .concat(token.modifier);
- }
- }
- }
- if (end) {
- if (!strict) route += ''.concat(delimiterRe, '?');
- route += !options.endsWith ? '$' : '(?='.concat(endsWithRe, ')');
- } else {
- var endToken = tokens[tokens.length - 1];
- var isEndDelimited =
- typeof endToken === 'string'
- ? delimiterRe.indexOf(endToken[endToken.length - 1]) > -1
- : endToken === undefined;
- if (!strict) {
- route += '(?:'.concat(delimiterRe, '(?=').concat(endsWithRe, '))?');
- }
- if (!isEndDelimited) {
- route += '(?='.concat(delimiterRe, '|').concat(endsWithRe, ')');
- }
- }
- return new RegExp(route, flags(options));
-}
-
-/**
- * Normalize the given path string, returning a regular expression.
- *
- * An empty array can be passed in for the keys, which will hold the
- * placeholder key descriptions. For example, using `/user/:id`, `keys` will
- * contain `[{ name: 'id', delimiter: '/', optional: false, repeat: false }]`.
- */
-export function pathToRegexp(path, keys, options) {
- if (path instanceof RegExp) return regexpToRegexp(path, keys);
- if (Array.isArray(path)) return arrayToRegexp(path, keys, options);
- return stringToRegexp(path, keys, options);
-}
diff --git a/src/main.js b/src/main.js
index 3f06a08..92f9a41 100644
--- a/src/main.js
+++ b/src/main.js
@@ -1,52 +1,58 @@
-import { lit as html } from './helpers/lit.js'
-
import {
- generateWalletData,
- deriveWalletData,
- getStoreData,
- loadStoreObject,
- formDataEntries,
-} from './helpers/utils.js'
+ DUFFS,
+ DIALOG_STATUS,
+} from './utils/constants.js'
import {
- DUFFS,
-} from './helpers/constants.js'
+ lit as html,
+ formDataEntries,
+} from './utils/generic.js'
import {
- findInStore,
- initDashSocket,
batchGenAccts,
batchGenAcctAddrs,
batchGenAcctsAddrs,
batchXkeyAddressGenerate,
- updateAllFunds,
- decryptKeystore,
- getStoredItems,
- loadWalletsForAlias,
- store,
- createTx,
- sendTx,
+ deriveWalletData,
+ generateWalletData,
+ getAccountWallet,
getAddrsWithFunds,
- storedData,
getUnusedChangeAddress,
- getAccountWallet,
+ loadWalletsForAlias,
+ getTransactionsByContactAlias,
+} from './utils/dash/local.js'
+
+import {
+ createTx,
dashsight,
getAddrsTransactions,
- getTransactionsByContactAlias,
getTxs,
-} from './helpers/wallet.js'
+ initDashSocket,
+ store,
+ sendTx,
+ updateAllFunds,
+} from './utils/dash/network.js'
+
+import {
+ decryptKeystore,
+ storedData,
+} from './utils/cryptic.js'
import {
- localForageBaseCfg,
- importFromJson,
exportWalletData,
+ findInStore,
+ getStoreData,
+ getStoredItems,
+ importFromJson,
+ loadStoreObject,
+ localForageBaseCfg,
saveJsonToFile,
-} from './helpers/db.js'
+} from './utils/db.js'
import {
+ appDialogs,
appState,
appTools,
- appDialogs,
userInfo,
walletFunds,
} from './state/index.js'
@@ -79,6 +85,8 @@ import pairQrRig from './rigs/pair-qr.js'
import txInfoRig from './rigs/tx-info.js'
import showErrorDialog from './rigs/show-error.js'
+import crowdnodeTransactionRig from './rigs/crowdnode-tx.js'
+
// app/data state
let accounts
let wallets
@@ -131,6 +139,7 @@ let contactsList = await setupContactsList(
}
let contactAccountID = Object.values(contactData.incoming || {})?.[0]?.accountIndex
+
console.log('contact click data', contactData)
let shareAccount = await deriveWalletData(
@@ -431,6 +440,40 @@ async function showNotification({
console.log('notification', {type, title, msg, sticky})
}
+async function showQrCode(state = {}) {
+ let initState = {
+ name: 'Share to receive funds',
+ submitTxt: `Edit Amount or Contact`,
+ submitAlt: `Change the currently selected contact`,
+ footer: state => html`
+
+
+ ${state.submitTxt}
+
+
+ `,
+ amount: 0,
+ wallet,
+ contacts: appState.contacts,
+ ...state,
+ }
+
+ let showRequestQRRender = await appDialogs.requestQr.render(
+ initState,
+ 'afterend',
+ )
+
+ let showRequestQR = await appDialogs.requestQr.showModal()
+
+ return showRequestQRRender
+}
+
async function main() {
appState.encryptionPassword = window.atob(
sessionStorage.encryptionPassword || ''
@@ -673,32 +716,9 @@ async function main() {
// }
// )
- await appDialogs.requestQr.render(
- {
- name: 'Share to receive funds',
- submitTxt: `Edit Amount or Contact`,
- submitAlt: `Change the currently selected contact`,
- footer: state => html`
-
-
- ${state.submitTxt}
-
-
- `,
- amount: 0,
- wallet,
- contacts: appState.contacts,
- },
- 'afterend',
- )
+ showQrCode()
- let showRequestQR = await appDialogs.requestQr.showModal()
+ // let showRequestQR = await appDialogs.requestQr.showModal()
}
} else {
await appDialogs.sendOrReceive.render({
@@ -766,6 +786,7 @@ async function main() {
res.push(await appTools.storedData?.decryptData?.(v) || v)
},
)
+ console.log('appState.contacts', appState.contacts)
await contactsList.render({
userInfo,
@@ -790,23 +811,272 @@ async function main() {
walletFunds,
})
})
+ import('./components/crowdnode-card.js')
+ .then(async ({ CrowdNodeCard }) => {
+ let cfg = {
+ state: {
+ // name: '',
+ },
+ events: {
+ submit: async event => {
+ event.preventDefault()
+ event.stopPropagation()
- integrationsSection.insertAdjacentHTML('beforeend', html`
-
-
- Coming soon
- Earn interest with
-
-
-
- `)
+ // this.elements.form?.removeEventListener('submit', this.events.submit)
+
+ let fde = formDataEntries(event)
+
+ console.log(
+ `Crowdnode Card submit`,
+ {event, fde},
+ )
+
+ if (fde.intent === 'signup') {
+ let confAct = await appDialogs.confirmAction.render({
+ name: 'Signup for Crowdnode',
+ actionTxt: 'Signup',
+ actionAlt: 'Signup for Crowdnode',
+ action: '',
+ actionType: 'infoo',
+ placement: 'center auto-height',
+ // status: DIALOG_STATUS.LOADING,
+ acceptedToS: false,
+ submitIcon: () => ``,
+ alert: state => html`
+
+
+
+ I accept the CrowdNode Terms and Conditions
+
+ This process may take a while, please be patient.
+
+ `,
+ fields: () => html`
+
+
+ To stake your Dash and begin earning interest, read and accept the CrowdNode Terms and Conditions.
+ Funds are required to complete the signup process.
+
+
+ `,
+ callback: async (state, fde) => {
+ state.status = DIALOG_STATUS.LOADING
+
+ if (fde?.acceptToS === 'on') {
+ state.acceptedToS = true
+ }
+
+ let cbConfAct = await appDialogs.confirmAction.render(state)
+
+ console.log(
+ `confirm action`,
+ {state, fde, cbConfAct},
+ )
+
+ let cnFunding = await showQrCode({
+ name: 'CrowdNode Funding',
+ amount: 1.1,
+ status: DIALOG_STATUS.LOADING,
+ generateNextAddress: state => html`
+ Send 1.1 Dash or more
+ to signup & fund your CrowdNode account.
+ `,
+ footer: state => html`
+
+ `,
+ })
+
+ state.status = DIALOG_STATUS.SUCCESS
+
+ cbConfAct = await appDialogs.confirmAction.render(state)
+
+ console.log('CN Card Funding Callback', cnCard)
+
+ cnCard.api.value = {
+ acceptedToS: true,
+ balance: 1.234,
+ earned: 0.987,
+ }
+
+ cnCard.render({
+ cfg,
+ el: integrationsSection,
+ position: 'beforeend'
+ })
+
+ console.log(
+ `confirm action SUCCESS`,
+ {state, fde, cnFunding},
+ )
+
+ return { state, fde }
+ },
+ })
+
+ console.log('CN Card confAct Submit Event', cnCard)
+ console.log('confAct', confAct, appDialogs.confirmAction)
+
+ confAct?.elements?.form?.classList.add?.('min-h-auto')
+
+ appDialogs.confirmAction.showModal()
+ }
+
+ console.log(
+ `Crowdnode Card submit TX`,
+ fde.intent,
+ {event, fde},
+ )
+
+ if (fde.intent === 'deposit') {
+ appDialogs.sendOrReceive?.elements?.form?.classList.add?.('min-h-auto')
+ await appDialogs.sendOrReceive.render({
+ name: 'Deposit to CrowdNode',
+ cashSend: () => html``,
+ hideAddressee: true,
+ action: fde.intent,
+ wallet,
+ account: appState.account,
+ userInfo,
+ contacts: appState.contacts,
+ to: '@crowdnode',
+ })
+ appDialogs.sendOrReceive.showModal()
+ }
+ if (fde.intent === 'withdraw') {
+ crowdnodeTransactionRig.markup.fields = html`
+
+
+
+
+ Enter the percentage you wish to unstake.
+ `
+
+ let cnWithdraw = crowdnodeTransactionRig.render({
+ el: mainApp,
+ cfg: {
+ state: {
+ name: 'Withdraw from CrowdNode',
+ submitTxt: 'Withdraw',
+ submitAlt: 'Withdraw funds from CrowdNode',
+ cancelTxt: 'Cancel',
+ cancelAlt: `Cancel Withdraw`,
+ callback: async (state, res) => {
+ console.log('cnWithdraw callback', { state, res })
+ state.value = {
+ ...state.value,
+ status: DIALOG_STATUS.SUCCESS
+ }
+ return { state, res }
+ },
+ },
+ events: {
+ input: event => {
+ if (
+ event?.target?.type === 'range' &&
+ event.target.value > -1
+ ) {
+ event.target.form.percent.value = event?.target?.value || 0
+ }
+
+ if (
+ event?.target?.type === 'number' &&
+ event.target.value > -1
+ ) {
+ event.target.form.percentRange.value = event?.target?.value || 0
+ }
+ },
+ },
+ },
+ })
+ cnWithdraw?.elements?.form?.classList.add?.('min-h-auto')
+ crowdnodeTransactionRig.markup.footer = html`
+
+
+ ${crowdnodeTransactionRig.state.value.cancelTxt}
+
+
+ ${crowdnodeTransactionRig.state.value.submitTxt}
+
+
+ `
+
+ console.log(
+ `Crowdnode Card TX`,
+ fde.intent,
+ {cnWithdraw},
+ )
+
+ cnWithdraw.showModal()
+ }
+ }
+ },
+ }
+
+ let cnCard = new CrowdNodeCard(cfg)
+ console.log('CN Card Outer', cnCard)
+
+ cnCard.render({
+ cfg,
+ el: integrationsSection,
+ position: 'beforeend'
+ })
+ })
+
+
+ // integrationsSection.insertAdjacentHTML('beforeend', html`
+ //
+ //
+ // Coming soon
+ // Earn interest with
+ //
+ //
+ //
+ // `)
let txs = await getTxs(
appState,
@@ -895,7 +1165,7 @@ async function main() {
action: 'lock',
actionType: 'warn',
alert: state => html``,
- callback: () => {
+ callback: async () => {
sessionStorage.clear()
window.location.reload()
},
@@ -925,7 +1195,7 @@ async function main() {
This is an irreversable action which removes all wallet data from your browser, make sure to backup your data first. WE RETAIN NO BACKUPS OF YOUR WALLET DATA.
`,
- callback: () => {
+ callback: async () => {
localStorage.clear()
sessionStorage.clear()
// @ts-ignore
diff --git a/src/rigs/add-contact.js b/src/rigs/add-contact.js
index e583b1f..80907fb 100644
--- a/src/rigs/add-contact.js
+++ b/src/rigs/add-contact.js
@@ -1,26 +1,29 @@
-import { lit as html } from '../helpers/lit.js'
-import { qrSvg } from '../helpers/qr.js'
import {
- deriveWalletData,
+ OIDC_CLAIMS,
+ ALIAS_REGEX,
+} from '../utils/constants.js'
+
+import {
+ lit as html,
formDataEntries,
setClipboard,
openBlobSVG,
- // sortContactsByAlias,
- // sortContactsByName,
+ debounce,
+} from '../utils/generic.js'
+
+import {
+ getStoreData,
+} from '../utils/db.js'
+
+import { qrSvg } from '../utils/qr.js'
+
+import {
+ deriveWalletData,
parseAddressField,
generateContactPairingURI,
- getStoreData,
- debounce,
- // nobounce,
- // getRandomWords,
getUniqueAlias,
isUniqueAlias,
-} from '../helpers/utils.js'
-
-import {
- OIDC_CLAIMS,
- ALIAS_REGEX,
-} from '../helpers/constants.js'
+} from '../utils/dash/local.js'
export let addContactRig = (async function (globals) {
'use strict';
diff --git a/src/rigs/confirm-action.js b/src/rigs/confirm-action.js
index 1e43ca1..8722e7b 100644
--- a/src/rigs/confirm-action.js
+++ b/src/rigs/confirm-action.js
@@ -1,7 +1,11 @@
-import { lit as html } from '../helpers/lit.js'
import {
+ DIALOG_STATUS,
+} from '../utils/constants.js'
+
+import {
+ lit as html,
formDataEntries,
-} from '../helpers/utils.js'
+} from '../utils/generic.js'
export let confirmActionRig = (async function (globals) {
'use strict';
@@ -26,6 +30,7 @@ export let confirmActionRig = (async function (globals) {
actionType: 'warn',
actionClasses: {
info: 'bg-info dark bg-info-hover',
+ infoo: 'outline brd-info info dark light-hover bg-info-hover',
warn: 'outline brd-warn warn dark-hover bg-warn-hover',
dang: 'outline brd-dang dang light-hover bg-dang-hover',
},
@@ -79,16 +84,7 @@ export let confirmActionRig = (async function (globals) {
`,
alert: state => html``,
- // alert: state => html`
- //
- //
- // This is an irreversable action, make sure to backup first.
- //
- //
- // `,
- content: state => html`
- ${state.header(state)}
-
+ fields: state => html`
Are you sure you want to ${state.action} ${
@@ -96,6 +92,11 @@ export let confirmActionRig = (async function (globals) {
}?
+ `,
+ content: state => html`
+ ${state.header(state)}
+
+ ${state.fields(state)}
${state.footer(state)}
`,
@@ -108,8 +109,17 @@ export let confirmActionRig = (async function (globals) {
if (fde?.intent === 'act') {
// state.elements.dialog.returnValue = String(fde.intent)
- state.callback?.(state, fde)
- confirmAction.close(fde.intent)
+ let res = await state.callback?.(state, fde)
+
+ if (
+ res.state.status === DIALOG_STATUS.SUCCESS ||
+ res.state.status === DIALOG_STATUS.ERROR
+ ) {
+ // state.elements.dialog?.querySelector('progress')?.remove()
+ state.elements.progress?.remove?.()
+
+ confirmAction.close(fde.intent)
+ }
}
},
},
diff --git a/src/rigs/confirm-delete.js b/src/rigs/confirm-delete.js
index 6bee8d7..04487df 100644
--- a/src/rigs/confirm-delete.js
+++ b/src/rigs/confirm-delete.js
@@ -1,9 +1,10 @@
-import { lit as html } from '../helpers/lit.js'
import {
+ lit as html,
formDataEntries,
+} from '../utils/generic.js'
+import {
getStoreData,
- sortContactsByAlias,
-} from '../helpers/utils.js'
+} from '../utils/db.js'
export let confirmDeleteRig = (async function (globals) {
'use strict';
diff --git a/src/rigs/crowdnode-tx.js b/src/rigs/crowdnode-tx.js
new file mode 100644
index 0000000..586a588
--- /dev/null
+++ b/src/rigs/crowdnode-tx.js
@@ -0,0 +1,85 @@
+import {
+ DIALOG_STATUS,
+} from '../utils/constants.js'
+
+import {
+ lit as html,
+ formDataEntries,
+} from '../utils/generic.js'
+
+import { DialogContructor } from '../components/modal.js'
+
+export const crowdnodeTransactionRig = (() => {
+ 'use strict';
+
+ let dialogConfig = {
+ state: {
+ name: 'CrowdNode Deposit / Withdraw',
+ actionTxt: 'Do It!',
+ actionAlt: 'Yeah, really do this!',
+ cancelTxt: 'Cancel',
+ cancelAlt: `Cancel`,
+ closeTxt: html` `,
+ closeAlt: `Cancel & Close`,
+ action: '',
+ target: '',
+ targetFallback: 'this wallet',
+ actionType: 'warn',
+ actionClasses: {
+ info: 'bg-info dark bg-info-hover',
+ infoo: 'outline brd-info info dark light-hover bg-info-hover',
+ warn: 'outline brd-warn warn dark-hover bg-warn-hover',
+ dang: 'outline brd-dang dang light-hover bg-dang-hover',
+ },
+ showCancelBtn: true,
+ showActBtn: true,
+ },
+ markup: {},
+ events: {},
+ appElement: document.body,
+ }
+
+ let crowdnodeTransaction = new DialogContructor(dialogConfig)
+
+ // console.log('Modal.js Dialog', crowdnodeTransaction)
+
+ dialogConfig.events.submit = async function (event) {
+ event.preventDefault()
+ event.stopPropagation()
+
+ let fde = formDataEntries(event)
+
+ if (fde?.intent === 'act') {
+ let res = await crowdnodeTransaction.state.value.callback?.(
+ crowdnodeTransaction.state,
+ fde,
+ )
+
+ // console.log('crowdnodeTransaction submit res', res)
+
+ if (
+ res.state.value.status === DIALOG_STATUS.SUCCESS ||
+ res.state.value.status === DIALOG_STATUS.ERROR
+ ) {
+ crowdnodeTransaction.elements.progress?.remove?.()
+
+ crowdnodeTransaction.elements.dialog.returnValue = String(fde.intent)
+ crowdnodeTransaction.elements.dialog?.close(String(fde.intent))
+ }
+ } else {
+ crowdnodeTransaction.elements.progress?.remove?.()
+
+ crowdnodeTransaction.elements.dialog.returnValue = 'cancel'
+ crowdnodeTransaction.elements.dialog?.close('cancel')
+ }
+ }
+
+ dialogConfig.markup.alert = html``
+ dialogConfig.markup.submitIcon = ``
+
+ crowdnodeTransaction.updateConfig(dialogConfig)
+
+ return crowdnodeTransaction
+})();
+
+export default crowdnodeTransactionRig
\ No newline at end of file
diff --git a/src/rigs/edit-contact.js b/src/rigs/edit-contact.js
index f80b933..97898b8 100644
--- a/src/rigs/edit-contact.js
+++ b/src/rigs/edit-contact.js
@@ -1,19 +1,25 @@
-import { lit as html } from '../helpers/lit.js'
import {
- deriveWalletData,
+ OIDC_CLAIMS,
+ ALIAS_REGEX,
+} from '../utils/constants.js'
+
+import {
+ lit as html,
formDataEntries,
- parseAddressField,
- getStoreData,
debounce,
getAvatar,
- getUniqueAlias,
- isUniqueAlias,
-} from '../helpers/utils.js'
+} from '../utils/generic.js'
import {
- OIDC_CLAIMS,
- ALIAS_REGEX,
-} from '../helpers/constants.js'
+ getStoreData,
+} from '../utils/db.js'
+
+import {
+ deriveWalletData,
+ parseAddressField,
+ getUniqueAlias,
+ isUniqueAlias,
+} from '../utils/dash/local.js'
export let editContactRig = (async function (globals) {
'use strict';
diff --git a/src/rigs/edit-profile.js b/src/rigs/edit-profile.js
index afe0b00..8af59cc 100644
--- a/src/rigs/edit-profile.js
+++ b/src/rigs/edit-profile.js
@@ -1,17 +1,15 @@
-import { lit as html } from '../helpers/lit.js'
-import { qrSvg } from '../helpers/qr.js'
import {
+ ALIAS_REGEX,
+} from '../utils/constants.js'
+
+import {
+ lit as html,
formDataEntries,
setClipboard,
openBlobSVG,
- // sortContactsByAlias,
- // sortContactsByName,
- // parseAddressField,
-} from '../helpers/utils.js'
+} from '../utils/generic.js'
-import {
- ALIAS_REGEX,
-} from '../helpers/constants.js'
+import { qrSvg } from '../utils/qr.js'
export let editProfileRig = (async function (globals) {
'use strict';
diff --git a/src/rigs/onboard.js b/src/rigs/onboard.js
index 899120f..4f7d457 100644
--- a/src/rigs/onboard.js
+++ b/src/rigs/onboard.js
@@ -1,7 +1,7 @@
-import { lit as html } from '../helpers/lit.js'
import {
+ lit as html,
formDataEntries,
-} from '../helpers/utils.js'
+} from '../utils/generic.js'
export let onboardRig = (async function (globals) {
'use strict';
diff --git a/src/rigs/pair-qr.js b/src/rigs/pair-qr.js
index c69d1e0..3c4f6b4 100644
--- a/src/rigs/pair-qr.js
+++ b/src/rigs/pair-qr.js
@@ -1,11 +1,14 @@
-import { lit as html } from '../helpers/lit.js'
-import { qrSvg } from '../helpers/qr.js'
import {
+ lit as html,
setClipboard,
openBlobSVG,
- // generatePaymentRequestURI,
+} from '../utils/generic.js'
+
+import { qrSvg } from '../utils/qr.js'
+
+import {
generateContactPairingURI,
-} from '../helpers/utils.js'
+} from '../utils/dash/local.js'
export let pairQrRig = (async function (globals) {
'use strict';
diff --git a/src/rigs/phrase-backup.js b/src/rigs/phrase-backup.js
index 4a23603..bbaef93 100644
--- a/src/rigs/phrase-backup.js
+++ b/src/rigs/phrase-backup.js
@@ -1,9 +1,11 @@
-import { lit as html } from '../helpers/lit.js'
import {
- formDataEntries,
- phraseToEl,
+ lit as html,
setClipboard,
-} from '../helpers/utils.js'
+} from '../utils/generic.js'
+
+import {
+ phraseToEl,
+} from '../utils/dash/local.js'
export let phraseBackupRig = (async function (globals) {
'use strict';
diff --git a/src/rigs/phrase-generate.js b/src/rigs/phrase-generate.js
index 7d003ed..c2ff9fa 100644
--- a/src/rigs/phrase-generate.js
+++ b/src/rigs/phrase-generate.js
@@ -1,10 +1,11 @@
-import { lit as html } from '../helpers/lit.js'
-import {
- formDataEntries,
-} from '../helpers/utils.js'
import {
ALIAS_REGEX,
-} from '../helpers/constants.js'
+} from '../utils/constants.js'
+
+import {
+ lit as html,
+ formDataEntries,
+} from '../utils/generic.js'
export let phraseGenerateRig = (async function (globals) {
'use strict';
diff --git a/src/rigs/phrase-import.js b/src/rigs/phrase-import.js
index dc569f9..5e81336 100644
--- a/src/rigs/phrase-import.js
+++ b/src/rigs/phrase-import.js
@@ -1,14 +1,18 @@
-import { lit as html } from '../helpers/lit.js'
import {
+ ALIAS_REGEX,
+ PHRASE_REGEX,
+} from '../utils/constants.js'
+
+import {
+ lit as html,
formDataEntries,
readFile,
- verifyPhrase,
fileIsSubType,
-} from '../helpers/utils.js'
+} from '../utils/generic.js'
+
import {
- ALIAS_REGEX,
- PHRASE_REGEX,
-} from '../helpers/constants.js'
+ verifyPhrase,
+} from '../utils/dash/local.js'
export let phraseImportRig = (async function (globals) {
'use strict';
diff --git a/src/rigs/request-qr.js b/src/rigs/request-qr.js
index 17191f1..72b92a0 100644
--- a/src/rigs/request-qr.js
+++ b/src/rigs/request-qr.js
@@ -1,15 +1,17 @@
-import { lit as html } from '../helpers/lit.js'
-import { qrSvg } from '../helpers/qr.js'
import {
+ lit as html,
formDataEntries,
setClipboard,
openBlobSVG,
+} from '../utils/generic.js'
+
+import {
generatePaymentRequestURI,
- fixedDash,
- roundUsing,
getPartialHDPath,
getAddressIndexFromUsage,
-} from '../helpers/utils.js'
+} from '../utils/dash/local.js'
+
+import { qrSvg } from '../utils/qr.js'
export let requestQrRig = (async function (globals) {
'use strict';
diff --git a/src/rigs/scan.js b/src/rigs/scan.js
index 0c481e1..7304224 100644
--- a/src/rigs/scan.js
+++ b/src/rigs/scan.js
@@ -1,7 +1,6 @@
-import { lit as html } from '../helpers/lit.js'
-// import {
-// formDataEntries,
-// } from '../helpers/utils.js'
+import {
+ lit as html,
+} from '../utils/generic.js'
export let scanContactRig = (async function (globals) {
'use strict';
diff --git a/src/rigs/send-confirm.js b/src/rigs/send-confirm.js
index 5e0fe8c..a89a120 100644
--- a/src/rigs/send-confirm.js
+++ b/src/rigs/send-confirm.js
@@ -1,10 +1,15 @@
-import { lit as html } from '../helpers/lit.js'
import {
+ lit as html,
formDataEntries,
+} from '../utils/generic.js'
+
+import {
getStoreData,
- sortContactsByAlias,
+} from '../utils/db.js'
+
+import {
formatDash,
-} from '../helpers/utils.js'
+} from '../utils/dash/local.js'
export let sendConfirmRig = (async function (globals) {
'use strict';
diff --git a/src/rigs/send-or-request.js b/src/rigs/send-or-request.js
index a2d94ac..eb5a5e9 100644
--- a/src/rigs/send-or-request.js
+++ b/src/rigs/send-or-request.js
@@ -1,15 +1,18 @@
-import { lit as html } from '../helpers/lit.js'
-import { AMOUNT_REGEX, USAGE } from '../helpers/constants.js'
+import { AMOUNT_REGEX, USAGE } from '../utils/constants.js'
+
import {
+ lit as html,
formDataEntries,
+} from '../utils/generic.js'
+
+import {
parseAddressField,
fixedDash,
- toDASH,
toDash,
roundUsing,
getPartialHDPath,
getAddressIndexFromUsage,
-} from '../helpers/utils.js'
+} from '../utils/dash/local.js'
export let sendOrReceiveRig = (async function (globals) {
'use strict';
@@ -38,6 +41,7 @@ export let sendOrReceiveRig = (async function (globals) {
closeTxt: html` `,
closeAlt: `Close`,
action: 'send',
+ hideAddressee: false,
submitIcon: state => {
const icon = {
send: html`
@@ -180,26 +184,56 @@ export let sendOrReceiveRig = (async function (globals) {
`
},
+ cashSend: state => html`
+
+
+ Use CashSend
+
+
+
+
+ `,
+ addressee: state => {
+ if (state.hideAddressee) {
+ return html`
+
+ `
+ }
+
+ return html`
+
+
+
+ ${state.qrScanBtn(state)}
+
+ `
+ },
content: state => html`
${state.header(state)}
-
-
-
- ${state.qrScanBtn(state)}
-
+ ${state.addressee(state)}
@@ -228,18 +262,7 @@ export let sendOrReceiveRig = (async function (globals) {
${state.fundAmountBtns(state)}
-
-
- Use CashSend
-
-
-
-
+ ${state.cashSend(state)}
@@ -444,6 +467,10 @@ export let sendOrReceiveRig = (async function (globals) {
inWallet = Object.values(contact?.incoming)?.[0]
}
+ console.log('send or request', {
+ to, contact, outWallet, inWallet
+ })
+
if (!inWallet) {
// state.wallet.addressIndex = (
// state.wallet?.addressIndex ?? -1
@@ -518,7 +545,7 @@ export let sendOrReceiveRig = (async function (globals) {
}
let leftoverBalance = walletFunds.balance - amount
- let fullTransfer = leftoverBalance <= 0.0010_0200
+ let fullTransfer = leftoverBalance > 0 && leftoverBalance <= 0.0010_0200
// let fullTransfer = leftoverBalance <= 0.0001_0200
if (
@@ -542,6 +569,7 @@ export let sendOrReceiveRig = (async function (globals) {
state.wallet.accountIndex,
state.wallet.addressIndex,
)
+
let amountNeeded = fixedDash(roundUsing(Math.floor, Math.abs(
walletFunds.balance - Number(fde.amount)
)))
diff --git a/src/rigs/show-error.js b/src/rigs/show-error.js
index 0e590d5..b47cd6e 100644
--- a/src/rigs/show-error.js
+++ b/src/rigs/show-error.js
@@ -1,4 +1,4 @@
-import { lit as html } from '../helpers/lit.js'
+import { lit as html } from '../utils/generic.js'
export async function showErrorDialog(options) {
let opts = {
@@ -60,7 +60,7 @@ export async function showErrorDialog(options) {
content: state => html`
${state.header(state)}
-
+
diff --git a/src/rigs/tx-info.js b/src/rigs/tx-info.js
index f7f7c92..5f89105 100644
--- a/src/rigs/tx-info.js
+++ b/src/rigs/tx-info.js
@@ -1,7 +1,7 @@
-import { lit as html } from '../helpers/lit.js'
+import { lit as html } from '../utils/generic.js'
import {
formatDash,
-} from '../helpers/utils.js'
+} from '../utils/dash/local.js'
export let txInfoRig = (async function (globals) {
'use strict';
diff --git a/src/rigs/wallet-backup.js b/src/rigs/wallet-backup.js
index fd32a49..5ed9551 100644
--- a/src/rigs/wallet-backup.js
+++ b/src/rigs/wallet-backup.js
@@ -1,7 +1,7 @@
-import { lit as html } from '../helpers/lit.js'
import {
+ lit as html,
formDataEntries,
-} from '../helpers/utils.js'
+} from '../utils/generic.js'
export let walletBackupRig = (async function (globals) {
'use strict';
diff --git a/src/rigs/wallet-decrypt.js b/src/rigs/wallet-decrypt.js
index f43cd27..deffee0 100644
--- a/src/rigs/wallet-decrypt.js
+++ b/src/rigs/wallet-decrypt.js
@@ -1,10 +1,10 @@
-import { lit as html } from '../helpers/lit.js'
import {
+ lit as html,
formDataEntries,
-} from '../helpers/utils.js'
+} from '../utils/generic.js'
import {
initWallet,
-} from '../helpers/wallet.js'
+} from '../utils/dash/local.js'
export let walletDecryptRig = (async function (globals) {
'use strict';
diff --git a/src/rigs/wallet-encrypt.js b/src/rigs/wallet-encrypt.js
index 0cc9c5c..04fbda4 100644
--- a/src/rigs/wallet-encrypt.js
+++ b/src/rigs/wallet-encrypt.js
@@ -1,10 +1,10 @@
-import { lit as html } from '../helpers/lit.js'
import {
+ lit as html,
formDataEntries,
-} from '../helpers/utils.js'
+} from '../utils/generic.js'
import {
initWallet,
-} from '../helpers/wallet.js'
+} from '../utils/dash/local.js'
export let walletEncryptRig = (async function (globals) {
'use strict';
diff --git a/src/secp.js b/src/secp.js
new file mode 100644
index 0000000..f49b0e8
--- /dev/null
+++ b/src/secp.js
@@ -0,0 +1,11 @@
+import '../node_modules/@dashincubator/secp256k1/secp256k1.js';
+
+// @ts-ignore
+const secp = window?.nobleSecp256k1 || globalThis?.nobleSecp256k1 || window?.Secp256k1 || globalThis?.Secp256k1
+
+// @ts-ignore
+window.Secp256k1 = secp
+
+export const Secp256k1 = secp
+
+export default secp
diff --git a/src/state/index.js b/src/state/index.js
index fb549e3..7781f54 100644
--- a/src/state/index.js
+++ b/src/state/index.js
@@ -1,12 +1,12 @@
import {
OIDC_CLAIMS,
-} from '../helpers/constants.js'
+} from '../utils/constants.js'
import {
envoy,
-} from '../helpers/utils.js'
+} from '../utils/retort.js'
import {
store,
-} from '../helpers/wallet.js'
+} from '../utils/dash/network.js'
export const appDialogs = envoy(
{
diff --git a/src/styles/components.css b/src/styles/components.css
index 43c7933..6751960 100644
--- a/src/styles/components.css
+++ b/src/styles/components.css
@@ -116,6 +116,9 @@
display: flex;
}
+.integration-sect {
+ padding: 0 1rem;
+}
.integration-sect > section > header {
flex-direction: column;
align-items: start;
@@ -140,6 +143,34 @@
text-decoration: none;
}
+.card {
+ border-radius: 1rem;
+ overflow: auto;
+ flex: 1 1 auto;
+ max-width: 49%;
+ overflow: hidden;
+}
+.card header,
+.card footer {
+ padding: 1rem 0;
+}
+
+.card header a,
+.card header a:active,
+.card header a:focus,
+.card header a:hover {
+ color: var(--fc);
+ text-decoration: none;
+}
+.card section {
+ padding: .25rem 0;
+}
+.card fieldset {
+ min-width: auto;
+}
+.card footer button {
+}
+
@@ -150,6 +181,21 @@
.cols > section {
padding: 0;
}
+
+ .card {
+ max-width: 32.5%;
+ padding: 0 1rem;
+ }
+ .card header,
+ .card footer {
+ padding: 1rem 0;
+ }
+ .card section {
+ padding: .25rem 0;
+ }
+ .integration-sect {
+ padding: 0;
+ }
}
@media (min-width: 980px) {
.cols {
diff --git a/src/styles/dialog.css b/src/styles/dialog.css
index 75c3b1b..5be19e4 100644
--- a/src/styles/dialog.css
+++ b/src/styles/dialog.css
@@ -181,6 +181,9 @@ dialog > form > fieldset div:has(input + div) {
border-radius: 6.25rem;
border: 1px solid var(--dark-600);
}
+dialog > form > fieldset input[type="range"] {
+ padding: 0;
+}
dialog > form > fieldset input + p,
dialog > form > fieldset label {
text-align: left;
@@ -197,7 +200,7 @@ dialog > form > fieldset input + p {
font-weight: 400;
margin: .5rem 0;
}
-dialog > form > fieldset p {
+dialog > form fieldset p {
display: flex;
padding: 0;
margin: .8rem;
@@ -217,7 +220,7 @@ dialog > form > fieldset div + p {
margin: 0 0 0 1rem;
text-align: left;
}
-dialog > form > fieldset p > span {
+dialog > form fieldset p > span {
padding: .25rem;
border: 1px solid var(--dark-500);
}
diff --git a/src/styles/form.css b/src/styles/form.css
index c1fc1b1..d1a4aca 100644
--- a/src/styles/form.css
+++ b/src/styles/form.css
@@ -494,7 +494,7 @@ label {
}
label > input[type=checkbox],
label + input[type=checkbox] {
- width: 2rem;
+ width: 1.25rem;
}
div.switch {
@@ -739,6 +739,25 @@ form[name="network"] {
cursor: default;
}
+main form header {
+ margin: 0 auto;
+ min-height: auto;
+ /* width: 100%;
+ display: flex;
+ justify-content: space-between;
+ align-items: flex-start;
+ flex-direction: column-reverse; */
+}
+
+.card footer {
+ gap: .25rem;
+}
+
+.card footer button {
+ padding: 0.63rem .25rem;
+ flex: 1 1 50%;
+}
+
@media (min-width: 650px) {
main form {
@@ -776,4 +795,8 @@ form[name="network"] {
form > article > figure figcaption + div.big {
font-size: 3.75rem;
} */
+
+ .card footer button {
+ padding: 0.63rem 1rem;
+ }
}
\ No newline at end of file
diff --git a/src/styles/motion.css b/src/styles/motion.css
index 26aa428..85cff62 100644
--- a/src/styles/motion.css
+++ b/src/styles/motion.css
@@ -1,16 +1,16 @@
@-webkit-keyframes await-progress {
- 0% {
- background-position: -322px;
+ 0% {
+ background-position: -150%;
}
100% {
- background-position: 322px;
+ background-position: 200%;
}
}
@keyframes await-progress {
- 0% {
- background-position: -322px;
+ 0% {
+ background-position: -150%;
}
100% {
- background-position: 322px;
+ background-position: 200%;
}
}
diff --git a/src/styles/progress.css b/src/styles/progress.css
index 1ad2c4f..6d30564 100644
--- a/src/styles/progress.css
+++ b/src/styles/progress.css
@@ -1,19 +1,3 @@
-progress:indeterminate {
- background: linear-gradient(
- 90deg,
- #0000 0%,
- #0000 50%,
- var(--info) 100%
- );
-}
-progress.recording:indeterminate {
- background: linear-gradient(
- 90deg,
- var(--livea) 0%,
- var(--live) 50%,
- var(--livea) 100%
- );
-}
progress.pending,
progress.pending[role] {
position: absolute;
@@ -26,14 +10,15 @@ progress.pending[role] {
padding: 0;
border: none;
background-color: transparent;
- background-size: auto;
+ /* background-size: auto; */
+ background-size: 50% 100%;
background-repeat: no-repeat;
background-position: 0 0;
appearance: none;
-moz-appearance: none;
-webkit-appearance: none;
border: 0 solid transparent;
- visibility: hidden;
+ /* visibility: hidden; */
}
progress.recording,
progress.recording[role] {
@@ -52,11 +37,3 @@ progress.pending:indeterminate {
-webkit-animation: await-progress 2.5s ease-in-out infinite;
animation: await-progress 2.5s ease-in-out infinite;
}
-
-
-/* form[name=toggle_relay]:has(input[type=checkbox]:checked) {
- border-bottom: .25rem solid var(--dang);
-} */
-form[name=toggle_relay]:has(input[type=checkbox]:checked) progress {
- visibility: visible;
-}
diff --git a/src/styles/theme.css b/src/styles/theme.css
index c8e5830..55d1c2e 100644
--- a/src/styles/theme.css
+++ b/src/styles/theme.css
@@ -19,6 +19,10 @@
--light-500: #004470;
--light-600: #003e66;
+ --card-100: hsl(200deg 80% 60% / 15%);
+ --card-100: #00447066;
+
+
--grayscale-gray-400: #9DA6A6;
--c: var(--dark-900);
@@ -140,11 +144,11 @@ a:hover {
}
svg {
- fill: currentColor;
vertical-align: bottom;
}
-svg path {
+svg g:not(.lock),
+svg path:not(.lock) {
fill: currentColor;
}
@@ -316,6 +320,11 @@ th {
border-radius: 6.25rem;
}
+.card {
+ background-color: var(--card-100);
+ /* background-color: var(--dark-600); */
+}
+
.cols > section > div {
background-color: var(--nav-bg);
}
diff --git a/src/helpers/constants.js b/src/utils/constants.js
similarity index 75%
rename from src/helpers/constants.js
rename to src/utils/constants.js
index 629a956..655999a 100644
--- a/src/helpers/constants.js
+++ b/src/utils/constants.js
@@ -1,5 +1,3 @@
-export const STOREAGE_SALT = 'b9f4088bd3a93783147e3d78aa10cc911a2449a0d79a226ae33a5957b368cc18'
-
export const KS_PRF = {
'hmac-sha256': 'SHA-256',
}
@@ -142,3 +140,59 @@ export const USAGE = {
RECEIVE,
CHANGE,
}
+
+const NOT_LOADING = 0
+const LOADING = 1
+const SUCCESS = 2
+const ERROR = 3
+
+export const DIALOG_STATUS = {
+ NOT_LOADING,
+ LOADING,
+ SUCCESS,
+ ERROR
+}
+
+export const CROWDNODE = {
+ offset: 20000,
+ duffs: 100000000,
+ satoshis: 100000000,
+ depositMinimum: 100000,
+ stakeMinimum: 50000000,
+
+ /**
+ * @type {Record}
+ */
+ requests: {
+ acceptTerms: 65536,
+ offset: 20000,
+ signupForApi: 131072,
+ toggleInstantPayout: 4096,
+ withdrawMin: 1,
+ withdrawMax: 1000,
+ },
+
+ /**
+ * @type {Record}
+ */
+ messages: {
+ PleaseAcceptTerms: 2,
+ WelcomeToCrowdNodeBlockChainAPI: 4,
+ DepositReceived: 8,
+ WithdrawalQueued: 16,
+ WithdrawalFailed: 32,
+ AutoWithdrawalEnabled: 64,
+ AutoWithdrawalDisabled: 128,
+ },
+
+ /**
+ * @type {Record}
+ */
+ responses: {},
+}
+
+CROWDNODE.responses = Object.fromEntries(
+ Object.entries(CROWDNODE.messages).map(
+ ([k,v]) => [v,k]
+ )
+)
diff --git a/src/utils/cryptic.js b/src/utils/cryptic.js
new file mode 100644
index 0000000..0b4a603
--- /dev/null
+++ b/src/utils/cryptic.js
@@ -0,0 +1,328 @@
+// @ts-ignore
+import blake from 'blakejs'
+// @ts-ignore
+import { keccak_256 } from '@noble/hashes/sha3'
+
+import {
+ Cryptic,
+} from '../imports.js'
+import {
+ KS_CIPHER, KS_PRF,
+} from './constants.js'
+
+export async function decryptWallet(
+ decryptPass,
+ decryptIV,
+ decryptSalt,
+ ciphertext,
+) {
+ const cryptic = Cryptic.create(
+ decryptPass,
+ decryptSalt,
+ )
+
+ return await cryptic.decrypt(ciphertext, decryptIV);
+}
+
+export function blake256(data) {
+ if ('string' === typeof data) {
+ data = Cryptic.hexToBuffer(data)
+ }
+ const context = blake.blake2bInit(32, null);
+ blake.blake2bUpdate(context, data);
+ return Cryptic.toHex(blake.blake2bFinal(context));
+}
+
+export function getKeystoreData(keystore) {
+ const {
+ ciphertext,
+ cipher,
+ mac,
+ } = keystore.crypto
+ const [
+ cipherAlgorithm,
+ cipherLength,
+ ] = KS_CIPHER[cipher]
+
+ const derivationAlgorithm = keystore.crypto.kdf.toUpperCase()
+ const hashingAlgorithm = KS_PRF[keystore.crypto.kdfparams.prf]
+ const derivedKeyLength = keystore?.crypto?.kdfparams?.dklen
+ const iterations = keystore.crypto.kdfparams.c
+ const iv = keystore.crypto.cipherparams.iv
+ const ivBuffer = Cryptic.hexToBuffer(iv)
+ const salt = keystore.crypto.kdfparams.salt
+ const saltBuffer = Cryptic.hexToBuffer(salt)
+
+ const keyLength = derivedKeyLength / 2
+ const numBits = (keyLength + iv.length) * 8
+
+ return {
+ cipher,
+ cipherAlgorithm,
+ cipherLength,
+ ciphertext,
+ mac,
+ derivationAlgorithm,
+ hashingAlgorithm,
+ derivedKeyLength,
+ iterations,
+ iv,
+ ivBuffer,
+ salt,
+ saltBuffer,
+ keyLength,
+ numBits,
+ }
+}
+
+export async function setupCryptic(
+ encryptionPassword,
+ keystore,
+) {
+ const ks = getKeystoreData(keystore)
+ const {
+ cipherLength, cipherAlgorithm,
+ derivationAlgorithm, hashingAlgorithm, iv,
+ iterations, salt,
+ } = ks
+
+ Cryptic.setConfig({
+ cipherAlgorithm,
+ cipherLength,
+ hashingAlgorithm,
+ derivationAlgorithm,
+ iterations,
+ })
+
+ const cryptic = Cryptic.create(
+ encryptionPassword,
+ salt,
+ );
+
+ return {
+ Cryptic,
+ cryptic,
+ ks,
+ }
+}
+
+export async function encryptData(
+ encryptionPassword,
+ keystore,
+ data,
+) {
+ const { cryptic, ks } = await setupCryptic(
+ encryptionPassword,
+ keystore,
+ )
+
+ return await cryptic.encrypt(data, ks.iv);
+}
+
+export async function decryptData(
+ encryptionPassword,
+ keystore,
+ data,
+) {
+ const { cryptic, ks } = await setupCryptic(
+ encryptionPassword,
+ keystore,
+ )
+
+ return await cryptic.decrypt(data, ks.iv)
+}
+
+export function storedData(
+ encryptionPassword,
+ keystore,
+) {
+ const SD = {}
+
+ SD.decryptData = async function(data) {
+ if (data && 'string' === typeof data && data.length > 0) {
+ data = JSON.parse(await decryptData(
+ encryptionPassword,
+ keystore,
+ data
+ ))
+ }
+
+ return data
+ }
+
+ SD.decryptItem = async function(targetStore, item,) {
+ let data = await targetStore.getItem(
+ item,
+ )
+
+ data = await SD.decryptData(data)
+
+ return data
+ }
+
+ /**
+ *
+ * @param {*} targetStore
+ * @param {*} item
+ * @param {*} data
+ * @param {*} extend
+ * @returns {Promise<[String,Object]>}
+ */
+ SD.encryptData = async function(
+ targetStore, item, data = {}, extend = true
+ ) {
+ let encryptedData = ''
+ let storedData = {}
+ let jsonData = {}
+ if (extend) {
+ // storedData = await targetStore.getItem(
+ // item,
+ // )
+ storedData = await SD.decryptItem(
+ targetStore,
+ item
+ )
+ }
+
+ if (data) {
+ jsonData = {
+ ...storedData,
+ ...data,
+ }
+ encryptedData = await encryptData(
+ encryptionPassword,
+ keystore,
+ JSON.stringify(jsonData)
+ )
+ }
+
+ return [
+ encryptedData,
+ jsonData,
+ ]
+ }
+
+ SD.encryptItem = async function(
+ targetStore, item, data = {}, extend = true
+ ) {
+ let encryptedData = ''
+ let encryptedResult = ''
+ let result = {}
+
+ if (data || extend) {
+ let d = await SD.encryptData(targetStore, item, data, extend)
+ encryptedResult = d[0]
+ result = d[1]
+ encryptedData = await targetStore.setItem(
+ item,
+ encryptedResult
+ )
+ }
+
+ return result || data || encryptedData
+ // return encryptedData
+ }
+
+ return SD
+}
+
+export async function decryptKeystore(
+ encryptionPassword,
+ keystore,
+) {
+ const { Cryptic, cryptic, ks } = await setupCryptic(
+ encryptionPassword,
+ keystore,
+ )
+
+ const derivedBytes = await cryptic.deriveBits(ks.numBits, ks.salt)
+
+ const bMAC = blake256([
+ ...new Uint8Array(derivedBytes.slice(16, 32)),
+ ...Cryptic.toBytes(ks.ciphertext),
+ ])
+ const kMAC = Cryptic.toHex(keccak_256(new Uint8Array([
+ ...new Uint8Array(derivedBytes.slice(16, 32)),
+ ...Cryptic.toBytes(ks.ciphertext),
+ ])));
+
+ if (ks.mac && ![bMAC, kMAC].includes(ks.mac)) {
+ throw new Error('Invalid password')
+ }
+
+ return await cryptic.decrypt(ks.ciphertext, ks.iv)
+}
+
+export function genKeystore(
+ // aes-256-gcm
+ cipher = 'aes-128-ctr',
+ salt = Cryptic.randomBytes(32),
+ iv = Cryptic.randomBytes(16),
+ iterations = 262144,
+ id = crypto.randomUUID(),
+) {
+ return {
+ crypto: {
+ cipher,
+ ciphertext: '',
+ cipherparams: {
+ iv: Cryptic.bufferToHex(iv),
+ },
+ kdf: "pbkdf2",
+ kdfparams: {
+ c: iterations,
+ dklen: 32,
+ prf: "hmac-sha256",
+ salt: Cryptic.bufferToHex(salt),
+ },
+ mac: '',
+ },
+ id,
+ meta: 'dash-incubator-keystore',
+ version: 3,
+ }
+}
+
+export async function encryptKeystore(
+ encryptionPassword,
+ recoveryPhrase,
+) {
+ let keystore = genKeystore()
+ const { Cryptic, cryptic, ks } = await setupCryptic(
+ encryptionPassword,
+ keystore,
+ )
+
+ const derivedBytes = await cryptic.deriveBits(ks.numBits, ks.salt)
+ const encryptedPhrase = await cryptic.encrypt(recoveryPhrase, ks.iv);
+
+ keystore.crypto.ciphertext = encryptedPhrase
+
+ const bMAC = blake256([
+ ...new Uint8Array(derivedBytes.slice(16, 32)),
+ ...Cryptic.toBytes(keystore.crypto.ciphertext),
+ ])
+ const kMAC = Cryptic.toHex(keccak_256(new Uint8Array([
+ ...new Uint8Array(derivedBytes.slice(16, 32)),
+ ...Cryptic.toBytes(keystore.crypto.ciphertext),
+ ])));
+
+ keystore.crypto.mac = bMAC
+
+ // console.log(
+ // 'encrypted keystore',
+ // ks,
+ // {
+ // encryptedPhrase,
+ // // keyMaterial,
+ // // derivedKey,
+ // // derivedBytes,
+ // },
+ // {
+ // bMAC,
+ // kMAC,
+ // },
+ // )
+
+ return keystore
+}
\ No newline at end of file
diff --git a/src/utils/dash/local.js b/src/utils/dash/local.js
new file mode 100644
index 0000000..93a74cb
--- /dev/null
+++ b/src/utils/dash/local.js
@@ -0,0 +1,1432 @@
+import {
+ USAGE,
+ DUFFS,
+ DASH_URI_REGEX,
+ OIDC_CLAIMS,
+ SUPPORTED_CLAIMS,
+} from '../constants.js'
+
+import {
+ DashHd,
+ DashTx,
+ DashPhrase,
+} from '../../imports.js'
+
+import {
+ DatabaseSetup,
+ findInStore,
+ getFilteredStoreLength,
+ getStoredItems,
+ loadStoreObject,
+} from '../db.js'
+
+import {
+ encryptData,
+ encryptKeystore,
+} from '../cryptic.js'
+
+export const store = await DatabaseSetup()
+
+/**
+ *
+ * @param {String} [phraseOrXkey]
+ * @param {Number} [accountIndex]
+ * @param {Number} [addressIndex]
+ * @param {Number} [usageIndex]
+ *
+ * @returns {Promise}
+ */
+export async function deriveWalletData(
+ phraseOrXkey,
+ accountIndex = 0,
+ addressIndex = 0,
+ usageIndex = DashHd.RECEIVE,
+) {
+ if (!phraseOrXkey) {
+ throw new Error('Seed phrase or xkey value empty or invalid')
+ }
+
+ let recoveryPhrase
+ let seed, derivedWallet, wpub, id, account
+ let xkey, xprv, xpub, xkeyId
+ let addressKey, addressKeyId, address
+ let secretSalt = ''; // "TREZOR";
+ let recoveryPhraseArr = phraseOrXkey.trim().split(' ')
+
+ if (recoveryPhraseArr?.length >= 12) {
+ recoveryPhrase = phraseOrXkey;
+ }
+
+ if (
+ ['xprv', 'xpub'].includes(
+ phraseOrXkey?.substring(0,4) || ''
+ )
+ ) {
+ xkey = await DashHd.fromXKey(phraseOrXkey);
+ } else {
+ seed = await DashPhrase.toSeed(recoveryPhrase, secretSalt);
+ derivedWallet = await DashHd.fromSeed(seed);
+ wpub = await DashHd.toXPub(derivedWallet);
+ id = await DashHd.toId(derivedWallet);
+ account = await derivedWallet.deriveAccount(accountIndex);
+ xkey = await account.deriveXKey(usageIndex);
+ xprv = await DashHd.toXPrv(xkey);
+ }
+
+ xkeyId = await DashHd.toId(xkey);
+ xpub = await DashHd.toXPub(xkey);
+ addressKey = await xkey.deriveAddress(addressIndex);
+ addressKeyId = await DashHd.toId(addressKey);
+ address = await DashHd.toAddr(addressKey.publicKey);
+
+ return {
+ id,
+ accountIndex,
+ usageIndex,
+ addressIndex,
+ addressKeyId,
+ addressKey,
+ address,
+ xkeyId,
+ xkey,
+ xprv,
+ xpub,
+ seed,
+ wpub,
+ account,
+ derivedWallet,
+ recoveryPhrase,
+ }
+}
+
+/**
+ *
+ * @param {Number} [accountIndex]
+ * @param {Number} [addressIndex]
+ * @param {Number} [use]
+ *
+ * @returns {Promise}
+ */
+export async function generateWalletData(
+ accountIndex = 0,
+ addressIndex = 0,
+ use = DashHd.RECEIVE
+) {
+ let targetBitEntropy = 128;
+ let recoveryPhrase = await DashPhrase.generate(targetBitEntropy);
+
+ return await deriveWalletData(
+ recoveryPhrase,
+ accountIndex,
+ addressIndex,
+ use
+ )
+}
+
+/**
+ *
+ * @example
+ * let acct = deriveAccountData(wallet, 0, 0, 0)
+ *
+ * @param {HDWallet} wallet
+ * @param {Number} [accountIndex]
+ * @param {Number} [addressIndex]
+ * @param {Number} [use]
+ *
+ * @returns
+ */
+export async function deriveAccountData(
+ wallet,
+ accountIndex = 0,
+ addressIndex = 0,
+ use = DashHd.RECEIVE,
+) {
+ let account = await wallet.deriveAccount(accountIndex);
+ let xkey = await account.deriveXKey(use);
+ let xkeyId = await DashHd.toId(xkey);
+ let xprv = await DashHd.toXPrv(xkey);
+ let xpub = await DashHd.toXPub(xkey);
+ let xpubKey = await DashHd.fromXKey(xpub);
+ let xpubId = await DashHd.toId(xpubKey);
+ let key = await xkey.deriveAddress(addressIndex);
+ let address = await DashHd.toAddr(key.publicKey);
+
+ return {
+ account,
+ xkeyId,
+ xkey,
+ xprv,
+ xpub,
+ xpubKey,
+ xpubId,
+ key,
+ address
+ }
+}
+
+/**
+ *
+ * @example
+ * let addr = deriveAddressData(wallet, 0, 0, 0)
+ *
+ * @param {HDWallet} wallet
+ * @param {Number} [accountIndex]
+ * @param {Number} [addressIndex]
+ * @param {Number} [use]
+ *
+ * @returns
+ */
+export async function deriveAddressData(
+ wallet,
+ accountIndex = 0,
+ addressIndex = 0,
+ use = DashHd.RECEIVE,
+) {
+ let account = await wallet.deriveAccount(accountIndex);
+ let xkey = await account.deriveXKey(use);
+ let key = await xkey.deriveAddress(addressIndex);
+ let address = await DashHd.toAddr(key.publicKey);
+
+ return address
+}
+
+export function phraseToEl(phrase, el = 'span', cls = 'tag') {
+ let words = phrase?.split(' ')
+ return words?.map(
+ w => `<${el} class="${cls}">${w}${el}>`
+ )?.join(' ')
+}
+
+/**
+ * @param {Number} duffs - ex: 00000000
+ * @param {Number} [fix] - value for toFixed - ex: 8
+ */
+export function toDash(duffs, fix = 8) {
+ return (duffs / DUFFS).toFixed(fix);
+}
+
+/**
+ * @param {String} dash - ex: 0.00000000
+ */
+export function toDashStr(dash, pad = 12) {
+ return `Đ ` + `${dash}`.padStart(pad, " ");
+}
+
+/**
+ * Based on https://stackoverflow.com/a/48100007
+ *
+ * @param {Number} dash - ex: 0.00000000
+ * @param {Number} [fix] - value for toFixed - ex: 8
+ */
+export function fixedDash(dash, fix = 8) {
+ return (
+ Math.trunc(dash * Math.pow(10, fix)) / Math.pow(10, fix)
+ )
+ .toFixed(fix);
+}
+
+// https://stackoverflow.com/a/27946310
+export function roundUsing(func, number, prec = 8) {
+ var tempnumber = number * Math.pow(10, prec);
+ tempnumber = func(tempnumber);
+ return tempnumber / Math.pow(10, prec);
+}
+
+/**
+ * @param {Number} duffs - ex: 00000000
+ */
+export function toDASH(duffs) {
+ let dash = toDash(duffs / DUFFS);
+ return toDashStr(dash);
+}
+
+/**
+ * @param {Number} dash - ex: 0.00000000
+ * @param {Number} [fix] - value for toFixed - ex: 8
+ */
+export function fixedDASH(dash, fix = 8) {
+ return toDashStr(fixedDash(dash, fix));
+}
+
+/**
+ * @param {String} dash - ex: 0.00000000
+ */
+export function toDuff(dash) {
+ return Math.round(parseFloat(dash) * DUFFS);
+}
+
+export function formatDash(
+ unformattedBalance,
+ options = {},
+) {
+ let opts = {
+ maxlen: 10,
+ fract: 8,
+ sigsplit: 3,
+ ...options,
+ }
+ let funds = 0
+ let balance = `${funds}`
+
+ if (unformattedBalance) {
+ funds += unformattedBalance
+ balance = fixedDash(funds, opts.fract)
+ // TODO FIX: does not support large balances
+
+ // console.log('balance fixedDash', balance, balance.length)
+
+ let [fundsInt,fundsFract] = balance.split('.')
+ opts.maxlen -= fundsInt.length
+
+ let fundsFraction = fundsFract?.substring(
+ 0, Math.min(Math.max(0, opts.maxlen), opts.sigsplit)
+ )
+
+ let fundsRemainder = fundsFract?.substring(
+ fundsFraction.length,
+ Math.max(0, opts.maxlen)
+ )
+
+ balance = `${
+ fundsInt
+ }.${
+ fundsFraction
+ } ${
+ fundsRemainder
+ } `
+ }
+
+ return balance
+}
+
+export async function getUnusedChangeAddress(account) {
+ let filterQuery = {
+ xkeyId: account.xkeyId,
+ usageIndex: DashHd.CHANGE,
+ }
+
+ let foundAddrs = await findInStore(store.addresses, filterQuery)
+
+ for (let [fkey,fval] of Object.entries(foundAddrs)) {
+ if (!fval.insight?.balance) {
+ return fkey
+ }
+ }
+
+ // return foundAddr.address
+ return null
+}
+
+export async function loadWalletsForAlias($alias) {
+ $alias.$wallets = {}
+
+ if ($alias?.wallets) {
+ for (let w of $alias.wallets) {
+ let wallet = await store.wallets.getItem(w)
+ $alias.$wallets[w] = wallet
+ }
+ }
+
+ return $alias
+}
+
+export async function initWalletsInfo(
+ info = {},
+) {
+ let wallets = await getStoredItems(store.wallets)
+
+ info = {
+ ...OIDC_CLAIMS,
+ ...info,
+ }
+
+ let alias = info.preferred_username
+
+ wallets = Object.values(wallets || {})
+ wallets = wallets
+ .filter(w => w.alias === alias)
+ .map(w => w.id)
+
+ return {
+ alias,
+ wallets,
+ info,
+ }
+}
+
+export async function initWallet(
+ encryptionPassword,
+ wallet,
+ keystore,
+ accountIndex = 0,
+ addressIndex = 0,
+ infoOverride = {},
+) {
+ let {
+ alias,
+ wallets,
+ info,
+ } = await initWalletsInfo(infoOverride)
+
+ let { id, recoveryPhrase } = wallet
+
+ // console.log(
+ // 'initWallet wallets',
+ // wallets,
+ // info,
+ // )
+
+ if (!wallets.includes(id)) {
+ wallets.push(id)
+ }
+
+ let addrs = await batchAddressUsageGenerate(
+ wallet,
+ accountIndex,
+ addressIndex,
+ )
+
+ console.log('init wallet batchAddressUsageGenerate', addrs)
+
+ for (let a of addrs.addresses) {
+ store.addresses.setItem(
+ a.address,
+ {
+ updatedAt: Date.now(),
+ walletId: wallet.id,
+ accountIndex: a.accountIndex,
+ addressIndex: a.addressIndex,
+ usageIndex: a.usageIndex,
+ xkeyId: a.xkeyId,
+ }
+ )
+ }
+
+ let storeWallet = await store.wallets.setItem(
+ `${id}`,
+ {
+ id,
+ updatedAt: Date.now(),
+ accountIndex,
+ addressIndex: addrs?.finalAddressIndex || addressIndex,
+ keystore: keystore || await encryptKeystore(
+ encryptionPassword,
+ recoveryPhrase
+ ),
+ }
+ )
+
+ let storedAlias = await store.aliases.setItem(
+ `${alias}`,
+ await encryptData(
+ encryptionPassword,
+ storeWallet.keystore,
+ JSON.stringify({
+ wallets,
+ info,
+ })
+ )
+ )
+
+ // console.log(
+ // 'initWallet stored values',
+ // storeWallet,
+ // storedAlias,
+ // )
+
+ let contacts = '{}'
+
+ return {
+ wallets,
+ contacts,
+ }
+}
+
+export function filterPairedContacts(contact) {
+ let outLen = Object.keys(contact.outgoing || {}).length
+ return outLen > 0 // && !!contact.alias
+}
+
+export function filterUnpairedContacts(contact) {
+ return !filterPairedContacts(contact)
+}
+
+export function sortContactsByAlias(a, b) {
+ const aliasA = a.alias || a.info?.preferred_username?.toUpperCase() || 'zzz';
+ const aliasB = b.alias || b.info?.preferred_username?.toUpperCase() || 'zzz';
+
+ if (aliasA < aliasB) {
+ return -1;
+ }
+ if (aliasA > aliasB) {
+ return 1;
+ }
+ return 0;
+}
+
+export function sortContactsByName(a, b) {
+ const nameA = a.info?.name?.toUpperCase();
+ const nameB = b.info?.name?.toUpperCase();
+
+ if (nameA < nameB) {
+ return -1;
+ }
+ if (nameA > nameB) {
+ return 1;
+ }
+ return 0;
+}
+
+export function sortTransactionsByTime(a, b) {
+ const timeA = a.time;
+ const timeB = b.time;
+
+ if (timeA > timeB) {
+ return -1;
+ }
+ if (timeA < timeB) {
+ return 1;
+ }
+ return 0;
+}
+
+export function DashURLSearchParams(params) {
+ let searchParams
+ let qry = {}
+
+ Object.defineProperties(this, {
+ entries: {
+ enumerable: false,
+ configurable: false,
+ writable: false,
+ value: () => Object.entries(qry),
+ },
+ toString: {
+ enumerable: false,
+ configurable: false,
+ writable: false,
+ value: () => this.entries().map(p => p.join('=')).join('&'),
+ },
+ size: {
+ enumerable: false,
+ configurable: false,
+ get() { return this.entries().length },
+ },
+ });
+
+ if (typeof params === 'string' && params !== '') {
+ searchParams = params.split('&')
+ searchParams.forEach(q => {
+ let [prop,val] = q.split('=')
+ qry[prop] = val
+ })
+ }
+
+ if(Array.isArray(params) && params.length > 0) {
+ params.forEach(q => {
+ let [prop,val] = q
+ qry[prop] = val
+ })
+ }
+
+ // console.log('DashURLSearchParams', {
+ // params, searchParams, qry,
+ // qryStr: this.toString(),
+ // })
+}
+
+export function parseDashURI(uri) {
+ let result = {}
+ let parsedUri = [
+ ...uri.matchAll(DASH_URI_REGEX)
+ ]?.[0]?.groups || {}
+ // let searchParams = new URLSearchParams(parsedUri?.params || '')
+ let searchParams = new DashURLSearchParams(parsedUri?.params || '')
+
+ console.log(
+ 'parseDashURI',
+ parsedUri,
+ searchParams
+ )
+
+ if (parsedUri?.address) {
+ result.address = parsedUri?.address
+ }
+
+ if (searchParams?.size > 0) {
+ let claims = Object.fromEntries(
+ searchParams?.entries()
+ )
+
+ for (let c in claims) {
+ if (SUPPORTED_CLAIMS.includes(c)) {
+ result[c] = claims[c]
+ }
+ }
+ }
+
+ return result
+}
+
+export function parseAddressField(uri) {
+ /* @type {Record} */
+ let result = {}
+
+ if (uri.includes(':')) {
+ let [protocol] = uri.split(':')
+ if (protocol.includes('dash')) {
+ // @ts-ignore
+ result = parseDashURI(uri)
+ }
+ } else if (
+ 'xprv' === uri?.substring(0,4)
+ ) {
+ result.xprv = uri
+ } else if (
+ 'xpub' === uri?.substring(0,4)
+ ) {
+ result.xpub = uri
+ } else {
+ result.address = uri
+ }
+
+ return result
+}
+
+export function isEmpty(value) {
+ if (value === null) {
+ return true
+ }
+ // if (typeof value === 'boolean' && value === false) {
+ // return true
+ // }
+ if (typeof value === 'string' && value?.length === 0) {
+ return true
+ }
+ if (typeof value === 'object' && Object.keys(value)?.length === 0) {
+ return true
+ }
+ if (Array.isArray(value) && value.length === 0) {
+ return true
+ }
+ return false;
+}
+
+export function generateContactPairingURI(
+ state,
+ protocol = 'dash', // 'web+dash'
+ joiner = ':'
+) {
+ let addr = state.wallet?.address || ''
+ let claims = [
+ ["xpub", state.wallet?.xpub || ''],
+ ["sub", state.wallet?.xkeyId || ''],
+ ]
+
+ if (state.userInfo) {
+ let filteredInfo = Array.from(
+ Object.entries(state.userInfo)
+ ).filter(p => {
+ let [key, val] = p
+ if (
+ ![
+ // 'updated_at',
+ 'email_verified',
+ 'phone_number_verified',
+ ].includes(key) &&
+ !isEmpty(val)
+ ) {
+ return true
+ }
+ })
+
+ claims = [
+ ...claims,
+ ...filteredInfo,
+ ]
+ }
+
+ let scope = claims.map(p => p[0]).join(',')
+ let searchParams = new DashURLSearchParams([
+ ...claims,
+ ['scope', scope]
+ ])
+
+ console.log(
+ 'Generate Dash URI claims',
+ claims, scope, searchParams,
+ searchParams.size,
+ searchParams.entries(),
+ )
+
+ let res = `${protocol}${joiner}${addr}`
+
+ if (searchParams.size > 0) {
+ res += `?${searchParams.toString()}`
+ }
+
+ return res
+}
+
+export function generatePaymentRequestURI(
+ state,
+ protocol = 'dash',
+ joiner = ':'
+) {
+ let addr = state.wallet?.address || ''
+ let claims = []
+
+ if (state.userInfo) {
+ let filteredInfo = Array.from(
+ Object.entries(state.userInfo)
+ ).filter(p => {
+ let [key, val] = p
+ if (
+ ![
+ 'updated_at',
+ 'email_verified',
+ 'phone_number_verified',
+ ].includes(key) &&
+ !isEmpty(val)
+ ) {
+ return true
+ }
+ })
+
+ claims = [
+ ...filteredInfo,
+ ]
+ }
+
+ if (state.amount > 0) {
+ claims.push(
+ ["amount", state.amount],
+ )
+ }
+
+ if (state.label) {
+ claims.push(
+ ["label", state.label],
+ )
+ }
+
+ if (state.message) {
+ claims.push(
+ ["message", state.message],
+ )
+ }
+
+ let searchParams = new DashURLSearchParams([
+ ...claims,
+ ])
+
+ let res = `${protocol}${joiner}${addr}`
+
+ if (searchParams.size > 0) {
+ res += `?${searchParams.toString()}`
+ }
+
+ return res
+}
+
+export async function getRandomWords(len = 32) {
+ return await DashPhrase.generate(len)
+}
+
+export async function verifyPhrase(phrase) {
+ return await DashPhrase.verify(phrase).catch(_ => false)
+}
+
+export function isUniqueAlias(aliases, preferredAlias) {
+ return !aliases[preferredAlias]
+}
+
+export async function getUniqueAlias(aliases, preferredAlias) {
+ let uniqueAlias = preferredAlias
+ let notUnique = !isUniqueAlias(aliases, uniqueAlias)
+
+ if (notUnique) {
+ let aliasArr = uniqueAlias.split('_')
+ let randomWords = (await getRandomWords()).split(' ')
+
+ if (aliasArr.length > 1) {
+ let lastWord = aliasArr.pop()
+ let index = DashPhrase.base2048.indexOf(lastWord);
+
+ if (index < 0) {
+ aliasArr.push(lastWord)
+ } else {
+ aliasArr.push(randomWords[0])
+ }
+ } else {
+ aliasArr.push(randomWords[0])
+ }
+
+ uniqueAlias = aliasArr.join('_')
+
+ return await getUniqueAlias(aliases, uniqueAlias)
+ }
+
+ return uniqueAlias
+}
+
+export function getPartialHDPath(wallet) {
+ return [
+ wallet.accountIndex,
+ wallet.usageIndex,
+ wallet.addressIndex,
+ ].join('/')
+}
+
+export function getAddressIndexFromUsage(wallet, account, usageIdx) {
+ let usageIndex = usageIdx ?? wallet?.usageIndex ?? 0
+ let addressIndex = account.usage?.[usageIndex] ?? account.addressIndex ?? 0
+ let usage = account.usage ?? [
+ account.addressIndex ?? 0,
+ 0
+ ]
+
+ // console.log(
+ // 'getAddressIndexFromUsage',
+ // usageIndex,
+ // addressIndex,
+ // account,
+ // usage,
+ // )
+
+ return {
+ ...account,
+ usage,
+ usageIndex,
+ addressIndex,
+ }
+}
+
+export async function generateAddressIterator(
+ xkey,
+ xkeyId,
+ addressIndex,
+) {
+ let key = await xkey.deriveAddress(addressIndex);
+ let address = await DashHd.toAddr(key.publicKey);
+
+ return {
+ address,
+ addressIndex,
+ usageIndex: xkey.index,
+ xkeyId,
+ }
+}
+
+export async function generateAndStoreAddressIterator(
+ xkey,
+ xkeyId,
+ walletId,
+ accountIndex,
+ addressIndex,
+ usageIndex = DashHd.RECEIVE,
+) {
+ let { address } = await generateAddressIterator(
+ xkey,
+ xkeyId,
+ addressIndex,
+ )
+
+ // console.log(
+ // 'generateAddressIterator',
+ // {xkey, xkeyId, key, address, accountIndex, addressIndex},
+ // )
+
+ store.addresses.getItem(address)
+ .then(a => {
+ let $addr = a || {}
+ // console.log(
+ // 'generateAddressIterator store.addresses.getItem',
+ // {address, $addr},
+ // )
+
+ store.addresses.setItem(
+ address,
+ {
+ ...$addr,
+ updatedAt: Date.now(),
+ walletId,
+ xkeyId,
+ accountIndex,
+ addressIndex,
+ usageIndex,
+ },
+ )
+ })
+
+ return {
+ address,
+ addressIndex,
+ accountIndex,
+ usageIndex: xkey.index,
+ xkeyId,
+ }
+}
+
+export async function batchXkeyAddressGenerate(
+ wallet,
+ addressIndex = 0,
+ batchSize = 20,
+) {
+ let batchLimit = addressIndex + batchSize
+ let addresses = []
+
+ for (let addrIdx = addressIndex; addrIdx < batchLimit; addrIdx++) {
+ addresses.push(
+ await generateAddressIterator(
+ wallet.xkey,
+ wallet.xkeyId,
+ addrIdx,
+ )
+ )
+ }
+
+ return {
+ addresses,
+ finalAddressIndex: batchLimit,
+ }
+}
+
+export async function batchAddressGenerate(
+ wallet,
+ accountIndex = 0,
+ addressIndex = 0,
+ usageIndex = DashHd.RECEIVE,
+ batchSize = 20,
+) {
+ // let hdpath = `m/44'/5'/${accountIndex}'/${usageIndex}/${addressIndex}`,
+ let batchLimit = addressIndex + batchSize
+ let addresses = []
+
+ let account = await wallet.derivedWallet.deriveAccount(accountIndex);
+ let xkey = await account.deriveXKey(usageIndex);
+ let xkeyId = await DashHd.toId(xkey);
+
+ if (usageIndex !== DashHd.RECEIVE) {
+ let xkeyReceive = await account.deriveXKey(DashHd.RECEIVE);
+ xkeyId = await DashHd.toId(xkeyReceive);
+ }
+
+ for (let addrIdx = addressIndex; addrIdx < batchLimit; addrIdx++) {
+ addresses.push(
+ await generateAndStoreAddressIterator(
+ xkey,
+ xkeyId,
+ wallet.id,
+ accountIndex,
+ addrIdx,
+ usageIndex,
+ )
+ )
+ }
+
+ return {
+ addresses,
+ finalAddressIndex: batchLimit,
+ }
+}
+
+export async function batchAddressUsageGenerate(
+ wallet,
+ accountIndex = 0,
+ addressIndex = 0,
+ batchSize = 20,
+) {
+ // let hdpath = `m/44'/5'/${accountIndex}'/${usageIndex}/${addressIndex}`,
+ let batchLimit = addressIndex + batchSize
+ let addresses = []
+
+ let account = await wallet.derivedWallet.deriveAccount(accountIndex);
+ let xkeyReceive = await account.deriveXKey(DashHd.RECEIVE);
+ let xkeyChange = await account.deriveXKey(DashHd.CHANGE);
+ let xkeyId = await DashHd.toId(xkeyReceive);
+
+ console.log(
+ 'batchAddressUsageGenerate',
+ {batchLimit, account, xkeyReceive, xkeyChange},
+ )
+
+ for (let addrIdx = addressIndex; addrIdx < batchLimit; addrIdx++) {
+ addresses.push(
+ await generateAndStoreAddressIterator(
+ xkeyReceive,
+ xkeyId,
+ wallet.id,
+ accountIndex,
+ addrIdx,
+ DashHd.RECEIVE,
+ )
+ )
+ addresses.push(
+ await generateAndStoreAddressIterator(
+ xkeyChange,
+ xkeyId,
+ wallet.id,
+ accountIndex,
+ addrIdx,
+ DashHd.CHANGE,
+ )
+ )
+ }
+
+ return {
+ addresses,
+ finalAddressIndex: batchLimit,
+ }
+}
+
+export async function getTotalFunds(wallet) {
+ let funds = 0
+ let result = {}
+ let addrsLen = await store.addresses.length()
+
+ return await store.addresses.iterate((
+ value, key, iterationNumber
+ ) => {
+ if (value?.walletId === wallet?.id) {
+ result[key] = value
+ funds += value?.insight?.balance || 0
+ }
+
+ if (iterationNumber === addrsLen) {
+ return funds
+ }
+ })
+}
+
+export async function getAddrsWithFunds(wallet) {
+ let result = {}
+ let addrsLen = await store.addresses.length()
+
+ return await store.addresses.iterate((
+ value, key, iterationNumber
+ ) => {
+ if (
+ value?.walletId === wallet?.id &&
+ value?.insight?.balance > 0
+ ) {
+ result[key] = {
+ ...value,
+ address: key,
+ }
+ }
+
+ if (iterationNumber === addrsLen) {
+ return result
+ }
+ })
+}
+
+export async function batchGenAccts(
+ phrase,
+ accountIndex = 0,
+ batchSize = 5,
+) {
+ let $accts = await getStoredItems(store.accounts)
+ // let $acctsArr = Object.values($accts)
+ let accts = {}
+ let batch = batchSize + accountIndex
+
+ console.log(
+ 'BATCH GENERATED ACCOUNTS START',
+ {
+ $accts,
+ // $acctsArr,
+ accountIndex,
+ batch,
+ }
+ )
+
+ for (let i = accountIndex; i < batch; i++) {
+ let acctWallet = await deriveWalletData(
+ phrase,
+ i,
+ )
+
+ if (!$accts[acctWallet.xkeyId]) {
+ let newAccount = await store.accounts.setItem(
+ acctWallet.xkeyId,
+ {
+ createdAt: (new Date()).toISOString(),
+ updatedAt: (new Date()).toISOString(),
+ accountIndex: i,
+ usage: [0,0],
+ walletId: acctWallet.id,
+ xkeyId: acctWallet.xkeyId,
+ addressKeyId: acctWallet.addressKeyId,
+ address: acctWallet.address,
+ }
+ )
+
+ accts[`acct__${i}`] = [ acctWallet, newAccount ]
+ }
+
+ // accts[`acct__${i}`] = batchGenAcctAddrs(
+ // acctWallet,
+ // newAccount,
+ // )
+ }
+
+ // let allBatches = Promise.allSettled(Object.values(accts))
+
+ return accts
+}
+
+export async function batchGenAcctAddrs(
+ wallet,
+ account,
+ usageIndex = -1,
+ batchSize = 20,
+) {
+ // console.log('batchGenAcctAddrs account', account, usageIndex)
+
+ let filterQuery = {
+ accountIndex: account.accountIndex,
+ }
+
+ if (usageIndex >= 0) {
+ filterQuery.usageIndex = usageIndex
+ }
+
+ let acctAddrsLen = await getFilteredStoreLength(
+ store.addresses,
+ filterQuery,
+ )
+
+ // console.log('getFilteredStoreLength res', acctAddrsLen)
+
+ let addrUsageIdx = account.usage?.[usageIndex] || 0
+ let addrIdx = addrUsageIdx
+ let batSize = batchSize
+
+ if (acctAddrsLen === 0) {
+ addrIdx = 0
+ batSize = addrUsageIdx + batchSize
+ }
+
+ if (acctAddrsLen <= addrUsageIdx + (batchSize / 2)) {
+ if (usageIndex >= 0) {
+ return await batchAddressGenerate(
+ wallet,
+ account.accountIndex,
+ account.usage[usageIndex],
+ usageIndex,
+ batSize,
+ )
+ } else {
+ return await batchAddressUsageGenerate(
+ wallet,
+ account.accountIndex,
+ addrIdx,
+ batSize,
+ )
+ }
+ }
+
+ return null
+}
+
+export async function batchGenAcctsAddrs(
+ wallet,
+ usageIndex = -1,
+ batchSize = 20,
+) {
+ let $accts = await getStoredItems(store.accounts)
+ let $acctsArr = Object.values($accts)
+ let accts = {}
+
+ if ($acctsArr.length > 0) {
+ for (let $a of $acctsArr) {
+ accts[`bat__${$a.accountIndex}`] = await batchGenAcctAddrs(
+ wallet,
+ $a,
+ usageIndex,
+ batchSize,
+ )
+ }
+
+ // console.warn(
+ // 'BATCH GENERATED ACCOUNTS',
+ // accts,
+ // )
+ }
+
+ return accts
+}
+
+export async function getAccountWallet(wallet, phrase) {
+ let acctFromStore = await store.accounts.getItem(
+ wallet.xkeyId,
+ ) || {}
+ let acctFromStoreWallet = getAddressIndexFromUsage(
+ wallet,
+ acctFromStore,
+ )
+
+ if (acctFromStoreWallet?.addressIndex > 0) {
+ return {
+ wallet: await deriveWalletData(
+ phrase,
+ acctFromStoreWallet.accountIndex,
+ acctFromStoreWallet.addressIndex,
+ acctFromStoreWallet?.usageIndex ?? USAGE.RECEIVE,
+ ),
+ account: acctFromStore,
+ }
+ }
+
+ return {
+ wallet,
+ account: acctFromStore,
+ }
+}
+
+export async function forceInsightUpdateForAddress(addr) {
+ let currentAddr = await store.addresses.getItem(
+ addr
+ )
+ await store.addresses.setItem(
+ addr,
+ {
+ ...currentAddr,
+ insight: {
+ ...currentAddr.insight,
+ updatedAt: 0
+ }
+ }
+ )
+}
+
+export function sortAddrs(a, b) {
+ // Ascending Lexicographical on TxId (prev-hash) in-memory (not wire) byte order
+ if (a.accountIndex > b.accountIndex) {
+ return 1;
+ }
+ if (a.accountIndex < b.accountIndex) {
+ return -1;
+ }
+ // addressIndex
+ // Ascending Vout (Numerical)
+ let indexDiff = a.addressIndex - b.addressIndex;
+ return indexDiff;
+}
+
+export function getBalance(utxos) {
+ return utxos.reduce(function (total, utxo) {
+ return total + utxo.satoshis;
+ }, 0);
+}
+
+export function selectOptimalUtxos(utxos, output) {
+ let balance = getBalance(utxos);
+ let fees = DashTx.appraise({
+ //@ts-ignore
+ inputs: [{}],
+ //@ts-ignore
+ outputs: [{}],
+ });
+
+ let fullSats = output + fees.min;
+
+ if (balance < fullSats) {
+ return [];
+ }
+
+ // from largest to smallest
+ utxos.sort(function (a, b) {
+ return b.satoshis - a.satoshis;
+ });
+
+ // /** @type Array */
+ let included = [];
+ let total = 0;
+
+ // try to get just one
+ utxos.every(function (utxo) {
+ if (utxo.satoshis > fullSats) {
+ included[0] = utxo;
+ total = utxo.satoshis;
+ return true;
+ }
+ return false;
+ });
+ if (total) {
+ return included;
+ }
+
+ // try to use as few coins as possible
+ utxos.some(function (utxo, i) {
+ included.push(utxo);
+ total += utxo.satoshis;
+ if (total >= fullSats) {
+ return true;
+ }
+
+ // it quickly becomes astronomically unlikely to hit the one
+ // exact possibility that least to paying the absolute minimum,
+ // but remains about 75% likely to hit any of the mid value
+ // possibilities
+ if (i < 2) {
+ // 1 input 25% chance of minimum (needs ~2 tries)
+ // 2 inputs 6.25% chance of minimum (needs ~8 tries)
+ fullSats = fullSats + DashTx.MIN_INPUT_SIZE;
+ return false;
+ }
+ // but by 3 inputs... 1.56% chance of minimum (needs ~32 tries)
+ // by 10 inputs... 0.00953674316% chance (needs ~524288 tries)
+ fullSats = fullSats + DashTx.MIN_INPUT_SIZE + 1;
+ });
+ return included;
+}
+
+export function sortIncomingAndOutgoingTxs({
+ conAddr, tx, addr, dir, sentAmount = null, receivedAmount = null,
+ byAlias = {}, byAddress = {}, byTx = {},
+}) {
+ let alias = byTx?.[tx.txid]?.alias || conAddr.alias
+ byAlias[conAddr.alias] = {
+ ...(byAlias[conAddr.alias] || []),
+ [tx.txid]: {
+ addr,
+ dir,
+ sentAmount,
+ receivedAmount,
+ ...tx,
+ ...conAddr,
+ alias,
+ }
+ }
+ byAddress[addr] = [
+ ...(byAddress[addr] || []),
+ {
+ receivedAmount,
+ sentAmount,
+ dir,
+ ...tx,
+ ...conAddr,
+ alias,
+ }
+ ]
+ byTx[tx.txid] = {
+ receivedAmount,
+ sentAmount,
+ dir,
+ ...tx,
+ ...conAddr,
+ alias,
+ }
+
+ // console.log(
+ // 'sortIncomingAndOutgoingTxs',
+ // conAddr.alias, conAddr.xkeyId, tx,
+ // )
+
+ return {
+ byAlias,
+ byAddress,
+ byTx,
+ }
+}
+
+export async function getContactsByXkeyId(
+ appState,
+) {
+ let contactsXkeys = {}
+
+ for await (let c of appState.contacts) {
+ let og = Object.values(c.outgoing || {})?.[0]
+ let ic = Object.values(c.incoming || {})?.[0]
+
+ if (og) {
+ contactsXkeys[og.xkeyId] = {
+ ...c,
+ dir: 'outgoing',
+ }
+ }
+ if (ic) {
+ contactsXkeys[ic.xkeyId] = {
+ ...c,
+ dir: 'incoming',
+ }
+ }
+ }
+
+ return contactsXkeys
+}
+
+export async function getContactsFromAddrs(
+ appState,
+) {
+ let accts = await loadStoreObject(store.accounts)
+ let addrs = await loadStoreObject(store.addresses)
+ let contactAddrs = await getContactsByXkeyId(appState)
+ let contacts = {}
+
+ for await (let [ck,cv] of Object.entries(addrs)) {
+ let contact = contactAddrs[cv.xkeyId]
+ let acct = accts[cv.xkeyId]
+ if (contact) {
+ contacts[ck] = contact
+ } else if (acct) {
+ contacts[ck] = {
+ ...acct,
+ alias: null,
+ }
+ }
+ }
+
+ return contacts
+}
+
+export async function deriveContactAddrs(
+ appState, dir = 'outgoing',
+) {
+ let addrs = {}
+
+ for await (let c of appState.contacts) {
+ let og = Object.values(c[dir] || {})?.[0]
+ let xkey = og?.xpub || og?.xprv
+
+ if (xkey) {
+ let contactWallet = await deriveWalletData(
+ xkey,
+ )
+ let contactAddrs = await batchXkeyAddressGenerate(
+ contactWallet,
+ contactWallet.addressIndex,
+ )
+
+ contactAddrs.addresses.forEach(g => {
+ addrs[g.address] = {
+ alias: c.alias,
+ xkeyId: contactWallet.xkeyId,
+ dir,
+ }
+ })
+ }
+ }
+
+ // console.log('deriveContactAddrs', {
+ // asc: appState.contacts,
+ // addrs,
+ // })
+
+ return addrs
+}
+
+export function getTransactionsByContactAlias(appState) {
+ return async res => {
+ if (!res) {
+ return []
+ }
+
+ appState.contacts = res
+
+ return res
+ }
+}
diff --git a/src/utils/dash/network.js b/src/utils/dash/network.js
new file mode 100644
index 0000000..e259793
--- /dev/null
+++ b/src/utils/dash/network.js
@@ -0,0 +1,727 @@
+import {
+ DashWallet,
+ DashTx,
+ DashSight,
+ DashSocket,
+} from '../../imports.js'
+
+import {
+ walletFunds,
+} from '../../state/index.js'
+
+import {
+ DatabaseSetup,
+ loadStoreObject,
+} from '../db.js'
+
+import {
+ batchGenAccts,
+ batchGenAcctsAddrs,
+ deriveWalletData,
+ deriveContactAddrs,
+ getContactsFromAddrs,
+ selectOptimalUtxos,
+ sortAddrs,
+ sortIncomingAndOutgoingTxs,
+} from './local.js'
+
+let defaultSocketEvents = {
+ onClose: async (e) => console.log('onClose', e),
+ onError: async (e) => console.log('onError', e),
+ onMessage: async (e, data) => console.log('onMessage', e, data),
+}
+
+export const store = await DatabaseSetup()
+
+// @ts-ignore
+export const dashsight = DashSight.create({
+ baseUrl: 'https://insight.dash.org',
+ // baseUrl: 'https://dashsight.dashincubator.dev',
+});
+
+export async function initDashSocket(
+ events = {}
+) {
+ // @ts-ignore
+ let dashsocket = DashSocket.create({
+ dashsocketBaseUrl: 'https://insight.dash.org/socket.io',
+ cookieStore: null,
+ debug: true,
+ ...defaultSocketEvents,
+ ...events,
+ })
+
+ await dashsocket.init()
+ .catch((e) => console.log('dashsocket catch err', e));
+
+ return dashsocket
+}
+
+export async function updateAddrFunds(
+ wallet, insightRes,
+) {
+ let updatedAt = Date.now()
+ let { addrStr, ...res } = insightRes
+ let $addr = await store.addresses.getItem(addrStr) || {}
+ let {
+ walletId,
+ xkeyId,
+ } = $addr
+
+ if (walletId && walletId === wallet?.id) {
+ let storedWallet = await store.wallets.getItem(walletId) || {}
+ let storedAccount = await store.accounts.getItem(xkeyId) || {}
+
+ $addr.insight = {
+ ...res,
+ updatedAt,
+ }
+
+ store.addresses.setItem(
+ addrStr,
+ $addr,
+ )
+
+ if (storedAccount.usage[$addr.usageIndex] < $addr.addressIndex) {
+ storedAccount.usage[$addr.usageIndex] = $addr.addressIndex
+ store.accounts.setItem(
+ xkeyId,
+ storedAccount
+ )
+ }
+ if (storedWallet.accountIndex < $addr.accountIndex) {
+ store.wallets.setItem(
+ walletId,
+ {
+ ...storedWallet,
+ accountIndex: $addr.accountIndex,
+ }
+ )
+ let storeAcctLen = (await store.accounts.length())-1
+
+ console.log('updateAddrFunds', {
+ acctIdx: $addr.accountIndex,
+ storeAcctLen,
+ sameOrBigger: $addr.accountIndex >= storeAcctLen,
+ })
+
+ if ($addr.accountIndex >= storeAcctLen) {
+ batchGenAccts(wallet.recoveryPhrase, $addr.accountIndex)
+ .then(() => {
+ batchGenAcctsAddrs(wallet)
+ .then(accts => {
+ console.log('batchGenAcctsAddrs', { accts })
+
+ updateAllFunds(wallet)
+ .then(funds => {
+ console.log('updateAllFunds then funds', funds)
+ })
+ .catch(err => console.error('catch updateAllFunds', err, wallet))
+ })
+ })
+ }
+ }
+
+ return res
+ }
+
+ return { balance: 0 }
+}
+
+export async function updateAllFunds(wallet) {
+ let funds = 0
+ let addrKeys = await store.addresses.keys()
+
+ if (addrKeys.length === 0) {
+ walletFunds.balance = funds
+ return funds
+ }
+
+ console.log(
+ 'updateAllFunds getInstantBalances for',
+ {addrKeys},
+ addrKeys.length,
+ )
+
+ let balances = await dashsight.getInstantBalances(addrKeys)
+
+ if (balances.length >= 0) {
+ walletFunds.balance = funds
+ }
+
+ // add insight balances to address
+ for (const insightRes of balances) {
+ let { addrStr } = insightRes
+ let addrIdx = addrKeys.indexOf(addrStr)
+ if (addrIdx > -1) {
+ addrKeys.splice(addrIdx, 1)
+ }
+ funds += (await updateAddrFunds(wallet, insightRes))?.balance || 0
+ walletFunds.balance = funds
+ }
+
+ // remove insight balances from address
+ for (const addr of addrKeys) {
+ let { insight, ...$addr } = await store.addresses.getItem(addr) || {}
+
+ // walletFunds.balance = funds - (_insight?.balance || 0)
+
+ store.addresses.setItem(
+ addr,
+ $addr,
+ )
+ }
+
+ console.log('updateAllFunds funds', {balances, funds})
+
+ return funds
+}
+
+export async function deriveTxWallet(
+ fromWallet,
+ fundAddrs,
+) {
+ let cachedAddrs = {}
+ let privateKeys = {}
+ let coreUtxos
+ let tmpWallet
+
+ if (Array.isArray(fundAddrs) && fundAddrs.length > 0) {
+ fundAddrs.sort(sortAddrs)
+
+ for (let w of fundAddrs) {
+ tmpWallet = await deriveWalletData(
+ fromWallet.recoveryPhrase,
+ w.accountIndex,
+ w.addressIndex,
+ w.usageIndex,
+ )
+ privateKeys[tmpWallet.address] = tmpWallet.addressKey.privateKey
+ cachedAddrs[w.address] = {
+ checked_at: w.updatedAt,
+ hdpath: `m/44'/${DashWallet.COIN_TYPE}'/${w.accountIndex}'/${w.usageIndex}`,
+ index: w.addressIndex,
+ wallet: w.walletId, // maybe `selectedAlias`?
+ txs: [],
+ utxos: [],
+ }
+ }
+
+ coreUtxos = await dashsight.getMultiCoreUtxos(
+ Object.keys(privateKeys)
+ )
+ } else {
+ tmpWallet = await deriveWalletData(
+ fromWallet.recoveryPhrase,
+ fundAddrs.accountIndex,
+ fundAddrs.addressIndex,
+ fundAddrs.usageIndex,
+ )
+ privateKeys[tmpWallet.address] = tmpWallet.addressKey.privateKey
+ cachedAddrs[fundAddrs.address] = {
+ checked_at: fundAddrs.updatedAt,
+ hdpath: `m/44'/${DashWallet.COIN_TYPE}'/${fundAddrs.accountIndex}'/${fundAddrs.usageIndex}`,
+ index: fundAddrs.addressIndex,
+ wallet: fundAddrs.walletId, // maybe `selectedAlias`?
+ txs: [],
+ utxos: [],
+ }
+ coreUtxos = await dashsight.getCoreUtxos(
+ tmpWallet.address
+ )
+ }
+
+ return {
+ privateKeys,
+ cachedAddrs,
+ coreUtxos,
+ }
+}
+
+export async function createStandardTx(
+ fromWallet,
+ fundAddrs,
+ changeAddrs,
+ recipient,
+ amount,
+ fullTransfer = false,
+) {
+ const amountSats = DashTx.toSats(amount)
+
+ console.log('amount to send', {
+ amount,
+ amountSats,
+ })
+
+ let selection
+ let receiverOutput
+ let outputs = []
+ let {
+ privateKeys,
+ coreUtxos,
+ cachedAddrs,
+ } = await deriveTxWallet(fromWallet, fundAddrs)
+ let changeAddr = changeAddrs[0]
+
+ let recipientAddr = recipient?.address || recipient
+
+ // @ts-ignore
+ let dashwallet = await DashWallet.create({
+ safe: {
+ cache: {
+ addresses: cachedAddrs
+ }
+ },
+ store: {
+ save: data => console.log('dashwallet.store.save', {data})
+ },
+ dashsight,
+ })
+
+ receiverOutput = DashWallet._parseSendInfo(dashwallet, amountSats);
+
+ selection = dashwallet.useMatchingCoins({
+ output: receiverOutput,
+ utxos: coreUtxos,
+ breakChange: false,
+ })
+
+ console.log('coreUtxos', {
+ coreUtxos,
+ selection,
+ amount,
+ amountSats,
+ fullTransfer,
+ })
+
+ let stampVal = dashwallet.__STAMP__ * selection.output.stampsPerCoin
+ let receiverDenoms = receiverOutput?.denoms.slice(0)
+
+ for (let denom of selection.output.denoms) {
+ let address = '';
+ let matchingDenomIndex = receiverDenoms.indexOf(denom)
+ if (matchingDenomIndex >= 0) {
+ void receiverDenoms.splice(matchingDenomIndex, 1)
+ address = recipientAddr
+ } else {
+ address = changeAddr
+ }
+
+ let coreOutput = {
+ address,
+ // address: addrsInfo.addresses.pop(),
+ satoshis: denom + stampVal,
+ faceValue: denom,
+ stamps: selection.output.stampsPerCoin,
+ }
+
+ outputs.push(coreOutput)
+ }
+
+ let txInfo = {
+ inputs: selection.inputs,
+ outputs: outputs,
+ };
+
+ txInfo.outputs.sort(DashTx.sortOutputs)
+ txInfo.inputs.sort(DashTx.sortInputs)
+
+ let keys = txInfo.inputs.map(
+ utxo => privateKeys[utxo.address]
+ )
+
+ return [
+ txInfo,
+ keys,
+ changeAddr,
+ ]
+}
+
+export async function createOptimalTx(
+ fromWallet,
+ fundAddrs,
+ changeAddrs,
+ recipient,
+ amount,
+) {
+ const MIN_FEE = 191;
+ const DUST = 2000;
+ const amountSats = DashTx.toSats(amount)
+
+ console.log('amount to send', {
+ amount,
+ amountSats,
+ fundAddrs,
+ })
+
+ let changeAddr = changeAddrs[0]
+
+ let {
+ privateKeys,
+ coreUtxos,
+ } = await deriveTxWallet(fromWallet, fundAddrs)
+
+ let optimalUtxos = selectOptimalUtxos(
+ coreUtxos,
+ amountSats,
+ )
+
+ console.log('utxos', {
+ core: coreUtxos,
+ optimal: optimalUtxos,
+ })
+
+ let recipientAddr = recipient?.address || recipient
+
+ let payments = [
+ {
+ address: recipientAddr,
+ satoshis: amountSats,
+ },
+ ]
+
+ let spendableDuffs = optimalUtxos.reduce(function (total, utxo) {
+ return total + utxo.satoshis;
+ }, 0)
+ let spentDuffs = payments.reduce(function (total, output) {
+ return total + output.satoshis;
+ }, 0)
+ let unspentDuffs = spendableDuffs - spentDuffs
+
+ let txInfo = {
+ inputs: optimalUtxos,
+ outputs: payments,
+ }
+
+ let sizes = DashTx.appraise(txInfo)
+ let midFee = sizes.mid
+
+ if (unspentDuffs < MIN_FEE) {
+ throw new Error(
+ `overspend: inputs total '${spendableDuffs}', but outputs total '${spentDuffs}', which leaves no way to pay the fee of '${sizes.mid}'`,
+ )
+ }
+
+ txInfo.inputs.sort(DashTx.sortInputs)
+
+ let outputs = txInfo.outputs.slice(0)
+ let change
+
+ change = unspentDuffs - (midFee + DashTx.OUTPUT_SIZE)
+ if (change < DUST) {
+ change = 0
+ }
+ if (change) {
+ txInfo.outputs = outputs.slice(0);
+ txInfo.outputs.push({
+ address: changeAddr,
+ satoshis: change,
+ })
+ }
+
+ txInfo.outputs.sort(DashTx.sortOutputs)
+
+ let keys = optimalUtxos.map(
+ utxo => privateKeys[utxo.address]
+ )
+
+ return [
+ txInfo,
+ keys,
+ changeAddr,
+ ]
+}
+
+export async function createDraftTx(
+ fromWallet,
+ fundAddrs,
+ changeAddrs,
+ recipient,
+ amount,
+ fullTransfer = false,
+) {
+ const amountSats = DashTx.toSats(amount)
+
+ console.log('amount to send', {
+ amount,
+ amountSats,
+ })
+
+ let {
+ privateKeys,
+ coreUtxos,
+ cachedAddrs,
+ } = await deriveTxWallet(fromWallet, fundAddrs)
+ let changeAddr = changeAddrs[0]
+
+ let recipientAddr = recipient?.address || recipient
+
+ // @ts-ignore
+ let dashwallet = await DashWallet.create({
+ safe: {
+ cache: {
+ addresses: cachedAddrs
+ }
+ },
+ store: {
+ save: data => console.log('dashwallet.store.save', {data})
+ },
+ dashsight,
+ })
+
+ let utxos = null;
+ let inputs = null;
+ let output = {
+ address: recipientAddr,
+ satoshis: amountSats
+ };
+
+ if (coreUtxos) {
+ inputs = coreUtxos;
+ if (fullTransfer) {
+ output.satoshis = null;
+ }
+ } else {
+ utxos = coreUtxos;
+ }
+
+ let txDraft = await dashwallet.legacy.draftTx({
+ utxos,
+ inputs,
+ output,
+ feeSize: 'max',
+ })
+
+ if (txDraft.change) {
+ txDraft.change.address = changeAddr;
+ }
+
+ let keys = txDraft.inputs.map(
+ utxo => privateKeys[utxo.address]
+ )
+
+ let txSummary = await dashwallet.legacy.finalizeAndSignTx(txDraft, keys);
+
+ return {
+ ...txSummary,
+ changeAddr,
+ }
+}
+
+export async function createTx(
+ fromWallet,
+ fundAddrs,
+ changeAddrs,
+ recipient,
+ amount,
+ fullTransfer = false,
+ mode = null,
+) {
+ let tmpTx
+ let dashTx = DashTx.create({
+ // @ts-ignore
+ version: 3,
+ });
+
+ if (fullTransfer) {
+ let tx = await createDraftTx(
+ fromWallet,
+ fundAddrs,
+ changeAddrs,
+ recipient,
+ amount,
+ fullTransfer,
+ )
+
+ console.log('fullTransfer tx', tx);
+
+ return {
+ tx,
+ changeAddr: tx.changeAddr,
+ fee: tx.fee,
+ // fee: inFee - outFee,
+ }
+ } else if (mode === 'cash') {
+ // Denominated TX
+ tmpTx = await createStandardTx(
+ fromWallet,
+ fundAddrs,
+ changeAddrs,
+ recipient,
+ amount,
+ fullTransfer,
+ )
+ } else {
+ // Non-Denominated TX
+ tmpTx = await createOptimalTx(
+ fromWallet,
+ fundAddrs,
+ changeAddrs,
+ recipient,
+ amount,
+ )
+ }
+
+ let [txInfo, keys, changeAddr] = tmpTx
+
+ let inFee = txInfo.inputs.reduce((acc, cur) => acc + cur.satoshis, 0)
+ let outFee = txInfo.outputs.reduce((acc, cur) => acc + cur.satoshis, 0)
+
+ console.log('txInfo', {
+ txInfo,
+ calcFee: {
+ in: inFee,
+ out: outFee,
+ fee: inFee - outFee,
+ },
+ });
+
+ // @ts-ignore
+ let tx = await dashTx.hashAndSignAll(txInfo, keys);
+
+ console.log('tx', tx);
+
+ return {
+ tx,
+ changeAddr,
+ fee: inFee - outFee,
+ }
+}
+
+export async function sendTx(
+ tx,
+) {
+ let txHex = tx.transaction;
+
+ console.log('txHex', [txHex]);
+
+ let result = await dashsight.instantSend(txHex);
+
+ console.log('instantSend result', result);
+
+ return result
+}
+
+
+
+export async function getAddrsTransactions({
+ appState, addrs, contactAddrs = {},
+ txs = [],
+}) {
+ let storeAddrs = await loadStoreObject(store.addresses)
+ if (txs.length === 0) {
+ txs = await dashsight.getAllTxs(addrs)
+ }
+ let byAddress = {}
+ let byAlias = {}
+ let byTx = {}
+
+ // console.log('getAddrsTransactions', {
+ // txs, addrs, contactAddrs, appT: appState.transactions
+ // })
+
+ for await (let tx of txs) {
+ let dir = 'received'
+ let conAddr
+ let sentAmount = 0
+ let receivedAmount = 0
+
+ for await (let vin of tx.vin) {
+ let addr = vin.addr
+ conAddr = contactAddrs[addr]
+
+ if(storeAddrs[addr]) {
+ dir = 'sent'
+ sentAmount += Number(vin.value)
+ }
+
+ if (conAddr) {
+ sortIncomingAndOutgoingTxs({
+ tx, addr, conAddr, dir, sentAmount,
+ byAlias, byAddress, byTx,
+ })
+ }
+ }
+
+ for await (let vout of tx.vout) {
+ if (vout?.scriptPubKey?.addresses) {
+ for await (let addr of vout.scriptPubKey.addresses) {
+ // let addr = vout.scriptPubKey.addresses[0]
+ conAddr = contactAddrs[addr]
+
+ if(storeAddrs[addr]) {
+ receivedAmount += Number(vout.value)
+ } else {
+ // sentAmount -= Number(vout.value)
+ }
+
+ if (conAddr) {
+ sortIncomingAndOutgoingTxs({
+ tx, addr, conAddr, dir, receivedAmount,
+ byAlias, byAddress, byTx,
+ })
+ }
+ }
+ }
+ }
+
+ byTx[tx.txid] = {
+ ...byTx[tx.txid],
+ receivedAmount,
+ sentAmount,
+ }
+
+ if (!appState.transactions[tx.txid]?.vin) {
+ store.transactions.setItem(
+ tx.txid,
+ {
+ ...(appState.transactions[tx.txid] || {}),
+ ...tx,
+ dir,
+ sentAmount,
+ receivedAmount,
+ },
+ )
+ }
+ }
+
+ // console.log('getAddrsTransactions by Tx', byTx)
+ // console.log('getAddrsTransactions by Alias', byAlias)
+ // console.log('getAddrsTransactions by Address', byAddress)
+
+ return {
+ byAddress,
+ byAlias,
+ byTx,
+ }
+}
+
+export async function getTxs(appState, transactions = []) {
+ let contactAddrs = await getContactsFromAddrs(appState)
+ let contactOutAddrs = await deriveContactAddrs(appState)
+
+ contactAddrs = {
+ ...contactAddrs,
+ ...contactOutAddrs,
+ }
+
+ let addrs = Object.keys(contactAddrs)
+
+ if (addrs.length === 0) {
+ return
+ }
+
+ // let TxStore = await store.transactions.keys()
+ // let txs = await dashsight.getAllTxs(addrs)
+
+ let txs = await getAddrsTransactions({
+ appState, addrs, contactAddrs,
+ txs: transactions,
+ })
+
+ // console.log('getTxs', {
+ // txs, contactAddrs, contactOutAddrs, addrs
+ // })
+
+ return txs
+}
diff --git a/src/helpers/db.js b/src/utils/db.js
similarity index 52%
rename from src/helpers/db.js
rename to src/utils/db.js
index 3314635..03b7222 100644
--- a/src/helpers/db.js
+++ b/src/utils/db.js
@@ -20,47 +20,26 @@ export const localForageBaseCfg = {
version: 1.0,
}
-export async function DatabaseSetup() {
- var wallets = localforage.createInstance({
- ...localForageBaseCfg,
- storeName: 'wallets',
- });
- var aliases = localforage.createInstance({
- ...localForageBaseCfg,
- storeName: 'aliases',
- });
- var contacts = localforage.createInstance({
- ...localForageBaseCfg,
- storeName: 'contacts',
- });
- var accounts = localforage.createInstance({
- ...localForageBaseCfg,
- storeName: 'accounts',
- });
- // accounts.ready(r => {
- // console.log('accounts', r, accounts, accounts._dbInfo.db)
- // let tx = accounts._dbInfo.db.transaction("accounts", "readwrite");
- // let accts = tx.objectStore("accounts");
- // let acctWalletIndex = accts.createIndex('wallet_id', 'walletId');
- // console.log('acctWalletIndex', tx, accts, acctWalletIndex)
- // })
- var addresses = localforage.createInstance({
- ...localForageBaseCfg,
- storeName: 'addresses',
- });
- var transactions = localforage.createInstance({
+const loadedStores = {}
+
+export function loadInstance(name) {
+ loadedStores[name] = loadedStores[name] || localforage.createInstance({
...localForageBaseCfg,
- storeName: 'transactions',
+ storeName: [name],
});
- return {
- wallets,
- addresses,
- contacts,
- accounts,
- aliases,
- transactions,
- }
+ return loadedStores[name]
+}
+
+export async function DatabaseSetup() {
+ loadInstance('wallets');
+ loadInstance('aliases');
+ loadInstance('contacts');
+ loadInstance('accounts');
+ loadInstance('addresses');
+ loadInstance('transactions');
+
+ return loadedStores
}
// https://gist.github.com/loilo/ed43739361ec718129a15ae5d531095b#file-idb-backup-and-restore-mjs
@@ -197,3 +176,167 @@ export function exportWalletData(name, version) {
.catch(console.error)
}
}
+
+export async function getStoreData(
+ store,
+ callback,
+ iterableCallback = res => async (v, k, i) => res.push(v)
+) {
+ let result = []
+
+ return await store.keys().then(async function(keys) {
+ for (let k of keys) {
+ let v = await store.getItem(k)
+ await iterableCallback(result)(v, k)
+ }
+
+ callback?.(result)
+
+ return result
+ }).catch(function(err) {
+ console.error('getStoreData', err)
+ return null
+ });
+}
+
+export async function loadStore(
+ store,
+ callback,
+ iterableCallback = res => v => res.push(v)
+) {
+ let result = []
+
+ return await store.iterate(iterableCallback(result))
+ .then(() => callback?.(result))
+ .catch(err => {
+ console.error('loadStore', err)
+ return null
+ });
+}
+
+export async function loadStoreObject(store, callback) {
+ let result = {}
+
+ return await store.iterate((v, k, i) => {
+ result[k] = v
+ })
+ .then(() => callback?.(result))
+ .then(() => result)
+ .catch(err => {
+ console.error('loadStoreObject', err)
+ return null
+ });
+}
+
+export async function getFilteredStoreLength(targStore, query = {}) {
+ let resLength = 0
+ let storeLen = await targStore.length()
+ let qs = Object.entries(query)
+
+ // console.log('getFilteredStoreLength qs', {
+ // storeName: targStore?._config?.storeName,
+ // storeLen,
+ // qs,
+ // })
+
+ if (storeLen === 0) {
+ return 0
+ }
+
+ return await targStore.iterate((
+ value, key, iterationNumber
+ ) => {
+ let res = true
+
+ // console.log('getFilteredStoreLength qs before each', key, res)
+
+ qs.forEach(([k,v]) => {
+ // console.log('getFilteredStoreLength qs each', k, v, value[k])
+ if (k === 'key' && key !== v || value[k] !== v) {
+ res = undefined
+ }
+ })
+
+ // console.log('getFilteredStoreLength qs after each', key, res)
+
+ if (res) {
+ resLength += 1
+ }
+
+ if (iterationNumber === storeLen) {
+ return resLength
+ }
+ })
+}
+
+export async function findInStore(targStore, query = {}) {
+ let result = {}
+ let storeLen = await targStore.length()
+ let qs = Object.entries(query)
+ // console.log('findInStore qs', qs)
+
+ return await targStore.iterate((
+ value, key, iterationNumber
+ ) => {
+ let res = value
+
+ // console.log('findInStore qs before each', key, res)
+
+ qs.forEach(([k,v]) => {
+ // console.log('findInStore qs each', k, v, value[k])
+ if (k === 'key' && key !== v || value[k] !== v) {
+ res = undefined
+ }
+ })
+
+ // console.log('findInStore qs after each', key, res)
+
+ if (res) {
+ result[key] = res
+ }
+
+ if (iterationNumber === storeLen) {
+ return result
+ }
+ })
+}
+
+export async function findOneInStore(targStore, query = {}) {
+ let storeLen = await targStore.length()
+ let qs = Object.entries(query)
+
+ return await targStore.iterate((
+ value, key, iterationNumber
+ ) => {
+ let res = value
+
+ qs.forEach(([k,v]) => {
+ if (k === 'key' && key !== v || value[k] !== v) {
+ res = undefined
+ }
+ })
+
+ if (res) {
+ return res
+ }
+
+ if (iterationNumber === storeLen) {
+ return undefined
+ }
+ })
+}
+
+export async function getStoredItems(targStore) {
+ let result = {}
+ let storeLen = await targStore.length()
+
+ return await targStore.iterate((
+ value, key, iterationNumber
+ ) => {
+ result[key] = value
+
+ if (iterationNumber === storeLen) {
+ return result
+ }
+ })
+}
diff --git a/src/utils/generic.js b/src/utils/generic.js
new file mode 100644
index 0000000..68531db
--- /dev/null
+++ b/src/utils/generic.js
@@ -0,0 +1,539 @@
+import {
+ TIMEAGO_LOCALE_EN, MOMENTS, NEVER,
+ SECONDS, MINUTE, HOUR, DAY, WEEK, MONTH, YEAR,
+} from './constants.js'
+
+let eventHandlers = []
+
+/**
+ * Code Highlighting for String Literals.
+ *
+ * {@link https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Template_literals#raw_strings MDN Reference}
+ *
+ * @example
+ * import { lit as html, lit as css } from './utils.js'
+ * let h = html`${example}
`
+ * let c = css`div > span { color: #bad; }`
+ *
+ * // falsey values now default to empty string
+ * let falsey = html`${doesNotExist && html`
`}
`
+
+ * // falsey === '
'
+ * // instead of
+ * // falsey === 'undefined
'
+ *
+ * @param {TemplateStringsArray} s
+ * @param {...any} v
+ *
+ * @returns {string}
+ */
+export const lit = (s, ...v) => String.raw({ raw: s }, ...(v.map(x => x || '')))
+
+export function isEmpty(value) {
+ if (value === null) {
+ return true
+ }
+ // if (typeof value === 'boolean' && value === false) {
+ // return true
+ // }
+ if (typeof value === 'string' && value?.length === 0) {
+ return true
+ }
+ if (typeof value === 'object' && Object.keys(value)?.length === 0) {
+ return true
+ }
+ if (Array.isArray(value) && value.length === 0) {
+ return true
+ }
+ return false;
+}
+
+/**
+ * promise debounce changes
+ *
+ * https://www.freecodecamp.org/news/javascript-debounce-example/
+ *
+ * @example
+ * const change = debounce((a) => console.log('Saving data', a));
+ * change('b');change('c');change('d');
+ * 'Saving data d'
+ *
+ * @param {(...args) => void} callback
+ * @param {number} [delay]
+*
+* @returns {Promise}
+*/
+export async function debouncePromise(callback, delay = 300) {
+ let timer
+
+ return await new Promise(resolve => async (...args) => {
+ clearTimeout(timer)
+
+ timer = setTimeout(() => {
+ resolve(callback.apply(this, args))
+ }, delay)
+ })
+}
+
+/**
+ * debounce changes
+ *
+ * https://www.freecodecamp.org/news/javascript-debounce-example/
+ *
+ * @example
+ * const change = debounce((a) => console.log('Saving data', a));
+ * change('b');change('c');change('d');
+ * 'Saving data d'
+ *
+ * @param {(...args) => void} callback
+ * @param {number} [delay]
+*
+* @returns {(...args) => void}
+*/
+export function debounce(callback, delay = 300) {
+ let timer
+
+ return (...args) => {
+ clearTimeout(timer)
+
+ timer = setTimeout(() => {
+ return callback.apply(this, args)
+ }, delay)
+
+ return timer
+ }
+}
+
+/**
+ * debounce that immediately triggers and black holes any extra
+ * executions within the time delay
+ *
+ * https://www.freecodecamp.org/news/javascript-debounce-example/
+ *
+ * @example
+ * const dry = nobounce((a) => console.log('Saving data', a));
+ * dry('b');dry('c');dry('d');
+ * 'Saving data b'
+ *
+ * @param {(...args) => void} callback
+ * @param {number} [delay]
+*
+* @returns {(...args) => void}
+*/
+export function nobounce(callback, delay = 300) {
+ let timer
+
+ return (...args) => {
+ if (!timer) {
+ callback.apply(this, args)
+ }
+
+ clearTimeout(timer)
+
+ timer = setTimeout(() => {
+ timer = undefined
+ }, delay)
+ }
+}
+
+
+/**
+ * @example
+ * await forIt(500);
+ * nowDoThis()
+ *
+ * @param {number} [delay]
+*
+* @returns {Promise}
+*/
+export function forIt(delay) {
+ return new Promise(resolve => setTimeout(resolve, delay));
+}
+
+export function timeago(ms, locale = TIMEAGO_LOCALE_EN) {
+ var ago = Math.floor(ms / 1000);
+ var part = 0;
+
+ if (ago < MOMENTS) { return locale.moment; }
+ if (ago < SECONDS) { return locale.moments; }
+ if (ago < MINUTE) { return locale.seconds.replace(/%\w?/, `${ago}`); }
+
+ if (ago < (2 * MINUTE)) { return locale.minute; }
+ if (ago < HOUR) {
+ while (ago >= MINUTE) { ago -= MINUTE; part += 1; }
+ return locale.minutes.replace(/%\w?/, `${part}`);
+ }
+
+ if (ago < (2 * HOUR)) { return locale.hour; }
+ if (ago < DAY) {
+ while (ago >= HOUR) { ago -= HOUR; part += 1; }
+ return locale.hours.replace(/%\w?/, `${part}`);
+ }
+
+ if (ago < (2 * DAY)) { return locale.day; }
+ if (ago < WEEK) {
+ while (ago >= DAY) { ago -= DAY; part += 1; }
+ return locale.days.replace(/%\w?/, `${part}`);
+ }
+
+ if (ago < (2 * WEEK)) { return locale.week; }
+ if (ago < MONTH) {
+ while (ago >= WEEK) { ago -= WEEK; part += 1; }
+ return locale.weeks.replace(/%\w?/, `${part}`);
+ }
+
+ if (ago < (2 * MONTH)) { return locale.month; }
+ if (ago < YEAR) { // 45 years, approximately the epoch
+ while (ago >= MONTH) { ago -= MONTH; part += 1; }
+ return locale.months.replace(/%\w?/, `${part}`);
+ }
+
+ if (ago < NEVER) {
+ return locale.years;
+ }
+
+ return locale.never;
+}
+
+// https://stackoverflow.com/a/66494926
+export function getBackgroundColor(stringInput) {
+ let stringUniqueHash = [...stringInput].reduce((acc, char) => {
+ return char.charCodeAt(0) + ((acc << 5) - acc);
+ }, 0);
+ return `hsl(${stringUniqueHash % 360}, 100%, 67%)`;
+}
+
+
+
+export async function sha256(str) {
+ const buf = await crypto.subtle.digest(
+ "SHA-256", new TextEncoder().encode(str)
+ );
+ return Array.prototype.map.call(
+ new Uint8Array(buf),
+ x => (('00' + x.toString(16)).slice(-2))
+ ).join('');
+}
+
+
+
+export async function getAvatarUrl(
+ email,
+ size = 48,
+ rating = 'pg',
+ srv = 'gravatar',
+) {
+ let emailSHA = await sha256(email || '')
+
+ if (srv === 'gravatar') {
+ return `https://gravatar.com/avatar/${
+ emailSHA
+ }?s=${size}&r=${rating}&d=retro`
+ }
+ if (srv === 'libravatar') {
+ return `https://seccdn.libravatar.org/avatar/${
+ emailSHA
+ }?s=${size}&r=${rating}&d=retro`
+ }
+
+ return ''
+}
+
+export async function getAvatar(c) {
+ let initials = c?.info?.name?.
+ split(' ').map(n => n[0]).slice(0,3).join('') || ''
+ let nameOrAlias = c?.info?.name || c?.alias || c?.info?.preferred_username
+
+ if (!initials) {
+ initials = (c?.alias || c?.info?.preferred_username)?.[0] || ''
+ }
+
+ let avStr = `${initials}
`
+}
+
+export function fileIsSubType(file, type) {
+ const fileType = file?.type?.split('/')?.[1]
+
+ if (!fileType) {
+ return false
+ }
+
+ return fileType === type
+}
+
+// fileInTypes({type:'application/json'}, ['image/png'])
+export function fileInMIMETypes(file, types = []) {
+ const fileType = file?.type
+
+ if (!fileType) {
+ return false
+ }
+
+ return types.includes(fileType)
+}
+
+export function fileTypeInTypes(file, types = []) {
+ const fileType = file?.type?.split('/')?.[0]
+
+ if (!fileType) {
+ return false
+ }
+
+ return types.includes(fileType)
+}
+
+export function fileTypeInSubtype(file, subtypes = []) {
+ const fileSubType = file?.type?.split('/')?.[1]
+
+ if (!fileSubType) {
+ return false
+ }
+
+ return subtypes.includes(fileSubType)
+}
+
+export function readFile(file, options) {
+ let opts = {
+ expectedFileType: 'json',
+ denyFileTypes: ['audio','video','image','font','model'],
+ denyFileSubTypes: ['msword','xml'],
+ callback: () => {},
+ errorCallback: () => {},
+ ...options,
+ }
+ let reader = new FileReader();
+ let result
+
+ reader.addEventListener('load', () => {
+ if (
+ fileTypeInTypes(
+ file,
+ opts.denyFileTypes,
+ ) || fileTypeInSubtype(
+ file,
+ opts.denyFileSubTypes,
+ )
+ ) {
+ return opts.errorCallback?.({
+ err: `Wrong file type: ${file.type}. Expected: ${opts.expectedFileType}.`,
+ file,
+ })
+ }
+
+ try {
+ // @ts-ignore
+ result = JSON.parse(reader?.result || '{}');
+
+ // console.log('parse loaded json', result);
+
+ opts.callback?.(result, file)
+
+ // state[key] = result
+ } catch(err) {
+ opts.errorCallback?.({
+ err,
+ file,
+ })
+
+ throw new Error(`failed to parse JSON data from ${file.name}`)
+ }
+ });
+
+ reader.readAsText(file);
+}
+
+
+
+
+
+
+
+export function toSlug(...slugs) {
+ return slugs.join('_').toLowerCase()
+ .replaceAll(/[^a-zA-Z _]/g, '')
+ .replaceAll(' ', '_')
+}
+
+
+
+
+
+export function addListener(
+ node,
+ event,
+ handler,
+ capture = false,
+ handlers = this?.eventHandlers || eventHandlers,
+) {
+ console.log('addListener', this, { node, event, handler, capture })
+ handlers.push({ node, event, handler, capture })
+ node.addEventListener(event, handler, capture)
+}
+
+export function addListeners(
+ resolve,
+ reject,
+) {
+ if (resolve && reject) {
+ addListener(
+ this.elements.dialog,
+ 'close',
+ this.events.close(resolve, reject),
+ )
+
+ addListener(
+ this.elements.dialog,
+ 'click',
+ this.events.click,
+ )
+ }
+
+ addListener(
+ this.elements.form,
+ 'blur',
+ this.events.blur,
+ )
+ addListener(
+ this.elements.form,
+ 'focusout',
+ this.events.focusout,
+ )
+ addListener(
+ this.elements.form,
+ 'focusin',
+ this.events.focusin,
+ )
+ addListener(
+ this.elements.form,
+ 'change',
+ this.events.change,
+ )
+ // if (updrop) {
+ addListener(
+ this.elements.form,
+ 'drop',
+ this.events.drop,
+ )
+ addListener(
+ this.elements.form,
+ 'dragover',
+ this.events.dragover,
+ )
+ addListener(
+ this.elements.form,
+ 'dragend',
+ this.events.dragend,
+ )
+ addListener(
+ this.elements.form,
+ 'dragleave',
+ this.events.dragleave,
+ )
+ // }
+ addListener(
+ this.elements.form,
+ 'input',
+ this.events.input,
+ )
+ addListener(
+ this.elements.form,
+ 'reset',
+ this.events.reset,
+ )
+ addListener(
+ this.elements.form,
+ 'submit',
+ this.events.submit,
+ )
+}
+
+export function removeAllListeners(
+ targets = [
+ this?.elements.dialog,
+ this?.elements.form,
+ ],
+ handlers = this?.eventHandlers || eventHandlers,
+) {
+ if (this.elements.updrop) {
+ targets.push(this.elements.updrop)
+ }
+ handlers = handlers
+ .filter(({ node, event, handler, capture }) => {
+ if (targets.includes(node)) {
+ node.removeEventListener(event, handler, capture)
+ return false
+ }
+ return true
+ })
+}
+
+
+export function formDataEntries(event) {
+ let fd = new FormData(
+ event.target,
+ event.submitter
+ )
+
+ return Object.fromEntries(fd.entries())
+}
+
+export function copyToClipboard(target) {
+ target.select();
+ document.execCommand("copy");
+}
+
+export function setClipboard(event) {
+ event.preventDefault()
+ let el = event.target?.previousElementSibling
+ let val = el.textContent?.trim()
+ if (el.nodeName === 'INPUT') {
+ val = el.value?.trim()
+ }
+ const type = "text/plain";
+ const blob = new Blob([val], { type });
+
+ if (
+ "clipboard" in navigator &&
+ typeof navigator.clipboard.write === "function"
+ ) {
+ const data = [new ClipboardItem({ [type]: blob })];
+
+ navigator.clipboard.write(data).then(
+ cv => {
+ console.log('setClipboard', cv)
+ },
+ ce => {
+ console.error('[fail] setClipboard', ce)
+ }
+ );
+ } else {
+ copyToClipboard(el)
+ }
+}
+
+export function openBlobSVG(target) {
+ const svgStr = new XMLSerializer().serializeToString(target);
+ const svgBlob = new Blob([svgStr], { type: "image/svg+xml" });
+ const url = URL.createObjectURL(svgBlob);
+ const win = open(url);
+ win.onload = (evt) => URL.revokeObjectURL(url);
+}
diff --git a/src/helpers/qr.js b/src/utils/qr.js
similarity index 70%
rename from src/helpers/qr.js
rename to src/utils/qr.js
index af57cd3..771aaf9 100644
--- a/src/helpers/qr.js
+++ b/src/utils/qr.js
@@ -1,7 +1,5 @@
"use strict";
-import { toDash } from './utils.js'
-
/**
* @typedef QrOpts
* @property {String} [background]
@@ -31,7 +29,7 @@ export function create(data, opts) {
background: opts?.background || "#fff",
ecl: opts?.ecl || "M",
});
-};
+}
/**
* @param {String} data
@@ -41,18 +39,4 @@ export function qrSvg (data, opts) {
// console.log('qrSvg', data)
let qrcode = create(data, opts);
return qrcode.svg();
-};
-
-/**
- * @param {String} addr - Base58Check pubKeyHash address
- * @param {Number} duffs - 1/100000000 of a DASH
- */
-export function showQr(addr, duffs = 0) {
- let dashAmount = toDash(duffs);
- let dashUri = `dash://${addr}`;
- if (duffs) {
- dashUri += `?amount=${dashAmount}`;
- }
-
- return qrSvg(dashUri, { indent: 4, size: "mini" });
}
diff --git a/src/utils/retort.js b/src/utils/retort.js
new file mode 100644
index 0000000..c4b5703
--- /dev/null
+++ b/src/utils/retort.js
@@ -0,0 +1,285 @@
+let currentSignal;
+let globalDerivedValue;
+
+/**
+ * Creates a reactive signal
+ *
+ * Inspired By
+ * {@link https://gist.github.com/developit/a0430c500f5559b715c2dddf9c40948d Valoo} &
+ * {@link https://dev.to/ratiu5/implementing-signals-from-scratch-3e4c Signals from Scratch}
+ *
+ * @example
+ * let count = createSignal(0)
+ * console.log(count.value) // 0
+ * count.value = 2
+ * console.log(count.value) // 2
+ *
+ * let off = count.on((value) => {
+ * document.querySelector("body").innerHTML = value;
+ * });
+ *
+ * off();
+ *
+ * @param {Object} initialValue inital value
+*/
+export function createSignal(initialValue) {
+ let _value = initialValue;
+ let _last = _value;
+ let subs = [];
+
+ function pub() {
+ for (let s of subs) {
+ s && s(_value, _last);
+ }
+ }
+
+ function unsub(fn) {
+ for (let i in subs) {
+ if (subs[i] === fn) {
+ subs[i] = 0;
+ // break;
+ }
+ }
+ }
+
+ function on(s) {
+ const i = subs.push(s)-1;
+ return () => { subs[i] = 0; };
+ }
+
+ function once(s) {
+ const i = subs.length
+
+ subs.push((_value, _last) => {
+ s && s(_value, _last);
+ subs[i] = 0;
+ });
+ }
+
+ return {
+ get value() {
+ if (currentSignal) {
+ on(currentSignal)
+ }
+ return _value;
+ },
+ set value(v) {
+ _last = _value
+ _value = v;
+ pub();
+ },
+ on,
+ once,
+ unsub,
+ }
+}
+
+/**
+ * Use a reactive signal in hook fashion
+ *
+ * @example
+ * let [count, setCount, on] = useSignal(0)
+ * console.log(count()) // 0
+ * setCount(2)
+ * console.log(count()) // 2
+ *
+ * let off = on(value => {
+ * document.querySelector("body").innerHTML = value;
+ * });
+ *
+ * off()
+ *
+ * @param {Object} initialValue inital value
+*/
+export function useSignal(initialValue) {
+ let _value = initialValue;
+ let _last = _value;
+ let subs = [];
+
+ function pub() {
+ for (let s of subs) {
+ s && s(_value, _last);
+ }
+ }
+
+ function unsub(fn) {
+ for (let i in subs) {
+ if (subs[i] === fn) {
+ subs[i] = 0;
+ // break;
+ }
+ }
+ }
+
+ function getValue(v) {
+ if (
+ currentSignal //&&
+ // currentSignal !== globalDerivedValue
+ ) {
+ on(currentSignal)
+ }
+ return _value;
+ }
+
+ function setValue(v) {
+ _last = _value
+ _value = v;
+ pub();
+ }
+
+ function on(s) {
+ const i = subs.push(s)-1;
+ return () => { subs[i] = 0; };
+ }
+
+ function once(s) {
+ const i = subs.length
+
+ subs.push((_value, _last) => {
+ s && s(_value, _last);
+ subs[i] = 0;
+ });
+ }
+
+ return [
+ // _value,
+ getValue,
+ setValue,
+ on,
+ once,
+ unsub,
+ ]
+}
+
+/**
+ * {@link https://youtu.be/t18Kzj9S8-M?t=351 Understanding Signals}
+ *
+ * {@link https://youtu.be/1TSLEzNzGQM Learn Why JavaScript Frameworks Love Signals By Implementing Them}
+ *
+ * @example
+ * const [count, setCount] = useSignal(10)
+ * effect(() => console.log(count()))
+ * setCount(25)
+ *
+ * let letter = createSignal('a')
+ * effect(() => console.log(letter.value))
+ * letter.value = 'b'
+ *
+ * @param {Function} fn
+ */
+export function effect(fn) {
+ currentSignal = fn;
+
+ fn();
+
+ currentSignal = null;
+
+ return fn
+}
+
+/**
+ * {@link https://youtu.be/1TSLEzNzGQM Learn Why JavaScript Frameworks Love Signals By Implementing Them}
+ *
+ * @example
+ * let count = createSignal(10)
+ * let double = derived(() => count.value * 2)
+ *
+ * effect(
+ * () => console.log(
+ * count.value,
+ * double.value,
+ * )
+ * )
+ *
+ * count.value = 25
+ *
+ * @param {Function} fn
+ */
+export function derived(fn) {
+ const derived = createSignal()
+
+ globalDerivedValue = function derivedValue() {
+ derived.value = fn()
+ }
+
+ effect(globalDerivedValue)
+
+ return derived
+}
+
+/**
+ * Creates a `Proxy` wrapped object with optional listeners
+ * that react to changes
+ *
+ * @example
+ * let fooHistory = []
+ *
+ * let kung = envoy(
+ * { foo: 'bar' },
+ * function firstListener(state, oldState) {
+ * if (state.foo !== oldState.foo) {
+ * localStorage.foo = state.foo
+ * },
+ * },
+ * async function secondListener(state, oldState) {
+ * if (state.foo !== oldState.foo) {
+ * fooHistory.push(oldState.foo)
+ * }
+ * }
+ * )
+ * kung.foo = 'baz'
+ * console.log(localStorage.foo) // 'baz'
+ * kung.foo = 'boo'
+ * console.log(fooHistory) // ['bar','baz']
+ *
+ * @param {Object} obj
+ * @param {...(
+ * state: any, oldState: any, prop: string | symbol
+ * ) => void | Promise?} [initListeners]
+ *
+ * @returns {obj}
+ */
+export function envoy(obj, ...initListeners) {
+ let _listeners = [...initListeners]
+ return new Proxy(obj, {
+ get(obj, prop, receiver) {
+ if (prop === '_listeners') {
+ return _listeners
+ }
+ return Reflect.get(obj, prop, receiver)
+ },
+ set(obj, prop, value) {
+ if (
+ prop === '_listeners' &&
+ Array.isArray(value)
+ ) {
+ _listeners = value
+ }
+
+ _listeners.forEach(
+ fn => fn(
+ {...obj, [prop]: value},
+ obj,
+ prop
+ )
+ )
+
+ obj[prop] = value
+
+ return true
+ }
+ })
+}
+
+export async function restate(
+ state = {},
+ renderState = {},
+) {
+ let renderKeys = Object.keys(renderState)
+
+ for await (let prop of renderKeys) {
+ state[prop] = renderState[prop]
+ }
+
+ return state
+}