wallet
This commit is contained in:
+2
-1
@@ -11,7 +11,8 @@
|
|||||||
"build-dashboard": "cd src/frontend/dashboard; npm run build; cp -r dist/dashboard/* ../../../static",
|
"build-dashboard": "cd src/frontend/dashboard; npm run build; cp -r dist/dashboard/* ../../../static",
|
||||||
"build-widget": "rollup -c src/frontend/widget/rollup.config.js; mkdir -p plugins/PluginManager; cp FrontendPlugin.js plugins/PluginManager",
|
"build-widget": "rollup -c src/frontend/widget/rollup.config.js; mkdir -p plugins/PluginManager; cp FrontendPlugin.js plugins/PluginManager",
|
||||||
"clean": "rm -rf lib static plugins data conf dist widget .rpt2_cache *.js *.ts src/frontend/dashboard/dist",
|
"clean": "rm -rf lib static plugins data conf dist widget .rpt2_cache *.js *.ts src/frontend/dashboard/dist",
|
||||||
"update-frontblock": "rm -rf node_modules/frontblock*; npm install"
|
"update-frontblock": "rm -rf node_modules/frontblock*; npm install",
|
||||||
|
"webpack": "webpack --entry ./lib/FrontblockLib.js --output ./FrontblockLib.min.js"
|
||||||
},
|
},
|
||||||
"repository": {
|
"repository": {
|
||||||
"type": "git",
|
"type": "git",
|
||||||
|
|||||||
@@ -35,7 +35,7 @@
|
|||||||
"scripts": [
|
"scripts": [
|
||||||
"node_modules/systemjs/dist/system.js",
|
"node_modules/systemjs/dist/system.js",
|
||||||
"node_modules/@webcomponents/custom-elements/custom-elements.min.js",
|
"node_modules/@webcomponents/custom-elements/custom-elements.min.js",
|
||||||
"node_modules/@clr/icons/clr-icons.min.js"
|
"node_modules/@clr/icons/clr-icons.min.js"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"configurations": {
|
"configurations": {
|
||||||
|
|||||||
Generated
+16
@@ -2588,6 +2588,17 @@
|
|||||||
"bsert": "~0.0.10"
|
"bsert": "~0.0.10"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"btc-hdkey": {
|
||||||
|
"version": "0.0.17",
|
||||||
|
"resolved": "https://registry.npmjs.org/btc-hdkey/-/btc-hdkey-0.0.17.tgz",
|
||||||
|
"integrity": "sha512-1hzd8DeK/cjrBY6nJ+LenBTGL5rQ+dFBaDEoDSmQ/7zvV3EoV9rsa/aYLLke0Q5XPfbxC1lV/Ro5KpNULA7EYQ==",
|
||||||
|
"requires": {
|
||||||
|
"bip39": "^3.0.2",
|
||||||
|
"bitcoinjs-lib": "^5.1.3",
|
||||||
|
"bsert": "0.0.10",
|
||||||
|
"hdkey": "^1.1.1"
|
||||||
|
}
|
||||||
|
},
|
||||||
"buffer": {
|
"buffer": {
|
||||||
"version": "4.9.1",
|
"version": "4.9.1",
|
||||||
"resolved": "https://registry.npmjs.org/buffer/-/buffer-4.9.1.tgz",
|
"resolved": "https://registry.npmjs.org/buffer/-/buffer-4.9.1.tgz",
|
||||||
@@ -2973,6 +2984,11 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"coinselect": {
|
||||||
|
"version": "3.1.11",
|
||||||
|
"resolved": "https://registry.npmjs.org/coinselect/-/coinselect-3.1.11.tgz",
|
||||||
|
"integrity": "sha1-4fBjvRpYgvZzXuBRm52LWsSpMJk="
|
||||||
|
},
|
||||||
"coinstring": {
|
"coinstring": {
|
||||||
"version": "2.3.0",
|
"version": "2.3.0",
|
||||||
"resolved": "https://registry.npmjs.org/coinstring/-/coinstring-2.3.0.tgz",
|
"resolved": "https://registry.npmjs.org/coinstring/-/coinstring-2.3.0.tgz",
|
||||||
|
|||||||
@@ -24,12 +24,16 @@
|
|||||||
"@clr/icons": "^2.1.1",
|
"@clr/icons": "^2.1.1",
|
||||||
"@clr/ui": "^2.1.1",
|
"@clr/ui": "^2.1.1",
|
||||||
"@webcomponents/custom-elements": "^1.0.0",
|
"@webcomponents/custom-elements": "^1.0.0",
|
||||||
|
"btc-hdkey": "0.0.17",
|
||||||
|
"coinselect": "^3.1.11",
|
||||||
"frontblock": "latest",
|
"frontblock": "latest",
|
||||||
"frontblock-generic": "^0.22.0",
|
"frontblock-generic": "^0.22.0",
|
||||||
"key-file-storage": "^2.2.4",
|
"key-file-storage": "^2.2.4",
|
||||||
|
"node-fetch": "^2.6.0",
|
||||||
"rxjs": "~6.5.2",
|
"rxjs": "~6.5.2",
|
||||||
"systemjs": "^0.21.3",
|
"systemjs": "^0.21.3",
|
||||||
"tslib": "^1.9.0",
|
"tslib": "^1.9.0",
|
||||||
|
"uuid": "^3.3.2",
|
||||||
"zone.js": "~0.10.1"
|
"zone.js": "~0.10.1"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
|
|||||||
@@ -1,40 +1,16 @@
|
|||||||
import { Component, OnInit, isDevMode } from '@angular/core';
|
import { Component, OnInit, isDevMode } from '@angular/core';
|
||||||
import { SubscriptionResponse, parseResponse, SuccessResponse } from 'frontblock-generic/Types.js';
|
import { SubscriptionResponse, parseResponse, SuccessResponse, Coin } from 'frontblock-generic/Types.js';
|
||||||
|
import { default as uuid } from 'uuid/v4'
|
||||||
|
|
||||||
const fb = !isDevMode() ? window["fb"] : {
|
declare const fb
|
||||||
ApiClient: {
|
|
||||||
setConfig: async () => {
|
|
||||||
await new Promise((resolve, reject) => {
|
|
||||||
setTimeout(() => {
|
|
||||||
resolve('Promise A win!');
|
|
||||||
}, 500)
|
|
||||||
})
|
|
||||||
},
|
|
||||||
getConsumptions: async () => {
|
|
||||||
return await new Promise((resolve, reject) => {
|
|
||||||
setTimeout(() => {
|
|
||||||
resolve([new SubscriptionResponse("654", "321"), new SubscriptionResponse("456", "123")]);
|
|
||||||
}, 500)
|
|
||||||
})
|
|
||||||
},
|
|
||||||
getSubscriptions: async () => {
|
|
||||||
return await new Promise((resolve, reject) => {
|
|
||||||
setTimeout(() => {
|
|
||||||
resolve([new SubscriptionResponse("321"), new SubscriptionResponse("123")]);
|
|
||||||
}, 500)
|
|
||||||
})
|
|
||||||
},
|
|
||||||
unsubscribe: (uid) => new SuccessResponse(),
|
|
||||||
quit: (uid) => new SuccessResponse()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'consumptions',
|
selector: 'consumers',
|
||||||
template: `
|
template: `
|
||||||
<div class="clr-row">
|
<div class="clr-row">
|
||||||
<div class="card clr-col-12 clr-col-sm-12 clr-col-md-12 clr-col-lg-12 clr-col-xl-12">
|
<div class="card clr-col-12 clr-col-sm-12 clr-col-md-12 clr-col-lg-auto clr-col-xl-auto">
|
||||||
<div class="card-header">
|
<div class="card-header">
|
||||||
Consumptions
|
Consumers
|
||||||
</div>
|
</div>
|
||||||
<clr-alert *ngFor="let entry of alerts" [clrAlertType]="entry.severity">
|
<clr-alert *ngFor="let entry of alerts" [clrAlertType]="entry.severity">
|
||||||
<clr-alert-item>
|
<clr-alert-item>
|
||||||
@@ -47,16 +23,18 @@ const fb = !isDevMode() ? window["fb"] : {
|
|||||||
<div *ngIf="loading" class="card-block">
|
<div *ngIf="loading" class="card-block">
|
||||||
<span class="spinner spinner-inline"></span>
|
<span class="spinner spinner-inline"></span>
|
||||||
</div>
|
</div>
|
||||||
<div *ngIf="entries.length !== 0 && !loading" class="card-block">
|
<div *ngIf="entries.length !== 0 && !loading" class="card-block limit-height">
|
||||||
<div class="card-text">
|
<div class="card-text limit-height">
|
||||||
<clr-datagrid>
|
<clr-datagrid>
|
||||||
<clr-dg-column>UUID</clr-dg-column>
|
<clr-dg-column>UUID</clr-dg-column>
|
||||||
|
<clr-dg-column>Subscription UUID</clr-dg-column>
|
||||||
<clr-dg-column>Expiry</clr-dg-column>
|
<clr-dg-column>Expiry</clr-dg-column>
|
||||||
<clr-dg-column>Creation</clr-dg-column>
|
<clr-dg-column>Creation</clr-dg-column>
|
||||||
|
|
||||||
<clr-dg-row *ngFor="let line of entries">
|
<clr-dg-row *clrDgItems="let line of entries">
|
||||||
<clr-dg-cell>{{line.uid}}</clr-dg-cell>
|
<clr-dg-cell>{{line.uid}}</clr-dg-cell>
|
||||||
<clr-dg-cell>{{line.expiry}}</clr-dg-cell>
|
<clr-dg-cell>{{line.message}}</clr-dg-cell>
|
||||||
|
<clr-dg-cell>{{line.expiry | date}}</clr-dg-cell>
|
||||||
<clr-dg-cell>{{line.created | date}}</clr-dg-cell>
|
<clr-dg-cell>{{line.created | date}}</clr-dg-cell>
|
||||||
|
|
||||||
<clr-dg-row-detail *clrIfExpanded>
|
<clr-dg-row-detail *clrIfExpanded>
|
||||||
@@ -64,7 +42,11 @@ const fb = !isDevMode() ? window["fb"] : {
|
|||||||
</clr-dg-row-detail>
|
</clr-dg-row-detail>
|
||||||
</clr-dg-row>
|
</clr-dg-row>
|
||||||
<clr-dg-footer>
|
<clr-dg-footer>
|
||||||
{{entries.length}} entries
|
<clr-dg-pagination #pagination [clrDgPageSize]="10">
|
||||||
|
<clr-dg-page-size [clrPageSizeOptions]="[10,20,50,100]">Users per page</clr-dg-page-size>
|
||||||
|
{{pagination.firstItem + 1}} - {{pagination.lastItem + 1}}
|
||||||
|
of {{pagination.totalItems}} users
|
||||||
|
</clr-dg-pagination>
|
||||||
</clr-dg-footer>
|
</clr-dg-footer>
|
||||||
</clr-datagrid>
|
</clr-datagrid>
|
||||||
</div>
|
</div>
|
||||||
@@ -76,9 +58,9 @@ const fb = !isDevMode() ? window["fb"] : {
|
|||||||
`
|
`
|
||||||
})
|
})
|
||||||
export class ApiclientConsumptionComponent implements OnInit {
|
export class ApiclientConsumptionComponent implements OnInit {
|
||||||
loading:boolean = false
|
loading: boolean = false
|
||||||
actionName:string = "load"
|
actionName: string = "load"
|
||||||
entries:SubscriptionResponse[] = []
|
entries: SubscriptionResponse[] = []
|
||||||
alerts: {
|
alerts: {
|
||||||
severity: "danger" | "warning" | "success"
|
severity: "danger" | "warning" | "success"
|
||||||
message: string
|
message: string
|
||||||
@@ -88,24 +70,24 @@ export class ApiclientConsumptionComponent implements OnInit {
|
|||||||
|
|
||||||
ngOnInit() { }
|
ngOnInit() { }
|
||||||
|
|
||||||
async load(){
|
async load() {
|
||||||
this.loading = true
|
this.loading = true
|
||||||
this.entries = await fb.ApiClient.getConsumptions()
|
this.entries = await fb.ApiClient.getConsumptions()
|
||||||
if(this.entries.length === 0){
|
if (this.entries.length === 0) {
|
||||||
this.alerts.push({ severity: "warning", message: "0 results were returned"})
|
this.alerts.push({ severity: "warning", message: "0 results were returned" })
|
||||||
}
|
}
|
||||||
this.loading = false
|
this.loading = false
|
||||||
this.actionName = "refresh"
|
this.actionName = "refresh"
|
||||||
}
|
}
|
||||||
|
|
||||||
async quit(uid:string){
|
async quit(uid: string) {
|
||||||
const r = await fb.ApiClient.quit(uid)
|
const r = await fb.ApiClient.quit(uid)
|
||||||
const res = parseResponse(r)
|
const res = parseResponse(r)
|
||||||
if(res instanceof SuccessResponse){
|
if (res instanceof SuccessResponse) {
|
||||||
this.alerts.push({ severity: "success", message: "Quit consuming "+uid})
|
this.alerts.push({ severity: "success", message: "Quit consuming " + uid })
|
||||||
this.load()
|
this.load()
|
||||||
}else{
|
} else {
|
||||||
this.alerts.push({ severity: "danger", message: "Error "+res.message})
|
this.alerts.push({ severity: "danger", message: "Error " + res.message })
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,35 +0,0 @@
|
|||||||
import { Component, OnInit } from '@angular/core';
|
|
||||||
import { SidebarEntry } from 'frontblock-generic/Plugin';
|
|
||||||
import { environment } from "../../environments/environment"
|
|
||||||
|
|
||||||
@Component({
|
|
||||||
selector: 'apiclient', //!!!!
|
|
||||||
template: `
|
|
||||||
<div class="clr-row">
|
|
||||||
<div class="clr-col-lg-auto">
|
|
||||||
<settings></settings>
|
|
||||||
</div>
|
|
||||||
<div class="clr-col-12 clr-col-lg-12 clr-col-xl-12 clr-col-sm-12">
|
|
||||||
<consumptions></consumptions>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="clr-row">
|
|
||||||
<div class="clr-col">
|
|
||||||
<subscriptions></subscriptions>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
`
|
|
||||||
})
|
|
||||||
export class ApiclientDevComponent implements OnInit {
|
|
||||||
|
|
||||||
constructor() { }
|
|
||||||
|
|
||||||
ngOnInit() { }
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
export const sidebarEntry:SidebarEntry = {
|
|
||||||
icon: "wrench",
|
|
||||||
route: "dev/apiclient",
|
|
||||||
text: "DEV ApiClient",
|
|
||||||
}
|
|
||||||
@@ -2,17 +2,7 @@ import { Component, OnInit } from '@angular/core';
|
|||||||
import { FrontblockApiConf } from 'frontblock';
|
import { FrontblockApiConf } from 'frontblock';
|
||||||
import { isDevMode } from '@angular/core';
|
import { isDevMode } from '@angular/core';
|
||||||
|
|
||||||
const fb = !isDevMode ? window["fb"] : {
|
declare const fb
|
||||||
ApiClient: {
|
|
||||||
setConfig: async () => {
|
|
||||||
await new Promise((resolve, reject) => {
|
|
||||||
setTimeout(() => {
|
|
||||||
resolve('Promise A win!');
|
|
||||||
}, 500)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'settings',
|
selector: 'settings',
|
||||||
|
|||||||
@@ -1,33 +1,7 @@
|
|||||||
import { Component, OnInit, isDevMode } from '@angular/core';
|
import { Component, OnInit, isDevMode } from '@angular/core';
|
||||||
import { SubscriptionResponse, SuccessResponse, parseResponse } from 'frontblock-generic/Types.js';
|
import { SubscriptionResponse, SuccessResponse, parseResponse, Coin } from 'frontblock-generic/Types.js';
|
||||||
|
import { default as uuid } from 'uuid/v4'
|
||||||
const fb = !isDevMode() ? window["fb"] : {
|
declare const fb
|
||||||
ApiClient: {
|
|
||||||
setConfig: async () => {
|
|
||||||
await new Promise((resolve, reject) => {
|
|
||||||
setTimeout(() => {
|
|
||||||
resolve('Promise A win!');
|
|
||||||
}, 500)
|
|
||||||
})
|
|
||||||
},
|
|
||||||
getConsumptions: async () => {
|
|
||||||
return await new Promise((resolve, reject) => {
|
|
||||||
setTimeout(() => {
|
|
||||||
resolve([new SubscriptionResponse("654", "321"), new SubscriptionResponse("456", "123")]);
|
|
||||||
}, 500)
|
|
||||||
})
|
|
||||||
},
|
|
||||||
getSubscriptions: async () => {
|
|
||||||
return await new Promise((resolve, reject) => {
|
|
||||||
setTimeout(() => {
|
|
||||||
resolve([new SubscriptionResponse("321"), new SubscriptionResponse("123")]);
|
|
||||||
}, 500)
|
|
||||||
})
|
|
||||||
},
|
|
||||||
unsubscribe: (uid) => new SuccessResponse(),
|
|
||||||
quit: (uid) => new SuccessResponse()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'subscriptions',
|
selector: 'subscriptions',
|
||||||
@@ -52,20 +26,34 @@ const fb = !isDevMode() ? window["fb"] : {
|
|||||||
<div class="card-text">
|
<div class="card-text">
|
||||||
<clr-datagrid>
|
<clr-datagrid>
|
||||||
<clr-dg-column>UUID</clr-dg-column>
|
<clr-dg-column>UUID</clr-dg-column>
|
||||||
|
<!--
|
||||||
|
<clr-dg-column>Currency</clr-dg-column>
|
||||||
|
<clr-dg-column>Address</clr-dg-column>
|
||||||
|
<clr-dg-column>Memo</clr-dg-column>
|
||||||
<clr-dg-column>Expiry</clr-dg-column>
|
<clr-dg-column>Expiry</clr-dg-column>
|
||||||
<clr-dg-column>Creation</clr-dg-column>
|
<clr-dg-column>Creation</clr-dg-column>
|
||||||
|
-->
|
||||||
|
|
||||||
<clr-dg-row *ngFor="let line of entries">
|
<clr-dg-row *clrDgItems="let line of entries">
|
||||||
<clr-dg-cell>{{line.uid}}</clr-dg-cell>
|
<clr-dg-cell>{{line.uid}}</clr-dg-cell>
|
||||||
<clr-dg-cell>{{line.expiry}}</clr-dg-cell>
|
<!--
|
||||||
<clr-dg-cell>{{line.created | date}}</clr-dg-cell>
|
<clr-dg-cell>{{line.currency}}</clr-dg-cell>
|
||||||
|
<clr-dg-cell>{{line.address}}</clr-dg-cell>
|
||||||
|
<clr-dg-cell>{{line.memo}}</clr-dg-cell>
|
||||||
|
<clr-dg-cell>{{line.expiry | date}}</clr-dg-cell>
|
||||||
|
<clr-dg-cell>{{line.created | date}}</clr-dg-cell>
|
||||||
|
-->
|
||||||
|
|
||||||
<clr-dg-row-detail *clrIfExpanded>
|
<clr-dg-row-detail *clrIfExpanded>
|
||||||
<button class="btn btn-icon btn-danger-outline" (click)="quit(line.uid)"><clr-icon shape="times"></clr-icon> Unsubscribe</button>
|
<button class="btn btn-icon btn-danger-outline" (click)="quit(line.uid)"><clr-icon shape="times"></clr-icon> Unsubscribe</button>
|
||||||
</clr-dg-row-detail>
|
</clr-dg-row-detail>
|
||||||
</clr-dg-row>
|
</clr-dg-row>
|
||||||
<clr-dg-footer>
|
<clr-dg-footer>
|
||||||
{{entries.length}} entries
|
<clr-dg-pagination #pagination [clrDgPageSize]="10">
|
||||||
|
<clr-dg-page-size [clrPageSizeOptions]="[10,20,50,100]">Users per page</clr-dg-page-size>
|
||||||
|
{{pagination.firstItem + 1}} - {{pagination.lastItem + 1}}
|
||||||
|
of {{pagination.totalItems}} users
|
||||||
|
</clr-dg-pagination>
|
||||||
</clr-dg-footer>
|
</clr-dg-footer>
|
||||||
</clr-datagrid>
|
</clr-datagrid>
|
||||||
</div>
|
</div>
|
||||||
@@ -81,7 +69,7 @@ const fb = !isDevMode() ? window["fb"] : {
|
|||||||
export class ApiclientSubscriptionComponent implements OnInit {
|
export class ApiclientSubscriptionComponent implements OnInit {
|
||||||
loading: boolean = false
|
loading: boolean = false
|
||||||
actionName: string = "load"
|
actionName: string = "load"
|
||||||
entries: SubscriptionResponse[] = []
|
entries: (SubscriptionResponse )[] = []
|
||||||
alerts: {
|
alerts: {
|
||||||
severity: "danger" | "warning" | "success"
|
severity: "danger" | "warning" | "success"
|
||||||
message: string
|
message: string
|
||||||
|
|||||||
@@ -2,7 +2,6 @@ import { NgModule } from '@angular/core';
|
|||||||
import { CommonModule } from '@angular/common';
|
import { CommonModule } from '@angular/common';
|
||||||
import { RouterModule } from '@angular/router';
|
import { RouterModule } from '@angular/router';
|
||||||
|
|
||||||
import { ApiclientDevComponent as ApiclientComponent } from './apiclient-dev.component';
|
|
||||||
import { ApiclientFormComponent } from './apiclient-settings-form.component';
|
import { ApiclientFormComponent } from './apiclient-settings-form.component';
|
||||||
import { ApiclientConsumptionComponent } from './apiclient-consumptions.component';
|
import { ApiclientConsumptionComponent } from './apiclient-consumptions.component';
|
||||||
import { ApiclientSubscriptionComponent } from './apiclient-subscriptions.component';
|
import { ApiclientSubscriptionComponent } from './apiclient-subscriptions.component';
|
||||||
@@ -21,12 +20,11 @@ import { FormsModule } from '@angular/forms';
|
|||||||
RouterModule.forChild([
|
RouterModule.forChild([
|
||||||
{path: "settings", component: ApiclientFormComponent},
|
{path: "settings", component: ApiclientFormComponent},
|
||||||
{path: "subscriptions", component: ApiclientSubscriptionComponent},
|
{path: "subscriptions", component: ApiclientSubscriptionComponent},
|
||||||
{path: "consumptions", component: ApiclientConsumptionComponent},
|
{path: "consumers", component: ApiclientConsumptionComponent},
|
||||||
]),
|
]),
|
||||||
],
|
],
|
||||||
exports: [RouterModule],
|
exports: [RouterModule],
|
||||||
declarations: [
|
declarations: [
|
||||||
ApiclientComponent,
|
|
||||||
ApiclientFormComponent,
|
ApiclientFormComponent,
|
||||||
ApiclientConsumptionComponent,
|
ApiclientConsumptionComponent,
|
||||||
ApiclientSubscriptionComponent
|
ApiclientSubscriptionComponent
|
||||||
@@ -42,8 +40,8 @@ export class PluginModule implements FrontendPlugin{
|
|||||||
route: "settings",
|
route: "settings",
|
||||||
text: "Settings"
|
text: "Settings"
|
||||||
},{
|
},{
|
||||||
route: "consumptions",
|
route: "consumers",
|
||||||
text: "Consumptions"
|
text: "Consumers"
|
||||||
},{
|
},{
|
||||||
route: "subscriptions",
|
route: "subscriptions",
|
||||||
text: "Subscriptions"
|
text: "Subscriptions"
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import { Component, OnInit } from '@angular/core';
|
import { Component, OnInit, isDevMode } from '@angular/core';
|
||||||
declare const fb
|
declare const fb
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'plugins', //!!!!
|
selector: 'plugins', //!!!!
|
||||||
@@ -15,35 +15,64 @@ declare const fb
|
|||||||
</clr-modal>
|
</clr-modal>
|
||||||
|
|
||||||
<div class="clr-row">
|
<div class="clr-row">
|
||||||
<div class="clr-col">
|
<div class="card clr-col-12 clr-col-sm-12 clr-col-md-12 clr-col-lg-12 clr-col-xl-12">
|
||||||
<div class="card">
|
<div class="card-header">
|
||||||
<div class="card-header">
|
Plugins
|
||||||
Plugins
|
</div>
|
||||||
</div>
|
<clr-alert *ngFor="let entry of alerts" [clrAlertType]="line.severity">
|
||||||
<div class="card-block">
|
<clr-alert-item>
|
||||||
<div class="card-text">
|
<span class="alert-text">
|
||||||
<clr-accordion>
|
{{line.message}}
|
||||||
<clr-accordion-panel *ngFor="let entry of plugins">
|
</span>
|
||||||
<clr-accordion-title>{{entry.name}}</clr-accordion-title>
|
</clr-alert-item>
|
||||||
<clr-accordion-content *clrIfExpanded>
|
</clr-alert>
|
||||||
<button *ngIf="entry.status === 'Stopped'" class="btn btn-icon btn-success-outline" (click)="start(entry.name)"><clr-icon shape="play"></clr-icon></button>
|
|
||||||
<button *ngIf="entry.status === 'Running'" class="btn btn-icon btn-warning-outline" (click)="stop(entry.name)"><clr-icon shape="stop"></clr-icon></button>
|
<div *ngIf="loading" class="card-block">
|
||||||
<button *ngIf="entry.status === 'Stopped'" class="btn btn-icon btn-danger-outline" (click)="delete(entry.name)"><clr-icon shape="trash"></clr-icon></button>
|
<span class="spinner spinner-inline"></span>
|
||||||
<button *ngIf="entry.status === 'Available'" class="btn btn-icon btn-info-outline" (click)="showConfirmation(entry.name)"><clr-icon shape="download"></clr-icon></button>
|
</div>
|
||||||
<button *ngIf="entry.available !== entry.installed && entry.status === 'Stopped'" class="btn btn-icon btn-outline " (click)="showConfirmation(entry.name)">
|
<div *ngIf="plugins.length !== 0 && !loading" class="card-block">
|
||||||
|
<div class="card-text">
|
||||||
|
<clr-datagrid>
|
||||||
|
<clr-dg-column>Name</clr-dg-column>
|
||||||
|
<clr-dg-column>Available</clr-dg-column>
|
||||||
|
<clr-dg-column>Installed</clr-dg-column>
|
||||||
|
<clr-dg-column>Status</clr-dg-column>
|
||||||
|
|
||||||
|
<clr-dg-row *clrDgItems="let line of plugins">
|
||||||
|
<clr-dg-cell>{{line.name}}</clr-dg-cell>
|
||||||
|
<clr-dg-cell>
|
||||||
|
<span *ngIf="line.installed != line.available" class="label label-light-blue">{{line.available}}</span>
|
||||||
|
<span *ngIf="line.installed == line.available">{{line.available}}</span>
|
||||||
|
</clr-dg-cell>
|
||||||
|
<clr-dg-cell>{{line.installed}}</clr-dg-cell>
|
||||||
|
<clr-dg-cell>{{line.status}}</clr-dg-cell>
|
||||||
|
|
||||||
|
<clr-dg-row-detail *clrIfExpanded>
|
||||||
|
<button *ngIf="line.status === 'Stopped'" class="btn btn-icon btn-success-outline" (click)="start(line.name)"><clr-icon shape="play"></clr-icon></button>
|
||||||
|
<button *ngIf="line.status === 'Running'" class="btn btn-icon btn-warning-outline" (click)="stop(line.name)"><clr-icon shape="pause"></clr-icon></button>
|
||||||
|
<button *ngIf="line.status === 'Stopped'" class="btn btn-icon btn-danger-outline" (click)="delete(line.name)"><clr-icon shape="trash"></clr-icon></button>
|
||||||
|
<button *ngIf="line.status === 'Available'" class="btn btn-icon btn-info-outline" (click)="showConfirmation(line.name)"><clr-icon shape="download"></clr-icon></button>
|
||||||
|
<button *ngIf="line.available !== line.installed && line.status === 'Stopped'" class="btn btn-icon btn-outline " (click)="showConfirmation(line.name)">
|
||||||
<clr-icon shape="sync"></clr-icon>
|
<clr-icon shape="sync"></clr-icon>
|
||||||
</button>
|
</button>
|
||||||
</clr-accordion-content>
|
</clr-dg-row-detail>
|
||||||
</clr-accordion-panel>
|
</clr-dg-row>
|
||||||
</clr-accordion>
|
<clr-dg-footer>
|
||||||
</div>
|
<clr-dg-pagination #pagination [clrDgPageSize]="10">
|
||||||
</div>
|
<clr-dg-page-size [clrPageSizeOptions]="[10,20,50,100]">per page</clr-dg-page-size>
|
||||||
<div class="card-footer">
|
{{pagination.firstItem + 1}} - {{pagination.lastItem + 1}}
|
||||||
<!-- <button class="btn btn-success" onclick="setup()">HELLO</button> -->
|
of {{pagination.totalItems}}
|
||||||
|
</clr-dg-pagination>
|
||||||
|
</clr-dg-footer>
|
||||||
|
</clr-datagrid>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
<div class="card-footer">
|
||||||
|
<button *ngIf="!reloading" class="btn btn-sm btn-link" (click)="reload()">Check</button>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
|
||||||
`
|
`
|
||||||
})
|
})
|
||||||
export class PluginsComponent implements OnInit {
|
export class PluginsComponent implements OnInit {
|
||||||
@@ -56,14 +85,20 @@ export class PluginsComponent implements OnInit {
|
|||||||
name: "test II: Return of test",
|
name: "test II: Return of test",
|
||||||
available: "0.0.1",
|
available: "0.0.1",
|
||||||
installed: "0.0.1",
|
installed: "0.0.1",
|
||||||
status: "Available"
|
status: "Stopped"
|
||||||
}, {
|
}, {
|
||||||
name: "test 3",
|
name: "test 3",
|
||||||
available: "0.0.2",
|
available: "0.0.2",
|
||||||
installed: "0.0.1",
|
installed: "",
|
||||||
|
status: "Available"
|
||||||
|
}, {
|
||||||
|
name: "test IV: minor bump",
|
||||||
|
available: "0.9.27",
|
||||||
|
installed: "0.0.11",
|
||||||
status: "Stopped"
|
status: "Stopped"
|
||||||
},]
|
},]
|
||||||
|
|
||||||
|
reloading = false
|
||||||
updatepending = false
|
updatepending = false
|
||||||
updateCandidate = ""
|
updateCandidate = ""
|
||||||
|
|
||||||
@@ -78,7 +113,9 @@ export class PluginsComponent implements OnInit {
|
|||||||
}
|
}
|
||||||
|
|
||||||
delete(name) {
|
delete(name) {
|
||||||
this.plugins.find((el) => el.name === name).status = "Available"
|
let plugin = this.plugins.find((el) => el.name === name)
|
||||||
|
plugin.status = "Available"
|
||||||
|
plugin.installed = ""
|
||||||
}
|
}
|
||||||
|
|
||||||
showConfirmation(name) {
|
showConfirmation(name) {
|
||||||
@@ -98,6 +135,21 @@ export class PluginsComponent implements OnInit {
|
|||||||
this.updatepending = !this.updatepending
|
this.updatepending = !this.updatepending
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async reload(){
|
||||||
|
this.reloading = true
|
||||||
|
if(isDevMode()){
|
||||||
|
await new Promise((resolve, reject) => {
|
||||||
|
setTimeout(resolve, 1000)
|
||||||
|
})
|
||||||
|
this.plugins = this.plugins.map(plugin => {
|
||||||
|
const parts = plugin.available.split('.')
|
||||||
|
plugin.available = parts[0]+"."+parts[1]+"."+(parseInt(parts[2])+1)
|
||||||
|
return plugin
|
||||||
|
})
|
||||||
|
this.reloading = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
ngOnInit() { }
|
ngOnInit() { }
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,115 @@
|
|||||||
|
|
||||||
|
|
||||||
|
import { Component, OnInit, isDevMode } from '@angular/core';
|
||||||
|
import { PluginsComponent } from './btc.component';
|
||||||
|
declare const fb
|
||||||
|
@Component({
|
||||||
|
selector: 'address-viewer', //!!!!
|
||||||
|
template: `
|
||||||
|
<div class="card">
|
||||||
|
<div class="card-header">
|
||||||
|
Addresses
|
||||||
|
</div>
|
||||||
|
<clr-alert *ngFor="let entry of alerts" [clrAlertType]="line.severity">
|
||||||
|
<clr-alert-item>
|
||||||
|
<span class="alert-text">
|
||||||
|
{{line.message}}
|
||||||
|
</span>
|
||||||
|
</clr-alert-item>
|
||||||
|
</clr-alert>
|
||||||
|
|
||||||
|
<clr-datagrid class="datagrid-compact">
|
||||||
|
<clr-dg-column>Address</clr-dg-column>
|
||||||
|
<clr-dg-column>Balance</clr-dg-column>
|
||||||
|
<clr-dg-column>Pending</clr-dg-column>
|
||||||
|
<clr-dg-column>Estimate total</clr-dg-column>
|
||||||
|
|
||||||
|
<clr-dg-row *clrDgItems="let line of regularAddresses">
|
||||||
|
<clr-dg-cell style="word-break: break-all">{{line.address}}</clr-dg-cell>
|
||||||
|
<clr-dg-cell>{{line.confirmed.balance}}</clr-dg-cell>
|
||||||
|
<clr-dg-cell>{{line.unconfirmed.balance}}</clr-dg-cell>
|
||||||
|
<clr-dg-cell>{{line.total.balance}}</clr-dg-cell>
|
||||||
|
<clr-dg-row-detail *clrIfExpanded >
|
||||||
|
<div class="clr-row">
|
||||||
|
<div class="clr-col-3">
|
||||||
|
<button class="btn btn-info-outline">Explorer</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div *ngIf="parent.keys[line.address] != null" class="clr-col-9" style="word-break:break-all">
|
||||||
|
<h6 style="margin-top: 0"> Public key </h6>
|
||||||
|
{{parent.keys[line.address].publicExtendedKey}}
|
||||||
|
<h6> Private key </h6>
|
||||||
|
{{parent.keys[line.address].privateExtendedKey}}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</clr-dg-row-detail>
|
||||||
|
</clr-dg-row>
|
||||||
|
|
||||||
|
<clr-dg-footer>
|
||||||
|
<clr-dg-pagination #pagination [clrDgPageSize]="5">
|
||||||
|
<clr-dg-page-size [clrPageSizeOptions]="[5,10,20,50,100]">per page</clr-dg-page-size>
|
||||||
|
{{pagination.firstItem + 1}} - {{pagination.lastItem + 1}}
|
||||||
|
of {{pagination.totalItems}}
|
||||||
|
</clr-dg-pagination>
|
||||||
|
</clr-dg-footer>
|
||||||
|
</clr-datagrid>
|
||||||
|
|
||||||
|
<clr-datagrid class="datagrid-compact">
|
||||||
|
<clr-dg-column>Address</clr-dg-column>
|
||||||
|
<clr-dg-column>Balance</clr-dg-column>
|
||||||
|
<clr-dg-column>Pending</clr-dg-column>
|
||||||
|
<clr-dg-column>Estimate total</clr-dg-column>
|
||||||
|
|
||||||
|
<clr-dg-row *clrDgItems="let line of changeAddresses">
|
||||||
|
<clr-dg-cell style="word-break: break-all">{{line.address}}</clr-dg-cell>
|
||||||
|
<clr-dg-cell>{{line.confirmed.balance}}</clr-dg-cell>
|
||||||
|
<clr-dg-cell>{{line.unconfirmed.balance}}</clr-dg-cell>
|
||||||
|
<clr-dg-cell>{{line.total.balance}}</clr-dg-cell>
|
||||||
|
<clr-dg-row-detail *clrIfExpanded >
|
||||||
|
<div class="clr-row">
|
||||||
|
<div class="clr-col-3">
|
||||||
|
<button class="btn btn-info-outline">Explorer</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div *ngIf="parent.keys[line.address] != null" class="clr-col-9" style="word-break:break-all">
|
||||||
|
<h6 style="margin-top: 0"> Public key </h6>
|
||||||
|
{{parent.keys[line.address].publicExtendedKey}}
|
||||||
|
<h6> Private key </h6>
|
||||||
|
{{parent.keys[line.address].privateExtendedKey}}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</clr-dg-row-detail>
|
||||||
|
</clr-dg-row>
|
||||||
|
|
||||||
|
<clr-dg-footer>
|
||||||
|
<clr-dg-pagination #pagination [clrDgPageSize]="5">
|
||||||
|
<clr-dg-page-size [clrPageSizeOptions]="[5,10,20,50,100]">per page</clr-dg-page-size>
|
||||||
|
{{pagination.firstItem + 1}} - {{pagination.lastItem + 1}}
|
||||||
|
of {{pagination.totalItems}}
|
||||||
|
</clr-dg-pagination>
|
||||||
|
</clr-dg-footer>
|
||||||
|
</clr-datagrid>
|
||||||
|
</div>
|
||||||
|
`
|
||||||
|
})
|
||||||
|
export class AddressViewerComponent implements OnInit {
|
||||||
|
|
||||||
|
regularAddresses = []
|
||||||
|
changeAddresses = []
|
||||||
|
keys = {}
|
||||||
|
parent:PluginsComponent
|
||||||
|
ngOnInit() { }
|
||||||
|
|
||||||
|
setParent(parent:PluginsComponent){
|
||||||
|
this.parent = parent
|
||||||
|
}
|
||||||
|
|
||||||
|
setRegularAddresses(addresses){
|
||||||
|
this.regularAddresses = addresses
|
||||||
|
}
|
||||||
|
|
||||||
|
setChangeAddresses(addresses){
|
||||||
|
this.changeAddresses = addresses
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@@ -0,0 +1,508 @@
|
|||||||
|
import { Component, OnInit, isDevMode, ViewChild, AfterViewInit } 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 fetch = require("node-fetch")
|
||||||
|
const coinselect = require('coinselect/accumulative')
|
||||||
|
import { TransactionBuilder } from 'bitcoinjs-lib'
|
||||||
|
import { networks } from 'bitcoinjs-lib'
|
||||||
|
let feeRate = 55 // satoshis per byte
|
||||||
|
|
||||||
|
const hdkey = new HDKey()
|
||||||
|
declare const fb
|
||||||
|
@Component({
|
||||||
|
selector: 'plugins', //!!!!
|
||||||
|
template: `
|
||||||
|
|
||||||
|
<wallet-generator></wallet-generator>
|
||||||
|
|
||||||
|
<!-- row 1 -->
|
||||||
|
<div class="clr-col-12 clr-col-sm-12 clr-col-md-12 clr-col-lg-12 clr-col-xl-12">
|
||||||
|
|
||||||
|
<!-- wallet picker + send box + balance box -->
|
||||||
|
<div class="clr-row">
|
||||||
|
|
||||||
|
<!-- left column -->
|
||||||
|
<div class="clr-col-12 clr-col-sm-12 clr-col-md-12 clr-col-lg-5 clr-col-xl-5">
|
||||||
|
|
||||||
|
<!-- Wallet picker -->
|
||||||
|
<div class="clr-row">
|
||||||
|
<walletpicker class="clr-col-12 clr-col-sm-12 clr-col-md-12 clr-col-lg-12 clr-col-xl-12"></walletpicker>
|
||||||
|
</div>
|
||||||
|
<!-- /Wallet picker -->
|
||||||
|
|
||||||
|
<!-- Send box -->
|
||||||
|
<div class="clr-row">
|
||||||
|
|
||||||
|
<div class="clr-col-12 clr-col-sm-12 clr-col-md-12 clr-col-lg-12 clr-col-xl-12">
|
||||||
|
<div class="card">
|
||||||
|
<div class="card-header">
|
||||||
|
Send
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="card-block" >
|
||||||
|
<input [(ngModel)]="sendTo" type="text" placeholder="To address" clrInput />
|
||||||
|
<input [(ngModel)]="sendAmount" type="number" min="0" placeholder="Amount" clrInput />
|
||||||
|
|
||||||
|
</div>
|
||||||
|
<div class="card-footer" >
|
||||||
|
<button class="btn btn-success-outline" (click)="createTx(sendTo, sendAmount)">send</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
<!-- /Send Box -->
|
||||||
|
|
||||||
|
</div>
|
||||||
|
<!-- /left column -->
|
||||||
|
|
||||||
|
<!-- right column -->
|
||||||
|
<div class="clr-col-12 clr-col-sm-12 clr-col-md-12 clr-col-lg-7 clr-col-xl-7" >
|
||||||
|
|
||||||
|
<!-- Balance box -->
|
||||||
|
<div class="card">
|
||||||
|
<div class="card-header" style="word-break: break-all">
|
||||||
|
{{rootkey.publicExtendedKey}}
|
||||||
|
</div>
|
||||||
|
<clr-alert *ngFor="let entry of alerts" [clrAlertType]="line.severity">
|
||||||
|
<clr-alert-item>
|
||||||
|
<span class="alert-text">
|
||||||
|
{{line.message}}
|
||||||
|
</span>
|
||||||
|
</clr-alert-item>
|
||||||
|
</clr-alert>
|
||||||
|
|
||||||
|
<div class="card-block" >
|
||||||
|
<h3 style="margin-top:12px">Balance: {{balance.confirmed}}</h3>
|
||||||
|
<p>
|
||||||
|
Unconfirmed: {{balance.unconfirmed}}<br />
|
||||||
|
Estimated total: {{balance.total}}
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<!-- Balance box-->
|
||||||
|
|
||||||
|
</div>
|
||||||
|
<!-- right column -->
|
||||||
|
|
||||||
|
</div>
|
||||||
|
<!-- /row wallet picker + send box + balance box -->
|
||||||
|
|
||||||
|
<div class="clr-col-12 clr-col-sm-12 clr-col-md-12 clr-col-lg-12 clr-col-xl-12" style="padding-left:0px; padding-right: 0px">
|
||||||
|
<address-viewer></address-viewer>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<!-- /row 1 -->
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
`
|
||||||
|
})
|
||||||
|
export class PluginsComponent implements AfterViewInit {
|
||||||
|
|
||||||
|
wallets:{
|
||||||
|
[someLabel in string]:{
|
||||||
|
key:string
|
||||||
|
name:string
|
||||||
|
}
|
||||||
|
} = {}
|
||||||
|
txToSend:any
|
||||||
|
rootkey = {
|
||||||
|
publicExtendedKey: "",
|
||||||
|
privateExtendedKey: ""
|
||||||
|
}
|
||||||
|
keys = {}
|
||||||
|
regaddresses = []
|
||||||
|
changeaddresses = []
|
||||||
|
balance = {
|
||||||
|
confirmed: 0,
|
||||||
|
total: 0,
|
||||||
|
unconfirmed: 0
|
||||||
|
}
|
||||||
|
mode : {
|
||||||
|
net:network
|
||||||
|
} = {
|
||||||
|
net: "testnet"
|
||||||
|
}
|
||||||
|
sendto:string
|
||||||
|
amount:number
|
||||||
|
|
||||||
|
constructor(){
|
||||||
|
window["btc"] = () => this
|
||||||
|
}
|
||||||
|
@ViewChild(AddressViewerComponent, {static: false})
|
||||||
|
AddressViewerComponent:AddressViewerComponent
|
||||||
|
|
||||||
|
@ViewChild(WalletGeneratorComponent, {static: false})
|
||||||
|
WalletGeneratorComponent:WalletGeneratorComponent
|
||||||
|
|
||||||
|
@ViewChild(WalletPickerComponent, {static: false})
|
||||||
|
WalletPickerComponent:WalletPickerComponent
|
||||||
|
|
||||||
|
ngAfterViewInit(): void {
|
||||||
|
this.WalletGeneratorComponent.setParent(this)
|
||||||
|
this.WalletPickerComponent.setParent(this)
|
||||||
|
this.AddressViewerComponent.setParent(this)
|
||||||
|
|
||||||
|
//tprv8cXm1S7PGS2fHuLjZajuDZZsvxerzv4zZJJAhkLAWZzwz5rE7efHm31xsqpzfoGMHBNC8bkRcsjiMdmkSuNn5C1qNqk7rrk1pCuhk4HfVak
|
||||||
|
//tpubD9Do9r9dQoiLBNNXTEQVcyDzVzAoAFFu8btwzGNTvqoLpa6zk3UswXdq3zeArrakUu8huCtG1L5pTqNYNftanuutf57fn1C74rBzFybH1xe
|
||||||
|
this.loadKey("tprv8cXm1S7PGS2fHuLjZajuDZZsvxerzv4zZJJAhkLAWZzwz5rE7efHm31xsqpzfoGMHBNC8bkRcsjiMdmkSuNn5C1qNqk7rrk1pCuhk4HfVak")
|
||||||
|
}
|
||||||
|
|
||||||
|
addWallet(wallet){
|
||||||
|
/* store somewhere */
|
||||||
|
|
||||||
|
this.wallets[wallet.name] = wallet
|
||||||
|
this.WalletPickerComponent.setWallets(this.wallets)
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
showWizard(){
|
||||||
|
this.WalletGeneratorComponent.open()
|
||||||
|
}
|
||||||
|
|
||||||
|
loadKey(key:string){
|
||||||
|
this.wallets = {}
|
||||||
|
this.txToSend = null
|
||||||
|
this.sendto = ""
|
||||||
|
this.regaddresses = []
|
||||||
|
|
||||||
|
const t = hdkey.checkHDKey(key)
|
||||||
|
if(t){
|
||||||
|
this.mode = t
|
||||||
|
}
|
||||||
|
|
||||||
|
this.rootkey = hdkey.HDKeyFromExtendedKey(key)
|
||||||
|
this.refresh()
|
||||||
|
}
|
||||||
|
|
||||||
|
async refresh(){
|
||||||
|
this.keys = {}
|
||||||
|
this.changeaddresses = []
|
||||||
|
this.regaddresses = []
|
||||||
|
this.AddressViewerComponent.setChangeAddresses([])
|
||||||
|
this.AddressViewerComponent.setRegularAddresses([])
|
||||||
|
this.balance.total = 0
|
||||||
|
this.balance.unconfirmed = 0
|
||||||
|
this.balance.confirmed = 0
|
||||||
|
|
||||||
|
let p1 = this.updateChange()
|
||||||
|
let p2 = this.update()
|
||||||
|
|
||||||
|
let balances:any[] = await Promise.all([p1,p2])
|
||||||
|
balances.forEach(balance => {
|
||||||
|
this.balance.confirmed += balance.confirmed
|
||||||
|
this.balance.total += balance.total
|
||||||
|
this.balance.unconfirmed += balance.unconfirmed
|
||||||
|
})
|
||||||
|
|
||||||
|
console.log(this.changeaddresses)
|
||||||
|
|
||||||
|
this.AddressViewerComponent.setChangeAddresses(this.changeaddresses)
|
||||||
|
this.AddressViewerComponent.setRegularAddresses(this.regaddresses)
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
async getInfo(rootAddr: string, n = 10, skip = 0, changeAddresses = false): Promise<any>{
|
||||||
|
const t = hdkey.checkHDKey(rootAddr)
|
||||||
|
if(!t){
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
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 false
|
||||||
|
}
|
||||||
|
addresses.push(addr)
|
||||||
|
}
|
||||||
|
let lookupstr = addresses.join(',')
|
||||||
|
try{
|
||||||
|
let r = await fetch(api[t.net]+'/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 getTxs(address: string, type:network){
|
||||||
|
let r = await fetch(api[type]+'/address/'+address, {
|
||||||
|
'method':'GET',
|
||||||
|
'mode':'cors'
|
||||||
|
})
|
||||||
|
if(r.status !== 200){
|
||||||
|
return {success: false}
|
||||||
|
}
|
||||||
|
|
||||||
|
return (await r).json()
|
||||||
|
}
|
||||||
|
|
||||||
|
async getUnspentTxs(addresses:string[], type:network):Promise<any>{
|
||||||
|
let lookupstr = addresses.join(',')
|
||||||
|
try{
|
||||||
|
let r = await fetch(api[type]+'/address/'+lookupstr+'/unspent', {
|
||||||
|
'method':'GET',
|
||||||
|
'mode':'cors'
|
||||||
|
})
|
||||||
|
if(r.status !== 200){
|
||||||
|
return {success: false}
|
||||||
|
}
|
||||||
|
|
||||||
|
return (await r).json()
|
||||||
|
}catch(e){
|
||||||
|
return {success: false}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
getKeyForAddress(keyName){
|
||||||
|
return this.keys[keyName]
|
||||||
|
}
|
||||||
|
|
||||||
|
async update(){
|
||||||
|
let n = 1
|
||||||
|
let regAddresses
|
||||||
|
|
||||||
|
while(true){
|
||||||
|
try{
|
||||||
|
regAddresses = await this.getInfo(this.rootkey.publicExtendedKey, 115*n, 115*(n-1))
|
||||||
|
}catch(e){
|
||||||
|
break
|
||||||
|
}
|
||||||
|
if(regAddresses.success === true){
|
||||||
|
if('addresses' in regAddresses){ // multiple
|
||||||
|
this.regaddresses = [...this.regaddresses, ...regAddresses.addresses]
|
||||||
|
}
|
||||||
|
if('address' in regAddresses){
|
||||||
|
this.regaddresses.push(regAddresses.address)
|
||||||
|
}
|
||||||
|
n++
|
||||||
|
}else{
|
||||||
|
console.log('No more balance found after attempting ', 115*n, 'addresses')
|
||||||
|
if(this.rootkey.privateExtendedKey != null){
|
||||||
|
for(let i = 0; i < 115*n; i++){
|
||||||
|
const keyN = hdkey.deriveKey(this.rootkey.privateExtendedKey, i)
|
||||||
|
const addrN = hdkey.addressFromHDKey(keyN._publicKey, this.mode.net)
|
||||||
|
this.keys[addrN] = keyN
|
||||||
|
}
|
||||||
|
}
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if(this.regaddresses.length === 0){
|
||||||
|
const addr = hdkey.deriveAddress(this.rootkey.publicExtendedKey, 0)
|
||||||
|
this.regaddresses.push({
|
||||||
|
address: addr,
|
||||||
|
confirmed: {balance: 0},
|
||||||
|
unconfirmed: {balance: 0},
|
||||||
|
total: {balance: 0},
|
||||||
|
})
|
||||||
|
return {
|
||||||
|
total: 0,
|
||||||
|
unconfirmed: 0,
|
||||||
|
confirmed: 0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
const resMap: any = {}
|
||||||
|
this.regaddresses.forEach(a => {
|
||||||
|
resMap[a.address] = a
|
||||||
|
})
|
||||||
|
|
||||||
|
const confirmed = <number>Object.values(resMap).reduce((accum:any, obj:any) => {
|
||||||
|
return accum + parseFloat(obj.confirmed.balance)
|
||||||
|
}, 0)
|
||||||
|
const unconfirmed = <number>Object.values(resMap).reduce((accum:any, obj:any) => {
|
||||||
|
return accum + parseFloat(obj.unconfirmed.balance)
|
||||||
|
}, 0)
|
||||||
|
|
||||||
|
return {
|
||||||
|
total: confirmed+unconfirmed,
|
||||||
|
unconfirmed: unconfirmed,
|
||||||
|
confirmed: confirmed
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async updateChange(){
|
||||||
|
let n = 1
|
||||||
|
let res
|
||||||
|
|
||||||
|
while(true){
|
||||||
|
try{
|
||||||
|
res = await this.getInfo(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.log('No more balance found after attempting ', 115*n, 'addresses')
|
||||||
|
if(this.rootkey.privateExtendedKey != null){
|
||||||
|
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)
|
||||||
|
this.keys[addrN] = keyN
|
||||||
|
}
|
||||||
|
}
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if(this.changeaddresses.length === 0){
|
||||||
|
const addr = hdkey.deriveAddress(this.rootkey.publicExtendedKey, 0, 'm/1/')
|
||||||
|
this.changeaddresses.push({
|
||||||
|
address: addr,
|
||||||
|
confirmed: {balance: 0},
|
||||||
|
unconfirmed: {balance: 0},
|
||||||
|
total: {balance: 0},
|
||||||
|
})
|
||||||
|
return {
|
||||||
|
total: 0,
|
||||||
|
unconfirmed: 0,
|
||||||
|
confirmed: 0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const resMap: any = {}
|
||||||
|
this.changeaddresses.forEach(a => {
|
||||||
|
resMap[a.address] = a
|
||||||
|
})
|
||||||
|
|
||||||
|
const confirmed = <number>Object.values(resMap).reduce((accum:any, obj:any) => {
|
||||||
|
return accum + parseFloat(obj.confirmed.balance)
|
||||||
|
}, 0)
|
||||||
|
const unconfirmed = <number>Object.values(resMap).reduce((accum:any, obj:any) => {
|
||||||
|
return accum + parseFloat(obj.unconfirmed.balance)
|
||||||
|
}, 0)
|
||||||
|
|
||||||
|
return {
|
||||||
|
confirmed: confirmed,
|
||||||
|
unconfirmed: unconfirmed,
|
||||||
|
total: unconfirmed + confirmed
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async createTx(target:string, value:number){
|
||||||
|
|
||||||
|
console.log(target, value)
|
||||||
|
|
||||||
|
if(target == null || typeof target !== 'string' || target === '' || value == null || value <= 0 ){
|
||||||
|
//this.error = 'Bad inputs. Expected bitcoin address and number'
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if(value > this.balance.confirmed){
|
||||||
|
//this.error = 'Insufficient funds'
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
let addresses: string[] = this.regaddresses.map(a => a.address)
|
||||||
|
addresses = [...addresses, ...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.error = 'No UTXOs found'
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
const utxos = txs.unspent.map(t => {
|
||||||
|
return {
|
||||||
|
address: t.addresses[0],
|
||||||
|
txId: t.txid,
|
||||||
|
vout: t.n,
|
||||||
|
value: t.value * 100000000
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
let { inputs, outputs, fee } = coinselect(utxos, [{address: target, value: value*100000000}], feeRate)
|
||||||
|
|
||||||
|
// .inputs and .outputs will be undefined if no solution was found
|
||||||
|
if (!inputs || !outputs){
|
||||||
|
//this.error = 'Could not build a valid transaction'
|
||||||
|
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])
|
||||||
|
})
|
||||||
|
|
||||||
|
this.txToSend = txb.build()
|
||||||
|
|
||||||
|
this.sendTx()
|
||||||
|
}
|
||||||
|
|
||||||
|
async sendTx(){
|
||||||
|
if(this.txToSend == null){
|
||||||
|
//this.error = 'No transaction to send!'
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
let url
|
||||||
|
if(this.mode.net === 'mainnet'){
|
||||||
|
url = 'https://api.smartbit.com.au/v1/blockchain/pushtx'
|
||||||
|
}else{
|
||||||
|
url = 'https://testnet-api.smartbit.com.au/v1/blockchain/pushtx'
|
||||||
|
}
|
||||||
|
let r = await fetch(url, {
|
||||||
|
'method':'POST',
|
||||||
|
'body': JSON.stringify({'hex': this.txToSend.toHex()})
|
||||||
|
})
|
||||||
|
this.txToSend = null
|
||||||
|
if(r.status === 200){
|
||||||
|
console.log(r)
|
||||||
|
this.refresh()
|
||||||
|
}else{
|
||||||
|
//this.error = 'The API '+url+' returned a non-200 error code '+r.status+' '+r.statusText
|
||||||
|
console.log(r)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
type network = 'testnet' | 'mainnet'
|
||||||
|
type keyType = 'public' | 'private'
|
||||||
|
|
||||||
|
type bip32Keypair = { publicExtendedKey: string, privateExtendedKey: string }
|
||||||
|
|
||||||
|
const api = {
|
||||||
|
testnet: 'https://testnet-api.smartbit.com.au/v1/blockchain',
|
||||||
|
mainnet: 'https://api.smartbit.com.au/v1/blockchain'
|
||||||
|
}
|
||||||
@@ -0,0 +1,265 @@
|
|||||||
|
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: `
|
||||||
|
<clr-wizard #wizard [(clrWizardOpen)]="mdOpen" [clrWizardForceForwardNavigation]="true" clrWizardSize="lg">
|
||||||
|
<clr-wizard-title>Bitcoin wallet generator and importer</clr-wizard-title>
|
||||||
|
|
||||||
|
<clr-wizard-button [type]="'cancel'">Cancel</clr-wizard-button>
|
||||||
|
<clr-wizard-button [type]="'previous'">Back</clr-wizard-button>
|
||||||
|
<clr-wizard-button [type]="'next'">Next</clr-wizard-button>
|
||||||
|
<clr-wizard-button [type]="'finish'">Finish</clr-wizard-button>
|
||||||
|
|
||||||
|
<clr-wizard-page (clrWizardPageCustomButton)="doCustomClick($event)">
|
||||||
|
<ng-template clrPageTitle>Menemonic/BIP39 (optional)</ng-template>
|
||||||
|
<ng-template clrPageNavTitle>
|
||||||
|
Menemonic/BIP39
|
||||||
|
</ng-template>
|
||||||
|
<p>
|
||||||
|
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.
|
||||||
|
</p>
|
||||||
|
<p>
|
||||||
|
Enter your menemonic below or generate a new one.
|
||||||
|
</p>
|
||||||
|
<textarea [(ngModel)]="menemonic" class="clr-textarea" style="margin-top: 24px; width: 100%; height: 200px">{{menemonic}}</textarea>
|
||||||
|
<div class="clr-row">
|
||||||
|
<clr-checkbox-wrapper class="clr-col">
|
||||||
|
<input type="checkbox" clrCheckbox value="testnet" name="testnet1" [(ngModel)]="testnet" />
|
||||||
|
<label>testnet</label>
|
||||||
|
</clr-checkbox-wrapper>
|
||||||
|
<button class="btn btn-outline-success clr-col" (click)="generateMenemonic()">{{"Generate"}}</button>
|
||||||
|
</div>
|
||||||
|
<ng-template clrPageButtons>
|
||||||
|
<clr-wizard-button [type]="'cancel'">Cancel</clr-wizard-button>
|
||||||
|
<clr-wizard-button [type]="'menemonic-next'">{{menemonic.length==0?"Skip":"Next"}}</clr-wizard-button>
|
||||||
|
</ng-template>
|
||||||
|
</clr-wizard-page>
|
||||||
|
|
||||||
|
<clr-wizard-page (clrWizardPageCustomButton)="doCustomClick($event)">
|
||||||
|
<ng-template clrPageTitle>Seed phrase (optional)</ng-template>
|
||||||
|
<ng-template clrPageNavTitle>
|
||||||
|
Seed phrase
|
||||||
|
</ng-template>
|
||||||
|
<p>
|
||||||
|
If you entered a menemonic in the previous step, a seed was generated from it.
|
||||||
|
</p>
|
||||||
|
<p>
|
||||||
|
If you would like to use your own seed enter it below. Use something sufficiently hard to guess.
|
||||||
|
</p>
|
||||||
|
<textarea class="clr-textarea" (ngModelChange)="updateSeed()" [(ngModel)]="seed" style="margin-top: 24px; width: 100%; height: 200px">{{seed}}</textarea>
|
||||||
|
<clr-checkbox-wrapper>
|
||||||
|
<input type="checkbox" clrCheckbox value="testnet" name="testnet2" [(ngModel)]="testnet" />
|
||||||
|
<label>testnet</label>
|
||||||
|
</clr-checkbox-wrapper>
|
||||||
|
<ng-template clrPageButtons>
|
||||||
|
<clr-wizard-button [type]="'cancel'">Cancel</clr-wizard-button>
|
||||||
|
<clr-wizard-button [type]="'back'">Back</clr-wizard-button>
|
||||||
|
<clr-wizard-button [type]="'seed-next'">{{seed.length==0?"Skip":"Next"}}</clr-wizard-button>
|
||||||
|
</ng-template>
|
||||||
|
</clr-wizard-page>
|
||||||
|
|
||||||
|
<clr-wizard-page [clrWizardPageNextDisabled]="keyInvalid" (clrWizardPageCustomButton)="doCustomClick($event)">
|
||||||
|
<ng-template clrPageTitle>Extended Key/BIP32</ng-template>
|
||||||
|
<p>
|
||||||
|
Accepts keys in the xpub and xprv (BIP32) format
|
||||||
|
</p>
|
||||||
|
<textarea [(ngModel)]="key" (ngModelChange)="updateKey()"class="clr-textarea" style="margin-top: 24px; width: 100%; height: 200px"></textarea>
|
||||||
|
</clr-wizard-page>
|
||||||
|
|
||||||
|
<clr-wizard-page class="break-word" (clrWizardPageCustomButton)="doCustomClick($event)">
|
||||||
|
<ng-template clrPageTitle>Confirm</ng-template>
|
||||||
|
|
||||||
|
<div class="alert alert-danger" role="alert">
|
||||||
|
<div class="alert-items">
|
||||||
|
<div class="alert-item static">
|
||||||
|
<div class="alert-icon-wrapper">
|
||||||
|
<clr-icon class="alert-icon" shape="exclamation-circle"></clr-icon>
|
||||||
|
</div>
|
||||||
|
<span class="alert-text">
|
||||||
|
Do not give any of this information to other people! Even your public key holds the power to correlate all your addresses back to you!
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div *ngIf="keypair.privateExtendedKey != null && keypair.privateExtendedKey.length != 0" class="alert alert-warning" role="alert">
|
||||||
|
<div class="alert-items">
|
||||||
|
<div class="alert-item static">
|
||||||
|
<div class="alert-icon-wrapper">
|
||||||
|
<clr-icon class="alert-icon" shape="exclamation-circle"></clr-icon>
|
||||||
|
</div>
|
||||||
|
<span class="alert-text">
|
||||||
|
WARNING: A private key is not required for operating frontblock
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div *ngIf="keypair.privateExtendedKey != null && keypair.privateExtendedKey.length != 0" class="alert alert-info" role="alert">
|
||||||
|
<div class="alert-items">
|
||||||
|
<div class="alert-item static">
|
||||||
|
<div class="alert-icon-wrapper">
|
||||||
|
<clr-icon class="alert-icon" shape="exclamation-circle"></clr-icon>
|
||||||
|
</div>
|
||||||
|
<span class="alert-text">
|
||||||
|
HINT: You can exclusively store the public key by going back one step and importing the xpub you just created
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<table class="table">
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th class="left">Type</th>
|
||||||
|
<th class="left">Value</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
<tr *ngIf="menemonic.length != 0">
|
||||||
|
<td class="left"> menemonic</td>
|
||||||
|
<td class="left">{{menemonic}}</td>
|
||||||
|
</tr>
|
||||||
|
<tr *ngIf="seed.length != 0">
|
||||||
|
<td class="left"> seed</td>
|
||||||
|
<textarea rows="3" class="clr-textarea left" [ngModel]="seed" style="width: 100%"></textarea>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td class="left"> xpub</td>
|
||||||
|
<textarea rows="3" class="clr-textarea left" [ngModel]="keypair.publicExtendedKey" style="width: 100%"></textarea>
|
||||||
|
</tr>
|
||||||
|
<tr *ngIf="keypair.privateExtendedKey != null && keypair.privateExtendedKey.length != 0">
|
||||||
|
<td class="left"> xprv</td>
|
||||||
|
<textarea rows="3" class="clr-textarea left" [ngModel]="keypair.privateExtendedKey" style="width: 100%"></textarea>
|
||||||
|
</tr>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
<clr-checkbox-wrapper>
|
||||||
|
<input type="checkbox" clrCheckbox value="option1" name="options" />
|
||||||
|
<label>I understand this is important</label>
|
||||||
|
</clr-checkbox-wrapper>
|
||||||
|
<ng-template clrPageButtons>
|
||||||
|
<clr-wizard-button [type]="'cancel'">Cancel</clr-wizard-button>
|
||||||
|
<clr-wizard-button class="btn btn-success" [type]="'myfinish'">Finish</clr-wizard-button>
|
||||||
|
</ng-template>
|
||||||
|
</clr-wizard-page>
|
||||||
|
</clr-wizard>
|
||||||
|
|
||||||
|
`
|
||||||
|
})
|
||||||
|
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
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,50 @@
|
|||||||
|
|
||||||
|
|
||||||
|
import { Component, OnInit, isDevMode } from '@angular/core';
|
||||||
|
import { PluginsComponent } from './btc.component';
|
||||||
|
declare const fb
|
||||||
|
@Component({
|
||||||
|
selector: 'walletpicker', //!!!!
|
||||||
|
|
||||||
|
template: `
|
||||||
|
<div class="card" style="overflow:hidden">
|
||||||
|
<div class="card-header">
|
||||||
|
Wallets
|
||||||
|
</div>
|
||||||
|
<clr-alert *ngFor="let entry of alerts" [clrAlertType]="line.severity">
|
||||||
|
<clr-alert-item>
|
||||||
|
<span class="alert-text">
|
||||||
|
{{line.message}}
|
||||||
|
</span>
|
||||||
|
</clr-alert-item>
|
||||||
|
</clr-alert>
|
||||||
|
|
||||||
|
<clr-tree-node style="margin-top:12px; margin-bottom:12px">
|
||||||
|
<a style="cursor:pointer" (click)="parent.showWizard()"><clr-icon shape="download" class="is-solid"></clr-icon> New / Import</a>
|
||||||
|
</clr-tree-node>
|
||||||
|
<clr-tree-node *ngFor="let line of wallets">
|
||||||
|
<a style="cursor:pointer" (click)="parent.loadKey(line.key)">
|
||||||
|
<clr-icon *ngIf="line.key.startsWith('xpub') || line.key.startsWith('tpub')" shape="lock" class="is-solid"></clr-icon>
|
||||||
|
<clr-icon *ngIf="line.key.startsWith('xprv') || line.key.startsWith('tprv')" shape="key" class="is-solid"></clr-icon>
|
||||||
|
{{line.name}}
|
||||||
|
</a>
|
||||||
|
</clr-tree-node>
|
||||||
|
</div>
|
||||||
|
`
|
||||||
|
})
|
||||||
|
export class WalletPickerComponent implements OnInit {
|
||||||
|
private parent:PluginsComponent
|
||||||
|
wallets = []
|
||||||
|
|
||||||
|
setWallets(wallets){
|
||||||
|
this.wallets = Object.values(wallets)
|
||||||
|
}
|
||||||
|
|
||||||
|
setParent(parent:PluginsComponent){
|
||||||
|
this.parent = parent
|
||||||
|
}
|
||||||
|
|
||||||
|
ngOnInit() { }
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
@@ -0,0 +1,60 @@
|
|||||||
|
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 { FrontendPlugin, SidebarEntries } from 'frontblock-generic/Plugin';
|
||||||
|
import { ClarityModule } from '@clr/angular';
|
||||||
|
import { FormsModule } from '@angular/forms';
|
||||||
|
import { WalletGeneratorComponent } from './btc/wallet-generator.component';
|
||||||
|
|
||||||
|
|
||||||
|
@NgModule({
|
||||||
|
imports: [
|
||||||
|
FormsModule,
|
||||||
|
ClarityModule,
|
||||||
|
|
||||||
|
CommonModule,
|
||||||
|
RouterModule.forChild([
|
||||||
|
{path: "btc", component: PluginsComponent},
|
||||||
|
]),
|
||||||
|
],
|
||||||
|
exports: [RouterModule],
|
||||||
|
declarations: [
|
||||||
|
PluginsComponent,
|
||||||
|
WalletPickerComponent,
|
||||||
|
AddressViewerComponent,
|
||||||
|
WalletGeneratorComponent
|
||||||
|
]
|
||||||
|
})
|
||||||
|
export class PluginModule implements FrontendPlugin{
|
||||||
|
getSidebarEntry(): SidebarEntries {
|
||||||
|
return {
|
||||||
|
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"
|
||||||
|
}]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,3 +1,10 @@
|
|||||||
.heightmax{
|
.heightmax{
|
||||||
height: 100%
|
height: 100%
|
||||||
|
}
|
||||||
|
.break-word {
|
||||||
|
word-wrap: break-word;
|
||||||
|
}
|
||||||
|
.monospace {
|
||||||
|
font-family: monospace;
|
||||||
|
font-size: 90%
|
||||||
}
|
}
|
||||||
@@ -1,7 +1,6 @@
|
|||||||
import { Component, ChangeDetectorRef, NgZone, ViewChild, OnInit, AfterContentInit } from '@angular/core';
|
import { Component, ChangeDetectorRef, NgZone, ViewChild, OnInit, AfterContentInit, isDevMode } from '@angular/core';
|
||||||
import { SidebarComponent } from './sidebar/sidebar.component';
|
import { SidebarComponent } from './sidebar/sidebar.component';
|
||||||
import { SidebarEntryService } from './sidebar-entry-service.service';
|
import { SidebarEntryService } from './sidebar-entry-service.service';
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'app-root',
|
selector: 'app-root',
|
||||||
templateUrl: './app.component.html',
|
templateUrl: './app.component.html',
|
||||||
@@ -15,6 +14,9 @@ export class AppComponent implements AfterContentInit {
|
|||||||
|
|
||||||
constructor(private zone:NgZone, private sidebarService: SidebarEntryService){
|
constructor(private zone:NgZone, private sidebarService: SidebarEntryService){
|
||||||
window["refresh"] = setInterval(() => zone.run(()=>{ /*this triggers an angular reload*/}), 200)
|
window["refresh"] = setInterval(() => zone.run(()=>{ /*this triggers an angular reload*/}), 200)
|
||||||
|
if(isDevMode()){
|
||||||
|
require("../assets/FrontblockLib")
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
ngAfterContentInit(){
|
ngAfterContentInit(){
|
||||||
|
|||||||
@@ -1,3 +1,3 @@
|
|||||||
<div class="content-area clr-col-12 clr-col-sm-12 clr-col-lg-12 clr-col-xl-12">
|
<div style="overflow:hidden" class="content-area clr-col-12 clr-col-sm-12 clr-col-lg-12 clr-col-xl-12">
|
||||||
<router-outlet></router-outlet>
|
<router-outlet></router-outlet>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -39,12 +39,11 @@ import { AfterViewInit, Component, ViewChild } from '@angular/core';
|
|||||||
import { SidebarComponent } from '../sidebar/sidebar.component';
|
import { SidebarComponent } from '../sidebar/sidebar.component';
|
||||||
import { Router } from '@angular/router';
|
import { Router } from '@angular/router';
|
||||||
import { FrontendPlugin, SidebarEntries, SidebarEntry } from "frontblock-generic/Plugin"
|
import { FrontendPlugin, SidebarEntries, SidebarEntry } from "frontblock-generic/Plugin"
|
||||||
import { SidebarEntryService } from '../sidebar-entry-service.service';
|
|
||||||
import { environment } from "../../environments/environment"
|
import { environment } from "../../environments/environment"
|
||||||
|
|
||||||
const fb = environment.production ? window["fb"] : {
|
const fb = environment.production ? window["fb"] : {
|
||||||
PluginManager: {
|
PluginManager: {
|
||||||
getLoadedPluginNames: () => ["ApiClient", "PluginManager"]
|
getLoadedPluginNames: () => ["ApiClient", "PluginManager", "Wallet"]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -6,7 +6,7 @@ import { Component, OnInit, isDevMode } from '@angular/core';
|
|||||||
styleUrls: ['./header-bar.component.scss']
|
styleUrls: ['./header-bar.component.scss']
|
||||||
})
|
})
|
||||||
export class HeaderBarComponent implements OnInit {
|
export class HeaderBarComponent implements OnInit {
|
||||||
devmode = isDevMode()?"{ DEV }":""
|
devmode = isDevMode()?"{ ng-DEV }":""
|
||||||
constructor() { }
|
constructor() { }
|
||||||
|
|
||||||
ngOnInit() {
|
ngOnInit() {
|
||||||
|
|||||||
@@ -0,0 +1 @@
|
|||||||
|
./FrontblockLib.js
|
||||||
File diff suppressed because one or more lines are too long
@@ -61,3 +61,7 @@ import 'zone.js/dist/zone'; // Included with Angular CLI.
|
|||||||
/***************************************************************************************************
|
/***************************************************************************************************
|
||||||
* APPLICATION IMPORTS
|
* APPLICATION IMPORTS
|
||||||
*/
|
*/
|
||||||
|
// Add global to window, assigning the value of window itself.
|
||||||
|
(window as any).global = window;
|
||||||
|
global.Buffer = global.Buffer || require('buffer').Buffer;
|
||||||
|
global.process = global.process || require('process')
|
||||||
|
|||||||
Reference in New Issue
Block a user