Rework tx link formatting
This commit is contained in:
parent
9c45cb2918
commit
69e992f06e
|
@ -82,13 +82,7 @@ WHERE not exists(SELECT *
|
||||||
}
|
}
|
||||||
client, ok := clients[bt.ChainID]
|
client, ok := clients[bt.ChainID]
|
||||||
if !ok {
|
if !ok {
|
||||||
var chainCfg *config.ChainConfig
|
chainCfg := cfg.GetChainConfig(bt.ChainID)
|
||||||
for _, c := range cfg.Chains {
|
|
||||||
if c.ChainID == bt.ChainID {
|
|
||||||
chainCfg = c
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if chainCfg == nil {
|
if chainCfg == nil {
|
||||||
logger.WithFields(fields).Fatal("can't find chain config")
|
logger.WithFields(fields).Fatal("can't find chain config")
|
||||||
}
|
}
|
||||||
|
|
|
@ -42,6 +42,9 @@
|
||||||
},
|
},
|
||||||
"safe_logs_request": {
|
"safe_logs_request": {
|
||||||
"type": "boolean"
|
"type": "boolean"
|
||||||
|
},
|
||||||
|
"explorer_tx_link_format": {
|
||||||
|
"type": "string"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"required": [
|
"required": [
|
||||||
|
|
15
config.yml
15
config.yml
|
@ -7,6 +7,7 @@ chains:
|
||||||
chain_id: 1
|
chain_id: 1
|
||||||
block_time: 15s
|
block_time: 15s
|
||||||
block_index_interval: 60s
|
block_index_interval: 60s
|
||||||
|
explorer_tx_link_format: 'https://etherscan.io/tx/%s'
|
||||||
bsc:
|
bsc:
|
||||||
rpc:
|
rpc:
|
||||||
host: https://bsc-dataseed2.defibit.io
|
host: https://bsc-dataseed2.defibit.io
|
||||||
|
@ -16,6 +17,7 @@ chains:
|
||||||
block_time: 3s
|
block_time: 3s
|
||||||
block_index_interval: 60s
|
block_index_interval: 60s
|
||||||
safe_logs_request: true
|
safe_logs_request: true
|
||||||
|
explorer_tx_link_format: 'https://bscscan.com/tx/%s'
|
||||||
kovan:
|
kovan:
|
||||||
rpc:
|
rpc:
|
||||||
host: https://kovan.infura.io/v3/${INFURA_PROJECT_KEY}
|
host: https://kovan.infura.io/v3/${INFURA_PROJECT_KEY}
|
||||||
|
@ -24,15 +26,17 @@ chains:
|
||||||
chain_id: 42
|
chain_id: 42
|
||||||
block_time: 5s
|
block_time: 5s
|
||||||
block_index_interval: 60s
|
block_index_interval: 60s
|
||||||
|
explorer_tx_link_format: 'https://kovan.etherscan.io/tx/%s'
|
||||||
xdai:
|
xdai:
|
||||||
rpc:
|
rpc:
|
||||||
host: https://rpc.xdaichain.com/oe-only
|
host: https://rpc.ankr.com/gnosis
|
||||||
timeout: 20s
|
timeout: 20s
|
||||||
rps: 10
|
rps: 10
|
||||||
chain_id: 100
|
chain_id: 100
|
||||||
block_time: 5s
|
block_time: 5s
|
||||||
block_index_interval: 30s
|
block_index_interval: 30s
|
||||||
safe_logs_request: true
|
safe_logs_request: true
|
||||||
|
explorer_tx_link_format: 'https://blockscout.com/xdai/mainnet/tx/%s'
|
||||||
poa:
|
poa:
|
||||||
rpc:
|
rpc:
|
||||||
host: https://core.poanetwork.dev
|
host: https://core.poanetwork.dev
|
||||||
|
@ -41,6 +45,7 @@ chains:
|
||||||
chain_id: 99
|
chain_id: 99
|
||||||
block_time: 5s
|
block_time: 5s
|
||||||
block_index_interval: 30s
|
block_index_interval: 30s
|
||||||
|
explorer_tx_link_format: 'https://blockscout.com/poa/core/tx/%s'
|
||||||
sokol:
|
sokol:
|
||||||
rpc:
|
rpc:
|
||||||
host: https://sokol.poa.network
|
host: https://sokol.poa.network
|
||||||
|
@ -49,6 +54,7 @@ chains:
|
||||||
chain_id: 77
|
chain_id: 77
|
||||||
block_time: 5s
|
block_time: 5s
|
||||||
block_index_interval: 30s
|
block_index_interval: 30s
|
||||||
|
explorer_tx_link_format: 'https://blockscout.com/poa/sokol/tx/%s'
|
||||||
rinkeby:
|
rinkeby:
|
||||||
rpc:
|
rpc:
|
||||||
host: https://rinkeby.infura.io/v3/${INFURA_PROJECT_KEY}
|
host: https://rinkeby.infura.io/v3/${INFURA_PROJECT_KEY}
|
||||||
|
@ -57,6 +63,7 @@ chains:
|
||||||
chain_id: 4
|
chain_id: 4
|
||||||
block_time: 15s
|
block_time: 15s
|
||||||
block_index_interval: 60s
|
block_index_interval: 60s
|
||||||
|
explorer_tx_link_format: 'https://rinkeby.etherscan.io/tx/%s'
|
||||||
bridges:
|
bridges:
|
||||||
xdai:
|
xdai:
|
||||||
bridge_mode: ERC_TO_NATIVE
|
bridge_mode: ERC_TO_NATIVE
|
||||||
|
@ -158,7 +165,7 @@ bridges:
|
||||||
validator_contract_address: 0x6f00218e7D985FE1211f5d47B350708fF915A842
|
validator_contract_address: 0x6f00218e7D985FE1211f5d47B350708fF915A842
|
||||||
start_block: 14496719
|
start_block: 14496719
|
||||||
required_block_confirmations: 12
|
required_block_confirmations: 12
|
||||||
max_block_range_size: 10000
|
max_block_range_size: 2000
|
||||||
foreign:
|
foreign:
|
||||||
chain: bsc
|
chain: bsc
|
||||||
address: 0x05185872898b6f94AA600177EF41B9334B1FA48B
|
address: 0x05185872898b6f94AA600177EF41B9334B1FA48B
|
||||||
|
@ -186,7 +193,7 @@ bridges:
|
||||||
validator_contract_address: 0xAC91dfb485ED2B96381686D3d299e3D041dB4051
|
validator_contract_address: 0xAC91dfb485ED2B96381686D3d299e3D041dB4051
|
||||||
start_block: 10030209
|
start_block: 10030209
|
||||||
required_block_confirmations: 12
|
required_block_confirmations: 12
|
||||||
max_block_range_size: 20000
|
max_block_range_size: 2000
|
||||||
foreign:
|
foreign:
|
||||||
chain: rinkeby
|
chain: rinkeby
|
||||||
address: 0xD4075FB57fCf038bFc702c915Ef9592534bED5c1
|
address: 0xD4075FB57fCf038bFc702c915Ef9592534bED5c1
|
||||||
|
@ -212,7 +219,7 @@ bridges:
|
||||||
validator_contract_address: 0x72C5E5f2C905f9b57FD433a58d6215AC8109991C
|
validator_contract_address: 0x72C5E5f2C905f9b57FD433a58d6215AC8109991C
|
||||||
start_block: 17976494
|
start_block: 17976494
|
||||||
required_block_confirmations: 12
|
required_block_confirmations: 12
|
||||||
max_block_range_size: 10000
|
max_block_range_size: 2000
|
||||||
foreign:
|
foreign:
|
||||||
chain: poa
|
chain: poa
|
||||||
address: 0xB2218bdEbe8e90f80D04286772B0968ead666942
|
address: 0xB2218bdEbe8e90f80D04286772B0968ead666942
|
||||||
|
|
|
@ -1,7 +1,6 @@
|
||||||
package config
|
package config
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"bytes"
|
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"math"
|
"math"
|
||||||
|
@ -10,7 +9,6 @@ import (
|
||||||
|
|
||||||
"github.com/ethereum/go-ethereum/common"
|
"github.com/ethereum/go-ethereum/common"
|
||||||
"github.com/sirupsen/logrus"
|
"github.com/sirupsen/logrus"
|
||||||
"gopkg.in/yaml.v3"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
|
@ -31,6 +29,7 @@ type ChainConfig struct {
|
||||||
BlockTime time.Duration `yaml:"block_time"`
|
BlockTime time.Duration `yaml:"block_time"`
|
||||||
BlockIndexInterval time.Duration `yaml:"block_index_interval"`
|
BlockIndexInterval time.Duration `yaml:"block_index_interval"`
|
||||||
SafeLogsRequest bool `yaml:"safe_logs_request"`
|
SafeLogsRequest bool `yaml:"safe_logs_request"`
|
||||||
|
ExplorerTxLinkFormat string `yaml:"explorer_tx_link_format"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type TokenConfig struct {
|
type TokenConfig struct {
|
||||||
|
@ -94,11 +93,21 @@ type Config struct {
|
||||||
Presenter *PresenterConfig `yaml:"presenter"`
|
Presenter *PresenterConfig `yaml:"presenter"`
|
||||||
}
|
}
|
||||||
|
|
||||||
func parseYaml(out *Config, blob []byte) error {
|
func (cfg *ChainConfig) FormatTxLink(txHash fmt.Stringer) string {
|
||||||
dec := yaml.NewDecoder(bytes.NewReader(blob))
|
if cfg == nil || cfg.ExplorerTxLinkFormat == "" {
|
||||||
dec.KnownFields(true)
|
return txHash.String()
|
||||||
if err := dec.Decode(out); err != nil {
|
}
|
||||||
return fmt.Errorf("can't parse yaml: %w", err)
|
return fmt.Sprintf(cfg.ExplorerTxLinkFormat, txHash)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (cfg *Config) GetChainConfig(chainID string) *ChainConfig {
|
||||||
|
if chainCfg, ok := cfg.Chains[chainID]; ok {
|
||||||
|
return chainCfg
|
||||||
|
}
|
||||||
|
for _, chainCfg := range cfg.Chains {
|
||||||
|
if chainCfg.ChainID == chainID {
|
||||||
|
return chainCfg
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
|
@ -22,6 +22,7 @@ chains:
|
||||||
chain_id: 1
|
chain_id: 1
|
||||||
block_time: 15s
|
block_time: 15s
|
||||||
block_index_interval: 60s
|
block_index_interval: 60s
|
||||||
|
explorer_tx_link_format: 'https://etherscan.io/tx/%s'
|
||||||
xdai:
|
xdai:
|
||||||
rpc:
|
rpc:
|
||||||
host: https://rpc.ankr.com/gnosis
|
host: https://rpc.ankr.com/gnosis
|
||||||
|
@ -109,6 +110,7 @@ func TestReadConfigWithEnv(t *testing.T) {
|
||||||
BlockTime: 15 * time.Second,
|
BlockTime: 15 * time.Second,
|
||||||
BlockIndexInterval: 60 * time.Second,
|
BlockIndexInterval: 60 * time.Second,
|
||||||
SafeLogsRequest: false,
|
SafeLogsRequest: false,
|
||||||
|
ExplorerTxLinkFormat: "https://etherscan.io/tx/%s",
|
||||||
}
|
}
|
||||||
xdaiChainCfg := &config.ChainConfig{
|
xdaiChainCfg := &config.ChainConfig{
|
||||||
RPC: &config.RPCConfig{
|
RPC: &config.RPCConfig{
|
||||||
|
@ -222,18 +224,18 @@ func TestBridgeSideConfig_ErcToNativeTokenAddresses(t *testing.T) {
|
||||||
cfg, err := config.ReadConfig([]byte(testCfg))
|
cfg, err := config.ReadConfig([]byte(testCfg))
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
tokenAddresses := cfg.Bridges["xdai"].Foreign.ErcToNativeTokenAddresses(7000000, 11000000)
|
tokenAddresses := cfg.Bridges["xdai"].Foreign.ErcToNativeTokenAddresses(7000000, 11000000)
|
||||||
require.Equal(t, tokenAddresses, []common.Address{
|
require.Equal(t, []common.Address{
|
||||||
common.HexToAddress("0x89d24A6b4CcB1B6fAA2625fE562bDD9a23260359"),
|
common.HexToAddress("0x89d24A6b4CcB1B6fAA2625fE562bDD9a23260359"),
|
||||||
common.HexToAddress("0x6B175474E89094C44Da98b954EedeAC495271d0F"),
|
common.HexToAddress("0x6B175474E89094C44Da98b954EedeAC495271d0F"),
|
||||||
})
|
}, tokenAddresses)
|
||||||
tokenAddresses = cfg.Bridges["xdai"].Foreign.ErcToNativeTokenAddresses(7000000, 8000000)
|
tokenAddresses = cfg.Bridges["xdai"].Foreign.ErcToNativeTokenAddresses(7000000, 8000000)
|
||||||
require.Equal(t, tokenAddresses, []common.Address{
|
require.Equal(t, []common.Address{
|
||||||
common.HexToAddress("0x89d24A6b4CcB1B6fAA2625fE562bDD9a23260359"),
|
common.HexToAddress("0x89d24A6b4CcB1B6fAA2625fE562bDD9a23260359"),
|
||||||
})
|
}, tokenAddresses)
|
||||||
tokenAddresses = cfg.Bridges["xdai"].Foreign.ErcToNativeTokenAddresses(10000000, 11000000)
|
tokenAddresses = cfg.Bridges["xdai"].Foreign.ErcToNativeTokenAddresses(10000000, 11000000)
|
||||||
require.Equal(t, tokenAddresses, []common.Address{
|
require.Equal(t, []common.Address{
|
||||||
common.HexToAddress("0x6B175474E89094C44Da98b954EedeAC495271d0F"),
|
common.HexToAddress("0x6B175474E89094C44Da98b954EedeAC495271d0F"),
|
||||||
})
|
}, tokenAddresses)
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestBridgeSideConfig_ContractAddresses(t *testing.T) {
|
func TestBridgeSideConfig_ContractAddresses(t *testing.T) {
|
||||||
|
@ -241,10 +243,34 @@ func TestBridgeSideConfig_ContractAddresses(t *testing.T) {
|
||||||
cfg, err := config.ReadConfig([]byte(testCfg))
|
cfg, err := config.ReadConfig([]byte(testCfg))
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
tokenAddresses := cfg.Bridges["xdai"].Foreign.ContractAddresses(7000000, 11000000)
|
tokenAddresses := cfg.Bridges["xdai"].Foreign.ContractAddresses(7000000, 11000000)
|
||||||
require.Equal(t, tokenAddresses, []common.Address{
|
require.Equal(t, []common.Address{
|
||||||
common.HexToAddress("0x89d24A6b4CcB1B6fAA2625fE562bDD9a23260359"),
|
common.HexToAddress("0x89d24A6b4CcB1B6fAA2625fE562bDD9a23260359"),
|
||||||
common.HexToAddress("0x6B175474E89094C44Da98b954EedeAC495271d0F"),
|
common.HexToAddress("0x6B175474E89094C44Da98b954EedeAC495271d0F"),
|
||||||
common.HexToAddress("0x4aa42145Aa6Ebf72e164C9bBC74fbD3788045016"),
|
common.HexToAddress("0x4aa42145Aa6Ebf72e164C9bBC74fbD3788045016"),
|
||||||
common.HexToAddress("0xe1579dEbdD2DF16Ebdb9db8694391fa74EeA201E"),
|
common.HexToAddress("0xe1579dEbdD2DF16Ebdb9db8694391fa74EeA201E"),
|
||||||
})
|
}, tokenAddresses)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestConfig_GetChainConfig(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
cfg, err := config.ReadConfig([]byte(testCfg))
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
require.NotNil(t, cfg.GetChainConfig("xdai"))
|
||||||
|
require.Equal(t, "100", cfg.GetChainConfig("xdai").ChainID)
|
||||||
|
require.NotNil(t, cfg.GetChainConfig("100"))
|
||||||
|
require.Equal(t, "100", cfg.GetChainConfig("100").ChainID)
|
||||||
|
require.Nil(t, cfg.GetChainConfig("123"))
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestChainConfig_FormatTxLink(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
cfg, err := config.ReadConfig([]byte(testCfg))
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
txHash := "0x0000000000000000000000000000000000000000000000000000000000000000"
|
||||||
|
link := "https://etherscan.io/tx/0x0000000000000000000000000000000000000000000000000000000000000000"
|
||||||
|
require.Equal(t, link, cfg.GetChainConfig("mainnet").FormatTxLink(common.Hash{}))
|
||||||
|
require.Equal(t, link, cfg.GetChainConfig("1").FormatTxLink(common.Hash{}))
|
||||||
|
require.Equal(t, txHash, cfg.GetChainConfig("123").FormatTxLink(common.Hash{}))
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,17 @@
|
||||||
|
package config
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
"gopkg.in/yaml.v3"
|
||||||
|
)
|
||||||
|
|
||||||
|
func parseYaml(out interface{}, blob []byte) error {
|
||||||
|
dec := yaml.NewDecoder(bytes.NewReader(blob))
|
||||||
|
dec.KnownFields(true)
|
||||||
|
if err := dec.Decode(out); err != nil {
|
||||||
|
return fmt.Errorf("can't parse yaml: %w", err)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
|
@ -71,13 +71,7 @@ func GetChainConfigMiddleware(cfg *config.Config) func(http.Handler) http.Handle
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
var chainCfg *config.ChainConfig
|
chainCfg := cfg.GetChainConfig(chainID)
|
||||||
for _, c := range cfg.Chains {
|
|
||||||
if c.ChainID == chainID {
|
|
||||||
chainCfg = c
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if chainCfg == nil {
|
if chainCfg == nil {
|
||||||
render.JSON(w, r, http.StatusNotFound, fmt.Sprintf("chain with id %s not found", chainID))
|
render.JSON(w, r, http.StatusNotFound, fmt.Sprintf("chain with id %s not found", chainID))
|
||||||
return
|
return
|
||||||
|
|
|
@ -529,6 +529,6 @@ func (p *Presenter) getTxInfo(ctx context.Context, logID uint) (*TxInfo, error)
|
||||||
return &TxInfo{
|
return &TxInfo{
|
||||||
BlockNumber: log.BlockNumber,
|
BlockNumber: log.BlockNumber,
|
||||||
Timestamp: bt.Timestamp,
|
Timestamp: bt.Timestamp,
|
||||||
Link: FormatLogTxLinkURL(log),
|
Link: p.cfg.GetChainConfig(log.ChainID).FormatTxLink(log.TransactionHash),
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
|
@ -101,3 +101,36 @@ type TxInfo struct {
|
||||||
Timestamp time.Time
|
Timestamp time.Time
|
||||||
Link string
|
Link string
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func NewMessageInfo(msg *entity.Message) *MessageInfo {
|
||||||
|
return &MessageInfo{
|
||||||
|
BridgeID: msg.BridgeID,
|
||||||
|
MsgHash: msg.MsgHash,
|
||||||
|
MessageID: msg.MessageID,
|
||||||
|
Direction: msg.Direction,
|
||||||
|
Sender: msg.Sender,
|
||||||
|
Executor: msg.Executor,
|
||||||
|
DataType: msg.DataType,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewInformationRequestInfo(req *entity.InformationRequest) *InformationRequestInfo {
|
||||||
|
return &InformationRequestInfo{
|
||||||
|
BridgeID: req.BridgeID,
|
||||||
|
MessageID: req.MessageID,
|
||||||
|
Direction: req.Direction,
|
||||||
|
Sender: req.Sender,
|
||||||
|
Executor: req.Executor,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewErcToNativeMessageInfo(req *entity.ErcToNativeMessage) *ErcToNativeMessageInfo {
|
||||||
|
return &ErcToNativeMessageInfo{
|
||||||
|
BridgeID: req.BridgeID,
|
||||||
|
MsgHash: req.MsgHash,
|
||||||
|
Direction: req.Direction,
|
||||||
|
Sender: req.Sender,
|
||||||
|
Receiver: req.Receiver,
|
||||||
|
Value: req.Value,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -1,57 +0,0 @@
|
||||||
package presenter
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
|
|
||||||
"github.com/poanetwork/tokenbridge-monitor/entity"
|
|
||||||
)
|
|
||||||
|
|
||||||
var formats = map[string]string{
|
|
||||||
"1": "https://etherscan.io/tx/%s",
|
|
||||||
"4": "https://rinkeby.etherscan.io/tx/%s",
|
|
||||||
"42": "https://kovan.etherscan.io/tx/%s",
|
|
||||||
"56": "https://bscscan.com/tx/%s",
|
|
||||||
"77": "https://blockscout.com/poa/sokol/tx/%s",
|
|
||||||
"99": "https://blockscout.com/poa/core/tx/%s",
|
|
||||||
"100": "https://blockscout.com/xdai/mainnet/tx/%s",
|
|
||||||
}
|
|
||||||
|
|
||||||
func FormatLogTxLinkURL(log *entity.Log) string {
|
|
||||||
if format, ok := formats[log.ChainID]; ok {
|
|
||||||
return fmt.Sprintf(format, log.TransactionHash)
|
|
||||||
}
|
|
||||||
return log.TransactionHash.String()
|
|
||||||
}
|
|
||||||
|
|
||||||
func NewMessageInfo(msg *entity.Message) *MessageInfo {
|
|
||||||
return &MessageInfo{
|
|
||||||
BridgeID: msg.BridgeID,
|
|
||||||
MsgHash: msg.MsgHash,
|
|
||||||
MessageID: msg.MessageID,
|
|
||||||
Direction: msg.Direction,
|
|
||||||
Sender: msg.Sender,
|
|
||||||
Executor: msg.Executor,
|
|
||||||
DataType: msg.DataType,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func NewInformationRequestInfo(req *entity.InformationRequest) *InformationRequestInfo {
|
|
||||||
return &InformationRequestInfo{
|
|
||||||
BridgeID: req.BridgeID,
|
|
||||||
MessageID: req.MessageID,
|
|
||||||
Direction: req.Direction,
|
|
||||||
Sender: req.Sender,
|
|
||||||
Executor: req.Executor,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func NewErcToNativeMessageInfo(req *entity.ErcToNativeMessage) *ErcToNativeMessageInfo {
|
|
||||||
return &ErcToNativeMessageInfo{
|
|
||||||
BridgeID: req.BridgeID,
|
|
||||||
MsgHash: req.MsgHash,
|
|
||||||
Direction: req.Direction,
|
|
||||||
Sender: req.Sender,
|
|
||||||
Receiver: req.Receiver,
|
|
||||||
Value: req.Value,
|
|
||||||
}
|
|
||||||
}
|
|
Loading…
Reference in New Issue