feat: hop integration

This commit is contained in:
Anthony Laibe 2022-09-13 09:10:59 +02:00 committed by Anthony Laibe
parent 60cb15739d
commit b11643e66d
30 changed files with 8493 additions and 184 deletions

View File

@ -8,6 +8,10 @@ import (
"github.com/ethereum/go-ethereum/ethclient"
"github.com/status-im/status-go/contracts/directory"
"github.com/status-im/status-go/contracts/ethscan"
"github.com/status-im/status-go/contracts/hop"
hopBridge "github.com/status-im/status-go/contracts/hop/bridge"
hopSwap "github.com/status-im/status-go/contracts/hop/swap"
hopWrapper "github.com/status-im/status-go/contracts/hop/wrapper"
"github.com/status-im/status-go/contracts/registrar"
"github.com/status-im/status-go/contracts/resolver"
"github.com/status-im/status-go/contracts/snt"
@ -164,7 +168,6 @@ func (c *ContractMaker) NewEthScan(chainID uint64) (*ethscan.BalanceScanner, err
if err != nil {
return nil, err
}
bytecode, err := backend.CodeAt(context.Background(), contractAddr, nil)
if err != nil {
return nil, err
@ -179,3 +182,51 @@ func (c *ContractMaker) NewEthScan(chainID uint64) (*ethscan.BalanceScanner, err
backend,
)
}
func (c *ContractMaker) NewHopL2SaddlSwap(chainID uint64, symbol string) (*hopSwap.HopSwap, error) {
contractAddr, err := hop.L2SaddleSwapContractAddress(chainID, symbol)
if err != nil {
return nil, err
}
backend, err := c.RPCClient.EthClient(chainID)
if err != nil {
return nil, err
}
return hopSwap.NewHopSwap(
contractAddr,
backend,
)
}
func (c *ContractMaker) NewHopL1Bridge(chainID uint64, symbol string) (*hopBridge.HopBridge, error) {
contractAddr, err := hop.L1BridgeContractAddress(chainID, symbol)
if err != nil {
return nil, err
}
backend, err := c.RPCClient.EthClient(chainID)
if err != nil {
return nil, err
}
return hopBridge.NewHopBridge(
contractAddr,
backend,
)
}
func (c *ContractMaker) NewHopL2AmmWrapper(chainID uint64, symbol string) (*hopWrapper.HopWrapper, error) {
contractAddr, err := hop.L2AmmWrapperContractAddress(chainID, symbol)
if err != nil {
return nil, err
}
backend, err := c.RPCClient.EthClient(chainID)
if err != nil {
return nil, err
}
return hopWrapper.NewHopWrapper(
contractAddr,
backend,
)
}

115
contracts/hop/address.go Normal file
View File

@ -0,0 +1,115 @@
package hop
import (
"errors"
"github.com/ethereum/go-ethereum/common"
)
var errorNotAvailableOnChainID = errors.New("not available for chainID")
var l2SaddleSwapContractAddresses = map[uint64]map[string]common.Address{
10: {
"USDC": common.HexToAddress("0x3c0FFAca566fCcfD9Cc95139FEF6CBA143795963"),
"USDT": common.HexToAddress("0xeC4B41Af04cF917b54AEb6Df58c0f8D78895b5Ef"),
"DAI": common.HexToAddress("0xF181eD90D6CfaC84B8073FdEA6D34Aa744B41810"),
"ETH": common.HexToAddress("0xaa30D6bba6285d0585722e2440Ff89E23EF68864"),
"WBTC": common.HexToAddress("0x46fc3Af3A47792cA3ED06fdF3D657145A675a8D8"),
},
42161: {
"USDC": common.HexToAddress("0x10541b07d8Ad2647Dc6cD67abd4c03575dade261"),
"USDT": common.HexToAddress("0x18f7402B673Ba6Fb5EA4B95768aABb8aaD7ef18a"),
"DAI": common.HexToAddress("0xa5A33aB9063395A90CCbEa2D86a62EcCf27B5742"),
"ETH": common.HexToAddress("0x652d27c0F72771Ce5C76fd400edD61B406Ac6D97"),
"WBTC": common.HexToAddress("0x7191061D5d4C60f598214cC6913502184BAddf18"),
},
420: {
"USDC": common.HexToAddress("0xE4757dD81AFbecF61E51824AB9238df6691c3D0e"),
"ETH": common.HexToAddress("0xa50395bdEaca7062255109fedE012eFE63d6D402"),
},
421613: {
"USDC": common.HexToAddress("0x83f6244Bd87662118d96D9a6D44f09dffF14b30E"),
"ETH": common.HexToAddress("0x69a71b7F6Ff088a0310b4f911b4f9eA11e2E9740"),
},
}
var l2AmmWrapperContractAddress = map[uint64]map[string]common.Address{
10: {
"USDC": common.HexToAddress("0x2ad09850b0CA4c7c1B33f5AcD6cBAbCaB5d6e796"),
"USDT": common.HexToAddress("0x7D269D3E0d61A05a0bA976b7DBF8805bF844AF3F"),
"DAI": common.HexToAddress("0xb3C68a491608952Cb1257FC9909a537a0173b63B"),
"ETH": common.HexToAddress("0x86cA30bEF97fB651b8d866D45503684b90cb3312"),
"WBTC": common.HexToAddress("0x2A11a98e2fCF4674F30934B5166645fE6CA35F56"),
},
42161: {
"USDC": common.HexToAddress("0xe22D2beDb3Eca35E6397e0C6D62857094aA26F52"),
"USDT": common.HexToAddress("0xCB0a4177E0A60247C0ad18Be87f8eDfF6DD30283"),
"DAI": common.HexToAddress("0xe7F40BF16AB09f4a6906Ac2CAA4094aD2dA48Cc2"),
"ETH": common.HexToAddress("0x33ceb27b39d2Bb7D2e61F7564d3Df29344020417"),
"WBTC": common.HexToAddress("0xC08055b634D43F2176d721E26A3428D3b7E7DdB5"),
},
420: {
"USDC": common.HexToAddress("0xfF21e82a4Bc305BCE591530A68628192b5b6B6FD"),
"ETH": common.HexToAddress("0xC1985d7a3429cDC85E59E2E4Fcc805b857e6Ee2E"),
},
421613: {
"USDC": common.HexToAddress("0x32219766597DFbb10297127238D921E7CCF5D920"),
"ETH": common.HexToAddress("0xa832293f2DCe2f092182F17dd873ae06AD5fDbaF"),
},
}
var l1BridgeContractAddress = map[uint64]map[string]common.Address{
1: {
"USDC": common.HexToAddress("0x3666f603Cc164936C1b87e207F36BEBa4AC5f18a"),
"USDT": common.HexToAddress("0x3666f603Cc164936C1b87e207F36BEBa4AC5f18a"),
"DAI": common.HexToAddress("0x3d4Cc8A61c7528Fd86C55cfe061a78dCBA48EDd1"),
"ETH": common.HexToAddress("0xb8901acB165ed027E32754E0FFe830802919727f"),
"WBTC": common.HexToAddress("0xb98454270065A31D71Bf635F6F7Ee6A518dFb849"),
},
5: {
"USDC": common.HexToAddress("0x7D269D3E0d61A05a0bA976b7DBF8805bF844AF3F"),
"ETH": common.HexToAddress("0xC8A4FB931e8D77df8497790381CA7d228E68a41b"),
},
}
func L2SaddleSwapContractAddress(chainID uint64, symbol string) (common.Address, error) {
tokens, exists := l2SaddleSwapContractAddresses[chainID]
if !exists {
return *new(common.Address), errorNotAvailableOnChainID
}
addr, exists := tokens[symbol]
if !exists {
return *new(common.Address), errorNotAvailableOnChainID
}
return addr, nil
}
func L2AmmWrapperContractAddress(chainID uint64, symbol string) (common.Address, error) {
tokens, exists := l2AmmWrapperContractAddress[chainID]
if !exists {
return *new(common.Address), errorNotAvailableOnChainID
}
addr, exists := tokens[symbol]
if !exists {
return *new(common.Address), errorNotAvailableOnChainID
}
return addr, nil
}
func L1BridgeContractAddress(chainID uint64, symbol string) (common.Address, error) {
tokens, exists := l1BridgeContractAddress[chainID]
if !exists {
return *new(common.Address), errorNotAvailableOnChainID
}
addr, exists := tokens[symbol]
if !exists {
return *new(common.Address), errorNotAvailableOnChainID
}
return addr, nil
}

File diff suppressed because one or more lines are too long

5
contracts/hop/doc.go Normal file
View File

@ -0,0 +1,5 @@
package hop
//go:generate abigen --abi l2SaddleSwap.abi --pkg hopSwap --out swap/l2SaddleSwap.go
//go:generate abigen --abi l1Bridge.abi --pkg hopBridge --out bridge/l1Bridge.go
//go:generate abigen --abi l2AmmWrapper.abi --pkg hopWrapper --out wrapper/l2AmmWrapper.go

File diff suppressed because one or more lines are too long

View File

@ -0,0 +1 @@
[{"inputs":[{"internalType":"contract L2_Bridge","name":"_bridge","type":"address"},{"internalType":"contract IERC20","name":"_l2CanonicalToken","type":"address"},{"internalType":"bool","name":"_l2CanonicalTokenIsEth","type":"bool"},{"internalType":"contract IERC20","name":"_hToken","type":"address"},{"internalType":"contract Swap","name":"_exchangeAddress","type":"address"}],"stateMutability":"nonpayable","type":"constructor"},{"inputs":[{"internalType":"address","name":"recipient","type":"address"},{"internalType":"uint256","name":"amount","type":"uint256"},{"internalType":"uint256","name":"amountOutMin","type":"uint256"},{"internalType":"uint256","name":"deadline","type":"uint256"}],"name":"attemptSwap","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"bridge","outputs":[{"internalType":"contract L2_Bridge","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"exchangeAddress","outputs":[{"internalType":"contract Swap","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"hToken","outputs":[{"internalType":"contract IERC20","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"l2CanonicalToken","outputs":[{"internalType":"contract IERC20","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"l2CanonicalTokenIsEth","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"chainId","type":"uint256"},{"internalType":"address","name":"recipient","type":"address"},{"internalType":"uint256","name":"amount","type":"uint256"},{"internalType":"uint256","name":"bonderFee","type":"uint256"},{"internalType":"uint256","name":"amountOutMin","type":"uint256"},{"internalType":"uint256","name":"deadline","type":"uint256"},{"internalType":"uint256","name":"destinationAmountOutMin","type":"uint256"},{"internalType":"uint256","name":"destinationDeadline","type":"uint256"}],"name":"swapAndSend","outputs":[],"stateMutability":"payable","type":"function"},{"stateMutability":"payable","type":"receive"}]

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@ -0,0 +1,390 @@
// Code generated - DO NOT EDIT.
// This file is a generated binding and any manual changes will be lost.
package hopWrapper
import (
"math/big"
"strings"
ethereum "github.com/ethereum/go-ethereum"
"github.com/ethereum/go-ethereum/accounts/abi"
"github.com/ethereum/go-ethereum/accounts/abi/bind"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/core/types"
"github.com/ethereum/go-ethereum/event"
)
// Reference imports to suppress errors if they are not otherwise used.
var (
_ = big.NewInt
_ = strings.NewReader
_ = ethereum.NotFound
_ = bind.Bind
_ = common.Big1
_ = types.BloomLookup
_ = event.NewSubscription
)
// HopWrapperABI is the input ABI used to generate the binding from.
const HopWrapperABI = "[{\"inputs\":[{\"internalType\":\"contractL2_Bridge\",\"name\":\"_bridge\",\"type\":\"address\"},{\"internalType\":\"contractIERC20\",\"name\":\"_l2CanonicalToken\",\"type\":\"address\"},{\"internalType\":\"bool\",\"name\":\"_l2CanonicalTokenIsEth\",\"type\":\"bool\"},{\"internalType\":\"contractIERC20\",\"name\":\"_hToken\",\"type\":\"address\"},{\"internalType\":\"contractSwap\",\"name\":\"_exchangeAddress\",\"type\":\"address\"}],\"stateMutability\":\"nonpayable\",\"type\":\"constructor\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"recipient\",\"type\":\"address\"},{\"internalType\":\"uint256\",\"name\":\"amount\",\"type\":\"uint256\"},{\"internalType\":\"uint256\",\"name\":\"amountOutMin\",\"type\":\"uint256\"},{\"internalType\":\"uint256\",\"name\":\"deadline\",\"type\":\"uint256\"}],\"name\":\"attemptSwap\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"bridge\",\"outputs\":[{\"internalType\":\"contractL2_Bridge\",\"name\":\"\",\"type\":\"address\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"exchangeAddress\",\"outputs\":[{\"internalType\":\"contractSwap\",\"name\":\"\",\"type\":\"address\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"hToken\",\"outputs\":[{\"internalType\":\"contractIERC20\",\"name\":\"\",\"type\":\"address\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"l2CanonicalToken\",\"outputs\":[{\"internalType\":\"contractIERC20\",\"name\":\"\",\"type\":\"address\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"l2CanonicalTokenIsEth\",\"outputs\":[{\"internalType\":\"bool\",\"name\":\"\",\"type\":\"bool\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"uint256\",\"name\":\"chainId\",\"type\":\"uint256\"},{\"internalType\":\"address\",\"name\":\"recipient\",\"type\":\"address\"},{\"internalType\":\"uint256\",\"name\":\"amount\",\"type\":\"uint256\"},{\"internalType\":\"uint256\",\"name\":\"bonderFee\",\"type\":\"uint256\"},{\"internalType\":\"uint256\",\"name\":\"amountOutMin\",\"type\":\"uint256\"},{\"internalType\":\"uint256\",\"name\":\"deadline\",\"type\":\"uint256\"},{\"internalType\":\"uint256\",\"name\":\"destinationAmountOutMin\",\"type\":\"uint256\"},{\"internalType\":\"uint256\",\"name\":\"destinationDeadline\",\"type\":\"uint256\"}],\"name\":\"swapAndSend\",\"outputs\":[],\"stateMutability\":\"payable\",\"type\":\"function\"},{\"stateMutability\":\"payable\",\"type\":\"receive\"}]"
// HopWrapper is an auto generated Go binding around an Ethereum contract.
type HopWrapper struct {
HopWrapperCaller // Read-only binding to the contract
HopWrapperTransactor // Write-only binding to the contract
HopWrapperFilterer // Log filterer for contract events
}
// HopWrapperCaller is an auto generated read-only Go binding around an Ethereum contract.
type HopWrapperCaller struct {
contract *bind.BoundContract // Generic contract wrapper for the low level calls
}
// HopWrapperTransactor is an auto generated write-only Go binding around an Ethereum contract.
type HopWrapperTransactor struct {
contract *bind.BoundContract // Generic contract wrapper for the low level calls
}
// HopWrapperFilterer is an auto generated log filtering Go binding around an Ethereum contract events.
type HopWrapperFilterer struct {
contract *bind.BoundContract // Generic contract wrapper for the low level calls
}
// HopWrapperSession is an auto generated Go binding around an Ethereum contract,
// with pre-set call and transact options.
type HopWrapperSession struct {
Contract *HopWrapper // Generic contract binding to set the session for
CallOpts bind.CallOpts // Call options to use throughout this session
TransactOpts bind.TransactOpts // Transaction auth options to use throughout this session
}
// HopWrapperCallerSession is an auto generated read-only Go binding around an Ethereum contract,
// with pre-set call options.
type HopWrapperCallerSession struct {
Contract *HopWrapperCaller // Generic contract caller binding to set the session for
CallOpts bind.CallOpts // Call options to use throughout this session
}
// HopWrapperTransactorSession is an auto generated write-only Go binding around an Ethereum contract,
// with pre-set transact options.
type HopWrapperTransactorSession struct {
Contract *HopWrapperTransactor // Generic contract transactor binding to set the session for
TransactOpts bind.TransactOpts // Transaction auth options to use throughout this session
}
// HopWrapperRaw is an auto generated low-level Go binding around an Ethereum contract.
type HopWrapperRaw struct {
Contract *HopWrapper // Generic contract binding to access the raw methods on
}
// HopWrapperCallerRaw is an auto generated low-level read-only Go binding around an Ethereum contract.
type HopWrapperCallerRaw struct {
Contract *HopWrapperCaller // Generic read-only contract binding to access the raw methods on
}
// HopWrapperTransactorRaw is an auto generated low-level write-only Go binding around an Ethereum contract.
type HopWrapperTransactorRaw struct {
Contract *HopWrapperTransactor // Generic write-only contract binding to access the raw methods on
}
// NewHopWrapper creates a new instance of HopWrapper, bound to a specific deployed contract.
func NewHopWrapper(address common.Address, backend bind.ContractBackend) (*HopWrapper, error) {
contract, err := bindHopWrapper(address, backend, backend, backend)
if err != nil {
return nil, err
}
return &HopWrapper{HopWrapperCaller: HopWrapperCaller{contract: contract}, HopWrapperTransactor: HopWrapperTransactor{contract: contract}, HopWrapperFilterer: HopWrapperFilterer{contract: contract}}, nil
}
// NewHopWrapperCaller creates a new read-only instance of HopWrapper, bound to a specific deployed contract.
func NewHopWrapperCaller(address common.Address, caller bind.ContractCaller) (*HopWrapperCaller, error) {
contract, err := bindHopWrapper(address, caller, nil, nil)
if err != nil {
return nil, err
}
return &HopWrapperCaller{contract: contract}, nil
}
// NewHopWrapperTransactor creates a new write-only instance of HopWrapper, bound to a specific deployed contract.
func NewHopWrapperTransactor(address common.Address, transactor bind.ContractTransactor) (*HopWrapperTransactor, error) {
contract, err := bindHopWrapper(address, nil, transactor, nil)
if err != nil {
return nil, err
}
return &HopWrapperTransactor{contract: contract}, nil
}
// NewHopWrapperFilterer creates a new log filterer instance of HopWrapper, bound to a specific deployed contract.
func NewHopWrapperFilterer(address common.Address, filterer bind.ContractFilterer) (*HopWrapperFilterer, error) {
contract, err := bindHopWrapper(address, nil, nil, filterer)
if err != nil {
return nil, err
}
return &HopWrapperFilterer{contract: contract}, nil
}
// bindHopWrapper binds a generic wrapper to an already deployed contract.
func bindHopWrapper(address common.Address, caller bind.ContractCaller, transactor bind.ContractTransactor, filterer bind.ContractFilterer) (*bind.BoundContract, error) {
parsed, err := abi.JSON(strings.NewReader(HopWrapperABI))
if err != nil {
return nil, err
}
return bind.NewBoundContract(address, parsed, caller, transactor, filterer), nil
}
// Call invokes the (constant) contract method with params as input values and
// sets the output to result. The result type might be a single field for simple
// returns, a slice of interfaces for anonymous returns and a struct for named
// returns.
func (_HopWrapper *HopWrapperRaw) Call(opts *bind.CallOpts, result *[]interface{}, method string, params ...interface{}) error {
return _HopWrapper.Contract.HopWrapperCaller.contract.Call(opts, result, method, params...)
}
// Transfer initiates a plain transaction to move funds to the contract, calling
// its default method if one is available.
func (_HopWrapper *HopWrapperRaw) Transfer(opts *bind.TransactOpts) (*types.Transaction, error) {
return _HopWrapper.Contract.HopWrapperTransactor.contract.Transfer(opts)
}
// Transact invokes the (paid) contract method with params as input values.
func (_HopWrapper *HopWrapperRaw) Transact(opts *bind.TransactOpts, method string, params ...interface{}) (*types.Transaction, error) {
return _HopWrapper.Contract.HopWrapperTransactor.contract.Transact(opts, method, params...)
}
// Call invokes the (constant) contract method with params as input values and
// sets the output to result. The result type might be a single field for simple
// returns, a slice of interfaces for anonymous returns and a struct for named
// returns.
func (_HopWrapper *HopWrapperCallerRaw) Call(opts *bind.CallOpts, result *[]interface{}, method string, params ...interface{}) error {
return _HopWrapper.Contract.contract.Call(opts, result, method, params...)
}
// Transfer initiates a plain transaction to move funds to the contract, calling
// its default method if one is available.
func (_HopWrapper *HopWrapperTransactorRaw) Transfer(opts *bind.TransactOpts) (*types.Transaction, error) {
return _HopWrapper.Contract.contract.Transfer(opts)
}
// Transact invokes the (paid) contract method with params as input values.
func (_HopWrapper *HopWrapperTransactorRaw) Transact(opts *bind.TransactOpts, method string, params ...interface{}) (*types.Transaction, error) {
return _HopWrapper.Contract.contract.Transact(opts, method, params...)
}
// Bridge is a free data retrieval call binding the contract method 0xe78cea92.
//
// Solidity: function bridge() view returns(address)
func (_HopWrapper *HopWrapperCaller) Bridge(opts *bind.CallOpts) (common.Address, error) {
var out []interface{}
err := _HopWrapper.contract.Call(opts, &out, "bridge")
if err != nil {
return *new(common.Address), err
}
out0 := *abi.ConvertType(out[0], new(common.Address)).(*common.Address)
return out0, err
}
// Bridge is a free data retrieval call binding the contract method 0xe78cea92.
//
// Solidity: function bridge() view returns(address)
func (_HopWrapper *HopWrapperSession) Bridge() (common.Address, error) {
return _HopWrapper.Contract.Bridge(&_HopWrapper.CallOpts)
}
// Bridge is a free data retrieval call binding the contract method 0xe78cea92.
//
// Solidity: function bridge() view returns(address)
func (_HopWrapper *HopWrapperCallerSession) Bridge() (common.Address, error) {
return _HopWrapper.Contract.Bridge(&_HopWrapper.CallOpts)
}
// ExchangeAddress is a free data retrieval call binding the contract method 0x9cd01605.
//
// Solidity: function exchangeAddress() view returns(address)
func (_HopWrapper *HopWrapperCaller) ExchangeAddress(opts *bind.CallOpts) (common.Address, error) {
var out []interface{}
err := _HopWrapper.contract.Call(opts, &out, "exchangeAddress")
if err != nil {
return *new(common.Address), err
}
out0 := *abi.ConvertType(out[0], new(common.Address)).(*common.Address)
return out0, err
}
// ExchangeAddress is a free data retrieval call binding the contract method 0x9cd01605.
//
// Solidity: function exchangeAddress() view returns(address)
func (_HopWrapper *HopWrapperSession) ExchangeAddress() (common.Address, error) {
return _HopWrapper.Contract.ExchangeAddress(&_HopWrapper.CallOpts)
}
// ExchangeAddress is a free data retrieval call binding the contract method 0x9cd01605.
//
// Solidity: function exchangeAddress() view returns(address)
func (_HopWrapper *HopWrapperCallerSession) ExchangeAddress() (common.Address, error) {
return _HopWrapper.Contract.ExchangeAddress(&_HopWrapper.CallOpts)
}
// HToken is a free data retrieval call binding the contract method 0xfc6e3b3b.
//
// Solidity: function hToken() view returns(address)
func (_HopWrapper *HopWrapperCaller) HToken(opts *bind.CallOpts) (common.Address, error) {
var out []interface{}
err := _HopWrapper.contract.Call(opts, &out, "hToken")
if err != nil {
return *new(common.Address), err
}
out0 := *abi.ConvertType(out[0], new(common.Address)).(*common.Address)
return out0, err
}
// HToken is a free data retrieval call binding the contract method 0xfc6e3b3b.
//
// Solidity: function hToken() view returns(address)
func (_HopWrapper *HopWrapperSession) HToken() (common.Address, error) {
return _HopWrapper.Contract.HToken(&_HopWrapper.CallOpts)
}
// HToken is a free data retrieval call binding the contract method 0xfc6e3b3b.
//
// Solidity: function hToken() view returns(address)
func (_HopWrapper *HopWrapperCallerSession) HToken() (common.Address, error) {
return _HopWrapper.Contract.HToken(&_HopWrapper.CallOpts)
}
// L2CanonicalToken is a free data retrieval call binding the contract method 0x1ee1bf67.
//
// Solidity: function l2CanonicalToken() view returns(address)
func (_HopWrapper *HopWrapperCaller) L2CanonicalToken(opts *bind.CallOpts) (common.Address, error) {
var out []interface{}
err := _HopWrapper.contract.Call(opts, &out, "l2CanonicalToken")
if err != nil {
return *new(common.Address), err
}
out0 := *abi.ConvertType(out[0], new(common.Address)).(*common.Address)
return out0, err
}
// L2CanonicalToken is a free data retrieval call binding the contract method 0x1ee1bf67.
//
// Solidity: function l2CanonicalToken() view returns(address)
func (_HopWrapper *HopWrapperSession) L2CanonicalToken() (common.Address, error) {
return _HopWrapper.Contract.L2CanonicalToken(&_HopWrapper.CallOpts)
}
// L2CanonicalToken is a free data retrieval call binding the contract method 0x1ee1bf67.
//
// Solidity: function l2CanonicalToken() view returns(address)
func (_HopWrapper *HopWrapperCallerSession) L2CanonicalToken() (common.Address, error) {
return _HopWrapper.Contract.L2CanonicalToken(&_HopWrapper.CallOpts)
}
// L2CanonicalTokenIsEth is a free data retrieval call binding the contract method 0x28555125.
//
// Solidity: function l2CanonicalTokenIsEth() view returns(bool)
func (_HopWrapper *HopWrapperCaller) L2CanonicalTokenIsEth(opts *bind.CallOpts) (bool, error) {
var out []interface{}
err := _HopWrapper.contract.Call(opts, &out, "l2CanonicalTokenIsEth")
if err != nil {
return *new(bool), err
}
out0 := *abi.ConvertType(out[0], new(bool)).(*bool)
return out0, err
}
// L2CanonicalTokenIsEth is a free data retrieval call binding the contract method 0x28555125.
//
// Solidity: function l2CanonicalTokenIsEth() view returns(bool)
func (_HopWrapper *HopWrapperSession) L2CanonicalTokenIsEth() (bool, error) {
return _HopWrapper.Contract.L2CanonicalTokenIsEth(&_HopWrapper.CallOpts)
}
// L2CanonicalTokenIsEth is a free data retrieval call binding the contract method 0x28555125.
//
// Solidity: function l2CanonicalTokenIsEth() view returns(bool)
func (_HopWrapper *HopWrapperCallerSession) L2CanonicalTokenIsEth() (bool, error) {
return _HopWrapper.Contract.L2CanonicalTokenIsEth(&_HopWrapper.CallOpts)
}
// AttemptSwap is a paid mutator transaction binding the contract method 0x676c5ef6.
//
// Solidity: function attemptSwap(address recipient, uint256 amount, uint256 amountOutMin, uint256 deadline) returns()
func (_HopWrapper *HopWrapperTransactor) AttemptSwap(opts *bind.TransactOpts, recipient common.Address, amount *big.Int, amountOutMin *big.Int, deadline *big.Int) (*types.Transaction, error) {
return _HopWrapper.contract.Transact(opts, "attemptSwap", recipient, amount, amountOutMin, deadline)
}
// AttemptSwap is a paid mutator transaction binding the contract method 0x676c5ef6.
//
// Solidity: function attemptSwap(address recipient, uint256 amount, uint256 amountOutMin, uint256 deadline) returns()
func (_HopWrapper *HopWrapperSession) AttemptSwap(recipient common.Address, amount *big.Int, amountOutMin *big.Int, deadline *big.Int) (*types.Transaction, error) {
return _HopWrapper.Contract.AttemptSwap(&_HopWrapper.TransactOpts, recipient, amount, amountOutMin, deadline)
}
// AttemptSwap is a paid mutator transaction binding the contract method 0x676c5ef6.
//
// Solidity: function attemptSwap(address recipient, uint256 amount, uint256 amountOutMin, uint256 deadline) returns()
func (_HopWrapper *HopWrapperTransactorSession) AttemptSwap(recipient common.Address, amount *big.Int, amountOutMin *big.Int, deadline *big.Int) (*types.Transaction, error) {
return _HopWrapper.Contract.AttemptSwap(&_HopWrapper.TransactOpts, recipient, amount, amountOutMin, deadline)
}
// SwapAndSend is a paid mutator transaction binding the contract method 0xeea0d7b2.
//
// Solidity: function swapAndSend(uint256 chainId, address recipient, uint256 amount, uint256 bonderFee, uint256 amountOutMin, uint256 deadline, uint256 destinationAmountOutMin, uint256 destinationDeadline) payable returns()
func (_HopWrapper *HopWrapperTransactor) SwapAndSend(opts *bind.TransactOpts, chainId *big.Int, recipient common.Address, amount *big.Int, bonderFee *big.Int, amountOutMin *big.Int, deadline *big.Int, destinationAmountOutMin *big.Int, destinationDeadline *big.Int) (*types.Transaction, error) {
return _HopWrapper.contract.Transact(opts, "swapAndSend", chainId, recipient, amount, bonderFee, amountOutMin, deadline, destinationAmountOutMin, destinationDeadline)
}
// SwapAndSend is a paid mutator transaction binding the contract method 0xeea0d7b2.
//
// Solidity: function swapAndSend(uint256 chainId, address recipient, uint256 amount, uint256 bonderFee, uint256 amountOutMin, uint256 deadline, uint256 destinationAmountOutMin, uint256 destinationDeadline) payable returns()
func (_HopWrapper *HopWrapperSession) SwapAndSend(chainId *big.Int, recipient common.Address, amount *big.Int, bonderFee *big.Int, amountOutMin *big.Int, deadline *big.Int, destinationAmountOutMin *big.Int, destinationDeadline *big.Int) (*types.Transaction, error) {
return _HopWrapper.Contract.SwapAndSend(&_HopWrapper.TransactOpts, chainId, recipient, amount, bonderFee, amountOutMin, deadline, destinationAmountOutMin, destinationDeadline)
}
// SwapAndSend is a paid mutator transaction binding the contract method 0xeea0d7b2.
//
// Solidity: function swapAndSend(uint256 chainId, address recipient, uint256 amount, uint256 bonderFee, uint256 amountOutMin, uint256 deadline, uint256 destinationAmountOutMin, uint256 destinationDeadline) payable returns()
func (_HopWrapper *HopWrapperTransactorSession) SwapAndSend(chainId *big.Int, recipient common.Address, amount *big.Int, bonderFee *big.Int, amountOutMin *big.Int, deadline *big.Int, destinationAmountOutMin *big.Int, destinationDeadline *big.Int) (*types.Transaction, error) {
return _HopWrapper.Contract.SwapAndSend(&_HopWrapper.TransactOpts, chainId, recipient, amount, bonderFee, amountOutMin, deadline, destinationAmountOutMin, destinationDeadline)
}
// Receive is a paid mutator transaction binding the contract receive function.
//
// Solidity: receive() payable returns()
func (_HopWrapper *HopWrapperTransactor) Receive(opts *bind.TransactOpts) (*types.Transaction, error) {
return _HopWrapper.contract.RawTransact(opts, nil) // calldata is disallowed for receive function
}
// Receive is a paid mutator transaction binding the contract receive function.
//
// Solidity: receive() payable returns()
func (_HopWrapper *HopWrapperSession) Receive() (*types.Transaction, error) {
return _HopWrapper.Contract.Receive(&_HopWrapper.TransactOpts)
}
// Receive is a paid mutator transaction binding the contract receive function.
//
// Solidity: receive() payable returns()
func (_HopWrapper *HopWrapperTransactorSession) Receive() (*types.Transaction, error) {
return _HopWrapper.Contract.Receive(&_HopWrapper.TransactOpts)
}

2
go.mod
View File

@ -2,7 +2,7 @@ module github.com/status-im/status-go
go 1.18
replace github.com/ethereum/go-ethereum v1.10.21 => github.com/status-im/go-ethereum v1.10.4-status.4
replace github.com/ethereum/go-ethereum v1.10.21 => github.com/status-im/go-ethereum v1.10.4-status.5
replace github.com/docker/docker => github.com/docker/engine v1.4.2-0.20190717161051-705d9623b7c1

View File

@ -463,7 +463,11 @@ func (b *StatusNode) appmetricsService() common.StatusService {
func (b *StatusNode) walletService(accountsDB *accounts.Database, accountsFeed *event.Feed, openseaAPIKey string) common.StatusService {
if b.walletSrvc == nil {
b.walletSrvc = wallet.NewService(b.appDB, accountsDB, b.rpcClient, accountsFeed, openseaAPIKey, b.gethAccountManager, b.transactor, b.config)
b.walletSrvc = wallet.NewService(
b.appDB, accountsDB, b.rpcClient, accountsFeed, openseaAPIKey, b.gethAccountManager, b.transactor, b.config,
b.ensService(),
b.stickersService(accountsDB),
)
}
return b.walletSrvc
}

View File

@ -346,12 +346,16 @@ func (api *API) ReleaseEstimate(ctx context.Context, chainID uint64, txArgs tran
return 0, err
}
return ethClient.EstimateGas(ctx, ethereum.CallMsg{
estimate, err := ethClient.EstimateGas(ctx, ethereum.CallMsg{
From: common.Address(txArgs.From),
To: &registryAddr,
Value: big.NewInt(0),
Data: data,
})
if err != nil {
return 0, err
}
return estimate + 1000, nil
}
func (api *API) Register(ctx context.Context, chainID uint64, txArgs transactions.SendTxArgs, password string, username string, pubkey string) (string, error) {
@ -465,7 +469,11 @@ func (api *API) RegisterEstimate(ctx context.Context, chainID uint64, txArgs tra
return 0, err
}
return ethClient.EstimateGas(ctx, callMsg)
estimate, err := ethClient.EstimateGas(ctx, callMsg)
if err != nil {
return 0, err
}
return estimate + 1000, nil
}
func (api *API) SetPubKey(ctx context.Context, chainID uint64, txArgs transactions.SendTxArgs, password string, username string, pubkey string) (string, error) {
@ -545,7 +553,11 @@ func (api *API) SetPubKeyEstimate(ctx context.Context, chainID uint64, txArgs tr
return 0, err
}
return ethClient.EstimateGas(ctx, callMsg)
estimate, err := ethClient.EstimateGas(ctx, callMsg)
if err != nil {
return 0, err
}
return estimate + 1000, nil
}
func (api *API) ResourceURL(ctx context.Context, chainID uint64, username string) (*URI, error) {

View File

@ -28,6 +28,7 @@ func NewService(acc *accounts.Database, rpcClient *rpc.Client, accountsManager *
httpServer: httpServer,
ctx: ctx,
cancel: cancel,
api: NewAPI(ctx, acc, rpcClient, accountsManager, rpcFiltersSrvc, config.KeyStoreDir, downloader, httpServer),
}
}
@ -42,6 +43,7 @@ type Service struct {
httpServer *server.MediaServer
ctx context.Context
cancel context.CancelFunc
api *API
}
// Start a service.
@ -55,13 +57,17 @@ func (s *Service) Stop() error {
return nil
}
func (s *Service) API() *API {
return s.api
}
// APIs returns list of available RPC APIs.
func (s *Service) APIs() []ethRpc.API {
return []ethRpc.API{
{
Namespace: "stickers",
Version: "0.1.0",
Service: NewAPI(s.ctx, s.accountsDB, s.rpcClient, s.accountsManager, s.rpcFiltersSrvc, s.keyStoreDir, s.downloader, s.httpServer),
Service: s.api,
},
}
}

View File

@ -12,9 +12,10 @@ import (
"github.com/status-im/status-go/eth-node/types"
"github.com/status-im/status-go/params"
"github.com/status-im/status-go/services/wallet/async"
"github.com/status-im/status-go/services/wallet/bridge"
"github.com/status-im/status-go/services/wallet/chain"
"github.com/status-im/status-go/services/wallet/token"
"github.com/status-im/status-go/services/wallet/transfer"
"github.com/status-im/status-go/transactions"
)
func NewAPI(s *Service) *API {
@ -93,7 +94,7 @@ func (api *API) GetTokensBalances(ctx context.Context, accounts, addresses []com
if err != nil {
return nil, err
}
return api.s.tokenManager.getBalances(ctx, []*chain.Client{chainClient}, accounts, addresses)
return api.s.tokenManager.GetBalances(ctx, []*chain.Client{chainClient}, accounts, addresses)
}
func (api *API) GetTokensBalancesForChainIDs(ctx context.Context, chainIDs []uint64, accounts, addresses []common.Address) (map[common.Address]map[common.Address]*hexutil.Big, error) {
@ -101,65 +102,65 @@ func (api *API) GetTokensBalancesForChainIDs(ctx context.Context, chainIDs []uin
if err != nil {
return nil, err
}
return api.s.tokenManager.getBalances(ctx, clients, accounts, addresses)
return api.s.tokenManager.GetBalances(ctx, clients, accounts, addresses)
}
func (api *API) GetTokens(ctx context.Context, chainID uint64) ([]*Token, error) {
func (api *API) GetTokens(ctx context.Context, chainID uint64) ([]*token.Token, error) {
log.Debug("call to get tokens")
rst, err := api.s.tokenManager.getTokens(chainID)
rst, err := api.s.tokenManager.GetTokens(chainID)
log.Debug("result from token store", "len", len(rst))
return rst, err
}
func (api *API) GetCustomTokens(ctx context.Context) ([]*Token, error) {
func (api *API) GetCustomTokens(ctx context.Context) ([]*token.Token, error) {
log.Debug("call to get custom tokens")
rst, err := api.s.tokenManager.getCustoms()
rst, err := api.s.tokenManager.GetCustoms()
log.Debug("result from database for custom tokens", "len", len(rst))
return rst, err
}
func (api *API) DiscoverToken(ctx context.Context, chainID uint64, address common.Address) (*Token, error) {
func (api *API) DiscoverToken(ctx context.Context, chainID uint64, address common.Address) (*token.Token, error) {
log.Debug("call to get discover token")
token, err := api.s.tokenManager.discoverToken(ctx, chainID, address)
token, err := api.s.tokenManager.DiscoverToken(ctx, chainID, address)
return token, err
}
func (api *API) GetVisibleTokens(chainIDs []uint64) (map[uint64][]*Token, error) {
func (api *API) GetVisibleTokens(chainIDs []uint64) (map[uint64][]*token.Token, error) {
log.Debug("call to get visible tokens")
rst, err := api.s.tokenManager.getVisible(chainIDs)
rst, err := api.s.tokenManager.GetVisible(chainIDs)
log.Debug("result from database for visible tokens", "len", len(rst))
return rst, err
}
func (api *API) ToggleVisibleToken(ctx context.Context, chainID uint64, address common.Address) (bool, error) {
log.Debug("call to toggle visible tokens")
err := api.s.tokenManager.toggle(chainID, address)
err := api.s.tokenManager.Toggle(chainID, address)
if err != nil {
return false, err
}
return true, nil
}
func (api *API) AddCustomToken(ctx context.Context, token Token) error {
func (api *API) AddCustomToken(ctx context.Context, token token.Token) error {
log.Debug("call to create or edit custom token")
if token.ChainID == 0 {
token.ChainID = api.s.rpcClient.UpstreamChainID
}
err := api.s.tokenManager.upsertCustom(token)
err := api.s.tokenManager.UpsertCustom(token)
log.Debug("result from database for create or edit custom token", "err", err)
return err
}
func (api *API) DeleteCustomToken(ctx context.Context, address common.Address) error {
log.Debug("call to remove custom token")
err := api.s.tokenManager.deleteCustom(api.s.rpcClient.UpstreamChainID, address)
err := api.s.tokenManager.DeleteCustom(api.s.rpcClient.UpstreamChainID, address)
log.Debug("result from database for remove custom token", "err", err)
return err
}
func (api *API) DeleteCustomTokenByChainID(ctx context.Context, chainID uint64, address common.Address) error {
log.Debug("call to remove custom token")
err := api.s.tokenManager.deleteCustom(chainID, address)
err := api.s.tokenManager.DeleteCustom(chainID, address)
log.Debug("result from database for remove custom token", "err", err)
return err
}
@ -325,14 +326,14 @@ func (api *API) GetSuggestedFees(ctx context.Context, chainID uint64) (*Suggeste
return api.s.feesManager.suggestedFees(ctx, chainID)
}
func (api *API) GetTransactionEstimatedTime(ctx context.Context, chainID uint64, maxFeePerGas float64) (TransactionEstimation, error) {
func (api *API) GetTransactionEstimatedTime(ctx context.Context, chainID uint64, maxFeePerGas *big.Float) (TransactionEstimation, error) {
log.Debug("call to getTransactionEstimatedTime")
return api.s.feesManager.transactionEstimatedTime(ctx, chainID, maxFeePerGas), nil
}
func (api *API) GetSuggestedRoutes(ctx context.Context, account common.Address, amount float64, tokenSymbol string, disabledChainIDs []uint64) (*SuggestedRoutes, error) {
func (api *API) GetSuggestedRoutes(ctx context.Context, sendType SendType, account common.Address, amountIn *hexutil.Big, tokenSymbol string, disabledFromChainIDs, disabledToChaindIDs, preferedChainIDs []uint64, gasFeeMode GasFeeMode) (*SuggestedRoutes, error) {
log.Debug("call to GetSuggestedRoutes")
return api.router.suggestedRoutes(ctx, account, amount, tokenSymbol, disabledChainIDs)
return api.router.suggestedRoutes(ctx, sendType, account, amountIn.ToInt(), tokenSymbol, disabledFromChainIDs, disabledToChaindIDs, preferedChainIDs, gasFeeMode)
}
func (api *API) GetDerivedAddressesForPath(ctx context.Context, password string, derivedFrom string, path string, pageSize int, pageNumber int) ([]*DerivedAddress, error) {
@ -476,7 +477,7 @@ func (api *API) getDerivedAddress(id string, derivedPath string) (*DerivedAddres
return address, nil
}
func (api *API) CreateMultiTransaction(ctx context.Context, multiTransaction *MultiTransaction, data map[uint64][]transactions.SendTxArgs, password string) (*MultiTransactionResult, error) {
func (api *API) CreateMultiTransaction(ctx context.Context, multiTransaction *MultiTransaction, data []*bridge.TransactionBridge, password string) (*MultiTransactionResult, error) {
log.Debug("[WalletAPI:: CreateMultiTransaction] create multi transaction")
return api.s.transactionManager.createMultiTransaction(ctx, multiTransaction, data, password)
return api.s.transactionManager.createMultiTransaction(ctx, multiTransaction, data, api.router.bridges, password)
}

View File

@ -0,0 +1,67 @@
package bridge
import (
"math/big"
"github.com/status-im/status-go/account"
"github.com/status-im/status-go/eth-node/types"
"github.com/status-im/status-go/params"
"github.com/status-im/status-go/services/wallet/token"
"github.com/status-im/status-go/transactions"
)
type TransactionBridge struct {
BridgeName string
ChainID uint64
SimpleTx *transactions.SendTxArgs
HopTx *HopTxArgs
}
func (t *TransactionBridge) Value() *big.Int {
if t.SimpleTx != nil && t.SimpleTx.To != nil {
return t.SimpleTx.Value.ToInt()
} else if t.HopTx != nil {
return t.HopTx.Amount.ToInt()
}
return big.NewInt(0)
}
func (t *TransactionBridge) From() types.Address {
if t.SimpleTx != nil && t.SimpleTx.To != nil {
return t.SimpleTx.From
} else if t.HopTx != nil {
return t.HopTx.From
}
return types.HexToAddress("0x0")
}
func (t *TransactionBridge) To() types.Address {
if t.SimpleTx != nil && t.SimpleTx.To != nil {
return *t.SimpleTx.To
} else if t.HopTx != nil {
return types.Address(t.HopTx.Recipient)
}
return types.HexToAddress("0x0")
}
func (t *TransactionBridge) Data() types.HexBytes {
if t.SimpleTx != nil && t.SimpleTx.To != nil {
return t.SimpleTx.Data
} else if t.HopTx != nil {
return types.HexBytes("")
}
return types.HexBytes("")
}
type Bridge interface {
Name() string
Can(from *params.Network, to *params.Network, token *token.Token, balance *big.Int) (bool, error)
CalculateFees(from, to *params.Network, token *token.Token, amountIn *big.Int, nativeTokenPrice, tokenPrice float64, gasPrice *big.Float) (*big.Int, *big.Int, error)
EstimateGas(from *params.Network, to *params.Network, token *token.Token, amountIn *big.Int) (uint64, error)
CalculateAmountOut(from, to *params.Network, amountIn *big.Int, symbol string) (*big.Int, error)
Send(sendArgs *TransactionBridge, verifiedAccount *account.SelectedExtKey) (types.Hash, error)
}

View File

@ -0,0 +1,380 @@
package bridge
import (
"context"
"math"
"math/big"
"time"
"github.com/ethereum/go-ethereum/accounts/abi/bind"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/common/hexutil"
ethTypes "github.com/ethereum/go-ethereum/core/types"
"github.com/status-im/status-go/account"
"github.com/status-im/status-go/contracts"
"github.com/status-im/status-go/eth-node/types"
"github.com/status-im/status-go/params"
"github.com/status-im/status-go/rpc"
"github.com/status-im/status-go/services/wallet/token"
"github.com/status-im/status-go/transactions"
)
const HopLpFeeBps = 4
const HopCanonicalTokenIndex = 0
const HophTokenIndex = 1
const HopMinBonderFeeUsd = 0.25
var HopBondTransferGasLimit = map[uint64]int64{
1: 165000,
5: 165000,
10: 100000000,
42161: 2500000,
420: 100000000,
421613: 2500000,
}
var HopSettlementGasLimitPerTx = map[uint64]int64{
1: 5141,
5: 5141,
10: 8545,
42161: 19843,
420: 8545,
421613: 19843,
}
var HopBonderFeeBps = map[string]map[uint64]int64{
"USDC": {
1: 14,
5: 14,
10: 14,
42161: 14,
420: 14,
421613: 14,
},
"USDT": {
1: 26,
10: 26,
421613: 26,
},
"DAI": {
1: 26,
10: 26,
42161: 26,
},
"ETH": {
1: 5,
5: 5,
10: 5,
42161: 5,
420: 5,
421613: 5,
},
"WBTC": {
1: 23,
10: 23,
42161: 23,
},
}
type HopTxArgs struct {
transactions.SendTxArgs
ChainID uint64 `json:"chainId"`
Symbol string `json:"symbol"`
Recipient common.Address `json:"recipient"`
Amount *hexutil.Big `json:"amount"`
BonderFee *hexutil.Big `json:"bonderFee"`
}
type HopBridge struct {
contractMaker *contracts.ContractMaker
}
func NewHopBridge(rpcClient *rpc.Client) *HopBridge {
return &HopBridge{
contractMaker: &contracts.ContractMaker{RPCClient: rpcClient},
}
}
func (h *HopBridge) Name() string {
return "Hop"
}
func (h *HopBridge) Can(from, to *params.Network, token *token.Token, balance *big.Int) (bool, error) {
if balance.Cmp(big.NewInt(0)) == 0 {
return false, nil
}
if from.ChainID == to.ChainID {
return false, nil
}
fees, ok := HopBonderFeeBps[token.Symbol]
if !ok {
return false, nil
}
if _, ok := fees[from.ChainID]; !ok {
return false, nil
}
if _, ok := fees[to.ChainID]; !ok {
return false, nil
}
return true, nil
}
func (s *HopBridge) EstimateGas(from, to *params.Network, token *token.Token, amountIn *big.Int) (uint64, error) {
// TODO: find why this doesn't work
// ethClient, err := s.contractMaker.RPCClient.EthClient(from.ChainID)
// if err != nil {
// return 0, err
// }
// zero := common.HexToAddress("0x0")
// zeroInt := big.NewInt(0)
// var data []byte
// if from.Layer == 1 {
// bridgeABI, err := abi.JSON(strings.NewReader(hopBridge.HopBridgeABI))
// if err != nil {
// return 0, err
// }
// data, err = bridgeABI.Pack("sendToL2", big.NewInt(int64(to.ChainID)), zero, amountIn, zeroInt, zeroInt, zero, zeroInt)
// if err != nil {
// return 0, err
// }
// } else {
// wrapperABI, err := abi.JSON(strings.NewReader(hopWrapper.HopWrapperABI))
// if err != nil {
// return 0, err
// }
// data, err = wrapperABI.Pack("swapAndSend", big.NewInt(int64(to.ChainID)), zero, amountIn, zeroInt, zeroInt, zeroInt, zeroInt, zeroInt)
// if err != nil {
// return 0, err
// }
// }
// estimate, err := ethClient.EstimateGas(context.Background(), ethereum.CallMsg{
// From: zero,
// To: &token.Address,
// Value: big.NewInt(0),
// Data: data,
// })
return 500000 + 1000, nil
}
func (s *HopBridge) Send(sendArgs *TransactionBridge, verifiedAccount *account.SelectedExtKey) (hash types.Hash, err error) {
networks, err := s.contractMaker.RPCClient.NetworkManager.Get(false)
if err != nil {
return hash, err
}
var fromNetwork *params.Network
for _, network := range networks {
if network.ChainID == sendArgs.ChainID {
fromNetwork = network
break
}
}
if fromNetwork.Layer == 1 {
return s.sendToL2(sendArgs.ChainID, sendArgs.HopTx, verifiedAccount)
}
return s.swapAndSend(sendArgs.ChainID, sendArgs.HopTx, verifiedAccount)
}
func getSigner(chainID uint64, from types.Address, verifiedAccount *account.SelectedExtKey) bind.SignerFn {
return func(addr common.Address, tx *ethTypes.Transaction) (*ethTypes.Transaction, error) {
s := ethTypes.NewLondonSigner(new(big.Int).SetUint64(chainID))
return ethTypes.SignTx(tx, s, verifiedAccount.AccountKey.PrivateKey)
}
}
func (s *HopBridge) sendToL2(chainID uint64, sendArgs *HopTxArgs, verifiedAccount *account.SelectedExtKey) (hash types.Hash, err error) {
bridge, err := s.contractMaker.NewHopL1Bridge(chainID, sendArgs.Symbol)
if err != nil {
return hash, err
}
txOpts := sendArgs.ToTransactOpts(getSigner(chainID, sendArgs.From, verifiedAccount))
txOpts.Value = (*big.Int)(sendArgs.Amount)
now := time.Now()
deadline := big.NewInt(now.Unix() + 604800)
tx, err := bridge.SendToL2(
txOpts,
big.NewInt(int64(sendArgs.ChainID)),
sendArgs.Recipient,
sendArgs.Amount.ToInt(),
big.NewInt(0),
deadline,
common.HexToAddress("0x0"),
big.NewInt(0),
)
if err != nil {
return hash, err
}
return types.Hash(tx.Hash()), nil
}
func (s *HopBridge) swapAndSend(chainID uint64, sendArgs *HopTxArgs, verifiedAccount *account.SelectedExtKey) (hash types.Hash, err error) {
ammWrapper, err := s.contractMaker.NewHopL2AmmWrapper(chainID, sendArgs.Symbol)
if err != nil {
return hash, err
}
txOpts := sendArgs.ToTransactOpts(getSigner(chainID, sendArgs.From, verifiedAccount))
txOpts.Value = (*big.Int)(sendArgs.Amount)
now := time.Now()
deadline := big.NewInt(now.Unix() + 604800)
tx, err := ammWrapper.SwapAndSend(
txOpts,
big.NewInt(int64(sendArgs.ChainID)),
sendArgs.Recipient,
sendArgs.Amount.ToInt(),
sendArgs.BonderFee.ToInt(),
big.NewInt(0),
deadline,
big.NewInt(0),
deadline,
)
if err != nil {
return hash, err
}
return types.Hash(tx.Hash()), nil
}
// CalculateBonderFees logics come from: https://docs.hop.exchange/fee-calculation
func (h *HopBridge) CalculateBonderFees(from, to *params.Network, token *token.Token, amountIn *big.Int, nativeTokenPrice, tokenPrice float64, gasPrice *big.Float) (*big.Int, error) {
amount := new(big.Float).SetInt(amountIn)
totalFee := big.NewFloat(0)
destinationTxFee, err := h.getDestinationTxFee(from, to, nativeTokenPrice, tokenPrice, gasPrice)
if err != nil {
return nil, err
}
bonderFeeRelative, err := h.getBonderFeeRelative(from, to, amount, token)
if err != nil {
return nil, err
}
if from.Layer != 1 {
adjustedBonderFee, err := h.calcFromHTokenAmount(to, bonderFeeRelative, token.Symbol)
if err != nil {
return nil, err
}
adjustedDestinationTxFee, err := h.calcToHTokenAmount(to, destinationTxFee, token.Symbol)
if err != nil {
return nil, err
}
bonderFeeAbsolute := h.getBonderFeeAbsolute(tokenPrice)
if adjustedBonderFee.Cmp(bonderFeeAbsolute) == -1 {
adjustedBonderFee = bonderFeeAbsolute
}
totalFee.Add(adjustedBonderFee, adjustedDestinationTxFee)
}
res, _ := new(big.Float).Mul(totalFee, big.NewFloat(math.Pow(10, float64(token.Decimals)))).Int(nil)
return res, nil
}
func (h *HopBridge) CalculateFees(from, to *params.Network, token *token.Token, amountIn *big.Int, nativeTokenPrice, tokenPrice float64, gasPrice *big.Float) (*big.Int, *big.Int, error) {
bonderFees, err := h.CalculateBonderFees(from, to, token, amountIn, nativeTokenPrice, tokenPrice, gasPrice)
if err != nil {
return nil, nil, err
}
amountOut, err := h.amountOut(from, to, new(big.Float).SetInt(amountIn), token.Symbol)
if err != nil {
return nil, nil, err
}
amountOutInt, _ := amountOut.Int(nil)
return bonderFees, new(big.Int).Add(
bonderFees,
new(big.Int).Sub(amountIn, amountOutInt),
), nil
}
func (h *HopBridge) calcToHTokenAmount(network *params.Network, amount *big.Float, symbol string) (*big.Float, error) {
if network.Layer == 1 || amount.Cmp(big.NewFloat(0)) == 0 {
return amount, nil
}
contract, err := h.contractMaker.NewHopL2SaddlSwap(network.ChainID, symbol)
if err != nil {
return nil, err
}
amountInt, _ := amount.Int(nil)
res, err := contract.CalculateSwap(&bind.CallOpts{Context: context.Background()}, HopCanonicalTokenIndex, HophTokenIndex, amountInt)
if err != nil {
return nil, err
}
return new(big.Float).SetInt(res), nil
}
func (h *HopBridge) calcFromHTokenAmount(network *params.Network, amount *big.Float, symbol string) (*big.Float, error) {
if network.Layer == 1 || amount.Cmp(big.NewFloat(0)) == 0 {
return amount, nil
}
contract, err := h.contractMaker.NewHopL2SaddlSwap(network.ChainID, symbol)
if err != nil {
return nil, err
}
amountInt, _ := amount.Int(nil)
res, err := contract.CalculateSwap(&bind.CallOpts{Context: context.Background()}, HophTokenIndex, HopCanonicalTokenIndex, amountInt)
if err != nil {
return nil, err
}
return new(big.Float).SetInt(res), nil
}
func (h *HopBridge) CalculateAmountOut(from, to *params.Network, amountIn *big.Int, symbol string) (*big.Int, error) {
amountOut, err := h.amountOut(from, to, new(big.Float).SetInt(amountIn), symbol)
if err != nil {
return nil, err
}
amountOutInt, _ := amountOut.Int(nil)
return amountOutInt, nil
}
func (h *HopBridge) amountOut(from, to *params.Network, amountIn *big.Float, symbol string) (*big.Float, error) {
hTokenAmount, err := h.calcToHTokenAmount(from, amountIn, symbol)
if err != nil {
return nil, err
}
return h.calcFromHTokenAmount(to, hTokenAmount, symbol)
}
func (h *HopBridge) getBonderFeeRelative(from, to *params.Network, amount *big.Float, token *token.Token) (*big.Float, error) {
if from.Layer != 1 {
return big.NewFloat(0), nil
}
hTokenAmount, err := h.calcToHTokenAmount(from, amount, token.Symbol)
if err != nil {
return nil, err
}
feeBps := HopBonderFeeBps[token.Symbol][to.ChainID]
factor := new(big.Float).Mul(hTokenAmount, big.NewFloat(float64(feeBps)))
return new(big.Float).Quo(
factor,
big.NewFloat(10000),
), nil
}
func (h *HopBridge) getBonderFeeAbsolute(tokenPrice float64) *big.Float {
return new(big.Float).Quo(big.NewFloat(HopMinBonderFeeUsd), big.NewFloat(tokenPrice))
}
func (h *HopBridge) getDestinationTxFee(from, to *params.Network, nativeTokenPrice, tokenPrice float64, gasPrice *big.Float) (*big.Float, error) {
if from.Layer != 1 {
return big.NewFloat(0), nil
}
bondTransferGasLimit := HopBondTransferGasLimit[to.ChainID]
settlementGasLimit := HopSettlementGasLimitPerTx[to.ChainID]
totalGasLimit := new(big.Int).Add(big.NewInt(bondTransferGasLimit), big.NewInt(settlementGasLimit))
rate := new(big.Float).Quo(big.NewFloat(nativeTokenPrice), big.NewFloat(tokenPrice))
txFeeEth := new(big.Float).Mul(gasPrice, new(big.Float).SetInt(totalGasLimit))
return new(big.Float).Mul(txFeeEth, rate), nil
}

View File

@ -0,0 +1,48 @@
package bridge
import (
"math/big"
"github.com/status-im/status-go/account"
"github.com/status-im/status-go/eth-node/types"
"github.com/status-im/status-go/params"
"github.com/status-im/status-go/services/wallet/token"
"github.com/status-im/status-go/transactions"
)
type SimpleBridge struct {
transactor *transactions.Transactor
}
func NewSimpleBridge(transactor *transactions.Transactor) *SimpleBridge {
return &SimpleBridge{transactor: transactor}
}
func (s *SimpleBridge) Name() string {
return "Simple"
}
func (s *SimpleBridge) Can(from, to *params.Network, token *token.Token, balance *big.Int) (bool, error) {
return from.ChainID == to.ChainID, nil
}
func (s *SimpleBridge) CalculateFees(from, to *params.Network, token *token.Token, amountIn *big.Int, nativeTokenPrice, tokenPrice float64, gasPrice *big.Float) (*big.Int, *big.Int, error) {
return big.NewInt(0), big.NewInt(0), nil
}
func (s *SimpleBridge) EstimateGas(from, to *params.Network, token *token.Token, amountIn *big.Int) (uint64, error) {
// TODO: replace by estimate function
if token.IsNative() {
return 22000, nil // default gas limit for eth transaction
}
return 200000, nil //default gas limit for erc20 transaction
}
func (s *SimpleBridge) Send(sendArgs *TransactionBridge, verifiedAccount *account.SelectedExtKey) (types.Hash, error) {
return s.transactor.SendTransactionWithChainID(sendArgs.ChainID, *sendArgs.SimpleTx, verifiedAccount)
}
func (s *SimpleBridge) CalculateAmountOut(from, to *params.Network, amountIn *big.Int, symbol string) (*big.Int, error) {
return amountIn, nil
}

View File

@ -12,6 +12,14 @@ import (
"github.com/status-im/status-go/rpc"
)
type GasFeeMode int
const (
GasFeeLow GasFeeMode = iota
GasFeeMedium
GasFeeHigh
)
type SuggestedFees struct {
GasPrice *big.Float `json:"gasPrice"`
BaseFee *big.Float `json:"baseFee"`
@ -22,6 +30,22 @@ type SuggestedFees struct {
EIP1559Enabled bool `json:"eip1559Enabled"`
}
func (s *SuggestedFees) feeFor(mode GasFeeMode) *big.Float {
if s.EIP1559Enabled {
return s.GasPrice
}
if mode == GasFeeLow {
return s.MaxFeePerGasLow
}
if mode == GasFeeHigh {
return s.MaxFeePerGasHigh
}
return s.MaxFeePerGasMedium
}
const inclusionThreshold = 0.95
type TransactionEstimation int
@ -52,9 +76,12 @@ func weiToGwei(val *big.Int) *big.Float {
return result.Quo(result, new(big.Float).SetInt(unit))
}
func gweiToWei(val float64) *big.Int {
res := new(big.Int)
res.SetUint64(uint64(val * 1000000000))
func gweiToEth(val *big.Float) *big.Float {
return new(big.Float).Quo(val, big.NewFloat(1000000000))
}
func gweiToWei(val *big.Float) *big.Int {
res, _ := new(big.Float).Mul(val, big.NewFloat(1000000000)).Int(nil)
return res
}
@ -67,12 +94,6 @@ func (f *FeeManager) suggestedFees(ctx context.Context, chainID uint64) (*Sugges
if err != nil {
return nil, err
}
block, err := backend.BlockByNumber(ctx, nil)
if err != nil {
return nil, err
}
maxPriorityFeePerGas, err := backend.SuggestGasTipCap(ctx)
if err != nil {
return &SuggestedFees{
@ -86,12 +107,25 @@ func (f *FeeManager) suggestedFees(ctx context.Context, chainID uint64) (*Sugges
}, nil
}
block, err := backend.BlockByNumber(ctx, nil)
if err != nil {
return nil, err
}
config := params.MainnetChainConfig
baseFee := misc.CalcBaseFee(config, block.Header())
fees, err := f.getFeeHistorySorted(chainID)
if err != nil {
return nil, err
return &SuggestedFees{
GasPrice: weiToGwei(gasPrice),
BaseFee: weiToGwei(baseFee),
MaxPriorityFeePerGas: weiToGwei(maxPriorityFeePerGas),
MaxFeePerGasLow: weiToGwei(maxPriorityFeePerGas),
MaxFeePerGasMedium: weiToGwei(maxPriorityFeePerGas),
MaxFeePerGasHigh: weiToGwei(maxPriorityFeePerGas),
EIP1559Enabled: false,
}, nil
}
perc10 := fees[int64(0.1*float64(len(fees)))-1]
@ -125,7 +159,7 @@ func (f *FeeManager) suggestedFees(ctx context.Context, chainID uint64) (*Sugges
}, nil
}
func (f *FeeManager) transactionEstimatedTime(ctx context.Context, chainID uint64, maxFeePerGas float64) TransactionEstimation {
func (f *FeeManager) transactionEstimatedTime(ctx context.Context, chainID uint64, maxFeePerGas *big.Float) TransactionEstimation {
fees, err := f.getFeeHistorySorted(chainID)
if err != nil {
return Unknown

View File

@ -8,6 +8,7 @@ import (
"github.com/ethereum/go-ethereum/common/hexutil"
"github.com/status-im/status-go/multiaccounts/accounts"
"github.com/status-im/status-go/services/wallet/chain"
"github.com/status-im/status-go/services/wallet/token"
"github.com/status-im/status-go/services/wallet/transfer"
)
@ -20,7 +21,7 @@ type Reader struct {
}
type ReaderToken struct {
Token *Token `json:"token"`
Token *token.Token `json:"token"`
OraclePrice float64 `json:"oraclePrice"`
CryptoBalance *hexutil.Big `json:"cryptoBalance"`
FiatBalance *big.Float `json:"fiatBalance"`
@ -39,8 +40,8 @@ type Wallet struct {
Accounts []ReaderAccount `json:"accounts"`
OnRamp []CryptoOnRamp `json:"onRamp"`
SavedAddresses map[uint64][]SavedAddress `json:"savedAddresses"`
Tokens map[uint64][]*Token `json:"tokens"`
CustomTokens []*Token `json:"customTokens"`
Tokens map[uint64][]*token.Token `json:"tokens"`
CustomTokens []*token.Token `json:"customTokens"`
PendingTransactions map[uint64][]*PendingTransaction `json:"pendingTransactions"`
FiatBalance *big.Float `json:"fiatBalance"`
@ -59,7 +60,7 @@ func (r *Reader) buildReaderAccount(
ctx context.Context,
chainIDs []uint64,
account *accounts.Account,
visibleTokens map[uint64][]*Token,
visibleTokens map[uint64][]*token.Token,
prices map[string]float64,
balances map[common.Address]*hexutil.Big,
) (ReaderAccount, error) {
@ -118,21 +119,21 @@ func (r *Reader) GetWallet(ctx context.Context, chainIDs []uint64) (*Wallet, err
return nil, err
}
tokensMap := make(map[uint64][]*Token)
tokensMap := make(map[uint64][]*token.Token)
for _, chainID := range chainIDs {
tokens, err := r.s.tokenManager.getTokens(chainID)
tokens, err := r.s.tokenManager.GetTokens(chainID)
if err != nil {
return nil, err
}
tokensMap[chainID] = tokens
}
customTokens, err := r.s.tokenManager.getCustoms()
customTokens, err := r.s.tokenManager.GetCustoms()
if err != nil {
return nil, err
}
visibleTokens, err := r.s.tokenManager.getVisible(chainIDs)
visibleTokens, err := r.s.tokenManager.GetVisible(chainIDs)
if err != nil {
return nil, err
}
@ -161,7 +162,7 @@ func (r *Reader) GetWallet(ctx context.Context, chainIDs []uint64) (*Wallet, err
return nil, err
}
balances, err := r.s.tokenManager.getBalances(ctx, clients, getAddresses(accounts), tokenAddresses)
balances, err := r.s.tokenManager.GetBalances(ctx, clients, getAddresses(accounts), tokenAddresses)
if err != nil {
return nil, err
}

View File

@ -2,54 +2,276 @@ package wallet
import (
"context"
"fmt"
"math"
"math/big"
"sync"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/common/hexutil"
"github.com/status-im/status-go/eth-node/types"
"github.com/status-im/status-go/params"
"github.com/status-im/status-go/services/wallet/async"
"github.com/status-im/status-go/services/wallet/bigint"
"github.com/status-im/status-go/services/wallet/bridge"
"github.com/status-im/status-go/services/wallet/chain"
"github.com/status-im/status-go/services/wallet/token"
"github.com/status-im/status-go/transactions"
)
type SuggestedRoutes struct {
Networks []params.Network `json:"networks"`
type SendType int
const (
Transfer SendType = iota
ENSRegister
ENSRelease
ENSSetPubKey
StickersBuy
)
const EstimateUsername = "RandomUsername"
const EstimatePubKey = "0x04bb2024ce5d72e45d4a4f8589ae657ef9745855006996115a23a1af88d536cf02c0524a585fce7bfa79d6a9669af735eda6205d6c7e5b3cdc2b8ff7b2fa1f0b56"
func (s SendType) isTransfer() bool {
return s == Transfer
}
func NewRouter(s *Service) *Router {
return &Router{s}
func (s SendType) isAvailableFor(network *params.Network) bool {
if s == Transfer {
return true
}
if network.ChainID == 1 || network.ChainID == 5 {
return true
}
return false
}
type Router struct {
s *Service
func (s SendType) EstimateGas(service *Service, network *params.Network) uint64 {
from := types.Address(common.HexToAddress("0x5ffa75ce51c3a7ebe23bde37b5e3a0143dfbcee0"))
tx := transactions.SendTxArgs{
From: from,
Value: (*hexutil.Big)(big.NewInt(0)),
}
if s == ENSRegister {
estimate, err := service.ens.API().RegisterEstimate(context.Background(), network.ChainID, tx, EstimateUsername, EstimatePubKey)
if err != nil {
return 400000
}
return estimate
}
if s == ENSRelease {
estimate, err := service.ens.API().ReleaseEstimate(context.Background(), network.ChainID, tx, EstimateUsername)
if err != nil {
return 200000
}
return estimate
}
if s == ENSSetPubKey {
estimate, err := service.ens.API().SetPubKeyEstimate(context.Background(), network.ChainID, tx, fmt.Sprint(EstimateUsername, ".stateofus.eth"), EstimatePubKey)
if err != nil {
return 400000
}
return estimate
}
if s == StickersBuy {
packId := &bigint.BigInt{Int: big.NewInt(2)}
estimate, err := service.stickers.API().BuyEstimate(context.Background(), network.ChainID, from, packId)
if err != nil {
return 400000
}
return estimate
}
return 0
}
func (r *Router) suitableTokenExists(ctx context.Context, network *params.Network, tokens []*Token, account common.Address, amount float64, tokenSymbol string) (bool, error) {
for _, token := range tokens {
if token.Symbol != tokenSymbol {
var zero = big.NewInt(0)
type Path struct {
BridgeName string
From *params.Network
To *params.Network
MaxAmountIn *hexutil.Big
AmountIn *hexutil.Big
AmountOut *hexutil.Big
GasAmount uint64
GasFees *SuggestedFees
BonderFees *hexutil.Big
TokenFees *big.Float
Cost *big.Float
Preferred bool
EstimatedTime TransactionEstimation
}
type Graph = []*Node
type Node struct {
Path *Path
Children Graph
}
func newNode(path *Path) *Node {
return &Node{Path: path, Children: make(Graph, 0)}
}
func buildGraph(AmountIn *big.Int, routes []*Path, level int, sourceChainIDs []uint64) Graph {
graph := make(Graph, 0)
for _, route := range routes {
found := false
for _, chainID := range sourceChainIDs {
if chainID == route.From.ChainID {
found = true
break
}
}
if found {
continue
}
node := newNode(route)
clients, err := chain.NewClients(r.s.rpcClient, []uint64{network.ChainID})
if err != nil {
return false, err
newRoutes := make([]*Path, 0)
for _, r := range routes {
if r.From.ChainID == route.From.ChainID && r.To.ChainID == route.To.ChainID {
continue
}
newRoutes = append(newRoutes, r)
}
balance, err := r.s.tokenManager.getBalance(ctx, clients[0], account, token.Address)
if err != nil {
return false, err
newAmountIn := new(big.Int).Sub(AmountIn, route.MaxAmountIn.ToInt())
if newAmountIn.Sign() > 0 {
newSourceChainIDs := make([]uint64, len(sourceChainIDs))
copy(newSourceChainIDs, sourceChainIDs)
newSourceChainIDs = append(newSourceChainIDs, route.From.ChainID)
node.Children = buildGraph(newAmountIn, newRoutes, level+1, newSourceChainIDs)
if len(node.Children) == 0 {
continue
}
}
amountForToken, _ := new(big.Float).Mul(big.NewFloat(amount), big.NewFloat(math.Pow10(int(token.Decimals)))).Int(nil)
if balance.Cmp(amountForToken) >= 0 {
return true, nil
graph = append(graph, node)
}
return graph
}
func (n Node) findBest(level int) ([]*Path, *big.Float) {
if len(n.Children) == 0 {
if n.Path == nil {
return []*Path{}, big.NewFloat(0)
}
return []*Path{n.Path}, n.Path.Cost
}
var best []*Path
bestTotalCost := big.NewFloat(math.Inf(1))
for _, node := range n.Children {
routes, totalCost := node.findBest(level + 1)
if totalCost.Cmp(bestTotalCost) < 0 {
best = routes
bestTotalCost = totalCost
}
}
return false, nil
if n.Path == nil {
return best, bestTotalCost
}
return append([]*Path{n.Path}, best...), new(big.Float).Add(bestTotalCost, n.Path.Cost)
}
func (r *Router) suggestedRoutes(ctx context.Context, account common.Address, amount float64, tokenSymbol string, disabledChainIDs []uint64) (*SuggestedRoutes, error) {
type SuggestedRoutes struct {
Best []*Path
Candidates []*Path
TokenPrice float64
NativeChainTokenPrice float64
}
func newSuggestedRoutes(amountIn *big.Int, candidates []*Path) *SuggestedRoutes {
if len(candidates) == 0 {
return &SuggestedRoutes{
Candidates: candidates,
Best: candidates,
}
}
node := &Node{
Path: nil,
Children: buildGraph(amountIn, candidates, 0, []uint64{}),
}
best, _ := node.findBest(0)
if len(best) > 0 {
rest := new(big.Int).Set(amountIn)
for _, path := range best {
diff := new(big.Int).Sub(rest, path.MaxAmountIn.ToInt())
if diff.Cmp(big.NewInt(0)) >= 0 {
path.AmountIn = path.MaxAmountIn
} else {
path.AmountIn = (*hexutil.Big)(new(big.Int).Set(rest))
}
rest.Sub(rest, path.AmountIn.ToInt())
}
}
return &SuggestedRoutes{
Candidates: candidates,
Best: best,
}
}
func NewRouter(s *Service) *Router {
bridges := make(map[string]bridge.Bridge)
simple := bridge.NewSimpleBridge(s.transactor)
hop := bridge.NewHopBridge(s.rpcClient)
bridges[simple.Name()] = simple
bridges[hop.Name()] = hop
return &Router{s, bridges}
}
func containsNetworkChainId(network *params.Network, chainIDs []uint64) bool {
for _, chainID := range chainIDs {
if chainID == network.ChainID {
return true
}
}
return false
}
type Router struct {
s *Service
bridges map[string]bridge.Bridge
}
func (r *Router) getBalance(ctx context.Context, network *params.Network, token *token.Token, account common.Address) (*big.Int, error) {
clients, err := chain.NewClients(r.s.rpcClient, []uint64{network.ChainID})
if err != nil {
return nil, err
}
return r.s.tokenManager.GetBalance(ctx, clients[0], account, token.Address)
}
func (r *Router) estimateTimes(ctx context.Context, network *params.Network, gasFees *SuggestedFees, gasFeeMode GasFeeMode) TransactionEstimation {
if gasFeeMode == GasFeeLow {
return r.s.feesManager.transactionEstimatedTime(ctx, network.ChainID, gasFees.MaxFeePerGasLow)
}
if gasFeeMode == GasFeeMedium {
return r.s.feesManager.transactionEstimatedTime(ctx, network.ChainID, gasFees.MaxFeePerGasMedium)
}
return r.s.feesManager.transactionEstimatedTime(ctx, network.ChainID, gasFees.MaxFeePerGasHigh)
}
func (r *Router) suggestedRoutes(ctx context.Context, sendType SendType, account common.Address, amountIn *big.Int, tokenSymbol string, disabledFromChainIDs, disabledToChaindIDs, preferedChainIDs []uint64, gasFeeMode GasFeeMode) (*SuggestedRoutes, error) {
areTestNetworksEnabled, err := r.s.accountsDB.GetTestNetworksEnabled()
if err != nil {
return nil, err
@ -60,10 +282,15 @@ func (r *Router) suggestedRoutes(ctx context.Context, account common.Address, am
return nil, err
}
prices, err := fetchCryptoComparePrices([]string{"ETH", tokenSymbol}, "USD")
if err != nil {
return nil, err
}
var (
group = async.NewAtomicGroup(ctx)
mu sync.Mutex
candidates = make([]params.Network, 0)
candidates = make([]*Path, 0)
)
for networkIdx := range networks {
network := networks[networkIdx]
@ -71,65 +298,136 @@ func (r *Router) suggestedRoutes(ctx context.Context, account common.Address, am
continue
}
networkFound := false
for _, chainID := range disabledChainIDs {
networkFound = false
if chainID == network.ChainID {
networkFound = true
break
}
if containsNetworkChainId(network, disabledFromChainIDs) {
continue
}
// This is network cannot be used as a suggestedRoute as the user has disabled it
if networkFound {
if !sendType.isAvailableFor(network) {
continue
}
token := r.s.tokenManager.FindToken(network, tokenSymbol)
if token == nil {
continue
}
nativeToken := r.s.tokenManager.FindToken(network, network.NativeCurrencySymbol)
if nativeToken == nil {
continue
}
group.Add(func(c context.Context) error {
if tokenSymbol == network.NativeCurrencySymbol {
tokens := []*Token{&Token{
Address: common.HexToAddress("0x"),
Symbol: network.NativeCurrencySymbol,
Decimals: uint(network.NativeCurrencyDecimals),
Name: network.NativeCurrencyName,
}}
ok, _ := r.suitableTokenExists(c, network, tokens, account, amount, tokenSymbol)
if ok {
mu.Lock()
candidates = append(candidates, *network)
mu.Unlock()
return nil
}
gasFees, err := r.s.feesManager.suggestedFees(ctx, network.ChainID)
if err != nil {
return err
}
tokens, err := r.s.tokenManager.getTokens(network.ChainID)
if err == nil {
ok, _ := r.suitableTokenExists(c, network, tokens, account, amount, tokenSymbol)
if ok {
mu.Lock()
candidates = append(candidates, *network)
mu.Unlock()
return nil
}
balance, err := r.getBalance(ctx, network, token, account)
if err != nil {
return err
}
customTokens, err := r.s.tokenManager.getCustomsByChainID(network.ChainID)
if err == nil {
ok, _ := r.suitableTokenExists(c, network, customTokens, account, amount, tokenSymbol)
nativeBalance, err := r.getBalance(ctx, network, nativeToken, account)
if err != nil {
return err
}
maxFees := gasFees.feeFor(gasFeeMode)
estimatedTime := r.s.feesManager.transactionEstimatedTime(ctx, network.ChainID, maxFees)
for _, bridge := range r.bridges {
for _, dest := range networks {
if dest.IsTest != areTestNetworksEnabled {
continue
}
if !sendType.isAvailableFor(network) {
continue
}
if len(preferedChainIDs) > 0 && !containsNetworkChainId(network, preferedChainIDs) {
continue
}
if containsNetworkChainId(dest, disabledToChaindIDs) {
continue
}
can, err := bridge.Can(network, dest, token, balance)
if err != nil || !can {
continue
}
bonderFees, tokenFees, err := bridge.CalculateFees(network, dest, token, amountIn, prices["ETH"], prices[tokenSymbol], gasFees.GasPrice)
if err != nil {
continue
}
gasLimit := uint64(0)
if sendType.isTransfer() {
gasLimit, err = bridge.EstimateGas(network, dest, token, amountIn)
if err != nil {
continue
}
} else {
gasLimit = sendType.EstimateGas(r.s, network)
}
requiredNativeBalance := new(big.Int).Mul(gweiToWei(maxFees), big.NewInt(int64(gasLimit)))
if nativeBalance.Cmp(requiredNativeBalance) <= 0 {
continue
}
preferred := containsNetworkChainId(dest, preferedChainIDs)
gasCost := new(big.Float)
gasCost.Mul(
new(big.Float).Mul(gweiToEth(maxFees), big.NewFloat((float64(gasLimit)))),
big.NewFloat(prices["ETH"]),
)
tokenFeesAsFloat := new(big.Float).Quo(
new(big.Float).SetInt(tokenFees),
big.NewFloat(math.Pow(10, float64(token.Decimals))),
)
tokenCost := new(big.Float)
tokenCost.Mul(tokenFeesAsFloat, big.NewFloat(prices[tokenSymbol]))
cost := new(big.Float)
cost.Add(tokenCost, gasCost)
if ok {
mu.Lock()
candidates = append(candidates, *network)
candidates = append(candidates, &Path{
BridgeName: bridge.Name(),
From: network,
To: dest,
MaxAmountIn: (*hexutil.Big)(balance),
AmountIn: (*hexutil.Big)(big.NewInt(0)),
AmountOut: (*hexutil.Big)(big.NewInt(0)),
GasAmount: gasLimit,
GasFees: gasFees,
BonderFees: (*hexutil.Big)(bonderFees),
TokenFees: tokenFeesAsFloat,
Preferred: preferred,
Cost: cost,
EstimatedTime: estimatedTime,
})
mu.Unlock()
}
}
return nil
})
}
group.Wait()
return &SuggestedRoutes{
Networks: candidates,
}, nil
suggestedRoutes := newSuggestedRoutes(amountIn, candidates)
suggestedRoutes.TokenPrice = prices[tokenSymbol]
suggestedRoutes.NativeChainTokenPrice = prices["ETH"]
for _, path := range suggestedRoutes.Best {
amountOut, err := r.bridges[path.BridgeName].CalculateAmountOut(path.From, path.To, (*big.Int)(path.AmountIn), tokenSymbol)
if err != nil {
continue
}
path.AmountOut = (*hexutil.Big)(amountOut)
}
return suggestedRoutes, nil
}

View File

@ -12,6 +12,9 @@ import (
"github.com/status-im/status-go/multiaccounts/accounts"
"github.com/status-im/status-go/params"
"github.com/status-im/status-go/rpc"
"github.com/status-im/status-go/services/ens"
"github.com/status-im/status-go/services/stickers"
"github.com/status-im/status-go/services/wallet/token"
"github.com/status-im/status-go/services/wallet/transfer"
"github.com/status-im/status-go/transactions"
)
@ -26,11 +29,13 @@ func NewService(
gethManager *account.GethManager,
transactor *transactions.Transactor,
config *params.NodeConfig,
ens *ens.Service,
stickers *stickers.Service,
) *Service {
cryptoOnRampManager := NewCryptoOnRampManager(&CryptoOnRampOptions{
dataSourceType: DataSourceStatic,
})
tokenManager := NewTokenManager(db, rpcClient, rpcClient.NetworkManager)
tokenManager := token.NewTokenManager(db, rpcClient, rpcClient.NetworkManager)
savedAddressesManager := &SavedAddressesManager{db: db}
transactionManager := &TransactionManager{db: db, transactor: transactor, gethManager: gethManager, config: config, accountsDB: accountsDB}
transferController := transfer.NewTransferController(db, rpcClient, accountFeed)
@ -47,6 +52,9 @@ func NewService(
openseaAPIKey: openseaAPIKey,
feesManager: &FeeManager{rpcClient},
gethManager: gethManager,
transactor: transactor,
ens: ens,
stickers: stickers,
}
}
@ -56,7 +64,7 @@ type Service struct {
accountsDB *accounts.Database
rpcClient *rpc.Client
savedAddressesManager *SavedAddressesManager
tokenManager *TokenManager
tokenManager *token.TokenManager
transactionManager *TransactionManager
cryptoOnRampManager *CryptoOnRampManager
transferController *transfer.Controller
@ -64,6 +72,9 @@ type Service struct {
started bool
openseaAPIKey string
gethManager *account.GethManager
transactor *transactions.Transactor
ens *ens.Service
stickers *stickers.Service
}
// Start signals transmitter.

View File

@ -1,4 +1,4 @@
package wallet
package token
import (
"context"
@ -14,6 +14,7 @@ import (
"github.com/ethereum/go-ethereum/log"
"github.com/status-im/status-go/contracts"
"github.com/status-im/status-go/contracts/ierc20"
"github.com/status-im/status-go/params"
"github.com/status-im/status-go/rpc"
"github.com/status-im/status-go/rpc/network"
"github.com/status-im/status-go/services/wallet/async"
@ -35,6 +36,10 @@ type Token struct {
ChainID uint64 `json:"chainId"`
}
func (t *Token) IsNative() bool {
return t.Address == nativeChainAddress
}
type TokenManager struct {
db *sql.DB
RPCClient *rpc.Client
@ -70,7 +75,34 @@ func NewTokenManager(
return tokenManager
}
func (tm *TokenManager) findSNT(chainID uint64) *Token {
func (tm *TokenManager) FindToken(network *params.Network, tokenSymbol string) *Token {
if tokenSymbol == network.NativeCurrencySymbol {
return &Token{
Address: common.HexToAddress("0x"),
Symbol: network.NativeCurrencySymbol,
Decimals: uint(network.NativeCurrencyDecimals),
Name: network.NativeCurrencyName,
}
}
tokens, err := tm.GetTokens(network.ChainID)
if err != nil {
return nil
}
customTokens, err := tm.GetCustomsByChainID(network.ChainID)
if err != nil {
return nil
}
allTokens := append(tokens, customTokens...)
for _, token := range allTokens {
if token.Symbol == tokenSymbol {
return token
}
}
return nil
}
func (tm *TokenManager) FindSNT(chainID uint64) *Token {
tokensMap, ok := tokenStore[chainID]
if !ok {
return nil
@ -85,7 +117,7 @@ func (tm *TokenManager) findSNT(chainID uint64) *Token {
return nil
}
func (tm *TokenManager) getTokens(chainID uint64) ([]*Token, error) {
func (tm *TokenManager) GetTokens(chainID uint64) ([]*Token, error) {
tokensMap, ok := tokenStore[chainID]
if !ok {
return nil, errors.New("no tokens for this network")
@ -100,7 +132,7 @@ func (tm *TokenManager) getTokens(chainID uint64) ([]*Token, error) {
return res, nil
}
func (tm *TokenManager) discoverToken(ctx context.Context, chainID uint64, address common.Address) (*Token, error) {
func (tm *TokenManager) DiscoverToken(ctx context.Context, chainID uint64, address common.Address) (*Token, error) {
backend, err := tm.RPCClient.EthClient(chainID)
if err != nil {
return nil, err
@ -139,7 +171,7 @@ func (tm *TokenManager) discoverToken(ctx context.Context, chainID uint64, addre
}, nil
}
func (tm *TokenManager) getCustoms() ([]*Token, error) {
func (tm *TokenManager) GetCustoms() ([]*Token, error) {
rows, err := tm.db.Query("SELECT address, name, symbol, decimals, color, network_id FROM tokens")
if err != nil {
return nil, err
@ -160,7 +192,7 @@ func (tm *TokenManager) getCustoms() ([]*Token, error) {
return rst, nil
}
func (tm *TokenManager) getCustomsByChainID(chainID uint64) ([]*Token, error) {
func (tm *TokenManager) GetCustomsByChainID(chainID uint64) ([]*Token, error) {
rows, err := tm.db.Query("SELECT address, name, symbol, decimals, color, network_id FROM tokens where network_id=?", chainID)
if err != nil {
return nil, err
@ -181,7 +213,7 @@ func (tm *TokenManager) getCustomsByChainID(chainID uint64) ([]*Token, error) {
return rst, nil
}
func (tm *TokenManager) isTokenVisible(chainID uint64, address common.Address) (bool, error) {
func (tm *TokenManager) IsTokenVisible(chainID uint64, address common.Address) (bool, error) {
rows, err := tm.db.Query("SELECT chain_id, address FROM visible_tokens WHERE chain_id = ? AND address = ?", chainID, address)
if err != nil {
return false, err
@ -191,8 +223,8 @@ func (tm *TokenManager) isTokenVisible(chainID uint64, address common.Address) (
return rows.Next(), nil
}
func (tm *TokenManager) toggle(chainID uint64, address common.Address) error {
isVisible, err := tm.isTokenVisible(chainID, address)
func (tm *TokenManager) Toggle(chainID uint64, address common.Address) error {
isVisible, err := tm.IsTokenVisible(chainID, address)
if err != nil {
return err
}
@ -212,8 +244,8 @@ func (tm *TokenManager) toggle(chainID uint64, address common.Address) error {
return err
}
func (tm *TokenManager) getVisible(chainIDs []uint64) (map[uint64][]*Token, error) {
customTokens, err := tm.getCustoms()
func (tm *TokenManager) GetVisible(chainIDs []uint64) (map[uint64][]*Token, error) {
customTokens, err := tm.GetCustoms()
if err != nil {
return nil, err
}
@ -271,7 +303,7 @@ func (tm *TokenManager) getVisible(chainIDs []uint64) (map[uint64][]*Token, erro
for _, chainID := range chainIDs {
if len(rst[chainID]) == 1 {
token := tm.findSNT(chainID)
token := tm.FindSNT(chainID)
if token != nil {
rst[chainID] = append(rst[chainID], token)
}
@ -280,7 +312,7 @@ func (tm *TokenManager) getVisible(chainIDs []uint64) (map[uint64][]*Token, erro
return rst, nil
}
func (tm *TokenManager) upsertCustom(token Token) error {
func (tm *TokenManager) UpsertCustom(token Token) error {
insert, err := tm.db.Prepare("INSERT OR REPLACE INTO TOKENS (network_id, address, name, symbol, decimals, color) VALUES (?, ?, ?, ?, ?, ?)")
if err != nil {
return err
@ -289,12 +321,12 @@ func (tm *TokenManager) upsertCustom(token Token) error {
return err
}
func (tm *TokenManager) deleteCustom(chainID uint64, address common.Address) error {
func (tm *TokenManager) DeleteCustom(chainID uint64, address common.Address) error {
_, err := tm.db.Exec(`DELETE FROM TOKENS WHERE address = ? and network_id = ?`, address, chainID)
return err
}
func (tm *TokenManager) getTokenBalance(ctx context.Context, client *chain.Client, account common.Address, token common.Address) (*big.Int, error) {
func (tm *TokenManager) GetTokenBalance(ctx context.Context, client *chain.Client, account common.Address, token common.Address) (*big.Int, error) {
caller, err := ierc20.NewIERC20Caller(token, client)
if err != nil {
return nil, err
@ -305,19 +337,19 @@ func (tm *TokenManager) getTokenBalance(ctx context.Context, client *chain.Clien
}, account)
}
func (tm *TokenManager) getChainBalance(ctx context.Context, client *chain.Client, account common.Address) (*big.Int, error) {
func (tm *TokenManager) GetChainBalance(ctx context.Context, client *chain.Client, account common.Address) (*big.Int, error) {
return client.BalanceAt(ctx, account, nil)
}
func (tm *TokenManager) getBalance(ctx context.Context, client *chain.Client, account common.Address, token common.Address) (*big.Int, error) {
func (tm *TokenManager) GetBalance(ctx context.Context, client *chain.Client, account common.Address, token common.Address) (*big.Int, error) {
if token == nativeChainAddress {
return tm.getChainBalance(ctx, client, account)
return tm.GetChainBalance(ctx, client, account)
}
return tm.getTokenBalance(ctx, client, account, token)
return tm.GetTokenBalance(ctx, client, account, token)
}
func (tm *TokenManager) getBalances(parent context.Context, clients []*chain.Client, accounts, tokens []common.Address) (map[common.Address]map[common.Address]*hexutil.Big, error) {
func (tm *TokenManager) GetBalances(parent context.Context, clients []*chain.Client, accounts, tokens []common.Address) (map[common.Address]map[common.Address]*hexutil.Big, error) {
var (
group = async.NewAtomicGroup(parent)
mu sync.Mutex
@ -421,7 +453,7 @@ func (tm *TokenManager) getBalances(parent context.Context, clients []*chain.Cli
group.Add(func(parent context.Context) error {
ctx, cancel := context.WithTimeout(parent, requestTimeout)
defer cancel()
balance, err := tm.getBalance(ctx, client, account, token)
balance, err := tm.GetBalance(ctx, client, account, token)
if err != nil {
log.Error("can't fetch erc20 token balance", "account", account, "token", token, "error", err)

View File

@ -1,4 +1,4 @@
package wallet
package token
import (
"io/ioutil"

View File

@ -1,4 +1,4 @@
package wallet
package token
import "github.com/ethereum/go-ethereum/common"
@ -1450,6 +1450,14 @@ var tokenStore = map[uint64]map[common.Address]*Token{
Decimals: 18,
ChainID: 5,
},
common.HexToAddress("0x98339d8c260052b7ad81c28c16c0b98420f2b46a"): &Token{
Address: common.HexToAddress("0x98339d8c260052b7ad81c28c16c0b98420f2b46a"),
Name: "USD Coin",
Symbol: "USDC",
Color: "#f8f8f8",
Decimals: 6,
ChainID: 5,
},
common.HexToAddress("0x022e292b44b5a146f2e8ee36ff44d3dd863c915c"): &Token{
Address: common.HexToAddress("0x022e292b44b5a146f2e8ee36ff44d3dd863c915c"),
Name: "Xeenus 💪",
@ -1475,6 +1483,16 @@ var tokenStore = map[uint64]map[common.Address]*Token{
ChainID: 5,
},
},
10: {
common.HexToAddress("0x7f5c764cbc14f9669b88837ca1490cca17c31607"): &Token{
Address: common.HexToAddress("0x7f5c764cbc14f9669b88837ca1490cca17c31607"),
Name: "USD Coin",
Symbol: "USDC",
Color: "#f8f8f8",
Decimals: 6,
ChainID: 10,
},
},
100: {
common.HexToAddress("0x3e50bf6703fc132a94e4baff068db2055655f11b"): &Token{
Address: common.HexToAddress("0x3e50bf6703fc132a94e4baff068db2055655f11b"),
@ -1485,6 +1503,16 @@ var tokenStore = map[uint64]map[common.Address]*Token{
ChainID: 100,
},
},
420: {
common.HexToAddress("0xcb4ceefce514b2d910d3ac529076d18e3add3775"): &Token{
Address: common.HexToAddress("0xcb4ceefce514b2d910d3ac529076d18e3add3775"),
Name: "USD Coin",
Symbol: "USDC",
Color: "#f8f8f8",
Decimals: 6,
ChainID: 420,
},
},
42161: {
common.HexToAddress("0xfd086bc7cd5c481dcc9c85ebe478a1c0b69fcbb9"): &Token{
Address: common.HexToAddress("0xfd086bc7cd5c481dcc9c85ebe478a1c0b69fcbb9"),
@ -1499,7 +1527,7 @@ var tokenStore = map[uint64]map[common.Address]*Token{
Name: "USD Coin",
Symbol: "USDC",
Color: "#f8f8f8",
Decimals: 18,
Decimals: 6,
ChainID: 42161,
},
common.HexToAddress("0xda10009cbd5d07dd0cecc66161fc93d7c9000da1"): &Token{
@ -1575,14 +1603,14 @@ var tokenStore = map[uint64]map[common.Address]*Token{
ChainID: 42161,
},
},
421611: {
common.HexToAddress("0x615fbe6372676474d9e6933d310469c9b68e9726"): &Token{
Address: common.HexToAddress("0x615fbe6372676474d9e6933d310469c9b68e9726"),
Name: "ChainLink Token",
Symbol: "Link",
421613: {
common.HexToAddress("0x17078F231AA8dc256557b49a8f2F72814A71f633"): &Token{
Address: common.HexToAddress("0x17078F231AA8dc256557b49a8f2F72814A71f633"),
Name: "USD Coin",
Symbol: "USDC",
Color: "#f8f8f8",
Decimals: 18,
ChainID: 421611,
Decimals: 6,
ChainID: 421613,
},
},
}

View File

@ -18,6 +18,7 @@ import (
"github.com/status-im/status-go/params"
"github.com/status-im/status-go/services/wallet/async"
"github.com/status-im/status-go/services/wallet/bigint"
"github.com/status-im/status-go/services/wallet/bridge"
"github.com/status-im/status-go/services/wallet/chain"
"github.com/status-im/status-go/transactions"
)
@ -228,7 +229,7 @@ func (tm *TransactionManager) watch(ctx context.Context, transactionHash common.
return watchTxCommand.Command()(commandContext)
}
func (tm *TransactionManager) createMultiTransaction(ctx context.Context, multiTransaction *MultiTransaction, data map[uint64][]transactions.SendTxArgs, password string) (*MultiTransactionResult, error) {
func (tm *TransactionManager) createMultiTransaction(ctx context.Context, multiTransaction *MultiTransaction, data []*bridge.TransactionBridge, bridges map[string]bridge.Bridge, password string) (*MultiTransactionResult, error) {
selectedAccount, err := tm.getVerifiedWalletAccount(multiTransaction.FromAddress.Hex(), password)
if err != nil {
return nil, err
@ -260,29 +261,26 @@ func (tm *TransactionManager) createMultiTransaction(ctx context.Context, multiT
}
hashes := make(map[uint64][]types.Hash)
for chainID, txs := range data {
for _, tx := range txs {
hash, err := tm.transactor.SendTransactionWithChainID(chainID, tx, selectedAccount)
if err != nil {
return nil, err
}
err = tm.addPending(PendingTransaction{
Hash: common.Hash(hash),
Timestamp: uint64(time.Now().Unix()),
Value: bigint.BigInt{tx.Value.ToInt()},
From: common.Address(tx.From),
To: common.Address(*tx.To),
Data: tx.Data.String(),
Type: WalletTransfer,
ChainID: chainID,
MultiTransactionID: multiTransactionID,
})
if err != nil {
return nil, err
}
hashes[chainID] = append(hashes[chainID], hash)
for _, tx := range data {
hash, err := bridges[tx.BridgeName].Send(tx, selectedAccount)
if err != nil {
return nil, err
}
err = tm.addPending(PendingTransaction{
Hash: common.Hash(hash),
Timestamp: uint64(time.Now().Unix()),
Value: bigint.BigInt{tx.Value()},
From: common.Address(tx.From()),
To: common.Address(tx.To()),
Data: tx.Data().String(),
Type: WalletTransfer,
ChainID: tx.ChainID,
MultiTransactionID: multiTransactionID,
})
if err != nil {
return nil, err
}
hashes[tx.ChainID] = append(hashes[tx.ChainID], hash)
}
return &MultiTransactionResult{

View File

@ -0,0 +1,462 @@
package types
import (
"context"
"encoding/binary"
"fmt"
"math/big"
"github.com/ethereum/go-ethereum/common/hexutil"
"github.com/ethereum/go-ethereum/common/math"
"github.com/ethereum/go-ethereum/log"
"github.com/ethereum/go-ethereum/common"
)
type fallbackError struct {
}
var fallbackErrorMsg = "missing trie node 0000000000000000000000000000000000000000000000000000000000000000 (path ) <nil>"
var fallbackErrorCode = -32000
func SetFallbackError(msg string, code int) {
fallbackErrorMsg = msg
fallbackErrorCode = code
log.Debug("setting fallback error", "msg", msg, "code", code)
}
func (f fallbackError) ErrorCode() int { return fallbackErrorCode }
func (f fallbackError) Error() string { return fallbackErrorMsg }
var ErrUseFallback = fallbackError{}
type FallbackClient interface {
CallContext(ctx context.Context, result interface{}, method string, args ...interface{}) error
}
var bigZero = big.NewInt(0)
func (tx *LegacyTx) isFake() bool { return false }
func (tx *AccessListTx) isFake() bool { return false }
func (tx *DynamicFeeTx) isFake() bool { return false }
type ArbitrumUnsignedTx struct {
ChainId *big.Int
From common.Address
Nonce uint64 // nonce of sender account
GasFeeCap *big.Int // wei per gas
Gas uint64 // gas limit
To *common.Address `rlp:"nil"` // nil means contract creation
Value *big.Int // wei amount
Data []byte // contract invocation input data
}
func (tx *ArbitrumUnsignedTx) txType() byte { return ArbitrumUnsignedTxType }
func (tx *ArbitrumUnsignedTx) copy() TxData {
cpy := &ArbitrumUnsignedTx{
ChainId: new(big.Int),
Nonce: tx.Nonce,
GasFeeCap: new(big.Int),
Gas: tx.Gas,
From: tx.From,
To: nil,
Value: new(big.Int),
Data: common.CopyBytes(tx.Data),
}
if tx.ChainId != nil {
cpy.ChainId.Set(tx.ChainId)
}
if tx.GasFeeCap != nil {
cpy.GasFeeCap.Set(tx.GasFeeCap)
}
if tx.To != nil {
tmp := *tx.To
cpy.To = &tmp
}
if tx.Value != nil {
cpy.Value.Set(tx.Value)
}
return cpy
}
func (tx *ArbitrumUnsignedTx) chainID() *big.Int { return tx.ChainId }
func (tx *ArbitrumUnsignedTx) accessList() AccessList { return nil }
func (tx *ArbitrumUnsignedTx) data() []byte { return tx.Data }
func (tx *ArbitrumUnsignedTx) gas() uint64 { return tx.Gas }
func (tx *ArbitrumUnsignedTx) gasPrice() *big.Int { return tx.GasFeeCap }
func (tx *ArbitrumUnsignedTx) gasTipCap() *big.Int { return bigZero }
func (tx *ArbitrumUnsignedTx) gasFeeCap() *big.Int { return tx.GasFeeCap }
func (tx *ArbitrumUnsignedTx) value() *big.Int { return tx.Value }
func (tx *ArbitrumUnsignedTx) nonce() uint64 { return tx.Nonce }
func (tx *ArbitrumUnsignedTx) to() *common.Address { return tx.To }
func (tx *ArbitrumUnsignedTx) isFake() bool { return false }
func (tx *ArbitrumUnsignedTx) rawSignatureValues() (v, r, s *big.Int) {
return bigZero, bigZero, bigZero
}
func (tx *ArbitrumUnsignedTx) setSignatureValues(chainID, v, r, s *big.Int) {
}
type ArbitrumContractTx struct {
ChainId *big.Int
RequestId common.Hash
From common.Address
GasFeeCap *big.Int // wei per gas
Gas uint64 // gas limit
To *common.Address `rlp:"nil"` // nil means contract creation
Value *big.Int // wei amount
Data []byte // contract invocation input data
}
func (tx *ArbitrumContractTx) txType() byte { return ArbitrumContractTxType }
func (tx *ArbitrumContractTx) copy() TxData {
cpy := &ArbitrumContractTx{
ChainId: new(big.Int),
RequestId: tx.RequestId,
GasFeeCap: new(big.Int),
Gas: tx.Gas,
From: tx.From,
To: nil,
Value: new(big.Int),
Data: common.CopyBytes(tx.Data),
}
if tx.ChainId != nil {
cpy.ChainId.Set(tx.ChainId)
}
if tx.GasFeeCap != nil {
cpy.GasFeeCap.Set(tx.GasFeeCap)
}
if tx.To != nil {
tmp := *tx.To
cpy.To = &tmp
}
if tx.Value != nil {
cpy.Value.Set(tx.Value)
}
return cpy
}
func (tx *ArbitrumContractTx) chainID() *big.Int { return tx.ChainId }
func (tx *ArbitrumContractTx) accessList() AccessList { return nil }
func (tx *ArbitrumContractTx) data() []byte { return tx.Data }
func (tx *ArbitrumContractTx) gas() uint64 { return tx.Gas }
func (tx *ArbitrumContractTx) gasPrice() *big.Int { return tx.GasFeeCap }
func (tx *ArbitrumContractTx) gasTipCap() *big.Int { return bigZero }
func (tx *ArbitrumContractTx) gasFeeCap() *big.Int { return tx.GasFeeCap }
func (tx *ArbitrumContractTx) value() *big.Int { return tx.Value }
func (tx *ArbitrumContractTx) nonce() uint64 { return 0 }
func (tx *ArbitrumContractTx) to() *common.Address { return tx.To }
func (tx *ArbitrumContractTx) rawSignatureValues() (v, r, s *big.Int) {
return bigZero, bigZero, bigZero
}
func (tx *ArbitrumContractTx) setSignatureValues(chainID, v, r, s *big.Int) {}
func (tx *ArbitrumContractTx) isFake() bool { return true }
type ArbitrumRetryTx struct {
ChainId *big.Int
Nonce uint64
From common.Address
GasFeeCap *big.Int // wei per gas
Gas uint64 // gas limit
To *common.Address `rlp:"nil"` // nil means contract creation
Value *big.Int // wei amount
Data []byte // contract invocation input data
TicketId common.Hash
RefundTo common.Address
MaxRefund *big.Int // the maximum refund sent to RefundTo (the rest goes to From)
SubmissionFeeRefund *big.Int // the submission fee to refund if successful (capped by MaxRefund)
}
func (tx *ArbitrumRetryTx) txType() byte { return ArbitrumRetryTxType }
func (tx *ArbitrumRetryTx) copy() TxData {
cpy := &ArbitrumRetryTx{
ChainId: new(big.Int),
Nonce: tx.Nonce,
GasFeeCap: new(big.Int),
Gas: tx.Gas,
From: tx.From,
To: nil,
Value: new(big.Int),
Data: common.CopyBytes(tx.Data),
TicketId: tx.TicketId,
RefundTo: tx.RefundTo,
MaxRefund: new(big.Int),
SubmissionFeeRefund: new(big.Int),
}
if tx.ChainId != nil {
cpy.ChainId.Set(tx.ChainId)
}
if tx.GasFeeCap != nil {
cpy.GasFeeCap.Set(tx.GasFeeCap)
}
if tx.To != nil {
tmp := *tx.To
cpy.To = &tmp
}
if tx.Value != nil {
cpy.Value.Set(tx.Value)
}
if tx.MaxRefund != nil {
cpy.MaxRefund.Set(tx.MaxRefund)
}
if tx.SubmissionFeeRefund != nil {
cpy.SubmissionFeeRefund.Set(tx.SubmissionFeeRefund)
}
return cpy
}
func (tx *ArbitrumRetryTx) chainID() *big.Int { return tx.ChainId }
func (tx *ArbitrumRetryTx) accessList() AccessList { return nil }
func (tx *ArbitrumRetryTx) data() []byte { return tx.Data }
func (tx *ArbitrumRetryTx) gas() uint64 { return tx.Gas }
func (tx *ArbitrumRetryTx) gasPrice() *big.Int { return tx.GasFeeCap }
func (tx *ArbitrumRetryTx) gasTipCap() *big.Int { return bigZero }
func (tx *ArbitrumRetryTx) gasFeeCap() *big.Int { return tx.GasFeeCap }
func (tx *ArbitrumRetryTx) value() *big.Int { return tx.Value }
func (tx *ArbitrumRetryTx) nonce() uint64 { return tx.Nonce }
func (tx *ArbitrumRetryTx) to() *common.Address { return tx.To }
func (tx *ArbitrumRetryTx) rawSignatureValues() (v, r, s *big.Int) {
return bigZero, bigZero, bigZero
}
func (tx *ArbitrumRetryTx) setSignatureValues(chainID, v, r, s *big.Int) {}
func (tx *ArbitrumRetryTx) isFake() bool { return true }
type ArbitrumSubmitRetryableTx struct {
ChainId *big.Int
RequestId common.Hash
From common.Address
L1BaseFee *big.Int
DepositValue *big.Int
GasFeeCap *big.Int // wei per gas
Gas uint64 // gas limit
RetryTo *common.Address `rlp:"nil"` // nil means contract creation
RetryValue *big.Int // wei amount
Beneficiary common.Address
MaxSubmissionFee *big.Int
FeeRefundAddr common.Address
RetryData []byte // contract invocation input data
}
func (tx *ArbitrumSubmitRetryableTx) txType() byte { return ArbitrumSubmitRetryableTxType }
func (tx *ArbitrumSubmitRetryableTx) copy() TxData {
cpy := &ArbitrumSubmitRetryableTx{
ChainId: new(big.Int),
RequestId: tx.RequestId,
DepositValue: new(big.Int),
L1BaseFee: new(big.Int),
GasFeeCap: new(big.Int),
Gas: tx.Gas,
From: tx.From,
RetryTo: tx.RetryTo,
RetryValue: new(big.Int),
Beneficiary: tx.Beneficiary,
MaxSubmissionFee: new(big.Int),
FeeRefundAddr: tx.FeeRefundAddr,
RetryData: common.CopyBytes(tx.RetryData),
}
if tx.ChainId != nil {
cpy.ChainId.Set(tx.ChainId)
}
if tx.DepositValue != nil {
cpy.DepositValue.Set(tx.DepositValue)
}
if tx.L1BaseFee != nil {
cpy.L1BaseFee.Set(tx.L1BaseFee)
}
if tx.GasFeeCap != nil {
cpy.GasFeeCap.Set(tx.GasFeeCap)
}
if tx.RetryTo != nil {
tmp := *tx.RetryTo
cpy.RetryTo = &tmp
}
if tx.RetryValue != nil {
cpy.RetryValue.Set(tx.RetryValue)
}
if tx.MaxSubmissionFee != nil {
cpy.MaxSubmissionFee.Set(tx.MaxSubmissionFee)
}
return cpy
}
func (tx *ArbitrumSubmitRetryableTx) chainID() *big.Int { return tx.ChainId }
func (tx *ArbitrumSubmitRetryableTx) accessList() AccessList { return nil }
func (tx *ArbitrumSubmitRetryableTx) gas() uint64 { return tx.Gas }
func (tx *ArbitrumSubmitRetryableTx) gasPrice() *big.Int { return tx.GasFeeCap }
func (tx *ArbitrumSubmitRetryableTx) gasTipCap() *big.Int { return big.NewInt(0) }
func (tx *ArbitrumSubmitRetryableTx) gasFeeCap() *big.Int { return tx.GasFeeCap }
func (tx *ArbitrumSubmitRetryableTx) value() *big.Int { return common.Big0 }
func (tx *ArbitrumSubmitRetryableTx) nonce() uint64 { return 0 }
func (tx *ArbitrumSubmitRetryableTx) to() *common.Address { return &ArbRetryableTxAddress }
func (tx *ArbitrumSubmitRetryableTx) rawSignatureValues() (v, r, s *big.Int) {
return bigZero, bigZero, bigZero
}
func (tx *ArbitrumSubmitRetryableTx) setSignatureValues(chainID, v, r, s *big.Int) {}
func (tx *ArbitrumSubmitRetryableTx) isFake() bool { return true }
func (tx *ArbitrumSubmitRetryableTx) data() []byte {
var retryTo common.Address
if tx.RetryTo != nil {
retryTo = *tx.RetryTo
}
data := make([]byte, 0)
data = append(data, tx.RequestId.Bytes()...)
data = append(data, math.U256Bytes(tx.L1BaseFee)...)
data = append(data, math.U256Bytes(tx.DepositValue)...)
data = append(data, math.U256Bytes(tx.RetryValue)...)
data = append(data, math.U256Bytes(tx.GasFeeCap)...)
data = append(data, math.U256Bytes(new(big.Int).SetUint64(tx.Gas))...)
data = append(data, math.U256Bytes(tx.MaxSubmissionFee)...)
data = append(data, make([]byte, 12)...)
data = append(data, tx.FeeRefundAddr.Bytes()...)
data = append(data, make([]byte, 12)...)
data = append(data, tx.Beneficiary.Bytes()...)
data = append(data, make([]byte, 12)...)
data = append(data, retryTo.Bytes()...)
offset := len(data) + 32
data = append(data, math.U256Bytes(big.NewInt(int64(offset)))...)
data = append(data, math.U256Bytes(big.NewInt(int64(len(tx.RetryData))))...)
data = append(data, tx.RetryData...)
extra := len(tx.RetryData) % 32
if extra > 0 {
data = append(data, make([]byte, 32-extra)...)
}
data = append(hexutil.MustDecode("0xc9f95d32"), data...)
return data
}
type ArbitrumDepositTx struct {
ChainId *big.Int
L1RequestId common.Hash
From common.Address
To common.Address
Value *big.Int
}
func (d *ArbitrumDepositTx) txType() byte {
return ArbitrumDepositTxType
}
func (d *ArbitrumDepositTx) copy() TxData {
tx := &ArbitrumDepositTx{
ChainId: new(big.Int),
L1RequestId: d.L1RequestId,
From: d.From,
To: d.To,
Value: new(big.Int),
}
if d.ChainId != nil {
tx.ChainId.Set(d.ChainId)
}
if d.Value != nil {
tx.Value.Set(d.Value)
}
return tx
}
func (d *ArbitrumDepositTx) chainID() *big.Int { return d.ChainId }
func (d *ArbitrumDepositTx) accessList() AccessList { return nil }
func (d *ArbitrumDepositTx) data() []byte { return nil }
func (d *ArbitrumDepositTx) gas() uint64 { return 0 }
func (d *ArbitrumDepositTx) gasPrice() *big.Int { return bigZero }
func (d *ArbitrumDepositTx) gasTipCap() *big.Int { return bigZero }
func (d *ArbitrumDepositTx) gasFeeCap() *big.Int { return bigZero }
func (d *ArbitrumDepositTx) value() *big.Int { return d.Value }
func (d *ArbitrumDepositTx) nonce() uint64 { return 0 }
func (d *ArbitrumDepositTx) to() *common.Address { return &d.To }
func (d *ArbitrumDepositTx) isFake() bool { return true }
func (d *ArbitrumDepositTx) rawSignatureValues() (v, r, s *big.Int) {
return bigZero, bigZero, bigZero
}
func (d *ArbitrumDepositTx) setSignatureValues(chainID, v, r, s *big.Int) {
}
type ArbitrumInternalTx struct {
ChainId *big.Int
Data []byte
}
func (t *ArbitrumInternalTx) txType() byte {
return ArbitrumInternalTxType
}
func (t *ArbitrumInternalTx) copy() TxData {
return &ArbitrumInternalTx{
new(big.Int).Set(t.ChainId),
common.CopyBytes(t.Data),
}
}
func (t *ArbitrumInternalTx) chainID() *big.Int { return t.ChainId }
func (t *ArbitrumInternalTx) accessList() AccessList { return nil }
func (t *ArbitrumInternalTx) data() []byte { return t.Data }
func (t *ArbitrumInternalTx) gas() uint64 { return 0 }
func (t *ArbitrumInternalTx) gasPrice() *big.Int { return bigZero }
func (t *ArbitrumInternalTx) gasTipCap() *big.Int { return bigZero }
func (t *ArbitrumInternalTx) gasFeeCap() *big.Int { return bigZero }
func (t *ArbitrumInternalTx) value() *big.Int { return common.Big0 }
func (t *ArbitrumInternalTx) nonce() uint64 { return 0 }
func (t *ArbitrumInternalTx) to() *common.Address { return &ArbosAddress }
func (t *ArbitrumInternalTx) isFake() bool { return true }
func (d *ArbitrumInternalTx) rawSignatureValues() (v, r, s *big.Int) {
return bigZero, bigZero, bigZero
}
func (d *ArbitrumInternalTx) setSignatureValues(chainID, v, r, s *big.Int) {
}
type HeaderInfo struct {
SendRoot common.Hash
SendCount uint64
L1BlockNumber uint64
ArbOSFormatVersion uint64
}
func (info HeaderInfo) extra() []byte {
return info.SendRoot[:]
}
func (info HeaderInfo) mixDigest() [32]byte {
mixDigest := common.Hash{}
binary.BigEndian.PutUint64(mixDigest[:8], info.SendCount)
binary.BigEndian.PutUint64(mixDigest[8:16], info.L1BlockNumber)
binary.BigEndian.PutUint64(mixDigest[16:24], info.ArbOSFormatVersion)
return mixDigest
}
func (info HeaderInfo) UpdateHeaderWithInfo(header *Header) {
header.MixDigest = info.mixDigest()
header.Extra = info.extra()
}
func DeserializeHeaderExtraInformation(header *Header) (HeaderInfo, error) {
if header.BaseFee == nil || header.BaseFee.Sign() == 0 || len(header.Extra) == 0 {
// imported blocks have no base fee
// The genesis block doesn't have an ArbOS encoded extra field
return HeaderInfo{}, nil
}
if len(header.Extra) != 32 {
return HeaderInfo{}, fmt.Errorf("unexpected header extra field length %v", len(header.Extra))
}
extra := HeaderInfo{}
copy(extra.SendRoot[:], header.Extra)
extra.SendCount = binary.BigEndian.Uint64(header.MixDigest[:8])
extra.L1BlockNumber = binary.BigEndian.Uint64(header.MixDigest[8:16])
extra.ArbOSFormatVersion = binary.BigEndian.Uint64(header.MixDigest[16:24])
return extra, nil
}

View File

@ -0,0 +1,54 @@
package types
import (
"bytes"
"errors"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/rlp"
)
type ArbitrumLegacyTxData struct {
LegacyTx
HashOverride common.Hash // Hash cannot be locally computed from other fields
EffectiveGasPrice uint64
L1BlockNumber uint64
Sender *common.Address `rlp:"optional,nil"` // only used in unsigned Txs
}
func NewArbitrumLegacyTx(origTx *Transaction, hashOverride common.Hash, effectiveGas uint64, l1Block uint64, senderOverride *common.Address) (*Transaction, error) {
if origTx.Type() != LegacyTxType {
return nil, errors.New("attempt to arbitrum-wrap non-legacy transaction")
}
legacyPtr := origTx.GetInner().(*LegacyTx)
inner := ArbitrumLegacyTxData{
LegacyTx: *legacyPtr,
HashOverride: hashOverride,
EffectiveGasPrice: effectiveGas,
L1BlockNumber: l1Block,
Sender: senderOverride,
}
return NewTx(&inner), nil
}
func (tx *ArbitrumLegacyTxData) copy() TxData {
legacyCopy := tx.LegacyTx.copy().(*LegacyTx)
var sender *common.Address
if tx.Sender != nil {
sender = new(common.Address)
*sender = *tx.Sender
}
return &ArbitrumLegacyTxData{
LegacyTx: *legacyCopy,
HashOverride: tx.HashOverride,
EffectiveGasPrice: tx.EffectiveGasPrice,
L1BlockNumber: tx.L1BlockNumber,
Sender: sender,
}
}
func (tx *ArbitrumLegacyTxData) txType() byte { return ArbitrumLegacyTxType }
func (tx *ArbitrumLegacyTxData) EncodeOnlyLegacyInto(w *bytes.Buffer) {
rlp.Encode(w, tx.LegacyTx)
}

View File

@ -0,0 +1,84 @@
package types
import (
"math/big"
"github.com/ethereum/go-ethereum/common"
)
var ArbosAddress = common.HexToAddress("0xa4b05")
var ArbSysAddress = common.HexToAddress("0x64")
var ArbGasInfoAddress = common.HexToAddress("0x6c")
var ArbRetryableTxAddress = common.HexToAddress("0x6e")
var NodeInterfaceAddress = common.HexToAddress("0xc8")
var NodeInterfaceDebugAddress = common.HexToAddress("0xc9")
type arbitrumSigner struct{ Signer }
func NewArbitrumSigner(signer Signer) Signer {
return arbitrumSigner{Signer: signer}
}
func (s arbitrumSigner) Sender(tx *Transaction) (common.Address, error) {
switch inner := tx.inner.(type) {
case *ArbitrumUnsignedTx:
return inner.From, nil
case *ArbitrumContractTx:
return inner.From, nil
case *ArbitrumDepositTx:
return inner.From, nil
case *ArbitrumInternalTx:
return ArbosAddress, nil
case *ArbitrumRetryTx:
return inner.From, nil
case *ArbitrumSubmitRetryableTx:
return inner.From, nil
case *ArbitrumLegacyTxData:
legacyData := tx.inner.(*ArbitrumLegacyTxData)
if legacyData.Sender != nil {
return *legacyData.Sender, nil
}
fakeTx := NewTx(&legacyData.LegacyTx)
return s.Signer.Sender(fakeTx)
default:
return s.Signer.Sender(tx)
}
}
func (s arbitrumSigner) Equal(s2 Signer) bool {
x, ok := s2.(arbitrumSigner)
return ok && x.Signer.Equal(s.Signer)
}
func (s arbitrumSigner) SignatureValues(tx *Transaction, sig []byte) (R, S, V *big.Int, err error) {
switch tx.inner.(type) {
case *ArbitrumUnsignedTx:
return bigZero, bigZero, bigZero, nil
case *ArbitrumContractTx:
return bigZero, bigZero, bigZero, nil
case *ArbitrumDepositTx:
return bigZero, bigZero, bigZero, nil
case *ArbitrumInternalTx:
return bigZero, bigZero, bigZero, nil
case *ArbitrumRetryTx:
return bigZero, bigZero, bigZero, nil
case *ArbitrumSubmitRetryableTx:
return bigZero, bigZero, bigZero, nil
case *ArbitrumLegacyTxData:
legacyData := tx.inner.(*ArbitrumLegacyTxData)
fakeTx := NewTx(&legacyData.LegacyTx)
return s.Signer.SignatureValues(fakeTx, sig)
default:
return s.Signer.SignatureValues(tx, sig)
}
}
// Hash returns the hash to be signed by the sender.
// It does not uniquely identify the transaction.
func (s arbitrumSigner) Hash(tx *Transaction) common.Hash {
if legacyData, isArbLegacy := tx.inner.(*ArbitrumLegacyTxData); isArbLegacy {
fakeTx := NewTx(&legacyData.LegacyTx)
return s.Signer.Hash(fakeTx)
}
return s.Signer.Hash(tx)
}

View File

@ -37,7 +37,7 @@ var (
ErrInvalidTxType = errors.New("transaction type not valid in this context")
ErrTxTypeNotSupported = errors.New("transaction type not supported")
ErrGasFeeCapTooLow = errors.New("fee cap less than base fee")
errEmptyTypedTx = errors.New("empty typed transaction bytes")
errShortTypedTx = errors.New("typed transaction too short")
)
// Transaction types.
@ -45,6 +45,13 @@ const (
LegacyTxType = iota
AccessListTxType
DynamicFeeTxType
ArbitrumDepositTxType = 100
ArbitrumUnsignedTxType = 101
ArbitrumContractTxType = 102
ArbitrumRetryTxType = 104
ArbitrumSubmitRetryableTxType = 105
ArbitrumInternalTxType = 106
ArbitrumLegacyTxType = 120
)
// Transaction is an Ethereum transaction.
@ -52,6 +59,9 @@ type Transaction struct {
inner TxData // Consensus contents of a transaction
time time.Time // Time first seen locally (spam avoidance)
// Arbitrum cache: must be atomically accessed
CalldataUnits uint64
// caches
hash atomic.Value
size atomic.Value
@ -85,6 +95,8 @@ type TxData interface {
rawSignatureValues() (v, r, s *big.Int)
setSignatureValues(chainID, v, r, s *big.Int)
isFake() bool
}
// EncodeRLP implements rlp.Encoder
@ -134,19 +146,17 @@ func (tx *Transaction) DecodeRLP(s *rlp.Stream) error {
tx.setDecoded(&inner, int(rlp.ListSize(size)))
}
return err
case kind == rlp.String:
default:
// It's an EIP-2718 typed TX envelope.
var b []byte
if b, err = s.Bytes(); err != nil {
return err
}
inner, err := tx.decodeTyped(b)
inner, err := tx.decodeTyped(b, true)
if err == nil {
tx.setDecoded(inner, len(b))
}
return err
default:
return rlp.ErrExpectedList
}
}
@ -164,7 +174,7 @@ func (tx *Transaction) UnmarshalBinary(b []byte) error {
return nil
}
// It's an EIP2718 typed transaction envelope.
inner, err := tx.decodeTyped(b)
inner, err := tx.decodeTyped(b, false)
if err != nil {
return err
}
@ -173,9 +183,37 @@ func (tx *Transaction) UnmarshalBinary(b []byte) error {
}
// decodeTyped decodes a typed transaction from the canonical format.
func (tx *Transaction) decodeTyped(b []byte) (TxData, error) {
if len(b) == 0 {
return nil, errEmptyTypedTx
func (tx *Transaction) decodeTyped(b []byte, arbParsing bool) (TxData, error) {
if len(b) <= 1 {
return nil, errShortTypedTx
}
if arbParsing {
switch b[0] {
case ArbitrumDepositTxType:
var inner ArbitrumDepositTx
err := rlp.DecodeBytes(b[1:], &inner)
return &inner, err
case ArbitrumInternalTxType:
var inner ArbitrumInternalTx
err := rlp.DecodeBytes(b[1:], &inner)
return &inner, err
case ArbitrumUnsignedTxType:
var inner ArbitrumUnsignedTx
err := rlp.DecodeBytes(b[1:], &inner)
return &inner, err
case ArbitrumContractTxType:
var inner ArbitrumContractTx
err := rlp.DecodeBytes(b[1:], &inner)
return &inner, err
case ArbitrumRetryTxType:
var inner ArbitrumRetryTx
err := rlp.DecodeBytes(b[1:], &inner)
return &inner, err
case ArbitrumSubmitRetryableTxType:
var inner ArbitrumSubmitRetryableTx
err := rlp.DecodeBytes(b[1:], &inner)
return &inner, err
}
}
switch b[0] {
case AccessListTxType:
@ -186,6 +224,10 @@ func (tx *Transaction) decodeTyped(b []byte) (TxData, error) {
var inner DynamicFeeTx
err := rlp.DecodeBytes(b[1:], &inner)
return &inner, err
case ArbitrumLegacyTxType:
var inner ArbitrumLegacyTxData
err := rlp.DecodeBytes(b[1:], &inner)
return &inner, err
default:
return nil, ErrTxTypeNotSupported
}
@ -250,6 +292,10 @@ func (tx *Transaction) Type() uint8 {
return tx.inner.txType()
}
func (tx *Transaction) GetInner() TxData {
return tx.inner.copy()
}
// ChainId returns the EIP155 chain ID of the transaction. The return value will always be
// non-nil. For legacy transactions which are not replay-protected, the return value is
// zero.
@ -284,13 +330,7 @@ func (tx *Transaction) Nonce() uint64 { return tx.inner.nonce() }
// To returns the recipient address of the transaction.
// For contract-creation transactions, To returns nil.
func (tx *Transaction) To() *common.Address {
// Copy the pointed-to address.
ito := tx.inner.to()
if ito == nil {
return nil
}
cpy := *ito
return &cpy
return copyAddressPtr(tx.inner.to())
}
// Cost returns gas * gasPrice + value.
@ -373,6 +413,8 @@ func (tx *Transaction) Hash() common.Hash {
var h common.Hash
if tx.Type() == LegacyTxType {
h = rlpHash(tx.inner)
} else if tx.Type() == ArbitrumLegacyTxType {
h = tx.inner.(*ArbitrumLegacyTxData).HashOverride
} else {
h = prefixedRlpHash(tx.Type(), tx.inner)
}
@ -417,6 +459,9 @@ func (s Transactions) EncodeIndex(i int, w *bytes.Buffer) {
tx := s[i]
if tx.Type() == LegacyTxType {
rlp.Encode(w, tx.inner)
} else if tx.Type() == ArbitrumLegacyTxType {
arbData := tx.inner.(*ArbitrumLegacyTxData)
arbData.EncodeOnlyLegacyInto(w)
} else {
tx.encodeTyped(w)
}
@ -440,6 +485,24 @@ func TxDifference(a, b Transactions) Transactions {
return keep
}
// HashDifference returns a new set which is the difference between a and b.
func HashDifference(a, b []common.Hash) []common.Hash {
keep := make([]common.Hash, 0, len(a))
remove := make(map[common.Hash]struct{})
for _, hash := range b {
remove[hash] = struct{}{}
}
for _, hash := range a {
if _, ok := remove[hash]; !ok {
keep = append(keep, hash)
}
}
return keep
}
// TxByNonce implements the sort interface to allow sorting a list of transactions
// by their nonces. This is usually only useful for sorting transactions from a
// single account, otherwise a nonce comparison doesn't make much sense.
@ -569,6 +632,9 @@ func (t *TransactionsByPriceAndNonce) Pop() {
//
// NOTE: In a future PR this will be removed.
type Message struct {
tx *Transaction
TxRunMode MessageRunMode
to *common.Address
from common.Address
nonce uint64
@ -580,8 +646,17 @@ type Message struct {
data []byte
accessList AccessList
checkNonce bool
isFake bool
}
type MessageRunMode uint8
const (
MessageCommitMode MessageRunMode = iota
MessageGasEstimationMode
MessageEthcallMode
)
func NewMessage(from common.Address, to *common.Address, nonce uint64, amount *big.Int, gasLimit uint64, gasPrice, gasFeeCap, gasTipCap *big.Int, data []byte, accessList AccessList, checkNonce bool) Message {
return Message{
from: from,
@ -595,12 +670,15 @@ func NewMessage(from common.Address, to *common.Address, nonce uint64, amount *b
data: data,
accessList: accessList,
checkNonce: checkNonce,
isFake: false,
}
}
// AsMessage returns the transaction as a core.Message.
func (tx *Transaction) AsMessage(s Signer, baseFee *big.Int) (Message, error) {
msg := Message{
tx: tx,
nonce: tx.Nonce(),
gasLimit: tx.Gas(),
gasPrice: new(big.Int).Set(tx.GasPrice()),
@ -611,6 +689,7 @@ func (tx *Transaction) AsMessage(s Signer, baseFee *big.Int) (Message, error) {
data: tx.Data(),
accessList: tx.AccessList(),
checkNonce: true,
isFake: tx.inner.isFake(),
}
// If baseFee provided, set gasPrice to effectiveGasPrice.
if baseFee != nil {
@ -621,6 +700,9 @@ func (tx *Transaction) AsMessage(s Signer, baseFee *big.Int) (Message, error) {
return msg, err
}
func (m Message) UnderlyingTransaction() *Transaction { return m.tx }
func (m Message) RunMode() MessageRunMode { return m.TxRunMode }
func (m Message) From() common.Address { return m.from }
func (m Message) To() *common.Address { return m.to }
func (m Message) GasPrice() *big.Int { return m.gasPrice }
@ -632,3 +714,13 @@ func (m Message) Nonce() uint64 { return m.nonce }
func (m Message) Data() []byte { return m.data }
func (m Message) AccessList() AccessList { return m.accessList }
func (m Message) CheckNonce() bool { return m.checkNonce }
func (m Message) IsFake() bool { return m.isFake }
// copyAddressPtr copies an address.
func copyAddressPtr(a *common.Address) *common.Address {
if a == nil {
return nil
}
cpy := *a
return &cpy
}

View File

@ -46,7 +46,24 @@ type txJSON struct {
ChainID *hexutil.Big `json:"chainId,omitempty"`
AccessList *AccessList `json:"accessList,omitempty"`
// Only used for encoding:
// Arbitrum fields:
From *common.Address `json:"from,omitempty"` // Contract SubmitRetryable Unsigned Retry
RequestId *common.Hash `json:"requestId,omitempty"` // Contract SubmitRetryable Deposit
TicketId *common.Hash `json:"ticketId,omitempty"` // Retry
MaxRefund *hexutil.Big `json:"maxRefund,omitempty"` // Retry
SubmissionFeeRefund *hexutil.Big `json:"submissionFeeRefund,omitempty"` // Retry
RefundTo *common.Address `json:"refundTo,omitempty"` // SubmitRetryable Retry
L1BaseFee *hexutil.Big `json:"l1BaseFee,omitempty"` // SubmitRetryable
DepositValue *hexutil.Big `json:"depositValue,omitempty"` // SubmitRetryable
RetryTo *common.Address `json:"retryTo,omitempty"` // SubmitRetryable
RetryValue *hexutil.Big `json:"retryValue,omitempty"` // SubmitRetryable
RetryData *hexutil.Bytes `json:"retryData,omitempty"` // SubmitRetryable
Beneficiary *common.Address `json:"beneficiary,omitempty"` // SubmitRetryable
MaxSubmissionFee *hexutil.Big `json:"maxSubmissionFee,omitempty"` // SubmitRetryable
EffectiveGasPrice *hexutil.Uint64 `json:"effectiveGasPrice,omitempty"` // ArbLegacy
L1BlockNumber *hexutil.Uint64 `json:"l1BlockNumber,omitempty"` // ArbLegacy
// Only used for encoding - and for ArbLegacy
Hash common.Hash `json:"hash"`
}
@ -57,6 +74,17 @@ func (t *Transaction) MarshalJSON() ([]byte, error) {
enc.Hash = t.Hash()
enc.Type = hexutil.Uint64(t.Type())
// Arbitrum: set to 0 for compatibility
var zero uint64
enc.Nonce = (*hexutil.Uint64)(&zero)
enc.Gas = (*hexutil.Uint64)(&zero)
enc.GasPrice = (*hexutil.Big)(common.Big0)
enc.Value = (*hexutil.Big)(common.Big0)
enc.Data = (*hexutil.Bytes)(&[]byte{})
enc.V = (*hexutil.Big)(common.Big0)
enc.R = (*hexutil.Big)(common.Big0)
enc.S = (*hexutil.Big)(common.Big0)
// Other fields are set conditionally depending on tx type.
switch tx := t.inner.(type) {
case *LegacyTx:
@ -94,6 +122,76 @@ func (t *Transaction) MarshalJSON() ([]byte, error) {
enc.V = (*hexutil.Big)(tx.V)
enc.R = (*hexutil.Big)(tx.R)
enc.S = (*hexutil.Big)(tx.S)
case *ArbitrumLegacyTxData:
enc.Nonce = (*hexutil.Uint64)(&tx.Nonce)
enc.Gas = (*hexutil.Uint64)(&tx.Gas)
enc.GasPrice = (*hexutil.Big)(tx.GasPrice)
enc.Value = (*hexutil.Big)(tx.Value)
enc.Data = (*hexutil.Bytes)(&tx.Data)
enc.To = t.To()
enc.V = (*hexutil.Big)(tx.V)
enc.R = (*hexutil.Big)(tx.R)
enc.S = (*hexutil.Big)(tx.S)
enc.EffectiveGasPrice = (*hexutil.Uint64)(&tx.EffectiveGasPrice)
enc.L1BlockNumber = (*hexutil.Uint64)(&tx.L1BlockNumber)
enc.From = tx.Sender
case *ArbitrumInternalTx:
enc.ChainID = (*hexutil.Big)(tx.ChainId)
enc.Data = (*hexutil.Bytes)(&tx.Data)
case *ArbitrumDepositTx:
enc.RequestId = &tx.L1RequestId
enc.From = &tx.From
enc.ChainID = (*hexutil.Big)(tx.ChainId)
enc.Value = (*hexutil.Big)(tx.Value)
enc.To = t.To()
case *ArbitrumUnsignedTx:
enc.From = (*common.Address)(&tx.From)
enc.ChainID = (*hexutil.Big)(tx.ChainId)
enc.Nonce = (*hexutil.Uint64)(&tx.Nonce)
enc.Gas = (*hexutil.Uint64)(&tx.Gas)
enc.MaxFeePerGas = (*hexutil.Big)(tx.GasFeeCap)
enc.Value = (*hexutil.Big)(tx.Value)
enc.Data = (*hexutil.Bytes)(&tx.Data)
enc.To = t.To()
case *ArbitrumContractTx:
enc.RequestId = &tx.RequestId
enc.From = (*common.Address)(&tx.From)
enc.ChainID = (*hexutil.Big)(tx.ChainId)
enc.Gas = (*hexutil.Uint64)(&tx.Gas)
enc.MaxFeePerGas = (*hexutil.Big)(tx.GasFeeCap)
enc.Value = (*hexutil.Big)(tx.Value)
enc.Data = (*hexutil.Bytes)(&tx.Data)
enc.To = t.To()
case *ArbitrumRetryTx:
enc.From = (*common.Address)(&tx.From)
enc.TicketId = &tx.TicketId
enc.RefundTo = &tx.RefundTo
enc.ChainID = (*hexutil.Big)(tx.ChainId)
enc.Nonce = (*hexutil.Uint64)(&tx.Nonce)
enc.Gas = (*hexutil.Uint64)(&tx.Gas)
enc.MaxFeePerGas = (*hexutil.Big)(tx.GasFeeCap)
enc.Value = (*hexutil.Big)(tx.Value)
enc.Data = (*hexutil.Bytes)(&tx.Data)
enc.MaxRefund = (*hexutil.Big)(tx.MaxRefund)
enc.SubmissionFeeRefund = (*hexutil.Big)(tx.SubmissionFeeRefund)
enc.To = t.To()
case *ArbitrumSubmitRetryableTx:
enc.RequestId = &tx.RequestId
enc.From = &tx.From
enc.L1BaseFee = (*hexutil.Big)(tx.L1BaseFee)
enc.DepositValue = (*hexutil.Big)(tx.DepositValue)
enc.Beneficiary = &tx.Beneficiary
enc.RefundTo = &tx.FeeRefundAddr
enc.MaxSubmissionFee = (*hexutil.Big)(tx.MaxSubmissionFee)
enc.ChainID = (*hexutil.Big)(tx.ChainId)
enc.Gas = (*hexutil.Uint64)(&tx.Gas)
enc.MaxFeePerGas = (*hexutil.Big)(tx.GasFeeCap)
enc.RetryTo = tx.RetryTo
enc.RetryValue = (*hexutil.Big)(tx.RetryValue)
enc.RetryData = (*hexutil.Bytes)(&tx.RetryData)
data := tx.data()
enc.Data = (*hexutil.Bytes)(&data)
enc.To = t.To()
}
return json.Marshal(&enc)
}
@ -263,6 +361,270 @@ func (t *Transaction) UnmarshalJSON(input []byte) error {
}
}
case ArbitrumLegacyTxType:
var itx LegacyTx
if dec.To != nil {
itx.To = dec.To
}
if dec.Nonce == nil {
return errors.New("missing required field 'nonce' in transaction")
}
itx.Nonce = uint64(*dec.Nonce)
if dec.GasPrice == nil {
return errors.New("missing required field 'gasPrice' in transaction")
}
itx.GasPrice = (*big.Int)(dec.GasPrice)
if dec.Gas == nil {
return errors.New("missing required field 'gas' in transaction")
}
itx.Gas = uint64(*dec.Gas)
if dec.Value == nil {
return errors.New("missing required field 'value' in transaction")
}
itx.Value = (*big.Int)(dec.Value)
if dec.Data == nil {
return errors.New("missing required field 'input' in transaction")
}
itx.Data = *dec.Data
if dec.V == nil {
return errors.New("missing required field 'v' in transaction")
}
itx.V = (*big.Int)(dec.V)
if dec.R == nil {
return errors.New("missing required field 'r' in transaction")
}
itx.R = (*big.Int)(dec.R)
if dec.S == nil {
return errors.New("missing required field 's' in transaction")
}
itx.S = (*big.Int)(dec.S)
withSignature := itx.V.Sign() != 0 || itx.R.Sign() != 0 || itx.S.Sign() != 0
if withSignature {
if err := sanityCheckSignature(itx.V, itx.R, itx.S, true); err != nil {
return err
}
}
if dec.EffectiveGasPrice == nil {
return errors.New("missing required field 'EffectiveGasPrice' in transaction")
}
if dec.L1BlockNumber == nil {
return errors.New("missing required field 'L1BlockNumber' in transaction")
}
inner = &ArbitrumLegacyTxData{
LegacyTx: itx,
HashOverride: dec.Hash,
EffectiveGasPrice: uint64(*dec.EffectiveGasPrice),
L1BlockNumber: uint64(*dec.L1BlockNumber),
Sender: dec.From,
}
case ArbitrumInternalTxType:
if dec.ChainID == nil {
return errors.New("missing required field 'chainId' in transaction")
}
if dec.Data == nil {
return errors.New("missing required field 'input' in transaction")
}
inner = &ArbitrumInternalTx{
ChainId: (*big.Int)(dec.ChainID),
Data: *dec.Data,
}
case ArbitrumDepositTxType:
if dec.ChainID == nil {
return errors.New("missing required field 'chainId' in transaction")
}
if dec.RequestId == nil {
return errors.New("missing required field 'requestId' in transaction")
}
if dec.To == nil {
return errors.New("missing required field 'to' in transaction")
}
if dec.From == nil {
return errors.New("missing required field 'from' in transaction")
}
if dec.Value == nil {
return errors.New("missing required field 'value' in transaction")
}
inner = &ArbitrumDepositTx{
ChainId: (*big.Int)(dec.ChainID),
L1RequestId: *dec.RequestId,
To: *dec.To,
From: *dec.From,
Value: (*big.Int)(dec.Value),
}
case ArbitrumUnsignedTxType:
if dec.ChainID == nil {
return errors.New("missing required field 'chainId' in transaction")
}
if dec.From == nil {
return errors.New("missing required field 'from' in transaction")
}
if dec.Nonce == nil {
return errors.New("missing required field 'nonce' in transaction")
}
if dec.MaxFeePerGas == nil {
return errors.New("missing required field 'maxFeePerGas' for txdata")
}
if dec.Gas == nil {
return errors.New("missing required field 'gas' in txdata")
}
if dec.Value == nil {
return errors.New("missing required field 'value' in transaction")
}
if dec.Data == nil {
return errors.New("missing required field 'input' in transaction")
}
inner = &ArbitrumUnsignedTx{
ChainId: (*big.Int)(dec.ChainID),
From: *dec.From,
Nonce: uint64(*dec.Nonce),
GasFeeCap: (*big.Int)(dec.MaxFeePerGas),
Gas: uint64(*dec.Gas),
To: dec.To,
Value: (*big.Int)(dec.Value),
Data: *dec.Data,
}
case ArbitrumContractTxType:
if dec.ChainID == nil {
return errors.New("missing required field 'chainId' in transaction")
}
if dec.RequestId == nil {
return errors.New("missing required field 'requestId' in transaction")
}
if dec.From == nil {
return errors.New("missing required field 'from' in transaction")
}
if dec.MaxFeePerGas == nil {
return errors.New("missing required field 'maxFeePerGas' for txdata")
}
if dec.Gas == nil {
return errors.New("missing required field 'gas' in txdata")
}
if dec.Value == nil {
return errors.New("missing required field 'value' in transaction")
}
if dec.Data == nil {
return errors.New("missing required field 'input' in transaction")
}
inner = &ArbitrumContractTx{
ChainId: (*big.Int)(dec.ChainID),
RequestId: *dec.RequestId,
From: *dec.From,
GasFeeCap: (*big.Int)(dec.MaxFeePerGas),
Gas: uint64(*dec.Gas),
To: dec.To,
Value: (*big.Int)(dec.Value),
Data: *dec.Data,
}
case ArbitrumRetryTxType:
if dec.ChainID == nil {
return errors.New("missing required field 'chainId' in transaction")
}
if dec.Nonce == nil {
return errors.New("missing required field 'nonce' in transaction")
}
if dec.From == nil {
return errors.New("missing required field 'from' in transaction")
}
if dec.MaxFeePerGas == nil {
return errors.New("missing required field 'maxFeePerGas' for txdata")
}
if dec.Gas == nil {
return errors.New("missing required field 'gas' in txdata")
}
if dec.Value == nil {
return errors.New("missing required field 'value' in transaction")
}
if dec.Data == nil {
return errors.New("missing required field 'input' in transaction")
}
if dec.TicketId == nil {
return errors.New("missing required field 'ticketId' in transaction")
}
if dec.RefundTo == nil {
return errors.New("missing required field 'refundTo' in transaction")
}
if dec.MaxRefund == nil {
return errors.New("missing required field 'maxRefund' in transaction")
}
if dec.SubmissionFeeRefund == nil {
return errors.New("missing required field 'submissionFeeRefund' in transaction")
}
inner = &ArbitrumRetryTx{
ChainId: (*big.Int)(dec.ChainID),
Nonce: uint64(*dec.Nonce),
From: *dec.From,
GasFeeCap: (*big.Int)(dec.MaxFeePerGas),
Gas: uint64(*dec.Gas),
To: dec.To,
Value: (*big.Int)(dec.Value),
Data: *dec.Data,
TicketId: *dec.TicketId,
RefundTo: *dec.RefundTo,
MaxRefund: (*big.Int)(dec.MaxRefund),
SubmissionFeeRefund: (*big.Int)(dec.SubmissionFeeRefund),
}
case ArbitrumSubmitRetryableTxType:
if dec.ChainID == nil {
return errors.New("missing required field 'chainId' in transaction")
}
if dec.RequestId == nil {
return errors.New("missing required field 'requestId' in transaction")
}
if dec.From == nil {
return errors.New("missing required field 'from' in transaction")
}
if dec.L1BaseFee == nil {
return errors.New("missing required field 'l1BaseFee' in transaction")
}
if dec.DepositValue == nil {
return errors.New("missing required field 'depositValue' in transaction")
}
if dec.MaxFeePerGas == nil {
return errors.New("missing required field 'maxFeePerGas' for txdata")
}
if dec.Gas == nil {
return errors.New("missing required field 'gas' in txdata")
}
if dec.RetryTo == nil {
return errors.New("missing required field 'retryTo' in txdata")
}
if dec.Beneficiary == nil {
return errors.New("missing required field 'beneficiary' in transaction")
}
if dec.MaxSubmissionFee == nil {
return errors.New("missing required field 'maxSubmissionFee' in transaction")
}
if dec.RefundTo == nil {
return errors.New("missing required field 'refundTo' in transaction")
}
if dec.RetryValue == nil {
return errors.New("missing required field 'retryValue' in transaction")
}
if dec.RetryData == nil {
return errors.New("missing required field 'retryData' in transaction")
}
inner = &ArbitrumSubmitRetryableTx{
ChainId: (*big.Int)(dec.ChainID),
RequestId: *dec.RequestId,
From: *dec.From,
L1BaseFee: (*big.Int)(dec.L1BaseFee),
DepositValue: (*big.Int)(dec.DepositValue),
GasFeeCap: (*big.Int)(dec.MaxFeePerGas),
Gas: uint64(*dec.Gas),
RetryTo: dec.RetryTo,
RetryValue: (*big.Int)(dec.RetryValue),
Beneficiary: *dec.Beneficiary,
MaxSubmissionFee: (*big.Int)(dec.MaxSubmissionFee),
FeeRefundAddr: *dec.RefundTo,
RetryData: *dec.RetryData,
}
default:
return ErrTxTypeNotSupported
}