diff --git a/package-lock.json b/package-lock.json index 1136145..374c534 100644 --- a/package-lock.json +++ b/package-lock.json @@ -406,6 +406,7 @@ "version": "6.10.2", "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.10.2.tgz", "integrity": "sha512-TXtUUEYHuaTEbLZWIKUr5pmBuhDLy+8KYtPYdcV8qC+pOZL+NKqYwvWSRrVXHn+ZmRRAu8vJTAznH7Oag6RVRw==", + "dev": true, "requires": { "fast-deep-equal": "^2.0.1", "fast-json-stable-stringify": "^2.0.0", @@ -609,9 +610,9 @@ "integrity": "sha1-tG6JCTSpWR8tL2+G1+ap8bP+dqg=" }, "aws4": { - "version": "1.8.0", - "resolved": "https://registry.npmjs.org/aws4/-/aws4-1.8.0.tgz", - "integrity": "sha512-ReZxvNHIOv88FlT7rxcXIIC0fPt4KZqZbOlivyWtXLt8ESx84zd3kMC6iK5jVeS2qt+g7ftS7ye4fi06X5rtRQ==" + "version": "1.11.0", + "resolved": "https://registry.npmjs.org/aws4/-/aws4-1.11.0.tgz", + "integrity": "sha512-xh1Rl34h6Fi1DC2WWKfxUTVqRsNnr6LsKz2+hfwDxQJWmrx8+c7ylaqBMcHfl1U1r2dsifOvKX3LQuLNZ+XSvA==" }, "balanced-match": { "version": "1.0.0", @@ -1854,7 +1855,8 @@ "fast-deep-equal": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-2.0.1.tgz", - "integrity": "sha1-ewUhjd+WZ79/Nwv3/bLLFf3Qqkk=" + "integrity": "sha1-ewUhjd+WZ79/Nwv3/bLLFf3Qqkk=", + "dev": true }, "fast-json-stable-stringify": { "version": "2.0.0", @@ -2764,6 +2766,11 @@ } } }, + "function-bind": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz", + "integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==" + }, "gauge": { "version": "2.7.4", "resolved": "https://registry.npmjs.org/gauge/-/gauge-2.7.4.tgz", @@ -2974,12 +2981,38 @@ "integrity": "sha1-qUwiJOvKwEeCoNkDVSHyRzW37JI=" }, "har-validator": { - "version": "5.1.3", - "resolved": "https://registry.npmjs.org/har-validator/-/har-validator-5.1.3.tgz", - "integrity": "sha512-sNvOCzEQNr/qrvJgc3UG/kD4QtlHycrzwS+6mfTrrSq97BvaYcPZZI1ZSqGSPR73Cxn4LKTD4PttRwfU7jWq5g==", + "version": "5.1.5", + "resolved": "https://registry.npmjs.org/har-validator/-/har-validator-5.1.5.tgz", + "integrity": "sha512-nmT2T0lljbxdQZfspsno9hgrG3Uir6Ks5afism62poxqBM6sDnMEuPmzTq8XN0OEwqKLLdh1jQI3qyE66Nzb3w==", "requires": { - "ajv": "^6.5.5", + "ajv": "^6.12.3", "har-schema": "^2.0.0" + }, + "dependencies": { + "ajv": { + "version": "6.12.6", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", + "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", + "requires": { + "fast-deep-equal": "^3.1.1", + "fast-json-stable-stringify": "^2.0.0", + "json-schema-traverse": "^0.4.1", + "uri-js": "^4.2.2" + } + }, + "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==" + } + } + }, + "has": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/has/-/has-1.0.3.tgz", + "integrity": "sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==", + "requires": { + "function-bind": "^1.1.1" } }, "has-flag": { @@ -3120,9 +3153,9 @@ "integrity": "sha512-Pgs951kaMm5GXP7MOvxERINe3gsaVjUWFm+UZPSq9xYriQAksyhg0csnS0KXSNRD5NmNdapXEpjxG49+AKh/ug==" }, "ignore-walk": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/ignore-walk/-/ignore-walk-3.0.2.tgz", - "integrity": "sha512-EXyErtpHbn75ZTsOADsfx6J/FPo6/5cjev46PXrcTpd8z3BoRkXgYu9/JVqrI7tusjmwCZutGeRJeU0Wo1e4Cw==", + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/ignore-walk/-/ignore-walk-3.0.3.tgz", + "integrity": "sha512-m7o6xuOaT1aqheYHKf8W6J5pYH85ZI9w077erOzLje3JsB1gkafkAhHHY19dqjulgIZHFm32Cp5uNZgcQqdJKw==", "requires": { "minimatch": "^3.0.4" }, @@ -3235,6 +3268,14 @@ "resolved": "https://registry.npmjs.org/is-buffer/-/is-buffer-1.1.6.tgz", "integrity": "sha512-NcdALwpXkTm5Zvvbk7owOUSvVvBKDgKP5/ewfXEznmQFfs4ZRmanOeKBTjRVjka3QFoN6XJ+9F3USqfHqTaU5w==" }, + "is-core-module": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.2.0.tgz", + "integrity": "sha512-XRAfAdyyY5F5cOXn7hYQDqh2Xmii+DEfIcQGxK/uNwMHhIkPWO0g8msXcbzLe+MpGoR951MlqM/2iIlU4vKDdQ==", + "requires": { + "has": "^1.0.3" + } + }, "is-data-descriptor": { "version": "0.1.4", "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-0.1.4.tgz", @@ -3774,27 +3815,27 @@ "integrity": "sha1-o1AIsg9BOD7sH7kU9M1d95omQoQ=" }, "minipass": { - "version": "2.6.4", - "resolved": "https://registry.npmjs.org/minipass/-/minipass-2.6.4.tgz", - "integrity": "sha512-D/+wBy2YykFsCcWvaIslCKKus5tqGQZ8MhEzNx4mujLNgHhXWaaUOZkok6/kztAlTt0QkYLEyIShrybNmzoeTA==", + "version": "2.9.0", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-2.9.0.tgz", + "integrity": "sha512-wxfUjg9WebH+CUDX/CdbRlh5SmfZiy/hpkxaRI16Y9W56Pa75sWgd/rvFilSgrauD9NyFymP/+JFV3KwzIsJeg==", "requires": { "safe-buffer": "^5.1.2", "yallist": "^3.0.0" }, "dependencies": { "yallist": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.0.3.tgz", - "integrity": "sha512-S+Zk8DEWE6oKpV+vI3qWkaK+jSbIK86pCwe2IF/xwIpQ8jEuxpw9NyaGjmp9+BoJv5FV2piqCDcoCtStppiq2A==" + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz", + "integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==" } } }, "minizlib": { - "version": "1.2.2", - "resolved": "https://registry.npmjs.org/minizlib/-/minizlib-1.2.2.tgz", - "integrity": "sha512-hR3At21uSrsjjDTWrbu0IMLTpnkpv8IIMFDFaoz43Tmu4LkmAXfH44vNNzpTnf+OAQQCHrb91y/wc2J4x5XgSQ==", + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/minizlib/-/minizlib-1.3.3.tgz", + "integrity": "sha512-6ZYMOEnmVsdCeTJVE0W9ZD+pVnE8h9Hma/iOwwRDsdQoePpoX56/8B6z3P9VNwppJuBKNRuFDRNRqRWexT9G9Q==", "requires": { - "minipass": "^2.2.1" + "minipass": "^2.9.0" } }, "mississippi": { @@ -3950,9 +3991,9 @@ } }, "needle": { - "version": "2.4.0", - "resolved": "https://registry.npmjs.org/needle/-/needle-2.4.0.tgz", - "integrity": "sha512-4Hnwzr3mi5L97hMYeNl8wRW/Onhy4nUKR/lVemJ8gJedxxUyBLm9kkrDColJvoSfwi0jCNhD+xCdOtiGDQiRZg==", + "version": "2.6.0", + "resolved": "https://registry.npmjs.org/needle/-/needle-2.6.0.tgz", + "integrity": "sha512-KKYdza4heMsEfSWD7VPUIz3zX2XDwOyX2d+geb4vrERZMT5RMU6ujjaD+I5Yr54uZxQ2w6XRTAhHBbSCyovZBg==", "requires": { "debug": "^3.2.6", "iconv-lite": "^0.4.4", @@ -3960,9 +4001,9 @@ }, "dependencies": { "debug": { - "version": "3.2.6", - "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.6.tgz", - "integrity": "sha512-mel+jf7nrtEl5Pn1Qx46zARXKDpBbvzezse7p7LqINmdoIk8PYP5SySaxEmYv6TZ0JyEKA1hsCId6DIhgITtWQ==", + "version": "3.2.7", + "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz", + "integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==", "requires": { "ms": "^2.1.1" } @@ -4077,9 +4118,9 @@ "integrity": "sha512-ma6oU4Sk0qOoKEAymVoTvk8EdXEobdS7m/mAGhDJ8Rouugho48crHBORAmy5BoOcv8wraPM6xumapQp5hl4iIQ==" }, "nopt": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/nopt/-/nopt-4.0.1.tgz", - "integrity": "sha1-0NRoWv1UFRk8jHUFYC0NF81kR00=", + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/nopt/-/nopt-4.0.3.tgz", + "integrity": "sha512-CvaGwVMztSMJLOeXPrez7fyfObdZqNUK1cPAEzLHrTybIua9pMdmmPR5YwtfNftIOMv3DPUhFaxsZMNTQO20Kg==", "requires": { "abbrev": "1", "osenv": "^0.1.4" @@ -4092,17 +4133,26 @@ "dev": true }, "npm-bundled": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/npm-bundled/-/npm-bundled-1.0.6.tgz", - "integrity": "sha512-8/JCaftHwbd//k6y2rEWp6k1wxVfpFzB6t1p825+cUb7Ym2XQfhwIC5KwhrvzZRJu+LtDE585zVaS32+CGtf0g==" + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/npm-bundled/-/npm-bundled-1.1.1.tgz", + "integrity": "sha512-gqkfgGePhTpAEgUsGEgcq1rqPXA+tv/aVBlgEzfXwA1yiUJF7xtEt3CtVwOjNYQOVknDk0F20w58Fnm3EtG0fA==", + "requires": { + "npm-normalize-package-bin": "^1.0.1" + } + }, + "npm-normalize-package-bin": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/npm-normalize-package-bin/-/npm-normalize-package-bin-1.0.1.tgz", + "integrity": "sha512-EPfafl6JL5/rU+ot6P3gRSCpPDW5VmIzX959Ob1+ySFUuuYHWHekXpwdUZcKP5C+DS4GEtdJluwBjnsNDl+fSA==" }, "npm-packlist": { - "version": "1.4.4", - "resolved": "https://registry.npmjs.org/npm-packlist/-/npm-packlist-1.4.4.tgz", - "integrity": "sha512-zTLo8UcVYtDU3gdeaFu2Xu0n0EvelfHDGuqtNIn5RO7yQj4H1TqNdBc/yZjxnWA0PVB8D3Woyp0i5B43JwQ6Vw==", + "version": "1.4.8", + "resolved": "https://registry.npmjs.org/npm-packlist/-/npm-packlist-1.4.8.tgz", + "integrity": "sha512-5+AZgwru5IevF5ZdnFglB5wNlHG1AOOuw28WhUq8/8emhBmLv6jX5by4WJCh7lW0uSYZYS6DXqIsyZVIXRZU9A==", "requires": { "ignore-walk": "^3.0.1", - "npm-bundled": "^1.0.1" + "npm-bundled": "^1.0.1", + "npm-normalize-package-bin": "^1.0.1" } }, "npm-run-path": { @@ -4528,9 +4578,9 @@ "integrity": "sha1-8FKijacOYYkX7wqKw0wa5aaChrM=" }, "psl": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/psl/-/psl-1.4.0.tgz", - "integrity": "sha512-HZzqCGPecFLyoRj5HLfuDSKYTJkAfB5thKBIkRHtGjWwY7p1dAyveIbXIq4tO0KYfDF2tHqPUgY9SDnGm00uFw==" + "version": "1.8.0", + "resolved": "https://registry.npmjs.org/psl/-/psl-1.8.0.tgz", + "integrity": "sha512-RIdOzyoavK+hA18OGGWDqUTsCLhtA7IcZ/6NCs4fFJaHBDab+pDDmDIByWFRQJq2Cd7r1OoQxBGKOaztq+hjIQ==" }, "public-encrypt": { "version": "4.0.3", @@ -4726,9 +4776,9 @@ "integrity": "sha1-jcrkcOHIirwtYA//Sndihtp15jc=" }, "request": { - "version": "2.88.0", - "resolved": "https://registry.npmjs.org/request/-/request-2.88.0.tgz", - "integrity": "sha512-NAqBSrijGLZdM0WZNsInLJpkJokL72XYjUpnB0iwsRgxh7dB6COrHnTBNwN0E+lHDAJzu7kLAkDeY08z2/A0hg==", + "version": "2.88.2", + "resolved": "https://registry.npmjs.org/request/-/request-2.88.2.tgz", + "integrity": "sha512-MsvtOrfG9ZcrOwAW+Qi+F6HbD0CWXEh9ou77uOb7FM2WPhwT7smM833PzanhJLsgXjN89Ir6V2PczXNnMpwKhw==", "requires": { "aws-sign2": "~0.7.0", "aws4": "^1.8.0", @@ -4737,7 +4787,7 @@ "extend": "~3.0.2", "forever-agent": "~0.6.1", "form-data": "~2.3.2", - "har-validator": "~5.1.0", + "har-validator": "~5.1.3", "http-signature": "~1.2.0", "is-typedarray": "~1.0.0", "isstream": "~0.1.2", @@ -4747,7 +4797,7 @@ "performance-now": "^2.1.0", "qs": "~6.5.2", "safe-buffer": "^5.1.2", - "tough-cookie": "~2.4.3", + "tough-cookie": "~2.5.0", "tunnel-agent": "^0.6.0", "uuid": "^3.3.2" }, @@ -4772,10 +4822,11 @@ "dev": true }, "resolve": { - "version": "1.12.0", - "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.12.0.tgz", - "integrity": "sha512-B/dOmuoAik5bKcD6s6nXDCjzUKnaDvdkRyAk6rsmsKLipWj4797iothd7jmmUhWTfinVMU+wc56rYKsit2Qy4w==", + "version": "1.20.0", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.20.0.tgz", + "integrity": "sha512-wENBPt4ySzg4ybFQW2TT1zMQucPK95HSh/nq2CFTZVOGut2+pQvSsgtda4d26YrYcr067wjbmzOG8byDPBX63A==", "requires": { + "is-core-module": "^2.2.0", "path-parse": "^1.0.6" } }, @@ -5422,13 +5473,13 @@ "dev": true }, "tar": { - "version": "4.4.10", - "resolved": "https://registry.npmjs.org/tar/-/tar-4.4.10.tgz", - "integrity": "sha512-g2SVs5QIxvo6OLp0GudTqEf05maawKUxXru104iaayWA09551tFCTI8f1Asb4lPfkBr91k07iL4c11XO3/b0tA==", + "version": "4.4.13", + "resolved": "https://registry.npmjs.org/tar/-/tar-4.4.13.tgz", + "integrity": "sha512-w2VwSrBoHa5BsSyH+KxEqeQBAllHhccyMFVHtGtdMpF4W7IRWfZjFiQceJPChOeTsSDVUpER2T8FA93pr0L+QA==", "requires": { "chownr": "^1.1.1", "fs-minipass": "^1.2.5", - "minipass": "^2.3.5", + "minipass": "^2.8.6", "minizlib": "^1.2.1", "mkdirp": "^0.5.0", "safe-buffer": "^5.1.2", @@ -5436,9 +5487,9 @@ }, "dependencies": { "yallist": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.0.3.tgz", - "integrity": "sha512-S+Zk8DEWE6oKpV+vI3qWkaK+jSbIK86pCwe2IF/xwIpQ8jEuxpw9NyaGjmp9+BoJv5FV2piqCDcoCtStppiq2A==" + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz", + "integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==" } } }, @@ -5549,19 +5600,12 @@ "integrity": "sha512-yaOH/Pk/VEhBWWTlhI+qXxDFXlejDGcQipMlyxda9nthulaxLZUNcUqFxokp0vcYnvteJln5FNQDRrxj3YcbVw==" }, "tough-cookie": { - "version": "2.4.3", - "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-2.4.3.tgz", - "integrity": "sha512-Q5srk/4vDM54WJsJio3XNn6K2sCG+CQ8G5Wz6bZhRZoAe/+TxjWB/GlFAnYEbkYVlON9FMk/fE3h2RLpPXo4lQ==", + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-2.5.0.tgz", + "integrity": "sha512-nlLsUzgm1kfLXSXfRZMc1KLAugd4hqJHDTvc2hDIwS3mZAfMEuMbc03SujMF+GEcpaX/qboeycw6iO8JwVv2+g==", "requires": { - "psl": "^1.1.24", - "punycode": "^1.4.1" - }, - "dependencies": { - "punycode": { - "version": "1.4.1", - "resolved": "https://registry.npmjs.org/punycode/-/punycode-1.4.1.tgz", - "integrity": "sha1-wNWmOycYgArY4esPpSachN1BhF4=" - } + "psl": "^1.1.28", + "punycode": "^2.1.1" } }, "trash": { @@ -5812,9 +5856,9 @@ "dev": true }, "v8flags": { - "version": "3.1.3", - "resolved": "https://registry.npmjs.org/v8flags/-/v8flags-3.1.3.tgz", - "integrity": "sha512-amh9CCg3ZxkzQ48Mhcb8iX7xpAfYJgePHxWMQCBWECpOSqJUXgY26ncA61UTV0BkPqfhcy6mzwCIoP4ygxpW8w==", + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/v8flags/-/v8flags-3.2.0.tgz", + "integrity": "sha512-mH8etigqMfiGWdeXpaaqGfs6BndypxusHHcv2qSHyZkGEznCd/qAXCWWRzeowtL54147cktFOC4P5y+kl8d8Jg==", "requires": { "homedir-polyfill": "^1.0.1" } diff --git a/src/frontend/src/app/app.module.ts b/src/frontend/src/app/app.module.ts index 18ff086..7f93628 100644 --- a/src/frontend/src/app/app.module.ts +++ b/src/frontend/src/app/app.module.ts @@ -21,6 +21,7 @@ import { KnexConfigComponent } from './knex-config/knex-config.component'; import { ApiclientFormComponent } from './apiclient/apiclient-settings-form.component'; import { SettingsComponent } from './settings/settings.component'; import { ApiclientModule } from './apiclient/module'; +import { WalletsModule } from './wallets/module'; export function createCompiler(fn: CompilerFactory): Compiler { @@ -44,6 +45,7 @@ const declarations = [ @NgModule({ declarations: declarations, imports: [ + WalletsModule, FormsModule, BrowserModule, BrowserAnimationsModule, diff --git a/src/frontend/src/app/sidebar/sidebar.component.ts b/src/frontend/src/app/sidebar/sidebar.component.ts index a2f367f..125d14b 100644 --- a/src/frontend/src/app/sidebar/sidebar.component.ts +++ b/src/frontend/src/app/sidebar/sidebar.component.ts @@ -29,7 +29,30 @@ export class SidebarComponent implements OnInit { route: "pluginmanager/plugins", text: "Plugins" }] - },] + },{ + icon: "wallet", + text: "Wallets", + parentRoute: "wallet", + links: [{ + route: "btc", + text: "BTC Wallet" + },{ + route: "ltc", + text: "LTC Wallet" + },{ + route: "eth", + text: "ETH Wallet" + },{ + route: "xrp", + text: "XRP Wallet" + },{ + route: "xlm", + text: "XLM Wallet" + },{ + route: "settings", + text: "Settings" + }] + }] constructor() { } diff --git a/src/frontend/src/app/wallets/btc/address-viewer.component.ts b/src/frontend/src/app/wallets/btc/address-viewer.component.ts new file mode 100644 index 0000000..cd96b87 --- /dev/null +++ b/src/frontend/src/app/wallets/btc/address-viewer.component.ts @@ -0,0 +1,174 @@ + + +import { Component, OnInit, isDevMode } from '@angular/core'; +import { PluginsComponent } from './btc.component'; +import { ClrDatagridStringFilterInterface, ClrDatagridNumericFilterInterface, ClrDropdownModule } from '@clr/angular'; +declare const fb +@Component({ + selector: 'address-viewer', //!!!! + template: ` +
+
+
+
+ Addresses +
+ + + + {{line.message}} + + + + + + Address + + Balance + + + Pending + + + Estimate total + + + + + {{line.address}} + {{line.confirmed.balance | number: '1.8-18'}} + + + {{line.unconfirmed.balance | number: '1.8-18'}} + + + {{line.total.balance | number: '1.8-18'}} + + +
+
+ +
+ +
+
Derivation
+ {{parent.keys[line.address].derivation}} +
Public key
+ {{parent.keys[line.address].publicExtendedKey}} +
Private key
+ {{parent.keys[line.address].privateExtendedKey}} +
+
+
+
+ + + + per page + {{pagination2.firstItem + 1}} - {{pagination2.lastItem + 1}} + of {{pagination2.totalItems}} + + +
+
+
+ +
+
+
+ Change Addresses +
+ + + + {{line.message}} + + + + + + Address + + Balance + + + Pending + + + Estimate total + + + + + {{line.address}} + {{line.confirmed.balance | number: '1.8-18'}} + + + {{line.unconfirmed.balance | number: '1.8-18'}} + + + {{line.total.balance | number: '1.8-18'}} + + +
+
+ +

+

+ +
+
Derivation
+ {{parent.keys[line.address].derivation}} +
Public key
+ {{parent.keys[line.address].publicExtendedKey}} +
Private key
+ {{parent.keys[line.address].privateExtendedKey}} +
+
+
+
+ + + + per page + {{pagination.firstItem + 1}} - {{pagination.lastItem + 1}} + of {{pagination.totalItems}} + + +
+
+
+
+ ` +}) +export class AddressViewerComponent implements OnInit{ + balanceFilter = new BalanceFilter() + otherBalanceFilter = new BalanceFilter() + regularAddresses = [] + changeAddresses = [] + keys = {} + parent:PluginsComponent + ngOnInit() { } + + setParent(parent:PluginsComponent){ + this.parent = parent + } + + setRegularAddresses(addresses){ + this.regularAddresses = addresses + } + + setChangeAddresses(addresses){ + this.changeAddresses = addresses + } +} + +class BalanceFilter implements ClrDatagridNumericFilterInterface{ + accepts(item: any, low: number, high: number): boolean { + high = parseFloat(""+high) + low = parseFloat(""+low) + const balance = (parseFloat(item.unconfirmed.balance) + parseFloat(item.confirmed.balance)) + return (Number.isNaN(low) || balance >= low) + && (Number.isNaN(high) || balance <= high) + } +} \ No newline at end of file diff --git a/src/frontend/src/app/wallets/btc/btc.component.ts b/src/frontend/src/app/wallets/btc/btc.component.ts new file mode 100644 index 0000000..f949bb7 --- /dev/null +++ b/src/frontend/src/app/wallets/btc/btc.component.ts @@ -0,0 +1,707 @@ +import { Component, OnInit, isDevMode, ViewChild, AfterViewInit, OnDestroy } from '@angular/core'; +import { WalletGeneratorComponent } from './wallet-generator.component'; +import { WalletPickerComponent } from './wallet-picker.component'; +import { HDKey } from "btc-hdkey" +import { AddressViewerComponent } from './address-viewer.component'; +const coinselect = require('coinselect/accumulative') +const fetch = require("node-fetch") +import { TransactionBuilder, Signer } from 'bitcoinjs-lib' +import { networks } from 'bitcoinjs-lib' +import { SubscriptionResponse, ErrorResponse } from 'frontblock-generic/Types'; +let feeRate = 55 // satoshis per byte + +const hdkey = new HDKey() +declare const fb +@Component({ + + selector: 'plugins', //!!!! + template: ` + + + + + + + + + + + +
+
+ +
+ Faucet +
+
+ + + + +
+ + + +
+
+ + + +
+ + +
+ + +
+ + +
+ +
+ + + +
+ +
+
+ +
+ Send +
+ + + + {{error}} + + + + + + + {{success}} + + + +
+ +
+ + +
+
+ + +
+
+ +
+ + +
+ + + +
+ + +
+
+ Testnet{{rootkey.publicExtendedKey}} +
+ + + + {{line.message}} + + + + +
+

{{balanceDisplay.total | number: '1.8-18'}} BTC

+

+  {{balanceDisplay.confirmed | number: '1.18'}} BTC confirmed
+ {{balanceDisplay.unconfirmed | number: '1.18'}} BTC pending +

+

+ Private key available +

+
+ + + +
+ + +
+ + +
+ + + + + +
+
+
+
+ +
+ Live Transactions +
+
+ + + + + + + + + + + + + + + + + + + +
HashAmountStatusToFrom
{{line.hash}}{{line.amount}}{{line.status}}{{line.to}}{{line.from.join(', ')}}
+
+
+
+
+
+ + + +
+ +
+ +
+ + ` +}) +export class PluginsComponent implements AfterViewInit, OnDestroy { + timerRefresh: NodeJS.Timeout + errors: string[] = [] + successes: string[] = [] + wallets: WalletStore = {} + rootkey: bip32Keypair = { publicExtendedKey: "", privateExtendedKey: "" } + keys: KeyStore = {} + regaddresses: AddressLine[] = [] + changeaddresses: AddressLine[] = [] + balanceDisplay = { + confirmed: 0, + total: 0, + unconfirmed: 0 + } + mode: { + type: keyType, + net: networkType + } = { + net: "testnet", + type: "public" + } + sendTo: string + sendAmount: number + sending = false + loading = false + loadingN = 1 + autorefresh = false + refreshing = false + apiClientPlugin = false + subscriptions = [] + transactions = [] + + + @ViewChild(AddressViewerComponent, { static: false }) + AddressViewerComponent: AddressViewerComponent + + @ViewChild(WalletGeneratorComponent, { static: false }) + WalletGeneratorComponent: WalletGeneratorComponent + + @ViewChild(WalletPickerComponent, { static: false }) + WalletPickerComponent: WalletPickerComponent + + async ngAfterViewInit(): Promise { + this.WalletGeneratorComponent.setParent(this) + this.WalletPickerComponent.setParent(this) + this.AddressViewerComponent.setParent(this) + this.apiClientPlugin = fb.ApiClient != null + window['btc'] = this + const keys: any[] = [] + + /* + if(this.apiClientPlugin){ + window.addEventListener('beforeunload', async (e) => { + await Promise.all(this.subscriptions.map(async s => { + const RR = await fb.ApiClient.unsubscribe(s) + console.log(RR) + })) + }) + } + */ + + keys.filter(key => key.type === "BTC").forEach(key => { + this.wallets[key.name] = {key: key.value, name: key.name} + }) + + this.timerRefresh = setInterval(async () => { if (this.autorefresh && !this.refreshing && !this.loading) this.refresh() }, 30000) + this.loadKey("tprv8cXm1S7PGS2fHuLjZajuDZZsvxerzv4zZJJAhkLAWZzwz5rE7efHm31xsqpzfoGMHBNC8bkRcsjiMdmkSuNn5C1qNqk7rrk1pCuhk4HfVak") + } + + ngOnDestroy(): void { + clearInterval(this.timerRefresh) + } + + addWallet(wallet: {key:string, name:string}) { + /* store somewhere */ + + //fb.Wallet.putKey(wallet.name, "plain", wallet.key, "BTC") + + this.wallets[wallet.name] = wallet + this.WalletPickerComponent.setWallets(this.wallets) + + } + + showWizard() { + this.WalletGeneratorComponent.open() + } + + loadKey(key: string) { + this.wallets = {} + this.regaddresses = [] + + const t = hdkey.checkHDKey(key) + if (t) { + this.mode = t + } + + this.rootkey = hdkey.HDKeyFromExtendedKey(key) + this.reload() + } + + async reload(): Promise { + if (this.loading) + return + this.loading = true + this.loadingN = 1 + + await this.refresh() + /* + if (fb.ApiClient) { + const res: (SubscriptionResponse | ErrorResponse)[] = await Promise.all([ + ...this.regaddresses, + ...this.changeaddresses + ].map(async a => await fb.ApiClient.subsume("BTC", { address: a.address }, tx => { this.transactions.unshift(tx) }))) + this.subscriptions.push(res.map(r => { + if (r.result = "Success") { + return r.message + } else { + return + } + })) + } + */ + this.loading = false + } + + async refresh(): Promise { + if (this.refreshing) + return + + this.refreshing = true + this.keys = {} + + let p1 = this.updateChange() + let p2 = this.updateBalance() + + let balances: GrandTotal[] = await Promise.all([p1, p2]) + + this.balanceDisplay = balances.reduce((a, o) => { + a.confirmed += o.confirmed + a.total += o.total + a.unconfirmed += o.unconfirmed + + return a + }, { + confirmed: 0, + unconfirmed: 0, + total: 0 + }) + + this.AddressViewerComponent.setChangeAddresses(this.changeaddresses) + this.AddressViewerComponent.setRegularAddresses(this.regaddresses) + this.refreshing = false + + } + + getAddresses(rootAddr: string, n = 10, skip = 0, changeAddresses = false): string[] | null { + let addresses: string[] = [] + for (let i = skip; i < skip + n; i++) { + let addr + if (changeAddresses) { + addr = hdkey.deriveAddress(rootAddr, i, 'm/1/') + } else { + addr = hdkey.deriveAddress(rootAddr, i) + } + if (!addr) { + return + } + addresses.push(addr) + } + return addresses + } + + async fetchAddresses(addresses: string[], network: networkType): Promise<{ success: boolean, address?: any, addresses?: any }> { + let lookupstr = addresses.join(',') + try { + let r = await fetch(api[network] + '/address/' + lookupstr, { + 'method': 'GET', + 'mode': 'cors', + 'data': { limit: 200 } + }) + if (r.status !== 200) { + return { success: false } + } + + let json = (await r).json() + return json + } catch (e) { + return { success: false } + } + } + + async getAddressInfos(rootAddr: string, n = 10, skip = 0, changeAddresses = false): Promise { + const t = hdkey.checkHDKey(rootAddr) + if (!t) { + return false + } + const addresses = this.getAddresses(rootAddr, n, skip, changeAddresses) + return await this.fetchAddresses(addresses, t.net) + + } + + /* + async getTxs(address: string, type:networkType){ + try{ + let r = await fetch(api[type]+'/address/'+address, { + 'method':'GET', + 'mode':'cors' + }) + if(r.status !== 200){ + return {success: false} + } + + return (await r).json() + }catch(e){ + console.warn ("getTxs", e) + } + } + */ + async getUnspentTxs(addresses: string[], type: networkType): Promise { + let lookupstr = addresses.join(',') + let url = api[type] + '/address/' + lookupstr + '/unspent' + let txs:getUnspentTxsApiResponse['unspent'] = [] + while (true) { + try { + + let resp = await fetch(url, { + 'method': 'GET', + 'mode': 'cors' + }) + if (resp.status !== 200) { + return { success: false } + } + const json: getUnspentTxsApiResponse = await resp.json() + if (!json.success) + break; + + txs = [...txs, ...(json.unspent?json.unspent.filter(u => u.confirmations > 0):[])] + if(!json.paging.next_link) + break; + + url = json.paging.next_link + } catch (e) { + console.warn("getUnspentTxs", e) + return { success: false } + } + } + return { + success: true, + unspent: txs + } + } + + + getKeyForAddress(keyName) { + return this.keys[keyName] + } + + calculateTotal(addresses: AddressLine[]): GrandTotal { + const resMap: any = addresses.reduce((o1:any, o2:any) => { o1[o2.address] = o2; return o1 }, {}) + const total: GrandTotal = Object.values(resMap).reduce((accum: GrandTotal, obj: AddressLine) => { + accum.confirmed += parseFloat(obj.confirmed.balance) + accum.unconfirmed += parseFloat(obj.unconfirmed.balance) + accum.total += parseFloat(obj.confirmed.balance) + parseFloat(obj.unconfirmed.balance) + return accum + }, { + confirmed: 0, + unconfirmed: 0, + total: 0 + }) + + return total + } + + async updateBalance(): Promise { + let n = 1 + let res: { success: boolean; addresses: any; address: any; } + this.regaddresses = [] + while (true) { + this.loadingN = n + try { + res = await this.getAddressInfos(this.rootkey.publicExtendedKey, 115 * n, 115 * (n - 1)) + } catch (e) { + break + } + if (res.success === true) { + if ('addresses' in res) { // multiple + this.regaddresses = [...this.regaddresses, ...res.addresses] + } + if ('address' in res) { + this.regaddresses.push(res.address) + } + n++ + } else { + console.warn(`This error is a result of a failed fetch request. +These are always shown by the browser and cannot be hidden (a design decision) +They are completely safe and mean no more addresses were found`) + console.log('No more balance found after attempting ', 115 * n, 'addresses') + if (this.mode.type == "private") { + for (let i = 0; i < 115 * n + 1; i++) { + const keyN = hdkey.deriveKey(this.rootkey.privateExtendedKey, i) + const addrN = hdkey.addressFromHDKey(keyN._publicKey, this.mode.net) + keyN.derivation = i + this.keys[addrN] = keyN + } + } + break + } + } + + //add an extra empty address at the end + const extraAddr = hdkey.deriveAddress(this.rootkey.publicExtendedKey, this.regaddresses.length) + this.regaddresses.push({ + address: extraAddr, + confirmed: { balance: 0 }, + unconfirmed: { balance: 0 }, + total: { balance: 0 }, + }) + + return this.calculateTotal(this.regaddresses) + } + + async updateChange(): Promise { + let n = 1 + this.changeaddresses = [] + while (true) { + let res + try { + res = await this.getAddressInfos(this.rootkey.publicExtendedKey, 115 * n, 115 * (n - 1), true) + } catch (e) { + break + } + if (res.success === true) { + if ('addresses' in res) { // multiple + this.changeaddresses = [...this.changeaddresses, ...res.addresses] + } + if ('address' in res) { + this.changeaddresses.push(res.address) + } + n++ + } else { + console.warn(`This error is a result of a failed fetch request. +These are always shown by the browser and cannot be hidden (a design decision) +They are completely safe and mean no more addresses were found`) + console.log('No more change balance found after attempting ', 115 * n, 'addresses') + if (this.mode.type == "private") { + //make keypairs for addresses + for (let i = 0; i < 115 * n; i++) { + const keyN = hdkey.deriveKey(this.rootkey.privateExtendedKey, i, 'm/1/') + const addrN = hdkey.addressFromHDKey(keyN._publicKey, this.mode.net) + keyN.derivation = i + this.keys[addrN] = keyN + } + } + break + } + } + + return this.calculateTotal(this.changeaddresses) + } + + async createAndSendTx(target: string, value: number) { + this.sending = true + if (target == null || typeof target !== 'string' || target === '' || value == null || value <= 0) { + this.errors.push('Bad inputs. Expected bitcoin address and number') + this.sending = false + return + } + + if (value > this.balanceDisplay.confirmed) { + this.errors.push('Insufficient funds') + this.sending = false + return + } + + let addresses: string[] = [...this.regaddresses.map(a => a.address), ...this.changeaddresses.map(a => a.address)] + const txs = await this.getUnspentTxs(addresses, this.mode.net) + + if (txs == null || txs.success !== true || txs.unspent.length === 0) { + this.errors.push('No UTXOs found') + this.sending = false + return + } + + const utxos = txs.unspent.map(t => { + return { + address: t.addresses[0], + txId: t.txid, + vout: t.n, + value: Math.floor(parseFloat(t.value) * 100000000) + } + }).filter(utx => utx.value > 0) + + console.log(utxos, target, value, feeRate) + + let { inputs, outputs, fee } = coinselect(utxos, [{ address: target, value: Math.floor(parseFloat(value) * 100000000) }], feeRate) + + // .inputs and .outputs will be undefined if no solution was found + if (!inputs || !outputs) { + console.log(fee) + this.errors.push('Could not build a valid transaction') + this.sending = false + return + } + + const txb = new TransactionBuilder(networks[this.mode.net]) + txb.setVersion(1) + inputs.forEach((input) => { + txb.addInput(input.txId, input.vout) + }) + outputs.forEach((output) => { + if (!output.address) { + output.address = hdkey.deriveAddress(this.rootkey.publicExtendedKey, this.changeaddresses.length, 'm/1/') + } + txb.addOutput(output.address, output.value) + }) + inputs.forEach((input, i) => { + txb.sign(i, this.keys[input.address]) + }) + + await this.sendTx(txb.build()) + } + + private async sendTx(tx) { + if (tx == null) { + this.errors.push('No transaction to send!') + return + } + + let url = api[this.mode.net] + "/pushtx" + let r + try { + r = await fetch(url, { + 'method': 'POST', + 'body': JSON.stringify({ 'hex': tx.toHex() }) + }) + } catch (e) { + console.warn("sendTx", e) + return + } + if (r.status === 200) { + this.successes.push("Sent " + this.sendAmount + " to " + this.sendTo) + this.sending = false + this.errors = [] + this.sendTo = "" + this.sendAmount = null + } else { + this.errors.push('The API ' + url + ' returned a non-200 error code ' + r.status + ' ' + r.statusText) + } + } + + addrExplorer(address) { + const url = this.mode.net === 'testnet' ? 'https://live.blockcypher.com/btc-testnet/address/' : 'https://live.blockcypher.com/btc/address/' + window.open(url + address, '_blank') + } +} + +export type KeyStore = { [keyname in string]: bip32Keypair & Signer } + +export type AddressLine = { + address: string, + confirmed: { balance: number }, + unconfirmed: { balance: number }, + total: { balance: number }, +} + +type networkType = 'testnet' | 'mainnet' +type keyType = 'public' | 'private' +type WalletStore = { + [address in string]: { + key: string + name: string + } +} +type GrandTotal = { + confirmed: number, + unconfirmed: number, + total: number +} +type bip32Keypair = { publicExtendedKey: string, privateExtendedKey: string } +type getUnspentTxsApiResponse = { + success: boolean, + paging?: { + next_link: string | null + } + unspent?: { + addresses: string[], + confirmations: number, + id: number, n: number, + req_sigs: number, + script_pub_key: any, + txid: string, + type: string, + value: string, + value_int: number + }[] +} +const api = { + testnet: 'https://testnet-api.smartbit.com.au/v1/blockchain', + mainnet: 'https://api.smartbit.com.au/v1/blockchain' +} \ No newline at end of file diff --git a/src/frontend/src/app/wallets/btc/wallet-generator.component.ts b/src/frontend/src/app/wallets/btc/wallet-generator.component.ts new file mode 100644 index 0000000..dec2d36 --- /dev/null +++ b/src/frontend/src/app/wallets/btc/wallet-generator.component.ts @@ -0,0 +1,266 @@ +import { Component, OnInit, isDevMode, ViewChild } from '@angular/core'; +import { ClrWizard } from '@clr/angular'; +import { HDKey } from "btc-hdkey" +import { PluginsComponent } from './btc.component'; + +declare const fb +@Component({ + selector: 'wallet-generator', //!!!! + template: ` + + Bitcoin wallet generator and importer + + Cancel + Back + Next + Finish + + + Menemonic/BIP39 (optional) + + Menemonic/BIP39 + +

+ A menemonic is a collection of words that is supposed to be easier to remember than a complex seed. It may be used to reconstruct the private key. +

+

+ Enter your menemonic below or generate a new one. +

+ +
+ + + + + +
+ + Cancel + {{menemonic.length==0?"Skip":"Next"}} + +
+ + + Seed phrase (optional) + + Seed phrase + +

+ If you entered a menemonic in the previous step, a seed was generated from it. +

+

+ If you would like to use your own seed enter it below. Use something sufficiently hard to guess. +

+ + + + + + + Cancel + Back + {{seed.length==0?"Skip":"Next"}} + +
+ + + Extended Key/BIP32 +

+ Accepts keys in the xpub and xprv (BIP32) format +

+ +
+ + + Confirm + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
TypeValue
menemonic{{menemonic}}
seed
xpub
xprv
+ + + + + + Cancel + Back + Finish + +
+
+ + ` +}) +export class WalletGeneratorComponent implements OnInit { + @ViewChild("wizard", {static: false}) wizard: ClrWizard; + key:string = "" + keyInvalid = true + accept = false + seed:string = "" + mdOpen: boolean = false + checkKey: boolean = true + menemonic:string = "" + keypair: any = {} + testnet: boolean = false + profileName + private hdKey = new HDKey() + private parent:PluginsComponent + + ngOnInit() { } + + open(){ + this.key = "" + this.keyInvalid = true + this.seed = "" + this.checkKey = true + this.menemonic = "" + this.keypair = {} + this.testnet = false + this.wizard.reset() + this.mdOpen = true + } + + setParent(parent:PluginsComponent){ + this.parent = parent + } + + async generateMenemonic(){ + this.menemonic = this.hdKey.randomMenemonic() + } + + async seedFromMenemonic(){ + try{ + this.seed = this.hdKey.seedFromMenemonic(this.menemonic) + }catch(e){ + /* invalid menemonic */ + console.log(e) + } + } + + async accountFromSeed(){ + this.keypair = this.hdKey.deriveAccount(this.seed, this.testnet?"testnet":"mainnet", 0) + console.log(this.keypair) + this.key = this.keypair.privateExtendedKey != null?this.keypair.privateExtendedKey:this.keypair.publicExtendedKey + this.keyInvalid = false + } + + async importKey(){ + this.hdKey.HDKeyFromExtendedKey(this.key) + } + + checkBip32(){ + return this.hdKey.checkHDKey(this.keypair.privateExtendedKey != null?this.keypair.privateExtendedKey:this.keypair.publicExtendedKey) + } + + finish(){ + this.parent.addWallet({ + name: this.key.substr(4,15), + key: this.key + }) + } + + public doCustomClick(buttonType: string): void { + if ("menemonic-next" === buttonType) { + if(this.menemonic.length != 0){ + this.seedFromMenemonic() + } + this.wizard.next(); + } + + if ("myfinish" === buttonType) { + this.finish() + this.wizard.close() + } + + if ("seed-next" === buttonType) { + if(this.seed.length != 0){ + this.accountFromSeed() + } + this.wizard.next(); + } + + if ("back" === buttonType) { + this.wizard.previous(); + } + } + + updateSeed(){ + this.menemonic = "" + } + + updateKey(){ + this.keyInvalid = true + this.menemonic = "" + this.seed = "" + let key + try{ + key = this.hdKey.HDKeyFromExtendedKey(this.key) + }catch(e){ + /* invalid key */ + console.log("bad key") + } + if(key){ + this.keypair = key + this.keyInvalid = false + } + } +} \ No newline at end of file diff --git a/src/frontend/src/app/wallets/btc/wallet-picker.component.ts b/src/frontend/src/app/wallets/btc/wallet-picker.component.ts new file mode 100644 index 0000000..802b3ab --- /dev/null +++ b/src/frontend/src/app/wallets/btc/wallet-picker.component.ts @@ -0,0 +1,50 @@ + + +import { Component, OnInit, isDevMode } from '@angular/core'; +import { PluginsComponent } from './btc.component'; +declare const fb +@Component({ + selector: 'walletpicker', //!!!! + + template: ` +
+
+ Wallets +
+ + + + {{line.message}} + + + + + + New / Import + + + + + + {{line.name}} + + +
+ ` +}) +export class WalletPickerComponent implements OnInit { + private parent:PluginsComponent + wallets = [] + + setWallets(wallets){ + this.wallets = Object.values(wallets) + } + + setParent(parent:PluginsComponent){ + this.parent = parent + } + + ngOnInit() { } + +} + diff --git a/src/frontend/src/app/wallets/module.ts b/src/frontend/src/app/wallets/module.ts new file mode 100644 index 0000000..ea64056 --- /dev/null +++ b/src/frontend/src/app/wallets/module.ts @@ -0,0 +1,37 @@ +import { NgModule } from '@angular/core'; +import { CommonModule } from '@angular/common'; +import { RouterModule } from '@angular/router'; + +import { PluginsComponent } from './btc/btc.component'; +import { WalletPickerComponent } from './btc/wallet-picker.component'; +import { AddressViewerComponent } from './btc/address-viewer.component'; + +import { ClarityModule } from '@clr/angular'; +import { FormsModule } from '@angular/forms'; +import { WalletGeneratorComponent } from './btc/wallet-generator.component'; +import { WalletSettingsComponent } from './settings.component'; + + +@NgModule({ + imports: [ + FormsModule, + ClarityModule, + CommonModule, + RouterModule.forChild([ + {path: "btc", component: PluginsComponent}, + {path: "settings", component: WalletSettingsComponent}, + ]), + ], + exports: [RouterModule], + declarations: [ + WalletSettingsComponent, + PluginsComponent, + WalletPickerComponent, + AddressViewerComponent, + WalletGeneratorComponent + ], + entryComponents: [ + WalletSettingsComponent + ] +}) +export class WalletsModule{} diff --git a/src/frontend/src/app/wallets/settings.component.ts b/src/frontend/src/app/wallets/settings.component.ts new file mode 100644 index 0000000..7aacd75 --- /dev/null +++ b/src/frontend/src/app/wallets/settings.component.ts @@ -0,0 +1,17 @@ + + +import { Component, OnInit, isDevMode } from '@angular/core'; +declare const fb +@Component({ + selector: 'wallet-settings', //!!!! + + template: + ` + WALLET CONFIG XD + ` +}) +export class WalletSettingsComponent implements OnInit { + ngOnInit() { } + +} + diff --git a/src/frontend/src/app/wallets/webpack.prod.js b/src/frontend/src/app/wallets/webpack.prod.js new file mode 100644 index 0000000..ab1160a --- /dev/null +++ b/src/frontend/src/app/wallets/webpack.prod.js @@ -0,0 +1,41 @@ +const path = require('path'); +const TerserPlugin = require('terser-webpack-plugin'); + +module.exports = { + mode: 'production', + target: "web", + entry: path.resolve(__dirname, 'module.ts'), + output: { + path: path.resolve(__dirname, '../../dist'), + filename: 'FrontendPlugin.js', + libraryTarget: 'commonjs', + }, + resolve: { + // Add `.ts` and `.tsx` as a resolvable extension. + extensions: [".ts", ".tsx", ".js"] + }, + module: { + rules: [ + { test: /\.ts?$/, loader: "ts-loader" } + ] + }, + optimization: { + minimizer: [ + new TerserPlugin({ + exclude: [ + /\.\/(.*)\/.ts/, + /\.\/(.*).ts/, + ], + }), + ], + }, + externals: { + '@angular/core': '@angular/core', + '@angular/common': '@angular/common', + '@angular/router': '@angular/router', + '@angular/animations': '@angular/animations', + '@angular/forms': '@angular/forms', + '@clr/angular': '@clr/angular', + '../../../knex-config/knex-config.module': 'knexconfig' + } +}