Generate typescript documentation from golang types

This commit is contained in:
Martin Boehm 2023-02-21 10:00:30 +01:00
parent 9ecfdfe11d
commit 15c19ac1fc
9 changed files with 682 additions and 160 deletions

11
.prettierrc Normal file
View File

@ -0,0 +1,11 @@
{
"printWidth": 100,
"arrowParens": "avoid",
"bracketSpacing": true,
"singleQuote": true,
"semi": true,
"trailingComma": "all",
"tabWidth": 4,
"useTabs": false,
"bracketSameLine": false
}

View File

@ -158,7 +158,7 @@ type MultiTokenValue struct {
// Token contains info about tokens held by an address
type Token struct {
Type bchain.TokenTypeName `json:"type"`
Type bchain.TokenTypeName `json:"type" ts_type:"'XPUBAddress' | 'ERC20' | 'ERC721' | 'ERC1155'"`
Name string `json:"name"`
Path string `json:"path,omitempty"`
Contract string `json:"contract,omitempty"`
@ -263,7 +263,7 @@ type Tx struct {
FeesSat *Amount `json:"fees,omitempty"`
Hex string `json:"hex,omitempty"`
Rbf bool `json:"rbf,omitempty"`
CoinSpecificData json.RawMessage `json:"coinSpecificData,omitempty"`
CoinSpecificData json.RawMessage `json:"coinSpecificData,omitempty" ts_type:"any"`
TokenTransfers []TokenTransfer `json:"tokenTransfers,omitempty"`
EthereumSpecific *EthereumSpecific `json:"ethereumSpecific,omitempty"`
AddressAliases AddressAliasesMap `json:"addressAliases,omitempty"`

412
blockbook-api.d.ts vendored Normal file
View File

@ -0,0 +1,412 @@
/* Do not change, this code is generated from Golang structs */
export interface APIError {
Text: string;
Public: boolean;
}
export interface AddressAlias {
Type: string;
Alias: string;
}
export interface EthereumInternalTransfer {
type: number;
from: string;
to: string;
value?: string;
}
export interface EthereumParsedInputParam {
type: string;
values?: string[];
}
export interface EthereumParsedInputData {
methodId: string;
name: string;
function?: string;
params?: EthereumParsedInputParam[];
}
export interface EthereumSpecific {
type?: number;
createdContract?: string;
status: number;
error?: string;
nonce: number;
gasLimit?: string;
gasUsed?: string;
gasPrice?: string;
data?: string;
parsedData?: EthereumParsedInputData;
internalTransfers?: EthereumInternalTransfer[];
}
export interface MultiTokenValue {
id?: string;
value?: string;
}
export interface TokenTransfer {
type: string;
from: string;
to: string;
contract: string;
name: string;
symbol: string;
decimals: number;
value?: string;
multiTokenValues?: MultiTokenValue[];
}
export interface Vout {
value?: string;
n: number;
spent?: boolean;
spentTxId?: string;
spentIndex?: number;
spentHeight?: number;
hex?: string;
asm?: string;
addresses: string[];
isAddress: boolean;
isOwn?: boolean;
type?: string;
}
export interface Vin {
txid?: string;
vout?: number;
sequence?: number;
n: number;
addresses?: string[];
isAddress: boolean;
isOwn?: boolean;
value?: string;
hex?: string;
asm?: string;
coinbase?: string;
}
export interface Tx {
txid: string;
version?: number;
lockTime?: number;
vin: Vin[];
vout: Vout[];
blockHash?: string;
blockHeight: number;
confirmations: number;
confirmationETABlocks?: number;
confirmationETASeconds?: number;
blockTime: number;
size?: number;
vsize?: number;
value?: string;
valueIn?: string;
fees?: string;
hex?: string;
rbf?: boolean;
coinSpecificData?: any;
tokenTransfers?: TokenTransfer[];
ethereumSpecific?: EthereumSpecific;
addressAliases?: { [key: string]: AddressAlias };
}
export interface FeeStats {
txCount: number;
totalFeesSat?: string;
averageFeePerKb: number;
decilesFeePerKb: number[];
}
export interface ContractInfo {
type: string;
contract: string;
name: string;
symbol: string;
decimals: number;
createdInBlock?: number;
destructedInBlock?: number;
}
export interface Token {
type: 'XPUBAddress' | 'ERC20' | 'ERC721' | 'ERC1155';
name: string;
path?: string;
contract?: string;
transfers: number;
symbol?: string;
decimals?: number;
balance?: string;
baseValue?: number;
secondaryValue?: number;
ids?: string[];
multiTokenValues?: MultiTokenValue[];
totalReceived?: string;
totalSent?: string;
}
export interface Address {
page?: number;
totalPages?: number;
itemsOnPage?: number;
address: string;
balance?: string;
totalReceived?: string;
totalSent?: string;
unconfirmedBalance?: string;
unconfirmedTxs: number;
txs: number;
nonTokenTxs?: number;
internalTxs?: number;
transactions?: Tx[];
txids?: string[];
nonce?: string;
usedTokens?: number;
tokens?: Token[];
secondaryValue?: number;
tokensBaseValue?: number;
tokensSecondaryValue?: number;
totalBaseValue?: number;
totalSecondaryValue?: number;
contractInfo?: ContractInfo;
erc20Contract?: ContractInfo;
addressAliases?: { [key: string]: AddressAlias };
}
export interface Utxo {
txid: string;
vout: number;
value?: string;
height?: number;
confirmations: number;
address?: string;
path?: string;
lockTime?: number;
coinbase?: boolean;
}
export interface BalanceHistory {
time: number;
txs: number;
received?: string;
sent?: string;
sentToSelf?: string;
rates?: { [key: string]: number };
txid?: string;
}
export interface BlockInfo {
Hash: string;
Time: number;
Txs: number;
Size: number;
Height: number;
}
export interface Blocks {
page?: number;
totalPages?: number;
itemsOnPage?: number;
blocks: BlockInfo[];
}
export interface Block {
page?: number;
totalPages?: number;
itemsOnPage?: number;
hash: string;
previousBlockHash?: string;
nextBlockHash?: string;
height: number;
confirmations: number;
size: number;
time?: number;
version: string;
merkleRoot: string;
nonce: string;
bits: string;
difficulty: string;
tx?: string[];
txCount: number;
txs?: Tx[];
addressAliases?: { [key: string]: AddressAlias };
}
export interface BlockRaw {
hex: string;
}
export interface BackendInfo {
error?: string;
chain?: string;
blocks?: number;
headers?: number;
bestBlockHash?: string;
difficulty?: string;
sizeOnDisk?: number;
version?: string;
subversion?: string;
protocolVersion?: string;
timeOffset?: number;
warnings?: string;
consensus_version?: string;
consensus?: any;
}
export interface InternalStateColumn {
name: string;
version: number;
rows: number;
keyBytes: number;
valueBytes: number;
updated: string;
}
export interface BlockbookInfo {
coin: string;
host: string;
version: string;
gitCommit: string;
buildTime: string;
syncMode: boolean;
initialSync: boolean;
inSync: boolean;
bestHeight: number;
lastBlockTime: string;
inSyncMempool: boolean;
lastMempoolTime: string;
mempoolSize: number;
decimals: number;
dbSize: number;
hasFiatRates?: boolean;
hasTokenFiatRates?: boolean;
currentFiatRatesTime?: string;
historicalFiatRatesTime?: string;
historicalTokenFiatRatesTime?: string;
dbSizeFromColumns?: number;
dbColumns?: InternalStateColumn[];
about: string;
}
export interface SystemInfo {
blockbook?: BlockbookInfo;
backend?: BackendInfo;
}
export interface FiatTicker {
ts?: number;
rates: { [key: string]: number };
error?: string;
}
export interface FiatTickers {
tickers: FiatTicker[];
}
export interface AvailableVsCurrencies {
ts?: number;
available_currencies: string[];
error?: string;
}
export interface WsReq {
id: string;
method:
| 'getAccountInfo'
| 'getInfo'
| 'getBlockHash'
| 'getAccountUtxo'
| 'getBalanceHistory'
| 'getTransaction'
| 'getTransactionSpecific'
| 'estimateFee'
| 'sendTransaction'
| 'subscribeNewBlock'
| 'unsubscribeNewBlock'
| 'subscribeNewTransaction'
| 'unsubscribeNewTransaction'
| 'subscribeAddresses'
| 'unsubscribeAddresses'
| 'subscribeFiatRates'
| 'unsubscribeFiatRates'
| 'ping'
| 'getCurrentFiatRates'
| 'getFiatRatesForTimestamps'
| 'getFiatRatesTickersList';
params: any;
}
export interface WsRes {
id: string;
data: any;
}
export interface WsAccountInfoReq {
descriptor: string;
details?: 'basic' | 'tokens' | 'tokenBalances' | 'txids' | 'txslight' | 'txs';
tokens?: 'derived' | 'used' | 'nonzero';
pageSize?: number;
page?: number;
from?: number;
to?: number;
contractFilter?: string;
secondaryCurrency?: string;
gap?: number;
}
export interface WsBackendInfo {
version?: string;
subversion?: string;
consensus_version?: string;
consensus?: any;
}
export interface WsInfoRes {
name: string;
shortcut: string;
decimals: number;
version: string;
bestHeight: number;
bestHash: string;
block0Hash: string;
testnet: boolean;
backend: WsBackendInfo;
}
export interface WsBlockHashReq {
height: number;
}
export interface WsBlockHashRes {
hash: string;
}
export interface WsBlockReq {
id: string;
pageSize?: number;
page?: number;
}
export interface WsAccountUtxoReq {
descriptor: string;
}
export interface WsBalanceHistoryReq {
descriptor: string;
from?: number;
to?: number;
currencies?: string[];
gap?: number;
groupBy?: number;
}
export interface WsTransactionReq {
txid: string;
}
export interface WsTransactionSpecificReq {
txid: string;
}
export interface WsEstimateFeeReq {
blocks?: number[];
specific?: {
conservative?: boolean;
txsize?: number;
from?: string;
to?: string;
data?: string;
value?: string;
};
}
export interface WsEstimateFeeRes {
feePerTx?: string;
feePerUnit?: string;
feeLimit?: string;
}
export interface WsSendTransactionReq {
hex: string;
}
export interface WsSubscribeAddressesReq {
addresses: string[];
}
export interface WsSubscribeFiatRatesReq {
currency?: string;
tokens?: string[];
}
export interface WsCurrentFiatRatesReq {
currencies?: string[];
token?: string;
}
export interface WsFiatRatesForTimestampsReq {
timestamps: number[];
currencies?: string[];
token?: string;
}
export interface WsFiatRatesTickersListReq {
timestamp?: number;
token?: string;
}

View File

@ -0,0 +1,65 @@
package main
import (
"fmt"
"math/big"
"time"
"github.com/tkrajina/typescriptify-golang-structs/typescriptify"
"github.com/trezor/blockbook/api"
"github.com/trezor/blockbook/server"
)
func main() {
t := typescriptify.New()
t.CreateInterface = true
t.Indent = " "
t.BackupDir = ""
t.ManageType(api.Amount{}, typescriptify.TypeOptions{TSType: "string"})
t.ManageType([]api.Amount{}, typescriptify.TypeOptions{TSType: "string[]"})
t.ManageType(big.Int{}, typescriptify.TypeOptions{TSType: "string"})
t.ManageType(time.Time{}, typescriptify.TypeOptions{TSType: "string", TSDoc: "Time in ISO 8601 YYYY-MM-DDTHH:mm:ss.sssZd"})
// API - REST and Websocket
t.Add(api.APIError{})
t.Add(api.Tx{})
t.Add(api.FeeStats{})
t.Add(api.Address{})
t.Add(api.Utxo{})
t.Add(api.BalanceHistory{})
t.Add(api.Blocks{})
t.Add(api.Block{})
t.Add(api.BlockRaw{})
t.Add(api.SystemInfo{})
t.Add(api.FiatTicker{})
t.Add(api.FiatTickers{})
t.Add(api.AvailableVsCurrencies{})
// Websocket specific
t.Add(server.WsReq{})
t.Add(server.WsRes{})
t.Add(server.WsAccountInfoReq{})
t.Add(server.WsInfoRes{})
t.Add(server.WsBlockHashReq{})
t.Add(server.WsBlockHashRes{})
t.Add(server.WsBlockReq{})
t.Add(server.WsAccountUtxoReq{})
t.Add(server.WsBalanceHistoryReq{})
t.Add(server.WsTransactionReq{})
t.Add(server.WsTransactionSpecificReq{})
t.Add(server.WsEstimateFeeReq{})
t.Add(server.WsEstimateFeeRes{})
t.Add(server.WsSendTransactionReq{})
t.Add(server.WsSubscribeAddressesReq{})
t.Add(server.WsSubscribeFiatRatesReq{})
t.Add(server.WsCurrentFiatRatesReq{})
t.Add(server.WsFiatRatesForTimestampsReq{})
t.Add(server.WsFiatRatesTickersListReq{})
err := t.ConvertToFile("blockbook-api.d.ts")
if err != nil {
panic(err.Error())
}
fmt.Println("OK")
}

2
go.mod
View File

@ -75,6 +75,8 @@ require (
github.com/stretchr/testify v1.8.1 // indirect
github.com/tklauser/go-sysconf v0.3.5 // indirect
github.com/tklauser/numcpus v0.2.2 // indirect
github.com/tkrajina/go-reflector v0.5.5 // indirect
github.com/tkrajina/typescriptify-golang-structs v0.1.10 // indirect
github.com/yusufpapurcu/wmi v1.2.2 // indirect
golang.org/x/exp v0.0.0-20220426173459-3bcf042a4bf5 // indirect
golang.org/x/sys v0.5.0 // indirect

4
go.sum
View File

@ -367,6 +367,10 @@ github.com/tklauser/go-sysconf v0.3.5 h1:uu3Xl4nkLzQfXNsWn15rPc/HQCJKObbt1dKJeWp
github.com/tklauser/go-sysconf v0.3.5/go.mod h1:MkWzOF4RMCshBAMXuhXJs64Rte09mITnppBXY/rYEFI=
github.com/tklauser/numcpus v0.2.2 h1:oyhllyrScuYI6g+h/zUvNXNp1wy7x8qQy3t/piefldA=
github.com/tklauser/numcpus v0.2.2/go.mod h1:x3qojaO3uyYt0i56EW/VUYs7uBvdl2fkfZFu0T9wgjM=
github.com/tkrajina/go-reflector v0.5.5 h1:gwoQFNye30Kk7NrExj8zm3zFtrGPqOkzFMLuQZg1DtQ=
github.com/tkrajina/go-reflector v0.5.5/go.mod h1:ECbqLgccecY5kPmPmXg1MrHW585yMcDkVl6IvJe64T4=
github.com/tkrajina/typescriptify-golang-structs v0.1.10 h1:W/Ta9Kqo2lV+7bVXuQoUhZ0bDlnjwtPpKsy3A9M1nYg=
github.com/tkrajina/typescriptify-golang-structs v0.1.10/go.mod h1:sjU00nti/PMEOZb07KljFlR+lJ+RotsC0GBQMv9EKls=
github.com/tyler-smith/go-bip39 v1.0.2 h1:+t3w+KwLXO6154GNJY+qUtIxLTmFjfUmpguQT1OlOT8=
github.com/urfave/cli/v2 v2.10.2 h1:x3p8awjp/2arX+Nl/G2040AZpOCHS/eMJJ1/a+mye4Y=
github.com/xrash/smetrics v0.0.0-20201216005158-039620a65673 h1:bAn7/zixMGCfxrRTfdpNzjtPYqr8smhKouy9mxVdGPU=

View File

@ -1262,7 +1262,7 @@ func websocketTestsBitcoinType(t *testing.T, ts *httptest.Server) {
"timestamps": []string{"yesterday"},
},
},
want: `{"id":"23","data":{"error":{"message":"json: cannot unmarshal string into Go struct field .timestamps of type int64"}}}`,
want: `{"id":"23","data":{"error":{"message":"json: cannot unmarshal string into Go struct field WsFiatRatesForTimestampsReq.timestamps of type int64"}}}`,
},
{
name: "websocket getFiatRatesForTimestamps empty currency",

View File

@ -34,21 +34,10 @@ var (
connectionCounter uint64
)
type websocketReq struct {
ID string `json:"id"`
Method string `json:"method"`
Params json.RawMessage `json:"params"`
}
type websocketRes struct {
ID string `json:"id"`
Data interface{} `json:"data"`
}
type websocketChannel struct {
id uint64
conn *websocket.Conn
out chan *websocketRes
out chan *WsRes
ip string
requestHeader http.Header
alive bool
@ -56,11 +45,6 @@ type websocketChannel struct {
addrDescs []string // subscribed address descriptors as strings
}
type fiatRatesSubscription struct {
Currency string `json:"currency"`
Tokens []string `json:"tokens"`
}
// WebsocketServer is a handle to websocket server
type WebsocketServer struct {
upgrader *websocket.Upgrader
@ -147,7 +131,7 @@ func (s *WebsocketServer) ServeHTTP(w http.ResponseWriter, r *http.Request) {
c := &websocketChannel{
id: atomic.AddUint64(&connectionCounter, 1),
conn: conn,
out: make(chan *websocketRes, outChannelSize),
out: make(chan *WsRes, outChannelSize),
ip: getIP(r),
requestHeader: r.Header,
alive: true,
@ -184,7 +168,7 @@ func (c *websocketChannel) CloseOut() bool {
return false
}
func (c *websocketChannel) DataOut(data *websocketRes) {
func (c *websocketChannel) DataOut(data *WsRes) {
c.aliveLock.Lock()
defer c.aliveLock.Unlock()
if c.alive {
@ -215,7 +199,7 @@ func (s *WebsocketServer) inputLoop(c *websocketChannel) {
}
switch t {
case websocket.TextMessage:
var req websocketReq
var req WsReq
err := json.Unmarshal(d, &req)
if err != nil {
glog.Error("Error parsing message from ", c.id, ", ", string(d), ", ", err)
@ -229,7 +213,6 @@ func (s *WebsocketServer) inputLoop(c *websocketChannel) {
return
case websocket.PingMessage:
c.conn.WriteControl(websocket.PongMessage, nil, time.Now().Add(defaultTimeout))
break
case websocket.CloseMessage:
s.closeChannel(c)
return
@ -270,36 +253,30 @@ func (s *WebsocketServer) onDisconnect(c *websocketChannel) {
s.metrics.WebsocketClients.Dec()
}
var requestHandlers = map[string]func(*WebsocketServer, *websocketChannel, *websocketReq) (interface{}, error){
"getAccountInfo": func(s *WebsocketServer, c *websocketChannel, req *websocketReq) (rv interface{}, err error) {
var requestHandlers = map[string]func(*WebsocketServer, *websocketChannel, *WsReq) (interface{}, error){
"getAccountInfo": func(s *WebsocketServer, c *websocketChannel, req *WsReq) (rv interface{}, err error) {
r, err := unmarshalGetAccountInfoRequest(req.Params)
if err == nil {
rv, err = s.getAccountInfo(r)
}
return
},
"getInfo": func(s *WebsocketServer, c *websocketChannel, req *websocketReq) (rv interface{}, err error) {
"getInfo": func(s *WebsocketServer, c *websocketChannel, req *WsReq) (rv interface{}, err error) {
return s.getInfo()
},
"getBlockHash": func(s *WebsocketServer, c *websocketChannel, req *websocketReq) (rv interface{}, err error) {
r := struct {
Height int `json:"height"`
}{}
"getBlockHash": func(s *WebsocketServer, c *websocketChannel, req *WsReq) (rv interface{}, err error) {
r := WsBlockHashReq{}
err = json.Unmarshal(req.Params, &r)
if err == nil {
rv, err = s.getBlockHash(r.Height)
}
return
},
"getBlock": func(s *WebsocketServer, c *websocketChannel, req *websocketReq) (rv interface{}, err error) {
"getBlock": func(s *WebsocketServer, c *websocketChannel, req *WsReq) (rv interface{}, err error) {
if !s.is.ExtendedIndex {
return nil, errors.New("Not supported")
}
r := struct {
Id string `json:"id"`
PageSize int `json:"pageSize"`
Page int `json:"page"`
}{}
r := WsBlockReq{}
err = json.Unmarshal(req.Params, &r)
if r.PageSize == 0 {
r.PageSize = 1000000
@ -309,25 +286,16 @@ var requestHandlers = map[string]func(*WebsocketServer, *websocketChannel, *webs
}
return
},
"getAccountUtxo": func(s *WebsocketServer, c *websocketChannel, req *websocketReq) (rv interface{}, err error) {
r := struct {
Descriptor string `json:"descriptor"`
}{}
"getAccountUtxo": func(s *WebsocketServer, c *websocketChannel, req *WsReq) (rv interface{}, err error) {
r := WsAccountUtxoReq{}
err = json.Unmarshal(req.Params, &r)
if err == nil {
rv, err = s.getAccountUtxo(r.Descriptor)
}
return
},
"getBalanceHistory": func(s *WebsocketServer, c *websocketChannel, req *websocketReq) (rv interface{}, err error) {
r := struct {
Descriptor string `json:"descriptor"`
From int64 `json:"from"`
To int64 `json:"to"`
Currencies []string `json:"currencies"`
Gap int `json:"gap"`
GroupBy uint32 `json:"groupBy"`
}{}
"getBalanceHistory": func(s *WebsocketServer, c *websocketChannel, req *WsReq) (rv interface{}, err error) {
r := WsBalanceHistoryReq{}
err = json.Unmarshal(req.Params, &r)
if err == nil {
if r.From <= 0 {
@ -346,63 +314,57 @@ var requestHandlers = map[string]func(*WebsocketServer, *websocketChannel, *webs
}
return
},
"getTransaction": func(s *WebsocketServer, c *websocketChannel, req *websocketReq) (rv interface{}, err error) {
r := struct {
Txid string `json:"txid"`
}{}
"getTransaction": func(s *WebsocketServer, c *websocketChannel, req *WsReq) (rv interface{}, err error) {
r := WsTransactionReq{}
err = json.Unmarshal(req.Params, &r)
if err == nil {
rv, err = s.getTransaction(r.Txid)
}
return
},
"getTransactionSpecific": func(s *WebsocketServer, c *websocketChannel, req *websocketReq) (rv interface{}, err error) {
r := struct {
Txid string `json:"txid"`
}{}
"getTransactionSpecific": func(s *WebsocketServer, c *websocketChannel, req *WsReq) (rv interface{}, err error) {
r := WsTransactionSpecificReq{}
err = json.Unmarshal(req.Params, &r)
if err == nil {
rv, err = s.getTransactionSpecific(r.Txid)
}
return
},
"estimateFee": func(s *WebsocketServer, c *websocketChannel, req *websocketReq) (rv interface{}, err error) {
"estimateFee": func(s *WebsocketServer, c *websocketChannel, req *WsReq) (rv interface{}, err error) {
return s.estimateFee(c, req.Params)
},
"sendTransaction": func(s *WebsocketServer, c *websocketChannel, req *websocketReq) (rv interface{}, err error) {
r := struct {
Hex string `json:"hex"`
}{}
"sendTransaction": func(s *WebsocketServer, c *websocketChannel, req *WsReq) (rv interface{}, err error) {
r := WsSendTransactionReq{}
err = json.Unmarshal(req.Params, &r)
if err == nil {
rv, err = s.sendTransaction(r.Hex)
}
return
},
"subscribeNewBlock": func(s *WebsocketServer, c *websocketChannel, req *websocketReq) (rv interface{}, err error) {
"subscribeNewBlock": func(s *WebsocketServer, c *websocketChannel, req *WsReq) (rv interface{}, err error) {
return s.subscribeNewBlock(c, req)
},
"unsubscribeNewBlock": func(s *WebsocketServer, c *websocketChannel, req *websocketReq) (rv interface{}, err error) {
"unsubscribeNewBlock": func(s *WebsocketServer, c *websocketChannel, req *WsReq) (rv interface{}, err error) {
return s.unsubscribeNewBlock(c)
},
"subscribeNewTransaction": func(s *WebsocketServer, c *websocketChannel, req *websocketReq) (rv interface{}, err error) {
"subscribeNewTransaction": func(s *WebsocketServer, c *websocketChannel, req *WsReq) (rv interface{}, err error) {
return s.subscribeNewTransaction(c, req)
},
"unsubscribeNewTransaction": func(s *WebsocketServer, c *websocketChannel, req *websocketReq) (rv interface{}, err error) {
"unsubscribeNewTransaction": func(s *WebsocketServer, c *websocketChannel, req *WsReq) (rv interface{}, err error) {
return s.unsubscribeNewTransaction(c)
},
"subscribeAddresses": func(s *WebsocketServer, c *websocketChannel, req *websocketReq) (rv interface{}, err error) {
"subscribeAddresses": func(s *WebsocketServer, c *websocketChannel, req *WsReq) (rv interface{}, err error) {
ad, err := s.unmarshalAddresses(req.Params)
if err == nil {
rv, err = s.subscribeAddresses(c, ad, req)
}
return
},
"unsubscribeAddresses": func(s *WebsocketServer, c *websocketChannel, req *websocketReq) (rv interface{}, err error) {
"unsubscribeAddresses": func(s *WebsocketServer, c *websocketChannel, req *WsReq) (rv interface{}, err error) {
return s.unsubscribeAddresses(c)
},
"subscribeFiatRates": func(s *WebsocketServer, c *websocketChannel, req *websocketReq) (rv interface{}, err error) {
var r fiatRatesSubscription
"subscribeFiatRates": func(s *WebsocketServer, c *websocketChannel, req *WsReq) (rv interface{}, err error) {
var r WsSubscribeFiatRatesReq
err = json.Unmarshal(req.Params, &r)
if err != nil {
return nil, err
@ -413,41 +375,31 @@ var requestHandlers = map[string]func(*WebsocketServer, *websocketChannel, *webs
}
return s.subscribeFiatRates(c, &r, req)
},
"unsubscribeFiatRates": func(s *WebsocketServer, c *websocketChannel, req *websocketReq) (rv interface{}, err error) {
"unsubscribeFiatRates": func(s *WebsocketServer, c *websocketChannel, req *WsReq) (rv interface{}, err error) {
return s.unsubscribeFiatRates(c)
},
"ping": func(s *WebsocketServer, c *websocketChannel, req *websocketReq) (rv interface{}, err error) {
"ping": func(s *WebsocketServer, c *websocketChannel, req *WsReq) (rv interface{}, err error) {
r := struct{}{}
return r, nil
},
"getCurrentFiatRates": func(s *WebsocketServer, c *websocketChannel, req *websocketReq) (rv interface{}, err error) {
r := struct {
Currencies []string `json:"currencies"`
Token string `json:"token"`
}{}
"getCurrentFiatRates": func(s *WebsocketServer, c *websocketChannel, req *WsReq) (rv interface{}, err error) {
r := WsCurrentFiatRatesReq{}
err = json.Unmarshal(req.Params, &r)
if err == nil {
rv, err = s.getCurrentFiatRates(r.Currencies, r.Token)
}
return
},
"getFiatRatesForTimestamps": func(s *WebsocketServer, c *websocketChannel, req *websocketReq) (rv interface{}, err error) {
r := struct {
Timestamps []int64 `json:"timestamps"`
Currencies []string `json:"currencies"`
Token string `json:"token"`
}{}
"getFiatRatesForTimestamps": func(s *WebsocketServer, c *websocketChannel, req *WsReq) (rv interface{}, err error) {
r := WsFiatRatesForTimestampsReq{}
err = json.Unmarshal(req.Params, &r)
if err == nil {
rv, err = s.getFiatRatesForTimestamps(r.Timestamps, r.Currencies, r.Token)
}
return
},
"getFiatRatesTickersList": func(s *WebsocketServer, c *websocketChannel, req *websocketReq) (rv interface{}, err error) {
r := struct {
Timestamp int64 `json:"timestamp"`
Token string `json:"token"`
}{}
"getFiatRatesTickersList": func(s *WebsocketServer, c *websocketChannel, req *WsReq) (rv interface{}, err error) {
r := WsFiatRatesTickersListReq{}
err = json.Unmarshal(req.Params, &r)
if err == nil {
rv, err = s.getAvailableVsCurrencies(r.Timestamp, r.Token)
@ -456,7 +408,7 @@ var requestHandlers = map[string]func(*WebsocketServer, *websocketChannel, *webs
},
}
func (s *WebsocketServer) onRequest(c *websocketChannel, req *websocketReq) {
func (s *WebsocketServer) onRequest(c *websocketChannel, req *WsReq) {
var err error
var data interface{}
defer func() {
@ -469,7 +421,7 @@ func (s *WebsocketServer) onRequest(c *websocketChannel, req *websocketReq) {
}
// nil data means no response
if data != nil {
c.DataOut(&websocketRes{
c.DataOut(&WsRes{
ID: req.ID,
Data: data,
})
@ -499,21 +451,8 @@ func (s *WebsocketServer) onRequest(c *websocketChannel, req *websocketReq) {
}
}
type accountInfoReq struct {
Descriptor string `json:"descriptor"`
Details string `json:"details"`
Tokens string `json:"tokens"`
PageSize int `json:"pageSize"`
Page int `json:"page"`
FromHeight int `json:"from"`
ToHeight int `json:"to"`
ContractFilter string `json:"contractFilter"`
SecondaryCurrency string `json:"secondaryCurrency"`
Gap int `json:"gap"`
}
func unmarshalGetAccountInfoRequest(params []byte) (*accountInfoReq, error) {
var r accountInfoReq
func unmarshalGetAccountInfoRequest(params []byte) (*WsAccountInfoReq, error) {
var r WsAccountInfoReq
err := json.Unmarshal(params, &r)
if err != nil {
return nil, err
@ -521,7 +460,7 @@ func unmarshalGetAccountInfoRequest(params []byte) (*accountInfoReq, error) {
return &r, nil
}
func (s *WebsocketServer) getAccountInfo(req *accountInfoReq) (res *api.Address, err error) {
func (s *WebsocketServer) getAccountInfo(req *WsAccountInfoReq) (res *api.Address, err error) {
var opt api.AccountDetails
switch req.Details {
case "tokens":
@ -563,7 +502,7 @@ func (s *WebsocketServer) getAccountInfo(req *accountInfoReq) (res *api.Address,
return a, nil
}
func (s *WebsocketServer) getAccountUtxo(descriptor string) (interface{}, error) {
func (s *WebsocketServer) getAccountUtxo(descriptor string) (api.Utxos, error) {
utxo, err := s.api.GetXpubUtxo(descriptor, false, 0)
if err != nil {
return s.api.GetAddressUtxo(descriptor, false)
@ -571,7 +510,7 @@ func (s *WebsocketServer) getAccountUtxo(descriptor string) (interface{}, error)
return utxo, nil
}
func (s *WebsocketServer) getTransaction(txid string) (interface{}, error) {
func (s *WebsocketServer) getTransaction(txid string) (*api.Tx, error) {
return s.api.GetTransaction(txid, false, false)
}
@ -579,31 +518,14 @@ func (s *WebsocketServer) getTransactionSpecific(txid string) (interface{}, erro
return s.chain.GetTransactionSpecific(&bchain.Tx{Txid: txid})
}
func (s *WebsocketServer) getInfo() (interface{}, error) {
func (s *WebsocketServer) getInfo() (*WsInfoRes, error) {
vi := common.GetVersionInfo()
bi := s.is.GetBackendInfo()
height, hash, err := s.db.GetBestBlock()
if err != nil {
return nil, err
}
type backendInfo struct {
Version string `json:"version,omitempty"`
Subversion string `json:"subversion,omitempty"`
ConsensusVersion string `json:"consensus_version,omitempty"`
Consensus interface{} `json:"consensus,omitempty"`
}
type info struct {
Name string `json:"name"`
Shortcut string `json:"shortcut"`
Decimals int `json:"decimals"`
Version string `json:"version"`
BestHeight int `json:"bestHeight"`
BestHash string `json:"bestHash"`
Block0Hash string `json:"block0Hash"`
Testnet bool `json:"testnet"`
Backend backendInfo `json:"backend"`
}
return &info{
return &WsInfoRes{
Name: s.is.Coin,
Shortcut: s.is.CoinShortcut,
Decimals: s.chainParser.AmountDecimals(),
@ -612,7 +534,7 @@ func (s *WebsocketServer) getInfo() (interface{}, error) {
Version: vi.Version,
Block0Hash: s.block0hash,
Testnet: s.chain.IsTestnet(),
Backend: backendInfo{
Backend: WsBackendInfo{
Version: bi.Version,
Subversion: bi.Subversion,
ConsensusVersion: bi.ConsensusVersion,
@ -621,15 +543,12 @@ func (s *WebsocketServer) getInfo() (interface{}, error) {
}, nil
}
func (s *WebsocketServer) getBlockHash(height int) (interface{}, error) {
func (s *WebsocketServer) getBlockHash(height int) (*WsBlockHashRes, error) {
h, err := s.db.GetBlockHash(uint32(height))
if err != nil {
return nil, err
}
type hash struct {
Hash string `json:"hash"`
}
return &hash{
return &WsBlockHashRes{
Hash: h,
}, nil
}
@ -643,21 +562,12 @@ func (s *WebsocketServer) getBlock(id string, page, pageSize int) (interface{},
}
func (s *WebsocketServer) estimateFee(c *websocketChannel, params []byte) (interface{}, error) {
type estimateFeeReq struct {
Blocks []int `json:"blocks"`
Specific map[string]interface{} `json:"specific"`
}
type estimateFeeRes struct {
FeePerTx string `json:"feePerTx,omitempty"`
FeePerUnit string `json:"feePerUnit,omitempty"`
FeeLimit string `json:"feeLimit,omitempty"`
}
var r estimateFeeReq
var r WsEstimateFeeReq
err := json.Unmarshal(params, &r)
if err != nil {
return nil, err
}
res := make([]estimateFeeRes, len(r.Blocks))
res := make([]WsEstimateFeeRes, len(r.Blocks))
if s.chainParser.GetChainType() == bchain.ChainEthereumType {
gas, err := s.chain.EthereumTypeEstimateGas(r.Specific)
if err != nil {
@ -729,7 +639,7 @@ type subscriptionResponseMessage struct {
Message string `json:"message"`
}
func (s *WebsocketServer) subscribeNewBlock(c *websocketChannel, req *websocketReq) (res interface{}, err error) {
func (s *WebsocketServer) subscribeNewBlock(c *websocketChannel, req *WsReq) (res interface{}, err error) {
s.newBlockSubscriptionsLock.Lock()
defer s.newBlockSubscriptionsLock.Unlock()
s.newBlockSubscriptions[c] = req.ID
@ -745,7 +655,7 @@ func (s *WebsocketServer) unsubscribeNewBlock(c *websocketChannel) (res interfac
return &subscriptionResponse{false}, nil
}
func (s *WebsocketServer) subscribeNewTransaction(c *websocketChannel, req *websocketReq) (res interface{}, err error) {
func (s *WebsocketServer) subscribeNewTransaction(c *websocketChannel, req *WsReq) (res interface{}, err error) {
s.newTransactionSubscriptionsLock.Lock()
defer s.newTransactionSubscriptionsLock.Unlock()
if !s.newTransactionEnabled {
@ -768,9 +678,7 @@ func (s *WebsocketServer) unsubscribeNewTransaction(c *websocketChannel) (res in
}
func (s *WebsocketServer) unmarshalAddresses(params []byte) ([]string, error) {
r := struct {
Addresses []string `json:"addresses"`
}{}
r := WsSubscribeAddressesReq{}
err := json.Unmarshal(params, &r)
if err != nil {
return nil, err
@ -804,7 +712,7 @@ func (s *WebsocketServer) doUnsubscribeAddresses(c *websocketChannel) {
c.addrDescs = nil
}
func (s *WebsocketServer) subscribeAddresses(c *websocketChannel, addrDesc []string, req *websocketReq) (res interface{}, err error) {
func (s *WebsocketServer) subscribeAddresses(c *websocketChannel, addrDesc []string, req *WsReq) (res interface{}, err error) {
s.addressSubscriptionsLock.Lock()
defer s.addressSubscriptionsLock.Unlock()
// unsubscribe all previous subscriptions
@ -847,7 +755,7 @@ func (s *WebsocketServer) doUnsubscribeFiatRates(c *websocketChannel) {
}
// subscribeFiatRates subscribes all FiatRates subscriptions by this channel
func (s *WebsocketServer) subscribeFiatRates(c *websocketChannel, d *fiatRatesSubscription, req *websocketReq) (res interface{}, err error) {
func (s *WebsocketServer) subscribeFiatRates(c *websocketChannel, d *WsSubscribeFiatRatesReq, req *WsReq) (res interface{}, err error) {
s.fiatRatesSubscriptionsLock.Lock()
defer s.fiatRatesSubscriptionsLock.Unlock()
// unsubscribe all previous subscriptions
@ -891,7 +799,7 @@ func (s *WebsocketServer) onNewBlockAsync(hash string, height uint32) {
Hash: hash,
}
for c, id := range s.newBlockSubscriptions {
c.DataOut(&websocketRes{
c.DataOut(&WsRes{
ID: id,
Data: &data,
})
@ -908,7 +816,7 @@ func (s *WebsocketServer) sendOnNewTx(tx *api.Tx) {
s.newTransactionSubscriptionsLock.Lock()
defer s.newTransactionSubscriptionsLock.Unlock()
for c, id := range s.newTransactionSubscriptions {
c.DataOut(&websocketRes{
c.DataOut(&WsRes{
ID: id,
Data: &tx,
})
@ -936,7 +844,7 @@ func (s *WebsocketServer) sendOnNewTxAddr(stringAddressDescriptor string, tx *ap
as, ok := s.addressSubscriptions[stringAddressDescriptor]
if ok {
for c, id := range as {
c.DataOut(&websocketRes{
c.DataOut(&WsRes{
ID: id,
Data: &data,
})
@ -1038,12 +946,12 @@ func (s *WebsocketServer) broadcastTicker(currency string, rates map[string]floa
dataWithTokens.TokenRates[token] = rate
}
}
c.DataOut(&websocketRes{
c.DataOut(&WsRes{
ID: id,
Data: &dataWithTokens,
})
} else {
c.DataOut(&websocketRes{
c.DataOut(&WsRes{
ID: id,
Data: &data,
})
@ -1063,17 +971,17 @@ func (s *WebsocketServer) OnNewFiatRatesTicker(ticker *common.CurrencyRatesTicke
s.broadcastTicker(allFiatRates, ticker.Rates, nil)
}
func (s *WebsocketServer) getCurrentFiatRates(currencies []string, token string) (interface{}, error) {
func (s *WebsocketServer) getCurrentFiatRates(currencies []string, token string) (*api.FiatTicker, error) {
ret, err := s.api.GetCurrentFiatRates(currencies, strings.ToLower(token))
return ret, err
}
func (s *WebsocketServer) getFiatRatesForTimestamps(timestamps []int64, currencies []string, token string) (interface{}, error) {
func (s *WebsocketServer) getFiatRatesForTimestamps(timestamps []int64, currencies []string, token string) (*api.FiatTickers, error) {
ret, err := s.api.GetFiatRatesForTimestamps(timestamps, currencies, strings.ToLower(token))
return ret, err
}
func (s *WebsocketServer) getAvailableVsCurrencies(timestamp int64, token string) (interface{}, error) {
func (s *WebsocketServer) getAvailableVsCurrencies(timestamp int64, token string) (*api.AvailableVsCurrencies, error) {
ret, err := s.api.GetAvailableVsCurrencies(timestamp, strings.ToLower(token))
return ret, err
}

120
server/ws_types.go Normal file
View File

@ -0,0 +1,120 @@
package server
import "encoding/json"
type WsReq struct {
ID string `json:"id"`
Method string `json:"method" ts_type:"'getAccountInfo' | 'getInfo' | 'getBlockHash' | 'getAccountUtxo' | 'getBalanceHistory' | 'getTransaction' | 'getTransactionSpecific' | 'estimateFee' | 'sendTransaction' | 'subscribeNewBlock' | 'unsubscribeNewBlock' | 'subscribeNewTransaction' | 'unsubscribeNewTransaction' | 'subscribeAddresses' | 'unsubscribeAddresses' | 'subscribeFiatRates' | 'unsubscribeFiatRates' | 'ping' | 'getCurrentFiatRates' | 'getFiatRatesForTimestamps' | 'getFiatRatesTickersList'"`
Params json.RawMessage `json:"params" ts_type:"any"`
}
type WsRes struct {
ID string `json:"id"`
Data interface{} `json:"data"`
}
type WsAccountInfoReq struct {
Descriptor string `json:"descriptor"`
Details string `json:"details,omitempty" ts_type:"'basic' | 'tokens' | 'tokenBalances' | 'txids' | 'txslight' | 'txs'"`
Tokens string `json:"tokens,omitempty" ts_type:"'derived' | 'used' | 'nonzero'"`
PageSize int `json:"pageSize,omitempty"`
Page int `json:"page,omitempty"`
FromHeight int `json:"from,omitempty"`
ToHeight int `json:"to,omitempty"`
ContractFilter string `json:"contractFilter,omitempty"`
SecondaryCurrency string `json:"secondaryCurrency,omitempty"`
Gap int `json:"gap,omitempty"`
}
type WsBackendInfo struct {
Version string `json:"version,omitempty"`
Subversion string `json:"subversion,omitempty"`
ConsensusVersion string `json:"consensus_version,omitempty"`
Consensus interface{} `json:"consensus,omitempty"`
}
type WsInfoRes struct {
Name string `json:"name"`
Shortcut string `json:"shortcut"`
Decimals int `json:"decimals"`
Version string `json:"version"`
BestHeight int `json:"bestHeight"`
BestHash string `json:"bestHash"`
Block0Hash string `json:"block0Hash"`
Testnet bool `json:"testnet"`
Backend WsBackendInfo `json:"backend"`
}
type WsBlockHashReq struct {
Height int `json:"height"`
}
type WsBlockHashRes struct {
Hash string `json:"hash"`
}
type WsBlockReq struct {
Id string `json:"id"`
PageSize int `json:"pageSize,omitempty"`
Page int `json:"page,omitempty"`
}
type WsAccountUtxoReq struct {
Descriptor string `json:"descriptor"`
}
type WsBalanceHistoryReq struct {
Descriptor string `json:"descriptor"`
From int64 `json:"from,omitempty"`
To int64 `json:"to,omitempty"`
Currencies []string `json:"currencies,omitempty"`
Gap int `json:"gap,omitempty"`
GroupBy uint32 `json:"groupBy,omitempty"`
}
type WsTransactionReq struct {
Txid string `json:"txid"`
}
type WsTransactionSpecificReq struct {
Txid string `json:"txid"`
}
type WsEstimateFeeReq struct {
Blocks []int `json:"blocks,omitempty"`
Specific map[string]interface{} `json:"specific,omitempty" ts_type:"{conservative?: boolean;txsize?: number;from?: string;to?: string;data?: string;value?: string;}"`
}
type WsEstimateFeeRes struct {
FeePerTx string `json:"feePerTx,omitempty"`
FeePerUnit string `json:"feePerUnit,omitempty"`
FeeLimit string `json:"feeLimit,omitempty"`
}
type WsSendTransactionReq struct {
Hex string `json:"hex"`
}
type WsSubscribeAddressesReq struct {
Addresses []string `json:"addresses"`
}
type WsSubscribeFiatRatesReq struct {
Currency string `json:"currency,omitempty"`
Tokens []string `json:"tokens,omitempty"`
}
type WsCurrentFiatRatesReq struct {
Currencies []string `json:"currencies,omitempty"`
Token string `json:"token,omitempty"`
}
type WsFiatRatesForTimestampsReq struct {
Timestamps []int64 `json:"timestamps"`
Currencies []string `json:"currencies,omitempty"`
Token string `json:"token,omitempty"`
}
type WsFiatRatesTickersListReq struct {
Timestamp int64 `json:"timestamp,omitempty"`
Token string `json:"token,omitempty"`
}