Merge branch 'xpub'
This commit is contained in:
commit
dda96b4a8f
8
.gitignore
vendored
8
.gitignore
vendored
@ -6,8 +6,10 @@ notes.txt
|
||||
debug*
|
||||
.vscode
|
||||
docker/blockbook
|
||||
build
|
||||
!build/templates
|
||||
!build/docker
|
||||
build/pkg-defs
|
||||
build/blockbook
|
||||
build/ldb
|
||||
build/sst_dump
|
||||
build/*.deb
|
||||
.bin-image
|
||||
.deb-image
|
||||
4
Gopkg.lock
generated
4
Gopkg.lock
generated
@ -94,8 +94,8 @@
|
||||
[[projects]]
|
||||
branch = "master"
|
||||
name = "github.com/martinboehm/btcutil"
|
||||
packages = [".","base58","bech32","chaincfg","txscript"]
|
||||
revision = "613fec26904062ae125fb073762af3a77c77b6c7"
|
||||
packages = [".","base58","bech32","chaincfg","hdkeychain","txscript"]
|
||||
revision = "63034958e64b209cb9294128309dbaed497cde7b"
|
||||
|
||||
[[projects]]
|
||||
branch = "master"
|
||||
|
||||
101
api/types.go
101
api/types.go
@ -5,28 +5,36 @@ import (
|
||||
"blockbook/common"
|
||||
"blockbook/db"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"math/big"
|
||||
"time"
|
||||
)
|
||||
|
||||
const maxUint32 = ^uint32(0)
|
||||
const maxInt = int(^uint(0) >> 1)
|
||||
const maxInt64 = int64(^uint64(0) >> 1)
|
||||
|
||||
// GetAddressOption specifies what data returns GetAddress api call
|
||||
type GetAddressOption int
|
||||
// AccountDetails specifies what data returns GetAddress and GetXpub calls
|
||||
type AccountDetails int
|
||||
|
||||
const (
|
||||
// Basic - only that address is indexed and some basic info
|
||||
Basic GetAddressOption = iota
|
||||
// Balance - only balances
|
||||
Balance
|
||||
// TxidHistory - balances and txids, subject to paging
|
||||
TxidHistory
|
||||
// TxHistoryLight - balances and easily obtained tx data (not requiring request to backend), subject to paging
|
||||
TxHistoryLight
|
||||
// TxHistory - balances and full tx data, subject to paging
|
||||
TxHistory
|
||||
// AccountDetailsBasic - only that address is indexed and some basic info
|
||||
AccountDetailsBasic AccountDetails = iota
|
||||
// AccountDetailsTokens - basic info + tokens
|
||||
AccountDetailsTokens
|
||||
// AccountDetailsTokenBalances - basic info + token with balance
|
||||
AccountDetailsTokenBalances
|
||||
// AccountDetailsTxidHistory - basic + token balances + txids, subject to paging
|
||||
AccountDetailsTxidHistory
|
||||
// AccountDetailsTxHistoryLight - basic + tokens + easily obtained tx data (not requiring requests to backend), subject to paging
|
||||
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
|
||||
type APIError struct {
|
||||
Text string
|
||||
@ -48,6 +56,11 @@ func NewAPIError(s string, public bool) error {
|
||||
// Amount is datatype holding amounts
|
||||
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
|
||||
func (a *Amount) MarshalJSON() (out []byte, err error) {
|
||||
if a == nil {
|
||||
@ -123,16 +136,22 @@ type TokenType string
|
||||
// ERC20TokenType is Ethereum ERC20 token
|
||||
const ERC20TokenType TokenType = "ERC20"
|
||||
|
||||
// XPUBAddressTokenType is address derived from xpub
|
||||
const XPUBAddressTokenType TokenType = "XPUBAddress"
|
||||
|
||||
// Token contains info about tokens held by an address
|
||||
type Token struct {
|
||||
Type TokenType `json:"type"`
|
||||
Contract string `json:"contract"`
|
||||
Transfers int `json:"transfers"`
|
||||
Name string `json:"name"`
|
||||
Symbol string `json:"symbol"`
|
||||
Decimals int `json:"decimals"`
|
||||
BalanceSat *Amount `json:"balance,omitempty"`
|
||||
ContractIndex string `json:"-"`
|
||||
Type TokenType `json:"type"`
|
||||
Name string `json:"name"`
|
||||
Path string `json:"path,omitempty"`
|
||||
Contract string `json:"contract,omitempty"`
|
||||
Transfers int `json:"transfers"`
|
||||
Symbol string `json:"symbol,omitempty"`
|
||||
Decimals int `json:"decimals,omitempty"`
|
||||
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
|
||||
@ -185,6 +204,9 @@ type Paging struct {
|
||||
ItemsOnPage int `json:"itemsOnPage,omitempty"`
|
||||
}
|
||||
|
||||
// TokenDetailLevel specifies detail level of tokens returned by GetAddress and GetXpubAddress
|
||||
type TokenDetailLevel int
|
||||
|
||||
const (
|
||||
// AddressFilterVoutOff disables filtering of transactions by vout
|
||||
AddressFilterVoutOff = -1
|
||||
@ -192,6 +214,13 @@ const (
|
||||
AddressFilterVoutInputs = -2
|
||||
// AddressFilterVoutOutputs specifies that only txs where the address is as output are returned
|
||||
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
|
||||
@ -200,6 +229,9 @@ type AddressFilter struct {
|
||||
Contract string
|
||||
FromHeight 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
|
||||
@ -216,18 +248,41 @@ type Address struct {
|
||||
Transactions []*Tx `json:"transactions,omitempty"`
|
||||
Txids []string `json:"txids,omitempty"`
|
||||
Nonce string `json:"nonce,omitempty"`
|
||||
TotalTokens int `json:"totalTokens,omitempty"`
|
||||
Tokens []Token `json:"tokens,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
|
||||
type AddressUtxo struct {
|
||||
// Utxo is one unspent transaction output
|
||||
type Utxo struct {
|
||||
Txid string `json:"txid"`
|
||||
Vout int32 `json:"vout"`
|
||||
AmountSat *Amount `json:"value"`
|
||||
Height int `json:"height,omitempty"`
|
||||
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
|
||||
|
||||
@ -192,7 +192,7 @@ func (w *Worker) AddressToV1(a *Address) *AddressV1 {
|
||||
}
|
||||
|
||||
// AddressUtxoToV1 converts []AddressUtxo to []AddressUtxoV1
|
||||
func (w *Worker) AddressUtxoToV1(au []AddressUtxo) []AddressUtxoV1 {
|
||||
func (w *Worker) AddressUtxoToV1(au Utxos) []AddressUtxoV1 {
|
||||
d := w.chainParser.AmountDecimals()
|
||||
v1 := make([]AddressUtxoV1, len(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
|
||||
// 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 {
|
||||
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 {
|
||||
// take only inputs
|
||||
if index < 0 {
|
||||
@ -364,7 +364,7 @@ func (w *Worker) getAddressTxids(addrDesc bchain.AddressDescriptor, mempool bool
|
||||
} else {
|
||||
to := filter.ToHeight
|
||||
if to == 0 {
|
||||
to = ^uint32(0)
|
||||
to = maxUint32
|
||||
}
|
||||
err = w.db.GetAddrDescTransactions(addrDesc, filter.FromHeight, to, callback)
|
||||
if err != nil {
|
||||
@ -479,7 +479,7 @@ func computePaging(count, page, itemsOnPage int) (Paging, int, int, int) {
|
||||
}, 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 (
|
||||
ba *db.AddrBalance
|
||||
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)
|
||||
}
|
||||
}
|
||||
tokens = make([]Token, len(ca.Contracts))
|
||||
var j int
|
||||
for i, c := range ca.Contracts {
|
||||
if len(filterDesc) > 0 {
|
||||
if !bytes.Equal(filterDesc, c.Contract) {
|
||||
continue
|
||||
if details > AccountDetailsBasic {
|
||||
tokens = make([]Token, len(ca.Contracts))
|
||||
var j int
|
||||
for i, c := range ca.Contracts {
|
||||
if len(filterDesc) > 0 {
|
||||
if !bytes.Equal(filterDesc, c.Contract) {
|
||||
continue
|
||||
}
|
||||
// filter only transactions of this contract
|
||||
filter.Vout = i + 1
|
||||
}
|
||||
// filter only transactions of this contract
|
||||
filter.Vout = i + 1
|
||||
}
|
||||
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)
|
||||
validContract := true
|
||||
ci, err := w.chain.EthereumTypeGetErc20ContractInfo(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)
|
||||
return nil, nil, nil, 0, 0, 0, errors.Annotatef(err, "EthereumTypeGetErc20ContractInfo %v", c.Contract)
|
||||
}
|
||||
} else {
|
||||
b = nil
|
||||
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 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{
|
||||
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]
|
||||
}
|
||||
tokens = tokens[:j]
|
||||
ci, err = w.chain.EthereumTypeGetErc20ContractInfo(addrDesc)
|
||||
if err != nil {
|
||||
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
|
||||
}
|
||||
|
||||
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
|
||||
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()
|
||||
page--
|
||||
if page < 0 {
|
||||
page = 0
|
||||
}
|
||||
addrDesc, err := w.chainParser.GetAddrDescFromAddress(address)
|
||||
if err != nil {
|
||||
return nil, NewAPIError(fmt.Sprintf("Invalid address, %v", err), true)
|
||||
}
|
||||
var (
|
||||
ba *db.AddrBalance
|
||||
tokens []Token
|
||||
@ -613,6 +660,10 @@ func (w *Worker) GetAddress(address string, page int, txsOnPage int, option GetA
|
||||
nonTokenTxs int
|
||||
totalResults int
|
||||
)
|
||||
addrDesc, address, err := w.getAddrDescAndNormalizeAddress(address)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if w.chainType == bchain.ChainEthereumType {
|
||||
var n uint64
|
||||
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 option >= TxidHistory || ba == nil {
|
||||
// convert the address to the format defined by the parser
|
||||
addresses, _, err := w.chainParser.GetAddressesFromAddrDesc(addrDesc)
|
||||
// if there are only unconfirmed transactions, there is no paging
|
||||
if ba == nil {
|
||||
ba = &db.AddrBalance{}
|
||||
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 {
|
||||
glog.V(2).Infof("GetAddressesFromAddrDesc error %v, %v", err, addrDesc)
|
||||
return nil, errors.Annotatef(err, "getAddressTxids %v true", addrDesc)
|
||||
}
|
||||
if len(addresses) == 1 {
|
||||
address = addresses[0]
|
||||
}
|
||||
// get txs from mempool only if blockheight filter is off
|
||||
if filter.FromHeight == 0 && filter.ToHeight == 0 {
|
||||
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)
|
||||
for _, txid := range txm {
|
||||
tx, err := w.GetTransaction(txid, false, false)
|
||||
// mempool transaction may fail
|
||||
if err != nil || tx == nil {
|
||||
glog.Warning("GetTransaction in mempool: ", err)
|
||||
} else {
|
||||
txs = make([]*Tx, len(txm)+to-from)
|
||||
}
|
||||
txi := 0
|
||||
// get mempool transactions
|
||||
for _, txid := range txm {
|
||||
tx, err := w.GetTransaction(txid, false, false)
|
||||
// mempool transaction may fail
|
||||
if err != nil || tx == nil {
|
||||
glog.Warning("GetTransaction in mempool: ", err)
|
||||
} 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++
|
||||
// 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 == AccountDetailsTxidHistory {
|
||||
txids = append(txids, tx.Txid)
|
||||
} else if option >= AccountDetailsTxHistoryLight {
|
||||
txs = append(txs, tx)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
// get confirmed transactions
|
||||
for i := from; i < to; i++ {
|
||||
txid := txc[i]
|
||||
if option == TxidHistory {
|
||||
txids[txi] = txid
|
||||
} else {
|
||||
// only ChainBitcoinType supports TxHistoryLight
|
||||
if option == TxHistoryLight && 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{}
|
||||
}
|
||||
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++
|
||||
}
|
||||
}
|
||||
// get tx history if requested by option or check mempool if there are some transactions for a new address
|
||||
if option >= AccountDetailsTxidHistory {
|
||||
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 = txids[:txi]
|
||||
} else if option >= TxHistoryLight {
|
||||
txs = txs[:txi]
|
||||
}
|
||||
for i := from; i < to; i++ {
|
||||
txid := txc[i]
|
||||
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
|
||||
}
|
||||
|
||||
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
|
||||
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 {
|
||||
return nil, NewAPIError("Not supported", true)
|
||||
}
|
||||
start := time.Now()
|
||||
addrDesc, err := w.chainParser.GetAddrDescFromAddress(address)
|
||||
if err != nil {
|
||||
return nil, NewAPIError(fmt.Sprintf("Invalid address, %v", 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")
|
||||
return nil, NewAPIError(fmt.Sprintf("Invalid address '%v', %v", address, err), true)
|
||||
}
|
||||
r, err := w.getAddrDescUtxo(addrDesc, nil, onlyConfirmed, false)
|
||||
glog.Info("GetAddressUtxo ", address, ", ", len(r), " utxos, finished in ", time.Since(start))
|
||||
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
|
||||
var hash string
|
||||
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))
|
||||
if err != nil {
|
||||
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
|
||||
}
|
||||
|
||||
// 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
|
||||
func (p *BaseParser) EthereumTypeGetErc20FromTx(tx *Tx) ([]Erc20Transfer, error) {
|
||||
return nil, errors.New("Not supported")
|
||||
|
||||
@ -6,12 +6,15 @@ import (
|
||||
"encoding/binary"
|
||||
"encoding/hex"
|
||||
"math/big"
|
||||
"strconv"
|
||||
|
||||
vlq "github.com/bsm/go-vlq"
|
||||
"github.com/juju/errors"
|
||||
"github.com/martinboehm/btcd/blockchain"
|
||||
"github.com/martinboehm/btcd/wire"
|
||||
"github.com/martinboehm/btcutil"
|
||||
"github.com/martinboehm/btcutil/chaincfg"
|
||||
"github.com/martinboehm/btcutil/hdkeychain"
|
||||
"github.com/martinboehm/btcutil/txscript"
|
||||
)
|
||||
|
||||
@ -23,6 +26,10 @@ type BitcoinParser struct {
|
||||
*bchain.BaseParser
|
||||
Params *chaincfg.Params
|
||||
OutputScriptToAddressesFunc OutputScriptToAddressesFunc
|
||||
XPubMagic uint32
|
||||
XPubMagicSegwitP2sh uint32
|
||||
XPubMagicSegwitNative uint32
|
||||
Slip44 uint32
|
||||
}
|
||||
|
||||
// NewBitcoinParser returns new BitcoinParser instance
|
||||
@ -32,7 +39,11 @@ func NewBitcoinParser(params *chaincfg.Params, c *Configuration) *BitcoinParser
|
||||
BlockAddressesToKeep: c.BlockAddressesToKeep,
|
||||
AmountDecimalPoint: 8,
|
||||
},
|
||||
Params: params,
|
||||
Params: params,
|
||||
XPubMagic: c.XPubMagic,
|
||||
XPubMagicSegwitP2sh: c.XPubMagicSegwitP2sh,
|
||||
XPubMagicSegwitNative: c.XPubMagicSegwitNative,
|
||||
Slip44: c.Slip44,
|
||||
}
|
||||
p.OutputScriptToAddressesFunc = p.outputScriptToAddresses
|
||||
return p
|
||||
@ -266,3 +277,104 @@ func (p *BitcoinParser) UnpackTx(buf []byte) (*bchain.Tx, uint32, error) {
|
||||
|
||||
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)
|
||||
}
|
||||
|
||||
func Test_GetAddrDescFromAddress(t *testing.T) {
|
||||
func TestGetAddrDescFromAddress(t *testing.T) {
|
||||
type args struct {
|
||||
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 {
|
||||
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 {
|
||||
script string
|
||||
}
|
||||
@ -316,7 +316,7 @@ func init() {
|
||||
}
|
||||
}
|
||||
|
||||
func Test_PackTx(t *testing.T) {
|
||||
func TestPackTx(t *testing.T) {
|
||||
type args struct {
|
||||
tx bchain.Tx
|
||||
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 {
|
||||
packedTx string
|
||||
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"`
|
||||
SupportsEstimateFee bool `json:"supports_estimate_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.
|
||||
|
||||
@ -263,6 +263,10 @@ type BlockChainParser interface {
|
||||
PackBlockHash(hash string) ([]byte, error)
|
||||
UnpackBlockHash(buf []byte) (string, 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
|
||||
EthereumTypeGetErc20FromTx(tx *Tx) ([]Erc20Transfer, error)
|
||||
}
|
||||
|
||||
@ -3,9 +3,8 @@
|
||||
{{- if .Blockbook.BlockChain.AdditionalParams}}
|
||||
{{- range $name, $value := .Blockbook.BlockChain.AdditionalParams}}
|
||||
"{{$name}}": {{jsonToString $value}},
|
||||
{{- end}}
|
||||
{{- end -}}
|
||||
{{end}}
|
||||
|
||||
"coin_name": "{{.Coin.Name}}",
|
||||
"coin_shortcut": "{{.Coin.Shortcut}}",
|
||||
"coin_label": "{{.Coin.Label}}",
|
||||
@ -17,6 +16,11 @@
|
||||
"message_queue_binding": "{{template "IPC.MessageQueueBindingTemplate" .}}",
|
||||
"subversion": "{{.Blockbook.BlockChain.Subversion}}",
|
||||
"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_sub_workers": {{.Blockbook.BlockChain.MempoolSubWorkers}},
|
||||
"block_addresses_to_keep": {{.Blockbook.BlockChain.BlockAddressesToKeep}}
|
||||
|
||||
@ -13,18 +13,6 @@ import (
|
||||
)
|
||||
|
||||
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 {
|
||||
Name string `json:"name"`
|
||||
Shortcut string `json:"shortcut"`
|
||||
@ -63,7 +51,7 @@ type Config struct {
|
||||
Mainnet bool `json:"mainnet"`
|
||||
ServerConfigFile string `json:"server_config_file"`
|
||||
ClientConfigFile string `json:"client_config_file"`
|
||||
AdditionalParams interface{} `json:"additional_params"`
|
||||
AdditionalParams interface{} `json:"additional_params,omitempty"`
|
||||
} `json:"backend"`
|
||||
Blockbook struct {
|
||||
PackageName string `json:"package_name"`
|
||||
@ -73,16 +61,32 @@ type Config struct {
|
||||
ExplorerURL string `json:"explorer_url"`
|
||||
AdditionalParams string `json:"additional_params"`
|
||||
BlockChain struct {
|
||||
Parse bool `json:"parse"`
|
||||
Subversion string `json:"subversion"`
|
||||
AddressFormat string `json:"address_format"`
|
||||
MempoolWorkers int `json:"mempool_workers"`
|
||||
MempoolSubWorkers int `json:"mempool_sub_workers"`
|
||||
BlockAddressesToKeep int `json:"block_addresses_to_keep"`
|
||||
AdditionalParams map[string]json.RawMessage `json:"additional_params"`
|
||||
Parse bool `json:"parse,omitempty"`
|
||||
Subversion string `json:"subversion,omitempty"`
|
||||
AddressFormat string `json:"address_format,omitempty"`
|
||||
MempoolWorkers int `json:"mempool_workers"`
|
||||
MempoolSubWorkers int `json:"mempool_sub_workers"`
|
||||
BlockAddressesToKeep int `json:"block_addresses_to_keep"`
|
||||
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:"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) {
|
||||
@ -266,7 +270,7 @@ func makeOutputDir(path string) 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 {
|
||||
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": {
|
||||
"name": "Bcash",
|
||||
"shortcut": "BCH",
|
||||
"label": "Bitcoin Cash",
|
||||
"alias": "bcash"
|
||||
"name": "Bcash",
|
||||
"shortcut": "BCH",
|
||||
"label": "Bitcoin Cash",
|
||||
"alias": "bcash"
|
||||
},
|
||||
"ports": {
|
||||
"backend_rpc": 8031,
|
||||
@ -28,7 +28,7 @@
|
||||
"verification_source": "788001fd5ce8ca0bd61e8a92f10d22b7a49695c3651c60fad11358ba29309a1b",
|
||||
"extract_command": "tar -C backend --strip 1 -xf",
|
||||
"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",
|
||||
"logrotate_files_template": "{{.Env.BackendDataPath}}/{{.Coin.Alias}}/backend/*.log",
|
||||
@ -54,6 +54,8 @@
|
||||
"mempool_workers": 8,
|
||||
"mempool_sub_workers": 2,
|
||||
"block_addresses_to_keep": 300,
|
||||
"xpub_magic": 76067358,
|
||||
"slip44": 145,
|
||||
"additional_params": {}
|
||||
}
|
||||
},
|
||||
@ -61,4 +63,4 @@
|
||||
"package_maintainer": "Petr Kracik",
|
||||
"package_maintainer_email": "petr.kracik@satoshilabs.com"
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -1,9 +1,9 @@
|
||||
{
|
||||
"coin": {
|
||||
"name": "Bcash Testnet",
|
||||
"shortcut": "TBCH",
|
||||
"label": "Bitcoin Cash Testnet",
|
||||
"alias": "bcash_testnet"
|
||||
"name": "Bcash Testnet",
|
||||
"shortcut": "TBCH",
|
||||
"label": "Bitcoin Cash Testnet",
|
||||
"alias": "bcash_testnet"
|
||||
},
|
||||
"ports": {
|
||||
"backend_rpc": 18031,
|
||||
@ -28,7 +28,7 @@
|
||||
"verification_source": "788001fd5ce8ca0bd61e8a92f10d22b7a49695c3651c60fad11358ba29309a1b",
|
||||
"extract_command": "tar -C backend --strip 1 -xf",
|
||||
"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",
|
||||
"logrotate_files_template": "{{.Env.BackendDataPath}}/{{.Coin.Alias}}/backend/testnet3/*.log",
|
||||
@ -54,6 +54,8 @@
|
||||
"mempool_workers": 8,
|
||||
"mempool_sub_workers": 2,
|
||||
"block_addresses_to_keep": 300,
|
||||
"xpub_magic": 70617039,
|
||||
"slip44": 1,
|
||||
"additional_params": {}
|
||||
}
|
||||
},
|
||||
@ -61,4 +63,4 @@
|
||||
"package_maintainer": "Petr Kracik",
|
||||
"package_maintainer_email": "petr.kracik@satoshilabs.com"
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -1,9 +1,9 @@
|
||||
{
|
||||
"coin": {
|
||||
"name": "Bgold",
|
||||
"shortcut": "BTG",
|
||||
"label": "Bitcoin Gold",
|
||||
"alias": "bgold"
|
||||
"name": "Bgold",
|
||||
"shortcut": "BTG",
|
||||
"label": "Bitcoin Gold",
|
||||
"alias": "bgold"
|
||||
},
|
||||
"ports": {
|
||||
"backend_rpc": 8035,
|
||||
@ -28,7 +28,7 @@
|
||||
"verification_source": "https://github.com/BTCGPU/BTCGPU/releases/download/v0.15.2/SHA256SUMS.asc",
|
||||
"extract_command": "tar -C backend --strip 1 -xf",
|
||||
"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",
|
||||
"logrotate_files_template": "{{.Env.BackendDataPath}}/{{.Coin.Alias}}/backend/*.log",
|
||||
@ -40,199 +40,199 @@
|
||||
"server_config_file": "bitcoin_like.conf",
|
||||
"client_config_file": "bitcoin_like_client.conf",
|
||||
"additional_params": {
|
||||
"mempoolexpiry": 72,
|
||||
"timeout": 768,
|
||||
"maxconnections": 250,
|
||||
"addnode": [
|
||||
"188.126.0.134",
|
||||
"45.56.84.44",
|
||||
"109.201.133.93:8338",
|
||||
"178.63.11.246:8338",
|
||||
"188.120.223.153:8338",
|
||||
"79.137.64.158:8338",
|
||||
"78.193.221.106:8338",
|
||||
"139.59.151.13:8338",
|
||||
"76.16.12.81:8338",
|
||||
"172.104.157.62:8338",
|
||||
"43.207.67.209:8338",
|
||||
"178.63.11.246:8338",
|
||||
"79.137.64.158:8338",
|
||||
"78.193.221.106:8338",
|
||||
"139.59.151.13:8338",
|
||||
"172.104.157.62:8338",
|
||||
"178.158.247.119:8338",
|
||||
"109.201.133.93:8338",
|
||||
"178.63.11.246:8338",
|
||||
"139.59.151.13:8338",
|
||||
"172.104.157.62:8338",
|
||||
"188.120.223.153:8338",
|
||||
"178.158.247.119:8338",
|
||||
"78.193.221.106:8338",
|
||||
"79.137.64.158:8338",
|
||||
"76.16.12.81:8338",
|
||||
"176.12.32.153:8338",
|
||||
"178.158.247.122:8338",
|
||||
"81.37.147.185:8338",
|
||||
"176.12.32.153:8338",
|
||||
"79.137.64.158:8338",
|
||||
"178.158.247.122:8338",
|
||||
"66.70.247.151:8338",
|
||||
"89.18.27.165:8338",
|
||||
"178.63.11.246:8338",
|
||||
"91.222.17.86:8338",
|
||||
"37.59.50.143:8338",
|
||||
"91.50.219.221:8338",
|
||||
"154.16.63.17:8338",
|
||||
"213.136.76.42:8338",
|
||||
"176.99.4.140:8338",
|
||||
"176.9.48.36:8338",
|
||||
"78.193.221.106:8338",
|
||||
"34.236.228.99:8338",
|
||||
"213.154.230.107:8338",
|
||||
"111.231.66.252:8338",
|
||||
"188.120.223.153:8338",
|
||||
"219.89.122.82:8338",
|
||||
"109.192.23.101:8338",
|
||||
"98.114.91.222:8338",
|
||||
"217.66.156.41:8338",
|
||||
"172.104.157.62:8338",
|
||||
"114.44.222.73:8338",
|
||||
"91.224.140.216:8338",
|
||||
"149.154.71.96:8338",
|
||||
"107.181.183.242:8338",
|
||||
"36.78.96.92:8338",
|
||||
"46.22.7.74:8338",
|
||||
"89.110.53.186:8338",
|
||||
"73.243.220.85:8338",
|
||||
"109.86.137.8:8338",
|
||||
"77.78.12.89:8338",
|
||||
"87.92.116.26:8338",
|
||||
"93.78.122.48:8338",
|
||||
"35.195.83.0:8338",
|
||||
"46.147.75.220:8338",
|
||||
"212.47.236.104:8338",
|
||||
"95.220.100.230:8338",
|
||||
"178.70.142.247:8338",
|
||||
"45.76.136.149:8338",
|
||||
"94.155.74.206:8338",
|
||||
"178.70.142.247:8338",
|
||||
"128.199.228.97:8338",
|
||||
"77.171.144.207:8338",
|
||||
"159.89.192.119:8338",
|
||||
"136.63.238.170:8338",
|
||||
"31.27.193.105:8338",
|
||||
"176.107.192.240:8338",
|
||||
"94.140.241.96:8338",
|
||||
"66.108.15.5:8338",
|
||||
"81.177.127.204:8338",
|
||||
"88.18.69.174:8338",
|
||||
"178.70.130.94:8338",
|
||||
"78.98.162.140:8338",
|
||||
"95.133.156.224:8338",
|
||||
"46.188.16.96:8338",
|
||||
"94.247.16.21:8338",
|
||||
"eunode.pool.gold:8338",
|
||||
"asianode.pool.gold:8338",
|
||||
"45.56.84.44:8338",
|
||||
"176.9.48.36:8338",
|
||||
"93.57.253.121:8338",
|
||||
"172.104.157.62:8338",
|
||||
"176.12.32.153:8338",
|
||||
"pool.serverpower.net:8338",
|
||||
"213.154.229.126:8338",
|
||||
"213.154.230.106:8338",
|
||||
"213.154.230.107:8338",
|
||||
"213.154.229.50:8338",
|
||||
"145.239.0.50:8338",
|
||||
"107.181.183.242:8338",
|
||||
"109.201.133.93:8338",
|
||||
"120.41.190.109:8338",
|
||||
"120.41.191.224:8338",
|
||||
"138.68.249.79:8338",
|
||||
"13.95.223.202:8338",
|
||||
"145.239.0.50:8338",
|
||||
"149.56.95.26:8338",
|
||||
"158.69.103.228:8338",
|
||||
"159.89.192.119:8338",
|
||||
"164.132.207.143:8338",
|
||||
"171.100.141.106:8338",
|
||||
"172.104.157.62:8338",
|
||||
"173.176.95.92:8338",
|
||||
"176.12.32.153:8338",
|
||||
"178.239.54.250:8338",
|
||||
"178.63.11.246:8338",
|
||||
"185.139.2.140:8338",
|
||||
"188.120.223.153:8338",
|
||||
"190.46.2.92:8338",
|
||||
"192.99.194.113:8338",
|
||||
"199.229.248.218:8338",
|
||||
"213.154.229.126:8338",
|
||||
"213.154.229.50:8338",
|
||||
"213.154.230.106:8338",
|
||||
"213.154.230.107:8338",
|
||||
"217.182.199.21",
|
||||
"35.189.127.200:8338",
|
||||
"35.195.83.0:8338",
|
||||
"35.197.197.166:8338",
|
||||
"35.200.168.155:8338",
|
||||
"35.203.167.11:8338",
|
||||
"37.59.50.143:8338",
|
||||
"45.27.161.195:8338",
|
||||
"45.32.234.160:8338",
|
||||
"45.56.84.44:8338",
|
||||
"46.188.16.96:8338",
|
||||
"46.251.19.171:8338",
|
||||
"5.157.119.109:8338",
|
||||
"52.28.162.48:8338",
|
||||
"54.153.140.202:8338",
|
||||
"54.68.81.2:83388338",
|
||||
"62.195.190.190:8338",
|
||||
"62.216.5.136:8338",
|
||||
"65.110.125.175:8338",
|
||||
"67.68.226.130:8338",
|
||||
"73.243.220.85:8338",
|
||||
"77.78.12.89:8338",
|
||||
"78.193.221.106:8338",
|
||||
"78.98.162.140:8338",
|
||||
"79.137.64.158:8338",
|
||||
"84.144.177.238:8338",
|
||||
"87.92.116.26:8338",
|
||||
"89.115.139.117:8338",
|
||||
"89.18.27.165:8338",
|
||||
"91.50.219.221:8338",
|
||||
"93.88.74.26",
|
||||
"93.88.74.26:8338",
|
||||
"94.155.74.206:8338",
|
||||
"95.154.201.132:8338",
|
||||
"98.29.248.131:8338",
|
||||
"u2.my.to:8338",
|
||||
"[2001:470:b:ce:dc70:83ff:fe7a:1e74]:8338",
|
||||
"2001:7b8:61d:1:250:56ff:fe90:c89f:8338",
|
||||
"2001:7b8:63a:1002:213:154:230:106:8338",
|
||||
"2001:7b8:63a:1002:213:154:230:107:8338",
|
||||
"45.56.84.44",
|
||||
"109.201.133.93:8338",
|
||||
"120.41.191.224:30607",
|
||||
"138.68.249.79:50992",
|
||||
"138.68.249.79:51314",
|
||||
"172.104.157.62",
|
||||
"178.63.11.246:8338",
|
||||
"185.139.2.140:8338",
|
||||
"199.229.248.218:28830",
|
||||
"35.189.127.200:41220",
|
||||
"35.189.127.200:48244",
|
||||
"35.195.83.0:35172",
|
||||
"35.195.83.0:35576",
|
||||
"35.195.83.0:35798",
|
||||
"35.197.197.166:32794",
|
||||
"35.197.197.166:33112",
|
||||
"35.197.197.166:33332",
|
||||
"35.203.167.11:52158",
|
||||
"37.59.50.143:35254",
|
||||
"45.27.161.195:33852",
|
||||
"45.27.161.195:36738",
|
||||
"45.27.161.195:58628"
|
||||
]
|
||||
"addnode": [
|
||||
"188.126.0.134",
|
||||
"45.56.84.44",
|
||||
"109.201.133.93:8338",
|
||||
"178.63.11.246:8338",
|
||||
"188.120.223.153:8338",
|
||||
"79.137.64.158:8338",
|
||||
"78.193.221.106:8338",
|
||||
"139.59.151.13:8338",
|
||||
"76.16.12.81:8338",
|
||||
"172.104.157.62:8338",
|
||||
"43.207.67.209:8338",
|
||||
"178.63.11.246:8338",
|
||||
"79.137.64.158:8338",
|
||||
"78.193.221.106:8338",
|
||||
"139.59.151.13:8338",
|
||||
"172.104.157.62:8338",
|
||||
"178.158.247.119:8338",
|
||||
"109.201.133.93:8338",
|
||||
"178.63.11.246:8338",
|
||||
"139.59.151.13:8338",
|
||||
"172.104.157.62:8338",
|
||||
"188.120.223.153:8338",
|
||||
"178.158.247.119:8338",
|
||||
"78.193.221.106:8338",
|
||||
"79.137.64.158:8338",
|
||||
"76.16.12.81:8338",
|
||||
"176.12.32.153:8338",
|
||||
"178.158.247.122:8338",
|
||||
"81.37.147.185:8338",
|
||||
"176.12.32.153:8338",
|
||||
"79.137.64.158:8338",
|
||||
"178.158.247.122:8338",
|
||||
"66.70.247.151:8338",
|
||||
"89.18.27.165:8338",
|
||||
"178.63.11.246:8338",
|
||||
"91.222.17.86:8338",
|
||||
"37.59.50.143:8338",
|
||||
"91.50.219.221:8338",
|
||||
"154.16.63.17:8338",
|
||||
"213.136.76.42:8338",
|
||||
"176.99.4.140:8338",
|
||||
"176.9.48.36:8338",
|
||||
"78.193.221.106:8338",
|
||||
"34.236.228.99:8338",
|
||||
"213.154.230.107:8338",
|
||||
"111.231.66.252:8338",
|
||||
"188.120.223.153:8338",
|
||||
"219.89.122.82:8338",
|
||||
"109.192.23.101:8338",
|
||||
"98.114.91.222:8338",
|
||||
"217.66.156.41:8338",
|
||||
"172.104.157.62:8338",
|
||||
"114.44.222.73:8338",
|
||||
"91.224.140.216:8338",
|
||||
"149.154.71.96:8338",
|
||||
"107.181.183.242:8338",
|
||||
"36.78.96.92:8338",
|
||||
"46.22.7.74:8338",
|
||||
"89.110.53.186:8338",
|
||||
"73.243.220.85:8338",
|
||||
"109.86.137.8:8338",
|
||||
"77.78.12.89:8338",
|
||||
"87.92.116.26:8338",
|
||||
"93.78.122.48:8338",
|
||||
"35.195.83.0:8338",
|
||||
"46.147.75.220:8338",
|
||||
"212.47.236.104:8338",
|
||||
"95.220.100.230:8338",
|
||||
"178.70.142.247:8338",
|
||||
"45.76.136.149:8338",
|
||||
"94.155.74.206:8338",
|
||||
"178.70.142.247:8338",
|
||||
"128.199.228.97:8338",
|
||||
"77.171.144.207:8338",
|
||||
"159.89.192.119:8338",
|
||||
"136.63.238.170:8338",
|
||||
"31.27.193.105:8338",
|
||||
"176.107.192.240:8338",
|
||||
"94.140.241.96:8338",
|
||||
"66.108.15.5:8338",
|
||||
"81.177.127.204:8338",
|
||||
"88.18.69.174:8338",
|
||||
"178.70.130.94:8338",
|
||||
"78.98.162.140:8338",
|
||||
"95.133.156.224:8338",
|
||||
"46.188.16.96:8338",
|
||||
"94.247.16.21:8338",
|
||||
"eunode.pool.gold:8338",
|
||||
"asianode.pool.gold:8338",
|
||||
"45.56.84.44:8338",
|
||||
"176.9.48.36:8338",
|
||||
"93.57.253.121:8338",
|
||||
"172.104.157.62:8338",
|
||||
"176.12.32.153:8338",
|
||||
"pool.serverpower.net:8338",
|
||||
"213.154.229.126:8338",
|
||||
"213.154.230.106:8338",
|
||||
"213.154.230.107:8338",
|
||||
"213.154.229.50:8338",
|
||||
"145.239.0.50:8338",
|
||||
"107.181.183.242:8338",
|
||||
"109.201.133.93:8338",
|
||||
"120.41.190.109:8338",
|
||||
"120.41.191.224:8338",
|
||||
"138.68.249.79:8338",
|
||||
"13.95.223.202:8338",
|
||||
"145.239.0.50:8338",
|
||||
"149.56.95.26:8338",
|
||||
"158.69.103.228:8338",
|
||||
"159.89.192.119:8338",
|
||||
"164.132.207.143:8338",
|
||||
"171.100.141.106:8338",
|
||||
"172.104.157.62:8338",
|
||||
"173.176.95.92:8338",
|
||||
"176.12.32.153:8338",
|
||||
"178.239.54.250:8338",
|
||||
"178.63.11.246:8338",
|
||||
"185.139.2.140:8338",
|
||||
"188.120.223.153:8338",
|
||||
"190.46.2.92:8338",
|
||||
"192.99.194.113:8338",
|
||||
"199.229.248.218:8338",
|
||||
"213.154.229.126:8338",
|
||||
"213.154.229.50:8338",
|
||||
"213.154.230.106:8338",
|
||||
"213.154.230.107:8338",
|
||||
"217.182.199.21",
|
||||
"35.189.127.200:8338",
|
||||
"35.195.83.0:8338",
|
||||
"35.197.197.166:8338",
|
||||
"35.200.168.155:8338",
|
||||
"35.203.167.11:8338",
|
||||
"37.59.50.143:8338",
|
||||
"45.27.161.195:8338",
|
||||
"45.32.234.160:8338",
|
||||
"45.56.84.44:8338",
|
||||
"46.188.16.96:8338",
|
||||
"46.251.19.171:8338",
|
||||
"5.157.119.109:8338",
|
||||
"52.28.162.48:8338",
|
||||
"54.153.140.202:8338",
|
||||
"54.68.81.2:83388338",
|
||||
"62.195.190.190:8338",
|
||||
"62.216.5.136:8338",
|
||||
"65.110.125.175:8338",
|
||||
"67.68.226.130:8338",
|
||||
"73.243.220.85:8338",
|
||||
"77.78.12.89:8338",
|
||||
"78.193.221.106:8338",
|
||||
"78.98.162.140:8338",
|
||||
"79.137.64.158:8338",
|
||||
"84.144.177.238:8338",
|
||||
"87.92.116.26:8338",
|
||||
"89.115.139.117:8338",
|
||||
"89.18.27.165:8338",
|
||||
"91.50.219.221:8338",
|
||||
"93.88.74.26",
|
||||
"93.88.74.26:8338",
|
||||
"94.155.74.206:8338",
|
||||
"95.154.201.132:8338",
|
||||
"98.29.248.131:8338",
|
||||
"u2.my.to:8338",
|
||||
"[2001:470:b:ce:dc70:83ff:fe7a:1e74]:8338",
|
||||
"2001:7b8:61d:1:250:56ff:fe90:c89f:8338",
|
||||
"2001:7b8:63a:1002:213:154:230:106:8338",
|
||||
"2001:7b8:63a:1002:213:154:230:107:8338",
|
||||
"45.56.84.44",
|
||||
"109.201.133.93:8338",
|
||||
"120.41.191.224:30607",
|
||||
"138.68.249.79:50992",
|
||||
"138.68.249.79:51314",
|
||||
"172.104.157.62",
|
||||
"178.63.11.246:8338",
|
||||
"185.139.2.140:8338",
|
||||
"199.229.248.218:28830",
|
||||
"35.189.127.200:41220",
|
||||
"35.189.127.200:48244",
|
||||
"35.195.83.0:35172",
|
||||
"35.195.83.0:35576",
|
||||
"35.195.83.0:35798",
|
||||
"35.197.197.166:32794",
|
||||
"35.197.197.166:33112",
|
||||
"35.197.197.166:33332",
|
||||
"35.203.167.11:52158",
|
||||
"37.59.50.143:35254",
|
||||
"45.27.161.195:33852",
|
||||
"45.27.161.195:36738",
|
||||
"45.27.161.195:58628"
|
||||
],
|
||||
"maxconnections": 250,
|
||||
"mempoolexpiry": 72,
|
||||
"timeout": 768
|
||||
}
|
||||
},
|
||||
"blockbook": {
|
||||
@ -248,6 +248,9 @@
|
||||
"mempool_workers": 8,
|
||||
"mempool_sub_workers": 2,
|
||||
"block_addresses_to_keep": 300,
|
||||
"xpub_magic": 76067358,
|
||||
"xpub_magic_segwit_p2sh": 77429938,
|
||||
"slip44": 156,
|
||||
"additional_params": {}
|
||||
}
|
||||
},
|
||||
@ -255,4 +258,4 @@
|
||||
"package_maintainer": "Jakub Matys",
|
||||
"package_maintainer_email": "jakub.matys@satoshilabs.com"
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -1,9 +1,9 @@
|
||||
{
|
||||
"coin": {
|
||||
"name": "Bitcoin",
|
||||
"shortcut": "BTC",
|
||||
"label": "Bitcoin",
|
||||
"alias": "bitcoin"
|
||||
"name": "Bitcoin",
|
||||
"shortcut": "BTC",
|
||||
"label": "Bitcoin",
|
||||
"alias": "bitcoin"
|
||||
},
|
||||
"ports": {
|
||||
"backend_rpc": 8030,
|
||||
@ -28,7 +28,7 @@
|
||||
"verification_source": "https://bitcoin.org/bin/bitcoin-core-0.17.1/SHA256SUMS.asc",
|
||||
"extract_command": "tar -C backend --strip 1 -xf",
|
||||
"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",
|
||||
"logrotate_files_template": "{{.Env.BackendDataPath}}/{{.Coin.Alias}}/backend/*.log",
|
||||
@ -40,7 +40,7 @@
|
||||
"server_config_file": "bitcoin.conf",
|
||||
"client_config_file": "bitcoin_client.conf",
|
||||
"additional_params": {
|
||||
"deprecatedrpc": "estimatefee"
|
||||
"deprecatedrpc": "estimatefee"
|
||||
}
|
||||
},
|
||||
"blockbook": {
|
||||
@ -55,6 +55,9 @@
|
||||
"mempool_workers": 8,
|
||||
"mempool_sub_workers": 2,
|
||||
"block_addresses_to_keep": 300,
|
||||
"xpub_magic": 76067358,
|
||||
"xpub_magic_segwit_p2sh": 77429938,
|
||||
"xpub_magic_segwit_native": 78792518,
|
||||
"additional_params": {}
|
||||
}
|
||||
},
|
||||
@ -62,4 +65,4 @@
|
||||
"package_maintainer": "Jakub Matys",
|
||||
"package_maintainer_email": "jakub.matys@satoshilabs.com"
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -1,9 +1,9 @@
|
||||
{
|
||||
"coin": {
|
||||
"name": "Testnet",
|
||||
"shortcut": "TEST",
|
||||
"label": "Bitcoin Testnet",
|
||||
"alias": "bitcoin_testnet"
|
||||
"name": "Testnet",
|
||||
"shortcut": "TEST",
|
||||
"label": "Bitcoin Testnet",
|
||||
"alias": "bitcoin_testnet"
|
||||
},
|
||||
"ports": {
|
||||
"backend_rpc": 18030,
|
||||
@ -28,7 +28,7 @@
|
||||
"verification_source": "https://bitcoin.org/bin/bitcoin-core-0.17.1/SHA256SUMS.asc",
|
||||
"extract_command": "tar -C backend --strip 1 -xf",
|
||||
"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",
|
||||
"logrotate_files_template": "{{.Env.BackendDataPath}}/{{.Coin.Alias}}/backend/testnet3/*.log",
|
||||
@ -40,7 +40,7 @@
|
||||
"server_config_file": "bitcoin.conf",
|
||||
"client_config_file": "bitcoin_client.conf",
|
||||
"additional_params": {
|
||||
"deprecatedrpc": "estimatefee"
|
||||
"deprecatedrpc": "estimatefee"
|
||||
}
|
||||
},
|
||||
"blockbook": {
|
||||
@ -55,6 +55,10 @@
|
||||
"mempool_workers": 8,
|
||||
"mempool_sub_workers": 2,
|
||||
"block_addresses_to_keep": 300,
|
||||
"xpub_magic": 70617039,
|
||||
"xpub_magic_segwit_p2sh": 71979618,
|
||||
"xpub_magic_segwit_native": 73342198,
|
||||
"slip44": 1,
|
||||
"additional_params": {}
|
||||
}
|
||||
},
|
||||
@ -62,4 +66,4 @@
|
||||
"package_maintainer": "Jakub Matys",
|
||||
"package_maintainer_email": "jakub.matys@satoshilabs.com"
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -1,9 +1,9 @@
|
||||
{
|
||||
"coin": {
|
||||
"name": "Dash",
|
||||
"shortcut": "DASH",
|
||||
"label": "Dash",
|
||||
"alias": "dash"
|
||||
"name": "Dash",
|
||||
"shortcut": "DASH",
|
||||
"label": "Dash",
|
||||
"alias": "dash"
|
||||
},
|
||||
"ports": {
|
||||
"backend_rpc": 8033,
|
||||
@ -28,7 +28,7 @@
|
||||
"verification_source": "https://github.com/dashpay/dash/releases/download/v0.13.1.0/SHA256SUMS.asc",
|
||||
"extract_command": "tar -C backend --strip 1 -xf",
|
||||
"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",
|
||||
"logrotate_files_template": "{{.Env.BackendDataPath}}/{{.Coin.Alias}}/backend/*.log",
|
||||
@ -40,7 +40,7 @@
|
||||
"server_config_file": "bitcoin_like.conf",
|
||||
"client_config_file": "bitcoin_like_client.conf",
|
||||
"additional_params": {
|
||||
"mempoolexpiry": 72
|
||||
"mempoolexpiry": 72
|
||||
}
|
||||
},
|
||||
"blockbook": {
|
||||
@ -56,6 +56,8 @@
|
||||
"mempool_workers": 8,
|
||||
"mempool_sub_workers": 2,
|
||||
"block_addresses_to_keep": 300,
|
||||
"xpub_magic": 50221772,
|
||||
"slip44": 5,
|
||||
"additional_params": {}
|
||||
}
|
||||
},
|
||||
@ -63,4 +65,4 @@
|
||||
"package_maintainer": "Petr Kracik",
|
||||
"package_maintainer_email": "petr.kracik@satoshilabs.com"
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -1,9 +1,9 @@
|
||||
{
|
||||
"coin": {
|
||||
"name": "Dash Testnet",
|
||||
"shortcut": "tDASH",
|
||||
"label": "Dash Testnet",
|
||||
"alias": "dash_testnet"
|
||||
"name": "Dash Testnet",
|
||||
"shortcut": "tDASH",
|
||||
"label": "Dash Testnet",
|
||||
"alias": "dash_testnet"
|
||||
},
|
||||
"ports": {
|
||||
"backend_rpc": 18033,
|
||||
@ -28,7 +28,7 @@
|
||||
"verification_source": "https://github.com/dashpay/dash/releases/download/v0.13.1.0/SHA256SUMS.asc",
|
||||
"extract_command": "tar -C backend --strip 1 -xf",
|
||||
"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",
|
||||
"logrotate_files_template": "{{.Env.BackendDataPath}}/{{.Coin.Alias}}/backend/testnet3/*.log",
|
||||
@ -40,7 +40,7 @@
|
||||
"server_config_file": "bitcoin_like.conf",
|
||||
"client_config_file": "bitcoin_like_client.conf",
|
||||
"additional_params": {
|
||||
"mempoolexpiry": 72
|
||||
"mempoolexpiry": 72
|
||||
}
|
||||
},
|
||||
"blockbook": {
|
||||
@ -56,6 +56,8 @@
|
||||
"mempool_workers": 8,
|
||||
"mempool_sub_workers": 2,
|
||||
"block_addresses_to_keep": 300,
|
||||
"xpub_magic": 70617039,
|
||||
"slip44": 1,
|
||||
"additional_params": {}
|
||||
}
|
||||
},
|
||||
@ -63,4 +65,4 @@
|
||||
"package_maintainer": "Petr Kracik",
|
||||
"package_maintainer_email": "petr.kracik@satoshilabs.com"
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -1,9 +1,9 @@
|
||||
{
|
||||
"coin": {
|
||||
"name": "DigiByte",
|
||||
"shortcut": "DGB",
|
||||
"label": "DigiByte",
|
||||
"alias": "digibyte"
|
||||
"name": "DigiByte",
|
||||
"shortcut": "DGB",
|
||||
"label": "DigiByte",
|
||||
"alias": "digibyte"
|
||||
},
|
||||
"ports": {
|
||||
"backend_rpc": 8042,
|
||||
@ -28,7 +28,7 @@
|
||||
"verification_source": "dd6bed0228087fbb51f08be55cbc08a0e3251acfe1be3249b634447837ecd857",
|
||||
"extract_command": "tar -C backend --strip 1 -xf",
|
||||
"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",
|
||||
"logrotate_files_template": "{{.Env.BackendDataPath}}/{{.Coin.Alias}}/backend/*.log",
|
||||
@ -40,7 +40,7 @@
|
||||
"server_config_file": "bitcoin_like.conf",
|
||||
"client_config_file": "bitcoin_like_client.conf",
|
||||
"additional_params": {
|
||||
"whitelist": "127.0.0.1"
|
||||
"whitelist": "127.0.0.1"
|
||||
}
|
||||
},
|
||||
"blockbook": {
|
||||
@ -55,6 +55,9 @@
|
||||
"mempool_workers": 8,
|
||||
"mempool_sub_workers": 2,
|
||||
"block_addresses_to_keep": 300,
|
||||
"xpub_magic": 76067358,
|
||||
"xpub_magic_segwit_p2sh": 77429938,
|
||||
"slip44": 20,
|
||||
"additional_params": {}
|
||||
}
|
||||
},
|
||||
@ -62,4 +65,4 @@
|
||||
"package_maintainer": "Martin Boehm",
|
||||
"package_maintainer_email": "martin.bohm@satoshilabs.com"
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -1,9 +1,9 @@
|
||||
{
|
||||
"coin": {
|
||||
"name": "Dogecoin",
|
||||
"shortcut": "DOGE",
|
||||
"label": "Dogecoin",
|
||||
"alias": "dogecoin"
|
||||
"name": "Dogecoin",
|
||||
"shortcut": "DOGE",
|
||||
"label": "Dogecoin",
|
||||
"alias": "dogecoin"
|
||||
},
|
||||
"ports": {
|
||||
"backend_rpc": 8038,
|
||||
@ -28,7 +28,7 @@
|
||||
"verification_source": "09871d8ff2ab5e0f05df2bdf5eba64c178229d030dd7c8473b08e6ed45d3327f",
|
||||
"extract_command": "tar -C backend --strip 1 -xf",
|
||||
"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",
|
||||
"logrotate_files_template": "{{.Env.BackendDataPath}}/{{.Coin.Alias}}/backend/*.log",
|
||||
@ -40,10 +40,10 @@
|
||||
"server_config_file": "bitcoin_like.conf",
|
||||
"client_config_file": "bitcoin_like_client.conf",
|
||||
"additional_params": {
|
||||
"whitelist": "127.0.0.1",
|
||||
"rpcthreads": 16,
|
||||
"upnp": 0,
|
||||
"discover": 0
|
||||
"discover": 0,
|
||||
"rpcthreads": 16,
|
||||
"upnp": 0,
|
||||
"whitelist": "127.0.0.1"
|
||||
}
|
||||
},
|
||||
"blockbook": {
|
||||
@ -58,6 +58,8 @@
|
||||
"mempool_workers": 8,
|
||||
"mempool_sub_workers": 2,
|
||||
"block_addresses_to_keep": 300,
|
||||
"xpub_magic": 49990397,
|
||||
"slip44": 3,
|
||||
"additional_params": {}
|
||||
}
|
||||
},
|
||||
@ -65,4 +67,4 @@
|
||||
"package_maintainer": "Jakub Matys",
|
||||
"package_maintainer_email": "jakub.matys@satoshilabs.com"
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -1,9 +1,9 @@
|
||||
{
|
||||
"coin": {
|
||||
"name": "Fujicoin",
|
||||
"shortcut": "FJC",
|
||||
"label": "Fujicoin",
|
||||
"alias": "fujicoin"
|
||||
"name": "Fujicoin",
|
||||
"shortcut": "FJC",
|
||||
"label": "Fujicoin",
|
||||
"alias": "fujicoin"
|
||||
},
|
||||
"ports": {
|
||||
"backend_rpc": 8048,
|
||||
@ -27,8 +27,7 @@
|
||||
"verification_type": "gpg-sha256",
|
||||
"verification_source": "https://www.fujicoin.org/fujicoin/3.0/SHA256SUMS.asc",
|
||||
"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",
|
||||
"logrotate_files_template": "{{.Env.BackendDataPath}}/{{.Coin.Alias}}/backend/*.log",
|
||||
"postinst_script_template": "",
|
||||
@ -38,8 +37,7 @@
|
||||
"mainnet": true,
|
||||
"server_config_file": "bitcoin_like.conf",
|
||||
"client_config_file": "bitcoin_like_client.conf",
|
||||
"additional_params": {
|
||||
}
|
||||
"additional_params": {}
|
||||
},
|
||||
"blockbook": {
|
||||
"package_name": "blockbook-fujicoin",
|
||||
@ -53,6 +51,10 @@
|
||||
"mempool_workers": 8,
|
||||
"mempool_sub_workers": 2,
|
||||
"block_addresses_to_keep": 300,
|
||||
"xpub_magic": 76067358,
|
||||
"xpub_magic_segwit_p2sh": 77429938,
|
||||
"xpub_magic_segwit_native": 78792518,
|
||||
"slip44": 75,
|
||||
"additional_params": {}
|
||||
}
|
||||
},
|
||||
@ -60,4 +62,4 @@
|
||||
"package_maintainer": "Motty",
|
||||
"package_maintainer_email": "fujicoin@gmail.com"
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -1,9 +1,9 @@
|
||||
{
|
||||
"coin": {
|
||||
"name": "GameCredits",
|
||||
"shortcut": "GAME",
|
||||
"label": "GameCredits",
|
||||
"alias": "gamecredits"
|
||||
"name": "GameCredits",
|
||||
"shortcut": "GAME",
|
||||
"label": "GameCredits",
|
||||
"alias": "gamecredits"
|
||||
},
|
||||
"ports": {
|
||||
"backend_rpc": 8044,
|
||||
@ -28,7 +28,7 @@
|
||||
"verification_source": "38531ea877dfc1cedd3125bb79216a587f0974f20bee6243efcde61d05e07e5c",
|
||||
"extract_command": "tar -C backend --strip 1 -xf",
|
||||
"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",
|
||||
"logrotate_files_template": "{{.Env.BackendDataPath}}/{{.Coin.Alias}}/backend/*.log",
|
||||
@ -40,7 +40,7 @@
|
||||
"server_config_file": "bitcoin_like.conf",
|
||||
"client_config_file": "bitcoin_like_client.conf",
|
||||
"additional_params": {
|
||||
"whitelist": "127.0.0.1"
|
||||
"whitelist": "127.0.0.1"
|
||||
}
|
||||
},
|
||||
"blockbook": {
|
||||
@ -55,6 +55,9 @@
|
||||
"mempool_workers": 8,
|
||||
"mempool_sub_workers": 2,
|
||||
"block_addresses_to_keep": 300,
|
||||
"xpub_magic": 27106558,
|
||||
"xpub_magic_segwit_p2sh": 28471030,
|
||||
"slip44": 101,
|
||||
"additional_params": {}
|
||||
}
|
||||
},
|
||||
@ -62,4 +65,4 @@
|
||||
"package_maintainer": "Samad Sajanlal",
|
||||
"package_maintainer_email": "samad@gamecredits.org"
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -1,9 +1,9 @@
|
||||
{
|
||||
"coin": {
|
||||
"name": "Groestlcoin",
|
||||
"shortcut": "GRS",
|
||||
"label": "Groestlcoin",
|
||||
"alias": "groestlcoin"
|
||||
"name": "Groestlcoin",
|
||||
"shortcut": "GRS",
|
||||
"label": "Groestlcoin",
|
||||
"alias": "groestlcoin"
|
||||
},
|
||||
"ports": {
|
||||
"backend_rpc": 8045,
|
||||
@ -28,7 +28,7 @@
|
||||
"verification_source": "https://github.com/Groestlcoin/groestlcoin/releases/download/v2.17.2/SHA256SUMS.asc",
|
||||
"extract_command": "tar -C backend --strip 1 -xf",
|
||||
"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",
|
||||
"logrotate_files_template": "{{.Env.BackendDataPath}}/{{.Coin.Alias}}/backend/*.log",
|
||||
@ -40,8 +40,8 @@
|
||||
"server_config_file": "bitcoin_like.conf",
|
||||
"client_config_file": "bitcoin_like_client.conf",
|
||||
"additional_params": {
|
||||
"deprecatedrpc": "estimatefee",
|
||||
"whitelist": "127.0.0.1"
|
||||
"deprecatedrpc": "estimatefee",
|
||||
"whitelist": "127.0.0.1"
|
||||
}
|
||||
},
|
||||
"blockbook": {
|
||||
@ -56,6 +56,10 @@
|
||||
"mempool_workers": 8,
|
||||
"mempool_sub_workers": 2,
|
||||
"block_addresses_to_keep": 300,
|
||||
"xpub_magic": 76067358,
|
||||
"xpub_magic_segwit_p2sh": 77429938,
|
||||
"xpub_magic_segwit_native": 78792518,
|
||||
"slip44": 17,
|
||||
"additional_params": {}
|
||||
}
|
||||
},
|
||||
@ -63,4 +67,4 @@
|
||||
"package_maintainer": "Groestlcoin Development Team",
|
||||
"package_maintainer_email": "jackie@groestlcoin.org"
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -1,9 +1,9 @@
|
||||
{
|
||||
"coin": {
|
||||
"name": "Groestlcoin Testnet",
|
||||
"shortcut": "tGRS",
|
||||
"label": "Groestlcoin Testnet",
|
||||
"alias": "groestlcoin_testnet"
|
||||
"name": "Groestlcoin Testnet",
|
||||
"shortcut": "tGRS",
|
||||
"label": "Groestlcoin Testnet",
|
||||
"alias": "groestlcoin_testnet"
|
||||
},
|
||||
"ports": {
|
||||
"backend_rpc": 18045,
|
||||
@ -28,7 +28,7 @@
|
||||
"verification_source": "https://github.com/Groestlcoin/groestlcoin/releases/download/v2.17.2/SHA256SUMS.asc",
|
||||
"extract_command": "tar -C backend --strip 1 -xf",
|
||||
"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",
|
||||
"logrotate_files_template": "{{.Env.BackendDataPath}}/{{.Coin.Alias}}/backend/testnet3/*.log",
|
||||
@ -40,8 +40,8 @@
|
||||
"server_config_file": "bitcoin_like.conf",
|
||||
"client_config_file": "bitcoin_like_client.conf",
|
||||
"additional_params": {
|
||||
"deprecatedrpc": "estimatefee",
|
||||
"whitelist": "127.0.0.1"
|
||||
"deprecatedrpc": "estimatefee",
|
||||
"whitelist": "127.0.0.1"
|
||||
}
|
||||
},
|
||||
"blockbook": {
|
||||
@ -56,6 +56,10 @@
|
||||
"mempool_workers": 8,
|
||||
"mempool_sub_workers": 2,
|
||||
"block_addresses_to_keep": 300,
|
||||
"xpub_magic": 70617039,
|
||||
"xpub_magic_segwit_p2sh": 71979618,
|
||||
"xpub_magic_segwit_native": 73342198,
|
||||
"slip44": 1,
|
||||
"additional_params": {}
|
||||
}
|
||||
},
|
||||
@ -63,4 +67,4 @@
|
||||
"package_maintainer": "Groestlcoin Development Team",
|
||||
"package_maintainer_email": "jackie@groestlcoin.org"
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -1,9 +1,9 @@
|
||||
{
|
||||
"coin": {
|
||||
"name": "Koto",
|
||||
"shortcut": "KOTO",
|
||||
"label": "Koto",
|
||||
"alias": "koto"
|
||||
"name": "Koto",
|
||||
"shortcut": "KOTO",
|
||||
"label": "Koto",
|
||||
"alias": "koto"
|
||||
},
|
||||
"ports": {
|
||||
"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",
|
||||
"extract_command": "tar -C backend --strip 1 -xf",
|
||||
"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",
|
||||
"logrotate_files_template": "{{.Env.BackendDataPath}}/{{.Coin.Alias}}/backend/*.log",
|
||||
@ -40,9 +40,9 @@
|
||||
"server_config_file": "bitcoin_like.conf",
|
||||
"client_config_file": "bitcoin_like_client.conf",
|
||||
"additional_params": {
|
||||
"addnode": [
|
||||
"dnsseed.ko-to.org"
|
||||
]
|
||||
"addnode": [
|
||||
"dnsseed.ko-to.org"
|
||||
]
|
||||
}
|
||||
},
|
||||
"blockbook": {
|
||||
@ -57,6 +57,8 @@
|
||||
"mempool_workers": 4,
|
||||
"mempool_sub_workers": 8,
|
||||
"block_addresses_to_keep": 300,
|
||||
"xpub_magic": 76067358,
|
||||
"slip44": 510,
|
||||
"additional_params": {}
|
||||
}
|
||||
},
|
||||
@ -64,4 +66,4 @@
|
||||
"package_maintainer": "WO",
|
||||
"package_maintainer_email": "wo@kotocoin.info"
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -1,9 +1,9 @@
|
||||
{
|
||||
"coin": {
|
||||
"name": "Litecoin",
|
||||
"shortcut": "LTC",
|
||||
"label": "Litecoin",
|
||||
"alias": "litecoin"
|
||||
"name": "Litecoin",
|
||||
"shortcut": "LTC",
|
||||
"label": "Litecoin",
|
||||
"alias": "litecoin"
|
||||
},
|
||||
"ports": {
|
||||
"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",
|
||||
"extract_command": "tar -C backend --strip 1 -xf",
|
||||
"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",
|
||||
"logrotate_files_template": "{{.Env.BackendDataPath}}/{{.Coin.Alias}}/backend/*.log",
|
||||
@ -40,7 +40,7 @@
|
||||
"server_config_file": "bitcoin_like.conf",
|
||||
"client_config_file": "bitcoin_like_client.conf",
|
||||
"additional_params": {
|
||||
"whitelist": "127.0.0.1"
|
||||
"whitelist": "127.0.0.1"
|
||||
}
|
||||
},
|
||||
"blockbook": {
|
||||
@ -55,6 +55,9 @@
|
||||
"mempool_workers": 8,
|
||||
"mempool_sub_workers": 2,
|
||||
"block_addresses_to_keep": 300,
|
||||
"xpub_magic": 27108450,
|
||||
"xpub_magic_segwit_p2sh": 28471030,
|
||||
"slip44": 2,
|
||||
"additional_params": {}
|
||||
}
|
||||
},
|
||||
@ -62,4 +65,4 @@
|
||||
"package_maintainer": "Jakub Matys",
|
||||
"package_maintainer_email": "jakub.matys@satoshilabs.com"
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -1,9 +1,9 @@
|
||||
{
|
||||
"coin": {
|
||||
"name": "Litecoin Testnet",
|
||||
"shortcut": "TLTC",
|
||||
"label": "Litecoin Testnet",
|
||||
"alias": "litecoin_testnet"
|
||||
"name": "Litecoin Testnet",
|
||||
"shortcut": "TLTC",
|
||||
"label": "Litecoin Testnet",
|
||||
"alias": "litecoin_testnet"
|
||||
},
|
||||
"ports": {
|
||||
"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",
|
||||
"extract_command": "tar -C backend --strip 1 -xf",
|
||||
"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",
|
||||
"logrotate_files_template": "{{.Env.BackendDataPath}}/{{.Coin.Alias}}/backend/testnet4/*.log",
|
||||
@ -40,7 +40,7 @@
|
||||
"server_config_file": "bitcoin_like.conf",
|
||||
"client_config_file": "bitcoin_like_client.conf",
|
||||
"additional_params": {
|
||||
"whitelist": "127.0.0.1"
|
||||
"whitelist": "127.0.0.1"
|
||||
}
|
||||
},
|
||||
"blockbook": {
|
||||
@ -55,6 +55,8 @@
|
||||
"mempool_workers": 8,
|
||||
"mempool_sub_workers": 2,
|
||||
"block_addresses_to_keep": 300,
|
||||
"xpub_magic": 70617039,
|
||||
"slip44": 1,
|
||||
"additional_params": {}
|
||||
}
|
||||
},
|
||||
@ -62,4 +64,4 @@
|
||||
"package_maintainer": "Jakub Matys",
|
||||
"package_maintainer_email": "jakub.matys@satoshilabs.com"
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -1,9 +1,9 @@
|
||||
{
|
||||
"coin": {
|
||||
"name": "Monacoin",
|
||||
"shortcut": "MONA",
|
||||
"label": "Monacoin",
|
||||
"alias": "monacoin"
|
||||
"name": "Monacoin",
|
||||
"shortcut": "MONA",
|
||||
"label": "Monacoin",
|
||||
"alias": "monacoin"
|
||||
},
|
||||
"ports": {
|
||||
"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",
|
||||
"extract_command": "tar -C backend --strip 1 -xf",
|
||||
"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",
|
||||
"logrotate_files_template": "{{.Env.BackendDataPath}}/{{.Coin.Alias}}/backend/*.log",
|
||||
@ -40,7 +40,7 @@
|
||||
"server_config_file": "bitcoin_like.conf",
|
||||
"client_config_file": "bitcoin_like_client.conf",
|
||||
"additional_params": {
|
||||
"whitelist": "127.0.0.1"
|
||||
"whitelist": "127.0.0.1"
|
||||
}
|
||||
},
|
||||
"blockbook": {
|
||||
@ -55,6 +55,9 @@
|
||||
"mempool_workers": 8,
|
||||
"mempool_sub_workers": 2,
|
||||
"block_addresses_to_keep": 300,
|
||||
"xpub_magic": 76067358,
|
||||
"xpub_magic_segwit_p2sh": 77429938,
|
||||
"slip44": 22,
|
||||
"additional_params": {}
|
||||
}
|
||||
},
|
||||
@ -62,4 +65,4 @@
|
||||
"package_maintainer": "wakiyamap",
|
||||
"package_maintainer_email": "wakiyamap@gmail.com"
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -53,6 +53,8 @@
|
||||
"mempool_workers": 8,
|
||||
"mempool_sub_workers": 2,
|
||||
"block_addresses_to_keep": 300,
|
||||
"xpub_magic": 76067358,
|
||||
"slip44": 90,
|
||||
"additional_params": {}
|
||||
}
|
||||
},
|
||||
@ -60,4 +62,4 @@
|
||||
"package_maintainer": "wlc-",
|
||||
"package_maintainer_email": "wwwwllllcccc@gmail.com"
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -1,9 +1,9 @@
|
||||
{
|
||||
"coin": {
|
||||
"name": "Namecoin",
|
||||
"shortcut": "NMC",
|
||||
"label": "Namecoin",
|
||||
"alias": "namecoin"
|
||||
"name": "Namecoin",
|
||||
"shortcut": "NMC",
|
||||
"label": "Namecoin",
|
||||
"alias": "namecoin"
|
||||
},
|
||||
"ports": {
|
||||
"backend_rpc": 8039,
|
||||
@ -28,7 +28,7 @@
|
||||
"verification_source": "14ebaaf6f22f69b057a5bcb9b6959548f0a3f1b62cc113f19581d2297044827e",
|
||||
"extract_command": "tar -C backend --strip 1 -xf",
|
||||
"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",
|
||||
"logrotate_files_template": "{{.Env.BackendDataPath}}/{{.Coin.Alias}}/backend/*.log",
|
||||
@ -40,14 +40,14 @@
|
||||
"server_config_file": "bitcoin_like.conf",
|
||||
"client_config_file": "bitcoin_like_client.conf",
|
||||
"additional_params": {
|
||||
"whitelist": "127.0.0.1",
|
||||
"upnp": 0,
|
||||
"discover": 0,
|
||||
"whitelistrelay": 1,
|
||||
"listenonion": 0,
|
||||
"addnode": [
|
||||
"45.24.110.177:8334"
|
||||
]
|
||||
"addnode": [
|
||||
"45.24.110.177:8334"
|
||||
],
|
||||
"discover": 0,
|
||||
"listenonion": 0,
|
||||
"upnp": 0,
|
||||
"whitelist": "127.0.0.1",
|
||||
"whitelistrelay": 1
|
||||
}
|
||||
},
|
||||
"blockbook": {
|
||||
@ -62,6 +62,8 @@
|
||||
"mempool_workers": 8,
|
||||
"mempool_sub_workers": 2,
|
||||
"block_addresses_to_keep": 300,
|
||||
"xpub_magic": 76067358,
|
||||
"slip44": 7,
|
||||
"additional_params": {}
|
||||
}
|
||||
},
|
||||
@ -69,4 +71,4 @@
|
||||
"package_maintainer": "Jakub Matys",
|
||||
"package_maintainer_email": "jakub.matys@satoshilabs.com"
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -1,9 +1,9 @@
|
||||
{
|
||||
"coin": {
|
||||
"name": "Vertcoin",
|
||||
"shortcut": "VTC",
|
||||
"label": "Vertcoin",
|
||||
"alias": "vertcoin"
|
||||
"name": "Vertcoin",
|
||||
"shortcut": "VTC",
|
||||
"label": "Vertcoin",
|
||||
"alias": "vertcoin"
|
||||
},
|
||||
"ports": {
|
||||
"backend_rpc": 8040,
|
||||
@ -38,7 +38,7 @@
|
||||
"server_config_file": "bitcoin_like.conf",
|
||||
"client_config_file": "bitcoin_like_client.conf",
|
||||
"additional_params": {
|
||||
"whitelist": "127.0.0.1"
|
||||
"whitelist": "127.0.0.1"
|
||||
}
|
||||
},
|
||||
"blockbook": {
|
||||
@ -53,6 +53,9 @@
|
||||
"mempool_workers": 8,
|
||||
"mempool_sub_workers": 2,
|
||||
"block_addresses_to_keep": 1000,
|
||||
"xpub_magic": 76067358,
|
||||
"xpub_magic_segwit_p2sh": 77429938,
|
||||
"slip44": 28,
|
||||
"additional_params": {}
|
||||
}
|
||||
},
|
||||
@ -60,4 +63,4 @@
|
||||
"package_maintainer": "Petr Kracik",
|
||||
"package_maintainer_email": "petr.kracik@satoshilabs.com"
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -1,9 +1,9 @@
|
||||
{
|
||||
"coin": {
|
||||
"name": "Zcash",
|
||||
"shortcut": "ZEC",
|
||||
"label": "Zcash",
|
||||
"alias": "zcash"
|
||||
"name": "Zcash",
|
||||
"shortcut": "ZEC",
|
||||
"label": "Zcash",
|
||||
"alias": "zcash"
|
||||
},
|
||||
"ports": {
|
||||
"backend_rpc": 8032,
|
||||
@ -38,9 +38,9 @@
|
||||
"server_config_file": "bitcoin_like.conf",
|
||||
"client_config_file": "bitcoin_like_client.conf",
|
||||
"additional_params": {
|
||||
"addnode": [
|
||||
"mainnet.z.cash"
|
||||
]
|
||||
"addnode": [
|
||||
"mainnet.z.cash"
|
||||
]
|
||||
}
|
||||
},
|
||||
"blockbook": {
|
||||
@ -55,6 +55,8 @@
|
||||
"mempool_workers": 4,
|
||||
"mempool_sub_workers": 8,
|
||||
"block_addresses_to_keep": 300,
|
||||
"xpub_magic": 76067358,
|
||||
"slip44": 133,
|
||||
"additional_params": {}
|
||||
}
|
||||
},
|
||||
@ -62,4 +64,4 @@
|
||||
"package_maintainer": "Petr Kracik",
|
||||
"package_maintainer_email": "petr.kracik@satoshilabs.com"
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -1,9 +1,9 @@
|
||||
{
|
||||
"coin": {
|
||||
"name": "Zcash Testnet",
|
||||
"shortcut": "TAZ",
|
||||
"label": "Zcash Testnet",
|
||||
"alias": "zcash_testnet"
|
||||
"name": "Zcash Testnet",
|
||||
"shortcut": "TAZ",
|
||||
"label": "Zcash Testnet",
|
||||
"alias": "zcash_testnet"
|
||||
},
|
||||
"ports": {
|
||||
"backend_rpc": 18032,
|
||||
@ -38,9 +38,9 @@
|
||||
"server_config_file": "bitcoin_like.conf",
|
||||
"client_config_file": "bitcoin_like_client.conf",
|
||||
"additional_params": {
|
||||
"addnode": [
|
||||
"testnet.z.cash"
|
||||
]
|
||||
"addnode": [
|
||||
"testnet.z.cash"
|
||||
]
|
||||
}
|
||||
},
|
||||
"blockbook": {
|
||||
@ -55,6 +55,8 @@
|
||||
"mempool_workers": 4,
|
||||
"mempool_sub_workers": 8,
|
||||
"block_addresses_to_keep": 300,
|
||||
"xpub_magic": 70617039,
|
||||
"slip44": 1,
|
||||
"additional_params": {}
|
||||
}
|
||||
},
|
||||
@ -62,4 +64,4 @@
|
||||
"package_maintainer": "Petr Kracik",
|
||||
"package_maintainer_email": "petr.kracik@satoshilabs.com"
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -1,9 +1,9 @@
|
||||
{
|
||||
"coin": {
|
||||
"name": "Zcoin",
|
||||
"shortcut": "XZC",
|
||||
"label": "Zcoin",
|
||||
"alias": "zcoin"
|
||||
"name": "Zcoin",
|
||||
"shortcut": "XZC",
|
||||
"label": "Zcoin",
|
||||
"alias": "zcoin"
|
||||
},
|
||||
"ports": {
|
||||
"backend_rpc": 8050,
|
||||
@ -52,7 +52,7 @@
|
||||
"server_config_file": "bitcoin_like.conf",
|
||||
"client_config_file": "bitcoin_like_client.conf",
|
||||
"additional_params": {
|
||||
"deprecatedrpc": "estimatefee"
|
||||
"deprecatedrpc": "estimatefee"
|
||||
}
|
||||
},
|
||||
"blockbook": {
|
||||
@ -66,6 +66,8 @@
|
||||
"mempool_workers": 8,
|
||||
"mempool_sub_workers": 2,
|
||||
"block_addresses_to_keep": 300,
|
||||
"xpub_magic": 76067358,
|
||||
"slip44": 136,
|
||||
"additional_params": {}
|
||||
}
|
||||
},
|
||||
@ -73,4 +75,4 @@
|
||||
"package_maintainer": "Putta Khunchalee",
|
||||
"package_maintainer_email": "putta@zcoin.io"
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -1,5 +1,5 @@
|
||||
{
|
||||
"version": "0.2.0",
|
||||
"version": "0.2.1",
|
||||
"backend_install_path": "/opt/coins/nodes",
|
||||
"backend_data_path": "/opt/coins/data",
|
||||
"blockbook_install_path": "/opt/coins/blockbook",
|
||||
|
||||
@ -237,7 +237,7 @@ func writeMarkdown(output string, slice PortInfoSlice) error {
|
||||
|
||||
out := os.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 {
|
||||
return err
|
||||
}
|
||||
|
||||
@ -8,7 +8,7 @@
|
||||
| Dash | 9033 | 9133 | 8033 | 38333 |
|
||||
| Litecoin | 9034 | 9134 | 8034 | 38334 |
|
||||
| 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 | |
|
||||
| Dogecoin | 9038 | 9138 | 8038 | 38338 |
|
||||
| Namecoin | 9039 | 9139 | 8039 | 38339 |
|
||||
|
||||
181
server/public.go
181
server/public.go
@ -131,6 +131,7 @@ func (s *PublicServer) ConnectFullPublicInterface() {
|
||||
// internal explorer handlers
|
||||
serveMux.HandleFunc(path+"tx/", s.htmlTemplateHandler(s.explorerTx))
|
||||
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+"blocks", s.htmlTemplateHandler(s.explorerBlocks))
|
||||
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/", s.jsonHandler(s.apiTx, 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/sendtx/", s.jsonHandler(s.apiSendTx, 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/", s.jsonHandler(s.apiTx, 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/sendtx/", s.jsonHandler(s.apiSendTx, 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/", s.jsonHandler(s.apiTx, 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/sendtx/", s.jsonHandler(s.apiSendTx, apiV2))
|
||||
serveMux.HandleFunc(path+"api/v2/estimatefee/", s.jsonHandler(s.apiEstimateFee, apiV2))
|
||||
@ -372,6 +375,7 @@ const (
|
||||
indexTpl
|
||||
txTpl
|
||||
addressTpl
|
||||
xpubTpl
|
||||
blocksTpl
|
||||
blockTpl
|
||||
sendTransactionTpl
|
||||
@ -381,26 +385,27 @@ const (
|
||||
|
||||
// TemplateData is used to transfer data to the templates
|
||||
type TemplateData struct {
|
||||
CoinName string
|
||||
CoinShortcut string
|
||||
CoinLabel string
|
||||
InternalExplorer bool
|
||||
ChainType bchain.ChainType
|
||||
Address *api.Address
|
||||
AddrStr string
|
||||
Tx *api.Tx
|
||||
Error *api.APIError
|
||||
Blocks *api.Blocks
|
||||
Block *api.Block
|
||||
Info *api.SystemInfo
|
||||
Page int
|
||||
PrevPage int
|
||||
NextPage int
|
||||
PagingRange []int
|
||||
PageParams template.URL
|
||||
TOSLink string
|
||||
SendTxHex string
|
||||
Status string
|
||||
CoinName string
|
||||
CoinShortcut string
|
||||
CoinLabel string
|
||||
InternalExplorer bool
|
||||
ChainType bchain.ChainType
|
||||
Address *api.Address
|
||||
AddrStr string
|
||||
Tx *api.Tx
|
||||
Error *api.APIError
|
||||
Blocks *api.Blocks
|
||||
Block *api.Block
|
||||
Info *api.SystemInfo
|
||||
Page int
|
||||
PrevPage int
|
||||
NextPage int
|
||||
PagingRange []int
|
||||
PageParams template.URL
|
||||
TOSLink string
|
||||
SendTxHex string
|
||||
Status string
|
||||
NonZeroBalanceTokens bool
|
||||
}
|
||||
|
||||
func (s *PublicServer) parseTemplates() []*template.Template {
|
||||
@ -410,7 +415,8 @@ func (s *PublicServer) parseTemplates() []*template.Template {
|
||||
"formatAmount": s.formatAmount,
|
||||
"formatAmountWithDecimals": formatAmountWithDecimals,
|
||||
"setTxToTemplateData": setTxToTemplateData,
|
||||
"stringInSlice": stringInSlice,
|
||||
"isOwnAddress": isOwnAddress,
|
||||
"isOwnAddresses": isOwnAddresses,
|
||||
}
|
||||
var createTemplate func(filenames ...string) *template.Template
|
||||
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[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
|
||||
}
|
||||
|
||||
@ -498,6 +505,29 @@ func setTxToTemplateData(td *TemplateData, tx *api.Tx) *TemplateData {
|
||||
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) {
|
||||
var tx *api.Tx
|
||||
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 {
|
||||
return errorTpl, nil, err
|
||||
}
|
||||
@ -577,6 +607,68 @@ func (s *PublicServer) explorerAddress(w http.ResponseWriter, r *http.Request) (
|
||||
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) {
|
||||
var blocks *api.Blocks
|
||||
var err error
|
||||
@ -638,6 +730,11 @@ func (s *PublicServer) explorerSearch(w http.ResponseWriter, r *http.Request) (t
|
||||
var err error
|
||||
s.metrics.ExplorerViews.With(common.Labels{"action": "search"}).Inc()
|
||||
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)
|
||||
if err == nil {
|
||||
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)
|
||||
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 {
|
||||
http.Redirect(w, r, joinURL("/address/", address.AddrStr), 302)
|
||||
return noTpl, nil, nil
|
||||
@ -810,7 +907,7 @@ func (s *PublicServer) apiAddress(r *http.Request, apiVersion int) (interface{},
|
||||
if ec != nil {
|
||||
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 {
|
||||
return s.api.AddressToV1(address), nil
|
||||
}
|
||||
@ -818,10 +915,22 @@ func (s *PublicServer) apiAddress(r *http.Request, apiVersion int) (interface{},
|
||||
return address, err
|
||||
}
|
||||
|
||||
func (s *PublicServer) apiAddressUtxo(r *http.Request, apiVersion int) (interface{}, error) {
|
||||
var utxo []api.AddressUtxo
|
||||
func (s *PublicServer) apiXpub(r *http.Request, apiVersion int) (interface{}, error) {
|
||||
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
|
||||
s.metrics.ExplorerViews.With(common.Labels{"action": "api-address"}).Inc()
|
||||
if i := strings.LastIndexByte(r.URL.Path, '/'); i > 0 {
|
||||
onlyConfirmed := false
|
||||
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)
|
||||
}
|
||||
}
|
||||
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 {
|
||||
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"`
|
||||
}
|
||||
|
||||
func stringInSlice(a string, list []string) bool {
|
||||
for _, b := range list {
|
||||
if b == a {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func txToResTx(tx *api.Tx) resTx {
|
||||
inputs := make([]txInputs, len(tx.Vin))
|
||||
for i := range tx.Vin {
|
||||
|
||||
@ -317,10 +317,10 @@ func (s *WebsocketServer) onRequest(c *websocketChannel, req *websocketReq) {
|
||||
}
|
||||
if err == nil {
|
||||
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 {
|
||||
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.Error.Message = err.Error()
|
||||
data = e
|
||||
@ -347,27 +347,39 @@ func unmarshalGetAccountInfoRequest(params []byte) (*accountInfoReq, error) {
|
||||
}
|
||||
|
||||
func (s *WebsocketServer) getAccountInfo(req *accountInfoReq) (res *api.Address, err error) {
|
||||
var opt api.GetAddressOption
|
||||
var opt api.AccountDetails
|
||||
switch req.Details {
|
||||
case "balance":
|
||||
opt = api.Balance
|
||||
case "tokens":
|
||||
opt = api.AccountDetailsTokens
|
||||
case "tokenBalances":
|
||||
opt = api.AccountDetailsTokenBalances
|
||||
case "txids":
|
||||
opt = api.TxidHistory
|
||||
opt = api.AccountDetailsTxidHistory
|
||||
case "txs":
|
||||
opt = api.TxHistory
|
||||
opt = api.AccountDetailsTxHistory
|
||||
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),
|
||||
ToHeight: uint32(req.ToHeight),
|
||||
Contract: req.ContractFilter,
|
||||
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) {
|
||||
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) {
|
||||
|
||||
@ -182,6 +182,26 @@ h3 {
|
||||
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 {
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
@ -283,5 +303,5 @@ table.data-table table.data-table th {
|
||||
}
|
||||
|
||||
.key {
|
||||
color: #333 ;
|
||||
color: #333;
|
||||
}
|
||||
@ -42,7 +42,7 @@
|
||||
</ul>
|
||||
<span class="d-none ml-md-auto d-md-flex navbar-form navbar-left">
|
||||
<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>
|
||||
</span>
|
||||
{{- 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="row line-bot">
|
||||
<div class="col-xs-7 col-md-8 ellipsis">
|
||||
@ -10,23 +10,23 @@
|
||||
</div>
|
||||
<div class="row line-mid">
|
||||
<div class="col-md-5">
|
||||
<div class="row">
|
||||
<div class="row tx-in">
|
||||
<table class="table data-table">
|
||||
<tbody>
|
||||
{{- range $vin := $tx.Vin -}}
|
||||
<tr>
|
||||
<tr{{if isOwnAddresses $data $vin.Addresses}} class="tx-own"{{end}}>
|
||||
<td>
|
||||
{{- if $vin.Txid -}}
|
||||
<a class="float-left text-muted" href="/tx/{{$vin.Txid}}" title="Outpoint {{$vin.Txid}},{{$vin.Vout}}">➡ </a>
|
||||
{{- end -}}
|
||||
{{- 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}}
|
||||
</span>
|
||||
{{- 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 -}}
|
||||
<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 -}}
|
||||
</td>
|
||||
</tr>
|
||||
@ -45,20 +45,20 @@
|
||||
</svg>
|
||||
</div>
|
||||
<div class="col-md-6">
|
||||
<div class="row">
|
||||
<div class="row tx-out">
|
||||
<table class="table data-table">
|
||||
<tbody>
|
||||
{{- range $vout := $tx.Vout -}}
|
||||
<tr>
|
||||
<tr{{if isOwnAddresses $data $vout.Addresses}} class="tx-own"{{end}}>
|
||||
<td>
|
||||
{{- 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 -}}
|
||||
</span>
|
||||
{{- else -}}
|
||||
<span class="float-left">Unparsed address</span>
|
||||
<span class="tx-addr">Unparsed address</span>
|
||||
{{- 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 -}}
|
||||
<span class="text-success" title="Unspent"> <b>×</b></span>
|
||||
{{- 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="row line-bot">
|
||||
<div class="col-xs-7 col-md-8 ellipsis">
|
||||
@ -11,18 +11,18 @@
|
||||
</div>
|
||||
<div class="row line-mid">
|
||||
<div class="col-md-4">
|
||||
<div class="row">
|
||||
<div class="row tx-in">
|
||||
<table class="table data-table">
|
||||
<tbody>
|
||||
{{- range $vin := $tx.Vin -}}
|
||||
<tr>
|
||||
<tr{{if isOwnAddresses $data $vin.Addresses}} class="tx-own"{{end}}>
|
||||
<td>
|
||||
{{- 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}}
|
||||
</span>
|
||||
{{- else -}}
|
||||
<span class="float-left">Unparsed address</span>
|
||||
<span class="tx-addr">Unparsed address</span>
|
||||
{{- end -}}
|
||||
</td>
|
||||
</tr>
|
||||
@ -41,18 +41,18 @@
|
||||
</svg>
|
||||
</div>
|
||||
<div class="col-md-4">
|
||||
<div class="row">
|
||||
<div class="row tx-out">
|
||||
<table class="table data-table">
|
||||
<tbody>
|
||||
{{- range $vout := $tx.Vout -}}
|
||||
<tr>
|
||||
<tr{{if isOwnAddresses $data $vout.Addresses}} class="tx-own"{{end}}>
|
||||
<td>
|
||||
{{- 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 -}}
|
||||
</span>
|
||||
{{- else -}}
|
||||
<span class="float-left">Unparsed address</span>
|
||||
<span class="tx-addr">Unparsed address</span>
|
||||
{{- end -}}
|
||||
</td>
|
||||
</tr>
|
||||
@ -76,12 +76,12 @@
|
||||
{{- range $erc20 := $tx.TokenTransfers -}}
|
||||
<div class="row" style="padding: 2px 15px;">
|
||||
<div class="col-md-4">
|
||||
<div class="row">
|
||||
<div class="row tx-in">
|
||||
<table class="table data-table">
|
||||
<tbody>
|
||||
<tr>
|
||||
<tr{{if isOwnAddress $data $erc20.From}} class="tx-own"{{end}}>
|
||||
<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>
|
||||
</tr>
|
||||
</tbody>
|
||||
@ -94,12 +94,12 @@
|
||||
</svg>
|
||||
</div>
|
||||
<div class="col-md-4">
|
||||
<div class="row">
|
||||
<div class="row tx-out">
|
||||
<table class="table data-table">
|
||||
<tbody>
|
||||
<tr>
|
||||
<tr{{if isOwnAddress $data $erc20.To}} class="tx-own"{{end}}>
|
||||
<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>
|
||||
</tr>
|
||||
</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">
|
||||
<select id="getAccountInfoDetails" style="width: 20%; margin-left: 5px;">
|
||||
<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="txs">Transactions</option>
|
||||
</select>
|
||||
|
||||
@ -16,14 +16,16 @@ const (
|
||||
TxidB2T3 = "05e2e48aeabdd9b75def7b48d756ba304713c2aba7b522bf9dbc893fc4231b07"
|
||||
TxidB2T4 = "fdd824a780cbb718eeb766eb05d83fdefc793a27082cd5e67f856d69798cf7db"
|
||||
|
||||
Xpub = "upub5E1xjDmZ7Hhej6LPpS8duATdKXnRYui7bDYj6ehfFGzWDZtmCmQkZhc3Zb7kgRLtHWd16QFxyP86JKL3ShZEBFX88aciJ3xyocuyhZZ8g6q"
|
||||
|
||||
Addr1 = "mfcWp7DB6NuaZsExybTTXpVgWz559Np4Ti" // 76a914010d39800f86122416e28f485029acf77507169288ac
|
||||
Addr2 = "mtGXQvBowMkBpnhLckhxhbwYK44Gs9eEtz" // 76a9148bdf0aa3c567aa5975c2e61321b8bebbe7293df688ac
|
||||
Addr3 = "mv9uLThosiEnGRbVPS7Vhyw6VssbVRsiAw" // 76a914a08eae93007f22668ab5e4a9c83c8cd1c325e3e088ac
|
||||
Addr4 = "2Mz1CYoppGGsLNUGF2YDhTif6J661JitALS" // a9144a21db08fb6882cb152e1ff06780a430740f770487
|
||||
Addr4 = "2MzmAKayJmja784jyHvRUW1bXPget1csRRG" // a91452724c5178682f70e0ba31c6ec0633755a3b41d987, xpub m/49'/1'/33'/0/0
|
||||
Addr5 = "2NEVv9LJmAnY99W1pFoc5UJjVdypBqdnvu1" // a914e921fc4912a315078f370d959f2c4f7b6d2a683c87
|
||||
Addr6 = "mzB8cYrfRwFRFAGTDzV8LkUQy5BQicxGhX" // 76a914ccaaaf374e1b06cb83118453d102587b4273d09588ac
|
||||
Addr7 = "mtR97eM2HPWVM6c8FGLGcukgaHHQv7THoL" // 76a9148d802c045445df49613f6a70ddd2e48526f3701f88ac
|
||||
Addr8 = "mwwoKQE5Lb1G4picHSHDQKg8jw424PF9SC" // 76a914b434eb0c1a3b7a02e8a29cc616e791ef1e0bf51f88ac
|
||||
Addr8 = "2N6utyMZfPNUb1Bk8oz7p2JqJrXkq83gegu" // a91495e9fbe306449c991d314afe3c3567d5bf78efd287, xpub m/49'/1'/33'/1/3
|
||||
Addr9 = "mmJx9Y8ayz9h14yd9fgCW1bUKoEpkBAquP" // 76a9143f8ba3fda3ba7b69f5818086e12223c6dd25e3c888ac
|
||||
AddrA = "mzVznVsCHkVHX9UN8WPFASWUUHtxnNn4Jj" // 76a914d03c0d863d189b23b061a95ad32940b65837609f88ac
|
||||
)
|
||||
|
||||
Loading…
Reference in New Issue
Block a user