Merge branch 'xpub'
This commit is contained in:
commit
dda96b4a8f
8
.gitignore
vendored
8
.gitignore
vendored
@ -6,8 +6,10 @@ notes.txt
|
|||||||
debug*
|
debug*
|
||||||
.vscode
|
.vscode
|
||||||
docker/blockbook
|
docker/blockbook
|
||||||
build
|
build/pkg-defs
|
||||||
!build/templates
|
build/blockbook
|
||||||
!build/docker
|
build/ldb
|
||||||
|
build/sst_dump
|
||||||
|
build/*.deb
|
||||||
.bin-image
|
.bin-image
|
||||||
.deb-image
|
.deb-image
|
||||||
4
Gopkg.lock
generated
4
Gopkg.lock
generated
@ -94,8 +94,8 @@
|
|||||||
[[projects]]
|
[[projects]]
|
||||||
branch = "master"
|
branch = "master"
|
||||||
name = "github.com/martinboehm/btcutil"
|
name = "github.com/martinboehm/btcutil"
|
||||||
packages = [".","base58","bech32","chaincfg","txscript"]
|
packages = [".","base58","bech32","chaincfg","hdkeychain","txscript"]
|
||||||
revision = "613fec26904062ae125fb073762af3a77c77b6c7"
|
revision = "63034958e64b209cb9294128309dbaed497cde7b"
|
||||||
|
|
||||||
[[projects]]
|
[[projects]]
|
||||||
branch = "master"
|
branch = "master"
|
||||||
|
|||||||
101
api/types.go
101
api/types.go
@ -5,28 +5,36 @@ import (
|
|||||||
"blockbook/common"
|
"blockbook/common"
|
||||||
"blockbook/db"
|
"blockbook/db"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
|
"errors"
|
||||||
"math/big"
|
"math/big"
|
||||||
"time"
|
"time"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
const maxUint32 = ^uint32(0)
|
||||||
const maxInt = int(^uint(0) >> 1)
|
const maxInt = int(^uint(0) >> 1)
|
||||||
|
const maxInt64 = int64(^uint64(0) >> 1)
|
||||||
|
|
||||||
// GetAddressOption specifies what data returns GetAddress api call
|
// AccountDetails specifies what data returns GetAddress and GetXpub calls
|
||||||
type GetAddressOption int
|
type AccountDetails int
|
||||||
|
|
||||||
const (
|
const (
|
||||||
// Basic - only that address is indexed and some basic info
|
// AccountDetailsBasic - only that address is indexed and some basic info
|
||||||
Basic GetAddressOption = iota
|
AccountDetailsBasic AccountDetails = iota
|
||||||
// Balance - only balances
|
// AccountDetailsTokens - basic info + tokens
|
||||||
Balance
|
AccountDetailsTokens
|
||||||
// TxidHistory - balances and txids, subject to paging
|
// AccountDetailsTokenBalances - basic info + token with balance
|
||||||
TxidHistory
|
AccountDetailsTokenBalances
|
||||||
// TxHistoryLight - balances and easily obtained tx data (not requiring request to backend), subject to paging
|
// AccountDetailsTxidHistory - basic + token balances + txids, subject to paging
|
||||||
TxHistoryLight
|
AccountDetailsTxidHistory
|
||||||
// TxHistory - balances and full tx data, subject to paging
|
// AccountDetailsTxHistoryLight - basic + tokens + easily obtained tx data (not requiring requests to backend), subject to paging
|
||||||
TxHistory
|
AccountDetailsTxHistoryLight
|
||||||
|
// AccountDetailsTxHistory - basic + tokens + full tx data, subject to paging
|
||||||
|
AccountDetailsTxHistory
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// ErrUnsupportedXpub is returned when coin type does not support xpub address derivation or provided string is not an xpub
|
||||||
|
var ErrUnsupportedXpub = errors.New("XPUB not supported")
|
||||||
|
|
||||||
// APIError extends error by information if the error details should be returned to the end user
|
// APIError extends error by information if the error details should be returned to the end user
|
||||||
type APIError struct {
|
type APIError struct {
|
||||||
Text string
|
Text string
|
||||||
@ -48,6 +56,11 @@ func NewAPIError(s string, public bool) error {
|
|||||||
// Amount is datatype holding amounts
|
// Amount is datatype holding amounts
|
||||||
type Amount big.Int
|
type Amount big.Int
|
||||||
|
|
||||||
|
// IsZeroBigInt if big int has zero value
|
||||||
|
func IsZeroBigInt(b *big.Int) bool {
|
||||||
|
return len(b.Bits()) == 0
|
||||||
|
}
|
||||||
|
|
||||||
// MarshalJSON Amount serialization
|
// MarshalJSON Amount serialization
|
||||||
func (a *Amount) MarshalJSON() (out []byte, err error) {
|
func (a *Amount) MarshalJSON() (out []byte, err error) {
|
||||||
if a == nil {
|
if a == nil {
|
||||||
@ -123,16 +136,22 @@ type TokenType string
|
|||||||
// ERC20TokenType is Ethereum ERC20 token
|
// ERC20TokenType is Ethereum ERC20 token
|
||||||
const ERC20TokenType TokenType = "ERC20"
|
const ERC20TokenType TokenType = "ERC20"
|
||||||
|
|
||||||
|
// XPUBAddressTokenType is address derived from xpub
|
||||||
|
const XPUBAddressTokenType TokenType = "XPUBAddress"
|
||||||
|
|
||||||
// Token contains info about tokens held by an address
|
// Token contains info about tokens held by an address
|
||||||
type Token struct {
|
type Token struct {
|
||||||
Type TokenType `json:"type"`
|
Type TokenType `json:"type"`
|
||||||
Contract string `json:"contract"`
|
Name string `json:"name"`
|
||||||
Transfers int `json:"transfers"`
|
Path string `json:"path,omitempty"`
|
||||||
Name string `json:"name"`
|
Contract string `json:"contract,omitempty"`
|
||||||
Symbol string `json:"symbol"`
|
Transfers int `json:"transfers"`
|
||||||
Decimals int `json:"decimals"`
|
Symbol string `json:"symbol,omitempty"`
|
||||||
BalanceSat *Amount `json:"balance,omitempty"`
|
Decimals int `json:"decimals,omitempty"`
|
||||||
ContractIndex string `json:"-"`
|
BalanceSat *Amount `json:"balance,omitempty"`
|
||||||
|
TotalReceivedSat *Amount `json:"totalReceived,omitempty"`
|
||||||
|
TotalSentSat *Amount `json:"totalSent,omitempty"`
|
||||||
|
ContractIndex string `json:"-"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// TokenTransfer contains info about a token transfer done in a transaction
|
// TokenTransfer contains info about a token transfer done in a transaction
|
||||||
@ -185,6 +204,9 @@ type Paging struct {
|
|||||||
ItemsOnPage int `json:"itemsOnPage,omitempty"`
|
ItemsOnPage int `json:"itemsOnPage,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TokenDetailLevel specifies detail level of tokens returned by GetAddress and GetXpubAddress
|
||||||
|
type TokenDetailLevel int
|
||||||
|
|
||||||
const (
|
const (
|
||||||
// AddressFilterVoutOff disables filtering of transactions by vout
|
// AddressFilterVoutOff disables filtering of transactions by vout
|
||||||
AddressFilterVoutOff = -1
|
AddressFilterVoutOff = -1
|
||||||
@ -192,6 +214,13 @@ const (
|
|||||||
AddressFilterVoutInputs = -2
|
AddressFilterVoutInputs = -2
|
||||||
// AddressFilterVoutOutputs specifies that only txs where the address is as output are returned
|
// AddressFilterVoutOutputs specifies that only txs where the address is as output are returned
|
||||||
AddressFilterVoutOutputs = -3
|
AddressFilterVoutOutputs = -3
|
||||||
|
|
||||||
|
// TokenDetailNonzeroBalance - use to return only tokens with nonzero balance
|
||||||
|
TokenDetailNonzeroBalance TokenDetailLevel = 0
|
||||||
|
// TokenDetailUsed - use to return tokens with some transfers (even if they have zero balance now)
|
||||||
|
TokenDetailUsed TokenDetailLevel = 1
|
||||||
|
// TokenDetailDiscovered - use to return all discovered tokens
|
||||||
|
TokenDetailDiscovered TokenDetailLevel = 2
|
||||||
)
|
)
|
||||||
|
|
||||||
// AddressFilter is used to filter data returned from GetAddress api method
|
// AddressFilter is used to filter data returned from GetAddress api method
|
||||||
@ -200,6 +229,9 @@ type AddressFilter struct {
|
|||||||
Contract string
|
Contract string
|
||||||
FromHeight uint32
|
FromHeight uint32
|
||||||
ToHeight uint32
|
ToHeight uint32
|
||||||
|
TokenLevel TokenDetailLevel
|
||||||
|
// OnlyConfirmed set to true will ignore mempool transactions; mempool is also ignored if FromHeight/ToHeight filter is specified
|
||||||
|
OnlyConfirmed bool
|
||||||
}
|
}
|
||||||
|
|
||||||
// Address holds information about address and its transactions
|
// Address holds information about address and its transactions
|
||||||
@ -216,18 +248,41 @@ type Address struct {
|
|||||||
Transactions []*Tx `json:"transactions,omitempty"`
|
Transactions []*Tx `json:"transactions,omitempty"`
|
||||||
Txids []string `json:"txids,omitempty"`
|
Txids []string `json:"txids,omitempty"`
|
||||||
Nonce string `json:"nonce,omitempty"`
|
Nonce string `json:"nonce,omitempty"`
|
||||||
|
TotalTokens int `json:"totalTokens,omitempty"`
|
||||||
Tokens []Token `json:"tokens,omitempty"`
|
Tokens []Token `json:"tokens,omitempty"`
|
||||||
Erc20Contract *bchain.Erc20Contract `json:"erc20contract,omitempty"`
|
Erc20Contract *bchain.Erc20Contract `json:"erc20contract,omitempty"`
|
||||||
Filter string `json:"-"`
|
// helpers for explorer
|
||||||
|
Filter string `json:"-"`
|
||||||
|
XPubAddresses map[string]struct{} `json:"-"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// AddressUtxo holds information about address and its transactions
|
// Utxo is one unspent transaction output
|
||||||
type AddressUtxo struct {
|
type Utxo struct {
|
||||||
Txid string `json:"txid"`
|
Txid string `json:"txid"`
|
||||||
Vout int32 `json:"vout"`
|
Vout int32 `json:"vout"`
|
||||||
AmountSat *Amount `json:"value"`
|
AmountSat *Amount `json:"value"`
|
||||||
Height int `json:"height,omitempty"`
|
Height int `json:"height,omitempty"`
|
||||||
Confirmations int `json:"confirmations"`
|
Confirmations int `json:"confirmations"`
|
||||||
|
Address string `json:"address,omitempty"`
|
||||||
|
Path string `json:"path,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// Utxos is array of Utxo
|
||||||
|
type Utxos []Utxo
|
||||||
|
|
||||||
|
func (a Utxos) Len() int { return len(a) }
|
||||||
|
func (a Utxos) Swap(i, j int) { a[i], a[j] = a[j], a[i] }
|
||||||
|
func (a Utxos) Less(i, j int) bool {
|
||||||
|
// sort in reverse order, unconfirmed (height==0) utxos on top
|
||||||
|
hi := a[i].Height
|
||||||
|
hj := a[j].Height
|
||||||
|
if hi == 0 {
|
||||||
|
hi = maxInt
|
||||||
|
}
|
||||||
|
if hj == 0 {
|
||||||
|
hj = maxInt
|
||||||
|
}
|
||||||
|
return hi >= hj
|
||||||
}
|
}
|
||||||
|
|
||||||
// Blocks is list of blocks with paging information
|
// Blocks is list of blocks with paging information
|
||||||
|
|||||||
@ -192,7 +192,7 @@ func (w *Worker) AddressToV1(a *Address) *AddressV1 {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// AddressUtxoToV1 converts []AddressUtxo to []AddressUtxoV1
|
// AddressUtxoToV1 converts []AddressUtxo to []AddressUtxoV1
|
||||||
func (w *Worker) AddressUtxoToV1(au []AddressUtxo) []AddressUtxoV1 {
|
func (w *Worker) AddressUtxoToV1(au Utxos) []AddressUtxoV1 {
|
||||||
d := w.chainParser.AmountDecimals()
|
d := w.chainParser.AmountDecimals()
|
||||||
v1 := make([]AddressUtxoV1, len(au))
|
v1 := make([]AddressUtxoV1, len(au))
|
||||||
for i := range au {
|
for i := range au {
|
||||||
|
|||||||
542
api/worker.go
542
api/worker.go
@ -51,7 +51,7 @@ func (w *Worker) getAddressesFromVout(vout *bchain.Vout) (bchain.AddressDescript
|
|||||||
// setSpendingTxToVout is helper function, that finds transaction that spent given output and sets it to the output
|
// setSpendingTxToVout is helper function, that finds transaction that spent given output and sets it to the output
|
||||||
// there is no direct index for the operation, it must be found using addresses -> txaddresses -> tx
|
// there is no direct index for the operation, it must be found using addresses -> txaddresses -> tx
|
||||||
func (w *Worker) setSpendingTxToVout(vout *Vout, txid string, height uint32) error {
|
func (w *Worker) setSpendingTxToVout(vout *Vout, txid string, height uint32) error {
|
||||||
err := w.db.GetAddrDescTransactions(vout.AddrDesc, height, ^uint32(0), func(t string, height uint32, indexes []int32) error {
|
err := w.db.GetAddrDescTransactions(vout.AddrDesc, height, maxUint32, func(t string, height uint32, indexes []int32) error {
|
||||||
for _, index := range indexes {
|
for _, index := range indexes {
|
||||||
// take only inputs
|
// take only inputs
|
||||||
if index < 0 {
|
if index < 0 {
|
||||||
@ -364,7 +364,7 @@ func (w *Worker) getAddressTxids(addrDesc bchain.AddressDescriptor, mempool bool
|
|||||||
} else {
|
} else {
|
||||||
to := filter.ToHeight
|
to := filter.ToHeight
|
||||||
if to == 0 {
|
if to == 0 {
|
||||||
to = ^uint32(0)
|
to = maxUint32
|
||||||
}
|
}
|
||||||
err = w.db.GetAddrDescTransactions(addrDesc, filter.FromHeight, to, callback)
|
err = w.db.GetAddrDescTransactions(addrDesc, filter.FromHeight, to, callback)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -479,7 +479,7 @@ func computePaging(count, page, itemsOnPage int) (Paging, int, int, int) {
|
|||||||
}, from, to, page
|
}, from, to, page
|
||||||
}
|
}
|
||||||
|
|
||||||
func (w *Worker) getEthereumTypeAddressBalances(addrDesc bchain.AddressDescriptor, option GetAddressOption, filter *AddressFilter) (*db.AddrBalance, []Token, *bchain.Erc20Contract, uint64, int, int, error) {
|
func (w *Worker) getEthereumTypeAddressBalances(addrDesc bchain.AddressDescriptor, details AccountDetails, filter *AddressFilter) (*db.AddrBalance, []Token, *bchain.Erc20Contract, uint64, int, int, error) {
|
||||||
var (
|
var (
|
||||||
ba *db.AddrBalance
|
ba *db.AddrBalance
|
||||||
tokens []Token
|
tokens []Token
|
||||||
@ -515,53 +515,55 @@ func (w *Worker) getEthereumTypeAddressBalances(addrDesc bchain.AddressDescripto
|
|||||||
return nil, nil, nil, 0, 0, 0, NewAPIError(fmt.Sprintf("Invalid contract filter, %v", err), true)
|
return nil, nil, nil, 0, 0, 0, NewAPIError(fmt.Sprintf("Invalid contract filter, %v", err), true)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
tokens = make([]Token, len(ca.Contracts))
|
if details > AccountDetailsBasic {
|
||||||
var j int
|
tokens = make([]Token, len(ca.Contracts))
|
||||||
for i, c := range ca.Contracts {
|
var j int
|
||||||
if len(filterDesc) > 0 {
|
for i, c := range ca.Contracts {
|
||||||
if !bytes.Equal(filterDesc, c.Contract) {
|
if len(filterDesc) > 0 {
|
||||||
continue
|
if !bytes.Equal(filterDesc, c.Contract) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
// filter only transactions of this contract
|
||||||
|
filter.Vout = i + 1
|
||||||
}
|
}
|
||||||
// filter only transactions of this contract
|
validContract := true
|
||||||
filter.Vout = i + 1
|
ci, err := w.chain.EthereumTypeGetErc20ContractInfo(c.Contract)
|
||||||
}
|
|
||||||
validContract := true
|
|
||||||
ci, err := w.chain.EthereumTypeGetErc20ContractInfo(c.Contract)
|
|
||||||
if err != nil {
|
|
||||||
return nil, nil, nil, 0, 0, 0, errors.Annotatef(err, "EthereumTypeGetErc20ContractInfo %v", c.Contract)
|
|
||||||
}
|
|
||||||
if ci == nil {
|
|
||||||
ci = &bchain.Erc20Contract{}
|
|
||||||
addresses, _, _ := w.chainParser.GetAddressesFromAddrDesc(c.Contract)
|
|
||||||
if len(addresses) > 0 {
|
|
||||||
ci.Contract = addresses[0]
|
|
||||||
ci.Name = addresses[0]
|
|
||||||
}
|
|
||||||
validContract = false
|
|
||||||
}
|
|
||||||
// do not read contract balances etc in case of Basic option
|
|
||||||
if option != Basic && validContract {
|
|
||||||
b, err = w.chain.EthereumTypeGetErc20ContractBalance(addrDesc, c.Contract)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
// return nil, nil, nil, errors.Annotatef(err, "EthereumTypeGetErc20ContractBalance %v %v", addrDesc, c.Contract)
|
return nil, nil, nil, 0, 0, 0, errors.Annotatef(err, "EthereumTypeGetErc20ContractInfo %v", c.Contract)
|
||||||
glog.Warningf("EthereumTypeGetErc20ContractBalance addr %v, contract %v, %v", addrDesc, c.Contract, err)
|
|
||||||
}
|
}
|
||||||
} else {
|
if ci == nil {
|
||||||
b = nil
|
ci = &bchain.Erc20Contract{}
|
||||||
|
addresses, _, _ := w.chainParser.GetAddressesFromAddrDesc(c.Contract)
|
||||||
|
if len(addresses) > 0 {
|
||||||
|
ci.Contract = addresses[0]
|
||||||
|
ci.Name = addresses[0]
|
||||||
|
}
|
||||||
|
validContract = false
|
||||||
|
}
|
||||||
|
// do not read contract balances etc in case of Basic option
|
||||||
|
if details >= AccountDetailsTokenBalances && validContract {
|
||||||
|
b, err = w.chain.EthereumTypeGetErc20ContractBalance(addrDesc, c.Contract)
|
||||||
|
if err != nil {
|
||||||
|
// return nil, nil, nil, errors.Annotatef(err, "EthereumTypeGetErc20ContractBalance %v %v", addrDesc, c.Contract)
|
||||||
|
glog.Warningf("EthereumTypeGetErc20ContractBalance addr %v, contract %v, %v", addrDesc, c.Contract, err)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
b = nil
|
||||||
|
}
|
||||||
|
tokens[j] = Token{
|
||||||
|
Type: ERC20TokenType,
|
||||||
|
BalanceSat: (*Amount)(b),
|
||||||
|
Contract: ci.Contract,
|
||||||
|
Name: ci.Name,
|
||||||
|
Symbol: ci.Symbol,
|
||||||
|
Transfers: int(c.Txs),
|
||||||
|
Decimals: ci.Decimals,
|
||||||
|
ContractIndex: strconv.Itoa(i + 1),
|
||||||
|
}
|
||||||
|
j++
|
||||||
}
|
}
|
||||||
tokens[j] = Token{
|
tokens = tokens[:j]
|
||||||
Type: ERC20TokenType,
|
|
||||||
BalanceSat: (*Amount)(b),
|
|
||||||
Contract: ci.Contract,
|
|
||||||
Name: ci.Name,
|
|
||||||
Symbol: ci.Symbol,
|
|
||||||
Transfers: int(c.Txs),
|
|
||||||
Decimals: ci.Decimals,
|
|
||||||
ContractIndex: strconv.Itoa(i + 1),
|
|
||||||
}
|
|
||||||
j++
|
|
||||||
}
|
}
|
||||||
tokens = tokens[:j]
|
|
||||||
ci, err = w.chain.EthereumTypeGetErc20ContractInfo(addrDesc)
|
ci, err = w.chain.EthereumTypeGetErc20ContractInfo(addrDesc)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, nil, nil, 0, 0, 0, err
|
return nil, nil, nil, 0, 0, 0, err
|
||||||
@ -588,17 +590,62 @@ func (w *Worker) getEthereumTypeAddressBalances(addrDesc bchain.AddressDescripto
|
|||||||
return ba, tokens, ci, n, nonContractTxs, totalResults, nil
|
return ba, tokens, ci, n, nonContractTxs, totalResults, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (w *Worker) txFromTxid(txid string, bestheight uint32, option AccountDetails) (*Tx, error) {
|
||||||
|
var tx *Tx
|
||||||
|
var err error
|
||||||
|
// only ChainBitcoinType supports TxHistoryLight
|
||||||
|
if option == AccountDetailsTxHistoryLight && w.chainType == bchain.ChainBitcoinType {
|
||||||
|
ta, err := w.db.GetTxAddresses(txid)
|
||||||
|
if err != nil {
|
||||||
|
return nil, errors.Annotatef(err, "GetTxAddresses %v", txid)
|
||||||
|
}
|
||||||
|
if ta == nil {
|
||||||
|
glog.Warning("DB inconsistency: tx ", txid, ": not found in txAddresses")
|
||||||
|
// as fallback, provide empty TxAddresses to return at least something
|
||||||
|
ta = &db.TxAddresses{}
|
||||||
|
}
|
||||||
|
bi, err := w.db.GetBlockInfo(ta.Height)
|
||||||
|
if err != nil {
|
||||||
|
return nil, errors.Annotatef(err, "GetBlockInfo %v", ta.Height)
|
||||||
|
}
|
||||||
|
if bi == nil {
|
||||||
|
glog.Warning("DB inconsistency: block height ", ta.Height, ": not found in db")
|
||||||
|
// provide empty BlockInfo to return the rest of tx data
|
||||||
|
bi = &db.BlockInfo{}
|
||||||
|
}
|
||||||
|
tx = w.txFromTxAddress(txid, ta, bi, bestheight)
|
||||||
|
} else {
|
||||||
|
tx, err = w.GetTransaction(txid, false, true)
|
||||||
|
if err != nil {
|
||||||
|
return nil, errors.Annotatef(err, "GetTransaction %v", txid)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return tx, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (w *Worker) getAddrDescAndNormalizeAddress(address string) (bchain.AddressDescriptor, string, error) {
|
||||||
|
addrDesc, err := w.chainParser.GetAddrDescFromAddress(address)
|
||||||
|
if err != nil {
|
||||||
|
return nil, "", NewAPIError(fmt.Sprintf("Invalid address, %v", err), true)
|
||||||
|
}
|
||||||
|
// convert the address to the format defined by the parser
|
||||||
|
addresses, _, err := w.chainParser.GetAddressesFromAddrDesc(addrDesc)
|
||||||
|
if err != nil {
|
||||||
|
glog.V(2).Infof("GetAddressesFromAddrDesc error %v, %v", err, addrDesc)
|
||||||
|
}
|
||||||
|
if len(addresses) == 1 {
|
||||||
|
address = addresses[0]
|
||||||
|
}
|
||||||
|
return addrDesc, address, nil
|
||||||
|
}
|
||||||
|
|
||||||
// GetAddress computes address value and gets transactions for given address
|
// GetAddress computes address value and gets transactions for given address
|
||||||
func (w *Worker) GetAddress(address string, page int, txsOnPage int, option GetAddressOption, filter *AddressFilter) (*Address, error) {
|
func (w *Worker) GetAddress(address string, page int, txsOnPage int, option AccountDetails, filter *AddressFilter) (*Address, error) {
|
||||||
start := time.Now()
|
start := time.Now()
|
||||||
page--
|
page--
|
||||||
if page < 0 {
|
if page < 0 {
|
||||||
page = 0
|
page = 0
|
||||||
}
|
}
|
||||||
addrDesc, err := w.chainParser.GetAddrDescFromAddress(address)
|
|
||||||
if err != nil {
|
|
||||||
return nil, NewAPIError(fmt.Sprintf("Invalid address, %v", err), true)
|
|
||||||
}
|
|
||||||
var (
|
var (
|
||||||
ba *db.AddrBalance
|
ba *db.AddrBalance
|
||||||
tokens []Token
|
tokens []Token
|
||||||
@ -613,6 +660,10 @@ func (w *Worker) GetAddress(address string, page int, txsOnPage int, option GetA
|
|||||||
nonTokenTxs int
|
nonTokenTxs int
|
||||||
totalResults int
|
totalResults int
|
||||||
)
|
)
|
||||||
|
addrDesc, address, err := w.getAddrDescAndNormalizeAddress(address)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
if w.chainType == bchain.ChainEthereumType {
|
if w.chainType == bchain.ChainEthereumType {
|
||||||
var n uint64
|
var n uint64
|
||||||
ba, tokens, erc20c, n, nonTokenTxs, totalResults, err = w.getEthereumTypeAddressBalances(addrDesc, option, filter)
|
ba, tokens, erc20c, n, nonTokenTxs, totalResults, err = w.getEthereumTypeAddressBalances(addrDesc, option, filter)
|
||||||
@ -635,114 +686,67 @@ func (w *Worker) GetAddress(address string, page int, txsOnPage int, option GetA
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// get tx history if requested by option or check mempool if there are some transactions for a new address
|
// if there are only unconfirmed transactions, there is no paging
|
||||||
if option >= TxidHistory || ba == nil {
|
if ba == nil {
|
||||||
// convert the address to the format defined by the parser
|
ba = &db.AddrBalance{}
|
||||||
addresses, _, err := w.chainParser.GetAddressesFromAddrDesc(addrDesc)
|
page = 0
|
||||||
|
}
|
||||||
|
// process mempool, only if toHeight is not specified
|
||||||
|
if filter.ToHeight == 0 && !filter.OnlyConfirmed {
|
||||||
|
txm, err = w.getAddressTxids(addrDesc, true, filter, maxInt)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
glog.V(2).Infof("GetAddressesFromAddrDesc error %v, %v", err, addrDesc)
|
return nil, errors.Annotatef(err, "getAddressTxids %v true", addrDesc)
|
||||||
}
|
}
|
||||||
if len(addresses) == 1 {
|
for _, txid := range txm {
|
||||||
address = addresses[0]
|
tx, err := w.GetTransaction(txid, false, false)
|
||||||
}
|
// mempool transaction may fail
|
||||||
// get txs from mempool only if blockheight filter is off
|
if err != nil || tx == nil {
|
||||||
if filter.FromHeight == 0 && filter.ToHeight == 0 {
|
glog.Warning("GetTransaction in mempool: ", err)
|
||||||
txm, err = w.getAddressTxids(addrDesc, true, filter, maxInt)
|
|
||||||
if err != nil {
|
|
||||||
return nil, errors.Annotatef(err, "getAddressTxids %v true", addrDesc)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// if there are only unconfirmed transactions, there is no paging
|
|
||||||
if ba == nil {
|
|
||||||
ba = &db.AddrBalance{}
|
|
||||||
page = 0
|
|
||||||
}
|
|
||||||
if option >= TxidHistory {
|
|
||||||
txc, err := w.getAddressTxids(addrDesc, false, filter, (page+1)*txsOnPage)
|
|
||||||
if err != nil {
|
|
||||||
return nil, errors.Annotatef(err, "getAddressTxids %v false", addrDesc)
|
|
||||||
}
|
|
||||||
bestheight, _, err := w.db.GetBestBlock()
|
|
||||||
if err != nil {
|
|
||||||
return nil, errors.Annotatef(err, "GetBestBlock")
|
|
||||||
}
|
|
||||||
var from, to int
|
|
||||||
pg, from, to, page = computePaging(len(txc), page, txsOnPage)
|
|
||||||
if len(txc) >= txsOnPage {
|
|
||||||
if totalResults < 0 {
|
|
||||||
pg.TotalPages = -1
|
|
||||||
} else {
|
|
||||||
pg, _, _, _ = computePaging(totalResults, page, txsOnPage)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if option == TxidHistory {
|
|
||||||
txids = make([]string, len(txm)+to-from)
|
|
||||||
} else {
|
} else {
|
||||||
txs = make([]*Tx, len(txm)+to-from)
|
// skip already confirmed txs, mempool may be out of sync
|
||||||
}
|
if tx.Confirmations == 0 {
|
||||||
txi := 0
|
uBalSat.Add(&uBalSat, tx.getAddrVoutValue(addrDesc))
|
||||||
// get mempool transactions
|
uBalSat.Sub(&uBalSat, tx.getAddrVinValue(addrDesc))
|
||||||
for _, txid := range txm {
|
if page == 0 {
|
||||||
tx, err := w.GetTransaction(txid, false, false)
|
if option == AccountDetailsTxidHistory {
|
||||||
// mempool transaction may fail
|
txids = append(txids, tx.Txid)
|
||||||
if err != nil || tx == nil {
|
} else if option >= AccountDetailsTxHistoryLight {
|
||||||
glog.Warning("GetTransaction in mempool: ", err)
|
txs = append(txs, tx)
|
||||||
} else {
|
|
||||||
// skip already confirmed txs, mempool may be out of sync
|
|
||||||
if tx.Confirmations == 0 {
|
|
||||||
uBalSat.Add(&uBalSat, tx.getAddrVoutValue(addrDesc))
|
|
||||||
uBalSat.Sub(&uBalSat, tx.getAddrVinValue(addrDesc))
|
|
||||||
if page == 0 {
|
|
||||||
if option == TxidHistory {
|
|
||||||
txids[txi] = tx.Txid
|
|
||||||
} else {
|
|
||||||
txs[txi] = tx
|
|
||||||
}
|
|
||||||
txi++
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// get confirmed transactions
|
}
|
||||||
for i := from; i < to; i++ {
|
}
|
||||||
txid := txc[i]
|
// get tx history if requested by option or check mempool if there are some transactions for a new address
|
||||||
if option == TxidHistory {
|
if option >= AccountDetailsTxidHistory {
|
||||||
txids[txi] = txid
|
txc, err := w.getAddressTxids(addrDesc, false, filter, (page+1)*txsOnPage)
|
||||||
} else {
|
if err != nil {
|
||||||
// only ChainBitcoinType supports TxHistoryLight
|
return nil, errors.Annotatef(err, "getAddressTxids %v false", addrDesc)
|
||||||
if option == TxHistoryLight && w.chainType == bchain.ChainBitcoinType {
|
}
|
||||||
ta, err := w.db.GetTxAddresses(txid)
|
bestheight, _, err := w.db.GetBestBlock()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, errors.Annotatef(err, "GetTxAddresses %v", txid)
|
return nil, errors.Annotatef(err, "GetBestBlock")
|
||||||
}
|
}
|
||||||
if ta == nil {
|
var from, to int
|
||||||
glog.Warning("DB inconsistency: tx ", txid, ": not found in txAddresses")
|
pg, from, to, page = computePaging(len(txc), page, txsOnPage)
|
||||||
// as fallback, provide empty TxAddresses to return at least something
|
if len(txc) >= txsOnPage {
|
||||||
ta = &db.TxAddresses{}
|
if totalResults < 0 {
|
||||||
}
|
pg.TotalPages = -1
|
||||||
bi, err := w.db.GetBlockInfo(ta.Height)
|
} else {
|
||||||
if err != nil {
|
pg, _, _, _ = computePaging(totalResults, page, txsOnPage)
|
||||||
return nil, errors.Annotatef(err, "GetBlockInfo %v", ta.Height)
|
|
||||||
}
|
|
||||||
if bi == nil {
|
|
||||||
glog.Warning("DB inconsistency: block height ", ta.Height, ": not found in db")
|
|
||||||
// provide empty BlockInfo to return the rest of tx data
|
|
||||||
bi = &db.BlockInfo{}
|
|
||||||
}
|
|
||||||
txs[txi] = w.txFromTxAddress(txid, ta, bi, bestheight)
|
|
||||||
} else {
|
|
||||||
txs[txi], err = w.GetTransaction(txid, false, true)
|
|
||||||
if err != nil {
|
|
||||||
return nil, errors.Annotatef(err, "GetTransaction %v", txid)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
txi++
|
|
||||||
}
|
}
|
||||||
if option == TxidHistory {
|
}
|
||||||
txids = txids[:txi]
|
for i := from; i < to; i++ {
|
||||||
} else if option >= TxHistoryLight {
|
txid := txc[i]
|
||||||
txs = txs[:txi]
|
if option == AccountDetailsTxidHistory {
|
||||||
|
txids = append(txids, txid)
|
||||||
|
} else {
|
||||||
|
tx, err := w.txFromTxid(txid, bestheight, option)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
txs = append(txs, tx)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -770,126 +774,138 @@ func (w *Worker) GetAddress(address string, page int, txsOnPage int, option GetA
|
|||||||
return r, nil
|
return r, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (w *Worker) getAddrDescUtxo(addrDesc bchain.AddressDescriptor, ba *db.AddrBalance, onlyConfirmed bool, onlyMempool bool) (Utxos, error) {
|
||||||
|
var err error
|
||||||
|
r := make(Utxos, 0, 8)
|
||||||
|
spentInMempool := make(map[string]struct{})
|
||||||
|
if !onlyConfirmed {
|
||||||
|
// get utxo from mempool
|
||||||
|
txm, err := w.getAddressTxids(addrDesc, true, &AddressFilter{Vout: AddressFilterVoutOff}, maxInt)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if len(txm) > 0 {
|
||||||
|
mc := make([]*bchain.Tx, len(txm))
|
||||||
|
for i, txid := range txm {
|
||||||
|
// get mempool txs and process their inputs to detect spends between mempool txs
|
||||||
|
bchainTx, _, err := w.txCache.GetTransaction(txid)
|
||||||
|
// mempool transaction may fail
|
||||||
|
if err != nil {
|
||||||
|
glog.Error("GetTransaction in mempool ", txid, ": ", err)
|
||||||
|
} else {
|
||||||
|
mc[i] = bchainTx
|
||||||
|
// get outputs spent by the mempool tx
|
||||||
|
for i := range bchainTx.Vin {
|
||||||
|
vin := &bchainTx.Vin[i]
|
||||||
|
spentInMempool[vin.Txid+strconv.Itoa(int(vin.Vout))] = struct{}{}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for _, bchainTx := range mc {
|
||||||
|
if bchainTx != nil {
|
||||||
|
for i := range bchainTx.Vout {
|
||||||
|
vout := &bchainTx.Vout[i]
|
||||||
|
vad, err := w.chainParser.GetAddrDescFromVout(vout)
|
||||||
|
if err == nil && bytes.Equal(addrDesc, vad) {
|
||||||
|
// report only outpoints that are not spent in mempool
|
||||||
|
_, e := spentInMempool[bchainTx.Txid+strconv.Itoa(i)]
|
||||||
|
if !e {
|
||||||
|
r = append(r, Utxo{
|
||||||
|
Txid: bchainTx.Txid,
|
||||||
|
Vout: int32(i),
|
||||||
|
AmountSat: (*Amount)(&vout.ValueSat),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if !onlyMempool {
|
||||||
|
// get utxo from index
|
||||||
|
if ba == nil {
|
||||||
|
ba, err = w.db.GetAddrDescBalance(addrDesc)
|
||||||
|
if err != nil {
|
||||||
|
return nil, NewAPIError(fmt.Sprintf("Address not found, %v", err), true)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// ba can be nil if the address is only in mempool!
|
||||||
|
if ba != nil && !IsZeroBigInt(&ba.BalanceSat) {
|
||||||
|
outpoints := make([]bchain.Outpoint, 0, 8)
|
||||||
|
err = w.db.GetAddrDescTransactions(addrDesc, 0, maxUint32, func(txid string, height uint32, indexes []int32) error {
|
||||||
|
for _, index := range indexes {
|
||||||
|
// take only outputs
|
||||||
|
if index >= 0 {
|
||||||
|
outpoints = append(outpoints, bchain.Outpoint{Txid: txid, Vout: index})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
var lastTxid string
|
||||||
|
var ta *db.TxAddresses
|
||||||
|
var checksum big.Int
|
||||||
|
checksum.Set(&ba.BalanceSat)
|
||||||
|
b, _, err := w.db.GetBestBlock()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
bestheight := int(b)
|
||||||
|
for i := len(outpoints) - 1; i >= 0 && checksum.Int64() > 0; i-- {
|
||||||
|
o := outpoints[i]
|
||||||
|
if lastTxid != o.Txid {
|
||||||
|
ta, err = w.db.GetTxAddresses(o.Txid)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
lastTxid = o.Txid
|
||||||
|
}
|
||||||
|
if ta == nil {
|
||||||
|
glog.Warning("DB inconsistency: tx ", o.Txid, ": not found in txAddresses")
|
||||||
|
} else {
|
||||||
|
if len(ta.Outputs) <= int(o.Vout) {
|
||||||
|
glog.Warning("DB inconsistency: txAddresses ", o.Txid, " does not have enough outputs")
|
||||||
|
} else {
|
||||||
|
if !ta.Outputs[o.Vout].Spent {
|
||||||
|
v := ta.Outputs[o.Vout].ValueSat
|
||||||
|
// report only outpoints that are not spent in mempool
|
||||||
|
_, e := spentInMempool[o.Txid+strconv.Itoa(int(o.Vout))]
|
||||||
|
if !e {
|
||||||
|
r = append(r, Utxo{
|
||||||
|
Txid: o.Txid,
|
||||||
|
Vout: o.Vout,
|
||||||
|
AmountSat: (*Amount)(&v),
|
||||||
|
Height: int(ta.Height),
|
||||||
|
Confirmations: bestheight - int(ta.Height) + 1,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
checksum.Sub(&checksum, &v)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if checksum.Uint64() != 0 {
|
||||||
|
glog.Warning("DB inconsistency: ", addrDesc, ": checksum is not zero")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return r, nil
|
||||||
|
}
|
||||||
|
|
||||||
// GetAddressUtxo returns unspent outputs for given address
|
// GetAddressUtxo returns unspent outputs for given address
|
||||||
func (w *Worker) GetAddressUtxo(address string, onlyConfirmed bool) ([]AddressUtxo, error) {
|
func (w *Worker) GetAddressUtxo(address string, onlyConfirmed bool) (Utxos, error) {
|
||||||
if w.chainType != bchain.ChainBitcoinType {
|
if w.chainType != bchain.ChainBitcoinType {
|
||||||
return nil, NewAPIError("Not supported", true)
|
return nil, NewAPIError("Not supported", true)
|
||||||
}
|
}
|
||||||
start := time.Now()
|
start := time.Now()
|
||||||
addrDesc, err := w.chainParser.GetAddrDescFromAddress(address)
|
addrDesc, err := w.chainParser.GetAddrDescFromAddress(address)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, NewAPIError(fmt.Sprintf("Invalid address, %v", err), true)
|
return nil, NewAPIError(fmt.Sprintf("Invalid address '%v', %v", address, err), true)
|
||||||
}
|
|
||||||
spentInMempool := make(map[string]struct{})
|
|
||||||
r := make([]AddressUtxo, 0, 8)
|
|
||||||
if !onlyConfirmed {
|
|
||||||
// get utxo from mempool
|
|
||||||
txm, err := w.getAddressTxids(addrDesc, true, &AddressFilter{Vout: AddressFilterVoutOff}, maxInt)
|
|
||||||
if err != nil {
|
|
||||||
return nil, errors.Annotatef(err, "getAddressTxids %v true", address)
|
|
||||||
}
|
|
||||||
mc := make([]*bchain.Tx, len(txm))
|
|
||||||
for i, txid := range txm {
|
|
||||||
// get mempool txs and process their inputs to detect spends between mempool txs
|
|
||||||
bchainTx, _, err := w.txCache.GetTransaction(txid)
|
|
||||||
// mempool transaction may fail
|
|
||||||
if err != nil {
|
|
||||||
glog.Error("GetTransaction in mempool ", txid, ": ", err)
|
|
||||||
} else {
|
|
||||||
mc[i] = bchainTx
|
|
||||||
// get outputs spent by the mempool tx
|
|
||||||
for i := range bchainTx.Vin {
|
|
||||||
vin := &bchainTx.Vin[i]
|
|
||||||
spentInMempool[vin.Txid+strconv.Itoa(int(vin.Vout))] = struct{}{}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
for _, bchainTx := range mc {
|
|
||||||
if bchainTx != nil {
|
|
||||||
for i := range bchainTx.Vout {
|
|
||||||
vout := &bchainTx.Vout[i]
|
|
||||||
vad, err := w.chainParser.GetAddrDescFromVout(vout)
|
|
||||||
if err == nil && bytes.Equal(addrDesc, vad) {
|
|
||||||
// report only outpoints that are not spent in mempool
|
|
||||||
_, e := spentInMempool[bchainTx.Txid+strconv.Itoa(i)]
|
|
||||||
if !e {
|
|
||||||
r = append(r, AddressUtxo{
|
|
||||||
Txid: bchainTx.Txid,
|
|
||||||
Vout: int32(i),
|
|
||||||
AmountSat: (*Amount)(&vout.ValueSat),
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// get utxo from index
|
|
||||||
ba, err := w.db.GetAddrDescBalance(addrDesc)
|
|
||||||
if err != nil {
|
|
||||||
return nil, NewAPIError(fmt.Sprintf("Address not found, %v", err), true)
|
|
||||||
}
|
|
||||||
var checksum big.Int
|
|
||||||
// ba can be nil if the address is only in mempool!
|
|
||||||
if ba != nil && ba.BalanceSat.Uint64() > 0 {
|
|
||||||
outpoints := make([]bchain.Outpoint, 0, 8)
|
|
||||||
err = w.db.GetAddrDescTransactions(addrDesc, 0, ^uint32(0), func(txid string, height uint32, indexes []int32) error {
|
|
||||||
for _, index := range indexes {
|
|
||||||
// take only outputs
|
|
||||||
if index >= 0 {
|
|
||||||
outpoints = append(outpoints, bchain.Outpoint{Txid: txid, Vout: index})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
})
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
var lastTxid string
|
|
||||||
var ta *db.TxAddresses
|
|
||||||
checksum = ba.BalanceSat
|
|
||||||
b, _, err := w.db.GetBestBlock()
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
bestheight := int(b)
|
|
||||||
for i := len(outpoints) - 1; i >= 0 && checksum.Int64() > 0; i-- {
|
|
||||||
o := outpoints[i]
|
|
||||||
if lastTxid != o.Txid {
|
|
||||||
ta, err = w.db.GetTxAddresses(o.Txid)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
lastTxid = o.Txid
|
|
||||||
}
|
|
||||||
if ta == nil {
|
|
||||||
glog.Warning("DB inconsistency: tx ", o.Txid, ": not found in txAddresses")
|
|
||||||
} else {
|
|
||||||
if len(ta.Outputs) <= int(o.Vout) {
|
|
||||||
glog.Warning("DB inconsistency: txAddresses ", o.Txid, " does not have enough outputs")
|
|
||||||
} else {
|
|
||||||
if !ta.Outputs[o.Vout].Spent {
|
|
||||||
v := ta.Outputs[o.Vout].ValueSat
|
|
||||||
// report only outpoints that are not spent in mempool
|
|
||||||
_, e := spentInMempool[o.Txid+strconv.Itoa(int(o.Vout))]
|
|
||||||
if !e {
|
|
||||||
r = append(r, AddressUtxo{
|
|
||||||
Txid: o.Txid,
|
|
||||||
Vout: o.Vout,
|
|
||||||
AmountSat: (*Amount)(&v),
|
|
||||||
Height: int(ta.Height),
|
|
||||||
Confirmations: bestheight - int(ta.Height) + 1,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
checksum.Sub(&checksum, &v)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if checksum.Uint64() != 0 {
|
|
||||||
glog.Warning("DB inconsistency: ", address, ": checksum is not zero")
|
|
||||||
}
|
}
|
||||||
|
r, err := w.getAddrDescUtxo(addrDesc, nil, onlyConfirmed, false)
|
||||||
glog.Info("GetAddressUtxo ", address, ", ", len(r), " utxos, finished in ", time.Since(start))
|
glog.Info("GetAddressUtxo ", address, ", ", len(r), " utxos, finished in ", time.Since(start))
|
||||||
return r, nil
|
return r, nil
|
||||||
}
|
}
|
||||||
@ -935,7 +951,7 @@ func (w *Worker) GetBlock(bid string, page int, txsOnPage int) (*Block, error) {
|
|||||||
// if it's a number, must be less than int32
|
// if it's a number, must be less than int32
|
||||||
var hash string
|
var hash string
|
||||||
height, err := strconv.Atoi(bid)
|
height, err := strconv.Atoi(bid)
|
||||||
if err == nil && height < int(^uint32(0)) {
|
if err == nil && height < int(maxUint32) {
|
||||||
hash, err = w.db.GetBlockHash(uint32(height))
|
hash, err = w.db.GetBlockHash(uint32(height))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
hash = bid
|
hash = bid
|
||||||
|
|||||||
574
api/xpub.go
Normal file
574
api/xpub.go
Normal file
@ -0,0 +1,574 @@
|
|||||||
|
package api
|
||||||
|
|
||||||
|
import (
|
||||||
|
"blockbook/bchain"
|
||||||
|
"blockbook/db"
|
||||||
|
"fmt"
|
||||||
|
"math/big"
|
||||||
|
"sort"
|
||||||
|
"sync"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/golang/glog"
|
||||||
|
"github.com/juju/errors"
|
||||||
|
)
|
||||||
|
|
||||||
|
const xpubLen = 111
|
||||||
|
const defaultAddressesGap = 20
|
||||||
|
const maxAddressesGap = 10000
|
||||||
|
|
||||||
|
const txInput = 1
|
||||||
|
const txOutput = 2
|
||||||
|
|
||||||
|
const xpubCacheSize = 512
|
||||||
|
const xpubCacheExpirationSeconds = 7200
|
||||||
|
|
||||||
|
var cachedXpubs = make(map[string]xpubData)
|
||||||
|
var cachedXpubsMux sync.Mutex
|
||||||
|
|
||||||
|
type xpubTxid struct {
|
||||||
|
txid string
|
||||||
|
height uint32
|
||||||
|
inputOutput byte
|
||||||
|
}
|
||||||
|
|
||||||
|
type xpubTxids []xpubTxid
|
||||||
|
|
||||||
|
func (a xpubTxids) Len() int { return len(a) }
|
||||||
|
func (a xpubTxids) Swap(i, j int) { a[i], a[j] = a[j], a[i] }
|
||||||
|
func (a xpubTxids) Less(i, j int) bool { return a[i].height >= a[j].height }
|
||||||
|
|
||||||
|
type xpubAddress struct {
|
||||||
|
addrDesc bchain.AddressDescriptor
|
||||||
|
balance *db.AddrBalance
|
||||||
|
txs uint32
|
||||||
|
maxHeight uint32
|
||||||
|
complete bool
|
||||||
|
txids xpubTxids
|
||||||
|
}
|
||||||
|
|
||||||
|
type xpubData struct {
|
||||||
|
gap int
|
||||||
|
accessed int64
|
||||||
|
basePath string
|
||||||
|
dataHeight uint32
|
||||||
|
dataHash string
|
||||||
|
txCountEstimate uint32
|
||||||
|
sentSat big.Int
|
||||||
|
balanceSat big.Int
|
||||||
|
addresses []xpubAddress
|
||||||
|
changeAddresses []xpubAddress
|
||||||
|
}
|
||||||
|
|
||||||
|
func (w *Worker) xpubGetAddressTxids(addrDesc bchain.AddressDescriptor, mempool bool, fromHeight, toHeight uint32, maxResults int) ([]xpubTxid, bool, error) {
|
||||||
|
var err error
|
||||||
|
complete := true
|
||||||
|
txs := make([]xpubTxid, 0, 4)
|
||||||
|
var callback db.GetTransactionsCallback
|
||||||
|
callback = func(txid string, height uint32, indexes []int32) error {
|
||||||
|
// take all txs in the last found block even if it exceeds maxResults
|
||||||
|
if len(txs) >= maxResults && txs[len(txs)-1].height != height {
|
||||||
|
complete = false
|
||||||
|
return &db.StopIteration{}
|
||||||
|
}
|
||||||
|
inputOutput := byte(0)
|
||||||
|
for _, index := range indexes {
|
||||||
|
if index < 0 {
|
||||||
|
inputOutput |= txInput
|
||||||
|
} else {
|
||||||
|
inputOutput |= txOutput
|
||||||
|
}
|
||||||
|
}
|
||||||
|
txs = append(txs, xpubTxid{txid, height, inputOutput})
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
if mempool {
|
||||||
|
uniqueTxs := make(map[string]int)
|
||||||
|
o, err := w.chain.GetMempoolTransactionsForAddrDesc(addrDesc)
|
||||||
|
if err != nil {
|
||||||
|
return nil, false, err
|
||||||
|
}
|
||||||
|
for _, m := range o {
|
||||||
|
if l, found := uniqueTxs[m.Txid]; !found {
|
||||||
|
l = len(txs)
|
||||||
|
callback(m.Txid, 0, []int32{m.Vout})
|
||||||
|
if len(txs) > l {
|
||||||
|
uniqueTxs[m.Txid] = l - 1
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if m.Vout < 0 {
|
||||||
|
txs[l].inputOutput |= txInput
|
||||||
|
} else {
|
||||||
|
txs[l].inputOutput |= txOutput
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
err = w.db.GetAddrDescTransactions(addrDesc, fromHeight, toHeight, callback)
|
||||||
|
if err != nil {
|
||||||
|
return nil, false, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return txs, complete, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (w *Worker) xpubCheckAndLoadTxids(ad *xpubAddress, filter *AddressFilter, maxHeight uint32, maxResults int) error {
|
||||||
|
// skip if not used
|
||||||
|
if ad.balance == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
// if completely loaded, check if there are not some new txs and load if necessary
|
||||||
|
if ad.complete {
|
||||||
|
if ad.balance.Txs != ad.txs {
|
||||||
|
newTxids, _, err := w.xpubGetAddressTxids(ad.addrDesc, false, ad.maxHeight+1, maxHeight, maxInt)
|
||||||
|
if err == nil {
|
||||||
|
ad.txids = append(newTxids, ad.txids...)
|
||||||
|
ad.maxHeight = maxHeight
|
||||||
|
ad.txs = uint32(len(ad.txids))
|
||||||
|
if ad.txs != ad.balance.Txs {
|
||||||
|
glog.Warning("xpubCheckAndLoadTxids inconsistency ", ad.addrDesc, ", ad.txs=", ad.txs, ", ad.balance.Txs=", ad.balance.Txs)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
// load all txids to get paging correctly
|
||||||
|
newTxids, complete, err := w.xpubGetAddressTxids(ad.addrDesc, false, 0, maxHeight, maxInt)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
ad.txids = newTxids
|
||||||
|
ad.complete = complete
|
||||||
|
ad.maxHeight = maxHeight
|
||||||
|
if complete {
|
||||||
|
ad.txs = uint32(len(ad.txids))
|
||||||
|
if ad.txs != ad.balance.Txs {
|
||||||
|
glog.Warning("xpubCheckAndLoadTxids inconsistency ", ad.addrDesc, ", ad.txs=", ad.txs, ", ad.balance.Txs=", ad.balance.Txs)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (w *Worker) xpubDerivedAddressBalance(data *xpubData, ad *xpubAddress) (bool, error) {
|
||||||
|
var err error
|
||||||
|
if ad.balance, err = w.db.GetAddrDescBalance(ad.addrDesc); err != nil {
|
||||||
|
return false, err
|
||||||
|
}
|
||||||
|
if ad.balance != nil {
|
||||||
|
data.txCountEstimate += ad.balance.Txs
|
||||||
|
data.sentSat.Add(&data.sentSat, &ad.balance.SentSat)
|
||||||
|
data.balanceSat.Add(&data.balanceSat, &ad.balance.BalanceSat)
|
||||||
|
return true, nil
|
||||||
|
}
|
||||||
|
return false, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (w *Worker) xpubScanAddresses(xpub string, data *xpubData, addresses []xpubAddress, gap int, change int, minDerivedIndex int, fork bool) (int, []xpubAddress, error) {
|
||||||
|
// rescan known addresses
|
||||||
|
lastUsed := 0
|
||||||
|
for i := range addresses {
|
||||||
|
ad := &addresses[i]
|
||||||
|
if fork {
|
||||||
|
// reset the cached data
|
||||||
|
ad.txs = 0
|
||||||
|
ad.maxHeight = 0
|
||||||
|
ad.complete = false
|
||||||
|
ad.txids = nil
|
||||||
|
}
|
||||||
|
used, err := w.xpubDerivedAddressBalance(data, ad)
|
||||||
|
if err != nil {
|
||||||
|
return 0, nil, err
|
||||||
|
}
|
||||||
|
if used {
|
||||||
|
lastUsed = i
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// derive new addresses as necessary
|
||||||
|
missing := len(addresses) - lastUsed
|
||||||
|
for missing < gap {
|
||||||
|
from := len(addresses)
|
||||||
|
to := from + gap - missing
|
||||||
|
if to < minDerivedIndex {
|
||||||
|
to = minDerivedIndex
|
||||||
|
}
|
||||||
|
descriptors, err := w.chainParser.DeriveAddressDescriptorsFromTo(xpub, uint32(change), uint32(from), uint32(to))
|
||||||
|
if err != nil {
|
||||||
|
return 0, nil, err
|
||||||
|
}
|
||||||
|
for i, a := range descriptors {
|
||||||
|
ad := xpubAddress{addrDesc: a}
|
||||||
|
used, err := w.xpubDerivedAddressBalance(data, &ad)
|
||||||
|
if err != nil {
|
||||||
|
return 0, nil, err
|
||||||
|
}
|
||||||
|
if used {
|
||||||
|
lastUsed = i + from
|
||||||
|
}
|
||||||
|
addresses = append(addresses, ad)
|
||||||
|
}
|
||||||
|
missing = len(addresses) - lastUsed
|
||||||
|
}
|
||||||
|
return lastUsed, addresses, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (w *Worker) tokenFromXpubAddress(data *xpubData, ad *xpubAddress, changeIndex int, index int, option AccountDetails) Token {
|
||||||
|
a, _, _ := w.chainParser.GetAddressesFromAddrDesc(ad.addrDesc)
|
||||||
|
var address string
|
||||||
|
if len(a) > 0 {
|
||||||
|
address = a[0]
|
||||||
|
}
|
||||||
|
var balance, totalReceived, totalSent *big.Int
|
||||||
|
var transfers int
|
||||||
|
if ad.balance != nil {
|
||||||
|
transfers = int(ad.balance.Txs)
|
||||||
|
if option >= AccountDetailsTokenBalances {
|
||||||
|
balance = &ad.balance.BalanceSat
|
||||||
|
totalSent = &ad.balance.SentSat
|
||||||
|
totalReceived = ad.balance.ReceivedSat()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return Token{
|
||||||
|
Type: XPUBAddressTokenType,
|
||||||
|
Name: address,
|
||||||
|
Decimals: w.chainParser.AmountDecimals(),
|
||||||
|
BalanceSat: (*Amount)(balance),
|
||||||
|
TotalReceivedSat: (*Amount)(totalReceived),
|
||||||
|
TotalSentSat: (*Amount)(totalSent),
|
||||||
|
Transfers: transfers,
|
||||||
|
Path: fmt.Sprintf("%s/%d/%d", data.basePath, changeIndex, index),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func evictXpubCacheItems() {
|
||||||
|
var oldestKey string
|
||||||
|
oldest := maxInt64
|
||||||
|
now := time.Now().Unix()
|
||||||
|
count := 0
|
||||||
|
for k, v := range cachedXpubs {
|
||||||
|
if v.accessed+xpubCacheExpirationSeconds < now {
|
||||||
|
delete(cachedXpubs, k)
|
||||||
|
count++
|
||||||
|
}
|
||||||
|
if v.accessed < oldest {
|
||||||
|
oldestKey = k
|
||||||
|
oldest = v.accessed
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if oldestKey != "" && oldest+xpubCacheExpirationSeconds >= now {
|
||||||
|
delete(cachedXpubs, oldestKey)
|
||||||
|
count++
|
||||||
|
}
|
||||||
|
glog.Info("Evicted ", count, " items from xpub cache, oldest item accessed at ", time.Unix(oldest, 0), ", cache size ", len(cachedXpubs))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (w *Worker) getXpubData(xpub string, page int, txsOnPage int, option AccountDetails, filter *AddressFilter, gap int) (*xpubData, uint32, error) {
|
||||||
|
if w.chainType != bchain.ChainBitcoinType || len(xpub) != xpubLen {
|
||||||
|
return nil, 0, ErrUnsupportedXpub
|
||||||
|
}
|
||||||
|
var (
|
||||||
|
err error
|
||||||
|
bestheight uint32
|
||||||
|
besthash string
|
||||||
|
)
|
||||||
|
if gap <= 0 {
|
||||||
|
gap = defaultAddressesGap
|
||||||
|
} else if gap > maxAddressesGap {
|
||||||
|
// limit the maximum gap to protect against unreasonably big values that could cause high load of the server
|
||||||
|
gap = maxAddressesGap
|
||||||
|
}
|
||||||
|
// gap is increased one as there must be gap of empty addresses before the derivation is stopped
|
||||||
|
gap++
|
||||||
|
var processedHash string
|
||||||
|
cachedXpubsMux.Lock()
|
||||||
|
data, found := cachedXpubs[xpub]
|
||||||
|
cachedXpubsMux.Unlock()
|
||||||
|
// to load all data for xpub may take some time, do it in a loop to process a possible new block
|
||||||
|
for {
|
||||||
|
bestheight, besthash, err = w.db.GetBestBlock()
|
||||||
|
if err != nil {
|
||||||
|
return nil, 0, errors.Annotatef(err, "GetBestBlock")
|
||||||
|
}
|
||||||
|
if besthash == processedHash {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
fork := false
|
||||||
|
if !found || data.gap != gap {
|
||||||
|
data = xpubData{gap: gap}
|
||||||
|
data.basePath, err = w.chainParser.DerivationBasePath(xpub)
|
||||||
|
if err != nil {
|
||||||
|
glog.Warning("DerivationBasePath error", err)
|
||||||
|
data.basePath = "unknown"
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
hash, err := w.db.GetBlockHash(data.dataHeight)
|
||||||
|
if err != nil {
|
||||||
|
return nil, 0, err
|
||||||
|
}
|
||||||
|
if hash != data.dataHash {
|
||||||
|
// in case of for reset all cached data
|
||||||
|
fork = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
processedHash = besthash
|
||||||
|
if data.dataHeight < bestheight || fork {
|
||||||
|
data.dataHeight = bestheight
|
||||||
|
data.dataHash = besthash
|
||||||
|
data.balanceSat = *new(big.Int)
|
||||||
|
data.sentSat = *new(big.Int)
|
||||||
|
data.txCountEstimate = 0
|
||||||
|
var lastUsedIndex int
|
||||||
|
lastUsedIndex, data.addresses, err = w.xpubScanAddresses(xpub, &data, data.addresses, gap, 0, 0, fork)
|
||||||
|
if err != nil {
|
||||||
|
return nil, 0, err
|
||||||
|
}
|
||||||
|
_, data.changeAddresses, err = w.xpubScanAddresses(xpub, &data, data.changeAddresses, gap, 1, lastUsedIndex, fork)
|
||||||
|
if err != nil {
|
||||||
|
return nil, 0, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if option >= AccountDetailsTxidHistory {
|
||||||
|
for _, da := range [][]xpubAddress{data.addresses, data.changeAddresses} {
|
||||||
|
for i := range da {
|
||||||
|
if err = w.xpubCheckAndLoadTxids(&da[i], filter, bestheight, (page+1)*txsOnPage); err != nil {
|
||||||
|
return nil, 0, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
data.accessed = time.Now().Unix()
|
||||||
|
cachedXpubsMux.Lock()
|
||||||
|
if len(cachedXpubs) >= xpubCacheSize {
|
||||||
|
evictXpubCacheItems()
|
||||||
|
}
|
||||||
|
cachedXpubs[xpub] = data
|
||||||
|
cachedXpubsMux.Unlock()
|
||||||
|
return &data, bestheight, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetXpubAddress computes address value and gets transactions for given address
|
||||||
|
func (w *Worker) GetXpubAddress(xpub string, page int, txsOnPage int, option AccountDetails, filter *AddressFilter, gap int) (*Address, error) {
|
||||||
|
start := time.Now()
|
||||||
|
page--
|
||||||
|
if page < 0 {
|
||||||
|
page = 0
|
||||||
|
}
|
||||||
|
type mempoolMap struct {
|
||||||
|
tx *Tx
|
||||||
|
inputOutput byte
|
||||||
|
}
|
||||||
|
var (
|
||||||
|
txc xpubTxids
|
||||||
|
txmMap map[string]*Tx
|
||||||
|
txCount int
|
||||||
|
txs []*Tx
|
||||||
|
txids []string
|
||||||
|
pg Paging
|
||||||
|
filtered bool
|
||||||
|
err error
|
||||||
|
uBalSat big.Int
|
||||||
|
)
|
||||||
|
data, bestheight, err := w.getXpubData(xpub, page, txsOnPage, option, filter, gap)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
// setup filtering of txids
|
||||||
|
var useTxids func(txid *xpubTxid, ad *xpubAddress) bool
|
||||||
|
if !(filter.FromHeight == 0 && filter.ToHeight == 0 && filter.Vout == AddressFilterVoutOff) {
|
||||||
|
toHeight := maxUint32
|
||||||
|
if filter.ToHeight != 0 {
|
||||||
|
toHeight = filter.ToHeight
|
||||||
|
}
|
||||||
|
useTxids = func(txid *xpubTxid, ad *xpubAddress) bool {
|
||||||
|
if txid.height < filter.FromHeight || txid.height > toHeight {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
if filter.Vout != AddressFilterVoutOff {
|
||||||
|
if filter.Vout == AddressFilterVoutInputs && txid.inputOutput&txInput == 0 ||
|
||||||
|
filter.Vout == AddressFilterVoutOutputs && txid.inputOutput&txOutput == 0 {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
filtered = true
|
||||||
|
}
|
||||||
|
// process mempool, only if ToHeight is not specified
|
||||||
|
if filter.ToHeight == 0 && !filter.OnlyConfirmed {
|
||||||
|
txmMap = make(map[string]*Tx)
|
||||||
|
for _, da := range [][]xpubAddress{data.addresses, data.changeAddresses} {
|
||||||
|
for i := range da {
|
||||||
|
ad := &da[i]
|
||||||
|
newTxids, _, err := w.xpubGetAddressTxids(ad.addrDesc, true, 0, 0, maxInt)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
for _, txid := range newTxids {
|
||||||
|
// the same tx can have multiple addresses from the same xpub, get it from backend it only once
|
||||||
|
tx, foundTx := txmMap[txid.txid]
|
||||||
|
if !foundTx {
|
||||||
|
tx, err = w.GetTransaction(txid.txid, false, false)
|
||||||
|
// mempool transaction may fail
|
||||||
|
if err != nil || tx == nil {
|
||||||
|
glog.Warning("GetTransaction in mempool: ", err)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
txmMap[txid.txid] = tx
|
||||||
|
}
|
||||||
|
// skip already confirmed txs, mempool may be out of sync
|
||||||
|
if tx.Confirmations == 0 {
|
||||||
|
uBalSat.Add(&uBalSat, tx.getAddrVoutValue(ad.addrDesc))
|
||||||
|
uBalSat.Sub(&uBalSat, tx.getAddrVinValue(ad.addrDesc))
|
||||||
|
if page == 0 && !foundTx && (useTxids == nil || useTxids(&txid, ad)) {
|
||||||
|
if option == AccountDetailsTxidHistory {
|
||||||
|
txids = append(txids, tx.Txid)
|
||||||
|
} else if option >= AccountDetailsTxHistoryLight {
|
||||||
|
txs = append(txs, tx)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if option >= AccountDetailsTxidHistory {
|
||||||
|
txcMap := make(map[string]bool)
|
||||||
|
txc = make(xpubTxids, 0, 32)
|
||||||
|
for _, da := range [][]xpubAddress{data.addresses, data.changeAddresses} {
|
||||||
|
for i := range da {
|
||||||
|
ad := &da[i]
|
||||||
|
for _, txid := range ad.txids {
|
||||||
|
added, foundTx := txcMap[txid.txid]
|
||||||
|
// count txs regardless of filter but only once
|
||||||
|
if !foundTx {
|
||||||
|
txCount++
|
||||||
|
}
|
||||||
|
// add tx only once
|
||||||
|
if !added {
|
||||||
|
add := useTxids == nil || useTxids(&txid, ad)
|
||||||
|
txcMap[txid.txid] = add
|
||||||
|
if add {
|
||||||
|
txc = append(txc, txid)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
sort.Stable(txc)
|
||||||
|
txCount = len(txcMap)
|
||||||
|
totalResults := txCount
|
||||||
|
if filtered {
|
||||||
|
totalResults = -1
|
||||||
|
}
|
||||||
|
var from, to int
|
||||||
|
pg, from, to, page = computePaging(len(txc), page, txsOnPage)
|
||||||
|
if len(txc) >= txsOnPage {
|
||||||
|
if totalResults < 0 {
|
||||||
|
pg.TotalPages = -1
|
||||||
|
} else {
|
||||||
|
pg, _, _, _ = computePaging(totalResults, page, txsOnPage)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// get confirmed transactions
|
||||||
|
for i := from; i < to; i++ {
|
||||||
|
xpubTxid := &txc[i]
|
||||||
|
if option == AccountDetailsTxidHistory {
|
||||||
|
txids = append(txids, xpubTxid.txid)
|
||||||
|
} else {
|
||||||
|
tx, err := w.txFromTxid(xpubTxid.txid, bestheight, option)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
txs = append(txs, tx)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
txCount = int(data.txCountEstimate)
|
||||||
|
}
|
||||||
|
totalTokens := 0
|
||||||
|
var tokens []Token
|
||||||
|
var xpubAddresses map[string]struct{}
|
||||||
|
if option > AccountDetailsBasic {
|
||||||
|
tokens = make([]Token, 0, 4)
|
||||||
|
xpubAddresses = make(map[string]struct{})
|
||||||
|
}
|
||||||
|
for ci, da := range [][]xpubAddress{data.addresses, data.changeAddresses} {
|
||||||
|
for i := range da {
|
||||||
|
ad := &da[i]
|
||||||
|
if ad.balance != nil {
|
||||||
|
totalTokens++
|
||||||
|
}
|
||||||
|
if option > AccountDetailsBasic {
|
||||||
|
token := w.tokenFromXpubAddress(data, ad, ci, i, option)
|
||||||
|
if filter.TokenLevel == TokenDetailDiscovered ||
|
||||||
|
filter.TokenLevel == TokenDetailUsed && ad.balance != nil ||
|
||||||
|
filter.TokenLevel == TokenDetailNonzeroBalance && ad.balance != nil && !IsZeroBigInt(&ad.balance.BalanceSat) {
|
||||||
|
tokens = append(tokens, token)
|
||||||
|
}
|
||||||
|
xpubAddresses[token.Name] = struct{}{}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
var totalReceived big.Int
|
||||||
|
totalReceived.Add(&data.balanceSat, &data.sentSat)
|
||||||
|
addr := Address{
|
||||||
|
Paging: pg,
|
||||||
|
AddrStr: xpub,
|
||||||
|
BalanceSat: (*Amount)(&data.balanceSat),
|
||||||
|
TotalReceivedSat: (*Amount)(&totalReceived),
|
||||||
|
TotalSentSat: (*Amount)(&data.sentSat),
|
||||||
|
Txs: txCount,
|
||||||
|
UnconfirmedBalanceSat: (*Amount)(&uBalSat),
|
||||||
|
UnconfirmedTxs: len(txmMap),
|
||||||
|
Transactions: txs,
|
||||||
|
Txids: txids,
|
||||||
|
TotalTokens: totalTokens,
|
||||||
|
Tokens: tokens,
|
||||||
|
XPubAddresses: xpubAddresses,
|
||||||
|
}
|
||||||
|
glog.Info("GetXpubAddress ", xpub[:16], ", ", len(data.addresses)+len(data.changeAddresses), " derived addresses, ", txCount, " confirmed txs, finished in ", time.Since(start))
|
||||||
|
return &addr, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetXpubUtxo returns unspent outputs for given xpub
|
||||||
|
func (w *Worker) GetXpubUtxo(xpub string, onlyConfirmed bool, gap int) (Utxos, error) {
|
||||||
|
start := time.Now()
|
||||||
|
data, _, err := w.getXpubData(xpub, 0, 1, AccountDetailsBasic, &AddressFilter{
|
||||||
|
Vout: AddressFilterVoutOff,
|
||||||
|
OnlyConfirmed: onlyConfirmed,
|
||||||
|
}, gap)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
r := make(Utxos, 0, 8)
|
||||||
|
for ci, da := range [][]xpubAddress{data.addresses, data.changeAddresses} {
|
||||||
|
for i := range da {
|
||||||
|
ad := &da[i]
|
||||||
|
onlyMempool := false
|
||||||
|
if ad.balance == nil {
|
||||||
|
if onlyConfirmed {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
onlyMempool = true
|
||||||
|
}
|
||||||
|
utxos, err := w.getAddrDescUtxo(ad.addrDesc, ad.balance, onlyConfirmed, onlyMempool)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if len(utxos) > 0 {
|
||||||
|
t := w.tokenFromXpubAddress(data, ad, ci, i, AccountDetailsTokens)
|
||||||
|
for j := range utxos {
|
||||||
|
a := &utxos[j]
|
||||||
|
a.Address = t.Name
|
||||||
|
a.Path = t.Path
|
||||||
|
}
|
||||||
|
r = append(r, utxos...)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
sort.Stable(r)
|
||||||
|
glog.Info("GetXpubUtxo ", xpub[:16], ", ", len(r), " utxos, finished in ", time.Since(start))
|
||||||
|
return r, nil
|
||||||
|
}
|
||||||
@ -268,6 +268,21 @@ func (p *BaseParser) UnpackTx(buf []byte) (*Tx, uint32, error) {
|
|||||||
return &tx, pt.Height, nil
|
return &tx, pt.Height, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// DerivationBasePath is unsupported
|
||||||
|
func (p *BaseParser) DerivationBasePath(xpub string) (string, error) {
|
||||||
|
return "", errors.New("Not supported")
|
||||||
|
}
|
||||||
|
|
||||||
|
// DeriveAddressDescriptors is unsupported
|
||||||
|
func (p *BaseParser) DeriveAddressDescriptors(xpub string, change uint32, indexes []uint32) ([]AddressDescriptor, error) {
|
||||||
|
return nil, errors.New("Not supported")
|
||||||
|
}
|
||||||
|
|
||||||
|
// DeriveAddressDescriptorsFromTo is unsupported
|
||||||
|
func (p *BaseParser) DeriveAddressDescriptorsFromTo(xpub string, change uint32, fromIndex uint32, toIndex uint32) ([]AddressDescriptor, error) {
|
||||||
|
return nil, errors.New("Not supported")
|
||||||
|
}
|
||||||
|
|
||||||
// EthereumTypeGetErc20FromTx is unsupported
|
// EthereumTypeGetErc20FromTx is unsupported
|
||||||
func (p *BaseParser) EthereumTypeGetErc20FromTx(tx *Tx) ([]Erc20Transfer, error) {
|
func (p *BaseParser) EthereumTypeGetErc20FromTx(tx *Tx) ([]Erc20Transfer, error) {
|
||||||
return nil, errors.New("Not supported")
|
return nil, errors.New("Not supported")
|
||||||
|
|||||||
@ -6,12 +6,15 @@ import (
|
|||||||
"encoding/binary"
|
"encoding/binary"
|
||||||
"encoding/hex"
|
"encoding/hex"
|
||||||
"math/big"
|
"math/big"
|
||||||
|
"strconv"
|
||||||
|
|
||||||
vlq "github.com/bsm/go-vlq"
|
vlq "github.com/bsm/go-vlq"
|
||||||
|
"github.com/juju/errors"
|
||||||
"github.com/martinboehm/btcd/blockchain"
|
"github.com/martinboehm/btcd/blockchain"
|
||||||
"github.com/martinboehm/btcd/wire"
|
"github.com/martinboehm/btcd/wire"
|
||||||
"github.com/martinboehm/btcutil"
|
"github.com/martinboehm/btcutil"
|
||||||
"github.com/martinboehm/btcutil/chaincfg"
|
"github.com/martinboehm/btcutil/chaincfg"
|
||||||
|
"github.com/martinboehm/btcutil/hdkeychain"
|
||||||
"github.com/martinboehm/btcutil/txscript"
|
"github.com/martinboehm/btcutil/txscript"
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -23,6 +26,10 @@ type BitcoinParser struct {
|
|||||||
*bchain.BaseParser
|
*bchain.BaseParser
|
||||||
Params *chaincfg.Params
|
Params *chaincfg.Params
|
||||||
OutputScriptToAddressesFunc OutputScriptToAddressesFunc
|
OutputScriptToAddressesFunc OutputScriptToAddressesFunc
|
||||||
|
XPubMagic uint32
|
||||||
|
XPubMagicSegwitP2sh uint32
|
||||||
|
XPubMagicSegwitNative uint32
|
||||||
|
Slip44 uint32
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewBitcoinParser returns new BitcoinParser instance
|
// NewBitcoinParser returns new BitcoinParser instance
|
||||||
@ -32,7 +39,11 @@ func NewBitcoinParser(params *chaincfg.Params, c *Configuration) *BitcoinParser
|
|||||||
BlockAddressesToKeep: c.BlockAddressesToKeep,
|
BlockAddressesToKeep: c.BlockAddressesToKeep,
|
||||||
AmountDecimalPoint: 8,
|
AmountDecimalPoint: 8,
|
||||||
},
|
},
|
||||||
Params: params,
|
Params: params,
|
||||||
|
XPubMagic: c.XPubMagic,
|
||||||
|
XPubMagicSegwitP2sh: c.XPubMagicSegwitP2sh,
|
||||||
|
XPubMagicSegwitNative: c.XPubMagicSegwitNative,
|
||||||
|
Slip44: c.Slip44,
|
||||||
}
|
}
|
||||||
p.OutputScriptToAddressesFunc = p.outputScriptToAddresses
|
p.OutputScriptToAddressesFunc = p.outputScriptToAddresses
|
||||||
return p
|
return p
|
||||||
@ -266,3 +277,104 @@ func (p *BitcoinParser) UnpackTx(buf []byte) (*bchain.Tx, uint32, error) {
|
|||||||
|
|
||||||
return tx, height, nil
|
return tx, height, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (p *BitcoinParser) addrDescFromExtKey(extKey *hdkeychain.ExtendedKey) (bchain.AddressDescriptor, error) {
|
||||||
|
var a btcutil.Address
|
||||||
|
var err error
|
||||||
|
if extKey.Version() == p.XPubMagicSegwitP2sh {
|
||||||
|
// redeemScript <witness version: OP_0><len pubKeyHash: 20><20-byte-pubKeyHash>
|
||||||
|
pubKeyHash := btcutil.Hash160(extKey.PubKeyBytes())
|
||||||
|
redeemScript := make([]byte, len(pubKeyHash)+2)
|
||||||
|
redeemScript[0] = 0
|
||||||
|
redeemScript[1] = byte(len(pubKeyHash))
|
||||||
|
copy(redeemScript[2:], pubKeyHash)
|
||||||
|
hash := btcutil.Hash160(redeemScript)
|
||||||
|
a, err = btcutil.NewAddressScriptHashFromHash(hash, p.Params)
|
||||||
|
} else if extKey.Version() == p.XPubMagicSegwitNative {
|
||||||
|
a, err = btcutil.NewAddressWitnessPubKeyHash(btcutil.Hash160(extKey.PubKeyBytes()), p.Params)
|
||||||
|
} else {
|
||||||
|
// default to P2PKH address
|
||||||
|
a, err = extKey.Address(p.Params)
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return txscript.PayToAddrScript(a)
|
||||||
|
}
|
||||||
|
|
||||||
|
// DeriveAddressDescriptors derives address descriptors from given xpub for listed indexes
|
||||||
|
func (p *BitcoinParser) DeriveAddressDescriptors(xpub string, change uint32, indexes []uint32) ([]bchain.AddressDescriptor, error) {
|
||||||
|
extKey, err := hdkeychain.NewKeyFromString(xpub)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
changeExtKey, err := extKey.Child(change)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
ad := make([]bchain.AddressDescriptor, len(indexes))
|
||||||
|
for i, index := range indexes {
|
||||||
|
indexExtKey, err := changeExtKey.Child(index)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
ad[i], err = p.addrDescFromExtKey(indexExtKey)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return ad, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// DeriveAddressDescriptorsFromTo derives address descriptors from given xpub for addresses in index range
|
||||||
|
func (p *BitcoinParser) DeriveAddressDescriptorsFromTo(xpub string, change uint32, fromIndex uint32, toIndex uint32) ([]bchain.AddressDescriptor, error) {
|
||||||
|
if toIndex <= fromIndex {
|
||||||
|
return nil, errors.New("toIndex<=fromIndex")
|
||||||
|
}
|
||||||
|
extKey, err := hdkeychain.NewKeyFromString(xpub)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
changeExtKey, err := extKey.Child(change)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
ad := make([]bchain.AddressDescriptor, toIndex-fromIndex)
|
||||||
|
for index := fromIndex; index < toIndex; index++ {
|
||||||
|
indexExtKey, err := changeExtKey.Child(index)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
ad[index-fromIndex], err = p.addrDescFromExtKey(indexExtKey)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return ad, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// DerivationBasePath returns base path of xpub
|
||||||
|
func (p *BitcoinParser) DerivationBasePath(xpub string) (string, error) {
|
||||||
|
extKey, err := hdkeychain.NewKeyFromString(xpub)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
var c, bip string
|
||||||
|
cn := extKey.ChildNum()
|
||||||
|
if cn >= 0x80000000 {
|
||||||
|
cn -= 0x80000000
|
||||||
|
c = "'"
|
||||||
|
}
|
||||||
|
c = strconv.Itoa(int(cn)) + c
|
||||||
|
if extKey.Depth() != 3 {
|
||||||
|
return "unknown/" + c, nil
|
||||||
|
}
|
||||||
|
if extKey.Version() == p.XPubMagicSegwitP2sh {
|
||||||
|
bip = "49"
|
||||||
|
} else if extKey.Version() == p.XPubMagicSegwitNative {
|
||||||
|
bip = "84"
|
||||||
|
} else {
|
||||||
|
bip = "44"
|
||||||
|
}
|
||||||
|
return "m/" + bip + "'/" + strconv.Itoa(int(p.Slip44)) + "'/" + c, nil
|
||||||
|
}
|
||||||
|
|||||||
@ -19,7 +19,7 @@ func TestMain(m *testing.M) {
|
|||||||
os.Exit(c)
|
os.Exit(c)
|
||||||
}
|
}
|
||||||
|
|
||||||
func Test_GetAddrDescFromAddress(t *testing.T) {
|
func TestGetAddrDescFromAddress(t *testing.T) {
|
||||||
type args struct {
|
type args struct {
|
||||||
address string
|
address string
|
||||||
}
|
}
|
||||||
@ -77,7 +77,7 @@ func Test_GetAddrDescFromAddress(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func Test_GetAddrDescFromVout(t *testing.T) {
|
func TestGetAddrDescFromVout(t *testing.T) {
|
||||||
type args struct {
|
type args struct {
|
||||||
vout bchain.Vout
|
vout bchain.Vout
|
||||||
}
|
}
|
||||||
@ -141,7 +141,7 @@ func Test_GetAddrDescFromVout(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func Test_GetAddressesFromAddrDesc(t *testing.T) {
|
func TestGetAddressesFromAddrDesc(t *testing.T) {
|
||||||
type args struct {
|
type args struct {
|
||||||
script string
|
script string
|
||||||
}
|
}
|
||||||
@ -316,7 +316,7 @@ func init() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func Test_PackTx(t *testing.T) {
|
func TestPackTx(t *testing.T) {
|
||||||
type args struct {
|
type args struct {
|
||||||
tx bchain.Tx
|
tx bchain.Tx
|
||||||
height uint32
|
height uint32
|
||||||
@ -367,7 +367,7 @@ func Test_PackTx(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func Test_UnpackTx(t *testing.T) {
|
func TestUnpackTx(t *testing.T) {
|
||||||
type args struct {
|
type args struct {
|
||||||
packedTx string
|
packedTx string
|
||||||
parser *BitcoinParser
|
parser *BitcoinParser
|
||||||
@ -417,3 +417,245 @@ func Test_UnpackTx(t *testing.T) {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestDeriveAddressDescriptors(t *testing.T) {
|
||||||
|
btcMainParser := NewBitcoinParser(GetChainParams("main"), &Configuration{XPubMagic: 76067358, XPubMagicSegwitP2sh: 77429938, XPubMagicSegwitNative: 78792518})
|
||||||
|
type args struct {
|
||||||
|
xpub string
|
||||||
|
change uint32
|
||||||
|
indexes []uint32
|
||||||
|
parser *BitcoinParser
|
||||||
|
}
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
args args
|
||||||
|
want []string
|
||||||
|
wantErr bool
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "m/44'/0'/0'",
|
||||||
|
args: args{
|
||||||
|
xpub: "xpub6BosfCnifzxcFwrSzQiqu2DBVTshkCXacvNsWGYJVVhhawA7d4R5WSWGFNbi8Aw6ZRc1brxMyWMzG3DSSSSoekkudhUd9yLb6qx39T9nMdj",
|
||||||
|
change: 0,
|
||||||
|
indexes: []uint32{0, 1234},
|
||||||
|
parser: btcMainParser,
|
||||||
|
},
|
||||||
|
want: []string{"1LqBGSKuX5yYUonjxT5qGfpUsXKYYWeabA", "1P9w11dXAmG3QBjKLAvCsek8izs1iR2iFi"},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "m/49'/0'/0'",
|
||||||
|
args: args{
|
||||||
|
xpub: "ypub6Ww3ibxVfGzLrAH1PNcjyAWenMTbbAosGNB6VvmSEgytSER9azLDWCxoJwW7Ke7icmizBMXrzBx9979FfaHxHcrArf3zbeJJJUZPf663zsP",
|
||||||
|
change: 0,
|
||||||
|
indexes: []uint32{0, 1234},
|
||||||
|
parser: btcMainParser,
|
||||||
|
},
|
||||||
|
want: []string{"37VucYSaXLCAsxYyAPfbSi9eh4iEcbShgf", "367meFzJ9KqDLm9PX6U8Z8RdmkSNBuxX8T"},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "m/84'/0'/0'",
|
||||||
|
args: args{
|
||||||
|
xpub: "zpub6rFR7y4Q2AijBEqTUquhVz398htDFrtymD9xYYfG1m4wAcvPhXNfE3EfH1r1ADqtfSdVCToUG868RvUUkgDKf31mGDtKsAYz2oz2AGutZYs",
|
||||||
|
change: 0,
|
||||||
|
indexes: []uint32{0, 1234},
|
||||||
|
parser: btcMainParser,
|
||||||
|
},
|
||||||
|
want: []string{"bc1qcr8te4kr609gcawutmrza0j4xv80jy8z306fyu", "bc1q4nm6g46ujzyjaeusralaz2nfv2rf04jjfyamkw"},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
for _, tt := range tests {
|
||||||
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
got, err := tt.args.parser.DeriveAddressDescriptors(tt.args.xpub, tt.args.change, tt.args.indexes)
|
||||||
|
if (err != nil) != tt.wantErr {
|
||||||
|
t.Errorf("DeriveAddressDescriptorsFromTo() error = %v, wantErr %v", err, tt.wantErr)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
gotAddresses := make([]string, len(got))
|
||||||
|
for i, ad := range got {
|
||||||
|
aa, _, err := tt.args.parser.GetAddressesFromAddrDesc(ad)
|
||||||
|
if err != nil || len(aa) != 1 {
|
||||||
|
t.Errorf("DeriveAddressDescriptorsFromTo() got incorrect address descriptor %v, error %v", ad, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
gotAddresses[i] = aa[0]
|
||||||
|
}
|
||||||
|
if !reflect.DeepEqual(gotAddresses, tt.want) {
|
||||||
|
t.Errorf("DeriveAddressDescriptorsFromTo() = %v, want %v", gotAddresses, tt.want)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestDeriveAddressDescriptorsFromTo(t *testing.T) {
|
||||||
|
btcMainParser := NewBitcoinParser(GetChainParams("main"), &Configuration{XPubMagic: 76067358, XPubMagicSegwitP2sh: 77429938, XPubMagicSegwitNative: 78792518})
|
||||||
|
btcTestnetsParser := NewBitcoinParser(GetChainParams("test"), &Configuration{XPubMagic: 70617039, XPubMagicSegwitP2sh: 71979618, XPubMagicSegwitNative: 73342198})
|
||||||
|
type args struct {
|
||||||
|
xpub string
|
||||||
|
change uint32
|
||||||
|
fromIndex uint32
|
||||||
|
toIndex uint32
|
||||||
|
parser *BitcoinParser
|
||||||
|
}
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
args args
|
||||||
|
want []string
|
||||||
|
wantErr bool
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "m/44'/0'/0'",
|
||||||
|
args: args{
|
||||||
|
xpub: "xpub6BosfCnifzxcFwrSzQiqu2DBVTshkCXacvNsWGYJVVhhawA7d4R5WSWGFNbi8Aw6ZRc1brxMyWMzG3DSSSSoekkudhUd9yLb6qx39T9nMdj",
|
||||||
|
change: 0,
|
||||||
|
fromIndex: 0,
|
||||||
|
toIndex: 1,
|
||||||
|
parser: btcMainParser,
|
||||||
|
},
|
||||||
|
want: []string{"1LqBGSKuX5yYUonjxT5qGfpUsXKYYWeabA"},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "m/49'/0'/0'",
|
||||||
|
args: args{
|
||||||
|
xpub: "ypub6Ww3ibxVfGzLrAH1PNcjyAWenMTbbAosGNB6VvmSEgytSER9azLDWCxoJwW7Ke7icmizBMXrzBx9979FfaHxHcrArf3zbeJJJUZPf663zsP",
|
||||||
|
change: 0,
|
||||||
|
fromIndex: 0,
|
||||||
|
toIndex: 1,
|
||||||
|
parser: btcMainParser,
|
||||||
|
},
|
||||||
|
want: []string{"37VucYSaXLCAsxYyAPfbSi9eh4iEcbShgf"},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "m/84'/0'/0'",
|
||||||
|
args: args{
|
||||||
|
xpub: "zpub6rFR7y4Q2AijBEqTUquhVz398htDFrtymD9xYYfG1m4wAcvPhXNfE3EfH1r1ADqtfSdVCToUG868RvUUkgDKf31mGDtKsAYz2oz2AGutZYs",
|
||||||
|
change: 0,
|
||||||
|
fromIndex: 0,
|
||||||
|
toIndex: 1,
|
||||||
|
parser: btcMainParser,
|
||||||
|
},
|
||||||
|
want: []string{"bc1qcr8te4kr609gcawutmrza0j4xv80jy8z306fyu"},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "m/49'/1'/0'",
|
||||||
|
args: args{
|
||||||
|
xpub: "upub5DR1Mg5nykixzYjFXWW5GghAU7dDqoPVJ2jrqFbL8sJ7Hs7jn69MP7KBnnmxn88GeZtnH8PRKV9w5MMSFX8AdEAoXY8Qd8BJPoXtpMeHMxJ",
|
||||||
|
change: 0,
|
||||||
|
fromIndex: 0,
|
||||||
|
toIndex: 10,
|
||||||
|
parser: btcTestnetsParser,
|
||||||
|
},
|
||||||
|
want: []string{"2N4Q5FhU2497BryFfUgbqkAJE87aKHUhXMp", "2Mt7P2BAfE922zmfXrdcYTLyR7GUvbwSEns", "2N6aUMgQk8y1zvoq6FeWFyotyj75WY9BGsu", "2NA7tbZWM9BcRwBuebKSQe2xbhhF1paJwBM", "2N8RZMzvrUUnpLmvACX9ysmJ2MX3GK5jcQM", "2MvUUSiQZDSqyeSdofKX9KrSCio1nANPDTe", "2NBXaWu1HazjoUVgrXgcKNoBLhtkkD9Gmet", "2N791Ttf89tMVw2maj86E1Y3VgxD9Mc7PU7", "2NCJmwEq8GJm8t8GWWyBXAfpw7F2qZEVP5Y", "2NEgW71hWKer2XCSA8ZCC2VnWpB77L6bk68"},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
for _, tt := range tests {
|
||||||
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
got, err := tt.args.parser.DeriveAddressDescriptorsFromTo(tt.args.xpub, tt.args.change, tt.args.fromIndex, tt.args.toIndex)
|
||||||
|
if (err != nil) != tt.wantErr {
|
||||||
|
t.Errorf("DeriveAddressDescriptorsFromTo() error = %v, wantErr %v", err, tt.wantErr)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
gotAddresses := make([]string, len(got))
|
||||||
|
for i, ad := range got {
|
||||||
|
aa, _, err := tt.args.parser.GetAddressesFromAddrDesc(ad)
|
||||||
|
if err != nil || len(aa) != 1 {
|
||||||
|
t.Errorf("DeriveAddressDescriptorsFromTo() got incorrect address descriptor %v, error %v", ad, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
gotAddresses[i] = aa[0]
|
||||||
|
}
|
||||||
|
if !reflect.DeepEqual(gotAddresses, tt.want) {
|
||||||
|
t.Errorf("DeriveAddressDescriptorsFromTo() = %v, want %v", gotAddresses, tt.want)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func BenchmarkDeriveAddressDescriptorsFromToXpub(b *testing.B) {
|
||||||
|
btcMainParser := NewBitcoinParser(GetChainParams("main"), &Configuration{XPubMagic: 76067358, XPubMagicSegwitP2sh: 77429938, XPubMagicSegwitNative: 78792518})
|
||||||
|
for i := 0; i < b.N; i++ {
|
||||||
|
btcMainParser.DeriveAddressDescriptorsFromTo("xpub6BosfCnifzxcFwrSzQiqu2DBVTshkCXacvNsWGYJVVhhawA7d4R5WSWGFNbi8Aw6ZRc1brxMyWMzG3DSSSSoekkudhUd9yLb6qx39T9nMdj", 1, 0, 100)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func BenchmarkDeriveAddressDescriptorsFromToYpub(b *testing.B) {
|
||||||
|
btcMainParser := NewBitcoinParser(GetChainParams("main"), &Configuration{XPubMagic: 76067358, XPubMagicSegwitP2sh: 77429938, XPubMagicSegwitNative: 78792518})
|
||||||
|
for i := 0; i < b.N; i++ {
|
||||||
|
btcMainParser.DeriveAddressDescriptorsFromTo("ypub6Ww3ibxVfGzLrAH1PNcjyAWenMTbbAosGNB6VvmSEgytSER9azLDWCxoJwW7Ke7icmizBMXrzBx9979FfaHxHcrArf3zbeJJJUZPf663zsP", 1, 0, 100)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func BenchmarkDeriveAddressDescriptorsFromToZpub(b *testing.B) {
|
||||||
|
btcMainParser := NewBitcoinParser(GetChainParams("main"), &Configuration{XPubMagic: 76067358, XPubMagicSegwitP2sh: 77429938, XPubMagicSegwitNative: 78792518})
|
||||||
|
for i := 0; i < b.N; i++ {
|
||||||
|
btcMainParser.DeriveAddressDescriptorsFromTo("zpub6rFR7y4Q2AijBEqTUquhVz398htDFrtymD9xYYfG1m4wAcvPhXNfE3EfH1r1ADqtfSdVCToUG868RvUUkgDKf31mGDtKsAYz2oz2AGutZYs", 1, 0, 100)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestBitcoinParser_DerivationBasePath(t *testing.T) {
|
||||||
|
btcMainParser := NewBitcoinParser(GetChainParams("main"), &Configuration{XPubMagic: 76067358, XPubMagicSegwitP2sh: 77429938, XPubMagicSegwitNative: 78792518, Slip44: 0})
|
||||||
|
btcTestnetsParser := NewBitcoinParser(GetChainParams("test"), &Configuration{XPubMagic: 70617039, XPubMagicSegwitP2sh: 71979618, XPubMagicSegwitNative: 73342198, Slip44: 1})
|
||||||
|
zecMainParser := NewBitcoinParser(GetChainParams("main"), &Configuration{XPubMagic: 76067358, Slip44: 133})
|
||||||
|
type args struct {
|
||||||
|
xpub string
|
||||||
|
parser *BitcoinParser
|
||||||
|
}
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
args args
|
||||||
|
want string
|
||||||
|
wantErr bool
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "m/84'/0'/0'",
|
||||||
|
args: args{
|
||||||
|
xpub: "zpub6rFR7y4Q2AijBEqTUquhVz398htDFrtymD9xYYfG1m4wAcvPhXNfE3EfH1r1ADqtfSdVCToUG868RvUUkgDKf31mGDtKsAYz2oz2AGutZYs",
|
||||||
|
parser: btcMainParser,
|
||||||
|
},
|
||||||
|
want: "m/84'/0'/0'",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "m/49'/0'/55 - not hardened account",
|
||||||
|
args: args{
|
||||||
|
xpub: "ypub6XKbB5DJRAbW4TRJLp4uXQXG3ob5BtByXsNZFBjq9qcbzrczjVXfCz5cEo1SFDexmeWRnbCMDaRgaW4m9d2nBaa8FvUQCu3n9G1UBR8WhbT",
|
||||||
|
parser: btcMainParser,
|
||||||
|
},
|
||||||
|
want: "m/49'/0'/55",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "m/49'/0' - incomplete path, without account",
|
||||||
|
args: args{
|
||||||
|
xpub: "ypub6UzM8PUqxcSoqC9gumfoiFhE8Qt84HbGpCD4eVJfJAojXTVtBxeddvTWJGJhGoaVBNJLmEgMdLXHgaLVJa4xEvk2tcokkdZhFdkxMLUE9sB",
|
||||||
|
parser: btcMainParser,
|
||||||
|
},
|
||||||
|
want: "unknown/0'",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "m/49'/1'/0'",
|
||||||
|
args: args{
|
||||||
|
xpub: "upub5DR1Mg5nykixzYjFXWW5GghAU7dDqoPVJ2jrqFbL8sJ7Hs7jn69MP7KBnnmxn88GeZtnH8PRKV9w5MMSFX8AdEAoXY8Qd8BJPoXtpMeHMxJ",
|
||||||
|
parser: btcTestnetsParser,
|
||||||
|
},
|
||||||
|
want: "m/49'/1'/0'",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "m/44'/133'/12'",
|
||||||
|
args: args{
|
||||||
|
xpub: "xpub6CQdEahwhKRTLYpP6cyb7ZaGb3r4tVdyPX6dC1PfrNuByrCkWDgUkmpD28UdV9QccKgY1ZiAbGv1Fakcg2LxdFVSTNKHcjdRjqhjPK8Trkb",
|
||||||
|
parser: zecMainParser,
|
||||||
|
},
|
||||||
|
want: "m/44'/133'/12'",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
for _, tt := range tests {
|
||||||
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
got, err := tt.args.parser.DerivationBasePath(tt.args.xpub)
|
||||||
|
if (err != nil) != tt.wantErr {
|
||||||
|
t.Errorf("BitcoinParser.DerivationBasePath() error = %v, wantErr %v", err, tt.wantErr)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if got != tt.want {
|
||||||
|
t.Errorf("BitcoinParser.DerivationBasePath() = %v, want %v", got, tt.want)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@ -51,6 +51,10 @@ type Configuration struct {
|
|||||||
AddressFormat string `json:"address_format"`
|
AddressFormat string `json:"address_format"`
|
||||||
SupportsEstimateFee bool `json:"supports_estimate_fee"`
|
SupportsEstimateFee bool `json:"supports_estimate_fee"`
|
||||||
SupportsEstimateSmartFee bool `json:"supports_estimate_smart_fee"`
|
SupportsEstimateSmartFee bool `json:"supports_estimate_smart_fee"`
|
||||||
|
XPubMagic uint32 `json:"xpub_magic,omitempty"`
|
||||||
|
XPubMagicSegwitP2sh uint32 `json:"xpub_magic_segwit_p2sh,omitempty"`
|
||||||
|
XPubMagicSegwitNative uint32 `json:"xpub_magic_segwit_native,omitempty"`
|
||||||
|
Slip44 uint32 `json:"slip44,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewBitcoinRPC returns new BitcoinRPC instance.
|
// NewBitcoinRPC returns new BitcoinRPC instance.
|
||||||
|
|||||||
@ -263,6 +263,10 @@ type BlockChainParser interface {
|
|||||||
PackBlockHash(hash string) ([]byte, error)
|
PackBlockHash(hash string) ([]byte, error)
|
||||||
UnpackBlockHash(buf []byte) (string, error)
|
UnpackBlockHash(buf []byte) (string, error)
|
||||||
ParseBlock(b []byte) (*Block, error)
|
ParseBlock(b []byte) (*Block, error)
|
||||||
|
// xpub
|
||||||
|
DerivationBasePath(xpub string) (string, error)
|
||||||
|
DeriveAddressDescriptors(xpub string, change uint32, indexes []uint32) ([]AddressDescriptor, error)
|
||||||
|
DeriveAddressDescriptorsFromTo(xpub string, change uint32, fromIndex uint32, toIndex uint32) ([]AddressDescriptor, error)
|
||||||
// EthereumType specific
|
// EthereumType specific
|
||||||
EthereumTypeGetErc20FromTx(tx *Tx) ([]Erc20Transfer, error)
|
EthereumTypeGetErc20FromTx(tx *Tx) ([]Erc20Transfer, error)
|
||||||
}
|
}
|
||||||
|
|||||||
@ -3,9 +3,8 @@
|
|||||||
{{- if .Blockbook.BlockChain.AdditionalParams}}
|
{{- if .Blockbook.BlockChain.AdditionalParams}}
|
||||||
{{- range $name, $value := .Blockbook.BlockChain.AdditionalParams}}
|
{{- range $name, $value := .Blockbook.BlockChain.AdditionalParams}}
|
||||||
"{{$name}}": {{jsonToString $value}},
|
"{{$name}}": {{jsonToString $value}},
|
||||||
{{- end}}
|
{{- end -}}
|
||||||
{{end}}
|
{{end}}
|
||||||
|
|
||||||
"coin_name": "{{.Coin.Name}}",
|
"coin_name": "{{.Coin.Name}}",
|
||||||
"coin_shortcut": "{{.Coin.Shortcut}}",
|
"coin_shortcut": "{{.Coin.Shortcut}}",
|
||||||
"coin_label": "{{.Coin.Label}}",
|
"coin_label": "{{.Coin.Label}}",
|
||||||
@ -17,6 +16,11 @@
|
|||||||
"message_queue_binding": "{{template "IPC.MessageQueueBindingTemplate" .}}",
|
"message_queue_binding": "{{template "IPC.MessageQueueBindingTemplate" .}}",
|
||||||
"subversion": "{{.Blockbook.BlockChain.Subversion}}",
|
"subversion": "{{.Blockbook.BlockChain.Subversion}}",
|
||||||
"address_format": "{{.Blockbook.BlockChain.AddressFormat}}",
|
"address_format": "{{.Blockbook.BlockChain.AddressFormat}}",
|
||||||
|
{{if .Blockbook.BlockChain.XPubMagic}} "xpub_magic": {{.Blockbook.BlockChain.XPubMagic}},
|
||||||
|
{{end}}{{if .Blockbook.BlockChain.XPubMagicSegwitP2sh}} "xpub_magic_segwit_p2sh": {{.Blockbook.BlockChain.XPubMagicSegwitP2sh}},
|
||||||
|
{{end}}{{if .Blockbook.BlockChain.XPubMagicSegwitNative}} "xpub_magic_segwit_native": {{.Blockbook.BlockChain.XPubMagicSegwitNative}},
|
||||||
|
{{end}}{{if .Blockbook.BlockChain.Slip44}} "slip44": {{.Blockbook.BlockChain.Slip44}},
|
||||||
|
{{end}}
|
||||||
"mempool_workers": {{.Blockbook.BlockChain.MempoolWorkers}},
|
"mempool_workers": {{.Blockbook.BlockChain.MempoolWorkers}},
|
||||||
"mempool_sub_workers": {{.Blockbook.BlockChain.MempoolSubWorkers}},
|
"mempool_sub_workers": {{.Blockbook.BlockChain.MempoolSubWorkers}},
|
||||||
"block_addresses_to_keep": {{.Blockbook.BlockChain.BlockAddressesToKeep}}
|
"block_addresses_to_keep": {{.Blockbook.BlockChain.BlockAddressesToKeep}}
|
||||||
|
|||||||
@ -13,18 +13,6 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
type Config struct {
|
type Config struct {
|
||||||
Meta struct {
|
|
||||||
BuildDatetime string // generated field
|
|
||||||
PackageMaintainer string `json:"package_maintainer"`
|
|
||||||
PackageMaintainerEmail string `json:"package_maintainer_email"`
|
|
||||||
}
|
|
||||||
Env struct {
|
|
||||||
Version string `json:"version"`
|
|
||||||
BackendInstallPath string `json:"backend_install_path"`
|
|
||||||
BackendDataPath string `json:"backend_data_path"`
|
|
||||||
BlockbookInstallPath string `json:"blockbook_install_path"`
|
|
||||||
BlockbookDataPath string `json:"blockbook_data_path"`
|
|
||||||
} `json:"env"`
|
|
||||||
Coin struct {
|
Coin struct {
|
||||||
Name string `json:"name"`
|
Name string `json:"name"`
|
||||||
Shortcut string `json:"shortcut"`
|
Shortcut string `json:"shortcut"`
|
||||||
@ -63,7 +51,7 @@ type Config struct {
|
|||||||
Mainnet bool `json:"mainnet"`
|
Mainnet bool `json:"mainnet"`
|
||||||
ServerConfigFile string `json:"server_config_file"`
|
ServerConfigFile string `json:"server_config_file"`
|
||||||
ClientConfigFile string `json:"client_config_file"`
|
ClientConfigFile string `json:"client_config_file"`
|
||||||
AdditionalParams interface{} `json:"additional_params"`
|
AdditionalParams interface{} `json:"additional_params,omitempty"`
|
||||||
} `json:"backend"`
|
} `json:"backend"`
|
||||||
Blockbook struct {
|
Blockbook struct {
|
||||||
PackageName string `json:"package_name"`
|
PackageName string `json:"package_name"`
|
||||||
@ -73,16 +61,32 @@ type Config struct {
|
|||||||
ExplorerURL string `json:"explorer_url"`
|
ExplorerURL string `json:"explorer_url"`
|
||||||
AdditionalParams string `json:"additional_params"`
|
AdditionalParams string `json:"additional_params"`
|
||||||
BlockChain struct {
|
BlockChain struct {
|
||||||
Parse bool `json:"parse"`
|
Parse bool `json:"parse,omitempty"`
|
||||||
Subversion string `json:"subversion"`
|
Subversion string `json:"subversion,omitempty"`
|
||||||
AddressFormat string `json:"address_format"`
|
AddressFormat string `json:"address_format,omitempty"`
|
||||||
MempoolWorkers int `json:"mempool_workers"`
|
MempoolWorkers int `json:"mempool_workers"`
|
||||||
MempoolSubWorkers int `json:"mempool_sub_workers"`
|
MempoolSubWorkers int `json:"mempool_sub_workers"`
|
||||||
BlockAddressesToKeep int `json:"block_addresses_to_keep"`
|
BlockAddressesToKeep int `json:"block_addresses_to_keep"`
|
||||||
AdditionalParams map[string]json.RawMessage `json:"additional_params"`
|
XPubMagic uint32 `json:"xpub_magic,omitempty"`
|
||||||
|
XPubMagicSegwitP2sh uint32 `json:"xpub_magic_segwit_p2sh,omitempty"`
|
||||||
|
XPubMagicSegwitNative uint32 `json:"xpub_magic_segwit_native,omitempty"`
|
||||||
|
Slip44 uint32 `json:"slip44,omitempty"`
|
||||||
|
|
||||||
|
AdditionalParams map[string]json.RawMessage `json:"additional_params"`
|
||||||
} `json:"block_chain"`
|
} `json:"block_chain"`
|
||||||
} `json:"blockbook"`
|
} `json:"blockbook"`
|
||||||
IntegrationTests map[string][]string `json:"integration_tests"`
|
Meta struct {
|
||||||
|
BuildDatetime string `json:"-"` // generated field
|
||||||
|
PackageMaintainer string `json:"package_maintainer"`
|
||||||
|
PackageMaintainerEmail string `json:"package_maintainer_email"`
|
||||||
|
} `json:"meta"`
|
||||||
|
Env struct {
|
||||||
|
Version string `json:"version"`
|
||||||
|
BackendInstallPath string `json:"backend_install_path"`
|
||||||
|
BackendDataPath string `json:"backend_data_path"`
|
||||||
|
BlockbookInstallPath string `json:"blockbook_install_path"`
|
||||||
|
BlockbookDataPath string `json:"blockbook_data_path"`
|
||||||
|
} `json:"-"`
|
||||||
}
|
}
|
||||||
|
|
||||||
func jsonToString(msg json.RawMessage) (string, error) {
|
func jsonToString(msg json.RawMessage) (string, error) {
|
||||||
@ -266,7 +270,7 @@ func makeOutputDir(path string) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func writeTemplate(path string, info os.FileInfo, templ *template.Template, config *Config) error {
|
func writeTemplate(path string, info os.FileInfo, templ *template.Template, config *Config) error {
|
||||||
f, err := os.OpenFile(path, os.O_CREATE|os.O_WRONLY, info.Mode())
|
f, err := os.OpenFile(path, os.O_CREATE|os.O_WRONLY|os.O_TRUNC, info.Mode())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|||||||
126
build/tools/trezor-common/sync-coins.go
Normal file
126
build/tools/trezor-common/sync-coins.go
Normal file
@ -0,0 +1,126 @@
|
|||||||
|
//usr/bin/go run $0 $@ ; exit
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
build "blockbook/build/tools"
|
||||||
|
"encoding/json"
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"io/ioutil"
|
||||||
|
"net/http"
|
||||||
|
"os"
|
||||||
|
"path/filepath"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
configsDir = "configs"
|
||||||
|
trezorCommonDefsURL = "https://raw.githubusercontent.com/trezor/trezor-common/master/defs/bitcoin/"
|
||||||
|
)
|
||||||
|
|
||||||
|
type trezorCommonDef struct {
|
||||||
|
Name string `json:"coin_name"`
|
||||||
|
Shortcut string `json:"coin_shortcut"`
|
||||||
|
Label string `json:"coin_label"`
|
||||||
|
XPubMagic uint32 `json:"xpub_magic"`
|
||||||
|
XPubMagicSegwitP2sh uint32 `json:"xpub_magic_segwit_p2sh"`
|
||||||
|
XPubMagicSegwitNative uint32 `json:"xpub_magic_segwit_native"`
|
||||||
|
Slip44 uint32 `json:"slip44,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func getTrezorCommonDef(coin string) (*trezorCommonDef, error) {
|
||||||
|
req, err := http.NewRequest("GET", trezorCommonDefsURL+coin+".json", nil)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
resp, err := http.DefaultClient.Do(req)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
defer resp.Body.Close()
|
||||||
|
if resp.StatusCode != http.StatusOK {
|
||||||
|
return nil, errors.New("Github request status code " + strconv.Itoa(resp.StatusCode))
|
||||||
|
}
|
||||||
|
bb, err := ioutil.ReadAll(resp.Body)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
var tcd trezorCommonDef
|
||||||
|
json.Unmarshal(bb, &tcd)
|
||||||
|
return &tcd, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func writeConfig(coin string, config *build.Config) error {
|
||||||
|
path := filepath.Join(configsDir, "coins", coin+".json")
|
||||||
|
out, err := os.OpenFile(path, os.O_CREATE|os.O_WRONLY|os.O_TRUNC, 0644)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
defer out.Close()
|
||||||
|
buf, err := json.MarshalIndent(config, "", " ")
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
n, err := out.Write(buf)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if n < len(buf) {
|
||||||
|
return io.ErrShortWrite
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
var coins []string
|
||||||
|
if len(os.Args) < 2 {
|
||||||
|
filepath.Walk(filepath.Join(configsDir, "coins"), func(path string, info os.FileInfo, err error) error {
|
||||||
|
n := strings.TrimSuffix(info.Name(), ".json")
|
||||||
|
if n != info.Name() {
|
||||||
|
coins = append(coins, n)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
coins = append(coins, os.Args[1])
|
||||||
|
}
|
||||||
|
for _, coin := range coins {
|
||||||
|
config, err := build.LoadConfig(configsDir, coin)
|
||||||
|
if err == nil {
|
||||||
|
var tcd *trezorCommonDef
|
||||||
|
tcd, err = getTrezorCommonDef(coin)
|
||||||
|
if err == nil {
|
||||||
|
if tcd.Name != "" {
|
||||||
|
config.Coin.Name = tcd.Name
|
||||||
|
}
|
||||||
|
if tcd.Shortcut != "" {
|
||||||
|
config.Coin.Shortcut = tcd.Shortcut
|
||||||
|
}
|
||||||
|
if tcd.Label != "" {
|
||||||
|
config.Coin.Label = tcd.Label
|
||||||
|
}
|
||||||
|
if tcd.XPubMagic != 0 {
|
||||||
|
config.Blockbook.BlockChain.XPubMagic = tcd.XPubMagic
|
||||||
|
}
|
||||||
|
if tcd.XPubMagicSegwitP2sh != 0 {
|
||||||
|
config.Blockbook.BlockChain.XPubMagicSegwitP2sh = tcd.XPubMagicSegwitP2sh
|
||||||
|
}
|
||||||
|
if tcd.XPubMagicSegwitNative != 0 {
|
||||||
|
config.Blockbook.BlockChain.XPubMagicSegwitNative = tcd.XPubMagicSegwitNative
|
||||||
|
}
|
||||||
|
if tcd.Slip44 != 0 {
|
||||||
|
config.Blockbook.BlockChain.Slip44 = tcd.Slip44
|
||||||
|
}
|
||||||
|
err = writeConfig(coin, config)
|
||||||
|
if err == nil {
|
||||||
|
fmt.Printf("%v updated\n", coin)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
fmt.Printf("%v update error %v\n", coin, err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -1,9 +1,9 @@
|
|||||||
{
|
{
|
||||||
"coin": {
|
"coin": {
|
||||||
"name": "Bcash",
|
"name": "Bcash",
|
||||||
"shortcut": "BCH",
|
"shortcut": "BCH",
|
||||||
"label": "Bitcoin Cash",
|
"label": "Bitcoin Cash",
|
||||||
"alias": "bcash"
|
"alias": "bcash"
|
||||||
},
|
},
|
||||||
"ports": {
|
"ports": {
|
||||||
"backend_rpc": 8031,
|
"backend_rpc": 8031,
|
||||||
@ -28,7 +28,7 @@
|
|||||||
"verification_source": "788001fd5ce8ca0bd61e8a92f10d22b7a49695c3651c60fad11358ba29309a1b",
|
"verification_source": "788001fd5ce8ca0bd61e8a92f10d22b7a49695c3651c60fad11358ba29309a1b",
|
||||||
"extract_command": "tar -C backend --strip 1 -xf",
|
"extract_command": "tar -C backend --strip 1 -xf",
|
||||||
"exclude_files": [
|
"exclude_files": [
|
||||||
"bin/bitcoin-qt"
|
"bin/bitcoin-qt"
|
||||||
],
|
],
|
||||||
"exec_command_template": "{{.Env.BackendInstallPath}}/{{.Coin.Alias}}/bin/bitcoind -datadir={{.Env.BackendDataPath}}/{{.Coin.Alias}}/backend -conf={{.Env.BackendInstallPath}}/{{.Coin.Alias}}/{{.Coin.Alias}}.conf -pid=/run/{{.Coin.Alias}}/{{.Coin.Alias}}.pid",
|
"exec_command_template": "{{.Env.BackendInstallPath}}/{{.Coin.Alias}}/bin/bitcoind -datadir={{.Env.BackendDataPath}}/{{.Coin.Alias}}/backend -conf={{.Env.BackendInstallPath}}/{{.Coin.Alias}}/{{.Coin.Alias}}.conf -pid=/run/{{.Coin.Alias}}/{{.Coin.Alias}}.pid",
|
||||||
"logrotate_files_template": "{{.Env.BackendDataPath}}/{{.Coin.Alias}}/backend/*.log",
|
"logrotate_files_template": "{{.Env.BackendDataPath}}/{{.Coin.Alias}}/backend/*.log",
|
||||||
@ -54,6 +54,8 @@
|
|||||||
"mempool_workers": 8,
|
"mempool_workers": 8,
|
||||||
"mempool_sub_workers": 2,
|
"mempool_sub_workers": 2,
|
||||||
"block_addresses_to_keep": 300,
|
"block_addresses_to_keep": 300,
|
||||||
|
"xpub_magic": 76067358,
|
||||||
|
"slip44": 145,
|
||||||
"additional_params": {}
|
"additional_params": {}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|||||||
@ -1,9 +1,9 @@
|
|||||||
{
|
{
|
||||||
"coin": {
|
"coin": {
|
||||||
"name": "Bcash Testnet",
|
"name": "Bcash Testnet",
|
||||||
"shortcut": "TBCH",
|
"shortcut": "TBCH",
|
||||||
"label": "Bitcoin Cash Testnet",
|
"label": "Bitcoin Cash Testnet",
|
||||||
"alias": "bcash_testnet"
|
"alias": "bcash_testnet"
|
||||||
},
|
},
|
||||||
"ports": {
|
"ports": {
|
||||||
"backend_rpc": 18031,
|
"backend_rpc": 18031,
|
||||||
@ -28,7 +28,7 @@
|
|||||||
"verification_source": "788001fd5ce8ca0bd61e8a92f10d22b7a49695c3651c60fad11358ba29309a1b",
|
"verification_source": "788001fd5ce8ca0bd61e8a92f10d22b7a49695c3651c60fad11358ba29309a1b",
|
||||||
"extract_command": "tar -C backend --strip 1 -xf",
|
"extract_command": "tar -C backend --strip 1 -xf",
|
||||||
"exclude_files": [
|
"exclude_files": [
|
||||||
"bin/bitcoin-qt"
|
"bin/bitcoin-qt"
|
||||||
],
|
],
|
||||||
"exec_command_template": "{{.Env.BackendInstallPath}}/{{.Coin.Alias}}/bin/bitcoind -datadir={{.Env.BackendDataPath}}/{{.Coin.Alias}}/backend -conf={{.Env.BackendInstallPath}}/{{.Coin.Alias}}/{{.Coin.Alias}}.conf -pid=/run/{{.Coin.Alias}}/{{.Coin.Alias}}.pid",
|
"exec_command_template": "{{.Env.BackendInstallPath}}/{{.Coin.Alias}}/bin/bitcoind -datadir={{.Env.BackendDataPath}}/{{.Coin.Alias}}/backend -conf={{.Env.BackendInstallPath}}/{{.Coin.Alias}}/{{.Coin.Alias}}.conf -pid=/run/{{.Coin.Alias}}/{{.Coin.Alias}}.pid",
|
||||||
"logrotate_files_template": "{{.Env.BackendDataPath}}/{{.Coin.Alias}}/backend/testnet3/*.log",
|
"logrotate_files_template": "{{.Env.BackendDataPath}}/{{.Coin.Alias}}/backend/testnet3/*.log",
|
||||||
@ -54,6 +54,8 @@
|
|||||||
"mempool_workers": 8,
|
"mempool_workers": 8,
|
||||||
"mempool_sub_workers": 2,
|
"mempool_sub_workers": 2,
|
||||||
"block_addresses_to_keep": 300,
|
"block_addresses_to_keep": 300,
|
||||||
|
"xpub_magic": 70617039,
|
||||||
|
"slip44": 1,
|
||||||
"additional_params": {}
|
"additional_params": {}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|||||||
@ -1,9 +1,9 @@
|
|||||||
{
|
{
|
||||||
"coin": {
|
"coin": {
|
||||||
"name": "Bgold",
|
"name": "Bgold",
|
||||||
"shortcut": "BTG",
|
"shortcut": "BTG",
|
||||||
"label": "Bitcoin Gold",
|
"label": "Bitcoin Gold",
|
||||||
"alias": "bgold"
|
"alias": "bgold"
|
||||||
},
|
},
|
||||||
"ports": {
|
"ports": {
|
||||||
"backend_rpc": 8035,
|
"backend_rpc": 8035,
|
||||||
@ -28,7 +28,7 @@
|
|||||||
"verification_source": "https://github.com/BTCGPU/BTCGPU/releases/download/v0.15.2/SHA256SUMS.asc",
|
"verification_source": "https://github.com/BTCGPU/BTCGPU/releases/download/v0.15.2/SHA256SUMS.asc",
|
||||||
"extract_command": "tar -C backend --strip 1 -xf",
|
"extract_command": "tar -C backend --strip 1 -xf",
|
||||||
"exclude_files": [
|
"exclude_files": [
|
||||||
"bin/bitcoin-qt"
|
"bin/bitcoin-qt"
|
||||||
],
|
],
|
||||||
"exec_command_template": "{{.Env.BackendInstallPath}}/{{.Coin.Alias}}/bin/bgoldd -datadir={{.Env.BackendDataPath}}/{{.Coin.Alias}}/backend -conf={{.Env.BackendInstallPath}}/{{.Coin.Alias}}/{{.Coin.Alias}}.conf -pid=/run/{{.Coin.Alias}}/{{.Coin.Alias}}.pid",
|
"exec_command_template": "{{.Env.BackendInstallPath}}/{{.Coin.Alias}}/bin/bgoldd -datadir={{.Env.BackendDataPath}}/{{.Coin.Alias}}/backend -conf={{.Env.BackendInstallPath}}/{{.Coin.Alias}}/{{.Coin.Alias}}.conf -pid=/run/{{.Coin.Alias}}/{{.Coin.Alias}}.pid",
|
||||||
"logrotate_files_template": "{{.Env.BackendDataPath}}/{{.Coin.Alias}}/backend/*.log",
|
"logrotate_files_template": "{{.Env.BackendDataPath}}/{{.Coin.Alias}}/backend/*.log",
|
||||||
@ -40,199 +40,199 @@
|
|||||||
"server_config_file": "bitcoin_like.conf",
|
"server_config_file": "bitcoin_like.conf",
|
||||||
"client_config_file": "bitcoin_like_client.conf",
|
"client_config_file": "bitcoin_like_client.conf",
|
||||||
"additional_params": {
|
"additional_params": {
|
||||||
"mempoolexpiry": 72,
|
"addnode": [
|
||||||
"timeout": 768,
|
"188.126.0.134",
|
||||||
"maxconnections": 250,
|
"45.56.84.44",
|
||||||
"addnode": [
|
"109.201.133.93:8338",
|
||||||
"188.126.0.134",
|
"178.63.11.246:8338",
|
||||||
"45.56.84.44",
|
"188.120.223.153:8338",
|
||||||
"109.201.133.93:8338",
|
"79.137.64.158:8338",
|
||||||
"178.63.11.246:8338",
|
"78.193.221.106:8338",
|
||||||
"188.120.223.153:8338",
|
"139.59.151.13:8338",
|
||||||
"79.137.64.158:8338",
|
"76.16.12.81:8338",
|
||||||
"78.193.221.106:8338",
|
"172.104.157.62:8338",
|
||||||
"139.59.151.13:8338",
|
"43.207.67.209:8338",
|
||||||
"76.16.12.81:8338",
|
"178.63.11.246:8338",
|
||||||
"172.104.157.62:8338",
|
"79.137.64.158:8338",
|
||||||
"43.207.67.209:8338",
|
"78.193.221.106:8338",
|
||||||
"178.63.11.246:8338",
|
"139.59.151.13:8338",
|
||||||
"79.137.64.158:8338",
|
"172.104.157.62:8338",
|
||||||
"78.193.221.106:8338",
|
"178.158.247.119:8338",
|
||||||
"139.59.151.13:8338",
|
"109.201.133.93:8338",
|
||||||
"172.104.157.62:8338",
|
"178.63.11.246:8338",
|
||||||
"178.158.247.119:8338",
|
"139.59.151.13:8338",
|
||||||
"109.201.133.93:8338",
|
"172.104.157.62:8338",
|
||||||
"178.63.11.246:8338",
|
"188.120.223.153:8338",
|
||||||
"139.59.151.13:8338",
|
"178.158.247.119:8338",
|
||||||
"172.104.157.62:8338",
|
"78.193.221.106:8338",
|
||||||
"188.120.223.153:8338",
|
"79.137.64.158:8338",
|
||||||
"178.158.247.119:8338",
|
"76.16.12.81:8338",
|
||||||
"78.193.221.106:8338",
|
"176.12.32.153:8338",
|
||||||
"79.137.64.158:8338",
|
"178.158.247.122:8338",
|
||||||
"76.16.12.81:8338",
|
"81.37.147.185:8338",
|
||||||
"176.12.32.153:8338",
|
"176.12.32.153:8338",
|
||||||
"178.158.247.122:8338",
|
"79.137.64.158:8338",
|
||||||
"81.37.147.185:8338",
|
"178.158.247.122:8338",
|
||||||
"176.12.32.153:8338",
|
"66.70.247.151:8338",
|
||||||
"79.137.64.158:8338",
|
"89.18.27.165:8338",
|
||||||
"178.158.247.122:8338",
|
"178.63.11.246:8338",
|
||||||
"66.70.247.151:8338",
|
"91.222.17.86:8338",
|
||||||
"89.18.27.165:8338",
|
"37.59.50.143:8338",
|
||||||
"178.63.11.246:8338",
|
"91.50.219.221:8338",
|
||||||
"91.222.17.86:8338",
|
"154.16.63.17:8338",
|
||||||
"37.59.50.143:8338",
|
"213.136.76.42:8338",
|
||||||
"91.50.219.221:8338",
|
"176.99.4.140:8338",
|
||||||
"154.16.63.17:8338",
|
"176.9.48.36:8338",
|
||||||
"213.136.76.42:8338",
|
"78.193.221.106:8338",
|
||||||
"176.99.4.140:8338",
|
"34.236.228.99:8338",
|
||||||
"176.9.48.36:8338",
|
"213.154.230.107:8338",
|
||||||
"78.193.221.106:8338",
|
"111.231.66.252:8338",
|
||||||
"34.236.228.99:8338",
|
"188.120.223.153:8338",
|
||||||
"213.154.230.107:8338",
|
"219.89.122.82:8338",
|
||||||
"111.231.66.252:8338",
|
"109.192.23.101:8338",
|
||||||
"188.120.223.153:8338",
|
"98.114.91.222:8338",
|
||||||
"219.89.122.82:8338",
|
"217.66.156.41:8338",
|
||||||
"109.192.23.101:8338",
|
"172.104.157.62:8338",
|
||||||
"98.114.91.222:8338",
|
"114.44.222.73:8338",
|
||||||
"217.66.156.41:8338",
|
"91.224.140.216:8338",
|
||||||
"172.104.157.62:8338",
|
"149.154.71.96:8338",
|
||||||
"114.44.222.73:8338",
|
"107.181.183.242:8338",
|
||||||
"91.224.140.216:8338",
|
"36.78.96.92:8338",
|
||||||
"149.154.71.96:8338",
|
"46.22.7.74:8338",
|
||||||
"107.181.183.242:8338",
|
"89.110.53.186:8338",
|
||||||
"36.78.96.92:8338",
|
"73.243.220.85:8338",
|
||||||
"46.22.7.74:8338",
|
"109.86.137.8:8338",
|
||||||
"89.110.53.186:8338",
|
"77.78.12.89:8338",
|
||||||
"73.243.220.85:8338",
|
"87.92.116.26:8338",
|
||||||
"109.86.137.8:8338",
|
"93.78.122.48:8338",
|
||||||
"77.78.12.89:8338",
|
"35.195.83.0:8338",
|
||||||
"87.92.116.26:8338",
|
"46.147.75.220:8338",
|
||||||
"93.78.122.48:8338",
|
"212.47.236.104:8338",
|
||||||
"35.195.83.0:8338",
|
"95.220.100.230:8338",
|
||||||
"46.147.75.220:8338",
|
"178.70.142.247:8338",
|
||||||
"212.47.236.104:8338",
|
"45.76.136.149:8338",
|
||||||
"95.220.100.230:8338",
|
"94.155.74.206:8338",
|
||||||
"178.70.142.247:8338",
|
"178.70.142.247:8338",
|
||||||
"45.76.136.149:8338",
|
"128.199.228.97:8338",
|
||||||
"94.155.74.206:8338",
|
"77.171.144.207:8338",
|
||||||
"178.70.142.247:8338",
|
"159.89.192.119:8338",
|
||||||
"128.199.228.97:8338",
|
"136.63.238.170:8338",
|
||||||
"77.171.144.207:8338",
|
"31.27.193.105:8338",
|
||||||
"159.89.192.119:8338",
|
"176.107.192.240:8338",
|
||||||
"136.63.238.170:8338",
|
"94.140.241.96:8338",
|
||||||
"31.27.193.105:8338",
|
"66.108.15.5:8338",
|
||||||
"176.107.192.240:8338",
|
"81.177.127.204:8338",
|
||||||
"94.140.241.96:8338",
|
"88.18.69.174:8338",
|
||||||
"66.108.15.5:8338",
|
"178.70.130.94:8338",
|
||||||
"81.177.127.204:8338",
|
"78.98.162.140:8338",
|
||||||
"88.18.69.174:8338",
|
"95.133.156.224:8338",
|
||||||
"178.70.130.94:8338",
|
"46.188.16.96:8338",
|
||||||
"78.98.162.140:8338",
|
"94.247.16.21:8338",
|
||||||
"95.133.156.224:8338",
|
"eunode.pool.gold:8338",
|
||||||
"46.188.16.96:8338",
|
"asianode.pool.gold:8338",
|
||||||
"94.247.16.21:8338",
|
"45.56.84.44:8338",
|
||||||
"eunode.pool.gold:8338",
|
"176.9.48.36:8338",
|
||||||
"asianode.pool.gold:8338",
|
"93.57.253.121:8338",
|
||||||
"45.56.84.44:8338",
|
"172.104.157.62:8338",
|
||||||
"176.9.48.36:8338",
|
"176.12.32.153:8338",
|
||||||
"93.57.253.121:8338",
|
"pool.serverpower.net:8338",
|
||||||
"172.104.157.62:8338",
|
"213.154.229.126:8338",
|
||||||
"176.12.32.153:8338",
|
"213.154.230.106:8338",
|
||||||
"pool.serverpower.net:8338",
|
"213.154.230.107:8338",
|
||||||
"213.154.229.126:8338",
|
"213.154.229.50:8338",
|
||||||
"213.154.230.106:8338",
|
"145.239.0.50:8338",
|
||||||
"213.154.230.107:8338",
|
"107.181.183.242:8338",
|
||||||
"213.154.229.50:8338",
|
"109.201.133.93:8338",
|
||||||
"145.239.0.50:8338",
|
"120.41.190.109:8338",
|
||||||
"107.181.183.242:8338",
|
"120.41.191.224:8338",
|
||||||
"109.201.133.93:8338",
|
"138.68.249.79:8338",
|
||||||
"120.41.190.109:8338",
|
"13.95.223.202:8338",
|
||||||
"120.41.191.224:8338",
|
"145.239.0.50:8338",
|
||||||
"138.68.249.79:8338",
|
"149.56.95.26:8338",
|
||||||
"13.95.223.202:8338",
|
"158.69.103.228:8338",
|
||||||
"145.239.0.50:8338",
|
"159.89.192.119:8338",
|
||||||
"149.56.95.26:8338",
|
"164.132.207.143:8338",
|
||||||
"158.69.103.228:8338",
|
"171.100.141.106:8338",
|
||||||
"159.89.192.119:8338",
|
"172.104.157.62:8338",
|
||||||
"164.132.207.143:8338",
|
"173.176.95.92:8338",
|
||||||
"171.100.141.106:8338",
|
"176.12.32.153:8338",
|
||||||
"172.104.157.62:8338",
|
"178.239.54.250:8338",
|
||||||
"173.176.95.92:8338",
|
"178.63.11.246:8338",
|
||||||
"176.12.32.153:8338",
|
"185.139.2.140:8338",
|
||||||
"178.239.54.250:8338",
|
"188.120.223.153:8338",
|
||||||
"178.63.11.246:8338",
|
"190.46.2.92:8338",
|
||||||
"185.139.2.140:8338",
|
"192.99.194.113:8338",
|
||||||
"188.120.223.153:8338",
|
"199.229.248.218:8338",
|
||||||
"190.46.2.92:8338",
|
"213.154.229.126:8338",
|
||||||
"192.99.194.113:8338",
|
"213.154.229.50:8338",
|
||||||
"199.229.248.218:8338",
|
"213.154.230.106:8338",
|
||||||
"213.154.229.126:8338",
|
"213.154.230.107:8338",
|
||||||
"213.154.229.50:8338",
|
"217.182.199.21",
|
||||||
"213.154.230.106:8338",
|
"35.189.127.200:8338",
|
||||||
"213.154.230.107:8338",
|
"35.195.83.0:8338",
|
||||||
"217.182.199.21",
|
"35.197.197.166:8338",
|
||||||
"35.189.127.200:8338",
|
"35.200.168.155:8338",
|
||||||
"35.195.83.0:8338",
|
"35.203.167.11:8338",
|
||||||
"35.197.197.166:8338",
|
"37.59.50.143:8338",
|
||||||
"35.200.168.155:8338",
|
"45.27.161.195:8338",
|
||||||
"35.203.167.11:8338",
|
"45.32.234.160:8338",
|
||||||
"37.59.50.143:8338",
|
"45.56.84.44:8338",
|
||||||
"45.27.161.195:8338",
|
"46.188.16.96:8338",
|
||||||
"45.32.234.160:8338",
|
"46.251.19.171:8338",
|
||||||
"45.56.84.44:8338",
|
"5.157.119.109:8338",
|
||||||
"46.188.16.96:8338",
|
"52.28.162.48:8338",
|
||||||
"46.251.19.171:8338",
|
"54.153.140.202:8338",
|
||||||
"5.157.119.109:8338",
|
"54.68.81.2:83388338",
|
||||||
"52.28.162.48:8338",
|
"62.195.190.190:8338",
|
||||||
"54.153.140.202:8338",
|
"62.216.5.136:8338",
|
||||||
"54.68.81.2:83388338",
|
"65.110.125.175:8338",
|
||||||
"62.195.190.190:8338",
|
"67.68.226.130:8338",
|
||||||
"62.216.5.136:8338",
|
"73.243.220.85:8338",
|
||||||
"65.110.125.175:8338",
|
"77.78.12.89:8338",
|
||||||
"67.68.226.130:8338",
|
"78.193.221.106:8338",
|
||||||
"73.243.220.85:8338",
|
"78.98.162.140:8338",
|
||||||
"77.78.12.89:8338",
|
"79.137.64.158:8338",
|
||||||
"78.193.221.106:8338",
|
"84.144.177.238:8338",
|
||||||
"78.98.162.140:8338",
|
"87.92.116.26:8338",
|
||||||
"79.137.64.158:8338",
|
"89.115.139.117:8338",
|
||||||
"84.144.177.238:8338",
|
"89.18.27.165:8338",
|
||||||
"87.92.116.26:8338",
|
"91.50.219.221:8338",
|
||||||
"89.115.139.117:8338",
|
"93.88.74.26",
|
||||||
"89.18.27.165:8338",
|
"93.88.74.26:8338",
|
||||||
"91.50.219.221:8338",
|
"94.155.74.206:8338",
|
||||||
"93.88.74.26",
|
"95.154.201.132:8338",
|
||||||
"93.88.74.26:8338",
|
"98.29.248.131:8338",
|
||||||
"94.155.74.206:8338",
|
"u2.my.to:8338",
|
||||||
"95.154.201.132:8338",
|
"[2001:470:b:ce:dc70:83ff:fe7a:1e74]:8338",
|
||||||
"98.29.248.131:8338",
|
"2001:7b8:61d:1:250:56ff:fe90:c89f:8338",
|
||||||
"u2.my.to:8338",
|
"2001:7b8:63a:1002:213:154:230:106:8338",
|
||||||
"[2001:470:b:ce:dc70:83ff:fe7a:1e74]:8338",
|
"2001:7b8:63a:1002:213:154:230:107:8338",
|
||||||
"2001:7b8:61d:1:250:56ff:fe90:c89f:8338",
|
"45.56.84.44",
|
||||||
"2001:7b8:63a:1002:213:154:230:106:8338",
|
"109.201.133.93:8338",
|
||||||
"2001:7b8:63a:1002:213:154:230:107:8338",
|
"120.41.191.224:30607",
|
||||||
"45.56.84.44",
|
"138.68.249.79:50992",
|
||||||
"109.201.133.93:8338",
|
"138.68.249.79:51314",
|
||||||
"120.41.191.224:30607",
|
"172.104.157.62",
|
||||||
"138.68.249.79:50992",
|
"178.63.11.246:8338",
|
||||||
"138.68.249.79:51314",
|
"185.139.2.140:8338",
|
||||||
"172.104.157.62",
|
"199.229.248.218:28830",
|
||||||
"178.63.11.246:8338",
|
"35.189.127.200:41220",
|
||||||
"185.139.2.140:8338",
|
"35.189.127.200:48244",
|
||||||
"199.229.248.218:28830",
|
"35.195.83.0:35172",
|
||||||
"35.189.127.200:41220",
|
"35.195.83.0:35576",
|
||||||
"35.189.127.200:48244",
|
"35.195.83.0:35798",
|
||||||
"35.195.83.0:35172",
|
"35.197.197.166:32794",
|
||||||
"35.195.83.0:35576",
|
"35.197.197.166:33112",
|
||||||
"35.195.83.0:35798",
|
"35.197.197.166:33332",
|
||||||
"35.197.197.166:32794",
|
"35.203.167.11:52158",
|
||||||
"35.197.197.166:33112",
|
"37.59.50.143:35254",
|
||||||
"35.197.197.166:33332",
|
"45.27.161.195:33852",
|
||||||
"35.203.167.11:52158",
|
"45.27.161.195:36738",
|
||||||
"37.59.50.143:35254",
|
"45.27.161.195:58628"
|
||||||
"45.27.161.195:33852",
|
],
|
||||||
"45.27.161.195:36738",
|
"maxconnections": 250,
|
||||||
"45.27.161.195:58628"
|
"mempoolexpiry": 72,
|
||||||
]
|
"timeout": 768
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"blockbook": {
|
"blockbook": {
|
||||||
@ -248,6 +248,9 @@
|
|||||||
"mempool_workers": 8,
|
"mempool_workers": 8,
|
||||||
"mempool_sub_workers": 2,
|
"mempool_sub_workers": 2,
|
||||||
"block_addresses_to_keep": 300,
|
"block_addresses_to_keep": 300,
|
||||||
|
"xpub_magic": 76067358,
|
||||||
|
"xpub_magic_segwit_p2sh": 77429938,
|
||||||
|
"slip44": 156,
|
||||||
"additional_params": {}
|
"additional_params": {}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|||||||
@ -1,9 +1,9 @@
|
|||||||
{
|
{
|
||||||
"coin": {
|
"coin": {
|
||||||
"name": "Bitcoin",
|
"name": "Bitcoin",
|
||||||
"shortcut": "BTC",
|
"shortcut": "BTC",
|
||||||
"label": "Bitcoin",
|
"label": "Bitcoin",
|
||||||
"alias": "bitcoin"
|
"alias": "bitcoin"
|
||||||
},
|
},
|
||||||
"ports": {
|
"ports": {
|
||||||
"backend_rpc": 8030,
|
"backend_rpc": 8030,
|
||||||
@ -28,7 +28,7 @@
|
|||||||
"verification_source": "https://bitcoin.org/bin/bitcoin-core-0.17.1/SHA256SUMS.asc",
|
"verification_source": "https://bitcoin.org/bin/bitcoin-core-0.17.1/SHA256SUMS.asc",
|
||||||
"extract_command": "tar -C backend --strip 1 -xf",
|
"extract_command": "tar -C backend --strip 1 -xf",
|
||||||
"exclude_files": [
|
"exclude_files": [
|
||||||
"bin/bitcoin-qt"
|
"bin/bitcoin-qt"
|
||||||
],
|
],
|
||||||
"exec_command_template": "{{.Env.BackendInstallPath}}/{{.Coin.Alias}}/bin/bitcoind -datadir={{.Env.BackendDataPath}}/{{.Coin.Alias}}/backend -conf={{.Env.BackendInstallPath}}/{{.Coin.Alias}}/{{.Coin.Alias}}.conf -pid=/run/{{.Coin.Alias}}/{{.Coin.Alias}}.pid",
|
"exec_command_template": "{{.Env.BackendInstallPath}}/{{.Coin.Alias}}/bin/bitcoind -datadir={{.Env.BackendDataPath}}/{{.Coin.Alias}}/backend -conf={{.Env.BackendInstallPath}}/{{.Coin.Alias}}/{{.Coin.Alias}}.conf -pid=/run/{{.Coin.Alias}}/{{.Coin.Alias}}.pid",
|
||||||
"logrotate_files_template": "{{.Env.BackendDataPath}}/{{.Coin.Alias}}/backend/*.log",
|
"logrotate_files_template": "{{.Env.BackendDataPath}}/{{.Coin.Alias}}/backend/*.log",
|
||||||
@ -40,7 +40,7 @@
|
|||||||
"server_config_file": "bitcoin.conf",
|
"server_config_file": "bitcoin.conf",
|
||||||
"client_config_file": "bitcoin_client.conf",
|
"client_config_file": "bitcoin_client.conf",
|
||||||
"additional_params": {
|
"additional_params": {
|
||||||
"deprecatedrpc": "estimatefee"
|
"deprecatedrpc": "estimatefee"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"blockbook": {
|
"blockbook": {
|
||||||
@ -55,6 +55,9 @@
|
|||||||
"mempool_workers": 8,
|
"mempool_workers": 8,
|
||||||
"mempool_sub_workers": 2,
|
"mempool_sub_workers": 2,
|
||||||
"block_addresses_to_keep": 300,
|
"block_addresses_to_keep": 300,
|
||||||
|
"xpub_magic": 76067358,
|
||||||
|
"xpub_magic_segwit_p2sh": 77429938,
|
||||||
|
"xpub_magic_segwit_native": 78792518,
|
||||||
"additional_params": {}
|
"additional_params": {}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|||||||
@ -1,9 +1,9 @@
|
|||||||
{
|
{
|
||||||
"coin": {
|
"coin": {
|
||||||
"name": "Testnet",
|
"name": "Testnet",
|
||||||
"shortcut": "TEST",
|
"shortcut": "TEST",
|
||||||
"label": "Bitcoin Testnet",
|
"label": "Bitcoin Testnet",
|
||||||
"alias": "bitcoin_testnet"
|
"alias": "bitcoin_testnet"
|
||||||
},
|
},
|
||||||
"ports": {
|
"ports": {
|
||||||
"backend_rpc": 18030,
|
"backend_rpc": 18030,
|
||||||
@ -28,7 +28,7 @@
|
|||||||
"verification_source": "https://bitcoin.org/bin/bitcoin-core-0.17.1/SHA256SUMS.asc",
|
"verification_source": "https://bitcoin.org/bin/bitcoin-core-0.17.1/SHA256SUMS.asc",
|
||||||
"extract_command": "tar -C backend --strip 1 -xf",
|
"extract_command": "tar -C backend --strip 1 -xf",
|
||||||
"exclude_files": [
|
"exclude_files": [
|
||||||
"bin/bitcoin-qt"
|
"bin/bitcoin-qt"
|
||||||
],
|
],
|
||||||
"exec_command_template": "{{.Env.BackendInstallPath}}/{{.Coin.Alias}}/bin/bitcoind -datadir={{.Env.BackendDataPath}}/{{.Coin.Alias}}/backend -conf={{.Env.BackendInstallPath}}/{{.Coin.Alias}}/{{.Coin.Alias}}.conf -pid=/run/{{.Coin.Alias}}/{{.Coin.Alias}}.pid",
|
"exec_command_template": "{{.Env.BackendInstallPath}}/{{.Coin.Alias}}/bin/bitcoind -datadir={{.Env.BackendDataPath}}/{{.Coin.Alias}}/backend -conf={{.Env.BackendInstallPath}}/{{.Coin.Alias}}/{{.Coin.Alias}}.conf -pid=/run/{{.Coin.Alias}}/{{.Coin.Alias}}.pid",
|
||||||
"logrotate_files_template": "{{.Env.BackendDataPath}}/{{.Coin.Alias}}/backend/testnet3/*.log",
|
"logrotate_files_template": "{{.Env.BackendDataPath}}/{{.Coin.Alias}}/backend/testnet3/*.log",
|
||||||
@ -40,7 +40,7 @@
|
|||||||
"server_config_file": "bitcoin.conf",
|
"server_config_file": "bitcoin.conf",
|
||||||
"client_config_file": "bitcoin_client.conf",
|
"client_config_file": "bitcoin_client.conf",
|
||||||
"additional_params": {
|
"additional_params": {
|
||||||
"deprecatedrpc": "estimatefee"
|
"deprecatedrpc": "estimatefee"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"blockbook": {
|
"blockbook": {
|
||||||
@ -55,6 +55,10 @@
|
|||||||
"mempool_workers": 8,
|
"mempool_workers": 8,
|
||||||
"mempool_sub_workers": 2,
|
"mempool_sub_workers": 2,
|
||||||
"block_addresses_to_keep": 300,
|
"block_addresses_to_keep": 300,
|
||||||
|
"xpub_magic": 70617039,
|
||||||
|
"xpub_magic_segwit_p2sh": 71979618,
|
||||||
|
"xpub_magic_segwit_native": 73342198,
|
||||||
|
"slip44": 1,
|
||||||
"additional_params": {}
|
"additional_params": {}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|||||||
@ -1,9 +1,9 @@
|
|||||||
{
|
{
|
||||||
"coin": {
|
"coin": {
|
||||||
"name": "Dash",
|
"name": "Dash",
|
||||||
"shortcut": "DASH",
|
"shortcut": "DASH",
|
||||||
"label": "Dash",
|
"label": "Dash",
|
||||||
"alias": "dash"
|
"alias": "dash"
|
||||||
},
|
},
|
||||||
"ports": {
|
"ports": {
|
||||||
"backend_rpc": 8033,
|
"backend_rpc": 8033,
|
||||||
@ -28,7 +28,7 @@
|
|||||||
"verification_source": "https://github.com/dashpay/dash/releases/download/v0.13.1.0/SHA256SUMS.asc",
|
"verification_source": "https://github.com/dashpay/dash/releases/download/v0.13.1.0/SHA256SUMS.asc",
|
||||||
"extract_command": "tar -C backend --strip 1 -xf",
|
"extract_command": "tar -C backend --strip 1 -xf",
|
||||||
"exclude_files": [
|
"exclude_files": [
|
||||||
"bin/dash-qt"
|
"bin/dash-qt"
|
||||||
],
|
],
|
||||||
"exec_command_template": "{{.Env.BackendInstallPath}}/{{.Coin.Alias}}/bin/dashd -datadir={{.Env.BackendDataPath}}/{{.Coin.Alias}}/backend -conf={{.Env.BackendInstallPath}}/{{.Coin.Alias}}/{{.Coin.Alias}}.conf -pid=/run/{{.Coin.Alias}}/{{.Coin.Alias}}.pid",
|
"exec_command_template": "{{.Env.BackendInstallPath}}/{{.Coin.Alias}}/bin/dashd -datadir={{.Env.BackendDataPath}}/{{.Coin.Alias}}/backend -conf={{.Env.BackendInstallPath}}/{{.Coin.Alias}}/{{.Coin.Alias}}.conf -pid=/run/{{.Coin.Alias}}/{{.Coin.Alias}}.pid",
|
||||||
"logrotate_files_template": "{{.Env.BackendDataPath}}/{{.Coin.Alias}}/backend/*.log",
|
"logrotate_files_template": "{{.Env.BackendDataPath}}/{{.Coin.Alias}}/backend/*.log",
|
||||||
@ -40,7 +40,7 @@
|
|||||||
"server_config_file": "bitcoin_like.conf",
|
"server_config_file": "bitcoin_like.conf",
|
||||||
"client_config_file": "bitcoin_like_client.conf",
|
"client_config_file": "bitcoin_like_client.conf",
|
||||||
"additional_params": {
|
"additional_params": {
|
||||||
"mempoolexpiry": 72
|
"mempoolexpiry": 72
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"blockbook": {
|
"blockbook": {
|
||||||
@ -56,6 +56,8 @@
|
|||||||
"mempool_workers": 8,
|
"mempool_workers": 8,
|
||||||
"mempool_sub_workers": 2,
|
"mempool_sub_workers": 2,
|
||||||
"block_addresses_to_keep": 300,
|
"block_addresses_to_keep": 300,
|
||||||
|
"xpub_magic": 50221772,
|
||||||
|
"slip44": 5,
|
||||||
"additional_params": {}
|
"additional_params": {}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|||||||
@ -1,9 +1,9 @@
|
|||||||
{
|
{
|
||||||
"coin": {
|
"coin": {
|
||||||
"name": "Dash Testnet",
|
"name": "Dash Testnet",
|
||||||
"shortcut": "tDASH",
|
"shortcut": "tDASH",
|
||||||
"label": "Dash Testnet",
|
"label": "Dash Testnet",
|
||||||
"alias": "dash_testnet"
|
"alias": "dash_testnet"
|
||||||
},
|
},
|
||||||
"ports": {
|
"ports": {
|
||||||
"backend_rpc": 18033,
|
"backend_rpc": 18033,
|
||||||
@ -28,7 +28,7 @@
|
|||||||
"verification_source": "https://github.com/dashpay/dash/releases/download/v0.13.1.0/SHA256SUMS.asc",
|
"verification_source": "https://github.com/dashpay/dash/releases/download/v0.13.1.0/SHA256SUMS.asc",
|
||||||
"extract_command": "tar -C backend --strip 1 -xf",
|
"extract_command": "tar -C backend --strip 1 -xf",
|
||||||
"exclude_files": [
|
"exclude_files": [
|
||||||
"bin/dash-qt"
|
"bin/dash-qt"
|
||||||
],
|
],
|
||||||
"exec_command_template": "{{.Env.BackendInstallPath}}/{{.Coin.Alias}}/bin/dashd -datadir={{.Env.BackendDataPath}}/{{.Coin.Alias}}/backend -conf={{.Env.BackendInstallPath}}/{{.Coin.Alias}}/{{.Coin.Alias}}.conf -pid=/run/{{.Coin.Alias}}/{{.Coin.Alias}}.pid",
|
"exec_command_template": "{{.Env.BackendInstallPath}}/{{.Coin.Alias}}/bin/dashd -datadir={{.Env.BackendDataPath}}/{{.Coin.Alias}}/backend -conf={{.Env.BackendInstallPath}}/{{.Coin.Alias}}/{{.Coin.Alias}}.conf -pid=/run/{{.Coin.Alias}}/{{.Coin.Alias}}.pid",
|
||||||
"logrotate_files_template": "{{.Env.BackendDataPath}}/{{.Coin.Alias}}/backend/testnet3/*.log",
|
"logrotate_files_template": "{{.Env.BackendDataPath}}/{{.Coin.Alias}}/backend/testnet3/*.log",
|
||||||
@ -40,7 +40,7 @@
|
|||||||
"server_config_file": "bitcoin_like.conf",
|
"server_config_file": "bitcoin_like.conf",
|
||||||
"client_config_file": "bitcoin_like_client.conf",
|
"client_config_file": "bitcoin_like_client.conf",
|
||||||
"additional_params": {
|
"additional_params": {
|
||||||
"mempoolexpiry": 72
|
"mempoolexpiry": 72
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"blockbook": {
|
"blockbook": {
|
||||||
@ -56,6 +56,8 @@
|
|||||||
"mempool_workers": 8,
|
"mempool_workers": 8,
|
||||||
"mempool_sub_workers": 2,
|
"mempool_sub_workers": 2,
|
||||||
"block_addresses_to_keep": 300,
|
"block_addresses_to_keep": 300,
|
||||||
|
"xpub_magic": 70617039,
|
||||||
|
"slip44": 1,
|
||||||
"additional_params": {}
|
"additional_params": {}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|||||||
@ -1,9 +1,9 @@
|
|||||||
{
|
{
|
||||||
"coin": {
|
"coin": {
|
||||||
"name": "DigiByte",
|
"name": "DigiByte",
|
||||||
"shortcut": "DGB",
|
"shortcut": "DGB",
|
||||||
"label": "DigiByte",
|
"label": "DigiByte",
|
||||||
"alias": "digibyte"
|
"alias": "digibyte"
|
||||||
},
|
},
|
||||||
"ports": {
|
"ports": {
|
||||||
"backend_rpc": 8042,
|
"backend_rpc": 8042,
|
||||||
@ -28,7 +28,7 @@
|
|||||||
"verification_source": "dd6bed0228087fbb51f08be55cbc08a0e3251acfe1be3249b634447837ecd857",
|
"verification_source": "dd6bed0228087fbb51f08be55cbc08a0e3251acfe1be3249b634447837ecd857",
|
||||||
"extract_command": "tar -C backend --strip 1 -xf",
|
"extract_command": "tar -C backend --strip 1 -xf",
|
||||||
"exclude_files": [
|
"exclude_files": [
|
||||||
"bin/digibyte-qt"
|
"bin/digibyte-qt"
|
||||||
],
|
],
|
||||||
"exec_command_template": "{{.Env.BackendInstallPath}}/{{.Coin.Alias}}/bin/digibyted -datadir={{.Env.BackendDataPath}}/{{.Coin.Alias}}/backend -conf={{.Env.BackendInstallPath}}/{{.Coin.Alias}}/{{.Coin.Alias}}.conf -pid=/run/{{.Coin.Alias}}/{{.Coin.Alias}}.pid",
|
"exec_command_template": "{{.Env.BackendInstallPath}}/{{.Coin.Alias}}/bin/digibyted -datadir={{.Env.BackendDataPath}}/{{.Coin.Alias}}/backend -conf={{.Env.BackendInstallPath}}/{{.Coin.Alias}}/{{.Coin.Alias}}.conf -pid=/run/{{.Coin.Alias}}/{{.Coin.Alias}}.pid",
|
||||||
"logrotate_files_template": "{{.Env.BackendDataPath}}/{{.Coin.Alias}}/backend/*.log",
|
"logrotate_files_template": "{{.Env.BackendDataPath}}/{{.Coin.Alias}}/backend/*.log",
|
||||||
@ -40,7 +40,7 @@
|
|||||||
"server_config_file": "bitcoin_like.conf",
|
"server_config_file": "bitcoin_like.conf",
|
||||||
"client_config_file": "bitcoin_like_client.conf",
|
"client_config_file": "bitcoin_like_client.conf",
|
||||||
"additional_params": {
|
"additional_params": {
|
||||||
"whitelist": "127.0.0.1"
|
"whitelist": "127.0.0.1"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"blockbook": {
|
"blockbook": {
|
||||||
@ -55,6 +55,9 @@
|
|||||||
"mempool_workers": 8,
|
"mempool_workers": 8,
|
||||||
"mempool_sub_workers": 2,
|
"mempool_sub_workers": 2,
|
||||||
"block_addresses_to_keep": 300,
|
"block_addresses_to_keep": 300,
|
||||||
|
"xpub_magic": 76067358,
|
||||||
|
"xpub_magic_segwit_p2sh": 77429938,
|
||||||
|
"slip44": 20,
|
||||||
"additional_params": {}
|
"additional_params": {}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|||||||
@ -1,9 +1,9 @@
|
|||||||
{
|
{
|
||||||
"coin": {
|
"coin": {
|
||||||
"name": "Dogecoin",
|
"name": "Dogecoin",
|
||||||
"shortcut": "DOGE",
|
"shortcut": "DOGE",
|
||||||
"label": "Dogecoin",
|
"label": "Dogecoin",
|
||||||
"alias": "dogecoin"
|
"alias": "dogecoin"
|
||||||
},
|
},
|
||||||
"ports": {
|
"ports": {
|
||||||
"backend_rpc": 8038,
|
"backend_rpc": 8038,
|
||||||
@ -28,7 +28,7 @@
|
|||||||
"verification_source": "09871d8ff2ab5e0f05df2bdf5eba64c178229d030dd7c8473b08e6ed45d3327f",
|
"verification_source": "09871d8ff2ab5e0f05df2bdf5eba64c178229d030dd7c8473b08e6ed45d3327f",
|
||||||
"extract_command": "tar -C backend --strip 1 -xf",
|
"extract_command": "tar -C backend --strip 1 -xf",
|
||||||
"exclude_files": [
|
"exclude_files": [
|
||||||
"bin/dogecoin-qt"
|
"bin/dogecoin-qt"
|
||||||
],
|
],
|
||||||
"exec_command_template": "{{.Env.BackendInstallPath}}/{{.Coin.Alias}}/bin/dogecoind -datadir={{.Env.BackendDataPath}}/{{.Coin.Alias}}/backend -conf={{.Env.BackendInstallPath}}/{{.Coin.Alias}}/{{.Coin.Alias}}.conf -pid=/run/{{.Coin.Alias}}/{{.Coin.Alias}}.pid",
|
"exec_command_template": "{{.Env.BackendInstallPath}}/{{.Coin.Alias}}/bin/dogecoind -datadir={{.Env.BackendDataPath}}/{{.Coin.Alias}}/backend -conf={{.Env.BackendInstallPath}}/{{.Coin.Alias}}/{{.Coin.Alias}}.conf -pid=/run/{{.Coin.Alias}}/{{.Coin.Alias}}.pid",
|
||||||
"logrotate_files_template": "{{.Env.BackendDataPath}}/{{.Coin.Alias}}/backend/*.log",
|
"logrotate_files_template": "{{.Env.BackendDataPath}}/{{.Coin.Alias}}/backend/*.log",
|
||||||
@ -40,10 +40,10 @@
|
|||||||
"server_config_file": "bitcoin_like.conf",
|
"server_config_file": "bitcoin_like.conf",
|
||||||
"client_config_file": "bitcoin_like_client.conf",
|
"client_config_file": "bitcoin_like_client.conf",
|
||||||
"additional_params": {
|
"additional_params": {
|
||||||
"whitelist": "127.0.0.1",
|
"discover": 0,
|
||||||
"rpcthreads": 16,
|
"rpcthreads": 16,
|
||||||
"upnp": 0,
|
"upnp": 0,
|
||||||
"discover": 0
|
"whitelist": "127.0.0.1"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"blockbook": {
|
"blockbook": {
|
||||||
@ -58,6 +58,8 @@
|
|||||||
"mempool_workers": 8,
|
"mempool_workers": 8,
|
||||||
"mempool_sub_workers": 2,
|
"mempool_sub_workers": 2,
|
||||||
"block_addresses_to_keep": 300,
|
"block_addresses_to_keep": 300,
|
||||||
|
"xpub_magic": 49990397,
|
||||||
|
"slip44": 3,
|
||||||
"additional_params": {}
|
"additional_params": {}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|||||||
@ -1,9 +1,9 @@
|
|||||||
{
|
{
|
||||||
"coin": {
|
"coin": {
|
||||||
"name": "Fujicoin",
|
"name": "Fujicoin",
|
||||||
"shortcut": "FJC",
|
"shortcut": "FJC",
|
||||||
"label": "Fujicoin",
|
"label": "Fujicoin",
|
||||||
"alias": "fujicoin"
|
"alias": "fujicoin"
|
||||||
},
|
},
|
||||||
"ports": {
|
"ports": {
|
||||||
"backend_rpc": 8048,
|
"backend_rpc": 8048,
|
||||||
@ -27,8 +27,7 @@
|
|||||||
"verification_type": "gpg-sha256",
|
"verification_type": "gpg-sha256",
|
||||||
"verification_source": "https://www.fujicoin.org/fujicoin/3.0/SHA256SUMS.asc",
|
"verification_source": "https://www.fujicoin.org/fujicoin/3.0/SHA256SUMS.asc",
|
||||||
"extract_command": "tar -C backend -xf",
|
"extract_command": "tar -C backend -xf",
|
||||||
"exclude_files": [
|
"exclude_files": [],
|
||||||
],
|
|
||||||
"exec_command_template": "{{.Env.BackendInstallPath}}/{{.Coin.Alias}}/fujicoind -datadir={{.Env.BackendDataPath}}/{{.Coin.Alias}}/backend -conf={{.Env.BackendInstallPath}}/{{.Coin.Alias}}/{{.Coin.Alias}}.conf -pid=/run/{{.Coin.Alias}}/{{.Coin.Alias}}.pid",
|
"exec_command_template": "{{.Env.BackendInstallPath}}/{{.Coin.Alias}}/fujicoind -datadir={{.Env.BackendDataPath}}/{{.Coin.Alias}}/backend -conf={{.Env.BackendInstallPath}}/{{.Coin.Alias}}/{{.Coin.Alias}}.conf -pid=/run/{{.Coin.Alias}}/{{.Coin.Alias}}.pid",
|
||||||
"logrotate_files_template": "{{.Env.BackendDataPath}}/{{.Coin.Alias}}/backend/*.log",
|
"logrotate_files_template": "{{.Env.BackendDataPath}}/{{.Coin.Alias}}/backend/*.log",
|
||||||
"postinst_script_template": "",
|
"postinst_script_template": "",
|
||||||
@ -38,8 +37,7 @@
|
|||||||
"mainnet": true,
|
"mainnet": true,
|
||||||
"server_config_file": "bitcoin_like.conf",
|
"server_config_file": "bitcoin_like.conf",
|
||||||
"client_config_file": "bitcoin_like_client.conf",
|
"client_config_file": "bitcoin_like_client.conf",
|
||||||
"additional_params": {
|
"additional_params": {}
|
||||||
}
|
|
||||||
},
|
},
|
||||||
"blockbook": {
|
"blockbook": {
|
||||||
"package_name": "blockbook-fujicoin",
|
"package_name": "blockbook-fujicoin",
|
||||||
@ -53,6 +51,10 @@
|
|||||||
"mempool_workers": 8,
|
"mempool_workers": 8,
|
||||||
"mempool_sub_workers": 2,
|
"mempool_sub_workers": 2,
|
||||||
"block_addresses_to_keep": 300,
|
"block_addresses_to_keep": 300,
|
||||||
|
"xpub_magic": 76067358,
|
||||||
|
"xpub_magic_segwit_p2sh": 77429938,
|
||||||
|
"xpub_magic_segwit_native": 78792518,
|
||||||
|
"slip44": 75,
|
||||||
"additional_params": {}
|
"additional_params": {}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|||||||
@ -1,9 +1,9 @@
|
|||||||
{
|
{
|
||||||
"coin": {
|
"coin": {
|
||||||
"name": "GameCredits",
|
"name": "GameCredits",
|
||||||
"shortcut": "GAME",
|
"shortcut": "GAME",
|
||||||
"label": "GameCredits",
|
"label": "GameCredits",
|
||||||
"alias": "gamecredits"
|
"alias": "gamecredits"
|
||||||
},
|
},
|
||||||
"ports": {
|
"ports": {
|
||||||
"backend_rpc": 8044,
|
"backend_rpc": 8044,
|
||||||
@ -28,7 +28,7 @@
|
|||||||
"verification_source": "38531ea877dfc1cedd3125bb79216a587f0974f20bee6243efcde61d05e07e5c",
|
"verification_source": "38531ea877dfc1cedd3125bb79216a587f0974f20bee6243efcde61d05e07e5c",
|
||||||
"extract_command": "tar -C backend --strip 1 -xf",
|
"extract_command": "tar -C backend --strip 1 -xf",
|
||||||
"exclude_files": [
|
"exclude_files": [
|
||||||
"bin/gamecredits-qt"
|
"bin/gamecredits-qt"
|
||||||
],
|
],
|
||||||
"exec_command_template": "{{.Env.BackendInstallPath}}/{{.Coin.Alias}}/bin/gamecreditsd -datadir={{.Env.BackendDataPath}}/{{.Coin.Alias}}/backend -conf={{.Env.BackendInstallPath}}/{{.Coin.Alias}}/{{.Coin.Alias}}.conf -pid=/run/{{.Coin.Alias}}/{{.Coin.Alias}}.pid",
|
"exec_command_template": "{{.Env.BackendInstallPath}}/{{.Coin.Alias}}/bin/gamecreditsd -datadir={{.Env.BackendDataPath}}/{{.Coin.Alias}}/backend -conf={{.Env.BackendInstallPath}}/{{.Coin.Alias}}/{{.Coin.Alias}}.conf -pid=/run/{{.Coin.Alias}}/{{.Coin.Alias}}.pid",
|
||||||
"logrotate_files_template": "{{.Env.BackendDataPath}}/{{.Coin.Alias}}/backend/*.log",
|
"logrotate_files_template": "{{.Env.BackendDataPath}}/{{.Coin.Alias}}/backend/*.log",
|
||||||
@ -40,7 +40,7 @@
|
|||||||
"server_config_file": "bitcoin_like.conf",
|
"server_config_file": "bitcoin_like.conf",
|
||||||
"client_config_file": "bitcoin_like_client.conf",
|
"client_config_file": "bitcoin_like_client.conf",
|
||||||
"additional_params": {
|
"additional_params": {
|
||||||
"whitelist": "127.0.0.1"
|
"whitelist": "127.0.0.1"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"blockbook": {
|
"blockbook": {
|
||||||
@ -55,6 +55,9 @@
|
|||||||
"mempool_workers": 8,
|
"mempool_workers": 8,
|
||||||
"mempool_sub_workers": 2,
|
"mempool_sub_workers": 2,
|
||||||
"block_addresses_to_keep": 300,
|
"block_addresses_to_keep": 300,
|
||||||
|
"xpub_magic": 27106558,
|
||||||
|
"xpub_magic_segwit_p2sh": 28471030,
|
||||||
|
"slip44": 101,
|
||||||
"additional_params": {}
|
"additional_params": {}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|||||||
@ -1,9 +1,9 @@
|
|||||||
{
|
{
|
||||||
"coin": {
|
"coin": {
|
||||||
"name": "Groestlcoin",
|
"name": "Groestlcoin",
|
||||||
"shortcut": "GRS",
|
"shortcut": "GRS",
|
||||||
"label": "Groestlcoin",
|
"label": "Groestlcoin",
|
||||||
"alias": "groestlcoin"
|
"alias": "groestlcoin"
|
||||||
},
|
},
|
||||||
"ports": {
|
"ports": {
|
||||||
"backend_rpc": 8045,
|
"backend_rpc": 8045,
|
||||||
@ -28,7 +28,7 @@
|
|||||||
"verification_source": "https://github.com/Groestlcoin/groestlcoin/releases/download/v2.17.2/SHA256SUMS.asc",
|
"verification_source": "https://github.com/Groestlcoin/groestlcoin/releases/download/v2.17.2/SHA256SUMS.asc",
|
||||||
"extract_command": "tar -C backend --strip 1 -xf",
|
"extract_command": "tar -C backend --strip 1 -xf",
|
||||||
"exclude_files": [
|
"exclude_files": [
|
||||||
"bin/groestlcoin-qt"
|
"bin/groestlcoin-qt"
|
||||||
],
|
],
|
||||||
"exec_command_template": "{{.Env.BackendInstallPath}}/{{.Coin.Alias}}/bin/groestlcoind -datadir={{.Env.BackendDataPath}}/{{.Coin.Alias}}/backend -conf={{.Env.BackendInstallPath}}/{{.Coin.Alias}}/{{.Coin.Alias}}.conf -pid=/run/{{.Coin.Alias}}/{{.Coin.Alias}}.pid",
|
"exec_command_template": "{{.Env.BackendInstallPath}}/{{.Coin.Alias}}/bin/groestlcoind -datadir={{.Env.BackendDataPath}}/{{.Coin.Alias}}/backend -conf={{.Env.BackendInstallPath}}/{{.Coin.Alias}}/{{.Coin.Alias}}.conf -pid=/run/{{.Coin.Alias}}/{{.Coin.Alias}}.pid",
|
||||||
"logrotate_files_template": "{{.Env.BackendDataPath}}/{{.Coin.Alias}}/backend/*.log",
|
"logrotate_files_template": "{{.Env.BackendDataPath}}/{{.Coin.Alias}}/backend/*.log",
|
||||||
@ -40,8 +40,8 @@
|
|||||||
"server_config_file": "bitcoin_like.conf",
|
"server_config_file": "bitcoin_like.conf",
|
||||||
"client_config_file": "bitcoin_like_client.conf",
|
"client_config_file": "bitcoin_like_client.conf",
|
||||||
"additional_params": {
|
"additional_params": {
|
||||||
"deprecatedrpc": "estimatefee",
|
"deprecatedrpc": "estimatefee",
|
||||||
"whitelist": "127.0.0.1"
|
"whitelist": "127.0.0.1"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"blockbook": {
|
"blockbook": {
|
||||||
@ -56,6 +56,10 @@
|
|||||||
"mempool_workers": 8,
|
"mempool_workers": 8,
|
||||||
"mempool_sub_workers": 2,
|
"mempool_sub_workers": 2,
|
||||||
"block_addresses_to_keep": 300,
|
"block_addresses_to_keep": 300,
|
||||||
|
"xpub_magic": 76067358,
|
||||||
|
"xpub_magic_segwit_p2sh": 77429938,
|
||||||
|
"xpub_magic_segwit_native": 78792518,
|
||||||
|
"slip44": 17,
|
||||||
"additional_params": {}
|
"additional_params": {}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|||||||
@ -1,9 +1,9 @@
|
|||||||
{
|
{
|
||||||
"coin": {
|
"coin": {
|
||||||
"name": "Groestlcoin Testnet",
|
"name": "Groestlcoin Testnet",
|
||||||
"shortcut": "tGRS",
|
"shortcut": "tGRS",
|
||||||
"label": "Groestlcoin Testnet",
|
"label": "Groestlcoin Testnet",
|
||||||
"alias": "groestlcoin_testnet"
|
"alias": "groestlcoin_testnet"
|
||||||
},
|
},
|
||||||
"ports": {
|
"ports": {
|
||||||
"backend_rpc": 18045,
|
"backend_rpc": 18045,
|
||||||
@ -28,7 +28,7 @@
|
|||||||
"verification_source": "https://github.com/Groestlcoin/groestlcoin/releases/download/v2.17.2/SHA256SUMS.asc",
|
"verification_source": "https://github.com/Groestlcoin/groestlcoin/releases/download/v2.17.2/SHA256SUMS.asc",
|
||||||
"extract_command": "tar -C backend --strip 1 -xf",
|
"extract_command": "tar -C backend --strip 1 -xf",
|
||||||
"exclude_files": [
|
"exclude_files": [
|
||||||
"bin/groestlcoin-qt"
|
"bin/groestlcoin-qt"
|
||||||
],
|
],
|
||||||
"exec_command_template": "{{.Env.BackendInstallPath}}/{{.Coin.Alias}}/bin/groestlcoind -datadir={{.Env.BackendDataPath}}/{{.Coin.Alias}}/backend -conf={{.Env.BackendInstallPath}}/{{.Coin.Alias}}/{{.Coin.Alias}}.conf -pid=/run/{{.Coin.Alias}}/{{.Coin.Alias}}.pid",
|
"exec_command_template": "{{.Env.BackendInstallPath}}/{{.Coin.Alias}}/bin/groestlcoind -datadir={{.Env.BackendDataPath}}/{{.Coin.Alias}}/backend -conf={{.Env.BackendInstallPath}}/{{.Coin.Alias}}/{{.Coin.Alias}}.conf -pid=/run/{{.Coin.Alias}}/{{.Coin.Alias}}.pid",
|
||||||
"logrotate_files_template": "{{.Env.BackendDataPath}}/{{.Coin.Alias}}/backend/testnet3/*.log",
|
"logrotate_files_template": "{{.Env.BackendDataPath}}/{{.Coin.Alias}}/backend/testnet3/*.log",
|
||||||
@ -40,8 +40,8 @@
|
|||||||
"server_config_file": "bitcoin_like.conf",
|
"server_config_file": "bitcoin_like.conf",
|
||||||
"client_config_file": "bitcoin_like_client.conf",
|
"client_config_file": "bitcoin_like_client.conf",
|
||||||
"additional_params": {
|
"additional_params": {
|
||||||
"deprecatedrpc": "estimatefee",
|
"deprecatedrpc": "estimatefee",
|
||||||
"whitelist": "127.0.0.1"
|
"whitelist": "127.0.0.1"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"blockbook": {
|
"blockbook": {
|
||||||
@ -56,6 +56,10 @@
|
|||||||
"mempool_workers": 8,
|
"mempool_workers": 8,
|
||||||
"mempool_sub_workers": 2,
|
"mempool_sub_workers": 2,
|
||||||
"block_addresses_to_keep": 300,
|
"block_addresses_to_keep": 300,
|
||||||
|
"xpub_magic": 70617039,
|
||||||
|
"xpub_magic_segwit_p2sh": 71979618,
|
||||||
|
"xpub_magic_segwit_native": 73342198,
|
||||||
|
"slip44": 1,
|
||||||
"additional_params": {}
|
"additional_params": {}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|||||||
@ -1,9 +1,9 @@
|
|||||||
{
|
{
|
||||||
"coin": {
|
"coin": {
|
||||||
"name": "Koto",
|
"name": "Koto",
|
||||||
"shortcut": "KOTO",
|
"shortcut": "KOTO",
|
||||||
"label": "Koto",
|
"label": "Koto",
|
||||||
"alias": "koto"
|
"alias": "koto"
|
||||||
},
|
},
|
||||||
"ports": {
|
"ports": {
|
||||||
"backend_rpc": 8051,
|
"backend_rpc": 8051,
|
||||||
@ -28,7 +28,7 @@
|
|||||||
"verification_source": "https://github.com/KotoDevelopers/koto/releases/download/v2.0.3/koto-2.0.3-linux64.tar.gz.asc",
|
"verification_source": "https://github.com/KotoDevelopers/koto/releases/download/v2.0.3/koto-2.0.3-linux64.tar.gz.asc",
|
||||||
"extract_command": "tar -C backend --strip 1 -xf",
|
"extract_command": "tar -C backend --strip 1 -xf",
|
||||||
"exclude_files": [
|
"exclude_files": [
|
||||||
"bin/koto-qt"
|
"bin/koto-qt"
|
||||||
],
|
],
|
||||||
"exec_command_template": "{{.Env.BackendInstallPath}}/{{.Coin.Alias}}/bin/kotod -datadir={{.Env.BackendDataPath}}/{{.Coin.Alias}}/backend -conf={{.Env.BackendInstallPath}}/{{.Coin.Alias}}/{{.Coin.Alias}}.conf -pid=/run/{{.Coin.Alias}}/{{.Coin.Alias}}.pid",
|
"exec_command_template": "{{.Env.BackendInstallPath}}/{{.Coin.Alias}}/bin/kotod -datadir={{.Env.BackendDataPath}}/{{.Coin.Alias}}/backend -conf={{.Env.BackendInstallPath}}/{{.Coin.Alias}}/{{.Coin.Alias}}.conf -pid=/run/{{.Coin.Alias}}/{{.Coin.Alias}}.pid",
|
||||||
"logrotate_files_template": "{{.Env.BackendDataPath}}/{{.Coin.Alias}}/backend/*.log",
|
"logrotate_files_template": "{{.Env.BackendDataPath}}/{{.Coin.Alias}}/backend/*.log",
|
||||||
@ -40,9 +40,9 @@
|
|||||||
"server_config_file": "bitcoin_like.conf",
|
"server_config_file": "bitcoin_like.conf",
|
||||||
"client_config_file": "bitcoin_like_client.conf",
|
"client_config_file": "bitcoin_like_client.conf",
|
||||||
"additional_params": {
|
"additional_params": {
|
||||||
"addnode": [
|
"addnode": [
|
||||||
"dnsseed.ko-to.org"
|
"dnsseed.ko-to.org"
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"blockbook": {
|
"blockbook": {
|
||||||
@ -57,6 +57,8 @@
|
|||||||
"mempool_workers": 4,
|
"mempool_workers": 4,
|
||||||
"mempool_sub_workers": 8,
|
"mempool_sub_workers": 8,
|
||||||
"block_addresses_to_keep": 300,
|
"block_addresses_to_keep": 300,
|
||||||
|
"xpub_magic": 76067358,
|
||||||
|
"slip44": 510,
|
||||||
"additional_params": {}
|
"additional_params": {}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|||||||
@ -1,9 +1,9 @@
|
|||||||
{
|
{
|
||||||
"coin": {
|
"coin": {
|
||||||
"name": "Litecoin",
|
"name": "Litecoin",
|
||||||
"shortcut": "LTC",
|
"shortcut": "LTC",
|
||||||
"label": "Litecoin",
|
"label": "Litecoin",
|
||||||
"alias": "litecoin"
|
"alias": "litecoin"
|
||||||
},
|
},
|
||||||
"ports": {
|
"ports": {
|
||||||
"backend_rpc": 8034,
|
"backend_rpc": 8034,
|
||||||
@ -28,7 +28,7 @@
|
|||||||
"verification_source": "https://download.litecoin.org/litecoin-0.16.3/linux/litecoin-0.16.3-linux-signatures.asc",
|
"verification_source": "https://download.litecoin.org/litecoin-0.16.3/linux/litecoin-0.16.3-linux-signatures.asc",
|
||||||
"extract_command": "tar -C backend --strip 1 -xf",
|
"extract_command": "tar -C backend --strip 1 -xf",
|
||||||
"exclude_files": [
|
"exclude_files": [
|
||||||
"bin/litecoin-qt"
|
"bin/litecoin-qt"
|
||||||
],
|
],
|
||||||
"exec_command_template": "{{.Env.BackendInstallPath}}/{{.Coin.Alias}}/bin/litecoind -datadir={{.Env.BackendDataPath}}/{{.Coin.Alias}}/backend -conf={{.Env.BackendInstallPath}}/{{.Coin.Alias}}/{{.Coin.Alias}}.conf -pid=/run/{{.Coin.Alias}}/{{.Coin.Alias}}.pid",
|
"exec_command_template": "{{.Env.BackendInstallPath}}/{{.Coin.Alias}}/bin/litecoind -datadir={{.Env.BackendDataPath}}/{{.Coin.Alias}}/backend -conf={{.Env.BackendInstallPath}}/{{.Coin.Alias}}/{{.Coin.Alias}}.conf -pid=/run/{{.Coin.Alias}}/{{.Coin.Alias}}.pid",
|
||||||
"logrotate_files_template": "{{.Env.BackendDataPath}}/{{.Coin.Alias}}/backend/*.log",
|
"logrotate_files_template": "{{.Env.BackendDataPath}}/{{.Coin.Alias}}/backend/*.log",
|
||||||
@ -40,7 +40,7 @@
|
|||||||
"server_config_file": "bitcoin_like.conf",
|
"server_config_file": "bitcoin_like.conf",
|
||||||
"client_config_file": "bitcoin_like_client.conf",
|
"client_config_file": "bitcoin_like_client.conf",
|
||||||
"additional_params": {
|
"additional_params": {
|
||||||
"whitelist": "127.0.0.1"
|
"whitelist": "127.0.0.1"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"blockbook": {
|
"blockbook": {
|
||||||
@ -55,6 +55,9 @@
|
|||||||
"mempool_workers": 8,
|
"mempool_workers": 8,
|
||||||
"mempool_sub_workers": 2,
|
"mempool_sub_workers": 2,
|
||||||
"block_addresses_to_keep": 300,
|
"block_addresses_to_keep": 300,
|
||||||
|
"xpub_magic": 27108450,
|
||||||
|
"xpub_magic_segwit_p2sh": 28471030,
|
||||||
|
"slip44": 2,
|
||||||
"additional_params": {}
|
"additional_params": {}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|||||||
@ -1,9 +1,9 @@
|
|||||||
{
|
{
|
||||||
"coin": {
|
"coin": {
|
||||||
"name": "Litecoin Testnet",
|
"name": "Litecoin Testnet",
|
||||||
"shortcut": "TLTC",
|
"shortcut": "TLTC",
|
||||||
"label": "Litecoin Testnet",
|
"label": "Litecoin Testnet",
|
||||||
"alias": "litecoin_testnet"
|
"alias": "litecoin_testnet"
|
||||||
},
|
},
|
||||||
"ports": {
|
"ports": {
|
||||||
"backend_rpc": 18034,
|
"backend_rpc": 18034,
|
||||||
@ -28,7 +28,7 @@
|
|||||||
"verification_source": "https://download.litecoin.org/litecoin-0.16.3/linux/litecoin-0.16.3-linux-signatures.asc",
|
"verification_source": "https://download.litecoin.org/litecoin-0.16.3/linux/litecoin-0.16.3-linux-signatures.asc",
|
||||||
"extract_command": "tar -C backend --strip 1 -xf",
|
"extract_command": "tar -C backend --strip 1 -xf",
|
||||||
"exclude_files": [
|
"exclude_files": [
|
||||||
"bin/litecoin-qt"
|
"bin/litecoin-qt"
|
||||||
],
|
],
|
||||||
"exec_command_template": "{{.Env.BackendInstallPath}}/{{.Coin.Alias}}/bin/litecoind -datadir={{.Env.BackendDataPath}}/{{.Coin.Alias}}/backend -conf={{.Env.BackendInstallPath}}/{{.Coin.Alias}}/{{.Coin.Alias}}.conf -pid=/run/{{.Coin.Alias}}/{{.Coin.Alias}}.pid",
|
"exec_command_template": "{{.Env.BackendInstallPath}}/{{.Coin.Alias}}/bin/litecoind -datadir={{.Env.BackendDataPath}}/{{.Coin.Alias}}/backend -conf={{.Env.BackendInstallPath}}/{{.Coin.Alias}}/{{.Coin.Alias}}.conf -pid=/run/{{.Coin.Alias}}/{{.Coin.Alias}}.pid",
|
||||||
"logrotate_files_template": "{{.Env.BackendDataPath}}/{{.Coin.Alias}}/backend/testnet4/*.log",
|
"logrotate_files_template": "{{.Env.BackendDataPath}}/{{.Coin.Alias}}/backend/testnet4/*.log",
|
||||||
@ -40,7 +40,7 @@
|
|||||||
"server_config_file": "bitcoin_like.conf",
|
"server_config_file": "bitcoin_like.conf",
|
||||||
"client_config_file": "bitcoin_like_client.conf",
|
"client_config_file": "bitcoin_like_client.conf",
|
||||||
"additional_params": {
|
"additional_params": {
|
||||||
"whitelist": "127.0.0.1"
|
"whitelist": "127.0.0.1"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"blockbook": {
|
"blockbook": {
|
||||||
@ -55,6 +55,8 @@
|
|||||||
"mempool_workers": 8,
|
"mempool_workers": 8,
|
||||||
"mempool_sub_workers": 2,
|
"mempool_sub_workers": 2,
|
||||||
"block_addresses_to_keep": 300,
|
"block_addresses_to_keep": 300,
|
||||||
|
"xpub_magic": 70617039,
|
||||||
|
"slip44": 1,
|
||||||
"additional_params": {}
|
"additional_params": {}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|||||||
@ -1,9 +1,9 @@
|
|||||||
{
|
{
|
||||||
"coin": {
|
"coin": {
|
||||||
"name": "Monacoin",
|
"name": "Monacoin",
|
||||||
"shortcut": "MONA",
|
"shortcut": "MONA",
|
||||||
"label": "Monacoin",
|
"label": "Monacoin",
|
||||||
"alias": "monacoin"
|
"alias": "monacoin"
|
||||||
},
|
},
|
||||||
"ports": {
|
"ports": {
|
||||||
"backend_rpc": 8041,
|
"backend_rpc": 8041,
|
||||||
@ -28,7 +28,7 @@
|
|||||||
"verification_source": "https://github.com/monacoinproject/monacoin/releases/download/monacoin-0.16.3/monacoin-0.16.3-signatures.asc",
|
"verification_source": "https://github.com/monacoinproject/monacoin/releases/download/monacoin-0.16.3/monacoin-0.16.3-signatures.asc",
|
||||||
"extract_command": "tar -C backend --strip 1 -xf",
|
"extract_command": "tar -C backend --strip 1 -xf",
|
||||||
"exclude_files": [
|
"exclude_files": [
|
||||||
"bin/monacoin-qt"
|
"bin/monacoin-qt"
|
||||||
],
|
],
|
||||||
"exec_command_template": "{{.Env.BackendInstallPath}}/{{.Coin.Alias}}/bin/monacoind -datadir={{.Env.BackendDataPath}}/{{.Coin.Alias}}/backend -conf={{.Env.BackendInstallPath}}/{{.Coin.Alias}}/{{.Coin.Alias}}.conf -pid=/run/{{.Coin.Alias}}/{{.Coin.Alias}}.pid",
|
"exec_command_template": "{{.Env.BackendInstallPath}}/{{.Coin.Alias}}/bin/monacoind -datadir={{.Env.BackendDataPath}}/{{.Coin.Alias}}/backend -conf={{.Env.BackendInstallPath}}/{{.Coin.Alias}}/{{.Coin.Alias}}.conf -pid=/run/{{.Coin.Alias}}/{{.Coin.Alias}}.pid",
|
||||||
"logrotate_files_template": "{{.Env.BackendDataPath}}/{{.Coin.Alias}}/backend/*.log",
|
"logrotate_files_template": "{{.Env.BackendDataPath}}/{{.Coin.Alias}}/backend/*.log",
|
||||||
@ -40,7 +40,7 @@
|
|||||||
"server_config_file": "bitcoin_like.conf",
|
"server_config_file": "bitcoin_like.conf",
|
||||||
"client_config_file": "bitcoin_like_client.conf",
|
"client_config_file": "bitcoin_like_client.conf",
|
||||||
"additional_params": {
|
"additional_params": {
|
||||||
"whitelist": "127.0.0.1"
|
"whitelist": "127.0.0.1"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"blockbook": {
|
"blockbook": {
|
||||||
@ -55,6 +55,9 @@
|
|||||||
"mempool_workers": 8,
|
"mempool_workers": 8,
|
||||||
"mempool_sub_workers": 2,
|
"mempool_sub_workers": 2,
|
||||||
"block_addresses_to_keep": 300,
|
"block_addresses_to_keep": 300,
|
||||||
|
"xpub_magic": 76067358,
|
||||||
|
"xpub_magic_segwit_p2sh": 77429938,
|
||||||
|
"slip44": 22,
|
||||||
"additional_params": {}
|
"additional_params": {}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|||||||
@ -53,6 +53,8 @@
|
|||||||
"mempool_workers": 8,
|
"mempool_workers": 8,
|
||||||
"mempool_sub_workers": 2,
|
"mempool_sub_workers": 2,
|
||||||
"block_addresses_to_keep": 300,
|
"block_addresses_to_keep": 300,
|
||||||
|
"xpub_magic": 76067358,
|
||||||
|
"slip44": 90,
|
||||||
"additional_params": {}
|
"additional_params": {}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|||||||
@ -1,9 +1,9 @@
|
|||||||
{
|
{
|
||||||
"coin": {
|
"coin": {
|
||||||
"name": "Namecoin",
|
"name": "Namecoin",
|
||||||
"shortcut": "NMC",
|
"shortcut": "NMC",
|
||||||
"label": "Namecoin",
|
"label": "Namecoin",
|
||||||
"alias": "namecoin"
|
"alias": "namecoin"
|
||||||
},
|
},
|
||||||
"ports": {
|
"ports": {
|
||||||
"backend_rpc": 8039,
|
"backend_rpc": 8039,
|
||||||
@ -28,7 +28,7 @@
|
|||||||
"verification_source": "14ebaaf6f22f69b057a5bcb9b6959548f0a3f1b62cc113f19581d2297044827e",
|
"verification_source": "14ebaaf6f22f69b057a5bcb9b6959548f0a3f1b62cc113f19581d2297044827e",
|
||||||
"extract_command": "tar -C backend --strip 1 -xf",
|
"extract_command": "tar -C backend --strip 1 -xf",
|
||||||
"exclude_files": [
|
"exclude_files": [
|
||||||
"bin/namecoin-qt"
|
"bin/namecoin-qt"
|
||||||
],
|
],
|
||||||
"exec_command_template": "{{.Env.BackendInstallPath}}/{{.Coin.Alias}}/bin/namecoind -datadir={{.Env.BackendDataPath}}/{{.Coin.Alias}}/backend -conf={{.Env.BackendInstallPath}}/{{.Coin.Alias}}/{{.Coin.Alias}}.conf -pid=/run/{{.Coin.Alias}}/{{.Coin.Alias}}.pid",
|
"exec_command_template": "{{.Env.BackendInstallPath}}/{{.Coin.Alias}}/bin/namecoind -datadir={{.Env.BackendDataPath}}/{{.Coin.Alias}}/backend -conf={{.Env.BackendInstallPath}}/{{.Coin.Alias}}/{{.Coin.Alias}}.conf -pid=/run/{{.Coin.Alias}}/{{.Coin.Alias}}.pid",
|
||||||
"logrotate_files_template": "{{.Env.BackendDataPath}}/{{.Coin.Alias}}/backend/*.log",
|
"logrotate_files_template": "{{.Env.BackendDataPath}}/{{.Coin.Alias}}/backend/*.log",
|
||||||
@ -40,14 +40,14 @@
|
|||||||
"server_config_file": "bitcoin_like.conf",
|
"server_config_file": "bitcoin_like.conf",
|
||||||
"client_config_file": "bitcoin_like_client.conf",
|
"client_config_file": "bitcoin_like_client.conf",
|
||||||
"additional_params": {
|
"additional_params": {
|
||||||
"whitelist": "127.0.0.1",
|
"addnode": [
|
||||||
"upnp": 0,
|
"45.24.110.177:8334"
|
||||||
"discover": 0,
|
],
|
||||||
"whitelistrelay": 1,
|
"discover": 0,
|
||||||
"listenonion": 0,
|
"listenonion": 0,
|
||||||
"addnode": [
|
"upnp": 0,
|
||||||
"45.24.110.177:8334"
|
"whitelist": "127.0.0.1",
|
||||||
]
|
"whitelistrelay": 1
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"blockbook": {
|
"blockbook": {
|
||||||
@ -62,6 +62,8 @@
|
|||||||
"mempool_workers": 8,
|
"mempool_workers": 8,
|
||||||
"mempool_sub_workers": 2,
|
"mempool_sub_workers": 2,
|
||||||
"block_addresses_to_keep": 300,
|
"block_addresses_to_keep": 300,
|
||||||
|
"xpub_magic": 76067358,
|
||||||
|
"slip44": 7,
|
||||||
"additional_params": {}
|
"additional_params": {}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|||||||
@ -1,9 +1,9 @@
|
|||||||
{
|
{
|
||||||
"coin": {
|
"coin": {
|
||||||
"name": "Vertcoin",
|
"name": "Vertcoin",
|
||||||
"shortcut": "VTC",
|
"shortcut": "VTC",
|
||||||
"label": "Vertcoin",
|
"label": "Vertcoin",
|
||||||
"alias": "vertcoin"
|
"alias": "vertcoin"
|
||||||
},
|
},
|
||||||
"ports": {
|
"ports": {
|
||||||
"backend_rpc": 8040,
|
"backend_rpc": 8040,
|
||||||
@ -38,7 +38,7 @@
|
|||||||
"server_config_file": "bitcoin_like.conf",
|
"server_config_file": "bitcoin_like.conf",
|
||||||
"client_config_file": "bitcoin_like_client.conf",
|
"client_config_file": "bitcoin_like_client.conf",
|
||||||
"additional_params": {
|
"additional_params": {
|
||||||
"whitelist": "127.0.0.1"
|
"whitelist": "127.0.0.1"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"blockbook": {
|
"blockbook": {
|
||||||
@ -53,6 +53,9 @@
|
|||||||
"mempool_workers": 8,
|
"mempool_workers": 8,
|
||||||
"mempool_sub_workers": 2,
|
"mempool_sub_workers": 2,
|
||||||
"block_addresses_to_keep": 1000,
|
"block_addresses_to_keep": 1000,
|
||||||
|
"xpub_magic": 76067358,
|
||||||
|
"xpub_magic_segwit_p2sh": 77429938,
|
||||||
|
"slip44": 28,
|
||||||
"additional_params": {}
|
"additional_params": {}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|||||||
@ -1,9 +1,9 @@
|
|||||||
{
|
{
|
||||||
"coin": {
|
"coin": {
|
||||||
"name": "Zcash",
|
"name": "Zcash",
|
||||||
"shortcut": "ZEC",
|
"shortcut": "ZEC",
|
||||||
"label": "Zcash",
|
"label": "Zcash",
|
||||||
"alias": "zcash"
|
"alias": "zcash"
|
||||||
},
|
},
|
||||||
"ports": {
|
"ports": {
|
||||||
"backend_rpc": 8032,
|
"backend_rpc": 8032,
|
||||||
@ -38,9 +38,9 @@
|
|||||||
"server_config_file": "bitcoin_like.conf",
|
"server_config_file": "bitcoin_like.conf",
|
||||||
"client_config_file": "bitcoin_like_client.conf",
|
"client_config_file": "bitcoin_like_client.conf",
|
||||||
"additional_params": {
|
"additional_params": {
|
||||||
"addnode": [
|
"addnode": [
|
||||||
"mainnet.z.cash"
|
"mainnet.z.cash"
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"blockbook": {
|
"blockbook": {
|
||||||
@ -55,6 +55,8 @@
|
|||||||
"mempool_workers": 4,
|
"mempool_workers": 4,
|
||||||
"mempool_sub_workers": 8,
|
"mempool_sub_workers": 8,
|
||||||
"block_addresses_to_keep": 300,
|
"block_addresses_to_keep": 300,
|
||||||
|
"xpub_magic": 76067358,
|
||||||
|
"slip44": 133,
|
||||||
"additional_params": {}
|
"additional_params": {}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|||||||
@ -1,9 +1,9 @@
|
|||||||
{
|
{
|
||||||
"coin": {
|
"coin": {
|
||||||
"name": "Zcash Testnet",
|
"name": "Zcash Testnet",
|
||||||
"shortcut": "TAZ",
|
"shortcut": "TAZ",
|
||||||
"label": "Zcash Testnet",
|
"label": "Zcash Testnet",
|
||||||
"alias": "zcash_testnet"
|
"alias": "zcash_testnet"
|
||||||
},
|
},
|
||||||
"ports": {
|
"ports": {
|
||||||
"backend_rpc": 18032,
|
"backend_rpc": 18032,
|
||||||
@ -38,9 +38,9 @@
|
|||||||
"server_config_file": "bitcoin_like.conf",
|
"server_config_file": "bitcoin_like.conf",
|
||||||
"client_config_file": "bitcoin_like_client.conf",
|
"client_config_file": "bitcoin_like_client.conf",
|
||||||
"additional_params": {
|
"additional_params": {
|
||||||
"addnode": [
|
"addnode": [
|
||||||
"testnet.z.cash"
|
"testnet.z.cash"
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"blockbook": {
|
"blockbook": {
|
||||||
@ -55,6 +55,8 @@
|
|||||||
"mempool_workers": 4,
|
"mempool_workers": 4,
|
||||||
"mempool_sub_workers": 8,
|
"mempool_sub_workers": 8,
|
||||||
"block_addresses_to_keep": 300,
|
"block_addresses_to_keep": 300,
|
||||||
|
"xpub_magic": 70617039,
|
||||||
|
"slip44": 1,
|
||||||
"additional_params": {}
|
"additional_params": {}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|||||||
@ -1,9 +1,9 @@
|
|||||||
{
|
{
|
||||||
"coin": {
|
"coin": {
|
||||||
"name": "Zcoin",
|
"name": "Zcoin",
|
||||||
"shortcut": "XZC",
|
"shortcut": "XZC",
|
||||||
"label": "Zcoin",
|
"label": "Zcoin",
|
||||||
"alias": "zcoin"
|
"alias": "zcoin"
|
||||||
},
|
},
|
||||||
"ports": {
|
"ports": {
|
||||||
"backend_rpc": 8050,
|
"backend_rpc": 8050,
|
||||||
@ -52,7 +52,7 @@
|
|||||||
"server_config_file": "bitcoin_like.conf",
|
"server_config_file": "bitcoin_like.conf",
|
||||||
"client_config_file": "bitcoin_like_client.conf",
|
"client_config_file": "bitcoin_like_client.conf",
|
||||||
"additional_params": {
|
"additional_params": {
|
||||||
"deprecatedrpc": "estimatefee"
|
"deprecatedrpc": "estimatefee"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"blockbook": {
|
"blockbook": {
|
||||||
@ -66,6 +66,8 @@
|
|||||||
"mempool_workers": 8,
|
"mempool_workers": 8,
|
||||||
"mempool_sub_workers": 2,
|
"mempool_sub_workers": 2,
|
||||||
"block_addresses_to_keep": 300,
|
"block_addresses_to_keep": 300,
|
||||||
|
"xpub_magic": 76067358,
|
||||||
|
"slip44": 136,
|
||||||
"additional_params": {}
|
"additional_params": {}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|||||||
@ -1,5 +1,5 @@
|
|||||||
{
|
{
|
||||||
"version": "0.2.0",
|
"version": "0.2.1",
|
||||||
"backend_install_path": "/opt/coins/nodes",
|
"backend_install_path": "/opt/coins/nodes",
|
||||||
"backend_data_path": "/opt/coins/data",
|
"backend_data_path": "/opt/coins/data",
|
||||||
"blockbook_install_path": "/opt/coins/blockbook",
|
"blockbook_install_path": "/opt/coins/blockbook",
|
||||||
|
|||||||
@ -237,7 +237,7 @@ func writeMarkdown(output string, slice PortInfoSlice) error {
|
|||||||
|
|
||||||
out := os.Stdout
|
out := os.Stdout
|
||||||
if output != "stdout" {
|
if output != "stdout" {
|
||||||
out, err = os.OpenFile(output, os.O_CREATE|os.O_WRONLY, 0644)
|
out, err = os.OpenFile(output, os.O_CREATE|os.O_WRONLY|os.O_TRUNC, 0644)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|||||||
@ -8,7 +8,7 @@
|
|||||||
| Dash | 9033 | 9133 | 8033 | 38333 |
|
| Dash | 9033 | 9133 | 8033 | 38333 |
|
||||||
| Litecoin | 9034 | 9134 | 8034 | 38334 |
|
| Litecoin | 9034 | 9134 | 8034 | 38334 |
|
||||||
| Bitcoin Gold | 9035 | 9135 | 8035 | 38335 |
|
| Bitcoin Gold | 9035 | 9135 | 8035 | 38335 |
|
||||||
| Ethereum | 9036 | 9136 | 8036 | 38336 p2p, 8136 http |
|
| Ethereum | 9036 | 9136 | 8036 | 8136 http, 38336 p2p |
|
||||||
| Ethereum Classic | 9037 | 9137 | 8037 | |
|
| Ethereum Classic | 9037 | 9137 | 8037 | |
|
||||||
| Dogecoin | 9038 | 9138 | 8038 | 38338 |
|
| Dogecoin | 9038 | 9138 | 8038 | 38338 |
|
||||||
| Namecoin | 9039 | 9139 | 8039 | 38339 |
|
| Namecoin | 9039 | 9139 | 8039 | 38339 |
|
||||||
|
|||||||
181
server/public.go
181
server/public.go
@ -131,6 +131,7 @@ func (s *PublicServer) ConnectFullPublicInterface() {
|
|||||||
// internal explorer handlers
|
// internal explorer handlers
|
||||||
serveMux.HandleFunc(path+"tx/", s.htmlTemplateHandler(s.explorerTx))
|
serveMux.HandleFunc(path+"tx/", s.htmlTemplateHandler(s.explorerTx))
|
||||||
serveMux.HandleFunc(path+"address/", s.htmlTemplateHandler(s.explorerAddress))
|
serveMux.HandleFunc(path+"address/", s.htmlTemplateHandler(s.explorerAddress))
|
||||||
|
serveMux.HandleFunc(path+"xpub/", s.htmlTemplateHandler(s.explorerXpub))
|
||||||
serveMux.HandleFunc(path+"search/", s.htmlTemplateHandler(s.explorerSearch))
|
serveMux.HandleFunc(path+"search/", s.htmlTemplateHandler(s.explorerSearch))
|
||||||
serveMux.HandleFunc(path+"blocks", s.htmlTemplateHandler(s.explorerBlocks))
|
serveMux.HandleFunc(path+"blocks", s.htmlTemplateHandler(s.explorerBlocks))
|
||||||
serveMux.HandleFunc(path+"block/", s.htmlTemplateHandler(s.explorerBlock))
|
serveMux.HandleFunc(path+"block/", s.htmlTemplateHandler(s.explorerBlock))
|
||||||
@ -156,7 +157,7 @@ func (s *PublicServer) ConnectFullPublicInterface() {
|
|||||||
serveMux.HandleFunc(path+"api/v1/tx-specific/", s.jsonHandler(s.apiTxSpecific, apiV1))
|
serveMux.HandleFunc(path+"api/v1/tx-specific/", s.jsonHandler(s.apiTxSpecific, apiV1))
|
||||||
serveMux.HandleFunc(path+"api/v1/tx/", s.jsonHandler(s.apiTx, apiV1))
|
serveMux.HandleFunc(path+"api/v1/tx/", s.jsonHandler(s.apiTx, apiV1))
|
||||||
serveMux.HandleFunc(path+"api/v1/address/", s.jsonHandler(s.apiAddress, apiV1))
|
serveMux.HandleFunc(path+"api/v1/address/", s.jsonHandler(s.apiAddress, apiV1))
|
||||||
serveMux.HandleFunc(path+"api/v1/utxo/", s.jsonHandler(s.apiAddressUtxo, apiV1))
|
serveMux.HandleFunc(path+"api/v1/utxo/", s.jsonHandler(s.apiUtxo, apiV1))
|
||||||
serveMux.HandleFunc(path+"api/v1/block/", s.jsonHandler(s.apiBlock, apiV1))
|
serveMux.HandleFunc(path+"api/v1/block/", s.jsonHandler(s.apiBlock, apiV1))
|
||||||
serveMux.HandleFunc(path+"api/v1/sendtx/", s.jsonHandler(s.apiSendTx, apiV1))
|
serveMux.HandleFunc(path+"api/v1/sendtx/", s.jsonHandler(s.apiSendTx, apiV1))
|
||||||
serveMux.HandleFunc(path+"api/v1/estimatefee/", s.jsonHandler(s.apiEstimateFee, apiV1))
|
serveMux.HandleFunc(path+"api/v1/estimatefee/", s.jsonHandler(s.apiEstimateFee, apiV1))
|
||||||
@ -165,7 +166,8 @@ func (s *PublicServer) ConnectFullPublicInterface() {
|
|||||||
serveMux.HandleFunc(path+"api/tx-specific/", s.jsonHandler(s.apiTxSpecific, apiDefault))
|
serveMux.HandleFunc(path+"api/tx-specific/", s.jsonHandler(s.apiTxSpecific, apiDefault))
|
||||||
serveMux.HandleFunc(path+"api/tx/", s.jsonHandler(s.apiTx, apiDefault))
|
serveMux.HandleFunc(path+"api/tx/", s.jsonHandler(s.apiTx, apiDefault))
|
||||||
serveMux.HandleFunc(path+"api/address/", s.jsonHandler(s.apiAddress, apiDefault))
|
serveMux.HandleFunc(path+"api/address/", s.jsonHandler(s.apiAddress, apiDefault))
|
||||||
serveMux.HandleFunc(path+"api/utxo/", s.jsonHandler(s.apiAddressUtxo, apiDefault))
|
serveMux.HandleFunc(path+"api/xpub/", s.jsonHandler(s.apiXpub, apiDefault))
|
||||||
|
serveMux.HandleFunc(path+"api/utxo/", s.jsonHandler(s.apiUtxo, apiDefault))
|
||||||
serveMux.HandleFunc(path+"api/block/", s.jsonHandler(s.apiBlock, apiDefault))
|
serveMux.HandleFunc(path+"api/block/", s.jsonHandler(s.apiBlock, apiDefault))
|
||||||
serveMux.HandleFunc(path+"api/sendtx/", s.jsonHandler(s.apiSendTx, apiDefault))
|
serveMux.HandleFunc(path+"api/sendtx/", s.jsonHandler(s.apiSendTx, apiDefault))
|
||||||
serveMux.HandleFunc(path+"api/estimatefee/", s.jsonHandler(s.apiEstimateFee, apiDefault))
|
serveMux.HandleFunc(path+"api/estimatefee/", s.jsonHandler(s.apiEstimateFee, apiDefault))
|
||||||
@ -174,7 +176,8 @@ func (s *PublicServer) ConnectFullPublicInterface() {
|
|||||||
serveMux.HandleFunc(path+"api/v2/tx-specific/", s.jsonHandler(s.apiTxSpecific, apiV2))
|
serveMux.HandleFunc(path+"api/v2/tx-specific/", s.jsonHandler(s.apiTxSpecific, apiV2))
|
||||||
serveMux.HandleFunc(path+"api/v2/tx/", s.jsonHandler(s.apiTx, apiV2))
|
serveMux.HandleFunc(path+"api/v2/tx/", s.jsonHandler(s.apiTx, apiV2))
|
||||||
serveMux.HandleFunc(path+"api/v2/address/", s.jsonHandler(s.apiAddress, apiV2))
|
serveMux.HandleFunc(path+"api/v2/address/", s.jsonHandler(s.apiAddress, apiV2))
|
||||||
serveMux.HandleFunc(path+"api/v2/utxo/", s.jsonHandler(s.apiAddressUtxo, apiV2))
|
serveMux.HandleFunc(path+"api/v2/xpub/", s.jsonHandler(s.apiXpub, apiV2))
|
||||||
|
serveMux.HandleFunc(path+"api/v2/utxo/", s.jsonHandler(s.apiUtxo, apiV2))
|
||||||
serveMux.HandleFunc(path+"api/v2/block/", s.jsonHandler(s.apiBlock, apiV2))
|
serveMux.HandleFunc(path+"api/v2/block/", s.jsonHandler(s.apiBlock, apiV2))
|
||||||
serveMux.HandleFunc(path+"api/v2/sendtx/", s.jsonHandler(s.apiSendTx, apiV2))
|
serveMux.HandleFunc(path+"api/v2/sendtx/", s.jsonHandler(s.apiSendTx, apiV2))
|
||||||
serveMux.HandleFunc(path+"api/v2/estimatefee/", s.jsonHandler(s.apiEstimateFee, apiV2))
|
serveMux.HandleFunc(path+"api/v2/estimatefee/", s.jsonHandler(s.apiEstimateFee, apiV2))
|
||||||
@ -372,6 +375,7 @@ const (
|
|||||||
indexTpl
|
indexTpl
|
||||||
txTpl
|
txTpl
|
||||||
addressTpl
|
addressTpl
|
||||||
|
xpubTpl
|
||||||
blocksTpl
|
blocksTpl
|
||||||
blockTpl
|
blockTpl
|
||||||
sendTransactionTpl
|
sendTransactionTpl
|
||||||
@ -381,26 +385,27 @@ const (
|
|||||||
|
|
||||||
// TemplateData is used to transfer data to the templates
|
// TemplateData is used to transfer data to the templates
|
||||||
type TemplateData struct {
|
type TemplateData struct {
|
||||||
CoinName string
|
CoinName string
|
||||||
CoinShortcut string
|
CoinShortcut string
|
||||||
CoinLabel string
|
CoinLabel string
|
||||||
InternalExplorer bool
|
InternalExplorer bool
|
||||||
ChainType bchain.ChainType
|
ChainType bchain.ChainType
|
||||||
Address *api.Address
|
Address *api.Address
|
||||||
AddrStr string
|
AddrStr string
|
||||||
Tx *api.Tx
|
Tx *api.Tx
|
||||||
Error *api.APIError
|
Error *api.APIError
|
||||||
Blocks *api.Blocks
|
Blocks *api.Blocks
|
||||||
Block *api.Block
|
Block *api.Block
|
||||||
Info *api.SystemInfo
|
Info *api.SystemInfo
|
||||||
Page int
|
Page int
|
||||||
PrevPage int
|
PrevPage int
|
||||||
NextPage int
|
NextPage int
|
||||||
PagingRange []int
|
PagingRange []int
|
||||||
PageParams template.URL
|
PageParams template.URL
|
||||||
TOSLink string
|
TOSLink string
|
||||||
SendTxHex string
|
SendTxHex string
|
||||||
Status string
|
Status string
|
||||||
|
NonZeroBalanceTokens bool
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *PublicServer) parseTemplates() []*template.Template {
|
func (s *PublicServer) parseTemplates() []*template.Template {
|
||||||
@ -410,7 +415,8 @@ func (s *PublicServer) parseTemplates() []*template.Template {
|
|||||||
"formatAmount": s.formatAmount,
|
"formatAmount": s.formatAmount,
|
||||||
"formatAmountWithDecimals": formatAmountWithDecimals,
|
"formatAmountWithDecimals": formatAmountWithDecimals,
|
||||||
"setTxToTemplateData": setTxToTemplateData,
|
"setTxToTemplateData": setTxToTemplateData,
|
||||||
"stringInSlice": stringInSlice,
|
"isOwnAddress": isOwnAddress,
|
||||||
|
"isOwnAddresses": isOwnAddresses,
|
||||||
}
|
}
|
||||||
var createTemplate func(filenames ...string) *template.Template
|
var createTemplate func(filenames ...string) *template.Template
|
||||||
if s.debug {
|
if s.debug {
|
||||||
@ -465,6 +471,7 @@ func (s *PublicServer) parseTemplates() []*template.Template {
|
|||||||
t[addressTpl] = createTemplate("./static/templates/address.html", "./static/templates/txdetail.html", "./static/templates/paging.html", "./static/templates/base.html")
|
t[addressTpl] = createTemplate("./static/templates/address.html", "./static/templates/txdetail.html", "./static/templates/paging.html", "./static/templates/base.html")
|
||||||
t[blockTpl] = createTemplate("./static/templates/block.html", "./static/templates/txdetail.html", "./static/templates/paging.html", "./static/templates/base.html")
|
t[blockTpl] = createTemplate("./static/templates/block.html", "./static/templates/txdetail.html", "./static/templates/paging.html", "./static/templates/base.html")
|
||||||
}
|
}
|
||||||
|
t[xpubTpl] = createTemplate("./static/templates/xpub.html", "./static/templates/txdetail.html", "./static/templates/paging.html", "./static/templates/base.html")
|
||||||
return t
|
return t
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -498,6 +505,29 @@ func setTxToTemplateData(td *TemplateData, tx *api.Tx) *TemplateData {
|
|||||||
return td
|
return td
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// returns true if address is "own",
|
||||||
|
// i.e. either the address of the address detail or belonging to the xpub
|
||||||
|
func isOwnAddress(td *TemplateData, a string) bool {
|
||||||
|
if a == td.AddrStr {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
if td.Address != nil && td.Address.XPubAddresses != nil {
|
||||||
|
if _, found := td.Address.XPubAddresses[a]; found {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
// returns true if addresses are "own",
|
||||||
|
// i.e. either the address of the address detail or belonging to the xpub
|
||||||
|
func isOwnAddresses(td *TemplateData, addresses []string) bool {
|
||||||
|
if len(addresses) == 1 {
|
||||||
|
return isOwnAddress(td, addresses[0])
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
func (s *PublicServer) explorerTx(w http.ResponseWriter, r *http.Request) (tpl, *TemplateData, error) {
|
func (s *PublicServer) explorerTx(w http.ResponseWriter, r *http.Request) (tpl, *TemplateData, error) {
|
||||||
var tx *api.Tx
|
var tx *api.Tx
|
||||||
var err error
|
var err error
|
||||||
@ -560,7 +590,7 @@ func (s *PublicServer) explorerAddress(w http.ResponseWriter, r *http.Request) (
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
address, err = s.api.GetAddress(r.URL.Path[i+1:], page, txsOnPage, api.TxHistoryLight, &api.AddressFilter{Vout: fn})
|
address, err = s.api.GetAddress(r.URL.Path[i+1:], page, txsOnPage, api.AccountDetailsTxHistoryLight, &api.AddressFilter{Vout: fn})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return errorTpl, nil, err
|
return errorTpl, nil, err
|
||||||
}
|
}
|
||||||
@ -577,6 +607,68 @@ func (s *PublicServer) explorerAddress(w http.ResponseWriter, r *http.Request) (
|
|||||||
return addressTpl, data, nil
|
return addressTpl, data, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (s *PublicServer) getXpubAddress(r *http.Request, xpub string, pageSize int, option api.AccountDetails) (*api.Address, api.TokenDetailLevel, error) {
|
||||||
|
var fn = api.AddressFilterVoutOff
|
||||||
|
page, ec := strconv.Atoi(r.URL.Query().Get("page"))
|
||||||
|
if ec != nil {
|
||||||
|
page = 0
|
||||||
|
}
|
||||||
|
filter := r.URL.Query().Get("filter")
|
||||||
|
if len(filter) > 0 {
|
||||||
|
if filter == "inputs" {
|
||||||
|
fn = api.AddressFilterVoutInputs
|
||||||
|
} else if filter == "outputs" {
|
||||||
|
fn = api.AddressFilterVoutOutputs
|
||||||
|
} else {
|
||||||
|
fn, ec = strconv.Atoi(filter)
|
||||||
|
if ec != nil || fn < 0 {
|
||||||
|
filter = ""
|
||||||
|
fn = api.AddressFilterVoutOff
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
gap, ec := strconv.Atoi(r.URL.Query().Get("gap"))
|
||||||
|
if ec != nil {
|
||||||
|
gap = 0
|
||||||
|
}
|
||||||
|
tokenLevel := api.TokenDetailNonzeroBalance
|
||||||
|
switch r.URL.Query().Get("tokenlevel") {
|
||||||
|
case "discovered":
|
||||||
|
tokenLevel = api.TokenDetailDiscovered
|
||||||
|
case "used":
|
||||||
|
tokenLevel = api.TokenDetailUsed
|
||||||
|
case "nonzero":
|
||||||
|
tokenLevel = api.TokenDetailNonzeroBalance
|
||||||
|
}
|
||||||
|
a, err := s.api.GetXpubAddress(xpub, page, pageSize, option, &api.AddressFilter{Vout: fn, TokenLevel: tokenLevel}, gap)
|
||||||
|
return a, tokenLevel, err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *PublicServer) explorerXpub(w http.ResponseWriter, r *http.Request) (tpl, *TemplateData, error) {
|
||||||
|
var address *api.Address
|
||||||
|
var tokenLevel api.TokenDetailLevel
|
||||||
|
var err error
|
||||||
|
s.metrics.ExplorerViews.With(common.Labels{"action": "xpub"}).Inc()
|
||||||
|
if i := strings.LastIndexByte(r.URL.Path, '/'); i > 0 {
|
||||||
|
address, tokenLevel, err = s.getXpubAddress(r, r.URL.Path[i+1:], txsOnPage, api.AccountDetailsTxHistoryLight)
|
||||||
|
if err != nil {
|
||||||
|
return errorTpl, nil, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
data := s.newTemplateData()
|
||||||
|
data.AddrStr = address.AddrStr
|
||||||
|
data.Address = address
|
||||||
|
data.Page = address.Page
|
||||||
|
data.PagingRange, data.PrevPage, data.NextPage = getPagingRange(address.Page, address.TotalPages)
|
||||||
|
filter := r.URL.Query().Get("filter")
|
||||||
|
if filter != "" {
|
||||||
|
data.PageParams = template.URL("&filter=" + filter)
|
||||||
|
data.Address.Filter = filter
|
||||||
|
}
|
||||||
|
data.NonZeroBalanceTokens = tokenLevel == api.TokenDetailNonzeroBalance
|
||||||
|
return xpubTpl, data, nil
|
||||||
|
}
|
||||||
|
|
||||||
func (s *PublicServer) explorerBlocks(w http.ResponseWriter, r *http.Request) (tpl, *TemplateData, error) {
|
func (s *PublicServer) explorerBlocks(w http.ResponseWriter, r *http.Request) (tpl, *TemplateData, error) {
|
||||||
var blocks *api.Blocks
|
var blocks *api.Blocks
|
||||||
var err error
|
var err error
|
||||||
@ -638,6 +730,11 @@ func (s *PublicServer) explorerSearch(w http.ResponseWriter, r *http.Request) (t
|
|||||||
var err error
|
var err error
|
||||||
s.metrics.ExplorerViews.With(common.Labels{"action": "search"}).Inc()
|
s.metrics.ExplorerViews.With(common.Labels{"action": "search"}).Inc()
|
||||||
if len(q) > 0 {
|
if len(q) > 0 {
|
||||||
|
address, err = s.api.GetXpubAddress(q, 0, 1, api.AccountDetailsBasic, &api.AddressFilter{Vout: api.AddressFilterVoutOff}, 0)
|
||||||
|
if err == nil {
|
||||||
|
http.Redirect(w, r, joinURL("/xpub/", address.AddrStr), 302)
|
||||||
|
return noTpl, nil, nil
|
||||||
|
}
|
||||||
block, err = s.api.GetBlock(q, 0, 1)
|
block, err = s.api.GetBlock(q, 0, 1)
|
||||||
if err == nil {
|
if err == nil {
|
||||||
http.Redirect(w, r, joinURL("/block/", block.Hash), 302)
|
http.Redirect(w, r, joinURL("/block/", block.Hash), 302)
|
||||||
@ -648,7 +745,7 @@ func (s *PublicServer) explorerSearch(w http.ResponseWriter, r *http.Request) (t
|
|||||||
http.Redirect(w, r, joinURL("/tx/", tx.Txid), 302)
|
http.Redirect(w, r, joinURL("/tx/", tx.Txid), 302)
|
||||||
return noTpl, nil, nil
|
return noTpl, nil, nil
|
||||||
}
|
}
|
||||||
address, err = s.api.GetAddress(q, 0, 1, api.Basic, &api.AddressFilter{Vout: api.AddressFilterVoutOff})
|
address, err = s.api.GetAddress(q, 0, 1, api.AccountDetailsBasic, &api.AddressFilter{Vout: api.AddressFilterVoutOff})
|
||||||
if err == nil {
|
if err == nil {
|
||||||
http.Redirect(w, r, joinURL("/address/", address.AddrStr), 302)
|
http.Redirect(w, r, joinURL("/address/", address.AddrStr), 302)
|
||||||
return noTpl, nil, nil
|
return noTpl, nil, nil
|
||||||
@ -810,7 +907,7 @@ func (s *PublicServer) apiAddress(r *http.Request, apiVersion int) (interface{},
|
|||||||
if ec != nil {
|
if ec != nil {
|
||||||
page = 0
|
page = 0
|
||||||
}
|
}
|
||||||
address, err = s.api.GetAddress(r.URL.Path[i+1:], page, txsInAPI, api.TxidHistory, &api.AddressFilter{Vout: api.AddressFilterVoutOff})
|
address, err = s.api.GetAddress(r.URL.Path[i+1:], page, txsInAPI, api.AccountDetailsTxidHistory, &api.AddressFilter{Vout: api.AddressFilterVoutOff})
|
||||||
if err == nil && apiVersion == apiV1 {
|
if err == nil && apiVersion == apiV1 {
|
||||||
return s.api.AddressToV1(address), nil
|
return s.api.AddressToV1(address), nil
|
||||||
}
|
}
|
||||||
@ -818,10 +915,22 @@ func (s *PublicServer) apiAddress(r *http.Request, apiVersion int) (interface{},
|
|||||||
return address, err
|
return address, err
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *PublicServer) apiAddressUtxo(r *http.Request, apiVersion int) (interface{}, error) {
|
func (s *PublicServer) apiXpub(r *http.Request, apiVersion int) (interface{}, error) {
|
||||||
var utxo []api.AddressUtxo
|
var address *api.Address
|
||||||
|
var err error
|
||||||
|
s.metrics.ExplorerViews.With(common.Labels{"action": "api-xpub"}).Inc()
|
||||||
|
if i := strings.LastIndexByte(r.URL.Path, '/'); i > 0 {
|
||||||
|
address, _, err = s.getXpubAddress(r, r.URL.Path[i+1:], txsInAPI, api.AccountDetailsTxidHistory)
|
||||||
|
if err == nil && apiVersion == apiV1 {
|
||||||
|
return s.api.AddressToV1(address), nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return address, err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *PublicServer) apiUtxo(r *http.Request, apiVersion int) (interface{}, error) {
|
||||||
|
var utxo []api.Utxo
|
||||||
var err error
|
var err error
|
||||||
s.metrics.ExplorerViews.With(common.Labels{"action": "api-address"}).Inc()
|
|
||||||
if i := strings.LastIndexByte(r.URL.Path, '/'); i > 0 {
|
if i := strings.LastIndexByte(r.URL.Path, '/'); i > 0 {
|
||||||
onlyConfirmed := false
|
onlyConfirmed := false
|
||||||
c := r.URL.Query().Get("confirmed")
|
c := r.URL.Query().Get("confirmed")
|
||||||
@ -831,7 +940,17 @@ func (s *PublicServer) apiAddressUtxo(r *http.Request, apiVersion int) (interfac
|
|||||||
return nil, api.NewAPIError("Parameter 'confirmed' cannot be converted to boolean", true)
|
return nil, api.NewAPIError("Parameter 'confirmed' cannot be converted to boolean", true)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
utxo, err = s.api.GetAddressUtxo(r.URL.Path[i+1:], onlyConfirmed)
|
gap, ec := strconv.Atoi(r.URL.Query().Get("gap"))
|
||||||
|
if ec != nil {
|
||||||
|
gap = 0
|
||||||
|
}
|
||||||
|
utxo, err = s.api.GetXpubUtxo(r.URL.Path[i+1:], onlyConfirmed, gap)
|
||||||
|
if err == nil {
|
||||||
|
s.metrics.ExplorerViews.With(common.Labels{"action": "api-xpub-utxo"}).Inc()
|
||||||
|
} else {
|
||||||
|
utxo, err = s.api.GetAddressUtxo(r.URL.Path[i+1:], onlyConfirmed)
|
||||||
|
s.metrics.ExplorerViews.With(common.Labels{"action": "api-address-utxo"}).Inc()
|
||||||
|
}
|
||||||
if err == nil && apiVersion == apiV1 {
|
if err == nil && apiVersion == apiV1 {
|
||||||
return s.api.AddressUtxoToV1(utxo), nil
|
return s.api.AddressUtxoToV1(utxo), nil
|
||||||
}
|
}
|
||||||
|
|||||||
File diff suppressed because one or more lines are too long
@ -292,15 +292,6 @@ type resultGetAddressHistory struct {
|
|||||||
} `json:"result"`
|
} `json:"result"`
|
||||||
}
|
}
|
||||||
|
|
||||||
func stringInSlice(a string, list []string) bool {
|
|
||||||
for _, b := range list {
|
|
||||||
if b == a {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
func txToResTx(tx *api.Tx) resTx {
|
func txToResTx(tx *api.Tx) resTx {
|
||||||
inputs := make([]txInputs, len(tx.Vin))
|
inputs := make([]txInputs, len(tx.Vin))
|
||||||
for i := range tx.Vin {
|
for i := range tx.Vin {
|
||||||
|
|||||||
@ -317,10 +317,10 @@ func (s *WebsocketServer) onRequest(c *websocketChannel, req *websocketReq) {
|
|||||||
}
|
}
|
||||||
if err == nil {
|
if err == nil {
|
||||||
glog.V(1).Info("Client ", c.id, " onRequest ", req.Method, " success")
|
glog.V(1).Info("Client ", c.id, " onRequest ", req.Method, " success")
|
||||||
s.metrics.SocketIORequests.With(common.Labels{"method": req.Method, "status": "success"}).Inc()
|
s.metrics.WebsocketRequests.With(common.Labels{"method": req.Method, "status": "success"}).Inc()
|
||||||
} else {
|
} else {
|
||||||
glog.Error("Client ", c.id, " onMessage ", req.Method, ": ", errors.ErrorStack(err))
|
glog.Error("Client ", c.id, " onMessage ", req.Method, ": ", errors.ErrorStack(err))
|
||||||
s.metrics.SocketIORequests.With(common.Labels{"method": req.Method, "status": err.Error()}).Inc()
|
s.metrics.WebsocketRequests.With(common.Labels{"method": req.Method, "status": err.Error()}).Inc()
|
||||||
e := resultError{}
|
e := resultError{}
|
||||||
e.Error.Message = err.Error()
|
e.Error.Message = err.Error()
|
||||||
data = e
|
data = e
|
||||||
@ -347,27 +347,39 @@ func unmarshalGetAccountInfoRequest(params []byte) (*accountInfoReq, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (s *WebsocketServer) getAccountInfo(req *accountInfoReq) (res *api.Address, err error) {
|
func (s *WebsocketServer) getAccountInfo(req *accountInfoReq) (res *api.Address, err error) {
|
||||||
var opt api.GetAddressOption
|
var opt api.AccountDetails
|
||||||
switch req.Details {
|
switch req.Details {
|
||||||
case "balance":
|
case "tokens":
|
||||||
opt = api.Balance
|
opt = api.AccountDetailsTokens
|
||||||
|
case "tokenBalances":
|
||||||
|
opt = api.AccountDetailsTokenBalances
|
||||||
case "txids":
|
case "txids":
|
||||||
opt = api.TxidHistory
|
opt = api.AccountDetailsTxidHistory
|
||||||
case "txs":
|
case "txs":
|
||||||
opt = api.TxHistory
|
opt = api.AccountDetailsTxHistory
|
||||||
default:
|
default:
|
||||||
opt = api.Basic
|
opt = api.AccountDetailsBasic
|
||||||
}
|
}
|
||||||
return s.api.GetAddress(req.Descriptor, req.Page, req.PageSize, opt, &api.AddressFilter{
|
filter := api.AddressFilter{
|
||||||
FromHeight: uint32(req.FromHeight),
|
FromHeight: uint32(req.FromHeight),
|
||||||
ToHeight: uint32(req.ToHeight),
|
ToHeight: uint32(req.ToHeight),
|
||||||
Contract: req.ContractFilter,
|
Contract: req.ContractFilter,
|
||||||
Vout: api.AddressFilterVoutOff,
|
Vout: api.AddressFilterVoutOff,
|
||||||
})
|
TokenLevel: api.TokenDetailDiscovered,
|
||||||
|
}
|
||||||
|
a, err := s.api.GetXpubAddress(req.Descriptor, req.Page, req.PageSize, opt, &filter, 0)
|
||||||
|
if err != nil {
|
||||||
|
return s.api.GetAddress(req.Descriptor, req.Page, req.PageSize, opt, &filter)
|
||||||
|
}
|
||||||
|
return a, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *WebsocketServer) getAccountUtxo(descriptor string) (interface{}, error) {
|
func (s *WebsocketServer) getAccountUtxo(descriptor string) (interface{}, error) {
|
||||||
return s.api.GetAddressUtxo(descriptor, false)
|
utxo, err := s.api.GetXpubUtxo(descriptor, false, 0)
|
||||||
|
if err != nil {
|
||||||
|
return s.api.GetAddressUtxo(descriptor, false)
|
||||||
|
}
|
||||||
|
return utxo, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *WebsocketServer) getTransaction(txid string) (interface{}, error) {
|
func (s *WebsocketServer) getTransaction(txid string) (interface{}, error) {
|
||||||
|
|||||||
@ -182,6 +182,26 @@ h3 {
|
|||||||
text-transform: uppercase;
|
text-transform: uppercase;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.tx-own {
|
||||||
|
background-color: #fbf8f0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tx-amt {
|
||||||
|
float: right!important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tx-in .tx-own .tx-amt {
|
||||||
|
color: #dc3545!important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tx-out .tx-own .tx-amt {
|
||||||
|
color: #28a745!important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tx-addr {
|
||||||
|
float: left!important;
|
||||||
|
}
|
||||||
|
|
||||||
.ellipsis {
|
.ellipsis {
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
text-overflow: ellipsis;
|
text-overflow: ellipsis;
|
||||||
@ -283,5 +303,5 @@ table.data-table table.data-table th {
|
|||||||
}
|
}
|
||||||
|
|
||||||
.key {
|
.key {
|
||||||
color: #333 ;
|
color: #333;
|
||||||
}
|
}
|
||||||
@ -42,7 +42,7 @@
|
|||||||
</ul>
|
</ul>
|
||||||
<span class="d-none ml-md-auto d-md-flex navbar-form navbar-left">
|
<span class="d-none ml-md-auto d-md-flex navbar-form navbar-left">
|
||||||
<form id="search" action="/search" method="get">
|
<form id="search" action="/search" method="get">
|
||||||
<input name="q" type="text" class="form-control" placeholder="Search for block, transaction or address" focus="true">
|
<input name="q" type="text" class="form-control" placeholder="Search for block, transaction, address or xpub" focus="true">
|
||||||
</form>
|
</form>
|
||||||
</span>
|
</span>
|
||||||
{{- end -}}
|
{{- end -}}
|
||||||
|
|||||||
@ -1,4 +1,4 @@
|
|||||||
{{define "txdetail"}}{{$cs := .CoinShortcut}}{{$addr := .AddrStr}}{{$tx := .Tx}}
|
{{define "txdetail"}}{{$cs := .CoinShortcut}}{{$addr := .AddrStr}}{{$tx := .Tx}}{{$data := .}}
|
||||||
<div class="alert alert-data">
|
<div class="alert alert-data">
|
||||||
<div class="row line-bot">
|
<div class="row line-bot">
|
||||||
<div class="col-xs-7 col-md-8 ellipsis">
|
<div class="col-xs-7 col-md-8 ellipsis">
|
||||||
@ -10,23 +10,23 @@
|
|||||||
</div>
|
</div>
|
||||||
<div class="row line-mid">
|
<div class="row line-mid">
|
||||||
<div class="col-md-5">
|
<div class="col-md-5">
|
||||||
<div class="row">
|
<div class="row tx-in">
|
||||||
<table class="table data-table">
|
<table class="table data-table">
|
||||||
<tbody>
|
<tbody>
|
||||||
{{- range $vin := $tx.Vin -}}
|
{{- range $vin := $tx.Vin -}}
|
||||||
<tr>
|
<tr{{if isOwnAddresses $data $vin.Addresses}} class="tx-own"{{end}}>
|
||||||
<td>
|
<td>
|
||||||
{{- if $vin.Txid -}}
|
{{- if $vin.Txid -}}
|
||||||
<a class="float-left text-muted" href="/tx/{{$vin.Txid}}" title="Outpoint {{$vin.Txid}},{{$vin.Vout}}">➡ </a>
|
<a class="float-left text-muted" href="/tx/{{$vin.Txid}}" title="Outpoint {{$vin.Txid}},{{$vin.Vout}}">➡ </a>
|
||||||
{{- end -}}
|
{{- end -}}
|
||||||
{{- range $a := $vin.Addresses -}}
|
{{- range $a := $vin.Addresses -}}
|
||||||
<span class="ellipsis float-left">
|
<span class="ellipsis tx-addr">
|
||||||
{{if and (ne $a $addr) $vin.Searchable}}<a href="/address/{{$a}}">{{$a}}</a>{{else}}{{$a}}{{end}}
|
{{if and (ne $a $addr) $vin.Searchable}}<a href="/address/{{$a}}">{{$a}}</a>{{else}}{{$a}}{{end}}
|
||||||
</span>
|
</span>
|
||||||
{{- else -}}
|
{{- else -}}
|
||||||
<span class="float-left">{{- if $vin.Hex -}}Unparsed address{{- else -}}No Inputs (Newly Generated Coins){{- end -}}</span>
|
<span class="tx-addr">{{- if $vin.Hex -}}Unparsed address{{- else -}}No Inputs (Newly Generated Coins){{- end -}}</span>
|
||||||
{{- end -}}{{- if $vin.Addresses -}}
|
{{- end -}}{{- if $vin.Addresses -}}
|
||||||
<span class="float-right{{if stringInSlice $addr $vin.Addresses}} text-danger{{end}}">{{formatAmount $vin.ValueSat}} {{$cs}}</span>
|
<span class="tx-amt">{{formatAmount $vin.ValueSat}} {{$cs}}</span>
|
||||||
{{- end -}}
|
{{- end -}}
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
@ -45,20 +45,20 @@
|
|||||||
</svg>
|
</svg>
|
||||||
</div>
|
</div>
|
||||||
<div class="col-md-6">
|
<div class="col-md-6">
|
||||||
<div class="row">
|
<div class="row tx-out">
|
||||||
<table class="table data-table">
|
<table class="table data-table">
|
||||||
<tbody>
|
<tbody>
|
||||||
{{- range $vout := $tx.Vout -}}
|
{{- range $vout := $tx.Vout -}}
|
||||||
<tr>
|
<tr{{if isOwnAddresses $data $vout.Addresses}} class="tx-own"{{end}}>
|
||||||
<td>
|
<td>
|
||||||
{{- range $a := $vout.Addresses -}}
|
{{- range $a := $vout.Addresses -}}
|
||||||
<span class="ellipsis float-left">
|
<span class="ellipsis tx-addr">
|
||||||
{{- if and (ne $a $addr) $vout.Searchable}}<a href="/address/{{$a}}">{{$a}}</a>{{else}}{{$a}}{{- end -}}
|
{{- if and (ne $a $addr) $vout.Searchable}}<a href="/address/{{$a}}">{{$a}}</a>{{else}}{{$a}}{{- end -}}
|
||||||
</span>
|
</span>
|
||||||
{{- else -}}
|
{{- else -}}
|
||||||
<span class="float-left">Unparsed address</span>
|
<span class="tx-addr">Unparsed address</span>
|
||||||
{{- end -}}
|
{{- end -}}
|
||||||
<span class="float-right{{if stringInSlice $addr $vout.Addresses}} text-success{{end}}">
|
<span class="tx-amt">
|
||||||
{{formatAmount $vout.ValueSat}} {{$cs}} {{if $vout.Spent}}<a class="text-danger" href="{{if $vout.SpentTxID}}/tx/{{$vout.SpentTxID}}{{else}}/spending/{{$tx.Txid}}/{{$vout.N}}{{end}}" title="Spent">➡</a>{{else -}}
|
{{formatAmount $vout.ValueSat}} {{$cs}} {{if $vout.Spent}}<a class="text-danger" href="{{if $vout.SpentTxID}}/tx/{{$vout.SpentTxID}}{{else}}/spending/{{$tx.Txid}}/{{$vout.N}}{{end}}" title="Spent">➡</a>{{else -}}
|
||||||
<span class="text-success" title="Unspent"> <b>×</b></span>
|
<span class="text-success" title="Unspent"> <b>×</b></span>
|
||||||
{{- end -}}
|
{{- end -}}
|
||||||
|
|||||||
@ -1,4 +1,4 @@
|
|||||||
{{define "txdetail"}}{{$cs := .CoinShortcut}}{{$addr := .AddrStr}}{{$tx := .Tx}}
|
{{define "txdetail"}}{{$cs := .CoinShortcut}}{{$addr := .AddrStr}}{{$tx := .Tx}}{{$data := .}}
|
||||||
<div class="alert alert-data"{{if eq $tx.EthereumSpecific.Status 0}} style="background-color: #faf2ee;"{{end}}>
|
<div class="alert alert-data"{{if eq $tx.EthereumSpecific.Status 0}} style="background-color: #faf2ee;"{{end}}>
|
||||||
<div class="row line-bot">
|
<div class="row line-bot">
|
||||||
<div class="col-xs-7 col-md-8 ellipsis">
|
<div class="col-xs-7 col-md-8 ellipsis">
|
||||||
@ -11,18 +11,18 @@
|
|||||||
</div>
|
</div>
|
||||||
<div class="row line-mid">
|
<div class="row line-mid">
|
||||||
<div class="col-md-4">
|
<div class="col-md-4">
|
||||||
<div class="row">
|
<div class="row tx-in">
|
||||||
<table class="table data-table">
|
<table class="table data-table">
|
||||||
<tbody>
|
<tbody>
|
||||||
{{- range $vin := $tx.Vin -}}
|
{{- range $vin := $tx.Vin -}}
|
||||||
<tr>
|
<tr{{if isOwnAddresses $data $vin.Addresses}} class="tx-own"{{end}}>
|
||||||
<td>
|
<td>
|
||||||
{{- range $a := $vin.Addresses -}}
|
{{- range $a := $vin.Addresses -}}
|
||||||
<span class="ellipsis float-left">
|
<span class="ellipsis tx-addr">
|
||||||
{{if and (ne $a $addr) $vin.Searchable}}<a href="/address/{{$a}}">{{$a}}</a>{{else}}{{$a}}{{end}}
|
{{if and (ne $a $addr) $vin.Searchable}}<a href="/address/{{$a}}">{{$a}}</a>{{else}}{{$a}}{{end}}
|
||||||
</span>
|
</span>
|
||||||
{{- else -}}
|
{{- else -}}
|
||||||
<span class="float-left">Unparsed address</span>
|
<span class="tx-addr">Unparsed address</span>
|
||||||
{{- end -}}
|
{{- end -}}
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
@ -41,18 +41,18 @@
|
|||||||
</svg>
|
</svg>
|
||||||
</div>
|
</div>
|
||||||
<div class="col-md-4">
|
<div class="col-md-4">
|
||||||
<div class="row">
|
<div class="row tx-out">
|
||||||
<table class="table data-table">
|
<table class="table data-table">
|
||||||
<tbody>
|
<tbody>
|
||||||
{{- range $vout := $tx.Vout -}}
|
{{- range $vout := $tx.Vout -}}
|
||||||
<tr>
|
<tr{{if isOwnAddresses $data $vout.Addresses}} class="tx-own"{{end}}>
|
||||||
<td>
|
<td>
|
||||||
{{- range $a := $vout.Addresses -}}
|
{{- range $a := $vout.Addresses -}}
|
||||||
<span class="ellipsis float-left">
|
<span class="ellipsis tx-addr">
|
||||||
{{- if and (ne $a $addr) $vout.Searchable}}<a href="/address/{{$a}}">{{$a}}</a>{{else}}{{$a}}{{- end -}}
|
{{- if and (ne $a $addr) $vout.Searchable}}<a href="/address/{{$a}}">{{$a}}</a>{{else}}{{$a}}{{- end -}}
|
||||||
</span>
|
</span>
|
||||||
{{- else -}}
|
{{- else -}}
|
||||||
<span class="float-left">Unparsed address</span>
|
<span class="tx-addr">Unparsed address</span>
|
||||||
{{- end -}}
|
{{- end -}}
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
@ -76,12 +76,12 @@
|
|||||||
{{- range $erc20 := $tx.TokenTransfers -}}
|
{{- range $erc20 := $tx.TokenTransfers -}}
|
||||||
<div class="row" style="padding: 2px 15px;">
|
<div class="row" style="padding: 2px 15px;">
|
||||||
<div class="col-md-4">
|
<div class="col-md-4">
|
||||||
<div class="row">
|
<div class="row tx-in">
|
||||||
<table class="table data-table">
|
<table class="table data-table">
|
||||||
<tbody>
|
<tbody>
|
||||||
<tr>
|
<tr{{if isOwnAddress $data $erc20.From}} class="tx-own"{{end}}>
|
||||||
<td>
|
<td>
|
||||||
<span class="ellipsis float-left">{{if ne $erc20.From $addr}}<a href="/address/{{$erc20.From}}">{{$erc20.From}}</a>{{else}}{{$erc20.From}}{{end}}</span>
|
<span class="ellipsis tx-addr">{{if ne $erc20.From $addr}}<a href="/address/{{$erc20.From}}">{{$erc20.From}}</a>{{else}}{{$erc20.From}}{{end}}</span>
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
</tbody>
|
</tbody>
|
||||||
@ -94,12 +94,12 @@
|
|||||||
</svg>
|
</svg>
|
||||||
</div>
|
</div>
|
||||||
<div class="col-md-4">
|
<div class="col-md-4">
|
||||||
<div class="row">
|
<div class="row tx-out">
|
||||||
<table class="table data-table">
|
<table class="table data-table">
|
||||||
<tbody>
|
<tbody>
|
||||||
<tr>
|
<tr{{if isOwnAddress $data $erc20.To}} class="tx-own"{{end}}>
|
||||||
<td>
|
<td>
|
||||||
<span class="ellipsis float-left">{{if ne $erc20.To $addr}}<a href="/address/{{$erc20.To}}">{{$erc20.To}}</a>{{else}}{{$erc20.To}}{{end}}</span>
|
<span class="ellipsis tx-addr">{{if ne $erc20.To $addr}}<a href="/address/{{$erc20.To}}">{{$erc20.To}}</a>{{else}}{{$erc20.To}}{{end}}</span>
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
</tbody>
|
</tbody>
|
||||||
|
|||||||
105
static/templates/xpub.html
Normal file
105
static/templates/xpub.html
Normal file
@ -0,0 +1,105 @@
|
|||||||
|
{{define "specific"}}{{$cs := .CoinShortcut}}{{$addr := .Address}}{{$data := .}}
|
||||||
|
<h1>XPUB <small class="text-muted">{{formatAmount $addr.BalanceSat}} {{$cs}}</small>
|
||||||
|
</h1>
|
||||||
|
<div class="alert alert-data ellipsis">
|
||||||
|
<span class="data">{{$addr.AddrStr}}</span>
|
||||||
|
</div>
|
||||||
|
<h3>Confirmed</h3>
|
||||||
|
<div class="data-div row">
|
||||||
|
<div class="col-md-10">
|
||||||
|
<table class="table data-table">
|
||||||
|
<tbody>
|
||||||
|
<tr>
|
||||||
|
<td style="width: 25%;">Total Received</td>
|
||||||
|
<td class="data">{{formatAmount $addr.TotalReceivedSat}} {{$cs}}</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td>Total Sent</td>
|
||||||
|
<td class="data">{{formatAmount $addr.TotalSentSat}} {{$cs}}</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td>Final Balance</td>
|
||||||
|
<td class="data">{{formatAmount $addr.BalanceSat}} {{$cs}}</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td>No. Transactions</td>
|
||||||
|
<td class="data">{{$addr.Txs}}</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td>Used XPUB Addresses</td>
|
||||||
|
<td class="data">{{$addr.TotalTokens}}</td>
|
||||||
|
</tr>
|
||||||
|
{{- if or $addr.Tokens $addr.TotalTokens -}}
|
||||||
|
<tr>
|
||||||
|
<td>{{if $data.NonZeroBalanceTokens}}XPUB Addresses with Balance{{else}}XPUB Addresses{{end}}</td>
|
||||||
|
<td style="padding: 0;">
|
||||||
|
<table class="table data-table">
|
||||||
|
<tbody>
|
||||||
|
<tr>
|
||||||
|
<th style="width: 50%;">Address</th>
|
||||||
|
<th>Balance</th>
|
||||||
|
<th style="width: 8%;">Txs</th>
|
||||||
|
<th style="width: 18%;">Path</th>
|
||||||
|
</tr>
|
||||||
|
{{- range $t := $addr.Tokens -}}
|
||||||
|
<tr>
|
||||||
|
<td class="data ellipsis"><a href="/address/{{$t.Name}}">{{$t.Name}}</a></td>
|
||||||
|
<td class="data">{{formatAmount $t.BalanceSat}} {{$cs}}</td>
|
||||||
|
<td class="data">{{$t.Transfers}}</td>
|
||||||
|
<td>{{$t.Path}}</td>
|
||||||
|
</tr>
|
||||||
|
{{- end -}}
|
||||||
|
{{- if $data.NonZeroBalanceTokens -}}
|
||||||
|
<tr>
|
||||||
|
<td colspan="4"><a href="?tokenlevel=used">Show all XPUB addresses</a></td>
|
||||||
|
</tr>
|
||||||
|
{{- end -}}
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
{{- end -}}
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
<div class="col-md-2">
|
||||||
|
<div id="qrcode" style="width: 160px; height: 160px;"></div>
|
||||||
|
<script type="text/javascript" src="/static/js/qrcode.min.js"></script>
|
||||||
|
<script type="text/javascript">
|
||||||
|
new QRCode(document.getElementById("qrcode"), { text: "{{$addr.AddrStr}}", width: 160, height: 160 });
|
||||||
|
</script>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{{- if $addr.UnconfirmedTxs -}}
|
||||||
|
<h3>Unconfirmed</h3>
|
||||||
|
<div class="data-div">
|
||||||
|
<table class="table data-table">
|
||||||
|
<tbody>
|
||||||
|
<tr>
|
||||||
|
<td style="width: 25%;">Unconfirmed Balance</td>
|
||||||
|
<td class="data">{{formatAmount $addr.UnconfirmedBalanceSat}} {{$cs}}</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td>No. Transactions</td>
|
||||||
|
<td class="data">{{$addr.UnconfirmedTxs}}</td>
|
||||||
|
</tr>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
{{- end}}{{if or $addr.Transactions $addr.Filter -}}
|
||||||
|
<div class="row h-container">
|
||||||
|
<h3 class="col-md-3">Transactions</h3>
|
||||||
|
<select class="col-md-2" style="background-color: #eaeaea;" onchange="self.location='?filter='+options[selectedIndex].value">
|
||||||
|
<option>All</option>
|
||||||
|
<option {{if eq $addr.Filter "inputs" -}} selected{{end}} value="inputs">Inputs</option>
|
||||||
|
<option {{if eq $addr.Filter "outputs" -}} selected{{end}} value="outputs">Outputs</option>
|
||||||
|
</select>
|
||||||
|
<div class="col-md-7">
|
||||||
|
<nav>{{template "paging" $data}}</nav>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="data-div">
|
||||||
|
{{- range $tx := $addr.Transactions}}{{$data := setTxToTemplateData $data $tx}}{{template "txdetail" $data}}{{end -}}
|
||||||
|
</div>
|
||||||
|
<nav>{{template "paging" $data }}</nav>
|
||||||
|
{{end}}{{end}}
|
||||||
@ -304,7 +304,8 @@
|
|||||||
<input type="text" placeholder="descriptor" style="width: 79%" class="form-control" id="getAccountInfoDescriptor" value="0xba98d6a5ac827632e3457de7512d211e4ff7e8bd">
|
<input type="text" placeholder="descriptor" style="width: 79%" class="form-control" id="getAccountInfoDescriptor" value="0xba98d6a5ac827632e3457de7512d211e4ff7e8bd">
|
||||||
<select id="getAccountInfoDetails" style="width: 20%; margin-left: 5px;">
|
<select id="getAccountInfoDetails" style="width: 20%; margin-left: 5px;">
|
||||||
<option value="basic">Basic</option>
|
<option value="basic">Basic</option>
|
||||||
<option value="balance">Balance</option>
|
<option value="tokens">Tokens</option>
|
||||||
|
<option value="tokenBalances">TokenBalances</option>
|
||||||
<option value="txids">Txids</option>
|
<option value="txids">Txids</option>
|
||||||
<option value="txs">Transactions</option>
|
<option value="txs">Transactions</option>
|
||||||
</select>
|
</select>
|
||||||
|
|||||||
@ -16,14 +16,16 @@ const (
|
|||||||
TxidB2T3 = "05e2e48aeabdd9b75def7b48d756ba304713c2aba7b522bf9dbc893fc4231b07"
|
TxidB2T3 = "05e2e48aeabdd9b75def7b48d756ba304713c2aba7b522bf9dbc893fc4231b07"
|
||||||
TxidB2T4 = "fdd824a780cbb718eeb766eb05d83fdefc793a27082cd5e67f856d69798cf7db"
|
TxidB2T4 = "fdd824a780cbb718eeb766eb05d83fdefc793a27082cd5e67f856d69798cf7db"
|
||||||
|
|
||||||
|
Xpub = "upub5E1xjDmZ7Hhej6LPpS8duATdKXnRYui7bDYj6ehfFGzWDZtmCmQkZhc3Zb7kgRLtHWd16QFxyP86JKL3ShZEBFX88aciJ3xyocuyhZZ8g6q"
|
||||||
|
|
||||||
Addr1 = "mfcWp7DB6NuaZsExybTTXpVgWz559Np4Ti" // 76a914010d39800f86122416e28f485029acf77507169288ac
|
Addr1 = "mfcWp7DB6NuaZsExybTTXpVgWz559Np4Ti" // 76a914010d39800f86122416e28f485029acf77507169288ac
|
||||||
Addr2 = "mtGXQvBowMkBpnhLckhxhbwYK44Gs9eEtz" // 76a9148bdf0aa3c567aa5975c2e61321b8bebbe7293df688ac
|
Addr2 = "mtGXQvBowMkBpnhLckhxhbwYK44Gs9eEtz" // 76a9148bdf0aa3c567aa5975c2e61321b8bebbe7293df688ac
|
||||||
Addr3 = "mv9uLThosiEnGRbVPS7Vhyw6VssbVRsiAw" // 76a914a08eae93007f22668ab5e4a9c83c8cd1c325e3e088ac
|
Addr3 = "mv9uLThosiEnGRbVPS7Vhyw6VssbVRsiAw" // 76a914a08eae93007f22668ab5e4a9c83c8cd1c325e3e088ac
|
||||||
Addr4 = "2Mz1CYoppGGsLNUGF2YDhTif6J661JitALS" // a9144a21db08fb6882cb152e1ff06780a430740f770487
|
Addr4 = "2MzmAKayJmja784jyHvRUW1bXPget1csRRG" // a91452724c5178682f70e0ba31c6ec0633755a3b41d987, xpub m/49'/1'/33'/0/0
|
||||||
Addr5 = "2NEVv9LJmAnY99W1pFoc5UJjVdypBqdnvu1" // a914e921fc4912a315078f370d959f2c4f7b6d2a683c87
|
Addr5 = "2NEVv9LJmAnY99W1pFoc5UJjVdypBqdnvu1" // a914e921fc4912a315078f370d959f2c4f7b6d2a683c87
|
||||||
Addr6 = "mzB8cYrfRwFRFAGTDzV8LkUQy5BQicxGhX" // 76a914ccaaaf374e1b06cb83118453d102587b4273d09588ac
|
Addr6 = "mzB8cYrfRwFRFAGTDzV8LkUQy5BQicxGhX" // 76a914ccaaaf374e1b06cb83118453d102587b4273d09588ac
|
||||||
Addr7 = "mtR97eM2HPWVM6c8FGLGcukgaHHQv7THoL" // 76a9148d802c045445df49613f6a70ddd2e48526f3701f88ac
|
Addr7 = "mtR97eM2HPWVM6c8FGLGcukgaHHQv7THoL" // 76a9148d802c045445df49613f6a70ddd2e48526f3701f88ac
|
||||||
Addr8 = "mwwoKQE5Lb1G4picHSHDQKg8jw424PF9SC" // 76a914b434eb0c1a3b7a02e8a29cc616e791ef1e0bf51f88ac
|
Addr8 = "2N6utyMZfPNUb1Bk8oz7p2JqJrXkq83gegu" // a91495e9fbe306449c991d314afe3c3567d5bf78efd287, xpub m/49'/1'/33'/1/3
|
||||||
Addr9 = "mmJx9Y8ayz9h14yd9fgCW1bUKoEpkBAquP" // 76a9143f8ba3fda3ba7b69f5818086e12223c6dd25e3c888ac
|
Addr9 = "mmJx9Y8ayz9h14yd9fgCW1bUKoEpkBAquP" // 76a9143f8ba3fda3ba7b69f5818086e12223c6dd25e3c888ac
|
||||||
AddrA = "mzVznVsCHkVHX9UN8WPFASWUUHtxnNn4Jj" // 76a914d03c0d863d189b23b061a95ad32940b65837609f88ac
|
AddrA = "mzVznVsCHkVHX9UN8WPFASWUUHtxnNn4Jj" // 76a914d03c0d863d189b23b061a95ad32940b65837609f88ac
|
||||||
)
|
)
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user