arnaucube
arnaucube/hermez-nodePublic Self-hosted repository
hermez-node/api/dbtoapistructs.go
api/dbtoapistructs.goRaw
  1package api
  2
  3import (
  4	"encoding/base64"
  5	"errors"
  6	"fmt"
  7	"math/big"
  8	"strconv"
  9	"time"
 10
 11	ethCommon "github.com/ethereum/go-ethereum/common"
 12	"github.com/hermeznetwork/hermez-node/common"
 13	"github.com/hermeznetwork/hermez-node/db"
 14	"github.com/hermeznetwork/hermez-node/db/historydb"
 15	"github.com/hermeznetwork/hermez-node/db/l2db"
 16	"github.com/hermeznetwork/hermez-node/eth"
 17	"github.com/iden3/go-iden3-crypto/babyjub"
 18	"github.com/iden3/go-merkletree"
 19)
 20
 21const exitIdx = "hez:EXIT:1"
 22
 23type errorMsg struct {
 24	Message string
 25}
 26
 27func bjjToString(bjj *babyjub.PublicKey) string {
 28	pkComp := [32]byte(bjj.Compress())
 29	sum := pkComp[0]
 30	for i := 1; i < len(pkComp); i++ {
 31		sum += pkComp[i]
 32	}
 33	bjjSum := append(pkComp[:], sum)
 34	return "hez:" + base64.RawURLEncoding.EncodeToString(bjjSum)
 35}
 36
 37func ethAddrToHez(addr ethCommon.Address) string {
 38	return "hez:" + addr.String()
 39}
 40
 41func idxToHez(idx common.Idx, tokenSymbol string) string {
 42	if idx == 1 {
 43		return exitIdx
 44	}
 45	return "hez:" + tokenSymbol + ":" + strconv.Itoa(int(idx))
 46}
 47
 48// History Tx
 49
 50type historyTxsAPI struct {
 51	Txs        []historyTxAPI `json:"transactions"`
 52	Pagination *db.Pagination `json:"pagination"`
 53}
 54
 55func (htx *historyTxsAPI) GetPagination() *db.Pagination {
 56	if htx.Txs[0].ItemID < htx.Txs[len(htx.Txs)-1].ItemID {
 57		htx.Pagination.FirstReturnedItem = htx.Txs[0].ItemID
 58		htx.Pagination.LastReturnedItem = htx.Txs[len(htx.Txs)-1].ItemID
 59	} else {
 60		htx.Pagination.LastReturnedItem = htx.Txs[0].ItemID
 61		htx.Pagination.FirstReturnedItem = htx.Txs[len(htx.Txs)-1].ItemID
 62	}
 63	return htx.Pagination
 64}
 65func (htx *historyTxsAPI) Len() int { return len(htx.Txs) }
 66
 67type l1Info struct {
 68	ToForgeL1TxsNum       *int64   `json:"toForgeL1TransactionsNum"`
 69	UserOrigin            bool     `json:"userOrigin"`
 70	FromEthAddr           string   `json:"fromHezEthereumAddress"`
 71	FromBJJ               string   `json:"fromBJJ"`
 72	LoadAmount            string   `json:"loadAmount"`
 73	HistoricLoadAmountUSD *float64 `json:"historicLoadAmountUSD"`
 74	EthBlockNum           int64    `json:"ethereumBlockNum"`
 75}
 76
 77type l2Info struct {
 78	Fee            common.FeeSelector `json:"fee"`
 79	HistoricFeeUSD *float64           `json:"historicFeeUSD"`
 80	Nonce          common.Nonce       `json:"nonce"`
 81}
 82
 83type historyTxAPI struct {
 84	IsL1        string           `json:"L1orL2"`
 85	TxID        common.TxID      `json:"id"`
 86	ItemID      int              `json:"itemId"`
 87	Type        common.TxType    `json:"type"`
 88	Position    int              `json:"position"`
 89	FromIdx     *string          `json:"fromAccountIndex"`
 90	ToIdx       string           `json:"toAccountIndex"`
 91	Amount      string           `json:"amount"`
 92	BatchNum    *common.BatchNum `json:"batchNum"`
 93	HistoricUSD *float64         `json:"historicUSD"`
 94	Timestamp   time.Time        `json:"timestamp"`
 95	L1Info      *l1Info          `json:"L1Info"`
 96	L2Info      *l2Info          `json:"L2Info"`
 97	Token       tokenAPI         `json:"token"`
 98}
 99
