2018-07-15 21:56:28 +02:00
import { LegacyWallet } from './legacy-wallet' ;
import Frisbee from 'frisbee' ;
2019-02-26 02:33:29 +01:00
import { BitcoinUnit , Chain } from '../models/bitcoinUnits' ;
2018-09-01 01:28:19 +02:00
let BigNumber = require ( 'bignumber.js' ) ;
2018-07-15 21:56:28 +02:00
export class LightningCustodianWallet extends LegacyWallet {
2018-12-28 16:52:06 +01:00
static type = 'lightningCustodianWallet' ;
static typeReadable = 'Lightning' ;
2019-03-08 22:54:47 +01:00
static defaultBaseUri = 'https://lndhub.herokuapp.com/' ;
2019-01-26 00:32:35 +01:00
constructor ( props ) {
super ( props ) ;
2018-12-11 23:52:46 +01:00
this . setBaseURI ( ) ; // no args to init with default value
2018-09-01 01:28:19 +02:00
this . init ( ) ;
this . refresh _token = '' ;
this . access _token = '' ;
this . _refresh _token _created _ts = 0 ;
this . _access _token _created _ts = 0 ;
this . refill _addressess = [ ] ;
this . pending _transactions _raw = [ ] ;
2019-01-10 18:50:33 +01:00
this . user _invoices _raw = [ ] ;
2018-09-01 01:28:19 +02:00
this . info _raw = false ;
2018-12-18 06:58:49 +01:00
this . preferredBalanceUnit = BitcoinUnit . SATS ;
2019-02-26 02:33:29 +01:00
this . chain = Chain . OFFCHAIN ;
2018-09-01 01:28:19 +02:00
}
2018-11-04 22:21:07 +01:00
/ * *
* requires calling init ( ) after setting
*
* @ param URI
* /
setBaseURI ( URI ) {
if ( URI ) {
this . baseURI = URI ;
} else {
2019-03-08 22:54:47 +01:00
this . baseURI = LightningCustodianWallet . defaultBaseUri ;
2018-11-04 22:21:07 +01:00
}
}
getBaseURI ( ) {
return this . baseURI ;
}
2018-10-09 06:25:36 +02:00
allowSend ( ) {
2019-02-19 01:37:34 +01:00
return true ;
2018-10-09 06:25:36 +02:00
}
2018-09-01 01:28:19 +02:00
getAddress ( ) {
return '' ;
}
2019-03-08 22:54:47 +01:00
getSecret ( ) {
if ( this . baseURI === LightningCustodianWallet . defaultBaseUri ) {
return this . secret ;
}
return this . secret + '@' + this . baseURI ;
}
2018-09-01 01:28:19 +02:00
timeToRefreshBalance ( ) {
2019-02-10 14:20:04 +01:00
return ( + new Date ( ) - this . _lastBalanceFetch ) / 1000 > 3600 ; // 1hr
2018-09-01 01:28:19 +02:00
}
timeToRefreshTransaction ( ) {
2019-02-10 14:20:04 +01:00
return ( + new Date ( ) - this . _lastTxFetch ) / 1000 > 3600 ; // 1hr
2018-09-01 01:28:19 +02:00
}
static fromJson ( param ) {
let obj = super . fromJson ( param ) ;
obj . init ( ) ;
return obj ;
}
init ( ) {
2018-07-15 21:56:28 +02:00
this . _api = new Frisbee ( {
2018-11-04 22:21:07 +01:00
baseURI : this . baseURI ,
2018-07-15 21:56:28 +02:00
} ) ;
}
2018-09-01 01:28:19 +02:00
accessTokenExpired ( ) {
return ( + new Date ( ) - this . _access _token _created _ts ) / 1000 >= 3600 * 2 ; // 2h
}
refreshTokenExpired ( ) {
return ( + new Date ( ) - this . _refresh _token _created _ts ) / 1000 >= 3600 * 24 * 7 ; // 7d
}
generate ( ) {
// nop
}
2018-09-04 00:44:45 +02:00
async createAccount ( isTest ) {
2018-09-01 01:28:19 +02:00
let response = await this . _api . post ( '/create' , {
2018-09-04 00:44:45 +02:00
body : { partnerid : 'bluewallet' , accounttype : ( isTest && 'test' ) || 'common' } ,
2018-09-01 01:28:19 +02:00
headers : { 'Access-Control-Allow-Origin' : '*' , 'Content-Type' : 'application/json' } ,
} ) ;
let json = response . body ;
if ( typeof json === 'undefined' ) {
throw new Error ( 'API failure: ' + response . err + ' ' + JSON . stringify ( response . body ) ) ;
}
2018-07-15 21:56:28 +02:00
2018-09-01 01:28:19 +02:00
if ( json && json . error ) {
2018-12-11 23:52:46 +01:00
throw new Error ( 'API error: ' + ( json . message ? json . message : json . error ) + ' (code ' + json . code + ')' ) ;
2018-09-01 01:28:19 +02:00
}
2018-07-15 21:56:28 +02:00
2018-09-01 01:28:19 +02:00
if ( ! json . login || ! json . password ) {
throw new Error ( 'API unexpected response: ' + JSON . stringify ( response . body ) ) ;
}
2018-07-15 21:56:28 +02:00
2018-12-11 23:52:46 +01:00
this . secret = 'lndhub://' + json . login + ':' + json . password ;
2018-09-01 01:28:19 +02:00
}
2018-07-15 21:56:28 +02:00
2019-01-05 17:29:13 +01:00
async payInvoice ( invoice , freeAmount = 0 ) {
2018-09-01 01:28:19 +02:00
let response = await this . _api . post ( '/payinvoice' , {
2019-01-05 17:29:13 +01:00
body : { invoice : invoice , amount : freeAmount } ,
2018-09-01 01:28:19 +02:00
headers : {
'Access-Control-Allow-Origin' : '*' ,
'Content-Type' : 'application/json' ,
Authorization : 'Bearer' + ' ' + this . access _token ,
} ,
} ) ;
2019-02-14 00:19:59 +01:00
if ( response . originalResponse && typeof response . originalResponse === 'string' ) {
try {
response . originalResponse = JSON . parse ( response . originalResponse ) ;
} catch ( _ ) { }
}
if ( response . originalResponse && response . originalResponse . status && response . originalResponse . status === 503 ) {
throw new Error ( 'Payment is in transit' ) ;
}
2018-09-01 01:28:19 +02:00
let json = response . body ;
if ( typeof json === 'undefined' ) {
throw new Error ( 'API failure: ' + response . err + ' ' + JSON . stringify ( response . originalResponse ) ) ;
}
2018-07-15 21:56:28 +02:00
2018-09-01 01:28:19 +02:00
if ( json && json . error ) {
throw new Error ( 'API error: ' + json . message + ' (code ' + json . code + ')' ) ;
}
2018-07-15 21:56:28 +02:00
2018-09-01 01:28:19 +02:00
this . last _paid _invoice _result = json ;
}
2018-09-04 00:44:45 +02:00
/ * *
* Returns list of LND invoices created by user
*
* @ return { Promise . < Array > }
* /
2019-01-10 18:50:33 +01:00
async getUserInvoices ( limit = false ) {
let limitString = '' ;
if ( limit ) limitString = '?limit=' + parseInt ( limit ) ;
let response = await this . _api . get ( '/getuserinvoices' + limitString , {
2018-09-04 00:44:45 +02:00
headers : {
'Access-Control-Allow-Origin' : '*' ,
'Content-Type' : 'application/json' ,
Authorization : 'Bearer' + ' ' + this . access _token ,
} ,
} ) ;
let json = response . body ;
if ( typeof json === 'undefined' ) {
throw new Error ( 'API failure: ' + response . err + ' ' + JSON . stringify ( response . originalResponse ) ) ;
}
if ( json && json . error ) {
throw new Error ( 'API error: ' + json . message + ' (code ' + json . code + ')' ) ;
}
2018-12-25 20:07:43 +01:00
2019-01-10 18:50:33 +01:00
if ( limit ) {
// need to merge existing invoices with the ones that arrived
// but the ones received later should overwrite older ones
for ( let oldInvoice of this . user _invoices _raw ) {
// iterate all OLD invoices
let found = false ;
for ( let newInvoice of json ) {
// iterate all NEW invoices
if ( newInvoice . payment _request === oldInvoice . payment _request ) found = true ;
}
if ( ! found ) {
// if old invoice is not found in NEW array, we simply add it:
json . push ( oldInvoice ) ;
}
}
}
this . user _invoices _raw = json . sort ( function ( a , b ) {
return a . timestamp - b . timestamp ;
} ) ;
return this . user _invoices _raw ;
2018-09-04 00:44:45 +02:00
}
2018-12-25 20:07:43 +01:00
/ * *
* Basically the same as this . getUserInvoices ( ) but saves invoices list
* to internal variable
*
* @ returns { Promise < void > }
* /
async fetchUserInvoices ( ) {
await this . getUserInvoices ( ) ;
}
2018-09-04 00:44:45 +02:00
async addInvoice ( amt , memo ) {
let response = await this . _api . post ( '/addinvoice' , {
2019-01-01 04:08:43 +01:00
body : { amt : amt + '' , memo : memo } ,
2018-09-04 00:44:45 +02:00
headers : {
'Access-Control-Allow-Origin' : '*' ,
'Content-Type' : 'application/json' ,
Authorization : 'Bearer' + ' ' + this . access _token ,
} ,
} ) ;
let json = response . body ;
if ( typeof json === 'undefined' ) {
throw new Error ( 'API failure: ' + response . err + ' ' + JSON . stringify ( response . originalResponse ) ) ;
}
if ( json && json . error ) {
throw new Error ( 'API error: ' + json . message + ' (code ' + json . code + ')' ) ;
}
2018-09-22 13:40:47 +02:00
if ( ! json . r _hash || ! json . pay _req ) {
throw new Error ( 'API unexpected response: ' + JSON . stringify ( response . body ) ) ;
}
return json . pay _req ;
2018-09-04 00:44:45 +02:00
}
2018-09-01 01:28:19 +02:00
async checkRouteInvoice ( invoice ) {
let response = await this . _api . get ( '/checkrouteinvoice?invoice=' + invoice , {
headers : {
'Access-Control-Allow-Origin' : '*' ,
'Content-Type' : 'application/json' ,
Authorization : 'Bearer' + ' ' + this . access _token ,
} ,
} ) ;
let json = response . body ;
if ( typeof json === 'undefined' ) {
throw new Error ( 'API failure: ' + response . err + ' ' + JSON . stringify ( response . body ) ) ;
}
if ( json && json . error ) {
throw new Error ( 'API error: ' + json . message + ' (code ' + json . code + ')' ) ;
}
}
/ * *
* Uses login & pass stored in ` this.secret ` to authorize
* and set internal ` access_token ` & ` refresh_token `
*
* @ return { Promise . < void > }
* /
async authorize ( ) {
2018-12-11 23:52:46 +01:00
let login , password ;
if ( this . secret . indexOf ( 'blitzhub://' ) !== - 1 ) {
login = this . secret . replace ( 'blitzhub://' , '' ) . split ( ':' ) [ 0 ] ;
password = this . secret . replace ( 'blitzhub://' , '' ) . split ( ':' ) [ 1 ] ;
} else {
login = this . secret . replace ( 'lndhub://' , '' ) . split ( ':' ) [ 0 ] ;
password = this . secret . replace ( 'lndhub://' , '' ) . split ( ':' ) [ 1 ] ;
}
2018-09-01 01:28:19 +02:00
let response = await this . _api . post ( '/auth?type=auth' , {
body : { login : login , password : password } ,
headers : { 'Access-Control-Allow-Origin' : '*' , 'Content-Type' : 'application/json' } ,
} ) ;
let json = response . body ;
if ( typeof json === 'undefined' ) {
throw new Error ( 'API failure: ' + response . err + ' ' + JSON . stringify ( response . body ) ) ;
}
if ( json && json . error ) {
throw new Error ( 'API error: ' + json . message + ' (code ' + json . code + ')' ) ;
}
if ( ! json . access _token || ! json . refresh _token ) {
throw new Error ( 'API unexpected response: ' + JSON . stringify ( response . body ) ) ;
}
this . refresh _token = json . refresh _token ;
this . access _token = json . access _token ;
this . _refresh _token _created _ts = + new Date ( ) ;
this . _access _token _created _ts = + new Date ( ) ;
}
async checkLogin ( ) {
if ( this . accessTokenExpired ( ) && this . refreshTokenExpired ( ) ) {
// all tokens expired, only option is to login with login and password
return this . authorize ( ) ;
}
if ( this . accessTokenExpired ( ) ) {
// only access token expired, so only refreshing it
let refreshedOk = true ;
try {
await this . refreshAcessToken ( ) ;
} catch ( Err ) {
refreshedOk = false ;
}
if ( ! refreshedOk ) {
// something went wrong, lets try to login regularly
return this . authorize ( ) ;
}
}
}
async refreshAcessToken ( ) {
let response = await this . _api . post ( '/auth?type=refresh_token' , {
body : { refresh _token : this . refresh _token } ,
headers : { 'Access-Control-Allow-Origin' : '*' , 'Content-Type' : 'application/json' } ,
} ) ;
let json = response . body ;
if ( typeof json === 'undefined' ) {
throw new Error ( 'API failure: ' + response . err + ' ' + JSON . stringify ( response . body ) ) ;
}
if ( json && json . error ) {
throw new Error ( 'API error: ' + json . message + ' (code ' + json . code + ')' ) ;
}
if ( ! json . access _token || ! json . refresh _token ) {
throw new Error ( 'API unexpected response: ' + JSON . stringify ( response . body ) ) ;
}
this . refresh _token = json . refresh _token ;
this . access _token = json . access _token ;
this . _refresh _token _created _ts = + new Date ( ) ;
this . _access _token _created _ts = + new Date ( ) ;
}
async fetchBtcAddress ( ) {
let response = await this . _api . get ( '/getbtc' , {
headers : {
'Access-Control-Allow-Origin' : '*' ,
'Content-Type' : 'application/json' ,
Authorization : 'Bearer' + ' ' + this . access _token ,
} ,
} ) ;
let json = response . body ;
if ( typeof json === 'undefined' ) {
throw new Error ( 'API failure: ' + response . err + ' ' + JSON . stringify ( response . body ) ) ;
}
if ( json && json . error ) {
throw new Error ( 'API error: ' + json . message + ' (code ' + json . code + ')' ) ;
}
this . refill _addressess = [ ] ;
for ( let arr of json ) {
this . refill _addressess . push ( arr . address ) ;
}
}
2018-07-15 21:56:28 +02:00
2018-08-19 16:54:15 +02:00
getTransactions ( ) {
2018-09-01 01:28:19 +02:00
let txs = [ ] ;
this . pending _transactions _raw = this . pending _transactions _raw || [ ] ;
2018-12-25 20:07:43 +01:00
this . user _invoices _raw = this . user _invoices _raw || [ ] ;
2018-09-01 01:28:19 +02:00
this . transactions _raw = this . transactions _raw || [ ] ;
2018-12-25 20:07:43 +01:00
txs = txs . concat ( this . pending _transactions _raw . slice ( ) , this . transactions _raw . slice ( ) . reverse ( ) , this . user _invoices _raw . slice ( ) ) ; // slice so array is cloned
2018-09-01 01:28:19 +02:00
// transforming to how wallets/list screen expects it
for ( let tx of txs ) {
2019-01-09 21:03:20 +01:00
tx . fromWallet = this . secret ;
2018-09-26 23:33:51 +02:00
if ( tx . amount ) {
// pending tx
tx . amt = tx . amount * - 100000000 ;
tx . fee = 0 ;
tx . timestamp = tx . time ;
tx . memo = 'On-chain transaction' ;
}
2018-09-05 00:18:24 +02:00
2018-09-22 13:40:47 +02:00
if ( typeof tx . amt !== 'undefined' && typeof tx . fee !== 'undefined' ) {
2018-09-05 00:18:24 +02:00
// lnd tx outgoing
2018-09-22 13:40:47 +02:00
tx . value = parseInt ( ( tx . amt * 1 + tx . fee * 1 ) * - 1 ) ;
}
2018-09-26 23:16:52 +02:00
if ( tx . type === 'paid_invoice' ) {
tx . memo = tx . memo || 'Lightning payment' ;
2019-01-09 14:31:34 +01:00
if ( tx . value > 0 ) tx . value = tx . value * - 1 ; // value already includes fee in it (see lndhub)
2018-12-29 00:36:47 +01:00
// outer code expects spending transactions to of negative value
2018-09-26 23:16:52 +02:00
}
if ( tx . type === 'bitcoind_tx' ) {
tx . memo = 'On-chain transaction' ;
2018-09-05 00:18:24 +02:00
}
2018-09-22 13:40:47 +02:00
2018-12-25 20:07:43 +01:00
if ( tx . type === 'user_invoice' ) {
// incoming ln tx
tx . value = parseInt ( tx . amt ) ;
tx . memo = tx . description || 'Lightning invoice' ;
}
tx . received = new Date ( tx . timestamp * 1000 ) . toString ( ) ;
2018-09-01 01:28:19 +02:00
}
2018-09-26 23:16:52 +02:00
return txs . sort ( function ( a , b ) {
return b . timestamp - a . timestamp ;
} ) ;
2018-08-19 16:54:15 +02:00
}
2018-09-01 01:28:19 +02:00
async fetchPendingTransactions ( ) {
let response = await this . _api . get ( '/getpending' , {
headers : {
'Access-Control-Allow-Origin' : '*' ,
'Content-Type' : 'application/json' ,
Authorization : 'Bearer' + ' ' + this . access _token ,
} ,
} ) ;
let json = response . body ;
if ( typeof json === 'undefined' ) {
2018-09-25 23:23:56 +02:00
throw new Error ( 'API failure: ' + response . err + ' ' + JSON . stringify ( response ) ) ;
2018-09-01 01:28:19 +02:00
}
if ( json && json . error ) {
throw new Error ( 'API error: ' + json . message + ' (code ' + json . code + ')' ) ;
}
this . pending _transactions _raw = json ;
2018-08-19 16:54:15 +02:00
}
2018-07-15 21:56:28 +02:00
2018-09-01 01:28:19 +02:00
async fetchTransactions ( ) {
// TODO: iterate over all available pages
const limit = 10 ;
let queryRes = '' ;
let offset = 0 ;
queryRes += '?limit=' + limit ;
queryRes += '&offset=' + offset ;
let response = await this . _api . get ( '/gettxs' + queryRes , {
headers : {
'Access-Control-Allow-Origin' : '*' ,
'Content-Type' : 'application/json' ,
Authorization : 'Bearer' + ' ' + this . access _token ,
} ,
} ) ;
let json = response . body ;
if ( typeof json === 'undefined' ) {
throw new Error ( 'API failure: ' + response . err + ' ' + JSON . stringify ( response . body ) ) ;
}
if ( json && json . error ) {
throw new Error ( 'API error: ' + json . message + ' (code ' + json . code + ')' ) ;
}
2018-09-22 13:40:47 +02:00
if ( ! Array . isArray ( json ) ) {
2018-09-01 01:28:19 +02:00
throw new Error ( 'API unexpected response: ' + JSON . stringify ( response . body ) ) ;
}
2019-02-10 14:20:04 +01:00
this . _lastTxFetch = + new Date ( ) ;
2018-09-22 13:40:47 +02:00
this . transactions _raw = json ;
2018-09-01 01:28:19 +02:00
}
2018-07-15 21:56:28 +02:00
2018-08-19 16:54:15 +02:00
getBalance ( ) {
2018-10-20 23:10:21 +02:00
return new BigNumber ( this . balance ) . dividedBy ( 100000000 ) . toString ( 10 ) ;
2018-09-01 01:28:19 +02:00
}
2018-09-22 13:40:47 +02:00
async fetchBalance ( noRetry ) {
2018-09-01 01:28:19 +02:00
await this . checkLogin ( ) ;
let response = await this . _api . get ( '/balance' , {
headers : {
'Access-Control-Allow-Origin' : '*' ,
'Content-Type' : 'application/json' ,
Authorization : 'Bearer' + ' ' + this . access _token ,
} ,
} ) ;
let json = response . body ;
if ( typeof json === 'undefined' ) {
throw new Error ( 'API failure: ' + response . err + ' ' + JSON . stringify ( response . body ) ) ;
}
if ( json && json . error ) {
2018-09-22 13:40:47 +02:00
if ( json . code * 1 === 1 && ! noRetry ) {
await this . authorize ( ) ;
return this . fetchBalance ( true ) ;
}
2018-09-01 01:28:19 +02:00
throw new Error ( 'API error: ' + json . message + ' (code ' + json . code + ')' ) ;
}
if ( ! json . BTC || typeof json . BTC . AvailableBalance === 'undefined' ) {
throw new Error ( 'API unexpected response: ' + JSON . stringify ( response . body ) ) ;
}
this . balance _raw = json ;
this . balance = json . BTC . AvailableBalance ;
this . _lastBalanceFetch = + new Date ( ) ;
}
/ * *
* Example return :
* { destination : '03864ef025fde8fb587d989186ce6a4a186895ee44a926bfc370e2c366597a3f8f' ,
* payment _hash : 'faf996300a468b668c58ca0702a12096475a0dd2c3dde8e812f954463966bcf4' ,
* num _satoshisnum _satoshis : '100' ,
* timestamp : '1535116657' ,
* expiry : '3600' ,
* description : 'hundredSatoshis blitzhub' ,
* description _hash : '' ,
* fallback _addr : '' ,
* cltv _expiry : '10' ,
* route _hints : [ ] }
*
* @ param invoice BOLT invoice string
* @ return { Promise . < Object > }
* /
async decodeInvoice ( invoice ) {
await this . checkLogin ( ) ;
let response = await this . _api . get ( '/decodeinvoice?invoice=' + invoice , {
headers : {
'Access-Control-Allow-Origin' : '*' ,
'Content-Type' : 'application/json' ,
Authorization : 'Bearer' + ' ' + this . access _token ,
} ,
} ) ;
let json = response . body ;
if ( typeof json === 'undefined' ) {
throw new Error ( 'API failure: ' + response . err + ' ' + JSON . stringify ( response . body ) ) ;
}
if ( json && json . error ) {
throw new Error ( 'API error: ' + json . message + ' (code ' + json . code + ')' ) ;
}
if ( ! json . payment _hash ) {
throw new Error ( 'API unexpected response: ' + JSON . stringify ( response . body ) ) ;
}
return ( this . decoded _invoice _raw = json ) ;
}
async fetchInfo ( ) {
let response = await this . _api . get ( '/getinfo' , {
headers : {
'Access-Control-Allow-Origin' : '*' ,
'Content-Type' : 'application/json' ,
Authorization : 'Bearer' + ' ' + this . access _token ,
} ,
} ) ;
let json = response . body ;
if ( typeof json === 'undefined' ) {
throw new Error ( 'API failure: ' + response . err + ' ' + JSON . stringify ( response . body ) ) ;
}
if ( json && json . error ) {
throw new Error ( 'API error: ' + json . message + ' (code ' + json . code + ')' ) ;
}
if ( ! json . identity _pubkey ) {
throw new Error ( 'API unexpected response: ' + JSON . stringify ( response . body ) ) ;
}
this . info _raw = json ;
2018-08-19 16:54:15 +02:00
}
2018-07-15 21:56:28 +02:00
2019-03-08 22:54:47 +01:00
static async isValidNodeAddress ( address ) {
let apiCall = new Frisbee ( {
baseURI : address ,
} ) ;
let response = await apiCall . get ( '/getinfo' , {
headers : {
'Access-Control-Allow-Origin' : '*' ,
'Content-Type' : 'application/json' ,
} ,
} ) ;
let json = response . body ;
if ( typeof json === 'undefined' ) {
throw new Error ( 'API failure: ' + response . err + ' ' + JSON . stringify ( response . body ) ) ;
}
if ( json && json . code && json . code !== 1 ) {
throw new Error ( 'API error: ' + json . message + ' (code ' + json . code + ')' ) ;
}
return true ;
}
2018-09-01 01:28:19 +02:00
allowReceive ( ) {
2018-12-25 17:34:51 +01:00
return true ;
2018-09-01 01:28:19 +02:00
}
2018-07-15 21:56:28 +02:00
}
2018-09-01 01:28:19 +02:00
/ *
pending tx :
[ { amount : 0.00078061 ,
account : '521172' ,
address : '3F9seBGCJZQ4WJJHwGhrxeGXCGbrm5SNpF' ,
category : 'receive' ,
confirmations : 0 ,
blockhash : '' ,
blockindex : 0 ,
blocktime : 0 ,
txid : '28a74277e47c2d772ee8a40464209c90dce084f3b5de38a2f41b14c79e3bfc62' ,
walletconflicts : [ ] ,
time : 1535024434 ,
timereceived : 1535024434 } ]
tx :
[ { amount : 0.00078061 ,
account : '521172' ,
address : '3F9seBGCJZQ4WJJHwGhrxeGXCGbrm5SNpF' ,
category : 'receive' ,
confirmations : 5 ,
blockhash : '0000000000000000000edf18e9ece18e449c6d8eed1f729946b3531c32ee9f57' ,
blockindex : 693 ,
blocktime : 1535024914 ,
txid : '28a74277e47c2d772ee8a40464209c90dce084f3b5de38a2f41b14c79e3bfc62' ,
walletconflicts : [ ] ,
time : 1535024434 ,
timereceived : 1535024434 } ]
* /