diff --git a/cypress/integration/spec_elm_single_segwit_wallet.js b/cypress/integration/spec_elm_single_segwit_wallet.js
index c37a25ec1..128456a0a 100644
--- a/cypress/integration/spec_elm_single_segwit_wallet.js
+++ b/cypress/integration/spec_elm_single_segwit_wallet.js
@@ -12,10 +12,10 @@ describe('Operating with an elements singlesig wallet', () => {
cy.addHotDevice("Hot Elements Device 1","elements")
// Segwit Wallet
- cy.addHotWallet("Elm Single Segwit Hot Wallet","elements", "segwit")
+ cy.addHotWallet("Elm Single Segwit Hot Wallet","Hot Elements Device 1", "elements", "segwit")
// Nested Segwit Wallet
- cy.addHotWallet("Elm Single Nested Hot Wallet","elements", "nested_segwit")
+ cy.addHotWallet("Elm Single Nested Hot Wallet","Hot Elements Device 1", "elements", "nested_segwit")
})
it('send confidential transaction from segwit', () => {
diff --git a/cypress/integration/spec_wallet_utxo.js b/cypress/integration/spec_wallet_utxo.js
index 32e85fee2..939debfe4 100644
--- a/cypress/integration/spec_wallet_utxo.js
+++ b/cypress/integration/spec_wallet_utxo.js
@@ -1,26 +1,41 @@
describe('Send transactions from wallets', () => {
it('Freeze and unfreeze UTXO', () => {
+ const name = "UTXO Hot Bitcoin3"
+ const wallet_name = name+" wallet"
+ var wallet_name_ref = wallet_name.toLowerCase().replace(/ /g,"_")
cy.viewport(1200,660)
- cy.task("btc:mine")
- cy.wait(10000)
- cy.task("btc:mine")
- cy.wait(10000)
- cy.task("btc:mine")
- cy.wait(10000)
+ cy.visit('/')
+ cy.addHotDevice(name+" device","bitcoin")
+ cy.addHotWallet(wallet_name,name+" device", "bitcoin", "segwit")
+ cy.get('#fullbalance_amount').then(($div) => {
+ const balance = parseFloat($div.text())
+ if ( balance <= 20) {
+ cy.log("balance " + balance + " too low. Mining!")
+ cy.mine2wallet("btc")
+ cy.mine2wallet("btc")
+ cy.mine2wallet("btc")
+ }
+ })
+
+ cy.contains(wallet_name).click()
- cy.visit('/wallets/wallet/test_hot_wallet_1/history')
-
- cy.wait(1000)
- cy.get('tx-table').shadow().find('.utxo-view-btn').click()
+ // The table as component is only available through the shadow tree
+ // That's why we have this stupid .shadow() ...
+ cy.get('tx-table').shadow().find('.utxo-view-btn').click({ force: true })
+ cy.log("Check that nothis is frozen")
cy.get('tx-table').shadow().find('tx-row').each(($el, index, $list) => {
cy.wrap($el).shadow().find('.tx-row').should('not.have.class', 'frozen')
cy.wrap($el).shadow().find('.frozen-img').should('have.class', 'hidden')
})
- // Freeze the UTXO
+
+ cy.log("First select it, then freeze it")
cy.get('tx-table').shadow().find('tx-row').eq(0).shadow().find('.select-tx-img').click()
+ cy.wait(100)
+ // then click the freeze-button
cy.get('tx-table').shadow().find('.freeze-tx-btn').click()
+ cy.wait(100)
cy.get('tx-table').shadow().find('tx-row').each(($el, index, $list) => {
if (index == 0) {
@@ -33,6 +48,7 @@ describe('Send transactions from wallets', () => {
})
// Test freeze UTXO can't be spend, and unfreeze works for coin selection option
+ cy.log("Select 3 UTXOs and freeze them")
cy.get('tx-table').shadow().find('tx-row').eq(0).shadow().find('.select-tx-img').click()
cy.get('tx-table').shadow().find('tx-row').eq(1).shadow().find('.select-tx-img').click()
cy.get('tx-table').shadow().find('tx-row').eq(3).shadow().find('.select-tx-img').click()
diff --git a/cypress/support/commands.js b/cypress/support/commands.js
index 9d7e5f8f3..06bbb61da 100644
--- a/cypress/support/commands.js
+++ b/cypress/support/commands.js
@@ -24,6 +24,8 @@
// -- This will overwrite an existing command --
// Cypress.Commands.overwrite("visit", (originalFn, url, options) => { ... })
+import 'cypress-wait-until';
+
Cypress.Commands.add("addDevice", (name) => {
cy.get('body').then(($body) => {
if ($body.text().includes(name)) {
@@ -61,6 +63,8 @@ Cypress.Commands.add("addHotDevice", (name, node_type) => {
var refName = "#device_list_item_"+name.toLowerCase().replace(/ /g,"_")
cy.get(refName).click()
cy.get('#forget_device').click()
+ // We might get an error here, if the device is used in a wallet
+ // We assume therefore that this is ok (see below)
}
cy.get('#side-content').click()
cy.get('#btn_new_device').click()
@@ -69,17 +73,22 @@ Cypress.Commands.add("addHotDevice", (name, node_type) => {
cy.get('#submit-mnemonic').click()
cy.get('#device_name').type(name)
cy.get('#submit-keys').click()
+ // It's a bit hackish as if the device already exists, we'll get an error
+ // but continue flaslessly nevertheless
cy.get('#devices_list > .item > div', { timeout: 8000 }).contains(name)
})
})
-Cypress.Commands.add("addHotWallet", (name, node_type, wallet_type, single_multi) => {
+Cypress.Commands.add("addHotWallet", (wallet_name, device_name, node_type, wallet_type, single_multi) => {
if (wallet_type == null) {
wallet_type = "segwit"
}
+ if (device_name == null) {
+ device_name = "Hot Elements Device 1"
+ }
cy.get('body').then(($body) => {
- if ($body.text().includes(name)) {
- cy.contains(name).click()
+ if ($body.text().includes(wallet_name)) {
+ cy.contains(wallet_name).click()
cy.get('#btn_settings' ).click( {force: true})
cy.get('#advanced_settings_tab_btn').click()
cy.get('#delete_wallet').click()
@@ -89,8 +98,9 @@ Cypress.Commands.add("addHotWallet", (name, node_type, wallet_type, single_multi
cy.get('#btn_new_wallet').click()
cy.get('[href="./simple/"]').click()
- cy.get('#hot_elements_device_1').click()
- cy.get('#wallet_name').type(name)
+ var device_button = "#"+device_name.toLowerCase().replace(/ /g,"_")
+ cy.get(device_button).click()
+ cy.get('#wallet_name').type(wallet_name)
if (wallet_type == "nested_segwit") {
cy.get(':nth-child(1) > #type_nested_segwit_btn').click()
}
@@ -103,7 +113,7 @@ Cypress.Commands.add("addHotWallet", (name, node_type, wallet_type, single_multi
cy.get('#btn_continue').click()
//Get some funds
- cy.mine2wallet("elm")
+ cy.mine2wallet(node_type)
})
})
@@ -127,21 +137,23 @@ Cypress.Commands.add("mine2wallet", (chain) => {
cy.get('#btn_transactions').click()
cy.get('#fullbalance_amount').then(($div) => {
const oldBalance = parseFloat($div.text())
- if (chain=="elm") {
+ if (chain=="elm" || chain=="elements") {
cy.task("elm:mine")
- } else if (chain=="btc") {
+ } else if (chain=="btc" || chain=="bitcoin") {
cy.task("btc:mine")
} else {
throw new Error("Unknown chain: " + chain)
}
- cy.wait(15000)
- cy.reload()
- cy.get('#fullbalance_amount') // Wait 5 secs + 15 secs timeout
- .should(($div) => {
+ cy.waitUntil( () => cy.reload().get('#fullbalance_amount', { timeout: 3000 })
+ .then(($div) => {
const n = parseFloat($div.text())
- expect(n).to.be.gt(oldBalance)
- }
- )
+ return n > oldBalance
+ })
+ , {
+ errorMsg: 'Waited for the funds arriving in the wallet from chain mining but it never did (timeout 30s) ',
+ timeout: 30000,
+ interval: 2000
+ })
})
})
diff --git a/package-lock.json b/package-lock.json
index 5c8eaa85c..0bdf834dc 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -1,8 +1,2059 @@
{
"name": "specter-desktop-cypress-testing",
"version": "0.0.1",
- "lockfileVersion": 1,
+ "lockfileVersion": 2,
"requires": true,
+ "packages": {
+ "": {
+ "name": "specter-desktop-cypress-testing",
+ "version": "0.0.1",
+ "license": "ISC",
+ "dependencies": {
+ "cypress": "^7.1.0",
+ "rimraf": "^3.0.2",
+ "wait-on": "^5.3.0"
+ },
+ "devDependencies": {
+ "cypress-wait-until": "^1.7.1"
+ }
+ },
+ "node_modules/@cypress/listr-verbose-renderer": {
+ "version": "0.4.1",
+ "resolved": "https://registry.npmjs.org/@cypress/listr-verbose-renderer/-/listr-verbose-renderer-0.4.1.tgz",
+ "integrity": "sha1-p3SS9LEdzHxEajSz4ochr9M8ZCo=",
+ "dependencies": {
+ "chalk": "^1.1.3",
+ "cli-cursor": "^1.0.2",
+ "date-fns": "^1.27.2",
+ "figures": "^1.7.0"
+ },
+ "engines": {
+ "node": ">=4"
+ }
+ },
+ "node_modules/@cypress/listr-verbose-renderer/node_modules/chalk": {
+ "version": "1.1.3",
+ "resolved": "https://registry.npmjs.org/chalk/-/chalk-1.1.3.tgz",
+ "integrity": "sha1-qBFcVeSnAv5NFQq9OHKCKn4J/Jg=",
+ "dependencies": {
+ "ansi-styles": "^2.2.1",
+ "escape-string-regexp": "^1.0.2",
+ "has-ansi": "^2.0.0",
+ "strip-ansi": "^3.0.0",
+ "supports-color": "^2.0.0"
+ },
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/@cypress/listr-verbose-renderer/node_modules/supports-color": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-2.0.0.tgz",
+ "integrity": "sha1-U10EXOa2Nj+kARcIRimZXp3zJMc=",
+ "engines": {
+ "node": ">=0.8.0"
+ }
+ },
+ "node_modules/@cypress/request": {
+ "version": "2.88.5",
+ "resolved": "https://registry.npmjs.org/@cypress/request/-/request-2.88.5.tgz",
+ "integrity": "sha512-TzEC1XMi1hJkywWpRfD2clreTa/Z+lOrXDCxxBTBPEcY5azdPi56A6Xw+O4tWJnaJH3iIE7G5aDXZC6JgRZLcA==",
+ "dependencies": {
+ "aws-sign2": "~0.7.0",
+ "aws4": "^1.8.0",
+ "caseless": "~0.12.0",
+ "combined-stream": "~1.0.6",
+ "extend": "~3.0.2",
+ "forever-agent": "~0.6.1",
+ "form-data": "~2.3.2",
+ "har-validator": "~5.1.3",
+ "http-signature": "~1.2.0",
+ "is-typedarray": "~1.0.0",
+ "isstream": "~0.1.2",
+ "json-stringify-safe": "~5.0.1",
+ "mime-types": "~2.1.19",
+ "oauth-sign": "~0.9.0",
+ "performance-now": "^2.1.0",
+ "qs": "~6.5.2",
+ "safe-buffer": "^5.1.2",
+ "tough-cookie": "~2.5.0",
+ "tunnel-agent": "^0.6.0",
+ "uuid": "^3.3.2"
+ },
+ "engines": {
+ "node": ">= 6"
+ }
+ },
+ "node_modules/@cypress/xvfb": {
+ "version": "1.2.4",
+ "resolved": "https://registry.npmjs.org/@cypress/xvfb/-/xvfb-1.2.4.tgz",
+ "integrity": "sha512-skbBzPggOVYCbnGgV+0dmBdW/s77ZkAOXIC1knS8NagwDjBrNC1LuXtQJeiN6l+m7lzmHtaoUw/ctJKdqkG57Q==",
+ "dependencies": {
+ "debug": "^3.1.0",
+ "lodash.once": "^4.1.1"
+ }
+ },
+ "node_modules/@cypress/xvfb/node_modules/debug": {
+ "version": "3.2.6",
+ "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.6.tgz",
+ "integrity": "sha512-mel+jf7nrtEl5Pn1Qx46zARXKDpBbvzezse7p7LqINmdoIk8PYP5SySaxEmYv6TZ0JyEKA1hsCId6DIhgITtWQ==",
+ "dependencies": {
+ "ms": "^2.1.1"
+ }
+ },
+ "node_modules/@hapi/hoek": {
+ "version": "9.1.0",
+ "resolved": "https://registry.npmjs.org/@hapi/hoek/-/hoek-9.1.0.tgz",
+ "integrity": "sha512-i9YbZPN3QgfighY/1X1Pu118VUz2Fmmhd6b2n0/O8YVgGGfw0FbUYoA97k7FkpGJ+pLCFEDLUmAPPV4D1kpeFw=="
+ },
+ "node_modules/@hapi/topo": {
+ "version": "5.0.0",
+ "resolved": "https://registry.npmjs.org/@hapi/topo/-/topo-5.0.0.tgz",
+ "integrity": "sha512-tFJlT47db0kMqVm3H4nQYgn6Pwg10GTZHb1pwmSiv1K4ks6drQOtfEF5ZnPjkvC+y4/bUPHK+bc87QvLcL+WMw==",
+ "dependencies": {
+ "@hapi/hoek": "^9.0.0"
+ }
+ },
+ "node_modules/@samverschueren/stream-to-observable": {
+ "version": "0.3.1",
+ "resolved": "https://registry.npmjs.org/@samverschueren/stream-to-observable/-/stream-to-observable-0.3.1.tgz",
+ "integrity": "sha512-c/qwwcHyafOQuVQJj0IlBjf5yYgBI7YPJ77k4fOJYesb41jio65eaJODRUmfYKhTOFBrIZ66kgvGPlNbjuoRdQ==",
+ "dependencies": {
+ "any-observable": "^0.3.0"
+ },
+ "engines": {
+ "node": ">=6"
+ }
+ },
+ "node_modules/@sideway/address": {
+ "version": "4.1.0",
+ "resolved": "https://registry.npmjs.org/@sideway/address/-/address-4.1.0.tgz",
+ "integrity": "sha512-wAH/JYRXeIFQRsxerIuLjgUu2Xszam+O5xKeatJ4oudShOOirfmsQ1D6LL54XOU2tizpCYku+s1wmU0SYdpoSA==",
+ "dependencies": {
+ "@hapi/hoek": "^9.0.0"
+ }
+ },
+ "node_modules/@sideway/formula": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/@sideway/formula/-/formula-3.0.0.tgz",
+ "integrity": "sha512-vHe7wZ4NOXVfkoRb8T5otiENVlT7a3IAiw7H5M2+GO+9CDgcVUUsX1zalAztCmwyOr2RUTGJdgB+ZvSVqmdHmg=="
+ },
+ "node_modules/@sideway/pinpoint": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/@sideway/pinpoint/-/pinpoint-2.0.0.tgz",
+ "integrity": "sha512-RNiOoTPkptFtSVzQevY/yWtZwf/RxyVnPy/OcA9HBM3MlGDnBEYL5B41H0MTn0Uec8Hi+2qUtTfG2WWZBmMejQ=="
+ },
+ "node_modules/@types/node": {
+ "version": "14.14.37",
+ "resolved": "https://registry.npmjs.org/@types/node/-/node-14.14.37.tgz",
+ "integrity": "sha512-XYmBiy+ohOR4Lh5jE379fV2IU+6Jn4g5qASinhitfyO71b/sCo6MKsMLF5tc7Zf2CE8hViVQyYSobJNke8OvUw=="
+ },
+ "node_modules/@types/sinonjs__fake-timers": {
+ "version": "6.0.2",
+ "resolved": "https://registry.npmjs.org/@types/sinonjs__fake-timers/-/sinonjs__fake-timers-6.0.2.tgz",
+ "integrity": "sha512-dIPoZ3g5gcx9zZEszaxLSVTvMReD3xxyyDnQUjA6IYDG9Ba2AV0otMPs+77sG9ojB4Qr2N2Vk5RnKeuA0X/0bg=="
+ },
+ "node_modules/@types/sizzle": {
+ "version": "2.3.2",
+ "resolved": "https://registry.npmjs.org/@types/sizzle/-/sizzle-2.3.2.tgz",
+ "integrity": "sha512-7EJYyKTL7tFR8+gDbB6Wwz/arpGa0Mywk1TJbNzKzHtzbwVmY4HR9WqS5VV7dsBUKQmPNr192jHr/VpBluj/hg=="
+ },
+ "node_modules/ajv": {
+ "version": "6.12.6",
+ "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz",
+ "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==",
+ "dependencies": {
+ "fast-deep-equal": "^3.1.1",
+ "fast-json-stable-stringify": "^2.0.0",
+ "json-schema-traverse": "^0.4.1",
+ "uri-js": "^4.2.2"
+ }
+ },
+ "node_modules/ansi-escapes": {
+ "version": "3.2.0",
+ "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-3.2.0.tgz",
+ "integrity": "sha512-cBhpre4ma+U0T1oM5fXg7Dy1Jw7zzwv7lt/GoCpr+hDQJoYnKVPLL4dCvSEFMmQurOQvSrwT7SL/DAlhBI97RQ==",
+ "engines": {
+ "node": ">=4"
+ }
+ },
+ "node_modules/ansi-regex": {
+ "version": "2.1.1",
+ "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-2.1.1.tgz",
+ "integrity": "sha1-w7M6te42DYbg5ijwRorn7yfWVN8=",
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/ansi-styles": {
+ "version": "2.2.1",
+ "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-2.2.1.tgz",
+ "integrity": "sha1-tDLdM1i2NM914eRmQ2gkBTPB3b4=",
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/any-observable": {
+ "version": "0.3.0",
+ "resolved": "https://registry.npmjs.org/any-observable/-/any-observable-0.3.0.tgz",
+ "integrity": "sha512-/FQM1EDkTsf63Ub2C6O7GuYFDsSXUwsaZDurV0np41ocwq0jthUAYCmhBX9f+KwlaCgIuWyr/4WlUQUBfKfZog==",
+ "engines": {
+ "node": ">=6"
+ }
+ },
+ "node_modules/arch": {
+ "version": "2.2.0",
+ "resolved": "https://registry.npmjs.org/arch/-/arch-2.2.0.tgz",
+ "integrity": "sha512-Of/R0wqp83cgHozfIYLbBMnej79U/SVGOOyuB3VVFv1NRM/PSFMK12x9KVtiYzJqmnU5WR2qp0Z5rHb7sWGnFQ=="
+ },
+ "node_modules/asn1": {
+ "version": "0.2.4",
+ "resolved": "https://registry.npmjs.org/asn1/-/asn1-0.2.4.tgz",
+ "integrity": "sha512-jxwzQpLQjSmWXgwaCZE9Nz+glAG01yF1QnWgbhGwHI5A6FRIEY6IVqtHhIepHqI7/kyEyQEagBC5mBEFlIYvdg==",
+ "dependencies": {
+ "safer-buffer": "~2.1.0"
+ }
+ },
+ "node_modules/assert-plus": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/assert-plus/-/assert-plus-1.0.0.tgz",
+ "integrity": "sha1-8S4PPF13sLHN2RRpQuTpbB5N1SU=",
+ "engines": {
+ "node": ">=0.8"
+ }
+ },
+ "node_modules/async": {
+ "version": "3.2.0",
+ "resolved": "https://registry.npmjs.org/async/-/async-3.2.0.tgz",
+ "integrity": "sha512-TR2mEZFVOj2pLStYxLht7TyfuRzaydfpxr3k9RpHIzMgw7A64dzsdqCxH1WJyQdoe8T10nDXd9wnEigmiuHIZw=="
+ },
+ "node_modules/asynckit": {
+ "version": "0.4.0",
+ "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz",
+ "integrity": "sha1-x57Zf380y48robyXkLzDZkdLS3k="
+ },
+ "node_modules/at-least-node": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/at-least-node/-/at-least-node-1.0.0.tgz",
+ "integrity": "sha512-+q/t7Ekv1EDY2l6Gda6LLiX14rU9TV20Wa3ofeQmwPFZbOMo9DXrLbOjFaaclkXKWidIaopwAObQDqwWtGUjqg==",
+ "engines": {
+ "node": ">= 4.0.0"
+ }
+ },
+ "node_modules/aws-sign2": {
+ "version": "0.7.0",
+ "resolved": "https://registry.npmjs.org/aws-sign2/-/aws-sign2-0.7.0.tgz",
+ "integrity": "sha1-tG6JCTSpWR8tL2+G1+ap8bP+dqg=",
+ "engines": {
+ "node": "*"
+ }
+ },
+ "node_modules/aws4": {
+ "version": "1.11.0",
+ "resolved": "https://registry.npmjs.org/aws4/-/aws4-1.11.0.tgz",
+ "integrity": "sha512-xh1Rl34h6Fi1DC2WWKfxUTVqRsNnr6LsKz2+hfwDxQJWmrx8+c7ylaqBMcHfl1U1r2dsifOvKX3LQuLNZ+XSvA=="
+ },
+ "node_modules/axios": {
+ "version": "0.21.4",
+ "resolved": "https://registry.npmjs.org/axios/-/axios-0.21.4.tgz",
+ "integrity": "sha512-ut5vewkiu8jjGBdqpM44XxjuCjq9LAKeHVmoVfHVzy8eHgxxq8SbAVQNovDA8mVi05kP0Ea/n/UzcSHcTJQfNg==",
+ "dependencies": {
+ "follow-redirects": "^1.14.0"
+ }
+ },
+ "node_modules/axios/node_modules/follow-redirects": {
+ "version": "1.14.4",
+ "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.14.4.tgz",
+ "integrity": "sha512-zwGkiSXC1MUJG/qmeIFH2HBJx9u0V46QGUe3YR1fXG8bXQxq7fLj0RjLZQ5nubr9qNJUZrH+xUcwXEoXNpfS+g==",
+ "engines": {
+ "node": ">=4.0"
+ }
+ },
+ "node_modules/balanced-match": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.0.tgz",
+ "integrity": "sha1-ibTRmasr7kneFk6gK4nORi1xt2c="
+ },
+ "node_modules/bcrypt-pbkdf": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/bcrypt-pbkdf/-/bcrypt-pbkdf-1.0.2.tgz",
+ "integrity": "sha1-pDAdOJtqQ/m2f/PKEaP2Y342Dp4=",
+ "dependencies": {
+ "tweetnacl": "^0.14.3"
+ }
+ },
+ "node_modules/blob-util": {
+ "version": "2.0.2",
+ "resolved": "https://registry.npmjs.org/blob-util/-/blob-util-2.0.2.tgz",
+ "integrity": "sha512-T7JQa+zsXXEa6/8ZhHcQEW1UFfVM49Ts65uBkFL6fz2QmrElqmbajIDJvuA0tEhRe5eIjpV9ZF+0RfZR9voJFQ=="
+ },
+ "node_modules/bluebird": {
+ "version": "3.7.2",
+ "resolved": "https://registry.npmjs.org/bluebird/-/bluebird-3.7.2.tgz",
+ "integrity": "sha512-XpNj6GDQzdfW+r2Wnn7xiSAd7TM3jzkxGXBGTtWKuSXv1xUV+azxAm8jdWZN06QTQk+2N2XB9jRDkvbmQmcRtg=="
+ },
+ "node_modules/brace-expansion": {
+ "version": "1.1.11",
+ "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz",
+ "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==",
+ "dependencies": {
+ "balanced-match": "^1.0.0",
+ "concat-map": "0.0.1"
+ }
+ },
+ "node_modules/buffer-crc32": {
+ "version": "0.2.13",
+ "resolved": "https://registry.npmjs.org/buffer-crc32/-/buffer-crc32-0.2.13.tgz",
+ "integrity": "sha1-DTM+PwDqxQqhRUq9MO+MKl2ackI=",
+ "engines": {
+ "node": "*"
+ }
+ },
+ "node_modules/buffer-from": {
+ "version": "1.1.1",
+ "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.1.tgz",
+ "integrity": "sha512-MQcXEUbCKtEo7bhqEs6560Hyd4XaovZlO/k9V3hjVUF/zwW7KBVdSK4gIt/bzwS9MbR5qob+F5jusZsb0YQK2A=="
+ },
+ "node_modules/cachedir": {
+ "version": "2.3.0",
+ "resolved": "https://registry.npmjs.org/cachedir/-/cachedir-2.3.0.tgz",
+ "integrity": "sha512-A+Fezp4zxnit6FanDmv9EqXNAi3vt9DWp51/71UEhXukb7QUuvtv9344h91dyAxuTLoSYJFU299qzR3tzwPAhw==",
+ "engines": {
+ "node": ">=6"
+ }
+ },
+ "node_modules/caseless": {
+ "version": "0.12.0",
+ "resolved": "https://registry.npmjs.org/caseless/-/caseless-0.12.0.tgz",
+ "integrity": "sha1-G2gcIf+EAzyCZUMJBolCDRhxUdw="
+ },
+ "node_modules/chalk": {
+ "version": "4.1.0",
+ "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.0.tgz",
+ "integrity": "sha512-qwx12AxXe2Q5xQ43Ac//I6v5aXTipYrSESdOgzrN+9XjgEpyjpKuvSGaN4qE93f7TQTlerQQ8S+EQ0EyDoVL1A==",
+ "dependencies": {
+ "ansi-styles": "^4.1.0",
+ "supports-color": "^7.1.0"
+ },
+ "engines": {
+ "node": ">=10"
+ }
+ },
+ "node_modules/chalk/node_modules/ansi-styles": {
+ "version": "4.3.0",
+ "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz",
+ "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==",
+ "dependencies": {
+ "color-convert": "^2.0.1"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/check-more-types": {
+ "version": "2.24.0",
+ "resolved": "https://registry.npmjs.org/check-more-types/-/check-more-types-2.24.0.tgz",
+ "integrity": "sha1-FCD/sQ/URNz8ebQ4kbv//TKoRgA=",
+ "engines": {
+ "node": ">= 0.8.0"
+ }
+ },
+ "node_modules/ci-info": {
+ "version": "3.1.1",
+ "resolved": "https://registry.npmjs.org/ci-info/-/ci-info-3.1.1.tgz",
+ "integrity": "sha512-kdRWLBIJwdsYJWYJFtAFFYxybguqeF91qpZaggjG5Nf8QKdizFG2hjqvaTXbxFIcYbSaD74KpAXv6BSm17DHEQ=="
+ },
+ "node_modules/cli-cursor": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/cli-cursor/-/cli-cursor-1.0.2.tgz",
+ "integrity": "sha1-ZNo/fValRBLll5S9Ytw1KV6PKYc=",
+ "dependencies": {
+ "restore-cursor": "^1.0.1"
+ },
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/cli-table3": {
+ "version": "0.6.0",
+ "resolved": "https://registry.npmjs.org/cli-table3/-/cli-table3-0.6.0.tgz",
+ "integrity": "sha512-gnB85c3MGC7Nm9I/FkiasNBOKjOiO1RNuXXarQms37q4QMpWdlbBgD/VnOStA2faG1dpXMv31RFApjX1/QdgWQ==",
+ "dependencies": {
+ "object-assign": "^4.1.0",
+ "string-width": "^4.2.0"
+ },
+ "engines": {
+ "node": "10.* || >= 12.*"
+ },
+ "optionalDependencies": {
+ "colors": "^1.1.2"
+ }
+ },
+ "node_modules/cli-truncate": {
+ "version": "0.2.1",
+ "resolved": "https://registry.npmjs.org/cli-truncate/-/cli-truncate-0.2.1.tgz",
+ "integrity": "sha1-nxXPuwcFAFNpIWxiasfQWrkN1XQ=",
+ "dependencies": {
+ "slice-ansi": "0.0.4",
+ "string-width": "^1.0.1"
+ },
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/cli-truncate/node_modules/is-fullwidth-code-point": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-1.0.0.tgz",
+ "integrity": "sha1-754xOG8DGn8NZDr4L95QxFfvAMs=",
+ "dependencies": {
+ "number-is-nan": "^1.0.0"
+ },
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/cli-truncate/node_modules/string-width": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/string-width/-/string-width-1.0.2.tgz",
+ "integrity": "sha1-EYvfW4zcUaKn5w0hHgfisLmxB9M=",
+ "dependencies": {
+ "code-point-at": "^1.0.0",
+ "is-fullwidth-code-point": "^1.0.0",
+ "strip-ansi": "^3.0.0"
+ },
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/code-point-at": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/code-point-at/-/code-point-at-1.1.0.tgz",
+ "integrity": "sha1-DQcLTQQ6W+ozovGkDi7bPZpMz3c=",
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/color-convert": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz",
+ "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==",
+ "dependencies": {
+ "color-name": "~1.1.4"
+ },
+ "engines": {
+ "node": ">=7.0.0"
+ }
+ },
+ "node_modules/color-name": {
+ "version": "1.1.4",
+ "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz",
+ "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA=="
+ },
+ "node_modules/colors": {
+ "version": "1.4.0",
+ "resolved": "https://registry.npmjs.org/colors/-/colors-1.4.0.tgz",
+ "integrity": "sha512-a+UqTh4kgZg/SlGvfbzDHpgRu7AAQOmmqRHJnxhRZICKFUT91brVhNNt58CMWU9PsBbv3PDCZUHbVxuDiH2mtA==",
+ "optional": true,
+ "engines": {
+ "node": ">=0.1.90"
+ }
+ },
+ "node_modules/combined-stream": {
+ "version": "1.0.8",
+ "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz",
+ "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==",
+ "dependencies": {
+ "delayed-stream": "~1.0.0"
+ },
+ "engines": {
+ "node": ">= 0.8"
+ }
+ },
+ "node_modules/commander": {
+ "version": "5.1.0",
+ "resolved": "https://registry.npmjs.org/commander/-/commander-5.1.0.tgz",
+ "integrity": "sha512-P0CysNDQ7rtVw4QIQtm+MRxV66vKFSvlsQvGYXZWR3qFU0jlMKHZZZgw8e+8DSah4UDKMqnknRDQz+xuQXQ/Zg==",
+ "engines": {
+ "node": ">= 6"
+ }
+ },
+ "node_modules/common-tags": {
+ "version": "1.8.0",
+ "resolved": "https://registry.npmjs.org/common-tags/-/common-tags-1.8.0.tgz",
+ "integrity": "sha512-6P6g0uetGpW/sdyUy/iQQCbFF0kWVMSIVSyYz7Zgjcgh8mgw8PQzDNZeyZ5DQ2gM7LBoZPHmnjz8rUthkBG5tw==",
+ "engines": {
+ "node": ">=4.0.0"
+ }
+ },
+ "node_modules/concat-map": {
+ "version": "0.0.1",
+ "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz",
+ "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s="
+ },
+ "node_modules/concat-stream": {
+ "version": "1.6.2",
+ "resolved": "https://registry.npmjs.org/concat-stream/-/concat-stream-1.6.2.tgz",
+ "integrity": "sha512-27HBghJxjiZtIk3Ycvn/4kbJk/1uZuJFfuPEns6LaEvpvG1f0hTea8lilrouyo9mVc2GWdcEZ8OLoGmSADlrCw==",
+ "engines": [
+ "node >= 0.8"
+ ],
+ "dependencies": {
+ "buffer-from": "^1.0.0",
+ "inherits": "^2.0.3",
+ "readable-stream": "^2.2.2",
+ "typedarray": "^0.0.6"
+ }
+ },
+ "node_modules/core-util-is": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.2.tgz",
+ "integrity": "sha1-tf1UIgqivFq1eqtxQMlAdUUDwac="
+ },
+ "node_modules/cross-spawn": {
+ "version": "7.0.3",
+ "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz",
+ "integrity": "sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==",
+ "dependencies": {
+ "path-key": "^3.1.0",
+ "shebang-command": "^2.0.0",
+ "which": "^2.0.1"
+ },
+ "engines": {
+ "node": ">= 8"
+ }
+ },
+ "node_modules/cypress": {
+ "version": "7.1.0",
+ "resolved": "https://registry.npmjs.org/cypress/-/cypress-7.1.0.tgz",
+ "integrity": "sha512-AptQP9fVtN/FfOv8rJ9hTGJE2XQFc8saLHT38r/EeyWhzp0q/+P/DYRTDtjGZHeLTCNznAUrT4lal8jm+ouS7Q==",
+ "dependencies": {
+ "@cypress/listr-verbose-renderer": "^0.4.1",
+ "@cypress/request": "^2.88.5",
+ "@cypress/xvfb": "^1.2.4",
+ "@types/node": "^14.14.31",
+ "@types/sinonjs__fake-timers": "^6.0.2",
+ "@types/sizzle": "^2.3.2",
+ "arch": "^2.2.0",
+ "blob-util": "^2.0.2",
+ "bluebird": "^3.7.2",
+ "cachedir": "^2.3.0",
+ "chalk": "^4.1.0",
+ "check-more-types": "^2.24.0",
+ "cli-table3": "~0.6.0",
+ "commander": "^5.1.0",
+ "common-tags": "^1.8.0",
+ "dayjs": "^1.10.4",
+ "debug": "4.3.2",
+ "eventemitter2": "^6.4.3",
+ "execa": "4.1.0",
+ "executable": "^4.1.1",
+ "extract-zip": "^1.7.0",
+ "fs-extra": "^9.1.0",
+ "getos": "^3.2.1",
+ "is-ci": "^3.0.0",
+ "is-installed-globally": "~0.4.0",
+ "lazy-ass": "^1.6.0",
+ "listr": "^0.14.3",
+ "lodash": "^4.17.21",
+ "log-symbols": "^4.0.0",
+ "minimist": "^1.2.5",
+ "ospath": "^1.2.2",
+ "pretty-bytes": "^5.6.0",
+ "ramda": "~0.27.1",
+ "request-progress": "^3.0.0",
+ "supports-color": "^8.1.1",
+ "tmp": "~0.2.1",
+ "untildify": "^4.0.0",
+ "url": "^0.11.0",
+ "yauzl": "^2.10.0"
+ },
+ "bin": {
+ "cypress": "bin/cypress"
+ },
+ "engines": {
+ "node": ">=12.0.0"
+ }
+ },
+ "node_modules/cypress-wait-until": {
+ "version": "1.7.1",
+ "resolved": "https://registry.npmjs.org/cypress-wait-until/-/cypress-wait-until-1.7.1.tgz",
+ "integrity": "sha512-8DL5IsBTbAxBjfYgCzdbohPq/bY+IKc63fxtso1C8RWhLnQkZbVESyaclNr76jyxfId6uyzX8+Xnt0ZwaXNtkA==",
+ "dev": true
+ },
+ "node_modules/cypress/node_modules/supports-color": {
+ "version": "8.1.1",
+ "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-8.1.1.tgz",
+ "integrity": "sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==",
+ "dependencies": {
+ "has-flag": "^4.0.0"
+ },
+ "engines": {
+ "node": ">=10"
+ }
+ },
+ "node_modules/dashdash": {
+ "version": "1.14.1",
+ "resolved": "https://registry.npmjs.org/dashdash/-/dashdash-1.14.1.tgz",
+ "integrity": "sha1-hTz6D3y+L+1d4gMmuN1YEDX24vA=",
+ "dependencies": {
+ "assert-plus": "^1.0.0"
+ },
+ "engines": {
+ "node": ">=0.10"
+ }
+ },
+ "node_modules/date-fns": {
+ "version": "1.30.1",
+ "resolved": "https://registry.npmjs.org/date-fns/-/date-fns-1.30.1.tgz",
+ "integrity": "sha512-hBSVCvSmWC+QypYObzwGOd9wqdDpOt+0wl0KbU+R+uuZBS1jN8VsD1ss3irQDknRj5NvxiTF6oj/nDRnN/UQNw=="
+ },
+ "node_modules/dayjs": {
+ "version": "1.10.4",
+ "resolved": "https://registry.npmjs.org/dayjs/-/dayjs-1.10.4.tgz",
+ "integrity": "sha512-RI/Hh4kqRc1UKLOAf/T5zdMMX5DQIlDxwUe3wSyMMnEbGunnpENCdbUgM+dW7kXidZqCttBrmw7BhN4TMddkCw=="
+ },
+ "node_modules/debug": {
+ "version": "4.3.2",
+ "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.2.tgz",
+ "integrity": "sha512-mOp8wKcvj7XxC78zLgw/ZA+6TSgkoE2C/ienthhRD298T7UNwAg9diBpLRxC0mOezLl4B0xV7M0cCO6P/O0Xhw==",
+ "dependencies": {
+ "ms": "2.1.2"
+ },
+ "engines": {
+ "node": ">=6.0"
+ }
+ },
+ "node_modules/delayed-stream": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz",
+ "integrity": "sha1-3zrhmayt+31ECqrgsp4icrJOxhk=",
+ "engines": {
+ "node": ">=0.4.0"
+ }
+ },
+ "node_modules/ecc-jsbn": {
+ "version": "0.1.2",
+ "resolved": "https://registry.npmjs.org/ecc-jsbn/-/ecc-jsbn-0.1.2.tgz",
+ "integrity": "sha1-OoOpBOVDUyh4dMVkt1SThoSamMk=",
+ "dependencies": {
+ "jsbn": "~0.1.0",
+ "safer-buffer": "^2.1.0"
+ }
+ },
+ "node_modules/elegant-spinner": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/elegant-spinner/-/elegant-spinner-1.0.1.tgz",
+ "integrity": "sha1-2wQ1IcldfjA/2PNFvtwzSc+wcp4=",
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/emoji-regex": {
+ "version": "8.0.0",
+ "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz",
+ "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A=="
+ },
+ "node_modules/end-of-stream": {
+ "version": "1.4.4",
+ "resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.4.tgz",
+ "integrity": "sha512-+uw1inIHVPQoaVuHzRyXd21icM+cnt4CzD5rW+NC1wjOUSTOs+Te7FOv7AhN7vS9x/oIyhLP5PR1H+phQAHu5Q==",
+ "dependencies": {
+ "once": "^1.4.0"
+ }
+ },
+ "node_modules/escape-string-regexp": {
+ "version": "1.0.5",
+ "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz",
+ "integrity": "sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ=",
+ "engines": {
+ "node": ">=0.8.0"
+ }
+ },
+ "node_modules/eventemitter2": {
+ "version": "6.4.3",
+ "resolved": "https://registry.npmjs.org/eventemitter2/-/eventemitter2-6.4.3.tgz",
+ "integrity": "sha512-t0A2msp6BzOf+QAcI6z9XMktLj52OjGQg+8SJH6v5+3uxNpWYRR3wQmfA+6xtMU9kOC59qk9licus5dYcrYkMQ=="
+ },
+ "node_modules/execa": {
+ "version": "4.1.0",
+ "resolved": "https://registry.npmjs.org/execa/-/execa-4.1.0.tgz",
+ "integrity": "sha512-j5W0//W7f8UxAn8hXVnwG8tLwdiUy4FJLcSupCg6maBYZDpyBvTApK7KyuI4bKj8KOh1r2YH+6ucuYtJv1bTZA==",
+ "dependencies": {
+ "cross-spawn": "^7.0.0",
+ "get-stream": "^5.0.0",
+ "human-signals": "^1.1.1",
+ "is-stream": "^2.0.0",
+ "merge-stream": "^2.0.0",
+ "npm-run-path": "^4.0.0",
+ "onetime": "^5.1.0",
+ "signal-exit": "^3.0.2",
+ "strip-final-newline": "^2.0.0"
+ },
+ "engines": {
+ "node": ">=10"
+ }
+ },
+ "node_modules/execa/node_modules/onetime": {
+ "version": "5.1.2",
+ "resolved": "https://registry.npmjs.org/onetime/-/onetime-5.1.2.tgz",
+ "integrity": "sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg==",
+ "dependencies": {
+ "mimic-fn": "^2.1.0"
+ },
+ "engines": {
+ "node": ">=6"
+ }
+ },
+ "node_modules/executable": {
+ "version": "4.1.1",
+ "resolved": "https://registry.npmjs.org/executable/-/executable-4.1.1.tgz",
+ "integrity": "sha512-8iA79xD3uAch729dUG8xaaBBFGaEa0wdD2VkYLFHwlqosEj/jT66AzcreRDSgV7ehnNLBW2WR5jIXwGKjVdTLg==",
+ "dependencies": {
+ "pify": "^2.2.0"
+ },
+ "engines": {
+ "node": ">=4"
+ }
+ },
+ "node_modules/exit-hook": {
+ "version": "1.1.1",
+ "resolved": "https://registry.npmjs.org/exit-hook/-/exit-hook-1.1.1.tgz",
+ "integrity": "sha1-8FyiM7SMBdVP/wd2XfhQfpXAL/g=",
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/extend": {
+ "version": "3.0.2",
+ "resolved": "https://registry.npmjs.org/extend/-/extend-3.0.2.tgz",
+ "integrity": "sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g=="
+ },
+ "node_modules/extract-zip": {
+ "version": "1.7.0",
+ "resolved": "https://registry.npmjs.org/extract-zip/-/extract-zip-1.7.0.tgz",
+ "integrity": "sha512-xoh5G1W/PB0/27lXgMQyIhP5DSY/LhoCsOyZgb+6iMmRtCwVBo55uKaMoEYrDCKQhWvqEip5ZPKAc6eFNyf/MA==",
+ "dependencies": {
+ "concat-stream": "^1.6.2",
+ "debug": "^2.6.9",
+ "mkdirp": "^0.5.4",
+ "yauzl": "^2.10.0"
+ },
+ "bin": {
+ "extract-zip": "cli.js"
+ }
+ },
+ "node_modules/extract-zip/node_modules/debug": {
+ "version": "2.6.9",
+ "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz",
+ "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==",
+ "dependencies": {
+ "ms": "2.0.0"
+ }
+ },
+ "node_modules/extract-zip/node_modules/ms": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz",
+ "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g="
+ },
+ "node_modules/extsprintf": {
+ "version": "1.3.0",
+ "resolved": "https://registry.npmjs.org/extsprintf/-/extsprintf-1.3.0.tgz",
+ "integrity": "sha1-lpGEQOMEGnpBT4xS48V06zw+HgU=",
+ "engines": [
+ "node >=0.6.0"
+ ]
+ },
+ "node_modules/fast-deep-equal": {
+ "version": "3.1.3",
+ "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz",
+ "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q=="
+ },
+ "node_modules/fast-json-stable-stringify": {
+ "version": "2.1.0",
+ "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz",
+ "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw=="
+ },
+ "node_modules/fd-slicer": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/fd-slicer/-/fd-slicer-1.1.0.tgz",
+ "integrity": "sha1-JcfInLH5B3+IkbvmHY85Dq4lbx4=",
+ "dependencies": {
+ "pend": "~1.2.0"
+ }
+ },
+ "node_modules/figures": {
+ "version": "1.7.0",
+ "resolved": "https://registry.npmjs.org/figures/-/figures-1.7.0.tgz",
+ "integrity": "sha1-y+Hjr/zxzUS4DK3+0o3Hk6lwHS4=",
+ "dependencies": {
+ "escape-string-regexp": "^1.0.5",
+ "object-assign": "^4.1.0"
+ },
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/forever-agent": {
+ "version": "0.6.1",
+ "resolved": "https://registry.npmjs.org/forever-agent/-/forever-agent-0.6.1.tgz",
+ "integrity": "sha1-+8cfDEGt6zf5bFd60e1C2P2sypE=",
+ "engines": {
+ "node": "*"
+ }
+ },
+ "node_modules/form-data": {
+ "version": "2.3.3",
+ "resolved": "https://registry.npmjs.org/form-data/-/form-data-2.3.3.tgz",
+ "integrity": "sha512-1lLKB2Mu3aGP1Q/2eCOx0fNbRMe7XdwktwOruhfqqd0rIJWwN4Dh+E3hrPSlDCXnSR7UtZ1N38rVXm+6+MEhJQ==",
+ "dependencies": {
+ "asynckit": "^0.4.0",
+ "combined-stream": "^1.0.6",
+ "mime-types": "^2.1.12"
+ },
+ "engines": {
+ "node": ">= 0.12"
+ }
+ },
+ "node_modules/fs-extra": {
+ "version": "9.1.0",
+ "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-9.1.0.tgz",
+ "integrity": "sha512-hcg3ZmepS30/7BSFqRvoo3DOMQu7IjqxO5nCDt+zM9XWjb33Wg7ziNT+Qvqbuc3+gWpzO02JubVyk2G4Zvo1OQ==",
+ "dependencies": {
+ "at-least-node": "^1.0.0",
+ "graceful-fs": "^4.2.0",
+ "jsonfile": "^6.0.1",
+ "universalify": "^2.0.0"
+ },
+ "engines": {
+ "node": ">=10"
+ }
+ },
+ "node_modules/fs.realpath": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz",
+ "integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8="
+ },
+ "node_modules/get-stream": {
+ "version": "5.2.0",
+ "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-5.2.0.tgz",
+ "integrity": "sha512-nBF+F1rAZVCu/p7rjzgA+Yb4lfYXrpl7a6VmJrU8wF9I1CKvP/QwPNZHnOlwbTkY6dvtFIzFMSyQXbLoTQPRpA==",
+ "dependencies": {
+ "pump": "^3.0.0"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/getos": {
+ "version": "3.2.1",
+ "resolved": "https://registry.npmjs.org/getos/-/getos-3.2.1.tgz",
+ "integrity": "sha512-U56CfOK17OKgTVqozZjUKNdkfEv6jk5WISBJ8SHoagjE6L69zOwl3Z+O8myjY9MEW3i2HPWQBt/LTbCgcC973Q==",
+ "dependencies": {
+ "async": "^3.2.0"
+ }
+ },
+ "node_modules/getpass": {
+ "version": "0.1.7",
+ "resolved": "https://registry.npmjs.org/getpass/-/getpass-0.1.7.tgz",
+ "integrity": "sha1-Xv+OPmhNVprkyysSgmBOi6YhSfo=",
+ "dependencies": {
+ "assert-plus": "^1.0.0"
+ }
+ },
+ "node_modules/glob": {
+ "version": "7.1.6",
+ "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.6.tgz",
+ "integrity": "sha512-LwaxwyZ72Lk7vZINtNNrywX0ZuLyStrdDtabefZKAY5ZGJhVtgdznluResxNmPitE0SAO+O26sWTHeKSI2wMBA==",
+ "dependencies": {
+ "fs.realpath": "^1.0.0",
+ "inflight": "^1.0.4",
+ "inherits": "2",
+ "minimatch": "^3.0.4",
+ "once": "^1.3.0",
+ "path-is-absolute": "^1.0.0"
+ },
+ "engines": {
+ "node": "*"
+ }
+ },
+ "node_modules/global-dirs": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/global-dirs/-/global-dirs-3.0.0.tgz",
+ "integrity": "sha512-v8ho2DS5RiCjftj1nD9NmnfaOzTdud7RRnVd9kFNOjqZbISlx5DQ+OrTkywgd0dIt7oFCvKetZSHoHcP3sDdiA==",
+ "dependencies": {
+ "ini": "2.0.0"
+ },
+ "engines": {
+ "node": ">=10"
+ }
+ },
+ "node_modules/graceful-fs": {
+ "version": "4.2.6",
+ "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.6.tgz",
+ "integrity": "sha512-nTnJ528pbqxYanhpDYsi4Rd8MAeaBA67+RZ10CM1m3bTAVFEDcd5AuA4a6W5YkGZ1iNXHzZz8T6TBKLeBuNriQ=="
+ },
+ "node_modules/har-schema": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/har-schema/-/har-schema-2.0.0.tgz",
+ "integrity": "sha1-qUwiJOvKwEeCoNkDVSHyRzW37JI=",
+ "engines": {
+ "node": ">=4"
+ }
+ },
+ "node_modules/har-validator": {
+ "version": "5.1.5",
+ "resolved": "https://registry.npmjs.org/har-validator/-/har-validator-5.1.5.tgz",
+ "integrity": "sha512-nmT2T0lljbxdQZfspsno9hgrG3Uir6Ks5afism62poxqBM6sDnMEuPmzTq8XN0OEwqKLLdh1jQI3qyE66Nzb3w==",
+ "dependencies": {
+ "ajv": "^6.12.3",
+ "har-schema": "^2.0.0"
+ },
+ "engines": {
+ "node": ">=6"
+ }
+ },
+ "node_modules/has-ansi": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/has-ansi/-/has-ansi-2.0.0.tgz",
+ "integrity": "sha1-NPUEnOHs3ysGSa8+8k5F7TVBbZE=",
+ "dependencies": {
+ "ansi-regex": "^2.0.0"
+ },
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/has-flag": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz",
+ "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==",
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/http-signature": {
+ "version": "1.2.0",
+ "resolved": "https://registry.npmjs.org/http-signature/-/http-signature-1.2.0.tgz",
+ "integrity": "sha1-muzZJRFHcvPZW2WmCruPfBj7rOE=",
+ "dependencies": {
+ "assert-plus": "^1.0.0",
+ "jsprim": "^1.2.2",
+ "sshpk": "^1.7.0"
+ },
+ "engines": {
+ "node": ">=0.8",
+ "npm": ">=1.3.7"
+ }
+ },
+ "node_modules/human-signals": {
+ "version": "1.1.1",
+ "resolved": "https://registry.npmjs.org/human-signals/-/human-signals-1.1.1.tgz",
+ "integrity": "sha512-SEQu7vl8KjNL2eoGBLF3+wAjpsNfA9XMlXAYj/3EdaNfAlxKthD1xjEQfGOUhllCGGJVNY34bRr6lPINhNjyZw==",
+ "engines": {
+ "node": ">=8.12.0"
+ }
+ },
+ "node_modules/indent-string": {
+ "version": "3.2.0",
+ "resolved": "https://registry.npmjs.org/indent-string/-/indent-string-3.2.0.tgz",
+ "integrity": "sha1-Sl/W0nzDMvN+VBmlBNu4NxBckok=",
+ "engines": {
+ "node": ">=4"
+ }
+ },
+ "node_modules/inflight": {
+ "version": "1.0.6",
+ "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz",
+ "integrity": "sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk=",
+ "dependencies": {
+ "once": "^1.3.0",
+ "wrappy": "1"
+ }
+ },
+ "node_modules/inherits": {
+ "version": "2.0.4",
+ "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz",
+ "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ=="
+ },
+ "node_modules/ini": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/ini/-/ini-2.0.0.tgz",
+ "integrity": "sha512-7PnF4oN3CvZF23ADhA5wRaYEQpJ8qygSkbtTXWBeXWXmEVRXK+1ITciHWwHhsjv1TmW0MgacIv6hEi5pX5NQdA==",
+ "engines": {
+ "node": ">=10"
+ }
+ },
+ "node_modules/is-ci": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/is-ci/-/is-ci-3.0.0.tgz",
+ "integrity": "sha512-kDXyttuLeslKAHYL/K28F2YkM3x5jvFPEw3yXbRptXydjD9rpLEz+C5K5iutY9ZiUu6AP41JdvRQwF4Iqs4ZCQ==",
+ "dependencies": {
+ "ci-info": "^3.1.1"
+ },
+ "bin": {
+ "is-ci": "bin.js"
+ }
+ },
+ "node_modules/is-fullwidth-code-point": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz",
+ "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==",
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/is-installed-globally": {
+ "version": "0.4.0",
+ "resolved": "https://registry.npmjs.org/is-installed-globally/-/is-installed-globally-0.4.0.tgz",
+ "integrity": "sha512-iwGqO3J21aaSkC7jWnHP/difazwS7SFeIqxv6wEtLU8Y5KlzFTjyqcSIT0d8s4+dDhKytsk9PJZ2BkS5eZwQRQ==",
+ "dependencies": {
+ "global-dirs": "^3.0.0",
+ "is-path-inside": "^3.0.2"
+ },
+ "engines": {
+ "node": ">=10"
+ }
+ },
+ "node_modules/is-observable": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/is-observable/-/is-observable-1.1.0.tgz",
+ "integrity": "sha512-NqCa4Sa2d+u7BWc6CukaObG3Fh+CU9bvixbpcXYhy2VvYS7vVGIdAgnIS5Ks3A/cqk4rebLJ9s8zBstT2aKnIA==",
+ "dependencies": {
+ "symbol-observable": "^1.1.0"
+ },
+ "engines": {
+ "node": ">=4"
+ }
+ },
+ "node_modules/is-path-inside": {
+ "version": "3.0.3",
+ "resolved": "https://registry.npmjs.org/is-path-inside/-/is-path-inside-3.0.3.tgz",
+ "integrity": "sha512-Fd4gABb+ycGAmKou8eMftCupSir5lRxqf4aD/vd0cD2qc4HL07OjCeuHMr8Ro4CoMaeCKDB0/ECBOVWjTwUvPQ==",
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/is-promise": {
+ "version": "2.2.2",
+ "resolved": "https://registry.npmjs.org/is-promise/-/is-promise-2.2.2.tgz",
+ "integrity": "sha512-+lP4/6lKUBfQjZ2pdxThZvLUAafmZb8OAxFb8XXtiQmS35INgr85hdOGoEs124ez1FCnZJt6jau/T+alh58QFQ=="
+ },
+ "node_modules/is-stream": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-2.0.0.tgz",
+ "integrity": "sha512-XCoy+WlUr7d1+Z8GgSuXmpuUFC9fOhRXglJMx+dwLKTkL44Cjd4W1Z5P+BQZpr+cR93aGP4S/s7Ftw6Nd/kiEw==",
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/is-typedarray": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/is-typedarray/-/is-typedarray-1.0.0.tgz",
+ "integrity": "sha1-5HnICFjfDBsR3dppQPlgEfzaSpo="
+ },
+ "node_modules/isarray": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz",
+ "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE="
+ },
+ "node_modules/isexe": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz",
+ "integrity": "sha1-6PvzdNxVb/iUehDcsFctYz8s+hA="
+ },
+ "node_modules/isstream": {
+ "version": "0.1.2",
+ "resolved": "https://registry.npmjs.org/isstream/-/isstream-0.1.2.tgz",
+ "integrity": "sha1-R+Y/evVa+m+S4VAOaQ64uFKcCZo="
+ },
+ "node_modules/joi": {
+ "version": "17.3.0",
+ "resolved": "https://registry.npmjs.org/joi/-/joi-17.3.0.tgz",
+ "integrity": "sha512-Qh5gdU6niuYbUIUV5ejbsMiiFmBdw8Kcp8Buj2JntszCkCfxJ9Cz76OtHxOZMPXrt5810iDIXs+n1nNVoquHgg==",
+ "dependencies": {
+ "@hapi/hoek": "^9.0.0",
+ "@hapi/topo": "^5.0.0",
+ "@sideway/address": "^4.1.0",
+ "@sideway/formula": "^3.0.0",
+ "@sideway/pinpoint": "^2.0.0"
+ }
+ },
+ "node_modules/jsbn": {
+ "version": "0.1.1",
+ "resolved": "https://registry.npmjs.org/jsbn/-/jsbn-0.1.1.tgz",
+ "integrity": "sha1-peZUwuWi3rXyAdls77yoDA7y9RM="
+ },
+ "node_modules/json-schema": {
+ "version": "0.2.3",
+ "resolved": "https://registry.npmjs.org/json-schema/-/json-schema-0.2.3.tgz",
+ "integrity": "sha1-tIDIkuWaLwWVTOcnvT8qTogvnhM="
+ },
+ "node_modules/json-schema-traverse": {
+ "version": "0.4.1",
+ "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz",
+ "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg=="
+ },
+ "node_modules/json-stringify-safe": {
+ "version": "5.0.1",
+ "resolved": "https://registry.npmjs.org/json-stringify-safe/-/json-stringify-safe-5.0.1.tgz",
+ "integrity": "sha1-Epai1Y/UXxmg9s4B1lcB4sc1tus="
+ },
+ "node_modules/jsonfile": {
+ "version": "6.1.0",
+ "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-6.1.0.tgz",
+ "integrity": "sha512-5dgndWOriYSm5cnYaJNhalLNDKOqFwyDB/rr1E9ZsGciGvKPs8R2xYGCacuf3z6K1YKDz182fd+fY3cn3pMqXQ==",
+ "dependencies": {
+ "graceful-fs": "^4.1.6",
+ "universalify": "^2.0.0"
+ }
+ },
+ "node_modules/jsprim": {
+ "version": "1.4.1",
+ "resolved": "https://registry.npmjs.org/jsprim/-/jsprim-1.4.1.tgz",
+ "integrity": "sha1-MT5mvB5cwG5Di8G3SZwuXFastqI=",
+ "engines": [
+ "node >=0.6.0"
+ ],
+ "dependencies": {
+ "assert-plus": "1.0.0",
+ "extsprintf": "1.3.0",
+ "json-schema": "0.2.3",
+ "verror": "1.10.0"
+ }
+ },
+ "node_modules/lazy-ass": {
+ "version": "1.6.0",
+ "resolved": "https://registry.npmjs.org/lazy-ass/-/lazy-ass-1.6.0.tgz",
+ "integrity": "sha1-eZllXoZGwX8In90YfRUNMyTVRRM=",
+ "engines": {
+ "node": "> 0.8"
+ }
+ },
+ "node_modules/listr": {
+ "version": "0.14.3",
+ "resolved": "https://registry.npmjs.org/listr/-/listr-0.14.3.tgz",
+ "integrity": "sha512-RmAl7su35BFd/xoMamRjpIE4j3v+L28o8CT5YhAXQJm1fD+1l9ngXY8JAQRJ+tFK2i5njvi0iRUKV09vPwA0iA==",
+ "dependencies": {
+ "@samverschueren/stream-to-observable": "^0.3.0",
+ "is-observable": "^1.1.0",
+ "is-promise": "^2.1.0",
+ "is-stream": "^1.1.0",
+ "listr-silent-renderer": "^1.1.1",
+ "listr-update-renderer": "^0.5.0",
+ "listr-verbose-renderer": "^0.5.0",
+ "p-map": "^2.0.0",
+ "rxjs": "^6.3.3"
+ },
+ "engines": {
+ "node": ">=6"
+ }
+ },
+ "node_modules/listr-silent-renderer": {
+ "version": "1.1.1",
+ "resolved": "https://registry.npmjs.org/listr-silent-renderer/-/listr-silent-renderer-1.1.1.tgz",
+ "integrity": "sha1-kktaN1cVN3C/Go4/v3S4u/P5JC4=",
+ "engines": {
+ "node": ">=4"
+ }
+ },
+ "node_modules/listr-update-renderer": {
+ "version": "0.5.0",
+ "resolved": "https://registry.npmjs.org/listr-update-renderer/-/listr-update-renderer-0.5.0.tgz",
+ "integrity": "sha512-tKRsZpKz8GSGqoI/+caPmfrypiaq+OQCbd+CovEC24uk1h952lVj5sC7SqyFUm+OaJ5HN/a1YLt5cit2FMNsFA==",
+ "dependencies": {
+ "chalk": "^1.1.3",
+ "cli-truncate": "^0.2.1",
+ "elegant-spinner": "^1.0.1",
+ "figures": "^1.7.0",
+ "indent-string": "^3.0.0",
+ "log-symbols": "^1.0.2",
+ "log-update": "^2.3.0",
+ "strip-ansi": "^3.0.1"
+ },
+ "engines": {
+ "node": ">=6"
+ }
+ },
+ "node_modules/listr-update-renderer/node_modules/chalk": {
+ "version": "1.1.3",
+ "resolved": "https://registry.npmjs.org/chalk/-/chalk-1.1.3.tgz",
+ "integrity": "sha1-qBFcVeSnAv5NFQq9OHKCKn4J/Jg=",
+ "dependencies": {
+ "ansi-styles": "^2.2.1",
+ "escape-string-regexp": "^1.0.2",
+ "has-ansi": "^2.0.0",
+ "strip-ansi": "^3.0.0",
+ "supports-color": "^2.0.0"
+ },
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/listr-update-renderer/node_modules/log-symbols": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/log-symbols/-/log-symbols-1.0.2.tgz",
+ "integrity": "sha1-N2/3tY6jCGoPCfrMdGF+ylAeGhg=",
+ "dependencies": {
+ "chalk": "^1.0.0"
+ },
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/listr-update-renderer/node_modules/supports-color": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-2.0.0.tgz",
+ "integrity": "sha1-U10EXOa2Nj+kARcIRimZXp3zJMc=",
+ "engines": {
+ "node": ">=0.8.0"
+ }
+ },
+ "node_modules/listr-verbose-renderer": {
+ "version": "0.5.0",
+ "resolved": "https://registry.npmjs.org/listr-verbose-renderer/-/listr-verbose-renderer-0.5.0.tgz",
+ "integrity": "sha512-04PDPqSlsqIOaaaGZ+41vq5FejI9auqTInicFRndCBgE3bXG8D6W1I+mWhk+1nqbHmyhla/6BUrd5OSiHwKRXw==",
+ "dependencies": {
+ "chalk": "^2.4.1",
+ "cli-cursor": "^2.1.0",
+ "date-fns": "^1.27.2",
+ "figures": "^2.0.0"
+ },
+ "engines": {
+ "node": ">=4"
+ }
+ },
+ "node_modules/listr-verbose-renderer/node_modules/ansi-styles": {
+ "version": "3.2.1",
+ "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz",
+ "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==",
+ "dependencies": {
+ "color-convert": "^1.9.0"
+ },
+ "engines": {
+ "node": ">=4"
+ }
+ },
+ "node_modules/listr-verbose-renderer/node_modules/chalk": {
+ "version": "2.4.2",
+ "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz",
+ "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==",
+ "dependencies": {
+ "ansi-styles": "^3.2.1",
+ "escape-string-regexp": "^1.0.5",
+ "supports-color": "^5.3.0"
+ },
+ "engines": {
+ "node": ">=4"
+ }
+ },
+ "node_modules/listr-verbose-renderer/node_modules/cli-cursor": {
+ "version": "2.1.0",
+ "resolved": "https://registry.npmjs.org/cli-cursor/-/cli-cursor-2.1.0.tgz",
+ "integrity": "sha1-s12sN2R5+sw+lHR9QdDQ9SOP/LU=",
+ "dependencies": {
+ "restore-cursor": "^2.0.0"
+ },
+ "engines": {
+ "node": ">=4"
+ }
+ },
+ "node_modules/listr-verbose-renderer/node_modules/color-convert": {
+ "version": "1.9.3",
+ "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz",
+ "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==",
+ "dependencies": {
+ "color-name": "1.1.3"
+ }
+ },
+ "node_modules/listr-verbose-renderer/node_modules/color-name": {
+ "version": "1.1.3",
+ "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz",
+ "integrity": "sha1-p9BVi9icQveV3UIyj3QIMcpTvCU="
+ },
+ "node_modules/listr-verbose-renderer/node_modules/figures": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/figures/-/figures-2.0.0.tgz",
+ "integrity": "sha1-OrGi0qYsi/tDGgyUy3l6L84nyWI=",
+ "dependencies": {
+ "escape-string-regexp": "^1.0.5"
+ },
+ "engines": {
+ "node": ">=4"
+ }
+ },
+ "node_modules/listr-verbose-renderer/node_modules/has-flag": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz",
+ "integrity": "sha1-tdRU3CGZriJWmfNGfloH87lVuv0=",
+ "engines": {
+ "node": ">=4"
+ }
+ },
+ "node_modules/listr-verbose-renderer/node_modules/mimic-fn": {
+ "version": "1.2.0",
+ "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-1.2.0.tgz",
+ "integrity": "sha512-jf84uxzwiuiIVKiOLpfYk7N46TSy8ubTonmneY9vrpHNAnp0QBt2BxWV9dO3/j+BoVAb+a5G6YDPW3M5HOdMWQ==",
+ "engines": {
+ "node": ">=4"
+ }
+ },
+ "node_modules/listr-verbose-renderer/node_modules/onetime": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/onetime/-/onetime-2.0.1.tgz",
+ "integrity": "sha1-BnQoIw/WdEOyeUsiu6UotoZ5YtQ=",
+ "dependencies": {
+ "mimic-fn": "^1.0.0"
+ },
+ "engines": {
+ "node": ">=4"
+ }
+ },
+ "node_modules/listr-verbose-renderer/node_modules/restore-cursor": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/restore-cursor/-/restore-cursor-2.0.0.tgz",
+ "integrity": "sha1-n37ih/gv0ybU/RYpI9YhKe7g368=",
+ "dependencies": {
+ "onetime": "^2.0.0",
+ "signal-exit": "^3.0.2"
+ },
+ "engines": {
+ "node": ">=4"
+ }
+ },
+ "node_modules/listr-verbose-renderer/node_modules/supports-color": {
+ "version": "5.5.0",
+ "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz",
+ "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==",
+ "dependencies": {
+ "has-flag": "^3.0.0"
+ },
+ "engines": {
+ "node": ">=4"
+ }
+ },
+ "node_modules/listr/node_modules/is-stream": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-1.1.0.tgz",
+ "integrity": "sha1-EtSj3U5o4Lec6428hBc66A2RykQ=",
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/lodash": {
+ "version": "4.17.21",
+ "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz",
+ "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg=="
+ },
+ "node_modules/lodash.once": {
+ "version": "4.1.1",
+ "resolved": "https://registry.npmjs.org/lodash.once/-/lodash.once-4.1.1.tgz",
+ "integrity": "sha1-DdOXEhPHxW34gJd9UEyI+0cal6w="
+ },
+ "node_modules/log-symbols": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/log-symbols/-/log-symbols-4.0.0.tgz",
+ "integrity": "sha512-FN8JBzLx6CzeMrB0tg6pqlGU1wCrXW+ZXGH481kfsBqer0hToTIiHdjH4Mq8xJUbvATujKCvaREGWpGUionraA==",
+ "dependencies": {
+ "chalk": "^4.0.0"
+ },
+ "engines": {
+ "node": ">=10"
+ }
+ },
+ "node_modules/log-update": {
+ "version": "2.3.0",
+ "resolved": "https://registry.npmjs.org/log-update/-/log-update-2.3.0.tgz",
+ "integrity": "sha1-iDKP19HOeTiykoN0bwsbwSayRwg=",
+ "dependencies": {
+ "ansi-escapes": "^3.0.0",
+ "cli-cursor": "^2.0.0",
+ "wrap-ansi": "^3.0.1"
+ },
+ "engines": {
+ "node": ">=4"
+ }
+ },
+ "node_modules/log-update/node_modules/cli-cursor": {
+ "version": "2.1.0",
+ "resolved": "https://registry.npmjs.org/cli-cursor/-/cli-cursor-2.1.0.tgz",
+ "integrity": "sha1-s12sN2R5+sw+lHR9QdDQ9SOP/LU=",
+ "dependencies": {
+ "restore-cursor": "^2.0.0"
+ },
+ "engines": {
+ "node": ">=4"
+ }
+ },
+ "node_modules/log-update/node_modules/mimic-fn": {
+ "version": "1.2.0",
+ "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-1.2.0.tgz",
+ "integrity": "sha512-jf84uxzwiuiIVKiOLpfYk7N46TSy8ubTonmneY9vrpHNAnp0QBt2BxWV9dO3/j+BoVAb+a5G6YDPW3M5HOdMWQ==",
+ "engines": {
+ "node": ">=4"
+ }
+ },
+ "node_modules/log-update/node_modules/onetime": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/onetime/-/onetime-2.0.1.tgz",
+ "integrity": "sha1-BnQoIw/WdEOyeUsiu6UotoZ5YtQ=",
+ "dependencies": {
+ "mimic-fn": "^1.0.0"
+ },
+ "engines": {
+ "node": ">=4"
+ }
+ },
+ "node_modules/log-update/node_modules/restore-cursor": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/restore-cursor/-/restore-cursor-2.0.0.tgz",
+ "integrity": "sha1-n37ih/gv0ybU/RYpI9YhKe7g368=",
+ "dependencies": {
+ "onetime": "^2.0.0",
+ "signal-exit": "^3.0.2"
+ },
+ "engines": {
+ "node": ">=4"
+ }
+ },
+ "node_modules/merge-stream": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/merge-stream/-/merge-stream-2.0.0.tgz",
+ "integrity": "sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w=="
+ },
+ "node_modules/mime-db": {
+ "version": "1.44.0",
+ "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.44.0.tgz",
+ "integrity": "sha512-/NOTfLrsPBVeH7YtFPgsVWveuL+4SjjYxaQ1xtM1KMFj7HdxlBlxeyNLzhyJVx7r4rZGJAZ/6lkKCitSc/Nmpg==",
+ "engines": {
+ "node": ">= 0.6"
+ }
+ },
+ "node_modules/mime-types": {
+ "version": "2.1.27",
+ "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.27.tgz",
+ "integrity": "sha512-JIhqnCasI9yD+SsmkquHBxTSEuZdQX5BuQnS2Vc7puQQQ+8yiP5AY5uWhpdv4YL4VM5c6iliiYWPgJ/nJQLp7w==",
+ "dependencies": {
+ "mime-db": "1.44.0"
+ },
+ "engines": {
+ "node": ">= 0.6"
+ }
+ },
+ "node_modules/mimic-fn": {
+ "version": "2.1.0",
+ "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-2.1.0.tgz",
+ "integrity": "sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==",
+ "engines": {
+ "node": ">=6"
+ }
+ },
+ "node_modules/minimatch": {
+ "version": "3.0.4",
+ "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz",
+ "integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==",
+ "dependencies": {
+ "brace-expansion": "^1.1.7"
+ },
+ "engines": {
+ "node": "*"
+ }
+ },
+ "node_modules/minimist": {
+ "version": "1.2.5",
+ "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.5.tgz",
+ "integrity": "sha512-FM9nNUYrRBAELZQT3xeZQ7fmMOBg6nWNmJKTcgsJeaLstP/UODVpGsr5OhXhhXg6f+qtJ8uiZ+PUxkDWcgIXLw=="
+ },
+ "node_modules/mkdirp": {
+ "version": "0.5.5",
+ "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.5.tgz",
+ "integrity": "sha512-NKmAlESf6jMGym1++R0Ra7wvhV+wFW63FaSOFPwRahvea0gMUcGUhVeAg/0BC0wiv9ih5NYPB1Wn1UEI1/L+xQ==",
+ "dependencies": {
+ "minimist": "^1.2.5"
+ },
+ "bin": {
+ "mkdirp": "bin/cmd.js"
+ }
+ },
+ "node_modules/ms": {
+ "version": "2.1.2",
+ "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz",
+ "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w=="
+ },
+ "node_modules/npm-run-path": {
+ "version": "4.0.1",
+ "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-4.0.1.tgz",
+ "integrity": "sha512-S48WzZW777zhNIrn7gxOlISNAqi9ZC/uQFnRdbeIHhZhCA6UqpkOT8T1G7BvfdgP4Er8gF4sUbaS0i7QvIfCWw==",
+ "dependencies": {
+ "path-key": "^3.0.0"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/number-is-nan": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/number-is-nan/-/number-is-nan-1.0.1.tgz",
+ "integrity": "sha1-CXtgK1NCKlIsGvuHkDGDNpQaAR0=",
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/oauth-sign": {
+ "version": "0.9.0",
+ "resolved": "https://registry.npmjs.org/oauth-sign/-/oauth-sign-0.9.0.tgz",
+ "integrity": "sha512-fexhUFFPTGV8ybAtSIGbV6gOkSv8UtRbDBnAyLQw4QPKkgNlsH2ByPGtMUqdWkos6YCRmAqViwgZrJc/mRDzZQ==",
+ "engines": {
+ "node": "*"
+ }
+ },
+ "node_modules/object-assign": {
+ "version": "4.1.1",
+ "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz",
+ "integrity": "sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM=",
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/once": {
+ "version": "1.4.0",
+ "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz",
+ "integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=",
+ "dependencies": {
+ "wrappy": "1"
+ }
+ },
+ "node_modules/onetime": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/onetime/-/onetime-1.1.0.tgz",
+ "integrity": "sha1-ofeDj4MUxRbwXs78vEzP4EtO14k=",
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/ospath": {
+ "version": "1.2.2",
+ "resolved": "https://registry.npmjs.org/ospath/-/ospath-1.2.2.tgz",
+ "integrity": "sha1-EnZjl3Sj+O8lcvf+QoDg6kVQwHs="
+ },
+ "node_modules/p-map": {
+ "version": "2.1.0",
+ "resolved": "https://registry.npmjs.org/p-map/-/p-map-2.1.0.tgz",
+ "integrity": "sha512-y3b8Kpd8OAN444hxfBbFfj1FY/RjtTd8tzYwhUqNYXx0fXx2iX4maP4Qr6qhIKbQXI02wTLAda4fYUbDagTUFw==",
+ "engines": {
+ "node": ">=6"
+ }
+ },
+ "node_modules/path-is-absolute": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz",
+ "integrity": "sha1-F0uSaHNVNP+8es5r9TpanhtcX18=",
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/path-key": {
+ "version": "3.1.1",
+ "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz",
+ "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==",
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/pend": {
+ "version": "1.2.0",
+ "resolved": "https://registry.npmjs.org/pend/-/pend-1.2.0.tgz",
+ "integrity": "sha1-elfrVQpng/kRUzH89GY9XI4AelA="
+ },
+ "node_modules/performance-now": {
+ "version": "2.1.0",
+ "resolved": "https://registry.npmjs.org/performance-now/-/performance-now-2.1.0.tgz",
+ "integrity": "sha1-Ywn04OX6kT7BxpMHrjZLSzd8nns="
+ },
+ "node_modules/pify": {
+ "version": "2.3.0",
+ "resolved": "https://registry.npmjs.org/pify/-/pify-2.3.0.tgz",
+ "integrity": "sha1-7RQaasBDqEnqWISY59yosVMw6Qw=",
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/pretty-bytes": {
+ "version": "5.6.0",
+ "resolved": "https://registry.npmjs.org/pretty-bytes/-/pretty-bytes-5.6.0.tgz",
+ "integrity": "sha512-FFw039TmrBqFK8ma/7OL3sDz/VytdtJr044/QUJtH0wK9lb9jLq9tJyIxUwtQJHwar2BqtiA4iCWSwo9JLkzFg==",
+ "engines": {
+ "node": ">=6"
+ }
+ },
+ "node_modules/process-nextick-args": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz",
+ "integrity": "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag=="
+ },
+ "node_modules/psl": {
+ "version": "1.8.0",
+ "resolved": "https://registry.npmjs.org/psl/-/psl-1.8.0.tgz",
+ "integrity": "sha512-RIdOzyoavK+hA18OGGWDqUTsCLhtA7IcZ/6NCs4fFJaHBDab+pDDmDIByWFRQJq2Cd7r1OoQxBGKOaztq+hjIQ=="
+ },
+ "node_modules/pump": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/pump/-/pump-3.0.0.tgz",
+ "integrity": "sha512-LwZy+p3SFs1Pytd/jYct4wpv49HiYCqd9Rlc5ZVdk0V+8Yzv6jR5Blk3TRmPL1ft69TxP0IMZGJ+WPFU2BFhww==",
+ "dependencies": {
+ "end-of-stream": "^1.1.0",
+ "once": "^1.3.1"
+ }
+ },
+ "node_modules/punycode": {
+ "version": "2.1.1",
+ "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.1.1.tgz",
+ "integrity": "sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A==",
+ "engines": {
+ "node": ">=6"
+ }
+ },
+ "node_modules/qs": {
+ "version": "6.5.2",
+ "resolved": "https://registry.npmjs.org/qs/-/qs-6.5.2.tgz",
+ "integrity": "sha512-N5ZAX4/LxJmF+7wN74pUD6qAh9/wnvdQcjq9TZjevvXzSUo7bfmw91saqMjzGS2xq91/odN2dW/WOl7qQHNDGA==",
+ "engines": {
+ "node": ">=0.6"
+ }
+ },
+ "node_modules/querystring": {
+ "version": "0.2.0",
+ "resolved": "https://registry.npmjs.org/querystring/-/querystring-0.2.0.tgz",
+ "integrity": "sha1-sgmEkgO7Jd+CDadW50cAWHhSFiA=",
+ "engines": {
+ "node": ">=0.4.x"
+ }
+ },
+ "node_modules/ramda": {
+ "version": "0.27.1",
+ "resolved": "https://registry.npmjs.org/ramda/-/ramda-0.27.1.tgz",
+ "integrity": "sha512-PgIdVpn5y5Yns8vqb8FzBUEYn98V3xcPgawAkkgj0YJ0qDsnHCiNmZYfOGMgOvoB0eWFLpYbhxUR3mxfDIMvpw=="
+ },
+ "node_modules/readable-stream": {
+ "version": "2.3.7",
+ "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.7.tgz",
+ "integrity": "sha512-Ebho8K4jIbHAxnuxi7o42OrZgF/ZTNcsZj6nRKyUmkhLFq8CHItp/fy6hQZuZmP/n3yZ9VBUbp4zz/mX8hmYPw==",
+ "dependencies": {
+ "core-util-is": "~1.0.0",
+ "inherits": "~2.0.3",
+ "isarray": "~1.0.0",
+ "process-nextick-args": "~2.0.0",
+ "safe-buffer": "~5.1.1",
+ "string_decoder": "~1.1.1",
+ "util-deprecate": "~1.0.1"
+ }
+ },
+ "node_modules/readable-stream/node_modules/safe-buffer": {
+ "version": "5.1.2",
+ "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz",
+ "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g=="
+ },
+ "node_modules/request-progress": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/request-progress/-/request-progress-3.0.0.tgz",
+ "integrity": "sha1-TKdUCBx/7GP1BeT6qCWqBs1mnb4=",
+ "dependencies": {
+ "throttleit": "^1.0.0"
+ }
+ },
+ "node_modules/restore-cursor": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/restore-cursor/-/restore-cursor-1.0.1.tgz",
+ "integrity": "sha1-NGYfRohjJ/7SmRR5FSJS35LapUE=",
+ "dependencies": {
+ "exit-hook": "^1.0.0",
+ "onetime": "^1.0.0"
+ },
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/rimraf": {
+ "version": "3.0.2",
+ "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz",
+ "integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==",
+ "dependencies": {
+ "glob": "^7.1.3"
+ },
+ "bin": {
+ "rimraf": "bin.js"
+ }
+ },
+ "node_modules/rxjs": {
+ "version": "6.6.3",
+ "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-6.6.3.tgz",
+ "integrity": "sha512-trsQc+xYYXZ3urjOiJOuCOa5N3jAZ3eiSpQB5hIT8zGlL2QfnHLJ2r7GMkBGuIausdJN1OneaI6gQlsqNHHmZQ==",
+ "dependencies": {
+ "tslib": "^1.9.0"
+ },
+ "engines": {
+ "npm": ">=2.0.0"
+ }
+ },
+ "node_modules/safe-buffer": {
+ "version": "5.2.1",
+ "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz",
+ "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ=="
+ },
+ "node_modules/safer-buffer": {
+ "version": "2.1.2",
+ "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz",
+ "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg=="
+ },
+ "node_modules/shebang-command": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz",
+ "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==",
+ "dependencies": {
+ "shebang-regex": "^3.0.0"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/shebang-regex": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz",
+ "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==",
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/signal-exit": {
+ "version": "3.0.3",
+ "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.3.tgz",
+ "integrity": "sha512-VUJ49FC8U1OxwZLxIbTTrDvLnf/6TDgxZcK8wxR8zs13xpx7xbG60ndBlhNrFi2EMuFRoeDoJO7wthSLq42EjA=="
+ },
+ "node_modules/slice-ansi": {
+ "version": "0.0.4",
+ "resolved": "https://registry.npmjs.org/slice-ansi/-/slice-ansi-0.0.4.tgz",
+ "integrity": "sha1-7b+JA/ZvfOL46v1s7tZeJkyDGzU=",
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/sshpk": {
+ "version": "1.16.1",
+ "resolved": "https://registry.npmjs.org/sshpk/-/sshpk-1.16.1.tgz",
+ "integrity": "sha512-HXXqVUq7+pcKeLqqZj6mHFUMvXtOJt1uoUx09pFW6011inTMxqI8BA8PM95myrIyyKwdnzjdFjLiE6KBPVtJIg==",
+ "dependencies": {
+ "asn1": "~0.2.3",
+ "assert-plus": "^1.0.0",
+ "bcrypt-pbkdf": "^1.0.0",
+ "dashdash": "^1.12.0",
+ "ecc-jsbn": "~0.1.1",
+ "getpass": "^0.1.1",
+ "jsbn": "~0.1.0",
+ "safer-buffer": "^2.0.2",
+ "tweetnacl": "~0.14.0"
+ },
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/string_decoder": {
+ "version": "1.1.1",
+ "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz",
+ "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==",
+ "dependencies": {
+ "safe-buffer": "~5.1.0"
+ }
+ },
+ "node_modules/string_decoder/node_modules/safe-buffer": {
+ "version": "5.1.2",
+ "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz",
+ "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g=="
+ },
+ "node_modules/string-width": {
+ "version": "4.2.0",
+ "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.0.tgz",
+ "integrity": "sha512-zUz5JD+tgqtuDjMhwIg5uFVV3dtqZ9yQJlZVfq4I01/K5Paj5UHj7VyrQOJvzawSVlKpObApbfD0Ed6yJc+1eg==",
+ "dependencies": {
+ "emoji-regex": "^8.0.0",
+ "is-fullwidth-code-point": "^3.0.0",
+ "strip-ansi": "^6.0.0"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/string-width/node_modules/ansi-regex": {
+ "version": "5.0.0",
+ "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.0.tgz",
+ "integrity": "sha512-bY6fj56OUQ0hU1KjFNDQuJFezqKdrAyFdIevADiqrWHwSlbmBNMHp5ak2f40Pm8JTFyM2mqxkG6ngkHO11f/lg==",
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/string-width/node_modules/strip-ansi": {
+ "version": "6.0.0",
+ "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.0.tgz",
+ "integrity": "sha512-AuvKTrTfQNYNIctbR1K/YGTR1756GycPsg7b9bdV9Duqur4gv6aKqHXah67Z8ImS7WEz5QVcOtlfW2rZEugt6w==",
+ "dependencies": {
+ "ansi-regex": "^5.0.0"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/strip-ansi": {
+ "version": "3.0.1",
+ "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz",
+ "integrity": "sha1-ajhfuIU9lS1f8F0Oiq+UJ43GPc8=",
+ "dependencies": {
+ "ansi-regex": "^2.0.0"
+ },
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/strip-final-newline": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/strip-final-newline/-/strip-final-newline-2.0.0.tgz",
+ "integrity": "sha512-BrpvfNAE3dcvq7ll3xVumzjKjZQ5tI1sEUIKr3Uoks0XUl45St3FlatVqef9prk4jRDzhW6WZg+3bk93y6pLjA==",
+ "engines": {
+ "node": ">=6"
+ }
+ },
+ "node_modules/supports-color": {
+ "version": "7.2.0",
+ "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz",
+ "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==",
+ "dependencies": {
+ "has-flag": "^4.0.0"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/symbol-observable": {
+ "version": "1.2.0",
+ "resolved": "https://registry.npmjs.org/symbol-observable/-/symbol-observable-1.2.0.tgz",
+ "integrity": "sha512-e900nM8RRtGhlV36KGEU9k65K3mPb1WV70OdjfxlG2EAuM1noi/E/BaW/uMhL7bPEssK8QV57vN3esixjUvcXQ==",
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/throttleit": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/throttleit/-/throttleit-1.0.0.tgz",
+ "integrity": "sha1-nnhYNtr0Z0MUWlmEtiaNgoUorGw="
+ },
+ "node_modules/tmp": {
+ "version": "0.2.1",
+ "resolved": "https://registry.npmjs.org/tmp/-/tmp-0.2.1.tgz",
+ "integrity": "sha512-76SUhtfqR2Ijn+xllcI5P1oyannHNHByD80W1q447gU3mp9G9PSpGdWmjUOHRDPiHYacIk66W7ubDTuPF3BEtQ==",
+ "dependencies": {
+ "rimraf": "^3.0.0"
+ },
+ "engines": {
+ "node": ">=8.17.0"
+ }
+ },
+ "node_modules/tough-cookie": {
+ "version": "2.5.0",
+ "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-2.5.0.tgz",
+ "integrity": "sha512-nlLsUzgm1kfLXSXfRZMc1KLAugd4hqJHDTvc2hDIwS3mZAfMEuMbc03SujMF+GEcpaX/qboeycw6iO8JwVv2+g==",
+ "dependencies": {
+ "psl": "^1.1.28",
+ "punycode": "^2.1.1"
+ },
+ "engines": {
+ "node": ">=0.8"
+ }
+ },
+ "node_modules/tslib": {
+ "version": "1.14.1",
+ "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz",
+ "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg=="
+ },
+ "node_modules/tunnel-agent": {
+ "version": "0.6.0",
+ "resolved": "https://registry.npmjs.org/tunnel-agent/-/tunnel-agent-0.6.0.tgz",
+ "integrity": "sha1-J6XeoGs2sEoKmWZ3SykIaPD8QP0=",
+ "dependencies": {
+ "safe-buffer": "^5.0.1"
+ },
+ "engines": {
+ "node": "*"
+ }
+ },
+ "node_modules/tweetnacl": {
+ "version": "0.14.5",
+ "resolved": "https://registry.npmjs.org/tweetnacl/-/tweetnacl-0.14.5.tgz",
+ "integrity": "sha1-WuaBd/GS1EViadEIr6k/+HQ/T2Q="
+ },
+ "node_modules/typedarray": {
+ "version": "0.0.6",
+ "resolved": "https://registry.npmjs.org/typedarray/-/typedarray-0.0.6.tgz",
+ "integrity": "sha1-hnrHTjhkGHsdPUfZlqeOxciDB3c="
+ },
+ "node_modules/universalify": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/universalify/-/universalify-2.0.0.tgz",
+ "integrity": "sha512-hAZsKq7Yy11Zu1DE0OzWjw7nnLZmJZYTDZZyEFHZdUhV8FkH5MCfoU1XMaxXovpyW5nq5scPqq0ZDP9Zyl04oQ==",
+ "engines": {
+ "node": ">= 10.0.0"
+ }
+ },
+ "node_modules/untildify": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/untildify/-/untildify-4.0.0.tgz",
+ "integrity": "sha512-KK8xQ1mkzZeg9inewmFVDNkg3l5LUhoq9kN6iWYB/CC9YMG8HA+c1Q8HwDe6dEX7kErrEVNVBO3fWsVq5iDgtw==",
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/uri-js": {
+ "version": "4.4.0",
+ "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.0.tgz",
+ "integrity": "sha512-B0yRTzYdUCCn9n+F4+Gh4yIDtMQcaJsmYBDsTSG8g/OejKBodLQ2IHfN3bM7jUsRXndopT7OIXWdYqc1fjmV6g==",
+ "dependencies": {
+ "punycode": "^2.1.0"
+ }
+ },
+ "node_modules/url": {
+ "version": "0.11.0",
+ "resolved": "https://registry.npmjs.org/url/-/url-0.11.0.tgz",
+ "integrity": "sha1-ODjpfPxgUh63PFJajlW/3Z4uKPE=",
+ "dependencies": {
+ "punycode": "1.3.2",
+ "querystring": "0.2.0"
+ }
+ },
+ "node_modules/url/node_modules/punycode": {
+ "version": "1.3.2",
+ "resolved": "https://registry.npmjs.org/punycode/-/punycode-1.3.2.tgz",
+ "integrity": "sha1-llOgNvt8HuQjQvIyXM7v6jkmxI0="
+ },
+ "node_modules/util-deprecate": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz",
+ "integrity": "sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8="
+ },
+ "node_modules/uuid": {
+ "version": "3.4.0",
+ "resolved": "https://registry.npmjs.org/uuid/-/uuid-3.4.0.tgz",
+ "integrity": "sha512-HjSDRw6gZE5JMggctHBcjVak08+KEVhSIiDzFnT9S9aegmp85S/bReBVTb4QTFaRNptJ9kuYaNhnbNEOkbKb/A==",
+ "bin": {
+ "uuid": "bin/uuid"
+ }
+ },
+ "node_modules/verror": {
+ "version": "1.10.0",
+ "resolved": "https://registry.npmjs.org/verror/-/verror-1.10.0.tgz",
+ "integrity": "sha1-OhBcoXBTr1XW4nDB+CiGguGNpAA=",
+ "engines": [
+ "node >=0.6.0"
+ ],
+ "dependencies": {
+ "assert-plus": "^1.0.0",
+ "core-util-is": "1.0.2",
+ "extsprintf": "^1.2.0"
+ }
+ },
+ "node_modules/wait-on": {
+ "version": "5.3.0",
+ "resolved": "https://registry.npmjs.org/wait-on/-/wait-on-5.3.0.tgz",
+ "integrity": "sha512-DwrHrnTK+/0QFaB9a8Ol5Lna3k7WvUR4jzSKmz0YaPBpuN2sACyiPVKVfj6ejnjcajAcvn3wlbTyMIn9AZouOg==",
+ "dependencies": {
+ "axios": "^0.21.1",
+ "joi": "^17.3.0",
+ "lodash": "^4.17.21",
+ "minimist": "^1.2.5",
+ "rxjs": "^6.6.3"
+ },
+ "bin": {
+ "wait-on": "bin/wait-on"
+ },
+ "engines": {
+ "node": ">=8.9.0"
+ }
+ },
+ "node_modules/which": {
+ "version": "2.0.2",
+ "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz",
+ "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==",
+ "dependencies": {
+ "isexe": "^2.0.0"
+ },
+ "bin": {
+ "node-which": "bin/node-which"
+ },
+ "engines": {
+ "node": ">= 8"
+ }
+ },
+ "node_modules/wrap-ansi": {
+ "version": "3.0.1",
+ "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-3.0.1.tgz",
+ "integrity": "sha1-KIoE2H7aXChuBg3+jxNc6NAH+Lo=",
+ "dependencies": {
+ "string-width": "^2.1.1",
+ "strip-ansi": "^4.0.0"
+ },
+ "engines": {
+ "node": ">=4"
+ }
+ },
+ "node_modules/wrap-ansi/node_modules/ansi-regex": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-3.0.0.tgz",
+ "integrity": "sha1-7QMXwyIGT3lGbAKWa922Bas32Zg=",
+ "engines": {
+ "node": ">=4"
+ }
+ },
+ "node_modules/wrap-ansi/node_modules/is-fullwidth-code-point": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz",
+ "integrity": "sha1-o7MKXE8ZkYMWeqq5O+764937ZU8=",
+ "engines": {
+ "node": ">=4"
+ }
+ },
+ "node_modules/wrap-ansi/node_modules/string-width": {
+ "version": "2.1.1",
+ "resolved": "https://registry.npmjs.org/string-width/-/string-width-2.1.1.tgz",
+ "integrity": "sha512-nOqH59deCq9SRHlxq1Aw85Jnt4w6KvLKqWVik6oA9ZklXLNIOlqg4F2yrT1MVaTjAqvVwdfeZ7w7aCvJD7ugkw==",
+ "dependencies": {
+ "is-fullwidth-code-point": "^2.0.0",
+ "strip-ansi": "^4.0.0"
+ },
+ "engines": {
+ "node": ">=4"
+ }
+ },
+ "node_modules/wrap-ansi/node_modules/strip-ansi": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-4.0.0.tgz",
+ "integrity": "sha1-qEeQIusaw2iocTibY1JixQXuNo8=",
+ "dependencies": {
+ "ansi-regex": "^3.0.0"
+ },
+ "engines": {
+ "node": ">=4"
+ }
+ },
+ "node_modules/wrappy": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz",
+ "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8="
+ },
+ "node_modules/yauzl": {
+ "version": "2.10.0",
+ "resolved": "https://registry.npmjs.org/yauzl/-/yauzl-2.10.0.tgz",
+ "integrity": "sha1-x+sXyT4RLLEIb6bY5R+wZnt5pfk=",
+ "dependencies": {
+ "buffer-crc32": "~0.2.3",
+ "fd-slicer": "~1.1.0"
+ }
+ }
+ },
"dependencies": {
"@cypress/listr-verbose-renderer": {
"version": "0.4.1",
@@ -480,6 +2531,12 @@
}
}
},
+ "cypress-wait-until": {
+ "version": "1.7.1",
+ "resolved": "https://registry.npmjs.org/cypress-wait-until/-/cypress-wait-until-1.7.1.tgz",
+ "integrity": "sha512-8DL5IsBTbAxBjfYgCzdbohPq/bY+IKc63fxtso1C8RWhLnQkZbVESyaclNr76jyxfId6uyzX8+Xnt0ZwaXNtkA==",
+ "dev": true
+ },
"dashdash": {
"version": "1.14.1",
"resolved": "https://registry.npmjs.org/dashdash/-/dashdash-1.14.1.tgz",
@@ -1411,6 +3468,21 @@
"tweetnacl": "~0.14.0"
}
},
+ "string_decoder": {
+ "version": "1.1.1",
+ "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz",
+ "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==",
+ "requires": {
+ "safe-buffer": "~5.1.0"
+ },
+ "dependencies": {
+ "safe-buffer": {
+ "version": "5.1.2",
+ "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz",
+ "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g=="
+ }
+ }
+ },
"string-width": {
"version": "4.2.0",
"resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.0.tgz",
@@ -1436,21 +3508,6 @@
}
}
},
- "string_decoder": {
- "version": "1.1.1",
- "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz",
- "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==",
- "requires": {
- "safe-buffer": "~5.1.0"
- },
- "dependencies": {
- "safe-buffer": {
- "version": "5.1.2",
- "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz",
- "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g=="
- }
- }
- },
"strip-ansi": {
"version": "3.0.1",
"resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz",
diff --git a/package.json b/package.json
index 3be9f3162..f529e3b5b 100644
--- a/package.json
+++ b/package.json
@@ -12,7 +12,9 @@
"rimraf": "^3.0.2",
"wait-on": "^5.3.0"
},
- "devDependencies": {},
+ "devDependencies": {
+ "cypress-wait-until": "^1.7.1"
+ },
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1"
},
diff --git a/requirements.in b/requirements.in
index a747e0ccd..4cb3a8ee9 100644
--- a/requirements.in
+++ b/requirements.in
@@ -16,7 +16,7 @@ requests==2.25.0
pysocks==1.7.1
six==1.12.0
stem==1.8.0
-embit==0.4.9
+embit==0.4.10
psutil==5.7.3
pyopenssl==20.0.1
flask_wtf==0.14.3
diff --git a/requirements.txt b/requirements.txt
index 8011d0e75..e4e5a6fc3 100644
--- a/requirements.txt
+++ b/requirements.txt
@@ -108,8 +108,8 @@ ecdsa==0.17.0 \
--hash=sha256:5cf31d5b33743abe0dfc28999036c849a69d548f994b535e527ee3cb7f3ef676 \
--hash=sha256:b9f500bb439e4153d0330610f5d26baaf18d17b8ced1bc54410d189385ea68aa \
# via bitbox02, hwi
-embit==0.4.9 \
- --hash=sha256:992332bd89af6e2d027e26fe437eb14aa33997db08c882c49064d49c3e6f4ab9 \
+embit==0.4.10 \
+ --hash=sha256:f6484bc495b45da27f3eb7fbe21a24c00cd72c0ab83c6e195660cf17db5cb5e2 \
# via -r requirements.in
flask-babel==2.0.0 \
--hash=sha256:e6820a052a8d344e178cdd36dd4bb8aea09b4bda3d5f9fa9f008df2c7f2f5468 \
diff --git a/src/cryptoadvance/specter/addresslist.py b/src/cryptoadvance/specter/addresslist.py
index 7fb4d56fc..06a03b905 100644
--- a/src/cryptoadvance/specter/addresslist.py
+++ b/src/cryptoadvance/specter/addresslist.py
@@ -53,11 +53,17 @@ class Address(dict):
def is_external(self):
return self.index is None
+ @property
+ def is_mine(self):
+ return not self.is_external
+
@property
def is_receiving(self):
- # change can be True, False or None
- # None means it's external
- return not self.is_external and not self.change
+ return self.is_mine and not self.change
+
+ @property
+ def is_change(self):
+ return self.is_mine and self.change
@property
def index(self):
@@ -206,7 +212,11 @@ class AddressList(dict):
return max(
0,
0,
- *[addr.index for addr in self.values() if addr.change == change],
+ *[
+ addr.index or 0
+ for addr in self.values()
+ if addr.is_mine and addr.change == change
+ ],
)
def max_used_index(self, change=False):
@@ -214,9 +224,9 @@ class AddressList(dict):
-1,
-1,
*[
- addr.index
+ addr.index or -1
for addr in self.values()
- if addr.used and addr.change == change
+ if addr.is_mine and addr.used and addr.change == change
],
)
diff --git a/src/cryptoadvance/specter/api/rest/resource_psbt.py b/src/cryptoadvance/specter/api/rest/resource_psbt.py
index cf171e058..19dd01a9e 100644
--- a/src/cryptoadvance/specter/api/rest/resource_psbt.py
+++ b/src/cryptoadvance/specter/api/rest/resource_psbt.py
@@ -25,8 +25,8 @@ class ResourcePsbt(SecureResource):
wallet: Wallet = app.specter.user_manager.get_user(
user
).wallet_manager.get_by_alias(wallet_alias)
- pending_psbts = wallet.pending_psbts
- return {"result": pending_psbts or []}
+ pending_psbts = wallet.pending_psbts_dict()
+ return {"result": pending_psbts or {}}
def post(self, wallet_alias):
user = auth.current_user()
@@ -37,5 +37,5 @@ class ResourcePsbt(SecureResource):
psbt_creator = PsbtCreator(
app.specter, wallet, "json", request_json=request.json
)
- psbt_creator.create_psbt(wallet)
- return {"result": psbt_creator.psbt}
+ psbt = psbt_creator.create_psbt(wallet)
+ return {"result": psbt}
diff --git a/src/cryptoadvance/specter/devices/bitcoin_core.py b/src/cryptoadvance/specter/devices/bitcoin_core.py
index b2660f591..2efaec160 100644
--- a/src/cryptoadvance/specter/devices/bitcoin_core.py
+++ b/src/cryptoadvance/specter/devices/bitcoin_core.py
@@ -24,6 +24,9 @@ class BitcoinCore(Device):
hot_wallet = True
taproot_support = True
+ # default sighash to use
+ SIGHASH = "ALL"
+
def __init__(self, *args, **kwargs):
self._use_descriptors = None
super().__init__(*args, **kwargs)
@@ -212,7 +215,7 @@ class BitcoinCore(Device):
)
if file_password:
rpc.walletpassphrase(file_password, 60)
- signed_psbt = rpc.walletprocesspsbt(base64_psbt)
+ signed_psbt = rpc.walletprocesspsbt(base64_psbt, True, self.SIGHASH)
if base64_psbt == signed_psbt["psbt"]:
raise Exception(
"Make sure you have entered the wallet file password correctly. (If your wallet is not encrypted submit empty password)"
diff --git a/src/cryptoadvance/specter/devices/elements_core.py b/src/cryptoadvance/specter/devices/elements_core.py
index 64c10558b..0e38e397d 100644
--- a/src/cryptoadvance/specter/devices/elements_core.py
+++ b/src/cryptoadvance/specter/devices/elements_core.py
@@ -16,6 +16,9 @@ class ElementsCore(BitcoinCore):
bitcoin_core_support = False
liquid_support = True
+ # default sighash to use
+ SIGHASH = "ALL|RANGEPROOF"
+
def add_hot_wallet_keys(
self,
mnemonic,
diff --git a/src/cryptoadvance/specter/devices/generic.py b/src/cryptoadvance/specter/devices/generic.py
index 8491779eb..8ee617525 100644
--- a/src/cryptoadvance/specter/devices/generic.py
+++ b/src/cryptoadvance/specter/devices/generic.py
@@ -1,6 +1,5 @@
from . import DeviceTypes
from ..device import Device
-from ..liquid.util.pset import to_canonical_pset
class GenericDevice(Device):
@@ -13,7 +12,6 @@ class GenericDevice(Device):
taproot_support = True
def create_psbts(self, base64_psbt, wallet):
- base64_psbt = to_canonical_pset(base64_psbt)
# in QR codes keep only xpubs
qr_psbt = wallet.fill_psbt(base64_psbt, non_witness=False, xpubs=True)
# in SD card put as much as possible
diff --git a/src/cryptoadvance/specter/helpers.py b/src/cryptoadvance/specter/helpers.py
index edfdab5ef..bfc41d367 100644
--- a/src/cryptoadvance/specter/helpers.py
+++ b/src/cryptoadvance/specter/helpers.py
@@ -32,10 +32,7 @@ EMOJIS = "😀😃😄😁😆😅😂🤣😊😇🙂🙃😉😌😍😘😗
def get_asset_label(asset, known_assets={}):
# TODO: lookup in the registry
- if (
- asset is None
- or asset == "0000000000000000000000000000000000000000000000000000000000000000"
- ):
+ if asset in [None, "00" * 32, "ff" * 32]:
return "???"
if asset == "bitcoin":
return "LBTC"
@@ -345,4 +342,4 @@ def get_address_from_dict(data_dict):
addr = data_dict.get("address")
if addr and addr != "Fee":
return addr
- raise RuntimeError("Missing address info in object")
+ raise RuntimeError(f"Missing address info in object {data_dict}")
diff --git a/src/cryptoadvance/specter/key.py b/src/cryptoadvance/specter/key.py
index f8e3a355b..2f24c06ae 100644
--- a/src/cryptoadvance/specter/key.py
+++ b/src/cryptoadvance/specter/key.py
@@ -2,7 +2,7 @@ from collections import OrderedDict
from binascii import hexlify
from embit import base58
from .util.xpub import get_xpub_fingerprint
-
+from embit.descriptor import Key as DescriptorKey
purposes = OrderedDict(
{
diff --git a/src/cryptoadvance/specter/liquid/addresslist.py b/src/cryptoadvance/specter/liquid/addresslist.py
index e78971924..7c37c958e 100644
--- a/src/cryptoadvance/specter/liquid/addresslist.py
+++ b/src/cryptoadvance/specter/liquid/addresslist.py
@@ -26,7 +26,7 @@ class LAddressList(AddressList):
self._update_scripts()
def _update_scripts(self):
- for addr in self:
+ for addr in list(self.keys()):
sc, _ = addr_decode(addr)
if sc and sc not in self._scripts:
self._scripts[sc] = super().__getitem__(addr)
@@ -39,9 +39,12 @@ class LAddressList(AddressList):
def __contains__(self, addr):
"""finds address by confidential or unconfidential address by converting to scriptpubkey"""
- sc, _ = addr_decode(addr)
- if sc and self._scripts.__contains__(sc):
- return True
+ try: # can fail if addr is "Fee", "Dummy" or hex-scriptpubkey
+ sc, _ = addr_decode(addr)
+ if sc and self._scripts.__contains__(sc):
+ return True
+ except:
+ pass
return super().__contains__(addr)
def __getitem__(self, addr):
diff --git a/src/cryptoadvance/specter/liquid/rpc.py b/src/cryptoadvance/specter/liquid/rpc.py
index 5a18cb86a..28eac53c2 100644
--- a/src/cryptoadvance/specter/liquid/rpc.py
+++ b/src/cryptoadvance/specter/liquid/rpc.py
@@ -13,7 +13,6 @@ from embit.liquid import slip77
from embit.psbt import read_string
import copy
from io import BytesIO
-
from .util.pset import to_canonical_pset
import logging
@@ -179,6 +178,23 @@ class LiquidRPC(BitcoinRPC):
inputs, outputs, locktime, options, *args, **kwargs
)
psbt = res.get("psbt", None)
+
+ # remove zero-output (bug in Elements)
+ # TODO: remove after release
+ if psbt:
+ try:
+ tx = PSET.from_string(psbt)
+ # check if there are zero outputs
+ has_zero = len([out for out in tx.outputs if out.value == 0]) > 0
+ has_blinded = any([out.blinding_pubkey for out in tx.outputs])
+ logger.error(has_zer, has_blinded)
+ if has_blinded and has_zero:
+ tx.outputs = [out for out in tx.outputs if out.value > 0]
+ psbt = str(tx)
+ res["psbt"] = psbt
+ except:
+ pass
+
# replace change addresses from the transactions if we can
if change_addresses and psbt:
try:
@@ -230,6 +246,9 @@ class LiquidRPC(BitcoinRPC):
# check that change is also blinded - fixes a bug in pset branch
tx = PSET.from_string(psbt)
changepos = res.get("changepos", None)
+ # no change output
+ if changepos < 0:
+ changepos = None
# generate all blinding stuff ourselves in deterministic way
tx.unblind(
@@ -300,9 +319,7 @@ class LiquidRPC(BitcoinRPC):
tx.xpubs.update(t2.xpubs)
tx.unknown.update(t2.unknown)
- for i in range(len(tx.inputs)):
- inp1 = tx.inputs[i]
- inp2 = t2.inputs[i]
+ for inp1, inp2 in zip(tx.inputs, t2.inputs):
inp1.value = inp1.value or inp2.value
inp1.value_blinding_factor = (
inp1.value_blinding_factor or inp2.value_blinding_factor
@@ -327,9 +344,7 @@ class LiquidRPC(BitcoinRPC):
inp1.unknown.update(inp2.unknown)
inp1.range_proof = inp1.range_proof or inp2.range_proof
- for i in range(len(tx.outputs)):
- out1 = tx.outputs[i]
- out2 = t2.outputs[i]
+ for out1, out2 in zip(tx.outputs, t2.outputs):
out1.value_commitment = out1.value_commitment or out2.value_commitment
out1.value_blinding_factor = (
out1.value_blinding_factor or out2.value_blinding_factor
@@ -353,66 +368,6 @@ class LiquidRPC(BitcoinRPC):
out1.unknown.update(out2.unknown)
return str(tx)
- def decodepsbt(self, b64psbt, *args, **kwargs):
- tx = PSET.from_string(b64psbt)
- # pre-processing of the transaction
- # so Elements Core doesn't complain
- inputs = [
- (inp.value or inp.utxo.value, inp.asset or inp.utxo.asset)
- for inp in tx.inputs
- ]
- for inp in tx.inputs:
- inp.value = None
- inp.asset = None
- inp.value_blinding_factor = None
- inp.asset_blinding_factor = None
-
- for out in tx.outputs:
- if out.asset and out.value:
- # out.asset = None
- out.asset_blinding_factor = None
- # out.value = None
- out.value_blinding_factor = None
- out.asset_commitment = None
- out.value_commitment = None
- out.range_proof = None
- out.surjection_proof = None
- out.ecdh_pubkey = None
- b64psbt = str(tx)
-
- decoded = super().__getattr__("decodepsbt")(b64psbt, *args, **kwargs)
- # pset branch - no fee and global tx fields...
- if "fees" in decoded and "bitcoin" in decoded["fees"]:
- decoded["fee"] = decoded["fees"]["bitcoin"]
- if "tx" not in decoded or "fee" not in decoded:
- pset = PSET.from_string(b64psbt)
- if "tx" not in decoded:
- decoded["tx"] = self.decoderawtransaction(str(pset.tx))
- if "fee" not in decoded:
- decoded["fee"] = pset.fee() * 1e-8
- for out in decoded["outputs"]:
- if "value" not in out:
- out["value"] = -1
- for out in decoded["tx"]["vout"]:
- if "value" not in out:
- out["value"] = -1
- for i, (v, a) in enumerate(inputs):
- inp = decoded["tx"]["vin"][i] # old psbt
- inp2 = decoded["inputs"][i] # new psbt
- if "utxo_rangeproof" in inp2:
- inp2.pop("utxo_rangeproof")
- a = bytes(reversed(a[-32:])).hex()
- v = round(v * 1e-8, 8)
- if "value" not in inp:
- inp["value"] = v
- if "asset" not in inp:
- inp["asset"] = a
- if "value" not in inp2:
- inp2["value"] = v
- if "asset" not in inp2:
- inp2["asset"] = a
- return decoded
-
def decoderawtransaction(self, tx):
blinded = super().__getattr__("decoderawtransaction")(tx)
try:
diff --git a/src/cryptoadvance/specter/liquid/txlist.py b/src/cryptoadvance/specter/liquid/txlist.py
index 683a7d199..b6dd6c580 100644
--- a/src/cryptoadvance/specter/liquid/txlist.py
+++ b/src/cryptoadvance/specter/liquid/txlist.py
@@ -1,5 +1,12 @@
from ..txlist import *
-from embit.liquid.transaction import LTransaction
+from embit.liquid.transaction import LTransaction, TxOutWitness, unblind
+from embit.liquid.pset import PSET
+from embit.liquid import slip77
+from embit.hashes import tagged_hash
+from embit.ec import PrivateKey
+from .util.pset import SpecterLTx, get_value, get_asset, SpecterPSET
+from io import BytesIO
+from embit.psbt import read_string
class LTxItem(TxItem):
@@ -33,6 +40,112 @@ class LTxItem(TxItem):
bool,
]
+ def __init__(self, *args, **kwargs):
+ super().__init__(*args, **kwargs)
+ # unblind what's blinded and remove
+ # will fill automatically
+ self.vsize
+ self._unblind()
+
+ def _unblind(self):
+ if not self.descriptor.is_blinded:
+ return
+
+ b = self.tx
+
+ mbpk = self.descriptor.blinding_key.key
+ net = self.network
+
+ values = [0 for out in b.vout]
+ assets = [b"\xFF" * 32 for out in b.vout]
+ datas = []
+ # search for datas encoded in rangeproofs
+ for i, out in enumerate(b.vout):
+ # unblinded
+ if isinstance(out.value, int):
+ values[i] = out.value
+ assets[i] = out.asset
+ continue
+
+ pk = slip77.blinding_key(mbpk, out.script_pubkey)
+ try:
+ res = out.unblind(pk.secret, message_length=1000)
+ value, asset, vbf, abf, extra, *_ = res
+ if len(extra.rstrip(b"\x00")) > 0:
+ datas.append(extra)
+ values[i] = value
+ assets[i] = asset
+ except Exception as e:
+ logger.warn(e) # TODO: remove, it's ok
+ pass
+
+ # to calculate blinding seed
+ tx = PSET(b)
+ seed = tagged_hash("liquid/blinding_seed", mbpk.secret)
+ txseed = tx.txseed(seed)
+ pubkeys = {}
+
+ for extra in datas:
+ s = BytesIO(extra)
+ while True:
+ k = read_string(s)
+ if len(k) == 0:
+ break
+ v = read_string(s)
+ if k[0] == 1 and len(k) == 5:
+ idx = int.from_bytes(k[1:], "little")
+ pubkeys[idx] = v
+ elif k == b"\x01\x00":
+ txseed = v
+
+ for i, out in enumerate(b.vout):
+ if out.witness.range_proof.is_empty:
+ continue
+ if i in pubkeys and len(pubkeys[i]) in [33, 65]:
+ nonce = tagged_hash(
+ "liquid/range_proof", txseed + i.to_bytes(4, "little")
+ )
+ if out.ecdh_pubkey == PrivateKey(nonce).sec():
+ try:
+ res = unblind(
+ pubkeys[i],
+ nonce,
+ out.witness.range_proof.data,
+ out.value,
+ out.asset,
+ out.script_pubkey,
+ )
+ value, asset, vbf, abf, extra, min_value, max_value = res
+ assets[i] = asset
+ values[i] = value
+ except Exception as e:
+ logger.warn(f"Failed at unblinding output {i}: {e}")
+ else:
+ logger.warn(f"Failed at unblinding: {e}")
+
+ for i, out in enumerate(b.vout):
+ out.asset = assets[i]
+ out.value = values[i]
+ out.witness = TxOutWitness()
+
+ @property
+ def vsize(self):
+ if self.get("vsize"):
+ return self["vsize"]
+ tx = self.tx
+ txsize = len(tx.serialize())
+ # tx size - flag - marker - witness
+ non_witness_size = (
+ txsize
+ - 2
+ - sum([len(inp.witness.serialize()) for inp in tx.vin])
+ - sum([len(out.witness.serialize()) for out in tx.vout])
+ )
+ witness_size = txsize - non_witness_size
+ weight = non_witness_size * 4 + witness_size
+ vsize = math.ceil(weight / 4)
+ return vsize
+
def __dict__(self):
return {
"txid": self["txid"],
@@ -52,102 +165,32 @@ class LTxItem(TxItem):
class LTxList(TxList):
ItemCls = LTxItem
+ PSBTCls = SpecterPSET
counter = 0
- def fill_missing(self, tx):
- raw_tx = self.decoderawtransaction(tx.hex)
- tx["vsize"] = raw_tx["vsize"]
-
- category = ""
- addresses = []
- amounts = {}
- assets = {}
- inputs_mine_count = 0
- for vin in raw_tx["vin"]:
- # coinbase tx
- if (
- vin["txid"]
- == "0000000000000000000000000000000000000000000000000000000000000000"
- ):
- category = "generate"
- break
- if vin["txid"] in self:
- try:
- address = get_address_from_dict(
- self.decoderawtransaction(self[vin["txid"]].hex)["vout"][
- vin["vout"]
- ]
- )
- address_info = self._addresses.get(address, None)
- if address_info and not address_info.is_external:
- inputs_mine_count += 1
- except Exception as e:
- logger.error(e)
- continue
-
- outputs_mine_count = 0
- for out in raw_tx["vout"]:
- try:
- address = get_address_from_dict(out)
- except Exception as e:
- # couldn't get address...
- logger.error(e)
- continue
- address_info = self._addresses.get(address)
- if address_info and not address_info.is_external:
- outputs_mine_count += 1
- addresses.append(address)
- amounts[address] = out.get("value", 0)
- assets[address] = out.get("asset", "Unknown")
-
- if inputs_mine_count:
- if outputs_mine_count == len(raw_tx["vout"]):
- category = "selftransfer"
- # remove change addresses from the dest list
- addresses2 = [
- address
- for address in addresses
- if self._addresses.get(address, None)
- and not self._addresses[address].change
- ]
- # use new list only if it's not empty
- if addresses2:
- addresses = addresses2
- else:
- category = "send"
- addresses = [
- address
- for address in addresses
- if not self._addresses.get(address, None)
- or self._addresses[address].is_external
- ]
- else:
- if not category:
- category = "receive"
- addresses = [
- address
- for address in addresses
- if self._addresses.get(address, None)
- and not self._addresses[address].is_external
- ]
-
- amounts = [amounts[address] for address in addresses]
- assets = [assets[address] for address in addresses]
+ def _get_psbt(self, raw_tx):
+ psbt = self.PSBTCls.from_transaction(raw_tx, self.descriptor, self.network)
+ psbt.psbt.version = 2
+ # fill derivation paths etc
+ updated = self.rpc.walletprocesspsbt(str(psbt), False).get("psbt", None)
+ if updated:
+ psbt.update(updated)
+ return psbt
+ def _update_destinations(self, tx, outs):
+ # remove dummy and fee
+ outs = [out for out in outs if out.get("address") not in ["Fee", "DUMMY"]]
+ # process the rest
+ addresses = [out.get("address", "Unknown") for out in outs]
+ amounts = [out.get("float_amount", 0) for out in outs]
+ assets = [out.get("asset", "ff" * 32) for out in outs]
if len(addresses) == 1:
addresses = addresses[0]
amounts = amounts[0]
assets = assets[0]
-
- tx["category"] = category
tx["address"] = addresses
tx["amount"] = amounts
tx["asset"] = assets
- if not addresses:
- tx["ismine"] = False
- else:
- tx["ismine"] = True
- def decoderawtransaction(self, txhex):
- # TODO: using rpc for now, can be moved to utils
- return self.rpc.decoderawtransaction(txhex)
+ def decoderawtransaction(self, tx: Union[LTransaction, str, bytes]):
+ return SpecterLTx(self, tx).to_dict()
diff --git a/src/cryptoadvance/specter/liquid/util/pset.py b/src/cryptoadvance/specter/liquid/util/pset.py
index bd5f941f0..44b697e72 100644
--- a/src/cryptoadvance/specter/liquid/util/pset.py
+++ b/src/cryptoadvance/specter/liquid/util/pset.py
@@ -1,7 +1,15 @@
-from embit.liquid.pset import PSET
+from embit.liquid.pset import PSET, LInputScope, LOutputScope
+from embit.liquid.transaction import LTransaction, LTransactionOutput
+from embit.liquid.networks import get_network
+from embit.liquid.addresses import address as liquid_address
+from embit.liquid import slip77
+from embit import bip32, ec, script
+from math import ceil
+import time
+from cryptoadvance.specter.util.psbt import *
-def to_canonical_pset(pset):
+def to_canonical_pset(pset: str) -> str:
"""
Removes unblinded information from the transaction
so Elements Core can decode it
@@ -24,3 +32,169 @@ def to_canonical_pset(pset):
out.value = None
out.value_blinding_factor = None
return str(tx)
+
+
+def get_address(script_pubkey: script.Script, network: dict) -> str:
+ if not script_pubkey.data:
+ return "Fee"
+ if script_pubkey.data.startswith(b"\x6a"):
+ if len(script_pubkey.data) == 1:
+ return "DUMMY" # dummy output to blind
+ else:
+ return "OP_RETURN " + script_pubkey.data[1:].hex()
+ try:
+ return script_pubkey.address(network)
+ except:
+ return script_pubkey.data.hex()
+
+
+def get_value(value) -> int:
+ if isinstance(value, int):
+ return value
+ return 0 # confidential
+
+
+def get_asset(asset) -> bytes:
+ if len(asset) != 32:
+ return (b"\xFF" * 32).hex() # confidential
+ return asset[::-1].hex()
+
+
+class SpecterLTx(SpecterTx):
+ TxCls = LTransaction
+
+ def vout_to_dict(self, vout: LTransactionOutput) -> dict:
+ i = self.tx.vout.index(vout)
+ return {
+ "value": round(1e-8 * get_value(vout.value), 8),
+ "sats": get_value(vout.value),
+ "n": i,
+ "asset": get_asset(vout.asset),
+ "scriptPubKey": {
+ "hex": vout.script_pubkey.data.hex(),
+ "addresses": [get_address(vout.script_pubkey, self.network)],
+ },
+ }
+
+
+class SpecterLInputScope(SpecterInputScope):
+ TxCls = SpecterLTx
+
+ @property
+ def assetid(self) -> str:
+ if self.scope.asset is None:
+ return "???"
+ return self.scope.asset[::-1].hex()
+
+ @property
+ def address(self) -> str:
+ # TODO: blinding key?
+ try:
+ return liquid_address(self.scope.script_pubkey, network=self.network)
+ except:
+ return None
+
+ @property
+ def sat_amount(self) -> int:
+ return self.scope.value or 0
+
+ def to_dict(self) -> dict:
+ obj = super().to_dict()
+ obj.update({"asset": self.assetid})
+ return obj
+
+
+class SpecterLOutputScope(SpecterOutputScope):
+ @property
+ def assetid(self) -> str:
+ if self.scope.asset is None:
+ return "???"
+ return self.scope.asset[::-1].hex()
+
+ @property
+ def address(self) -> str:
+ if not self.scope.script_pubkey.data:
+ return "Fee"
+ if self.scope.script_pubkey.data.startswith(b"\x6a"):
+ if len(self.scope.script_pubkey.data) == 1:
+ return "DUMMY" # dummy output to blind
+ else:
+ return "OP_RETURN " + self.scope.script_pubkey.data[1:].hex()
+ try:
+ # try making a liquid address
+ return liquid_address(
+ self.scope.script_pubkey, self.blinding_key, network=self.network
+ )
+ except:
+ # if failed - return hex of the scriptpubkey
+ return self.scope.script_pubkey.data.hex()
+
+ def extra_weight(self) -> int:
+ wit = 0
+ if self.scope.is_blinded:
+ wit += 33 * 4 # nonce
+ wit += (33 - 9) * 4 # value
+ if self.scope.range_proof and self.scope.surjection_proof:
+ # serialized witness length
+ wit += (
+ len(self.scope.surjection_proof) + len(self.scope.range_proof) + 3
+ )
+ else:
+ # we don't have proofs yet but we can estimate their size
+ wit += 4245
+ return wit
+
+ @property
+ def blinding_key(self) -> ec.PublicKey:
+ if self.scope.blinding_pubkey:
+ return ec.PublicKey.parse(self.scope.blinding_pubkey)
+
+ @property
+ def sat_amount(self) -> int:
+ return self.scope.value or 0
+
+ def to_dict(self) -> dict:
+ obj = super().to_dict()
+ obj.update({"asset": self.assetid})
+ return obj
+
+
+class SpecterPSET(SpecterPSBT):
+ """Specter's PSBT class with some handy functions"""
+
+ PSBTCls = PSET
+ InputCls = SpecterLInputScope
+ OutputCls = SpecterLOutputScope
+ TxCls = SpecterLTx
+
+ def __init__(self, *args, **kwargs):
+ super().__init__(*args, **kwargs)
+ self.psbt.verify(ignore_missing=True)
+
+ @property
+ def full_size(self) -> int:
+ size = len(self.psbt.tx.serialize()) * 4
+ # witness and redeem script
+ size += len(self.inputs) * self.extra_input_weight
+ for out in self.outputs:
+ size += out.extra_weight()
+ return ceil(size / 4)
+
+ def should_display(self, out: SpecterLOutputScope) -> bool:
+ """Checks if this output should be displayed"""
+ if not out.scope.script_pubkey.data:
+ # Fee
+ return False
+ if out.scope.value == 0 and out.scope.script_pubkey.data == b"\x6a":
+ # Dummy output
+ return False
+ return super().should_display(out)
+
+ @property
+ def assets(self) -> List[str]:
+ return [out.assetid for out in self.outputs if self.should_display(out)]
+
+ def to_dict(self) -> dict:
+ obj = super().to_dict()
+ obj.update({"asset": self.assets})
+ return obj
diff --git a/src/cryptoadvance/specter/liquid/wallet.py b/src/cryptoadvance/specter/liquid/wallet.py
index 9a4a0a917..090f0a09e 100644
--- a/src/cryptoadvance/specter/liquid/wallet.py
+++ b/src/cryptoadvance/specter/liquid/wallet.py
@@ -3,10 +3,13 @@ from ..addresslist import Address
from embit import ec
from embit.liquid.pset import PSET
from embit.liquid.transaction import LTransaction
+from embit.liquid.descriptor import LDescriptor
+from embit.descriptor.checksum import add_checksum
from .txlist import LTxList
from .addresslist import LAddressList
from embit.liquid.addresses import to_unconfidential
from ..specter_error import SpecterError
+from .util.pset import SpecterPSET
class LWallet(Wallet):
@@ -14,51 +17,24 @@ class LWallet(Wallet):
AddressListCls = LAddressList
TxListCls = LTxList
TxCls = LTransaction
- PSBTCls = PSET
+ PSBTCls = SpecterPSET
+ DescriptorCls = LDescriptor
@classmethod
- def create(
- cls,
- rpc,
- rpc_path,
- working_folder,
- device_manager,
- wallet_manager,
- name,
- alias,
- sigs_required,
- key_type,
- keys,
- devices,
- core_version=None,
+ def construct_descriptor(
+ cls, sigs_required, key_type, keys, devices, blinding_key=None
):
- """Creates a wallet. If core_version is not specified - get it from rpc"""
- # get xpubs in a form [fgp/der]xpub from all keys
- xpubs = [key.metadata["combined"] for key in keys]
- recv_keys = ["%s/0/*" % xpub for xpub in xpubs]
- change_keys = ["%s/1/*" % xpub for xpub in xpubs]
- is_multisig = len(keys) > 1
- # we start by constructing an argument for descriptor wrappers
- if is_multisig:
- recv_descriptor = "sortedmulti({},{})".format(
- sigs_required, ",".join(recv_keys)
- )
- change_descriptor = "sortedmulti({},{})".format(
- sigs_required, ",".join(change_keys)
- )
- else:
- recv_descriptor = recv_keys[0]
- change_descriptor = change_keys[0]
- # now we iterate over script-type in reverse order
- # to get sh(wpkh(xpub)) from sh-wpkh and xpub
- arr = key_type.split("-")
- for el in arr[::-1]:
- recv_descriptor = "%s(%s)" % (el, recv_descriptor)
- change_descriptor = "%s(%s)" % (el, change_descriptor)
+ """
+ Creates a wallet descriptor from arguments.
+ We need to pass `devices` for Liquid wallet, here it's not used.
+ """
+ # construct normal bitcoin descriptor first
+ btcdescriptor = Wallet.construct_descriptor(
+ sigs_required, key_type, keys, devices
+ )
- # get blinding key for the wallet
- blinding_key = None
- if len(devices) == 1:
+ # get blinding key for the wallet if it's not provided
+ if len(devices) == 1 and blinding_key is None:
blinding_key = devices[0].blinding_key
if not blinding_key:
raise SpecterError(
@@ -68,14 +44,13 @@ class LWallet(Wallet):
# if we don't have slip77 key for a device or it is multisig
# we use chaincodes to generate slip77 key.
if not blinding_key:
- desc = LDescriptor.from_string(recv_descriptor)
# For now we use sha256(b"blinding_key", xor(chaincodes)) as a blinding key
# where chaincodes are corresponding to xpub of the first receiving address.
# It's not a standard but we use that until musig(blinding_xpubs) is implemented.
# Chaincodes of the first address are not used anywhere else so they can be used
# as a source for the blinding keys. They are also independent of the xpub's origin.
xor = bytearray(32)
- desc_keys = desc.derive(0).keys
+ desc_keys = btcdescriptor.derive(0, branch_index=0).keys
for k in desc_keys:
if k.is_extended:
chaincode = k.key.chain_code
@@ -83,81 +58,21 @@ class LWallet(Wallet):
xor[i] = xor[i] ^ chaincode[i]
secret = hashlib.sha256(b"blinding_key" + bytes(xor)).digest()
blinding_key = ec.PrivateKey(secret).wif()
- if blinding_key:
- recv_descriptor = f"blinded(slip77({blinding_key}),{recv_descriptor})"
- change_descriptor = f"blinded(slip77({blinding_key}),{change_descriptor})"
- recv_descriptor = AddChecksum(recv_descriptor)
- change_descriptor = AddChecksum(change_descriptor)
- assert recv_descriptor != change_descriptor
-
- # get Core version if we don't know it
- if core_version is None:
- core_version = rpc.getnetworkinfo().get("version", 0)
-
- use_descriptors = core_version >= 209900
- # v20.99 is pre-v21 Elements Core for descriptors
- if use_descriptors:
- # Use descriptor wallet
- rpc.createwallet(os.path.join(rpc_path, alias), True, True, "", False, True)
- else:
- rpc.createwallet(os.path.join(rpc_path, alias), True)
-
- wallet_rpc = rpc.wallet(os.path.join(rpc_path, alias))
- # import descriptors
- args = [
- {
- "desc": desc,
- "internal": change,
- "timestamp": "now",
- "watchonly": True,
- }
- for (change, desc) in [(False, recv_descriptor), (True, change_descriptor)]
- ]
- for arg in args:
- if use_descriptors:
- arg["active"] = True
- else:
- arg["keypool"] = True
- arg["range"] = [0, cls.GAP_LIMIT]
-
- assert args[0] != args[1]
-
- # Descriptor wallets were introduced in v0.21.0, but upgraded nodes may
- # still have legacy wallets. Use getwalletinfo to check the wallet type.
- # The "keypool" for descriptor wallets is automatically refilled
- if use_descriptors:
- res = wallet_rpc.importdescriptors(args)
- else:
- res = wallet_rpc.importmulti(args, {"rescan": False})
-
- assert all([r["success"] for r in res])
-
- return cls(
- name,
- alias,
- "{} of {} {}".format(sigs_required, len(keys), purposes[key_type])
- if len(keys) > 1
- else purposes[key_type],
- addrtypes[key_type],
- "",
- -1,
- "",
- -1,
- 0,
- 0,
- recv_descriptor,
- change_descriptor,
- keys,
- devices,
- sigs_required,
- {},
- [],
- os.path.join(working_folder, "%s.json" % alias),
- device_manager,
- wallet_manager,
+ return cls.DescriptorCls.from_string(
+ f"blinded(slip77({blinding_key}),{str(btcdescriptor)})"
)
+ def derive_descriptor(self, index: int, change: bool, keep_xpubs=False):
+ """
+ For derived descriptor for individual address we remove blinding key
+ as it is used in HWI calls that doesn't support blinding descriptors yet.
+ TODO: handle blinding keys in HWI
+ """
+ desc = super().derive_descriptor(index, change, keep_xpubs)
+ desc.blinding_key = None
+ return desc
+
def getdata(self):
self.fetch_transactions()
self.check_utxo()
@@ -246,8 +161,7 @@ class LWallet(Wallet):
fee_rate: float = 1.0,
selected_coins=[],
readonly=False,
- rbf=True,
- existing_psbt=None,
+ rbf=False,
rbf_edit_mode=False,
assets=None,
):
@@ -257,6 +171,9 @@ class LWallet(Wallet):
if fee_rate > 0 and fee_rate < self.MIN_FEE_RATE:
fee_rate = self.MIN_FEE_RATE
+ if not assets:
+ raise SpecterError("Missing assets information")
+
options = {"includeWatching": True, "replaceable": rbf}
extra_inputs = []
# get change addresses for all assets + for LBTC
@@ -265,163 +182,92 @@ class LWallet(Wallet):
for i in range(len(assets) + 1)
]
- if not existing_psbt:
- # if not rbf_edit_mode:
- # if self.full_available_balance < sum(amounts):
- # raise SpecterError(
- # "The wallet does not have sufficient funds to make the transaction."
- # )
+ if selected_coins:
+ extra_inputs = selected_coins
- # if selected_coins != []:
- # still_needed = sum(amounts)
- # for coin in selected_coins:
- # coin_txid = coin.split(",")[0]
- # coin_vout = int(coin.split(",")[1])
- # coin_amount = self.gettransaction(coin_txid, decode=True)["vout"][
- # coin_vout
- # ]["value"]
- # extra_inputs.append({"txid": coin_txid, "vout": coin_vout})
- # still_needed -= coin_amount
- # if still_needed < 0:
- # break
- # if still_needed > 0:
- # raise SpecterError(
- # "Selected coins does not cover Full amount! Please select more coins!"
- # )
- # elif self.available_balance["trusted"] <= sum(amounts):
- # txlist = self.rpc.listunspent(0, 0)
- # b = sum(amounts) - self.available_balance["trusted"]
- # for tx in txlist:
- # extra_inputs.append({"txid": tx["txid"], "vout": tx["vout"]})
- # b -= tx["amount"]
- # if b < 0:
- # break
+ # subtract fee from amount of this output:
+ # currently only one address is supported, so either
+ # empty array (subtract from change) or [0]
+ subtract_arr = [subtract_from] if subtract else []
- # subtract fee from amount of this output:
- # currently only one address is supported, so either
- # empty array (subtract from change) or [0]
- subtract_arr = [subtract_from] if subtract else []
-
- options = {
- "includeWatching": True,
- # FIXME: get back change addresses
- # "changeAddress": self.change_address,
- "subtractFeeFromOutputs": subtract_arr,
- "replaceable": rbf,
- "changeAddresses": change_addresses, # not supported by Elements - custom field for out LiquidRPC
- }
-
- # 209900 is pre-v21 for Elements Core
- if self.manager.bitcoin_core_version_raw >= 209900:
- options["add_inputs"] = selected_coins == []
-
- if fee_rate > 0:
- # bitcoin core needs us to convert sat/B to BTC/kB
- options["feeRate"] = round((fee_rate * 1000) / 1e8, 8)
-
- # looks like change_type is required in nested segwit wallets
- # but not in native segwit
- if "changeAddress" not in options and self.address_type:
- options["change_type"] = self.address_type
-
- r = self.rpc.walletcreatefundedpsbt(
- extra_inputs, # inputs
- [
- {addresses[i]: amounts[i], "asset": assets[i]}
- for i in range(len(addresses))
- ], # output
- 0, # locktime
- options, # options
- True, # bip32-der
- )
-
- b64psbt = r["psbt"]
- psbt = self.rpc.decodepsbt(b64psbt)
- else:
- psbt = existing_psbt
- # vins from psbt v0 or v2
- if "tx" in psbt:
- extra_inputs = [
- {"txid": tx["txid"], "vout": tx["vout"]} for tx in psbt["tx"]["vin"]
- ]
- else:
- extra_inputs = [
- {"txid": inp["previous_txid"], "vout": inp["previous_vout"]}
- for inp in psbt["inputs"]
- ]
+ options = {
+ "includeWatching": True,
# FIXME: get back change addresses
- # if "changeAddress" in psbt:
- # options["changeAddress"] = psbt["changeAddress"]
- # if "change_type" in options:
- # options.pop("change_type")
- if "base64" in psbt:
- b64psbt = psbt["base64"]
+ # "changeAddress": self.change_address,
+ "subtractFeeFromOutputs": subtract_arr,
+ "replaceable": rbf,
+ "changeAddresses": change_addresses, # not supported by Elements - custom field for our LiquidRPC
+ }
+
+ options["add_inputs"] = not selected_coins
+
+ if fee_rate > 0:
+ # bitcoin core needs us to convert sat/B to BTC/kB
+ options["feeRate"] = round((fee_rate * 1000) / 1e8, 8)
# looks like change_type is required in nested segwit wallets
# but not in native segwit
if "changeAddress" not in options and self.address_type:
options["change_type"] = self.address_type
- if fee_rate > 0.0:
- if not existing_psbt:
- adjusted_fee_rate = self.adjust_fee(psbt, fee_rate)
- options["feeRate"] = "%.8f" % round((adjusted_fee_rate * 1000) / 1e8, 8)
- else:
- options["feeRate"] = "%.8f" % round((fee_rate * 1000) / 1e8, 8)
+ try:
+ locktime = min([tip["height"] for tip in self.rpc.getchaintips()])
+ except:
+ locktime = 0
+
+ r = self.rpc.walletcreatefundedpsbt(
+ extra_inputs, # inputs
+ [
+ {addresses[i]: amounts[i], "asset": assets[i]}
+ for i in range(len(addresses))
+ ], # outputs
+ locktime,
+ options, # options
+ True, # bip32-der
+ )
+
+ b64psbt = r["psbt"]
+ psbt = self.PSBTCls(
+ b64psbt,
+ self.descriptor,
+ self.network,
+ devices=list(zip(self.keys, self._devices)),
+ )
+
+ if fee_rate > 0:
+ # scale by which Core misses the fee rate
+ scale = fee_rate / psbt.fee_rate
+ adjusted_fee_rate = fee_rate * scale
+ options["feeRate"] = round((adjusted_fee_rate * 1000) / 1e8, 8)
+
r = self.rpc.walletcreatefundedpsbt(
extra_inputs, # inputs
[
{addresses[i]: amounts[i], "asset": assets[i]}
for i in range(len(addresses))
], # output
- 0, # locktime
+ locktime,
options, # options
True, # bip32-der
)
b64psbt = r["psbt"]
- psbt = self.rpc.decodepsbt(b64psbt)
- psbt["fee_rate"] = options["feeRate"]
- # estimate full size
- tx_full_size = ceil(
- psbt["tx"]["vsize"] + len(psbt["inputs"]) * self.weight_per_input / 4
- )
- psbt["tx_full_size"] = tx_full_size
+ psbt = self.PSBTCls(
+ b64psbt,
+ self.descriptor,
+ self.network,
+ devices=list(zip(self.keys, self._devices)),
+ )
- psbt["base64"] = b64psbt
- psbt["amount"] = amounts
- psbt["address"] = addresses
- if assets:
- psbt["asset"] = assets
- psbt["time"] = time.time()
- psbt["sigs_count"] = 0
if not readonly:
self.save_pending_psbt(psbt)
+ return psbt.to_dict()
- return psbt
+ def canceltx(self, *args, **kwargs):
+ raise SpecterError("RBF is not implemented on Liquid")
- def adjust_fee(self, psbt, fee_rate):
- psbt_fees_sats = int(psbt.get("fees", {}).get("bitcoin", 0) * 1e8)
-
- # TODO: handle non-blind outputs differently
- num_blinded_outs = len(psbt["outputs"]) - 1
- # estimate final size: add weight of inputs and outputs
- # out witness weight is 4245 (from some random tx)
- # commitments in blinded tx: 33 for nonce and 33 for value
- commitment_size = 33 + 33
- tx_full_size = ceil(
- psbt["tx"]["vsize"]
- + len(psbt["inputs"]) * self.weight_per_input / 4
- + num_blinded_outs * 4245 / 4
- # probably elements doesn't count commitments
- + len(psbt["inputs"]) * commitment_size
- + num_blinded_outs * commitment_size
- )
- return (
- fee_rate
- * (fee_rate / (psbt_fees_sats / psbt["tx"]["vsize"]))
- * (tx_full_size / psbt["tx"]["vsize"])
- )
+ def bumpfee(self, *args, **kwargs):
+ raise SpecterError("RBF is not implemented on Liquid")
def addresses_info(self, is_change):
"""Create a list of (receive or change) addresses from cache and retrieve the
@@ -432,7 +278,7 @@ class LWallet(Wallet):
addresses_info = []
addresses_cache = [
- v for _, v in self._addresses.items() if v.change == is_change
+ v for _, v in self._addresses.items() if v.change == is_change and v.is_mine
]
for addr in addresses_cache:
diff --git a/src/cryptoadvance/specter/managers/wallet_manager.py b/src/cryptoadvance/specter/managers/wallet_manager.py
index 43271f9d3..41586fdc1 100644
--- a/src/cryptoadvance/specter/managers/wallet_manager.py
+++ b/src/cryptoadvance/specter/managers/wallet_manager.py
@@ -161,16 +161,11 @@ class WalletManager:
for psbt in loaded_wallet.pending_psbts:
logger.info(
"lock %s " % wallet_alias,
- loaded_wallet.pending_psbts[psbt]["tx"]["vin"],
+ loaded_wallet.pending_psbts[psbt].utxo_dict(),
)
loaded_wallet.rpc.lockunspent(
False,
- [
- utxo
- for utxo in loaded_wallet.pending_psbts[
- psbt
- ]["tx"]["vin"]
- ],
+ loaded_wallet.pending_psbts[psbt].utxo_dict(),
)
if len(loaded_wallet.frozen_utxo) > 0:
loaded_wallet.rpc.lockunspent(
@@ -285,7 +280,7 @@ class WalletManager:
logger.debug(f"Updating WalletManager rpc {self._rpc} with None")
self._rpc = value
- def create_wallet(self, name, sigs_required, key_type, keys, devices):
+ def create_wallet(self, name, sigs_required, key_type, keys, devices, **kwargs):
try:
walletsindir = [
wallet["name"] for wallet in self.rpc.listwalletdir()["wallets"]
@@ -314,6 +309,7 @@ class WalletManager:
keys,
devices,
self.bitcoin_core_version_raw,
+ **kwargs,
)
# save wallet file to disk
if w and self.working_folder is not None:
diff --git a/src/cryptoadvance/specter/server_endpoints/filters.py b/src/cryptoadvance/specter/server_endpoints/filters.py
index 0dc822614..f5c42d945 100644
--- a/src/cryptoadvance/specter/server_endpoints/filters.py
+++ b/src/cryptoadvance/specter/server_endpoints/filters.py
@@ -3,7 +3,6 @@ from flask import current_app as app
from flask import Blueprint
from jinja2 import contextfilter
from ..helpers import to_ascii20
-from ..liquid.util.pset import to_canonical_pset
filters_bp = Blueprint("filters", __name__)
@@ -16,6 +15,12 @@ def ascii20(context, name):
return to_ascii20(name)
+@contextfilter
+@filters_bp.app_template_filter("unique_len")
+def unique_len(context, arr):
+ return len(set(arr))
+
+
@contextfilter
@filters_bp.app_template_filter("datetime")
def timedatetime(context, s):
@@ -31,12 +36,6 @@ def btcamount(context, value):
return "{:,.8f}".format(value).rstrip("0").rstrip(".")
-@contextfilter
-@filters_bp.app_template_filter("to_canonical")
-def to_canonical(context, psbt):
- return to_canonical_pset(psbt)
-
-
@contextfilter
@filters_bp.app_template_filter("btc2sat")
def btc2sat(context, value):
diff --git a/src/cryptoadvance/specter/server_endpoints/wallets.py b/src/cryptoadvance/specter/server_endpoints/wallets.py
index 022713788..4c6f75704 100644
--- a/src/cryptoadvance/specter/server_endpoints/wallets.py
+++ b/src/cryptoadvance/specter/server_endpoints/wallets.py
@@ -29,7 +29,6 @@ from ..helpers import (
get_devices_with_keys_by_type,
get_txid,
)
-from ..liquid.util.pset import to_canonical_pset
from ..key import Key
from ..persistence import delete_file
from ..rpc import RpcError
@@ -186,8 +185,8 @@ def new_wallet(wallet_type):
cosigners=wallet_importer.cosigners,
unknown_cosigners=wallet_importer.unknown_cosigners,
unknown_cosigners_types=wallet_importer.unknown_cosigners_types,
- sigs_required=wallet_importer.descriptor.multisig_M,
- sigs_total=wallet_importer.descriptor.multisig_N,
+ sigs_required=wallet_importer.sigs_required,
+ sigs_total=wallet_importer.sigs_total,
specter=app.specter,
rand=rand,
)
@@ -528,11 +527,24 @@ def send_new(wallet_alias):
rand=rand,
)
- elif action == "rbf":
+ elif action in ["rbf", "rbf_cancel"]:
try:
rbf_tx_id = request.form["rbf_tx_id"]
rbf_fee_rate = float(request.form["rbf_fee_rate"])
- psbt = wallet.bumpfee(rbf_tx_id, rbf_fee_rate)
+
+ if action == "rbf":
+ psbt = wallet.bumpfee(rbf_tx_id, rbf_fee_rate)
+ elif action == "rbf_cancel":
+ psbt = wallet.canceltx(rbf_tx_id, rbf_fee_rate)
+ else:
+ raise SpecterError("Invalid action")
+
+ if psbt["fee_rate"] - rbf_fee_rate > wallet.MIN_FEE_RATE / 10:
+ flash(
+ _(
+ "We had to increase the fee rate from {} to {} sat/vbyte"
+ ).format(rbf_fee_rate, psbt["fee_rate"])
+ )
return render_template(
"wallet/send/sign/wallet_send_sign_psbt.jinja",
psbt=psbt,
@@ -544,24 +556,8 @@ def send_new(wallet_alias):
)
except Exception as e:
flash(_("Failed to perform RBF. Error: {}").format(e), "error")
- elif action == "rbf_cancel":
- try:
- rbf_tx_id = request.form["rbf_tx_id"]
- rbf_fee_rate = float(request.form["rbf_fee_rate"])
- psbt = wallet.canceltx(rbf_tx_id, rbf_fee_rate)
- return render_template(
- "wallet/send/sign/wallet_send_sign_psbt.jinja",
- psbt=psbt,
- labels=[],
- wallet_alias=wallet_alias,
- wallet=wallet,
- specter=app.specter,
- rand=rand,
- )
- except Exception as e:
- flash(
- _("Failed to cancel transaction with RBF. Error: {}").format(e),
- "error",
+ return redirect(
+ url_for("wallets_endpoint.history", wallet_alias=wallet_alias)
)
elif action == "rbf_edit":
try:
@@ -589,7 +585,8 @@ def send_new(wallet_alias):
elif action == "signhotwallet":
passphrase = request.form["passphrase"]
psbt = json.loads(request.form["psbt"])
- b64psbt = wallet.pending_psbts[psbt["tx"]["txid"]]["base64"]
+ current_psbt = wallet.pending_psbts[psbt["tx"]["txid"]]
+ b64psbt = str(current_psbt)
device = request.form["device"]
if "devices_signed" not in psbt or device not in psbt["devices_signed"]:
try:
@@ -597,15 +594,12 @@ def send_new(wallet_alias):
signed_psbt = app.specter.device_manager.get_by_alias(
device
).sign_psbt(b64psbt, wallet, passphrase)
+ raw = None
if signed_psbt["complete"]:
- if "devices_signed" not in psbt:
- psbt["devices_signed"] = []
- psbt["devices_signed"].append(device)
- psbt["sigs_count"] = len(psbt["devices_signed"])
raw = wallet.rpc.finalizepsbt(b64psbt)
- if "hex" in raw:
- psbt["raw"] = raw["hex"]
+ current_psbt.update(signed_psbt["psbt"], raw)
signed_psbt = signed_psbt["psbt"]
+ psbt = current_psbt.to_dict()
except Exception as e:
signed_psbt = None
flash(_("Failed to sign PSBT: {}").format(e), "error")
@@ -692,13 +686,7 @@ def send_pending(wallet_alias):
specter=app.specter,
rand=rand,
)
- pending_psbts = wallet.pending_psbts
- ######## Migration to multiple recipients format ###############
- for psbt in pending_psbts:
- if not isinstance(pending_psbts[psbt]["address"], list):
- pending_psbts[psbt]["address"] = [pending_psbts[psbt]["address"]]
- pending_psbts[psbt]["amount"] = [pending_psbts[psbt]["amount"]]
- ###############################################################
+ pending_psbts = wallet.pending_psbts_dict()
return render_template(
"wallet/send/pending/wallet_sendpending.jinja",
pending_psbts=pending_psbts,
@@ -910,9 +898,6 @@ def combine(wallet_alias):
return e.error_msg, e.status_code
except Exception as e:
return _("Unknown error: {}").format(e), 500
- if "psbt" in raw:
- # returned psbt should be valid for Bitcoin or Elements Core decoding
- raw["psbt"] = to_canonical_pset(raw["psbt"])
return json.dumps(raw)
@@ -1672,7 +1657,12 @@ def process_txlist(txlist, idx=0, limit=100, search=None, sortby=None, sortdir="
if app.specter.is_liquid:
for tx in txlist:
if "asset" in tx:
- tx["assetlabel"] = app.specter.asset_label(tx["asset"])
+ if isinstance(tx["asset"], list):
+ tx["assetlabel"] = [
+ app.specter.asset_label(asset) for asset in tx["asset"]
+ ]
+ else:
+ tx["assetlabel"] = app.specter.asset_label(tx["asset"])
return {"txlist": json.dumps(txlist), "pageCount": page_count}
diff --git a/src/cryptoadvance/specter/static/img/mixed.svg b/src/cryptoadvance/specter/static/img/mixed.svg
new file mode 100644
index 000000000..a95b36b10
--- /dev/null
+++ b/src/cryptoadvance/specter/static/img/mixed.svg
@@ -0,0 +1,9 @@
+
\ No newline at end of file
diff --git a/src/cryptoadvance/specter/templates/includes/asset-label.html b/src/cryptoadvance/specter/templates/includes/asset-label.html
index a9f073127..fd8b8bd3f 100644
--- a/src/cryptoadvance/specter/templates/includes/asset-label.html
+++ b/src/cryptoadvance/specter/templates/includes/asset-label.html
@@ -70,6 +70,16 @@
async connectedCallback() {
this.asset = this.getAttribute('data-asset');
this.labelValue = this.getAttribute('data-label');
+ // edit-mode can be 'hover', 'disabled', 'enabled'
+ this.mode = this.getAttribute('edit-mode');
+ // we don't know the asset
+ if( (this.asset == null) ||
+ (this.asset == "ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff") ||
+ (this.asset == "0000000000000000000000000000000000000000000000000000000000000000")){
+ this.asset = "Confidential";
+ this.labelValue = "Confidential";
+ this.mode = "disabled";
+ }
// Set the label - fetch if not specified
if (this.labelValue) {
@@ -78,8 +88,6 @@
await this.fetchAssetLabel();
}
- // edit-mode can be 'hover', 'disabled', 'enabled'
- this.mode = this.getAttribute('edit-mode');
if(!this.mode){
this.mode = 'hover';
}
diff --git a/src/cryptoadvance/specter/templates/includes/tx-row.html b/src/cryptoadvance/specter/templates/includes/tx-row.html
index 055ab4f7b..b3bda765a 100644
--- a/src/cryptoadvance/specter/templates/includes/tx-row.html
+++ b/src/cryptoadvance/specter/templates/includes/tx-row.html
@@ -9,7 +9,7 @@
.svg-receive, .svg-immature, .svg-generate {
filter: invert(61%) sepia(48%) saturate(3609%) hue-rotate(189deg) brightness(91%) contrast(94%);
}
- .svg-selftransfer {
+ .svg-selftransfer, .svg-mixed {
filter: invert(77%) sepia(68%) saturate(3384%) hue-rotate(44deg) brightness(112%) contrast(74%);
}
.svg-cancelled {
@@ -145,12 +145,25 @@
this.amount = parseInt(this.amount * 1e8);
}
- if (this.amount == 1e-8) {
+ if (this.amount == 1e-8 || this.amount == 0) {
this.amountText.innerText = '{{ _("Confidential") }}';
} else {
- this.amountText.innerHTML = this.hideSensitiveInfo ? '#########' : `${numberWithCommas(this.amount.toString())}`;
- if("assetlabel" in this.tx){
- this.amountText.innerHTML += `