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
214
215type tokensAPI struct {
216	Tokens     []tokenAPI     `json:"tokens"`
217	Pagination *db.Pagination `json:"pagination"`
218}
219
220func (t *tokensAPI) GetPagination() *db.Pagination {
221	if t.Tokens[0].ItemID < t.Tokens[len(t.Tokens)-1].ItemID {
222		t.Pagination.FirstReturnedItem = t.Tokens[0].ItemID
223		t.Pagination.LastReturnedItem = t.Tokens[len(t.Tokens)-1].ItemID
224	} else {
225		t.Pagination.LastReturnedItem = t.Tokens[0].ItemID
226		t.Pagination.FirstReturnedItem = t.Tokens[len(t.Tokens)-1].ItemID
227	}
228	return t.Pagination
229}
230func (t *tokensAPI) Len() int { return len(t.Tokens) }
231
232type tokenAPI struct {
233	ItemID      int               `json:"itemId"`
234	TokenID     common.TokenID    `json:"id"`
235	EthBlockNum int64             `json:"ethereumBlockNum"` // Ethereum block number in which this token was registered
236	EthAddr     ethCommon.Address `json:"ethereumAddress"`
237	Name        string            `json:"name"`
238	Symbol      string            `json:"symbol"`
239	Decimals    uint64            `json:"decimals"`
240	USD         *float64          `json:"USD"`
241	USDUpdate   *time.Time        `json:"fiatUpdate"`
242}
243
244func tokensToAPI(dbTokens []historydb.TokenRead) []tokenAPI {
245	apiTokens := []tokenAPI{}
246	for i := 0; i < len(dbTokens); i++ {
247		apiTokens = append(apiTokens, tokenAPI{
248			ItemID:      dbTokens[i].ItemID,
249			TokenID:     dbTokens[i].TokenID,
250			EthBlockNum: dbTokens[i].EthBlockNum,
251			EthAddr:     dbTokens[i].EthAddr,
252			Name:        dbTokens[i].Name,
253			Symbol:      dbTokens[i].Symbol,
254			Decimals:    dbTokens[i].Decimals,
255			USD:         dbTokens[i].USD,
256			USDUpdate:   dbTokens[i].USDUpdate,
257		})
258	}
259	return apiTokens
260}
261
262// Config
263
264type rollupConstants struct {
265	PublicConstants         eth.RollupPublicConstants `json:"publicConstants"`
266	MaxFeeIdxCoordinator    int                       `json:"maxFeeIdxCoordinator"`
267	ReservedIdx             int                       `json:"reservedIdx"`
268	ExitIdx                 int                       `json:"exitIdx"`
269	LimitLoadAmount         *big.Int                  `json:"limitLoadAmount"`
270	LimitL2TransferAmount   *big.Int                  `json:"limitL2TransferAmount"`
271	LimitTokens             int                       `json:"limitTokens"`
272	L1CoordinatorTotalBytes int                       `json:"l1CoordinatorTotalBytes"`
273	L1UserTotalBytes        int                       `json:"l1UserTotalBytes"`
274	MaxL1UserTx             int                       `json:"maxL1UserTx"`
275	MaxL1Tx                 int                       `json:"maxL1Tx"`
276	InputSHAConstantBytes   int                       `json:"inputSHAConstantBytes"`
277	NumBuckets              int                       `json:"numBuckets"`
278	MaxWithdrawalDelay      int                       `json:"maxWithdrawalDelay"`
279	ExchangeMultiplier      int                       `json:"exchangeMultiplier"`
280}
281
282type configAPI struct {
283	RollupConstants   rollupConstants       `json:"hermez"`
284	AuctionConstants  eth.AuctionConstants  `json:"auction"`
285	WDelayerConstants eth.WDelayerConstants `json:"withdrawalDelayer"`
286}
287
288// PoolL2Tx
289
290type receivedPoolTx struct {
291	TxID        common.TxID           `json:"id" binding:"required"`
292	Type        common.TxType         `json:"type" binding:"required"`
293	TokenID     common.TokenID        `json:"tokenId"`
294	FromIdx     string                `json:"fromAccountIndex" binding:"required"`
295	ToIdx       *string               `json:"toAccountIndex"`
296	ToEthAddr   *string               `json:"toHezEthereumAddress"`
297	ToBJJ       *string               `json:"toBjj"`
298	Amount      string                `json:"amount" binding:"required"`
299	Fee         common.FeeSelector    `json:"fee"`
300	Nonce       common.Nonce          `json:"nonce"`
301	Signature   babyjub.SignatureComp `json:"signature" binding:"required"`
302	RqFromIdx   *string               `json:"requestFromAccountIndex"`
303	RqToIdx     *string               `json:"requestToAccountIndex"`
304	RqToEthAddr *string               `json:"requestToHezEthereumAddress"`
305	RqToBJJ     *string               `json:"requestToBjj"`
306	RqTokenID   *common.TokenID       `json:"requestTokenId"`
307	RqAmount    *string               `json:"requestAmount"`
308	RqFee       *common.FeeSelector   `json:"requestFee"`
309	RqNonce     *common.Nonce         `json:"requestNonce"`
310}
311
312func (tx *receivedPoolTx) toDBWritePoolL2Tx() (*l2db.PoolL2TxWrite, error) {
313	amount := new(big.Int)
314	amount.SetString(tx.Amount, 10)
315	txw := &l2db.PoolL2TxWrite{
316		TxID:      tx.TxID,
317		TokenID:   tx.TokenID,
318		Amount:    amount,
319		Fee:       tx.Fee,
320		Nonce:     tx.Nonce,
321		State:     common.PoolL2TxStatePending,
322		Signature: tx.Signature,
323		RqTokenID: tx.RqTokenID,
324		RqFee:     tx.RqFee,
325		RqNonce:   tx.RqNonce,
326		Type:      tx.Type,
327	}
328	// Check FromIdx (required)
329	fidx, err := stringToIdx(tx.FromIdx, "fromAccountIndex")
330	if err != nil {
331		return nil, err
332	}
333	if fidx == nil {
334		return nil, errors.New("invalid fromAccountIndex")
335	}
336	// Set FromIdx
337	txw.FromIdx = common.Idx(*fidx)
338	// Set AmountFloat
339	f := new(big.Float).SetInt(amount)
340	amountF, _ := f.Float64()
341	txw.AmountFloat = amountF
342	if amountF < 0 {
343		return nil, errors.New("amount must be positive")
344	}
345	// Check "to" fields, only one of: ToIdx, ToEthAddr, ToBJJ
346	if tx.ToIdx != nil { // Case: Tx with ToIdx setted
347		// Set ToIdx
348		tidxUint, err := stringToIdx(*tx.ToIdx, "toAccountIndex")
349		if err != nil || tidxUint == nil {
350			return nil, errors.New("invalid toAccountIndex")
351		}
352		tidx := common.Idx(*tidxUint)
353		txw.ToIdx = &tidx
354	} else if tx.ToBJJ != nil { // Case: Tx with ToBJJ setted
355		// tx.ToEthAddr must be equal to ethAddrWhenBJJLower or ethAddrWhenBJJUpper
356		if tx.ToEthAddr != nil {
357			toEthAddr, err := hezStringToEthAddr(*tx.ToEthAddr, "toHezEthereumAddress")
358			if err != nil || *toEthAddr != common.FFAddr {
359				return nil, fmt.Errorf("if toBjj is setted, toHezEthereumAddress must be hez:%s", common.FFAddr.Hex())
360			}
361		} else {
362			return nil, fmt.Errorf("if toBjj is setted, toHezEthereumAddress must be hez:%s and toAccountIndex must be null", common.FFAddr.Hex())
363		}
364		// Set ToEthAddr and ToBJJ
365		toBJJ, err := hezStringToBJJ(*tx.ToBJJ, "toBjj")
366		if err != nil || toBJJ == nil {
367			return nil, errors.New("invalid toBjj")
368		}
369		txw.ToBJJ = toBJJ
370		txw.ToEthAddr = &common.FFAddr
371	} else if tx.ToEthAddr != nil { // Case: Tx with ToEthAddr setted
372		// Set ToEthAddr
373		toEthAddr, err := hezStringToEthAddr(*tx.ToEthAddr, "toHezEthereumAddress")
374		if err != nil || toEthAddr == nil {
375			return nil, errors.New("invalid toHezEthereumAddress")
376		}
377		txw.ToEthAddr = toEthAddr
378	} else {
379		return nil, errors.New("one of toAccountIndex, toHezEthereumAddress or toBjj must be setted")
380	}
381	// Check "rq" fields
382	if tx.RqFromIdx != nil {
383		// check and set RqFromIdx
384		rqfidxUint, err := stringToIdx(tx.FromIdx, "requestFromAccountIndex")
385		if err != nil || rqfidxUint == nil {
386			return nil, errors.New("invalid requestFromAccountIndex")
387		}
388		// Set RqFromIdx
389		rqfidx := common.Idx(*rqfidxUint)
390		txw.RqFromIdx = &rqfidx
391		// Case: RqTx with RqToIdx setted
392		if tx.RqToIdx != nil {
393			// Set ToIdx
394			tidxUint, err := stringToIdx(*tx.RqToIdx, "requestToAccountIndex")
395			if err != nil || tidxUint == nil {
396				return nil, errors.New("invalid requestToAccountIndex")
397			}
398			tidx := common.Idx(*tidxUint)
399			txw.ToIdx = &tidx
400		} else if tx.RqToBJJ != nil { // Case: Tx with ToBJJ setted
401			// tx.ToEthAddr must be equal to ethAddrWhenBJJLower or ethAddrWhenBJJUpper
402			if tx.RqToEthAddr != nil {
403				rqEthAddr, err := hezStringToEthAddr(*tx.RqToEthAddr, "")
404				if err != nil || *rqEthAddr != common.FFAddr {
405					return nil, fmt.Errorf("if requestToBjj is setted, requestToHezEthereumAddress must be hez:%s", common.FFAddr.Hex())
406				}
407			} else {
408				return nil, fmt.Errorf("if requestToBjj is setted, toHezEthereumAddress must be hez:%s and requestToAccountIndex must be null", common.FFAddr.Hex())
409			}
410			// Set ToEthAddr and ToBJJ
411			rqToBJJ, err := hezStringToBJJ(*tx.RqToBJJ, "requestToBjj")
412			if err != nil || rqToBJJ == nil {
413				return nil, errors.New("invalid requestToBjj")
414			}
415			txw.RqToBJJ = rqToBJJ
416			txw.RqToEthAddr = &common.FFAddr
417		} else if tx.RqToEthAddr != nil { // Case: Tx with ToEthAddr setted
418			// Set ToEthAddr
419			rqToEthAddr, err := hezStringToEthAddr(*tx.ToEthAddr, "requestToHezEthereumAddress")
420			if err != nil || rqToEthAddr == nil {
421				return nil, errors.New("invalid requestToHezEthereumAddress")
422			}
423			txw.RqToEthAddr = rqToEthAddr
424		} else {
425			return nil, errors.New("one of requestToAccountIndex, requestToHezEthereumAddress or requestToBjj must be setted")
426		}
427		if tx.RqAmount == nil {
428			return nil, errors.New("requestAmount must be provided if other request fields are setted")
429		}
430		rqAmount := new(big.Int)
431		rqAmount.SetString(*tx.RqAmount, 10)
432		txw.RqAmount = rqAmount
433	} else if tx.RqToIdx != nil && tx.RqToEthAddr != nil && tx.RqToBJJ != nil &&
434		tx.RqTokenID != nil && tx.RqAmount != nil && tx.RqNonce != nil && tx.RqFee != nil {
435		// if tx.RqToIdx is not setted, tx.Rq* must be null as well
436		return nil, errors.New("if requestFromAccountIndex is setted, the rest of request fields must be null as well")
437	}
438
439	return txw, validatePoolL2TxWrite(txw)
440}
441
442func validatePoolL2TxWrite(txw *l2db.PoolL2TxWrite) error {
443	poolTx := common.PoolL2Tx{
444		TxID:      txw.TxID,
445		FromIdx:   txw.FromIdx,
446		ToBJJ:     txw.ToBJJ,
447		TokenID:   txw.TokenID,
448		Amount:    txw.Amount,
449		Fee:       txw.Fee,
450		Nonce:     txw.Nonce,
451		State:     txw.State,
452		Signature: txw.Signature,
453		RqToBJJ:   txw.RqToBJJ,
454		RqAmount:  txw.RqAmount,
455		Type:      txw.Type,
456	}
457	// ToIdx
458	if txw.ToIdx != nil {
459		poolTx.ToIdx = *txw.ToIdx
460	}
461	// ToEthAddr
462	if txw.ToEthAddr == nil {
463		poolTx.ToEthAddr = common.EmptyAddr
464	} else {
465		poolTx.ToEthAddr = *txw.ToEthAddr
466	}
467	// RqFromIdx
468	if txw.RqFromIdx != nil {
469		poolTx.RqFromIdx = *txw.RqFromIdx
470	}
471	// RqToIdx
472	if txw.RqToIdx != nil {
473		poolTx.RqToIdx = *txw.RqToIdx
474	}
475	// RqToEthAddr
476	if txw.RqToEthAddr == nil {
477		poolTx.RqToEthAddr = common.EmptyAddr
478	} else {
479		poolTx.RqToEthAddr = *txw.RqToEthAddr
480	}
481	// RqTokenID
482	if txw.RqTokenID != nil {
483		poolTx.RqTokenID = *txw.RqTokenID
484	}
485	// RqFee
486	if txw.RqFee != nil {
487		poolTx.RqFee = *txw.RqFee
488	}
489	// RqNonce
490	if txw.RqNonce != nil {
491		poolTx.RqNonce = *txw.RqNonce
492	}
493	// Check type and id
494	_, err := common.NewPoolL2Tx(&poolTx)
495	if err != nil {
496		return err
497	}
498	// Check signature
499	// Get public key
500	account, err := s.GetAccount(poolTx.FromIdx)
501	if err != nil {
502		return err
503	}
504	if !poolTx.VerifySignature(account.PublicKey) {
505		return errors.New("wrong signature")
506	}
507	return nil
508}
509
510type sendPoolTx struct {
511	TxID        common.TxID           `json:"id"`
512	Type        common.TxType         `json:"type"`
513	FromIdx     string                `json:"fromAccountIndex"`
514	ToIdx       *string               `json:"toAccountIndex"`
515	ToEthAddr   *string               `json:"toHezEthereumAddress"`
516	ToBJJ       *string               `json:"toBjj"`
517	Amount      string                `json:"amount"`
518	Fee         common.FeeSelector    `json:"fee"`
519	Nonce       common.Nonce          `json:"nonce"`
520	State       common.PoolL2TxState  `json:"state"`
521	Signature   babyjub.SignatureComp `json:"signature"`
522	Timestamp   time.Time             `json:"timestamp"`
523	BatchNum    *common.BatchNum      `json:"batchNum"`
524	RqFromIdx   *string               `json:"requestFromAccountIndex"`
525	RqToIdx     *string               `json:"requestToAccountIndex"`
526	RqToEthAddr *string               `json:"requestToHezEthereumAddress"`
527	RqToBJJ     *string               `json:"requestToBJJ"`
528	RqTokenID   *common.TokenID       `json:"requestTokenId"`
529	RqAmount    *string               `json:"requestAmount"`
530	RqFee       *common.FeeSelector   `json:"requestFee"`
531	RqNonce     *common.Nonce         `json:"requestNonce"`
532	Token       tokenAPI              `json:"token"`
533}
534
535func poolL2TxReadToSend(dbTx *l2db.PoolL2TxRead) *sendPoolTx {
536	tx := &sendPoolTx{
537		TxID:      dbTx.TxID,
538		Type:      dbTx.Type,
539		FromIdx:   idxToHez(dbTx.FromIdx, dbTx.TokenSymbol),
540		Amount:    dbTx.Amount.String(),
541		Fee:       dbTx.Fee,
542		Nonce:     dbTx.Nonce,
543		State:     dbTx.State,
544		Signature: dbTx.Signature,
545		Timestamp: dbTx.Timestamp,
546		BatchNum:  dbTx.BatchNum,
547		RqTokenID: dbTx.RqTokenID,
548		RqFee:     dbTx.RqFee,
549		RqNonce:   dbTx.RqNonce,
550		Token: tokenAPI{
551			TokenID:     dbTx.TokenID,
552			EthBlockNum: dbTx.TokenEthBlockNum,
553			EthAddr:     dbTx.TokenEthAddr,
554			Name:        dbTx.TokenName,
555			Symbol:      dbTx.TokenSymbol,
556			Decimals:    dbTx.TokenDecimals,
557			USD:         dbTx.TokenUSD,
558			USDUpdate:   dbTx.TokenUSDUpdate,
559		},
560	}
561	// ToIdx
562	if dbTx.ToIdx != nil {
563		toIdx := idxToHez(*dbTx.ToIdx, dbTx.TokenSymbol)
564		tx.ToIdx = &toIdx
565	}
566	// ToEthAddr
567	if dbTx.ToEthAddr != nil {
568		toEth := ethAddrToHez(*dbTx.ToEthAddr)
569		tx.ToEthAddr = &toEth
570	}
571	// ToBJJ
572	if dbTx.ToBJJ != nil {
573		toBJJ := bjjToString(dbTx.ToBJJ)
574		tx.ToBJJ = &toBJJ
575	}
576	// RqFromIdx
577	if dbTx.RqFromIdx != nil {
578		rqFromIdx := idxToHez(*dbTx.RqFromIdx, dbTx.TokenSymbol)
579		tx.RqFromIdx = &rqFromIdx
580	}
581	// RqToIdx
582	if dbTx.RqToIdx != nil {
583		rqToIdx := idxToHez(*dbTx.RqToIdx, dbTx.TokenSymbol)
584		tx.RqToIdx = &rqToIdx
585	}
586	// RqToEthAddr
587	if dbTx.RqToEthAddr != nil {
588		rqToEth := ethAddrToHez(*dbTx.RqToEthAddr)
589		tx.RqToEthAddr = &rqToEth
590	}
591	// RqToBJJ
592	if dbTx.RqToBJJ != nil {
593		rqToBJJ := bjjToString(dbTx.RqToBJJ)
594		tx.RqToBJJ = &rqToBJJ
595	}
596	// RqAmount
597	if dbTx.RqAmount != nil {
598		rqAmount := dbTx.RqAmount.String()
599		tx.RqAmount = &rqAmount
600	}
601	return tx
602}