100func historyTxsToAPI(dbTxs []historydb.HistoryTx) []historyTxAPI {
101	apiTxs := []historyTxAPI{}
102	for i := 0; i < len(dbTxs); i++ {
103		apiTx := historyTxAPI{
104			TxID:        dbTxs[i].TxID,
105			ItemID:      dbTxs[i].ItemID,
106			Type:        dbTxs[i].Type,
107			Position:    dbTxs[i].Position,
108			ToIdx:       idxToHez(dbTxs[i].ToIdx, dbTxs[i].TokenSymbol),
109			Amount:      dbTxs[i].Amount.String(),
110			HistoricUSD: dbTxs[i].HistoricUSD,
111			BatchNum:    dbTxs[i].BatchNum,
112			Timestamp:   dbTxs[i].Timestamp,
113			Token: tokenAPI{
114				TokenID:     dbTxs[i].TokenID,
115				EthBlockNum: dbTxs[i].TokenEthBlockNum,
116				EthAddr:     dbTxs[i].TokenEthAddr,
117				Name:        dbTxs[i].TokenName,
118				Symbol:      dbTxs[i].TokenSymbol,
119				Decimals:    dbTxs[i].TokenDecimals,
120				USD:         dbTxs[i].TokenUSD,
121				USDUpdate:   dbTxs[i].TokenUSDUpdate,
122			},
123			L1Info: nil,
124			L2Info: nil,
125		}
126		if dbTxs[i].FromIdx != nil {
127			fromIdx := new(string)
128			*fromIdx = idxToHez(*dbTxs[i].FromIdx, dbTxs[i].TokenSymbol)
129			apiTx.FromIdx = fromIdx
130		}
131		if dbTxs[i].IsL1 {
132			apiTx.IsL1 = "L1"
133			apiTx.L1Info = &l1Info{
134				ToForgeL1TxsNum:       dbTxs[i].ToForgeL1TxsNum,
135				UserOrigin:            *dbTxs[i].UserOrigin,
136				FromEthAddr:           ethAddrToHez(*dbTxs[i].FromEthAddr),
137				FromBJJ:               bjjToString(dbTxs[i].FromBJJ),
138				LoadAmount:            dbTxs[i].LoadAmount.String(),
139				HistoricLoadAmountUSD: dbTxs[i].HistoricLoadAmountUSD,
140				EthBlockNum:           dbTxs[i].EthBlockNum,
141			}
142		} else {
143			apiTx.IsL1 = "L2"
144			apiTx.L2Info = &l2Info{
145				Fee:            *dbTxs[i].Fee,
146				HistoricFeeUSD: dbTxs[i].HistoricFeeUSD,
147				Nonce:          *dbTxs[i].Nonce,
148			}
149		}
150		apiTxs = append(apiTxs, apiTx)
151	}
152	return apiTxs
153}
154
155// Exit
156
157type exitsAPI struct {
158	Exits      []exitAPI      `json:"exits"`
159	Pagination *db.Pagination `json:"pagination"`
160}
161
162func (e *exitsAPI) GetPagination() *db.Pagination {
163	if e.Exits[0].ItemID < e.Exits[len(e.Exits)-1].ItemID {
164		e.Pagination.FirstReturnedItem = e.Exits[0].ItemID
165		e.Pagination.LastReturnedItem = e.Exits[len(e.Exits)-1].ItemID
166	} else {
167		e.Pagination.LastReturnedItem = e.Exits[0].ItemID
168		e.Pagination.FirstReturnedItem = e.Exits[len(e.Exits)-1].ItemID
169	}
170	return e.Pagination
171}
172func (e *exitsAPI) Len() int { return len(e.Exits) }
173
174type exitAPI struct {
175	ItemID                 int                             `json:"itemId"`
176	BatchNum               common.BatchNum                 `json:"batchNum"`
177	AccountIdx             string                          `json:"accountIndex"`
178	MerkleProof            *merkletree.CircomVerifierProof `json:"merkleProof"`
179	Balance                string                          `json:"balance"`
180	InstantWithdrawn       *int64                          `json:"instantWithdrawn"`
181	DelayedWithdrawRequest *int64                          `json:"delayedWithdrawRequest"`
182	DelayedWithdrawn       *int64                          `json:"delayedWithdrawn"`
183	Token                  tokenAPI                        `json:"token"`
184}
185
186func historyExitsToAPI(dbExits []historydb.HistoryExit) []exitAPI {
187	apiExits := []exitAPI{}
188	for i := 0; i < len(dbExits); i++ {
189		apiExits = append(apiExits, exitAPI{
190			ItemID:                 dbExits[i].ItemID,
191			BatchNum:               dbExits[i].BatchNum,
192			AccountIdx:             idxToHez(dbExits[i].AccountIdx, dbExits[i].TokenSymbol),
193			MerkleProof:            dbExits[i].MerkleProof,
194			Balance:                dbExits[i].Balance.String(),
195			InstantWithdrawn:       dbExits[i].InstantWithdrawn,
196			DelayedWithdrawRequest: dbExits[i].DelayedWithdrawRequest,
197			DelayedWithdrawn:       dbExits[i].DelayedWithdrawn,
198			Token: tokenAPI{
199				TokenID:     dbExits[i].TokenID,
200				EthBlockNum: dbExits[i].TokenEthBlockNum,
201				EthAddr:     dbExits[i].TokenEthAddr,
202				Name:        dbExits[i].TokenName,
203				Symbol:      dbExits[i].TokenSymbol,
204				Decimals:    dbExits[i].TokenDecimals,
205				USD:         dbExits[i].TokenUSD,
206				USDUpdate:   dbExits[i].TokenUSDUpdate,
207			},
208		})
209	}
210	return apiExits
211}
212
213// Tokens
214type tokensAPI struct {
215	Tokens     []tokenAPI     `json:"tokens"`
216	Pagination *db.Pagination `json:"pagination"`
217}
218
219func (t *tokensAPI) GetPagination() *db.Pagination {
220	if t.Tokens[0].ItemID < t.Tokens[len(t.Tokens)-1].ItemID {
221		t.Pagination.FirstReturnedItem = t.Tokens[0].ItemID
222		t.Pagination.LastReturnedItem = t.Tokens[len(t.Tokens)-1].ItemID
223	} else {
224		t.Pagination.LastReturnedItem = t.Tokens[0].ItemID
225		t.Pagination.FirstReturnedItem = t.Tokens[len(t.Tokens)-1].ItemID
226	}
227	return t.Pagination
228}
229func (t *tokensAPI) Len() int { return len(t.Tokens) }
230
231type tokenAPI struct {
232	ItemID      int               `json:"itemId"`
233	TokenID     common.TokenID    `json:"id"`
234	EthBlockNum int64             `json:"ethereumBlockNum"` // Ethereum block number in which this token was registered
235	EthAddr     ethCommon.Address `json:"ethereumAddress"`
236	Name        string            `json:"name"`
237	Symbol      string            `json:"symbol"`
238	Decimals    uint64            `json:"decimals"`
239	USD         *float64          `json:"USD"`
240	USDUpdate   *time.Time        `json:"fiatUpdate"`
241}
242
243func tokensToAPI(dbTokens []historydb.TokenRead) []tokenAPI {
244	apiTokens := []tokenAPI{}
245	for i := 0; i < len(dbTokens); i++ {
246		apiTokens = append(apiTokens, tokenAPI{
247			ItemID:      dbTokens[i].ItemID,
248			TokenID:     dbTokens[i].TokenID,
249			EthBlockNum: dbTokens[i].EthBlockNum,
250			EthAddr:     dbTokens[i].EthAddr,
251			Name:        dbTokens[i].Name,
252			Symbol:      dbTokens[i].Symbol,
253			Decimals:    dbTokens[i].Decimals,
254			USD:         dbTokens[i].USD,
255			USDUpdate:   dbTokens[i].USDUpdate,
256		})
257	}
258	return apiTokens
259}
260
261// Config
262
263type rollupConstants struct {
264	PublicConstants         eth.RollupPublicConstants `json:"publicConstants"`
265	MaxFeeIdxCoordinator    int                       `json:"maxFeeIdxCoordinator"`
266	ReservedIdx             int                       `json:"reservedIdx"`
267	ExitIdx                 int                       `json:"exitIdx"`
268	LimitLoadAmount         *big.Int                  `json:"limitLoadAmount"`
269	LimitL2TransferAmount   *big.Int                  `json:"limitL2TransferAmount"`
270	LimitTokens             int                       `json:"limitTokens"`
271	L1CoordinatorTotalBytes int                       `json:"l1CoordinatorTotalBytes"`
272	L1UserTotalBytes        int                       `json:"l1UserTotalBytes"`
273	MaxL1UserTx             int                       `json:"maxL1UserTx"`
274	MaxL1Tx                 int                       `json:"maxL1Tx"`
275	InputSHAConstantBytes   int                       `json:"inputSHAConstantBytes"`
276	NumBuckets              int                       `json:"numBuckets"`
277	MaxWithdrawalDelay      int                       `json:"maxWithdrawalDelay"`
278	ExchangeMultiplier      int                       `json:"exchangeMultiplier"`
279}
280
281type configAPI struct {
282	RollupConstants   rollupConstants       `json:"hermez"`
283	AuctionConstants  eth.AuctionConstants  `json:"auction"`
284	WDelayerConstants eth.WDelayerConstants `json:"withdrawalDelayer"`
285}
286
287// PoolL2Tx
288
289type receivedPoolTx struct {
290	TxID        common.TxID           `json:"id" binding:"required"`
291	Type        common.TxType         `json:"type" binding:"required"`
292	TokenID     common.TokenID        `json:"tokenId"`
293	FromIdx     string                `json:"fromAccountIndex" binding:"required"`
294	ToIdx       *string               `json:"toAccountIndex"`
295	ToEthAddr   *string               `json:"toHezEthereumAddress"`
296	ToBJJ       *string               `json:"toBjj"`
297	Amount      string                `json:"amount" binding:"required"`
298	Fee         common.FeeSelector    `json:"fee"`
299	Nonce       common.Nonce          `json:"nonce"`
300	Signature   babyjub.SignatureComp `json:"signature" binding:"required"`
301	RqFromIdx   *string               `json:"requestFromAccountIndex"`
302	RqToIdx     *string               `json:"requestToAccountIndex"`
303	RqToEthAddr *string               `json:"requestToHezEthereumAddress"`
304	RqToBJJ     *string               `json:"requestToBjj"`
305	RqTokenID   *common.TokenID       `json:"requestTokenId"`
306	RqAmount    *string               `json:"requestAmount"`
307	RqFee       *common.FeeSelector   `json:"requestFee"`
308	RqNonce     *common.Nonce         `json:"requestNonce"`
309}
310
311func (tx *receivedPoolTx) toDBWritePoolL2Tx() (*l2db.PoolL2TxWrite, error) {
312	amount := new(big.Int)
313	amount.SetString(tx.Amount, 10)
314	txw := &l2db.PoolL2TxWrite{
315		TxID:      tx.TxID,
316		TokenID:   tx.TokenID,
317		Amount:    amount,
318		Fee:       tx.Fee,
319		Nonce:     tx.Nonce,
320		State:     common.PoolL2TxStatePending,
321		Signature: tx.Signature,
322		RqTokenID: tx.RqTokenID,
323		RqFee:     tx.RqFee,
324		RqNonce:   tx.RqNonce,
325		Type:      tx.Type,
326	}
327	// Check FromIdx (required)
328	fidx, err := stringToIdx(tx.FromIdx, "fromAccountIndex")
329	if err != nil {
330		return nil, err
331	}
332	if fidx == nil {
333		return nil, errors.New("invalid fromAccountIndex")
334	}
335	// Set FromIdx
336	txw.FromIdx = common.Idx(*fidx)
337	// Set AmountFloat
338	f := new(big.Float).SetInt(amount)
339	amountF, _ := f.Float64()
340	txw.AmountFloat = amountF
341	if amountF < 0 {
342		return nil, errors.New("amount must be positive")
343	}
344	// Check "to" fields, only one of: ToIdx, ToEthAddr, ToBJJ
345	if tx.ToIdx != nil { // Case: Tx with ToIdx setted
346		// Set ToIdx
347		tidxUint, err := stringToIdx(*tx.ToIdx, "toAccountIndex")
348		if err != nil || tidxUint == nil {
349			return nil, errors.New("invalid toAccountIndex")
350		}
351		tidx := common.Idx(*tidxUint)
352		txw.ToIdx = &tidx
353	} else if tx.ToBJJ != nil { // Case: Tx with ToBJJ setted
354		// tx.ToEthAddr must be equal to ethAddrWhenBJJLower or ethAddrWhenBJJUpper
355		if tx.ToEthAddr != nil {
356			toEthAddr, err := hezStringToEthAddr(*tx.ToEthAddr, "toHezEthereumAddress")
357			if err != nil || *toEthAddr != common.FFAddr {
358				return nil, fmt.Errorf("if toBjj is setted, toHezEthereumAddress must be hez:%s", common.FFAddr.Hex())
359			}
360		} else {
361			return nil, fmt.Errorf("if toBjj is setted, toHezEthereumAddress must be hez:%s and toAccountIndex must be null", common.FFAddr.Hex())
362		}
363		// Set ToEthAddr and ToBJJ
364		toBJJ, err := hezStringToBJJ(*tx.ToBJJ, "toBjj")
365		if err != nil || toBJJ == nil {
366			return nil, errors.New("invalid toBjj")
367		}
368		txw.ToBJJ = toBJJ
369		txw.ToEthAddr = &common.FFAddr
370	} else if tx.ToEthAddr != nil { // Case: Tx with ToEthAddr setted
371		// Set ToEthAddr
372		toEthAddr, err := hezStringToEthAddr(*tx.ToEthAddr, "toHezEthereumAddress")
373		if err != nil || toEthAddr == nil {
374			return nil, errors.New("invalid toHezEthereumAddress")
375		}
376		txw.ToEthAddr = toEthAddr
377	} else {
378		return nil, errors.New("one of toAccountIndex, toHezEthereumAddress or toBjj must be setted")
379	}
380	// Check "rq" fields
381	if tx.RqFromIdx != nil {
382		// check and set RqFromIdx
383		rqfidxUint, err := stringToIdx(tx.FromIdx, "requestFromAccountIndex")
384		if err != nil || rqfidxUint == nil {
385			return nil, errors.New("invalid requestFromAccountIndex")
386		}
387		// Set RqFromIdx
388		rqfidx := common.Idx(*rqfidxUint)
389		txw.RqFromIdx = &rqfidx
390		// Case: RqTx with RqToIdx setted
391		if tx.RqToIdx != nil {
392			// Set ToIdx
393			tidxUint, err := stringToIdx(*tx.RqToIdx, "requestToAccountIndex")
394			if err != nil || tidxUint == nil {
395				return nil, errors.New("invalid requestToAccountIndex")
396			}
397			tidx := common.Idx(*tidxUint)
398			txw.ToIdx = &tidx
399		} else if tx.RqToBJJ != nil { // Case: Tx with ToBJJ setted
400			// tx.ToEthAddr must be equal to ethAddrWhenBJJLower or ethAddrWhenBJJUpper
401			if tx.RqToEthAddr != nil {
402				rqEthAddr, err := hezStringToEthAddr(*tx.RqToEthAddr, "")
403				if err != nil || *rqEthAddr != common.FFAddr {
404					return nil, fmt.Errorf("if requestToBjj is setted, requestToHezEthereumAddress must be hez:%s", common.FFAddr.Hex())
405				}
406			} else {
407				return nil, fmt.Errorf("if requestToBjj is setted, toHezEthereumAddress must be hez:%s and requestToAccountIndex must be null", common.FFAddr.Hex())
408			}
409			// Set ToEthAddr and ToBJJ
410			rqToBJJ, err := hezStringToBJJ(*tx.RqToBJJ, "requestToBjj")
411			if err != nil || rqToBJJ == nil {
412				return nil, errors.New("invalid requestToBjj")
413			}
414			txw.RqToBJJ = rqToBJJ
415			txw.RqToEthAddr = &common.FFAddr
416		} else if tx.RqToEthAddr != nil { // Case: Tx with ToEthAddr setted
417			// Set ToEthAddr
418			rqToEthAddr, err := hezStringToEthAddr(*tx.ToEthAddr, "requestToHezEthereumAddress")
419			if err != nil || rqToEthAddr == nil {
420				return nil, errors.New("invalid requestToHezEthereumAddress")
421			}
422			txw.RqToEthAddr = rqToEthAddr
423		} else {
424			return nil, errors.New("one of requestToAccountIndex, requestToHezEthereumAddress or requestToBjj must be setted")
425		}
426		if tx.RqAmount == nil {
427			return nil, errors.New("requestAmount must be provided if other request fields are setted")
428		}
429		rqAmount := new(big.Int)
430		rqAmount.SetString(*tx.RqAmount, 10)
431		txw.RqAmount = rqAmount
432	} else if tx.RqToIdx != nil && tx.RqToEthAddr != nil && tx.RqToBJJ != nil &&
433		tx.RqTokenID != nil && tx.RqAmount != nil && tx.RqNonce != nil && tx.RqFee != nil {
434		// if tx.RqToIdx is not setted, tx.Rq* must be null as well
435		return nil, errors.New("if requestFromAccountIndex is setted, the rest of request fields must be null as well")
436	}
437
438	return txw, validatePoolL2TxWrite(txw)
439}
440
441func validatePoolL2TxWrite(txw *l2db.PoolL2TxWrite) error {
442	poolTx := common.PoolL2Tx{
443		TxID:      txw.TxID,
444		FromIdx:   txw.FromIdx,
445		ToBJJ:     txw.ToBJJ,
446		TokenID:   txw.TokenID,
447		Amount:    txw.Amount,
448		Fee:       txw.Fee,
449		Nonce:     txw.Nonce,
450		State:     txw.State,
451		Signature: txw.Signature,
452		RqToBJJ:   txw.RqToBJJ,
453		RqAmount:  txw.RqAmount,
454		Type:      txw.Type,
455	}
456	// ToIdx
457	if txw.ToIdx != nil {
458		poolTx.ToIdx = *txw.ToIdx
459	}
460	// ToEthAddr
461	if txw.ToEthAddr == nil {
462		poolTx.ToEthAddr = common.EmptyAddr
463	} else {
464		poolTx.ToEthAddr = *txw.ToEthAddr
465	}
466	// RqFromIdx
467	if txw.RqFromIdx != nil {
468		poolTx.RqFromIdx = *txw.RqFromIdx
469	}
470	// RqToIdx
471	if txw.RqToIdx != nil {
472		poolTx.RqToIdx = *txw.RqToIdx
473	}
474	// RqToEthAddr
475	if txw.RqToEthAddr == nil {
476		poolTx.RqToEthAddr = common.EmptyAddr
477	} else {
478		poolTx.RqToEthAddr = *txw.RqToEthAddr
479	}
480	// RqTokenID
481	if txw.RqTokenID != nil {
482		poolTx.RqTokenID = *txw.RqTokenID
483	}
484	// RqFee
485	if txw.RqFee != nil {
486		poolTx.RqFee = *txw.RqFee
487	}
488	// RqNonce
489	if txw.RqNonce != nil {
490		poolTx.RqNonce = *txw.RqNonce
491	}
492	// Check type and id
493	_, err := common.NewPoolL2Tx(&poolTx)
494	if err != nil {
495		return err
496	}
497	// Check signature
498	// Get public key
499	account, err := s.GetAccount(poolTx.FromIdx)
500	if err != nil {
501		return err
502	}
503	if !poolTx.VerifySignature(account.PublicKey) {
504		return errors.New("wrong signature")
505	}
506	return nil
507}
508
509type sendPoolTx struct {
510	TxID        common.TxID           `json:"id"`
511	Type        common.TxType         `json:"type"`
512	FromIdx     string                `json:"fromAccountIndex"`
513	ToIdx       *string               `json:"toAccountIndex"`
514	ToEthAddr   *string               `json:"toHezEthereumAddress"`
515	ToBJJ       *string               `json:"toBjj"`
516	Amount      string                `json:"amount"`
517	Fee         common.FeeSelector    `json:"fee"`
518	Nonce       common.Nonce          `json:"nonce"`
519	State       common.PoolL2TxState  `json:"state"`
520	Signature   babyjub.SignatureComp `json:"signature"`
521	Timestamp   time.Time             `json:"timestamp"`
522	BatchNum    *common.BatchNum      `json:"batchNum"`
523	RqFromIdx   *string               `json:"requestFromAccountIndex"`
524	RqToIdx     *string               `json:"requestToAccountIndex"`
525	RqToEthAddr *string               `json:"requestToHezEthereumAddress"`
526	RqToBJJ     *string               `json:"requestToBJJ"`
527	RqTokenID   *common.TokenID       `json:"requestTokenId"`
528	RqAmount    *string               `json:"requestAmount"`
529	RqFee       *common.FeeSelector   `json:"requestFee"`
530	RqNonce     *common.Nonce         `json:"requestNonce"`
531	Token       tokenAPI              `json:"token"`
532}
533
534func poolL2TxReadToSend(dbTx *l2db.PoolL2TxRead) *sendPoolTx {
535	tx := &sendPoolTx{
536		TxID:      dbTx.TxID,
537		Type:      dbTx.Type,
538		FromIdx:   idxToHez(dbTx.FromIdx, dbTx.TokenSymbol),
539		Amount:    dbTx.Amount.String(),
540		Fee:       dbTx.Fee,
541		Nonce:     dbTx.Nonce,
542		State:     dbTx.State,
543		Signature: dbTx.Signature,
544		Timestamp: dbTx.Timestamp,
545		BatchNum:  dbTx.BatchNum,
546		RqTokenID: dbTx.RqTokenID,
547		RqFee:     dbTx.RqFee,
548		RqNonce:   dbTx.RqNonce,
549		Token: tokenAPI{
550			TokenID:     dbTx.TokenID,
551			EthBlockNum: dbTx.TokenEthBlockNum,
552			EthAddr:     dbTx.TokenEthAddr,
553			Name:        dbTx.TokenName,
554			Symbol:      dbTx.TokenSymbol,
555			Decimals:    dbTx.TokenDecimals,
556			USD:         dbTx.TokenUSD,
557			USDUpdate:   dbTx.TokenUSDUpdate,
558		},
559	}
560	// ToIdx
561	if dbTx.ToIdx != nil {
562		toIdx := idxToHez(*dbTx.ToIdx, dbTx.TokenSymbol)
563		tx.ToIdx = &toIdx
564	}
565	// ToEthAddr
566	if dbTx.ToEthAddr != nil {
567		toEth := ethAddrToHez(*dbTx.ToEthAddr)
568		tx.ToEthAddr = &toEth
569	}
570	// ToBJJ
571	if dbTx.ToBJJ != nil {
572		toBJJ := bjjToString(dbTx.ToBJJ)
573		tx.ToBJJ = &toBJJ
574	}
575	// RqFromIdx
576	if dbTx.RqFromIdx != nil {
577		rqFromIdx := idxToHez(*dbTx.RqFromIdx, dbTx.TokenSymbol)
578		tx.RqFromIdx = &rqFromIdx
579	}
580	// RqToIdx
581	if dbTx.RqToIdx != nil {
582		rqToIdx := idxToHez(*dbTx.RqToIdx, dbTx.TokenSymbol)
583		tx.RqToIdx = &rqToIdx
584	}
585	// RqToEthAddr
586	if dbTx.RqToEthAddr != nil {
587		rqToEth := ethAddrToHez(*dbTx.RqToEthAddr)
588		tx.RqToEthAddr = &rqToEth
589	}
590	// RqToBJJ
591	if dbTx.RqToBJJ != nil {
592		rqToBJJ := bjjToString(dbTx.RqToBJJ)
593		tx.RqToBJJ = &rqToBJJ
594	}
595	// RqAmount
596	if dbTx.RqAmount != nil {
597		rqAmount := dbTx.RqAmount.String()
598		tx.RqAmount = &rqAmount
599	}
600	return tx
601}
602
603// Coordinators
604
605type coordinatorAPI struct {
606	ItemID      int               `json:"itemId"`
607	Bidder      ethCommon.Address `json:"bidderAddr"`
608	Forger      ethCommon.Address `json:"forgerAddr"`
609	EthBlockNum int64             `json:"ethereumBlock"`
610	URL         string            `json:"URL"`
611}
612
613type coordinatorsAPI struct {
614	Coordinators []coordinatorAPI `json:"coordinators"`
615	Pagination   *db.Pagination   `json:"pagination"`
616}
617
618func (t *coordinatorsAPI) GetPagination() *db.Pagination {
619	if t.Coordinators[0].ItemID < t.Coordinators[len(t.Coordinators)-1].ItemID {
620		t.Pagination.FirstReturnedItem = t.Coordinators[0].ItemID
621		t.Pagination.LastReturnedItem = t.Coordinators[len(t.Coordinators)-1].ItemID
622	} else {
623		t.Pagination.LastReturnedItem = t.Coordinators[0].ItemID
624		t.Pagination.FirstReturnedItem = t.Coordinators[len(t.Coordinators)-1].ItemID
625	}
626	return t.Pagination
627}
628
629func (t *coordinatorsAPI) Len() int { return len(t.Coordinators) }
630
631func coordinatorsToAPI(dbCoordinators []historydb.HistoryCoordinator) []coordinatorAPI {
632	apiCoordinators := []coordinatorAPI{}
633	for i := 0; i < len(dbCoordinators); i++ {
634		apiCoordinators = append(apiCoordinators, coordinatorAPI{
635			ItemID:      dbCoordinators[i].ItemID,
636			Bidder:      dbCoordinators[i].Bidder,
637			Forger:      dbCoordinators[i].Forger,
638			EthBlockNum: dbCoordinators[i].EthBlockNum,
639			URL:         dbCoordinators[i].URL,
640		})
641	}
642	return apiCoordinators
643}