better wallet

This commit is contained in:
peter
2019-08-16 20:46:06 +02:00
parent 193a4e8c12
commit 92666ba0c0
11 changed files with 459 additions and 286 deletions
+13
View File
@@ -3724,6 +3724,11 @@
"minimalistic-crypto-utils": "^1.0.0"
}
},
"emitter-component": {
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/emitter-component/-/emitter-component-1.1.1.tgz",
"integrity": "sha1-Bl4tvtaVm/RwZ57avq95gdEAOrY="
},
"emoji-regex": {
"version": "7.0.3",
"resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-7.0.3.tgz",
@@ -9138,6 +9143,14 @@
"resolved": "https://registry.npmjs.org/statuses/-/statuses-1.5.0.tgz",
"integrity": "sha1-Fhx9rBd2Wf2YEfQ3cfqZOBR4Yow="
},
"stream": {
"version": "0.0.2",
"resolved": "https://registry.npmjs.org/stream/-/stream-0.0.2.tgz",
"integrity": "sha1-f1Nj8Ff2WSxVlfALyAon9c7B8O8=",
"requires": {
"emitter-component": "^1.1.1"
}
},
"stream-browserify": {
"version": "2.0.2",
"resolved": "https://registry.npmjs.org/stream-browserify/-/stream-browserify-2.0.2.tgz",
+1
View File
@@ -31,6 +31,7 @@
"key-file-storage": "^2.2.4",
"node-fetch": "^2.6.0",
"rxjs": "~6.5.2",
"stream": "0.0.2",
"systemjs": "^0.21.3",
"tslib": "^1.9.0",
"uuid": "^3.3.2",
@@ -41,7 +41,7 @@ declare const fb
<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" class="label info">{{line.available}}</span>
<span *ngIf="line.installed == line.available">{{line.available}}</span>
</clr-dg-cell>
<clr-dg-cell>{{line.installed}}</clr-dg-cell>
@@ -2,98 +2,148 @@
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: `
<div class="card">
<div class="card-header">
Addresses
<div class="clr-row">
<div *ngIf="regularAddresses.length != 0" class="clr-col-12 clr-col-sm-12 clr-col-md-12 clr-col-lg-12 clr-col-xl-6">
<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-numeric-filter [clrDgNumericFilter]="otherBalanceFilter" [clrFilterValue]="[0, null]"></clr-dg-numeric-filter>
</clr-dg-column>
<clr-dg-row *clrDgItems="let line of regularAddresses">
<clr-dg-cell class="monospace" style="word-break: break-all">{{line.address}}</clr-dg-cell>
<clr-dg-cell class="monospace">{{line.confirmed.balance | number: '1.8-18'}}</clr-dg-cell>
<clr-dg-cell class="monospace">
<span *ngIf="line.unconfirmed.balance != 0">
{{line.unconfirmed.balance | number: '1.8-18'}}
</span>
</clr-dg-cell>
<clr-dg-cell class="monospace">{{line.total.balance | number: '1.8-18'}}</clr-dg-cell>
<clr-dg-row-detail *clrIfExpanded >
<div class="clr-row">
<div class="clr-col-3">
<button class="btn btn-info-outline" (click)="parent.addrExplorer(line.address)">Explorer</button>
</div>
<div *ngIf="parent.keys[line.address] != null" class="clr-col-9" style="word-break:break-all">
<h6 style="margin-top: 0">Derivation </h6>
{{parent.keys[line.address].derivation}}
<h6> 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 #pagination2 [clrDgPageSize]="10">
<clr-dg-page-size [clrPageSizeOptions]="[5,10,20,50,100]">per page</clr-dg-page-size>
{{pagination2.firstItem + 1}} - {{pagination2.lastItem + 1}}
of {{pagination2.totalItems}}
</clr-dg-pagination>
</clr-dg-footer>
</clr-datagrid>
</div>
</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>
<div *ngIf="changeAddresses.length != 0" class="clr-col-12 clr-col-sm-12 clr-col-md-12 clr-col-lg-12 clr-col-xl-6">
<div class="card">
<div class="card-header">
Change 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-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>
<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-numeric-filter [clrDgNumericFilter]="balanceFilter" [clrFilterValue]="[0.00000001, null]"></clr-dg-numeric-filter>
</clr-dg-column>
<clr-dg-row *clrDgItems="let line of changeAddresses">
<clr-dg-cell class="monospace" style="word-break: break-all">{{line.address}}</clr-dg-cell>
<clr-dg-cell class="monospace">{{line.confirmed.balance | number: '1.8-18'}}</clr-dg-cell>
<clr-dg-cell class="monospace">
<span *ngIf="line.unconfirmed.balance != 0">
{{line.unconfirmed.balance | number: '1.8-18'}}
</span>
</clr-dg-cell>
<clr-dg-cell class="monospace">{{line.total.balance | number: '1.8-18'}}</clr-dg-cell>
<clr-dg-row-detail *clrIfExpanded >
<div class="clr-row">
<div class="clr-col-3">
<button class="btn btn-info-outline" (click)="parent.addrExplorer(line.address)">Explorer</button>
<p>
</div>
<div *ngIf="parent.keys[line.address] != null" class="clr-col-9" style="word-break:break-all">
<h6 style="margin-top: 0">Derivation </h6>
{{parent.keys[line.address].derivation}}
<h6> 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>
<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>
<clr-dg-footer>
<clr-dg-pagination #pagination [clrDgPageSize]="10">
<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>
</div>
</div>
`
})
export class AddressViewerComponent implements OnInit {
export class AddressViewerComponent implements OnInit{
balanceFilter = new BalanceFilter()
otherBalanceFilter = new BalanceFilter()
regularAddresses = []
changeAddresses = []
keys = {}
@@ -110,6 +160,15 @@ export class AddressViewerComponent implements OnInit {
setChangeAddresses(addresses){
this.changeAddresses = addresses
}
}
}
class BalanceFilter implements ClrDatagridNumericFilterInterface<any>{
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)
}
}
@@ -5,7 +5,7 @@ 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 { TransactionBuilder, Signer } from 'bitcoinjs-lib'
import { networks } from 'bitcoinjs-lib'
let feeRate = 55 // satoshis per byte
@@ -17,8 +17,43 @@ declare const fb
<wallet-generator></wallet-generator>
<!-- loading -->
<clr-modal [clrModalSize]="'md'" [(clrModalOpen)]="loading">
<div class="modal-body ">
<span class="spinner spinner-inverse spinner-md">
Loading...
</span>
<h3>Checking addresses</h3> <br>
Checking the first {{loadingN*115}} addresses. <br>
Found addresses containing balance: {{regaddresses.length + changeaddresses.length}}
</div>
</clr-modal>
<!-- / loading -->
<!-- row 0 -->
<div *ngIf="mode.net == 'testnet'" class="clr-all-12">
<div class="card">
<div class="card-header">
Faucet
</div>
<div class="card-block" >
<button class="btn btn-icon btn-info-outline" onclick="window.open('https://testnet-faucet.mempool.co/', '_blank')"><clr-icon shape="fuel"></clr-icon></button>
<button class="btn btn-icon btn-info-outline" onclick="window.open('https://bitcoinfaucet.uo1.net/', '_blank')"><clr-icon shape="fuel"></clr-icon></button>
<button class="btn btn-icon btn-info-outline" onclick="window.open('https://coinfaucet.eu/en/btc-testnet/', '_blank')"><clr-icon shape="fuel"></clr-icon></button>
<button class="btn btn-icon btn-success-outline" onclick="window.open('https://www.google.com/search?q=btc+testnet+faucet&oq=btc+testnet+faucet', '_blank')"><clr-icon shape="plus"></clr-icon> more</button>
</div>
<div class="card-footer" >
Get free test-BTC
</div>
</div>
</div>
<!-- row 1 -->
<div class="clr-col-12 clr-col-sm-12 clr-col-md-12 clr-col-lg-12 clr-col-xl-12">
<div class="clr-all-12">
<!-- wallet picker + send box + balance box -->
<div class="clr-row">
@@ -28,26 +63,42 @@ declare const fb
<!-- 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>
<walletpicker class="clr-all-12"></walletpicker>
</div>
<!-- /Wallet picker -->
<!-- Send box -->
<div class="clr-row">
<div *ngIf="mode.type=='private'" 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="clr-all-12">
<div class="card">
<div class="card-header">
Send
</div>
<clr-alert *ngFor="let error of errors" [clrAlertType]="'danger'">
<clr-alert-item>
<span class="alert-text">
{{error}}
</span>
</clr-alert-item>
</clr-alert>
<clr-alert *ngFor="let success of successes" [clrAlertType]="'success'">
<clr-alert-item>
<span class="alert-text">
{{success}}
</span>
</clr-alert-item>
</clr-alert>
<div class="card-block" >
<input [(ngModel)]="sendTo" type="text" placeholder="To address" clrInput />
<input [(ngModel)]="sendAmount" type="number" min="0" placeholder="Amount" clrInput />
<div class="clr-row" style="padding-left:12px; padding-right:12px">
<input [(ngModel)]="sendTo" type="text" placeholder="To address" class="clr-input clr-col-12" />
<input [(ngModel)]="sendAmount" type="text" min="0" placeholder="Amount" class="clr-input clr-col-12" style="margin-top:12px; margin-borrom:12px" />
</div>
</div>
<div class="card-footer" >
<button class="btn btn-success-outline" (click)="createTx(sendTo, sendAmount)">send</button>
<button class="btn btn-success-outline" (click)="createAndSendTx(sendTo, sendAmount)">send</button>
</div>
</div>
@@ -63,9 +114,9 @@ declare const fb
<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 *ngIf="rootkey.publicExtendedKey != ''" class="card">
<div class="card-header" style="word-break: break-all">
{{rootkey.publicExtendedKey}}
<span *ngIf="mode.net=='testnet'" class="label label-danger">Testnet</span>{{rootkey.publicExtendedKey}}
</div>
<clr-alert *ngFor="let entry of alerts" [clrAlertType]="line.severity">
<clr-alert-item>
@@ -75,12 +126,19 @@ declare const fb
</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}}
<div class="card-block">
<h3 style="margin-top:12px">{{balanceDisplay.total | number: '1.8-18'}} BTC </h3>
<p class="monospace">
<span *ngIf="balanceDisplay.unconfirmed < 0">&nbsp;</span>{{balanceDisplay.confirmed | number: '1.18'}} BTC confirmed<br />
{{balanceDisplay.unconfirmed | number: '1.18'}} BTC pending
</p>
<p *ngIf="mode.type=='private'">
<clr-icon shape="key"></clr-icon> Private key available
</p>
</div>
<div class="card-footer" >
<button class="btn btn-icon" (click)="reload()"> <clr-icon shape="refresh"></clr-icon> Reload </button>
</div>
</div>
<!-- Balance box-->
@@ -91,50 +149,39 @@ declare const fb
</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">
<div class="clr-all-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
}
} = {}
errors:string[] = []
successes:string[] = []
wallets:WalletStore = {}
txToSend:any
rootkey = {
publicExtendedKey: "",
privateExtendedKey: ""
}
keys = {}
regaddresses = []
changeaddresses = []
balance = {
rootkey:bip32Keypair = {publicExtendedKey: "",privateExtendedKey: ""}
keys:KeyStore = {}
regaddresses:AddressLine[] = []
changeaddresses:AddressLine[] = []
balanceDisplay = {
confirmed: 0,
total: 0,
unconfirmed: 0
}
mode : {
net:network
type:keyType,
net:networkType
} = {
net: "testnet"
net: "testnet",
type: "public"
}
sendto:string
amount:number
sendTo:string
sendAmount:number
loading = false
loadingN = 1
constructor(){
window["btc"] = () => this
}
@ViewChild(AddressViewerComponent, {static: false})
AddressViewerComponent:AddressViewerComponent
@@ -149,8 +196,8 @@ export class PluginsComponent implements AfterViewInit {
this.WalletPickerComponent.setParent(this)
this.AddressViewerComponent.setParent(this)
//tprv8cXm1S7PGS2fHuLjZajuDZZsvxerzv4zZJJAhkLAWZzwz5rE7efHm31xsqpzfoGMHBNC8bkRcsjiMdmkSuNn5C1qNqk7rrk1pCuhk4HfVak
//tpubD9Do9r9dQoiLBNNXTEQVcyDzVzAoAFFu8btwzGNTvqoLpa6zk3UswXdq3zeArrakUu8huCtG1L5pTqNYNftanuutf57fn1C74rBzFybH1xe
window['btc'] = this
this.loadKey("tprv8cXm1S7PGS2fHuLjZajuDZZsvxerzv4zZJJAhkLAWZzwz5rE7efHm31xsqpzfoGMHBNC8bkRcsjiMdmkSuNn5C1qNqk7rrk1pCuhk4HfVak")
}
@@ -169,50 +216,46 @@ export class PluginsComponent implements AfterViewInit {
loadKey(key:string){
this.wallets = {}
this.txToSend = null
this.sendto = ""
this.sendTo = ""
this.regaddresses = []
const t = hdkey.checkHDKey(key)
if(t){
console.log(t)
this.mode = t
}
this.rootkey = hdkey.HDKeyFromExtendedKey(key)
this.refresh()
this.reload()
}
async refresh(){
async reload():Promise<void>{
this.loading = true
this.loadingN = 1
this.keys = {}
this.changeaddresses = []
this.regaddresses = []
this.AddressViewerComponent.setChangeAddresses([])
this.AddressViewerComponent.setRegularAddresses([])
this.balance.total = 0
this.balance.unconfirmed = 0
this.balance.confirmed = 0
this.balanceDisplay.total = 0
this.balanceDisplay.unconfirmed = 0
this.balanceDisplay.confirmed = 0
await this.refresh()
this.loading = false
}
async refresh():Promise<void>{
let p1 = this.updateChange()
let p2 = this.update()
let p2 = this.updateBalance()
let balances:any[] = await Promise.all([p1,p2])
let balances:GrandTotal[] = await Promise.all([p1,p2])
balances.forEach(balance => {
this.balance.confirmed += balance.confirmed
this.balance.total += balance.total
this.balance.unconfirmed += balance.unconfirmed
this.balanceDisplay.confirmed += balance.confirmed
this.balanceDisplay.total += balance.total
this.balanceDisplay.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
}
getAddresses(rootAddr: string, n = 10, skip = 0, changeAddresses = false): string[] | null{
let addresses: string[] = []
for(let i = skip; i < skip+n; i++){
let addr
@@ -222,13 +265,17 @@ export class PluginsComponent implements AfterViewInit {
addr = hdkey.deriveAddress(rootAddr, i)
}
if(!addr){
return false
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[t.net]+'/address/'+lookupstr, {
let r = await fetch(api[network]+'/address/'+lookupstr, {
'method':'GET',
'mode':'cors',
'data': {limit: 200}
@@ -244,20 +291,35 @@ export class PluginsComponent implements AfterViewInit {
}
}
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}
async getAddressInfos(rootAddr: string, n = 10, skip = 0, changeAddresses = false): Promise<any>{
const t = hdkey.checkHDKey(rootAddr)
if(!t){
return false
}
return (await r).json()
const addresses = this.getAddresses(rootAddr, n, skip, changeAddresses)
return await this.fetchAddresses(addresses, t.net)
}
async getUnspentTxs(addresses:string[], type:network):Promise<any>{
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<any>{
let lookupstr = addresses.join(',')
try{
let r = await fetch(api[type]+'/address/'+lookupstr+'/unspent', {
'method':'GET',
@@ -269,6 +331,7 @@ export class PluginsComponent implements AfterViewInit {
return (await r).json()
}catch(e){
console.warn ("getUnspentTxs", e)
return {success: false}
}
}
@@ -278,30 +341,51 @@ export class PluginsComponent implements AfterViewInit {
return this.keys[keyName]
}
async update(){
let n = 1
let regAddresses
calculateTotal(addresses:AddressLine[]):GrandTotal{
const resMap: any = addresses.reduce((o1, o2)=>{o1[o2.address] = o2; return o1}, {})
const total: any = Object.values(resMap).reduce((accum:GrandTotal, obj:AddressLine) => {
accum.confirmed += parseFloat(<any>obj.confirmed.balance)
accum.unconfirmed += parseFloat(<any>obj.unconfirmed.balance)
accum.total += parseFloat(<any>obj.confirmed.balance) + parseFloat(<any>obj.unconfirmed.balance)
return accum
},{
confirmed: 0,
unconfirmed: 0,
total: 0
})
return total
}
async updateBalance():Promise<GrandTotal>{
let n = 1
let res: { success: boolean; addresses: any; address: any; }
this.regaddresses = []
while(true){
this.loadingN = n
try{
regAddresses = await this.getInfo(this.rootkey.publicExtendedKey, 115*n, 115*(n-1))
res = await this.getAddressInfos(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(res.success === true){
if('addresses' in res){ // multiple
this.regaddresses = [...this.regaddresses, ...res.addresses]
}
if('address' in regAddresses){
this.regaddresses.push(regAddresses.address)
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.rootkey.privateExtendedKey != null){
for(let i = 0; i < 115*n; i++){
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
}
}
@@ -309,46 +393,25 @@ export class PluginsComponent implements AfterViewInit {
}
}
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
//add an extra empty address at the end
const extraAddr = hdkey.deriveAddress(this.rootkey.publicExtendedKey, this.regaddresses.length)
this.regaddresses.push({
address: <string>extraAddr,
confirmed: {balance: 0},
unconfirmed: {balance: 0},
total: {balance: 0},
})
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
}
return this.calculateTotal(this.regaddresses)
}
async updateChange(){
async updateChange():Promise<GrandTotal>{
let n = 1
let res
this.changeaddresses = []
while(true){
let res
try{
res = await this.getInfo(this.rootkey.publicExtendedKey, 115*n, 115*(n-1), true)
res = await this.getAddressInfos(this.rootkey.publicExtendedKey, 115*n, 115*(n-1), true)
}catch(e){
break
}
@@ -361,11 +424,16 @@ export class PluginsComponent implements AfterViewInit {
}
n++
}else{
console.log('No more balance found after attempting ', 115*n, 'addresses')
if(this.rootkey.privateExtendedKey != null){
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
}
}
@@ -373,60 +441,26 @@ export class PluginsComponent implements AfterViewInit {
}
}
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
}
return this.calculateTotal(this.changeaddresses)
}
async createTx(target:string, value:number){
console.log(target, value)
async createAndSendTx(target:string, value:number){
if(target == null || typeof target !== 'string' || target === '' || value == null || value <= 0 ){
//this.error = 'Bad inputs. Expected bitcoin address and number'
this.errors.push('Bad inputs. Expected bitcoin address and number')
return
}
if(value > this.balance.confirmed){
//this.error = 'Insufficient funds'
if(value > this.balanceDisplay.confirmed){
this.errors.push('Insufficient funds')
return
}
let addresses: string[] = this.regaddresses.map(a => a.address)
addresses = [...addresses, ...this.changeaddresses.map(a => a.address)]
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.error = 'No UTXOs found'
this.errors.push('No UTXOs found')
return
}
@@ -443,7 +477,7 @@ export class PluginsComponent implements AfterViewInit {
// .inputs and .outputs will be undefined if no solution was found
if (!inputs || !outputs){
//this.error = 'Could not build a valid transaction'
this.errors.push('Could not build a valid transaction')
return
}
@@ -461,7 +495,7 @@ export class PluginsComponent implements AfterViewInit {
txb.addOutput(output.address, output.value)
})
inputs.forEach((input, i) => {
txb.sign(i, this.keys[input.address])
txb.sign(i, <Signer>this.keys[input.address])
})
this.txToSend = txb.build()
@@ -469,37 +503,61 @@ export class PluginsComponent implements AfterViewInit {
this.sendTx()
}
async sendTx(){
private async sendTx(){
if(this.txToSend == null){
//this.error = 'No transaction to send!'
this.errors.push('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 url = api[this.mode.net]+"/pushtx"
let r
try{
r = await fetch(url, {
'method':'POST',
'body': JSON.stringify({'hex': this.txToSend.toHex()})
})
}catch(e){
console.warn("sendTx", e)
return
}
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()
this.errors = []
this.successes.push("Sent "+this.sendAmount+" to "+this.sendTo)
this.reload()
}else{
//this.error = 'The API '+url+' returned a non-200 error code '+r.status+' '+r.statusText
console.log(r)
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}
type network = 'testnet' | 'mainnet'
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 }
const api = {
@@ -142,6 +142,7 @@ declare const fb
</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 class="btn btn-success" [type]="'myfinish'">Finish</clr-wizard-button>
</ng-template>
</clr-wizard-page>
@@ -7,7 +7,7 @@ declare const fb
selector: 'walletpicker', //!!!!
template: `
<div class="card" style="overflow:hidden">
<div class="card" style="overflow:hidden; padding-bottom:12px">
<div class="card-header">
Wallets
</div>
@@ -1,4 +1,4 @@
<clr-main-container >
<clr-main-container >
<header-bar></header-bar>
<subnav></subnav>
<dynamic-loader style="height: 100%;"></dynamic-loader>
@@ -1,10 +0,0 @@
.heightmax{
height: 100%
}
.break-word {
word-wrap: break-word;
}
.monospace {
font-family: monospace;
font-size: 90%
}
@@ -1,3 +1,3 @@
<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>
<div class="content-area" style="max-height: 100%">
<router-outlet class="clr-all-12" ></router-outlet>
</div>
+51
View File
@@ -1 +1,52 @@
/* You can add global styles to this file, and also import other style files */
@import "../node_modules/@clr/ui/src/utils/components.clarity";
.heightmax{
height: 100%
}
.break-word {
word-wrap: break-word;
}
.monospace {
font-family: monospace;
font-size: 90%
}
*::-webkit-scrollbar-track
{
box-shadow: inset 0 0 6px rgba(0,0,0,0.3);
-webkit-box-shadow: inset 0 0 6px rgba(0,0,0,0.3);
background-color: rgba(0,0,0,0);
border-radius: 10px;
}
*::-webkit-scrollbar
{
width: 7px;
background-color: rgba(0,0,0,0);
}
*::-webkit-scrollbar-thumb
{
border-radius: 7px;
background-color: #0079B8;
}
.clr-all-12{
@extend .clr-col-12, .clr-col-sm-12, .clr-col-md-12, .clr-col-lg-12, .clr-col-xl-12
}
.clr-all-4{
@extend .clr-col-4, .clr-col-sm-4, .clr-col-md-4, .clr-col-lg-4, .clr-col-xl-4
}
.clr-all-6{
@extend .clr-col-6, .clr-col-sm-6, .clr-col-md-6, .clr-col-lg-6, .clr-col-xl-6
}
.clr-all-3{
@extend .clr-col-3, .clr-col-sm-3, .clr-col-md-3, .clr-col-lg-3, .clr-col-xl-3
}
.clr-all-2{
@extend .clr-col-2, .clr-col-sm-2, .clr-col-md-2, .clr-col-lg-2, .clr-col-xl-2
}