@@ -23,15 +23,19 @@ import (
23
23
"net/http"
24
24
"net/url"
25
25
"path"
26
+ "time"
26
27
27
28
"github.com/BitBoxSwiss/bitbox-wallet-app/backend/accounts"
29
+ "github.com/BitBoxSwiss/bitbox-wallet-app/backend/coins/coin"
28
30
"github.com/BitBoxSwiss/bitbox-wallet-app/backend/coins/eth/erc20"
29
31
"github.com/BitBoxSwiss/bitbox-wallet-app/backend/coins/eth/rpcclient"
30
32
ethtypes "github.com/BitBoxSwiss/bitbox-wallet-app/backend/coins/eth/types"
31
33
"github.com/BitBoxSwiss/bitbox-wallet-app/util/errp"
34
+ "github.com/BitBoxSwiss/bitbox-wallet-app/util/logging"
32
35
"github.com/ethereum/go-ethereum"
33
36
"github.com/ethereum/go-ethereum/common"
34
37
"github.com/ethereum/go-ethereum/core/types"
38
+ "github.com/sirupsen/logrus"
35
39
"golang.org/x/time/rate"
36
40
)
37
41
@@ -45,6 +49,8 @@ type Blockbook struct {
45
49
url string
46
50
httpClient * http.Client
47
51
limiter * rate.Limiter
52
+ // TODO remove before merging into master?
53
+ log * logrus.Entry
48
54
}
49
55
50
56
// NewBlockbook creates a new instance of EtherScan.
@@ -54,6 +60,7 @@ func NewBlockbook(chainId string, httpClient *http.Client) *Blockbook {
54
60
url : "https://bb1.shiftcrypto.io/api/" ,
55
61
httpClient : httpClient ,
56
62
limiter : rate .NewLimiter (rate .Limit (callsPerSec ), 1 ),
63
+ log : logging .Get ().WithField ("ETH Client" , "Blockbook" ),
57
64
}
58
65
}
59
66
@@ -76,15 +83,15 @@ func (blockbook *Blockbook) call(ctx context.Context, handler string, params url
76
83
if err != nil {
77
84
return errp .WithStack (err )
78
85
}
86
+
79
87
if err := json .Unmarshal (body , result ); err != nil {
80
88
return errp .Newf ("unexpected response from blockbook: %s" , string (body ))
81
89
}
82
90
83
91
return nil
84
92
}
85
93
86
- func (blockbook * Blockbook ) address (ctx context.Context , account common.Address , result interface {}) error {
87
- params := url.Values {}
94
+ func (blockbook * Blockbook ) address (ctx context.Context , account common.Address , params url.Values , result interface {}) error {
88
95
address := account .Hex ()
89
96
90
97
if err := blockbook .call (ctx , path .Join ("address" , address ), params , result ); err != nil {
@@ -100,7 +107,7 @@ func (blockbook *Blockbook) Balance(ctx context.Context, account common.Address)
100
107
Balance string `json:"balance"`
101
108
}{}
102
109
103
- if err := blockbook .address (ctx , account , & result ); err != nil {
110
+ if err := blockbook .address (ctx , account , url. Values {}, & result ); err != nil {
104
111
return nil , errp .WithStack (err )
105
112
}
106
113
@@ -122,9 +129,114 @@ func (blockbook *Blockbook) PendingNonceAt(ctx context.Context, account common.A
122
129
return 0 , fmt .Errorf ("Not yet implemented" )
123
130
}
124
131
132
+ // prepareTransactions casts to []accounts.Transactions and removes duplicate entries and sets the
133
+ // transaction type (send, receive, send to self) based on the account address.
134
+ func prepareTransactions (
135
+ isERC20 bool ,
136
+ blockTipHeight * big.Int ,
137
+ isInternal bool ,
138
+ transactions []* Tx , address common.Address ) ([]* accounts.TransactionData , error ) {
139
+ seen := map [string ]struct {}{}
140
+
141
+ // TODO figure out if needed. Etherscan.go uses this to compute the num of confirmations.
142
+ // But numConfirmations is already returned by the API call.
143
+ _ = blockTipHeight
144
+
145
+ _ = isInternal // TODO figure out how to deal with internal txs.
146
+
147
+ castedTransactions := make ([]* accounts.TransactionData , 0 , len (transactions ))
148
+ ours := address .Hex ()
149
+ for _ , tx := range transactions {
150
+ if _ , ok := seen [tx .Txid ]; ok {
151
+ // Skip duplicate transactions.
152
+ continue
153
+ }
154
+ seen [tx .Txid ] = struct {}{}
155
+
156
+ fee := coin .NewAmount (tx .FeesSat .Int )
157
+ timestamp := time .Unix (tx .Blocktime , 0 )
158
+ status , err := tx .Status ()
159
+ // TODO do not ignore unconfirmed tx
160
+ if status == accounts .TxStatusPending {
161
+ continue
162
+ }
163
+ if err != nil {
164
+ return nil , errp .WithStack (err )
165
+ }
166
+ from := tx .Vin [0 ].Addresses [0 ]
167
+ var to string
168
+ if len (tx .TokenTransfers ) > 0 {
169
+ to = tx .TokenTransfers [0 ].To
170
+ } else {
171
+ to = tx .Vout [0 ].Addresses [0 ]
172
+ }
173
+ if ours != from && ours != to {
174
+ return nil , errp .New ("transaction does not belong to our account" )
175
+ }
176
+
177
+ var txType accounts.TxType
178
+ switch {
179
+ case ours == from && ours == to :
180
+ txType = accounts .TxTypeSendSelf
181
+ case ours == from :
182
+ txType = accounts .TxTypeSend
183
+ default :
184
+ txType = accounts .TxTypeReceive
185
+ }
186
+
187
+ addresses , err := tx .Addresses (isERC20 )
188
+ if err != nil {
189
+ return nil , errp .WithStack (err )
190
+ }
191
+ castedTransaction := & accounts.TransactionData {
192
+ Fee : & fee ,
193
+ FeeIsDifferentUnit : isERC20 ,
194
+ Timestamp : & timestamp ,
195
+ TxID : tx .Txid ,
196
+ InternalID : tx .Txid ,
197
+ Height : tx .Blockheight ,
198
+ NumConfirmations : int (tx .Confirmations ),
199
+ NumConfirmationsComplete : ethtypes .NumConfirmationsComplete ,
200
+ Status : status ,
201
+ Type : txType ,
202
+ Amount : tx .Amount (address .Hex (), isERC20 ),
203
+ Gas : tx .EthereumSpecific .GasUsed .Uint64 (),
204
+ Nonce : & tx .EthereumSpecific .Nonce ,
205
+ Addresses : addresses ,
206
+ IsErc20 : isERC20 ,
207
+ }
208
+ castedTransactions = append (castedTransactions , castedTransaction )
209
+ }
210
+ return castedTransactions , nil
211
+ }
212
+
125
213
// Transactions implement TransactionSource.
126
214
func (blockbook * Blockbook ) Transactions (blockTipHeight * big.Int , address common.Address , endBlock * big.Int , erc20Token * erc20.Token ) ([]* accounts.TransactionData , error ) {
127
- return nil , fmt .Errorf ("Not yet implemented" )
215
+ params := url.Values {}
216
+ isERC20 := erc20Token != nil
217
+ if isERC20 {
218
+ params .Set ("contract" , erc20Token .ContractAddress ().Hex ())
219
+ }
220
+ params .Set ("details" , "txslight" )
221
+ if endBlock != nil {
222
+ params .Set ("endBlock" , endBlock .String ())
223
+ }
224
+ result := struct {
225
+ Transactions []* Tx `json:"transactions"`
226
+ }{}
227
+
228
+ if err := blockbook .address (context .Background (), address , params , & result ); err != nil {
229
+ return nil , errp .WithStack (err )
230
+ }
231
+
232
+ transactionsNormal , err := prepareTransactions (isERC20 , blockTipHeight , false , result .Transactions , address )
233
+
234
+ if err != nil {
235
+ return nil , errp .WithStack (err )
236
+ }
237
+
238
+ return transactionsNormal , nil
239
+
128
240
}
129
241
130
242
// SendTransaction implements rpc.Interface.
0 commit comments