├── .github └── workflows │ ├── int-test.yaml │ └── test.yaml ├── .gitignore ├── Bits.go ├── ChainId.go ├── ChangePubKey.go ├── Contract.go ├── EthProvider.go ├── EthProviderMock_test.go ├── EthSigner.go ├── EthSigner_test.go ├── Fee.go ├── IntegrationTests └── Integration_test.go ├── Makefile ├── NFT.go ├── Provider.go ├── ProviderMock_test.go ├── README.md ├── Receipt.go ├── SigningUtils.go ├── State.go ├── Swap.go ├── TimeRange.go ├── Token.go ├── Transaction.go ├── Transfer.go ├── Wallet.go ├── Wallet_test.go ├── Withdraw.go ├── ZkSigner.go ├── ZkSigner_test.go ├── contracts ├── ERC20 │ └── ERC20.go ├── README.md └── ZkSync │ └── ZkSync.go ├── go.mod └── go.sum /.github/workflows/int-test.yaml: -------------------------------------------------------------------------------- 1 | name: Test Golang SDK library 2 | 3 | on: 4 | pull_request: 5 | branches: 6 | - master 7 | 8 | jobs: 9 | test: 10 | name: Run tests 11 | runs-on: ubuntu-latest 12 | steps: 13 | - name: Set up Go 14 | uses: actions/setup-go@v2 15 | with: 16 | go-version: 1.16 17 | - name: Checkout 18 | uses: actions/checkout@v2 19 | - name: Run unit tests 20 | run: make tests 21 | - name: Run integration test on Rinkeby 22 | run: make integration-test 23 | -------------------------------------------------------------------------------- /.github/workflows/test.yaml: -------------------------------------------------------------------------------- 1 | name: Test Golang SDK library 2 | 3 | on: 4 | push: 5 | branches: 6 | - develop 7 | 8 | jobs: 9 | test: 10 | name: Run tests 11 | runs-on: ubuntu-latest 12 | steps: 13 | - name: Set up Go 14 | uses: actions/setup-go@v2 15 | with: 16 | go-version: 1.16 17 | - name: Checkout 18 | uses: actions/checkout@v2 19 | - name: Run unit tests 20 | run: make tests 21 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .idea 2 | libs/ 3 | -------------------------------------------------------------------------------- /Bits.go: -------------------------------------------------------------------------------- 1 | package zksync 2 | 3 | import "github.com/pkg/errors" 4 | 5 | type Bits struct { 6 | bits []bool 7 | } 8 | 9 | func NewBits(len uint) *Bits { 10 | return &Bits{bits: make([]bool, len)} 11 | } 12 | 13 | func (b *Bits) Len() uint { 14 | return uint(len(b.bits)) 15 | } 16 | 17 | func (b *Bits) SetBit(i uint, v bool) { 18 | // len is not checked, can cause panic 19 | b.bits[i] = v 20 | } 21 | 22 | func (b *Bits) GetBit(i uint) bool { 23 | // len is not checked, can cause panic 24 | return b.bits[i] 25 | } 26 | 27 | func (b *Bits) Clone() *Bits { 28 | clone := NewBits(b.Len()) 29 | copy(clone.bits, b.bits) 30 | return clone 31 | } 32 | 33 | func (b *Bits) Append(a *Bits) *Bits { 34 | b.bits = append(b.bits, a.bits...) 35 | return b 36 | } 37 | 38 | func (b *Bits) Reverse() *Bits { 39 | for i, j := 0, len(b.bits)-1; i < j; i, j = i+1, j-1 { 40 | b.bits[i], b.bits[j] = b.bits[j], b.bits[i] 41 | } 42 | return b 43 | } 44 | 45 | func (b *Bits) String() (s string) { 46 | for _, v := range b.bits { 47 | if v { 48 | s += `1` 49 | } else { 50 | s += `0` 51 | } 52 | } 53 | return 54 | } 55 | 56 | func (b *Bits) ToBytesBE() ([]byte, error) { 57 | bits := len(b.bits) 58 | if bits%8 != 0 { 59 | return nil, errors.New("Wrong number of bits to pack") 60 | } 61 | bytes := len(b.bits) / 8 62 | res := make([]byte, bytes) 63 | for i, b := range b.bits { 64 | if b { 65 | byteIdx := i / 8 66 | bitIdx := 7 - i%8 67 | res[byteIdx] = res[byteIdx] | 1< 0) 76 | b.SetBit(uint(i*8+1), v&0x40 > 0) 77 | b.SetBit(uint(i*8+2), v&0x20 > 0) 78 | b.SetBit(uint(i*8+3), v&0x10 > 0) 79 | b.SetBit(uint(i*8+4), v&0x08 > 0) 80 | b.SetBit(uint(i*8+5), v&0x04 > 0) 81 | b.SetBit(uint(i*8+6), v&0x02 > 0) 82 | b.SetBit(uint(i*8+7), v&0x01 > 0) 83 | } 84 | return b 85 | } 86 | -------------------------------------------------------------------------------- /ChainId.go: -------------------------------------------------------------------------------- 1 | package zksync 2 | 3 | type ChainId int 4 | 5 | const ( 6 | ChainIdMainnet ChainId = 1 7 | ChainIdRinkeby ChainId = 4 8 | ChainIdRopsten ChainId = 3 9 | ChainIdLocalhost ChainId = 9 10 | ) 11 | -------------------------------------------------------------------------------- /ChangePubKey.go: -------------------------------------------------------------------------------- 1 | package zksync 2 | 3 | import ( 4 | "encoding/hex" 5 | "github.com/ethereum/go-ethereum/common" 6 | ) 7 | 8 | type TransactionTypeChangePubKey struct { 9 | ChangePubKey string `json:"ChangePubKey"` 10 | } 11 | 12 | const ( 13 | TransactionTypeChangePubKeyOnchain TransactionType = "Onchain" 14 | TransactionTypeChangePubKeyECDSA TransactionType = "ECDSA" 15 | TransactionTypeChangePubKeyCREATE2 TransactionType = "CREATE2" 16 | ) 17 | 18 | type ChangePubKey struct { 19 | Type string `json:"type"` 20 | AccountId uint32 `json:"accountId"` 21 | Account common.Address `json:"account"` 22 | NewPkHash string `json:"newPkHash"` 23 | FeeToken uint32 `json:"feeToken"` 24 | Fee string `json:"fee"` 25 | Nonce uint32 `json:"nonce"` 26 | Signature *Signature `json:"signature"` 27 | EthAuthData ChangePubKeyVariant `json:"ethAuthData"` 28 | *TimeRange 29 | } 30 | 31 | func (t *ChangePubKey) getType() string { 32 | return "ChangePubKey" 33 | } 34 | 35 | type ChangePubKeyAuthType string 36 | 37 | const ( 38 | ChangePubKeyAuthTypeOnchain ChangePubKeyAuthType = `Onchain` 39 | ChangePubKeyAuthTypeECDSA ChangePubKeyAuthType = `ECDSA` 40 | ChangePubKeyAuthTypeCREATE2 ChangePubKeyAuthType = `CREATE2` 41 | ) 42 | 43 | type ChangePubKeyVariant interface { 44 | getType() ChangePubKeyAuthType 45 | getBytes() []byte 46 | } 47 | 48 | type ChangePubKeyOnchain struct { 49 | Type ChangePubKeyAuthType `json:"type"` 50 | } 51 | 52 | func (t *ChangePubKeyOnchain) getType() ChangePubKeyAuthType { 53 | return ChangePubKeyAuthTypeOnchain 54 | } 55 | 56 | func (t *ChangePubKeyOnchain) getBytes() []byte { 57 | return make([]byte, 32) 58 | } 59 | 60 | type ChangePubKeyECDSA struct { 61 | Type ChangePubKeyAuthType `json:"type"` 62 | EthSignature string `json:"ethSignature"` 63 | BatchHash string `json:"batchHash"` 64 | } 65 | 66 | func (t *ChangePubKeyECDSA) getType() ChangePubKeyAuthType { 67 | return ChangePubKeyAuthTypeECDSA 68 | } 69 | 70 | func (t *ChangePubKeyECDSA) getBytes() []byte { 71 | res, _ := hex.DecodeString(t.BatchHash[2:]) 72 | return res 73 | } 74 | 75 | type ChangePubKeyCREATE2 struct { 76 | Type ChangePubKeyAuthType `json:"type"` 77 | CreatorAddress string `json:"creatorAddress"` 78 | SaltArg string `json:"saltArg"` 79 | CodeHash string `json:"codeHash"` 80 | } 81 | 82 | func (t *ChangePubKeyCREATE2) getType() ChangePubKeyAuthType { 83 | return ChangePubKeyAuthTypeCREATE2 84 | } 85 | 86 | func (t *ChangePubKeyCREATE2) getBytes() []byte { 87 | return make([]byte, 32) 88 | } 89 | 90 | type Signature struct { 91 | PubKey string `json:"pubKey"` 92 | Signature string `json:"signature"` 93 | } 94 | -------------------------------------------------------------------------------- /Contract.go: -------------------------------------------------------------------------------- 1 | package zksync 2 | 3 | import "github.com/ethereum/go-ethereum/common" 4 | 5 | type ContractAddress struct { 6 | MainContract string `json:"mainContract"` 7 | GovContract string `json:"govContract"` 8 | } 9 | 10 | func (a *ContractAddress) GetMainAddress() common.Address { 11 | return common.HexToAddress(a.MainContract) 12 | } 13 | 14 | func (a *ContractAddress) GetGovAddress() common.Address { 15 | return common.HexToAddress(a.GovContract) 16 | } 17 | -------------------------------------------------------------------------------- /EthProvider.go: -------------------------------------------------------------------------------- 1 | package zksync 2 | 3 | import ( 4 | "context" 5 | "github.com/ethereum/go-ethereum/accounts/abi/bind" 6 | "github.com/ethereum/go-ethereum/common" 7 | "github.com/ethereum/go-ethereum/core/types" 8 | "github.com/ethereum/go-ethereum/ethclient" 9 | "github.com/pkg/errors" 10 | "github.com/zksync-sdk/zksync-go/contracts/ERC20" 11 | "github.com/zksync-sdk/zksync-go/contracts/ZkSync" 12 | "math/big" 13 | ) 14 | 15 | //go:generate mockery -name=EthProvider -output=./ -outpkg=zksync -filename=EthProviderMock_test.go -structname=EthProviderMock -inpkg 16 | 17 | type EthProvider interface { 18 | ApproveDeposits(token *Token, limit *big.Int, options *GasOptions) (*types.Transaction, error) 19 | IsDepositApproved(token *Token, userAddress common.Address, threshold *big.Int) (bool, error) 20 | Deposit(token *Token, amount *big.Int, userAddress common.Address, options *GasOptions) (*types.Transaction, error) 21 | SetAuthPubkeyHash(pubKeyHash string, zkNonce uint32, options *GasOptions) (*types.Transaction, error) 22 | IsOnChainAuthPubkeyHashSet(nonce uint32) (bool, error) 23 | GetBalance(token *Token) (*big.Int, error) 24 | GetNonce() (uint64, error) 25 | FullExit(token *Token, accountId uint32, options *GasOptions) (*types.Transaction, error) 26 | FullExitNFT(token *NFT, accountId uint32, options *GasOptions) (*types.Transaction, error) 27 | } 28 | 29 | type DefaultEthProvider struct { 30 | client *ethclient.Client 31 | contract *ZkSync.ZkSync 32 | address common.Address 33 | auth *bind.TransactOpts 34 | } 35 | 36 | type GasOptions struct { 37 | GasPrice *big.Int // Gas price to use for the transaction execution (nil = gas price oracle) 38 | GasLimit uint64 // Gas limit to set for the transaction execution (0 = estimate) 39 | } 40 | 41 | func (p *DefaultEthProvider) ApproveDeposits(token *Token, limit *big.Int, options *GasOptions) (*types.Transaction, error) { 42 | tokenContract, err := ERC20.NewERC20(token.GetAddress(), p.client) 43 | if err != nil { 44 | return nil, errors.Wrap(err, "failed to load token contract") 45 | } 46 | if limit == nil { 47 | // max approve amount 2^256 - 1 48 | limit = big.NewInt(0).Sub(big.NewInt(0).Exp(big.NewInt(2), big.NewInt(256), nil), big.NewInt(1)) 49 | } 50 | auth := p.getAuth(options) 51 | return tokenContract.Approve(auth, p.address, limit) 52 | } 53 | 54 | func (p *DefaultEthProvider) IsDepositApproved(token *Token, userAddress common.Address, threshold *big.Int) (bool, error) { 55 | tokenContract, err := ERC20.NewERC20(token.GetAddress(), p.client) 56 | if err != nil { 57 | return false, errors.Wrap(err, "failed to load token contract") 58 | } 59 | auth := &bind.CallOpts{} 60 | allowed, err := tokenContract.Allowance(auth, userAddress, p.address) 61 | if err != nil { 62 | return false, errors.Wrap(err, "failed to call Allowance view of token contract") 63 | } 64 | if threshold == nil { 65 | // default threshold 2^255 66 | threshold = big.NewInt(0).Exp(big.NewInt(2), big.NewInt(255), nil) 67 | } 68 | return allowed.Cmp(threshold) >= 0, nil 69 | } 70 | 71 | func (p *DefaultEthProvider) Deposit(token *Token, amount *big.Int, userAddress common.Address, options *GasOptions) (*types.Transaction, error) { 72 | auth := p.getAuth(options) 73 | if token.IsETH() { 74 | auth.Value = amount 75 | return p.contract.DepositETH(auth, userAddress) 76 | } else { 77 | auth.Value = nil 78 | return p.contract.DepositERC20(auth, token.GetAddress(), amount, userAddress) 79 | } 80 | } 81 | 82 | func (p *DefaultEthProvider) SetAuthPubkeyHash(pubKeyHash string, zkNonce uint32, options *GasOptions) (*types.Transaction, error) { 83 | auth := p.getAuth(options) 84 | pkh, err := pkhToBytes(pubKeyHash) 85 | if err != nil { 86 | return nil, errors.Wrap(err, "invalid pubKeyHash value") 87 | } 88 | return p.contract.SetAuthPubkeyHash(auth, pkh, zkNonce) 89 | } 90 | 91 | func (p *DefaultEthProvider) IsOnChainAuthPubkeyHashSet(nonce uint32) (bool, error) { 92 | opts := &bind.CallOpts{} 93 | publicKeyHash, err := p.contract.AuthFacts(opts, p.auth.From, nonce) 94 | if err != nil { 95 | return false, errors.Wrap(err, "failed to call AuthFacts") 96 | } 97 | return publicKeyHash != [32]byte{}, nil 98 | } 99 | 100 | func (p *DefaultEthProvider) GetBalance(token *Token) (*big.Int, error) { 101 | if token == nil || token.IsETH() { 102 | return p.client.BalanceAt(context.Background(), p.auth.From, nil) // latest 103 | } 104 | tokenContract, err := ERC20.NewERC20(token.GetAddress(), p.client) 105 | if err != nil { 106 | return nil, errors.Wrap(err, "failed to load token contract") 107 | } 108 | opts := &bind.CallOpts{} 109 | return tokenContract.BalanceOf(opts, p.auth.From) 110 | } 111 | 112 | func (p *DefaultEthProvider) GetNonce() (uint64, error) { 113 | return p.client.PendingNonceAt(context.Background(), p.auth.From) // pending 114 | } 115 | 116 | func (p *DefaultEthProvider) FullExit(token *Token, accountId uint32, options *GasOptions) (*types.Transaction, error) { 117 | auth := p.getAuth(options) 118 | return p.contract.RequestFullExit(auth, accountId, token.GetAddress()) 119 | } 120 | 121 | func (p *DefaultEthProvider) FullExitNFT(token *NFT, accountId uint32, options *GasOptions) (*types.Transaction, error) { 122 | auth := p.getAuth(options) 123 | return p.contract.RequestFullExitNFT(auth, accountId, token.Id) 124 | } 125 | 126 | // getAuth make a new copy of origin TransactOpts to be used safely for each call 127 | func (p *DefaultEthProvider) getAuth(options *GasOptions) *bind.TransactOpts { 128 | newAuth := &bind.TransactOpts{ 129 | From: p.auth.From, 130 | Signer: p.auth.Signer, 131 | } 132 | if options != nil { 133 | newAuth.GasPrice = options.GasPrice 134 | newAuth.GasLimit = options.GasLimit 135 | } 136 | return newAuth 137 | } 138 | -------------------------------------------------------------------------------- /EthProviderMock_test.go: -------------------------------------------------------------------------------- 1 | // Code generated by mockery v1.0.0. DO NOT EDIT. 2 | 3 | package zksync 4 | 5 | import ( 6 | big "math/big" 7 | 8 | common "github.com/ethereum/go-ethereum/common" 9 | mock "github.com/stretchr/testify/mock" 10 | 11 | types "github.com/ethereum/go-ethereum/core/types" 12 | ) 13 | 14 | // EthProviderMock is an autogenerated mock type for the EthProvider type 15 | type EthProviderMock struct { 16 | mock.Mock 17 | } 18 | 19 | // ApproveDeposits provides a mock function with given fields: token, limit, options 20 | func (_m *EthProviderMock) ApproveDeposits(token *Token, limit *big.Int, options *GasOptions) (*types.Transaction, error) { 21 | ret := _m.Called(token, limit, options) 22 | 23 | var r0 *types.Transaction 24 | if rf, ok := ret.Get(0).(func(*Token, *big.Int, *GasOptions) *types.Transaction); ok { 25 | r0 = rf(token, limit, options) 26 | } else { 27 | if ret.Get(0) != nil { 28 | r0 = ret.Get(0).(*types.Transaction) 29 | } 30 | } 31 | 32 | var r1 error 33 | if rf, ok := ret.Get(1).(func(*Token, *big.Int, *GasOptions) error); ok { 34 | r1 = rf(token, limit, options) 35 | } else { 36 | r1 = ret.Error(1) 37 | } 38 | 39 | return r0, r1 40 | } 41 | 42 | // Deposit provides a mock function with given fields: token, amount, userAddress, options 43 | func (_m *EthProviderMock) Deposit(token *Token, amount *big.Int, userAddress common.Address, options *GasOptions) (*types.Transaction, error) { 44 | ret := _m.Called(token, amount, userAddress, options) 45 | 46 | var r0 *types.Transaction 47 | if rf, ok := ret.Get(0).(func(*Token, *big.Int, common.Address, *GasOptions) *types.Transaction); ok { 48 | r0 = rf(token, amount, userAddress, options) 49 | } else { 50 | if ret.Get(0) != nil { 51 | r0 = ret.Get(0).(*types.Transaction) 52 | } 53 | } 54 | 55 | var r1 error 56 | if rf, ok := ret.Get(1).(func(*Token, *big.Int, common.Address, *GasOptions) error); ok { 57 | r1 = rf(token, amount, userAddress, options) 58 | } else { 59 | r1 = ret.Error(1) 60 | } 61 | 62 | return r0, r1 63 | } 64 | 65 | // FullExit provides a mock function with given fields: token, accountId, options 66 | func (_m *EthProviderMock) FullExit(token *Token, accountId uint32, options *GasOptions) (*types.Transaction, error) { 67 | ret := _m.Called(token, accountId, options) 68 | 69 | var r0 *types.Transaction 70 | if rf, ok := ret.Get(0).(func(*Token, uint32, *GasOptions) *types.Transaction); ok { 71 | r0 = rf(token, accountId, options) 72 | } else { 73 | if ret.Get(0) != nil { 74 | r0 = ret.Get(0).(*types.Transaction) 75 | } 76 | } 77 | 78 | var r1 error 79 | if rf, ok := ret.Get(1).(func(*Token, uint32, *GasOptions) error); ok { 80 | r1 = rf(token, accountId, options) 81 | } else { 82 | r1 = ret.Error(1) 83 | } 84 | 85 | return r0, r1 86 | } 87 | 88 | // FullExitNFT provides a mock function with given fields: token, accountId, options 89 | func (_m *EthProviderMock) FullExitNFT(token *NFT, accountId uint32, options *GasOptions) (*types.Transaction, error) { 90 | ret := _m.Called(token, accountId, options) 91 | 92 | var r0 *types.Transaction 93 | if rf, ok := ret.Get(0).(func(*NFT, uint32, *GasOptions) *types.Transaction); ok { 94 | r0 = rf(token, accountId, options) 95 | } else { 96 | if ret.Get(0) != nil { 97 | r0 = ret.Get(0).(*types.Transaction) 98 | } 99 | } 100 | 101 | var r1 error 102 | if rf, ok := ret.Get(1).(func(*NFT, uint32, *GasOptions) error); ok { 103 | r1 = rf(token, accountId, options) 104 | } else { 105 | r1 = ret.Error(1) 106 | } 107 | 108 | return r0, r1 109 | } 110 | 111 | // GetBalance provides a mock function with given fields: token 112 | func (_m *EthProviderMock) GetBalance(token *Token) (*big.Int, error) { 113 | ret := _m.Called(token) 114 | 115 | var r0 *big.Int 116 | if rf, ok := ret.Get(0).(func(*Token) *big.Int); ok { 117 | r0 = rf(token) 118 | } else { 119 | if ret.Get(0) != nil { 120 | r0 = ret.Get(0).(*big.Int) 121 | } 122 | } 123 | 124 | var r1 error 125 | if rf, ok := ret.Get(1).(func(*Token) error); ok { 126 | r1 = rf(token) 127 | } else { 128 | r1 = ret.Error(1) 129 | } 130 | 131 | return r0, r1 132 | } 133 | 134 | // GetNonce provides a mock function with given fields: 135 | func (_m *EthProviderMock) GetNonce() (uint64, error) { 136 | ret := _m.Called() 137 | 138 | var r0 uint64 139 | if rf, ok := ret.Get(0).(func() uint64); ok { 140 | r0 = rf() 141 | } else { 142 | r0 = ret.Get(0).(uint64) 143 | } 144 | 145 | var r1 error 146 | if rf, ok := ret.Get(1).(func() error); ok { 147 | r1 = rf() 148 | } else { 149 | r1 = ret.Error(1) 150 | } 151 | 152 | return r0, r1 153 | } 154 | 155 | // IsDepositApproved provides a mock function with given fields: token, userAddress, threshold 156 | func (_m *EthProviderMock) IsDepositApproved(token *Token, userAddress common.Address, threshold *big.Int) (bool, error) { 157 | ret := _m.Called(token, userAddress, threshold) 158 | 159 | var r0 bool 160 | if rf, ok := ret.Get(0).(func(*Token, common.Address, *big.Int) bool); ok { 161 | r0 = rf(token, userAddress, threshold) 162 | } else { 163 | r0 = ret.Get(0).(bool) 164 | } 165 | 166 | var r1 error 167 | if rf, ok := ret.Get(1).(func(*Token, common.Address, *big.Int) error); ok { 168 | r1 = rf(token, userAddress, threshold) 169 | } else { 170 | r1 = ret.Error(1) 171 | } 172 | 173 | return r0, r1 174 | } 175 | 176 | // IsOnChainAuthPubkeyHashSet provides a mock function with given fields: nonce 177 | func (_m *EthProviderMock) IsOnChainAuthPubkeyHashSet(nonce uint32) (bool, error) { 178 | ret := _m.Called(nonce) 179 | 180 | var r0 bool 181 | if rf, ok := ret.Get(0).(func(uint32) bool); ok { 182 | r0 = rf(nonce) 183 | } else { 184 | r0 = ret.Get(0).(bool) 185 | } 186 | 187 | var r1 error 188 | if rf, ok := ret.Get(1).(func(uint32) error); ok { 189 | r1 = rf(nonce) 190 | } else { 191 | r1 = ret.Error(1) 192 | } 193 | 194 | return r0, r1 195 | } 196 | 197 | // SetAuthPubkeyHash provides a mock function with given fields: pubKeyHash, zkNonce, options 198 | func (_m *EthProviderMock) SetAuthPubkeyHash(pubKeyHash string, zkNonce uint32, options *GasOptions) (*types.Transaction, error) { 199 | ret := _m.Called(pubKeyHash, zkNonce, options) 200 | 201 | var r0 *types.Transaction 202 | if rf, ok := ret.Get(0).(func(string, uint32, *GasOptions) *types.Transaction); ok { 203 | r0 = rf(pubKeyHash, zkNonce, options) 204 | } else { 205 | if ret.Get(0) != nil { 206 | r0 = ret.Get(0).(*types.Transaction) 207 | } 208 | } 209 | 210 | var r1 error 211 | if rf, ok := ret.Get(1).(func(string, uint32, *GasOptions) error); ok { 212 | r1 = rf(pubKeyHash, zkNonce, options) 213 | } else { 214 | r1 = ret.Error(1) 215 | } 216 | 217 | return r0, r1 218 | } 219 | -------------------------------------------------------------------------------- /EthSigner.go: -------------------------------------------------------------------------------- 1 | package zksync 2 | 3 | import ( 4 | "crypto/ecdsa" 5 | "encoding/hex" 6 | "fmt" 7 | "github.com/ethereum/go-ethereum/accounts" 8 | "github.com/ethereum/go-ethereum/common" 9 | "github.com/ethereum/go-ethereum/crypto" 10 | "github.com/miguelmota/go-ethereum-hdwallet" 11 | "github.com/pkg/errors" 12 | "math/big" 13 | "strings" 14 | ) 15 | 16 | type EthSigner interface { 17 | GetAddress() common.Address 18 | SignMessage([]byte) ([]byte, error) 19 | SignHash(msg []byte) ([]byte, error) 20 | SignAuth(txData *ChangePubKey) (*ChangePubKeyECDSA, error) 21 | SignTransaction(tx ZksTransaction, nonce uint32, token *Token, fee *big.Int) (*EthSignature, error) 22 | SignBatch(txs []ZksTransaction, nonce uint32, token *Token, fee *big.Int) (*EthSignature, error) 23 | SignOrder(order *Order, sell, buy *Token) (*EthSignature, error) 24 | } 25 | 26 | type DefaultEthSigner struct { 27 | pk *ecdsa.PrivateKey 28 | address common.Address 29 | } 30 | 31 | func NewEthSignerFromMnemonic(mnemonic string) (*DefaultEthSigner, error) { 32 | return NewEthSignerFromMnemonicAndAccountId(mnemonic, 0) 33 | } 34 | 35 | func NewEthSignerFromMnemonicAndAccountId(mnemonic string, accountId uint32) (*DefaultEthSigner, error) { 36 | wallet, err := hdwallet.NewFromMnemonic(mnemonic) 37 | if err != nil { 38 | return nil, errors.Wrap(err, "failed to create HD wallet from mnemonic") 39 | } 40 | path, err := accounts.ParseDerivationPath(fmt.Sprintf("m/44'/60'/0'/0/%d", accountId)) 41 | if err != nil { 42 | return nil, errors.Wrap(err, "failed to parse derivation path") 43 | } 44 | account, err := wallet.Derive(path, true) 45 | if err != nil { 46 | return nil, errors.Wrap(err, "failed to derive account from HD wallet") 47 | } 48 | pk, err := wallet.PrivateKey(account) 49 | if err != nil { 50 | return nil, errors.Wrap(err, "failed to get account's private key from HD wallet") 51 | } 52 | pub := pk.Public().(*ecdsa.PublicKey) 53 | return &DefaultEthSigner{ 54 | pk: pk, 55 | address: crypto.PubkeyToAddress(*pub), 56 | }, nil 57 | } 58 | 59 | func NewEthSignerFromRawPrivateKey(rawPk []byte) (*DefaultEthSigner, error) { 60 | pk, err := crypto.ToECDSA(rawPk) 61 | if err != nil { 62 | return nil, errors.Wrap(err, "invalid raw private key") 63 | } 64 | pub := pk.Public().(*ecdsa.PublicKey) 65 | return &DefaultEthSigner{ 66 | pk: pk, 67 | address: crypto.PubkeyToAddress(*pub), 68 | }, nil 69 | } 70 | 71 | func (s *DefaultEthSigner) GetAddress() common.Address { 72 | return s.address 73 | } 74 | 75 | func (s *DefaultEthSigner) SignMessage(msg []byte) ([]byte, error) { 76 | sig, err := s.SignHash(accounts.TextHash(msg)) // prefixed 77 | if err != nil { 78 | return nil, errors.Wrap(err, "failed to sign message") 79 | } 80 | // set recovery byte to 27/28 81 | if len(sig) == 65 { 82 | sig[64] += 27 83 | } 84 | return sig, nil 85 | } 86 | 87 | func (s *DefaultEthSigner) SignHash(msg []byte) ([]byte, error) { 88 | sig, err := crypto.Sign(msg, s.pk) 89 | if err != nil { 90 | return nil, errors.Wrap(err, "failed to sign hash") 91 | } 92 | return sig, nil 93 | } 94 | 95 | func (s *DefaultEthSigner) SignAuth(txData *ChangePubKey) (*ChangePubKeyECDSA, error) { 96 | auth := &ChangePubKeyECDSA{ 97 | Type: ChangePubKeyAuthTypeECDSA, 98 | EthSignature: "", 99 | BatchHash: "0x" + hex.EncodeToString(make([]byte, 32)), 100 | } 101 | txData.EthAuthData = auth 102 | msg, err := GetChangePubKeyData(txData) 103 | if err != nil { 104 | return nil, errors.Wrap(err, "failed to get ChangePubKey data for sign") 105 | } 106 | sig, err := s.SignMessage(msg) 107 | if err != nil { 108 | return nil, errors.Wrap(err, "failed to sign ChangePubKeyECDSA msg") 109 | } 110 | auth.EthSignature = "0x" + hex.EncodeToString(sig) 111 | return auth, nil 112 | } 113 | 114 | func (s *DefaultEthSigner) SignTransaction(tx ZksTransaction, nonce uint32, token *Token, fee *big.Int) (*EthSignature, error) { 115 | msg, err := GetSignMessage(tx, token, fee) 116 | if err != nil { 117 | return nil, errors.Wrap(err, "failed to get sign message for tx") 118 | } 119 | msg += "\n" + GetNonceMessagePart(nonce) 120 | sig, err := s.SignMessage([]byte(msg)) 121 | if err != nil { 122 | return nil, errors.Wrap(err, "failed to sign tx") 123 | } 124 | return &EthSignature{ 125 | Type: EthSignatureTypeEth, 126 | Signature: "0x" + hex.EncodeToString(sig), 127 | }, nil 128 | } 129 | 130 | func (s *DefaultEthSigner) SignBatch(txs []ZksTransaction, nonce uint32, token *Token, fee *big.Int) (*EthSignature, error) { 131 | batchMsgs := make([]string, 0, len(txs)) 132 | for _, tx := range txs { 133 | msg, err := GetSignMessage(tx, token, fee) 134 | if err != nil { 135 | return nil, errors.Wrap(err, "failed to get sign message for one of txs") 136 | } 137 | batchMsgs = append(batchMsgs, msg) 138 | } 139 | batchMsg := strings.Join(batchMsgs, "\n") 140 | batchMsg += "\n" + GetNonceMessagePart(nonce) 141 | sig, err := s.SignMessage([]byte(batchMsg)) 142 | if err != nil { 143 | return nil, errors.Wrap(err, "failed to sign batch of txs") 144 | } 145 | return &EthSignature{ 146 | Type: EthSignatureTypeEth, 147 | Signature: "0x" + hex.EncodeToString(sig), 148 | }, nil 149 | } 150 | 151 | func (s *DefaultEthSigner) SignOrder(order *Order, sell, buy *Token) (*EthSignature, error) { 152 | msg, err := GetOrderMessagePart(order.RecipientAddress.String(), order.Amount, sell, buy, order.Ratio) 153 | if err != nil { 154 | return nil, errors.Wrap(err, "failed to get Order message part") 155 | } 156 | msg += "\n" + GetNonceMessagePart(order.Nonce) 157 | sig, err := s.SignMessage([]byte(msg)) 158 | if err != nil { 159 | return nil, errors.Wrap(err, "failed to sign Order") 160 | } 161 | return &EthSignature{ 162 | Type: EthSignatureTypeEth, 163 | Signature: "0x" + hex.EncodeToString(sig), 164 | }, nil 165 | } 166 | 167 | type EthSignatureType string 168 | 169 | const ( 170 | EthSignatureTypeEth EthSignatureType = "EthereumSignature" 171 | EthSignatureTypeEIP1271 EthSignatureType = "EIP1271Signature" 172 | ) 173 | 174 | type EthSignature struct { 175 | Type EthSignatureType `json:"type"` 176 | Signature string `json:"signature"` 177 | } 178 | -------------------------------------------------------------------------------- /EthSigner_test.go: -------------------------------------------------------------------------------- 1 | package zksync 2 | 3 | import ( 4 | "encoding/hex" 5 | "github.com/ethereum/go-ethereum/common" 6 | "github.com/stretchr/testify/assert" 7 | "github.com/stretchr/testify/require" 8 | "testing" 9 | ) 10 | 11 | var ( 12 | ethSigner *DefaultEthSigner 13 | mnemonic = "kick swallow air vanish path pelican author bring group remove space retire retreat denial sphere" 14 | ethPkHex = "0945012b971f943073f6e066581f513c8cd81660bb5a64306d5d092b8df9dd3f" 15 | expAddress = "0xFE64d0cF81848190C18ea5a7ff8d193ac807fd7E" 16 | 17 | mnemonic2 = "timber chronic resemble glide unlock click balance summer beauty cannon intact dwarf cross wrestle super" 18 | toAddress = "0x09084AAA8814F1781147F8d98798Dce2A86f96A6" 19 | 20 | signMsg = []byte("sample message") 21 | expMsgSignature = []byte{0x6d, 0xa3, 0x47, 0x59, 0xb6, 0xa0, 0xe, 0x2d, 0x64, 0x48, 0xad, 0x2, 0x71, 0xab, 0x16, 0x92, 0xb3, 0x62, 0x2b, 0x6f, 0x8d, 0x5f, 0xba, 0x97, 0xc2, 0x8f, 0xc4, 0xc6, 0x47, 0x7, 0xb7, 0x15, 0x47, 0xe4, 0xb, 0x6d, 0xbc, 0x4a, 0xed, 0x5b, 0x66, 0x98, 0xfc, 0xa2, 0xd3, 0x11, 0x5c, 0xc2, 0x57, 0xd6, 0x58, 0x1d, 0xaf, 0xb4, 0x63, 0x32, 0xcc, 0xfe, 0xbd, 0x24, 0x3f, 0x50, 0x8f, 0x50, 0x1c} 22 | signHash = common.BytesToHash([]byte("sample hash")) 23 | expHashSignature = []byte{0x7c, 0x9a, 0x8, 0x3c, 0x94, 0x28, 0xe9, 0xd2, 0x13, 0x1f, 0x56, 0x20, 0xe9, 0xbf, 0xf1, 0x17, 0x76, 0xf9, 0xeb, 0xae, 0x66, 0x55, 0x77, 0x5f, 0x46, 0x9, 0x97, 0xcd, 0x84, 0x2a, 0x9e, 0x4, 0x55, 0x58, 0xc7, 0xd0, 0x34, 0x9a, 0xe6, 0x5c, 0xed, 0x3, 0x46, 0x5e, 0x43, 0x3b, 0x33, 0xf5, 0xd2, 0xaf, 0x4, 0x63, 0x3b, 0x9b, 0x74, 0x19, 0xf9, 0x2e, 0x78, 0x4f, 0x54, 0x66, 0x6f, 0x95, 0x0} 24 | ) 25 | 26 | func TestNewEthSignerFromMnemonic(t *testing.T) { 27 | t.Run("success", func(t *testing.T) { 28 | ethSigner, err = NewEthSignerFromMnemonic(mnemonic) 29 | require.NoError(t, err) 30 | assert.NotNil(t, ethSigner) 31 | assert.IsType(t, &DefaultEthSigner{}, ethSigner) 32 | address := ethSigner.GetAddress() 33 | assert.NotNil(t, address) 34 | assert.IsType(t, common.Address{}, address) 35 | assert.EqualValues(t, expAddress, address.String()) 36 | }) 37 | 38 | t.Run("invalid mnemonic", func(t *testing.T) { 39 | ethSigner, err := NewEthSignerFromMnemonic("invalid mnemonic") 40 | require.Error(t, err) 41 | assert.Nil(t, ethSigner) 42 | }) 43 | 44 | } 45 | 46 | func TestNewEthSignerFromRawPrivateKey(t *testing.T) { 47 | t.Run("success", func(t *testing.T) { 48 | pk, err := hex.DecodeString(ethPkHex) 49 | require.NoError(t, err) 50 | ethSigner, err = NewEthSignerFromRawPrivateKey(pk) 51 | require.NoError(t, err) 52 | assert.NotNil(t, ethSigner) 53 | assert.IsType(t, &DefaultEthSigner{}, ethSigner) 54 | address := ethSigner.GetAddress() 55 | assert.NotNil(t, address) 56 | assert.IsType(t, common.Address{}, address) 57 | assert.EqualValues(t, expAddress, address.String()) 58 | }) 59 | 60 | t.Run("invalid raw pk", func(t *testing.T) { 61 | ethSigner, err := NewEthSignerFromRawPrivateKey([]byte{1, 2, 3}) 62 | require.Error(t, err) 63 | assert.Nil(t, ethSigner) 64 | }) 65 | 66 | } 67 | 68 | func TestSignMessage(t *testing.T) { 69 | signature, err := ethSigner.SignMessage(signMsg) 70 | require.NoError(t, err) 71 | assert.NotNil(t, signature) 72 | assert.EqualValues(t, expMsgSignature, signature) 73 | } 74 | 75 | func TestSignHash(t *testing.T) { 76 | t.Run("main positive flow", func(t *testing.T) { 77 | signature, err := ethSigner.SignHash(signHash.Bytes()) 78 | require.NoError(t, err) 79 | assert.NotNil(t, signature) 80 | assert.EqualValues(t, expHashSignature, signature) 81 | }) 82 | t.Run("invalid hash", func(t *testing.T) { 83 | signature, err := ethSigner.SignHash([]byte("invalid hash")) 84 | require.Error(t, err) 85 | assert.Nil(t, signature) 86 | }) 87 | } 88 | -------------------------------------------------------------------------------- /Fee.go: -------------------------------------------------------------------------------- 1 | package zksync 2 | 3 | import "math/big" 4 | 5 | type TransactionFeeDetails struct { 6 | GasTxAmount string `json:"gasTxAmount"` 7 | GasPriceWei string `json:"gasPriceWei"` 8 | GasFee string `json:"gasFee"` 9 | ZkpFee string `json:"zkpFee"` 10 | TotalFee string `json:"totalFee"` 11 | } 12 | 13 | func (d *TransactionFeeDetails) GetTotalFee() *big.Int { 14 | n := new(big.Int) 15 | if n, ok := n.SetString(d.TotalFee, 10); ok { 16 | return n 17 | } 18 | return new(big.Int) 19 | } 20 | 21 | func (d *TransactionFeeDetails) GetTxFee(feeToken *Token) *TransactionFee { 22 | return &TransactionFee{ 23 | FeeToken: feeToken.Address, 24 | Fee: d.GetTotalFee(), 25 | } 26 | } 27 | 28 | type TransactionFee struct { 29 | FeeToken string `json:"feeToken"` 30 | Fee *big.Int `json:"fee"` 31 | } 32 | -------------------------------------------------------------------------------- /IntegrationTests/Integration_test.go: -------------------------------------------------------------------------------- 1 | package IntegrationTests 2 | 3 | import ( 4 | "context" 5 | "crypto/ecdsa" 6 | "fmt" 7 | "github.com/ethereum/go-ethereum" 8 | "github.com/ethereum/go-ethereum/common" 9 | "github.com/ethereum/go-ethereum/core/types" 10 | "github.com/ethereum/go-ethereum/crypto" 11 | "github.com/ethereum/go-ethereum/ethclient" 12 | "github.com/miguelmota/go-ethereum-hdwallet" 13 | "github.com/pkg/errors" 14 | "github.com/stretchr/testify/require" 15 | "github.com/zksync-sdk/zksync-go" 16 | "math/big" 17 | "testing" 18 | "time" 19 | ) 20 | 21 | var ( 22 | masterPkHex = "0945012b971f943073f6e066581f513c8cd81660bb5a64306d5d092b8df9dd3f" 23 | ethNode = "https://rinkeby.infura.io/v3/511e4b90f71747658a167e9737ed544a" 24 | testAmount int64 = 2000000000000000 // in wei (0.002 ETH) 25 | testAmountUSDC int64 = 2000000 // 2 USDC 26 | depositAmount int64 = 1000000000000000 // in wei (0.001 ETH) 27 | depositAmountUSDC int64 = 1000000 // 1 USDC 28 | transferAmount = big.NewInt(100000000000000) // in wei (0.0001 ETH) 29 | withdrawAmount = big.NewInt(100000000000000) // in wei (0.0001 ETH) 30 | swapAmount = big.NewInt(500000) // 0.5 USDC 31 | txWaitTimeout = time.Minute * 15 32 | txCheckInterval = time.Second * 2 33 | rinkebyUSDCsc = "0xeb8f08a975ab53e34d8a0330e0d34de942c95926" // USDC smart-contract address on Rinkeby 34 | 35 | err error 36 | ethClient *ethclient.Client 37 | ) 38 | 39 | func TestFullFlow(t *testing.T) { 40 | ethClient, err = ethclient.Dial(ethNode) 41 | require.NoError(t, err) 42 | require.NotNil(t, ethClient) 43 | var w1 *zksync.Wallet 44 | var w2 *zksync.Wallet 45 | var ep1 zksync.EthProvider 46 | 47 | t.Run("wallet 1", func(t *testing.T) { 48 | w, zs := newWallet(t, "") 49 | w1 = w 50 | ep, err := w.CreateEthereumProvider(ethClient) 51 | require.NoError(t, err) 52 | require.NotNil(t, ep) 53 | ep1 = ep 54 | 55 | //// 0 - fulfill new wallet with some test balance 56 | fulfillment(t, w.GetAddress()) 57 | // also send some USDC 58 | fulfillmentUSDC(t, w.GetAddress()) 59 | 60 | // 1 - deposit amount to zkSync 61 | deposit(t, w, ep) 62 | waitZkAccount(t, w.GetProvider(), w.GetAddress()) 63 | 64 | // deposit USDC also 65 | allTokens, err := w1.GetProvider().GetTokens() 66 | require.NoError(t, err) 67 | require.NotNil(t, allTokens) 68 | usdcToken, err := allTokens.GetToken("USDC") 69 | require.NoError(t, err) 70 | require.NotNil(t, usdcToken) 71 | depositUSDC(t, w1, ep1, usdcToken) 72 | 73 | // 2 - SetAuthPubkeyHash for next ChangePubKeyOnchain 74 | state, err := w.GetState() 75 | require.NoError(t, err) 76 | require.NotNil(t, state) 77 | require.NotNil(t, state.Committed) 78 | isPkhSet, err := ep.IsOnChainAuthPubkeyHashSet(state.Committed.Nonce) 79 | require.NoError(t, err) 80 | require.False(t, isPkhSet) 81 | tx, err := ep.SetAuthPubkeyHash(zs.GetPublicKeyHash(), state.Committed.Nonce, nil) 82 | require.NoError(t, err) 83 | require.NotNil(t, tx) 84 | waitEthTx(t, context.Background(), tx.Hash(), "setAuthPubkeyHash") 85 | 86 | // 3 - ChangePubKeyOnchain 87 | is := w.IsSigningKeySet() 88 | require.False(t, is) 89 | fee, err := w.GetProvider().GetTransactionFee(zksync.TransactionTypeChangePubKeyOnchain, w.GetAddress(), zksync.CreateETH()) 90 | require.NoError(t, err) 91 | require.NotNil(t, fee) 92 | state, err = w.GetState() 93 | require.NoError(t, err) 94 | require.NotNil(t, state) 95 | txHash, err := w.SetSigningKey(fee.GetTxFee(zksync.CreateETH()), state.Committed.Nonce, true, zksync.DefaultTimeRange()) 96 | require.NoError(t, err) 97 | require.NotEmpty(t, txHash) 98 | err = waitZkTx(w.GetProvider(), txHash, "ChangePubKeyOnchain") 99 | require.NoError(t, err) 100 | }) 101 | 102 | t.Run("wallet 2", func(t *testing.T) { 103 | w, _ := newWallet(t, "") 104 | w2 = w 105 | ep, err := w.CreateEthereumProvider(ethClient) 106 | require.NoError(t, err) 107 | require.NotNil(t, ep) 108 | 109 | //// 0 - fulfill new wallet with some test balance 110 | fulfillment(t, w.GetAddress()) 111 | 112 | // 1 - deposit amount to zkSync 113 | deposit(t, w, ep) 114 | waitZkAccount(t, w.GetProvider(), w.GetAddress()) 115 | 116 | // 2 - ChangePubKeyECDSA 117 | state, err := w.GetState() 118 | require.NoError(t, err) 119 | require.NotNil(t, state) 120 | require.NotNil(t, state.Committed) 121 | is := w.IsSigningKeySet() 122 | require.False(t, is) 123 | fee, err := w.GetProvider().GetTransactionFee(zksync.TransactionTypeChangePubKeyECDSA, w.GetAddress(), zksync.CreateETH()) 124 | require.NoError(t, err) 125 | require.NotNil(t, fee) 126 | txHash, err := w.SetSigningKey(fee.GetTxFee(zksync.CreateETH()), state.Committed.Nonce, false, zksync.DefaultTimeRange()) 127 | require.NoError(t, err) 128 | require.NotEmpty(t, txHash) 129 | err = waitZkTx(w.GetProvider(), txHash, "ChangePubKeyECDSA") 130 | require.NoError(t, err) 131 | }) 132 | 133 | t.Run("transfer", func(t *testing.T) { 134 | state, err := w1.GetState() 135 | require.NoError(t, err) 136 | require.NotNil(t, state) 137 | require.NotNil(t, state.Committed) 138 | fee, err := w1.GetProvider().GetTransactionFee(zksync.TransactionTypeTransfer, w1.GetAddress(), zksync.CreateETH()) 139 | require.NoError(t, err) 140 | require.NotNil(t, fee) 141 | txHash, err := w1.SyncTransfer(w2.GetAddress(), transferAmount, fee.GetTxFee(zksync.CreateETH()), state.Committed.Nonce, zksync.DefaultTimeRange()) 142 | require.NoError(t, err) 143 | require.NotEmpty(t, txHash) 144 | err = waitZkTx(w1.GetProvider(), txHash, "SyncTransfer") 145 | require.NoError(t, err) 146 | }) 147 | 148 | t.Run("withdraw", func(t *testing.T) { 149 | state, err := w2.GetState() 150 | require.NoError(t, err) 151 | require.NotNil(t, state) 152 | require.NotNil(t, state.Committed) 153 | fee, err := w2.GetProvider().GetTransactionFee(zksync.TransactionTypeWithdraw, w2.GetAddress(), zksync.CreateETH()) 154 | require.NoError(t, err) 155 | require.NotNil(t, fee) 156 | txHash, err := w2.SyncWithdraw(w2.GetAddress(), withdrawAmount, fee.GetTxFee(zksync.CreateETH()), state.Committed.Nonce, false, zksync.DefaultTimeRange()) 157 | require.NoError(t, err) 158 | require.NotEmpty(t, txHash) 159 | err = waitZkTx(w2.GetProvider(), txHash, "SyncWithdraw") 160 | require.NoError(t, err) 161 | }) 162 | 163 | t.Run("mint NFT 1", func(t *testing.T) { 164 | state, err := w1.GetState() 165 | require.NoError(t, err) 166 | require.NotNil(t, state) 167 | require.NotNil(t, state.Committed) 168 | fee, err := w1.GetProvider().GetTransactionFee(zksync.TransactionTypeMintNFT, w1.GetAddress(), zksync.CreateETH()) 169 | require.NoError(t, err) 170 | require.NotNil(t, fee) 171 | txHash, err := w1.SyncMintNFT(w1.GetAddress(), common.HexToHash("1111"), fee.GetTxFee(zksync.CreateETH()), state.Committed.Nonce) 172 | require.NoError(t, err) 173 | require.NotEmpty(t, txHash) 174 | err = waitZkTx(w1.GetProvider(), txHash, "SyncMintNFT") 175 | require.NoError(t, err) 176 | }) 177 | 178 | t.Run("mint NFT 2", func(t *testing.T) { 179 | state, err := w2.GetState() 180 | require.NoError(t, err) 181 | require.NotNil(t, state) 182 | require.NotNil(t, state.Committed) 183 | fee, err := w2.GetProvider().GetTransactionFee(zksync.TransactionTypeMintNFT, w2.GetAddress(), zksync.CreateETH()) 184 | require.NoError(t, err) 185 | require.NotNil(t, fee) 186 | txHash, err := w2.SyncMintNFT(w2.GetAddress(), common.HexToHash("2222"), fee.GetTxFee(zksync.CreateETH()), state.Committed.Nonce) 187 | require.NoError(t, err) 188 | require.NotEmpty(t, txHash) 189 | err = waitZkTx(w2.GetProvider(), txHash, "SyncMintNFT") 190 | require.NoError(t, err) 191 | }) 192 | 193 | // can't test transfer of just minted NFT because mint tx must be verified (too much time to wait) 194 | //t.Run("transfer NFT", func(t *testing.T) { 195 | // state, err := w1.GetState() 196 | // require.NoError(t, err) 197 | // require.NotNil(t, state) 198 | // require.NotNil(t, state.Committed) 199 | // require.NotNil(t, state.Verified) 200 | // require.NotNil(t, state.Verified.Nfts) 201 | // require.GreaterOrEqual(t, len(state.Verified.Nfts), 1) 202 | // var nft *zksync.NFT 203 | // for _, nft = range state.Verified.Nfts { 204 | // break // get some first NFT 205 | // } 206 | // fee, err := w1.GetProvider().GetTransactionsBatchFee( 207 | // []zksync.TransactionType{zksync.TransactionTypeTransfer, zksync.TransactionTypeTransfer}, 208 | // []common.Address{w1.GetAddress(), w1.GetAddress()}, zksync.CreateETH()) 209 | // require.NoError(t, err) 210 | // require.NotNil(t, fee) 211 | // 212 | // txHashes, err := w1.SyncTransferNFT(w2.GetAddress(), nft, fee.GetTxFee(zksync.CreateETH()), state.Committed.Nonce, zksync.DefaultTimeRange()) 213 | // require.NoError(t, err) 214 | // require.NotEmpty(t, txHashes) 215 | // require.Equal(t, len(txHashes), 2) 216 | // err = waitZkTx(w1.GetProvider(), txHashes[0], "SyncTransferNFT") 217 | // require.NoError(t, err) 218 | //}) 219 | 220 | t.Run("full exit NFT 1", func(t *testing.T) { 221 | state, err := w1.GetState() 222 | require.NoError(t, err) 223 | require.NotNil(t, state) 224 | require.NotNil(t, state.Committed) 225 | require.NotNil(t, state.Committed.Nfts) 226 | require.GreaterOrEqual(t, len(state.Committed.Nfts), 1) 227 | var nft *zksync.NFT 228 | for _, nft = range state.Committed.Nfts { 229 | break // get some first NFT 230 | } 231 | tx, err := ep1.FullExitNFT(nft, 0, &zksync.GasOptions{GasLimit: 300000}) 232 | require.NoError(t, err) 233 | require.NotNil(t, tx) 234 | waitEthTx(t, context.Background(), tx.Hash(), "FullExitNFT") 235 | }) 236 | 237 | t.Run("withdraw NFT 2", func(t *testing.T) { 238 | state, err := w2.GetState() 239 | require.NoError(t, err) 240 | require.NotNil(t, state) 241 | require.NotNil(t, state.Committed) 242 | require.NotNil(t, state.Committed.Nfts) 243 | require.GreaterOrEqual(t, len(state.Committed.Nfts), 1) 244 | var nft *zksync.NFT 245 | for _, nft = range state.Committed.Nfts { 246 | break // get some first NFT 247 | } 248 | fee, err := w2.GetProvider().GetTransactionFee(zksync.TransactionTypeWithdrawNFT, w2.GetAddress(), zksync.CreateETH()) 249 | require.NoError(t, err) 250 | require.NotNil(t, fee) 251 | txHash, err := w2.SyncWithdrawNFT(w2.GetAddress(), nft, fee.GetTxFee(zksync.CreateETH()), state.Committed.Nonce, zksync.DefaultTimeRange()) 252 | require.NoError(t, err) 253 | require.NotEmpty(t, txHash) 254 | err = waitZkTx(w2.GetProvider(), txHash, "SyncWithdrawNFT") 255 | require.NoError(t, err) 256 | }) 257 | 258 | t.Run("swap", func(t *testing.T) { 259 | allTokens, err := w1.GetProvider().GetTokens() 260 | require.NoError(t, err) 261 | require.NotNil(t, allTokens) 262 | usdcToken, err := allTokens.GetToken("USDC") 263 | require.NoError(t, err) 264 | require.NotNil(t, usdcToken) 265 | 266 | fee, err := w1.GetProvider().GetTransactionsBatchFee( 267 | []zksync.TransactionType{zksync.TransactionTypeSwap}, 268 | []common.Address{w1.GetAddress()}, zksync.CreateETH()) 269 | require.NoError(t, err) 270 | require.NotNil(t, fee) 271 | 272 | state1, err := w1.GetState() 273 | require.NoError(t, err) 274 | require.NotNil(t, state1) 275 | require.NotNil(t, state1.Committed) 276 | o1, err := w1.BuildSignedOrder(w1.GetAddress(), usdcToken, zksync.CreateETH(), []*big.Int{big.NewInt(1), big.NewInt(10)}, swapAmount, state1.Committed.Nonce, zksync.DefaultTimeRange()) 277 | require.NoError(t, err) 278 | require.NotNil(t, o1) 279 | 280 | state2, err := w2.GetState() 281 | require.NoError(t, err) 282 | require.NotNil(t, state2) 283 | require.NotNil(t, state2.Committed) 284 | o2, err := w2.BuildSignedOrder(w2.GetAddress(), zksync.CreateETH(), usdcToken, []*big.Int{big.NewInt(10), big.NewInt(1)}, big.NewInt(0).Mul(swapAmount, big.NewInt(10)), state2.Committed.Nonce, zksync.DefaultTimeRange()) 285 | require.NoError(t, err) 286 | require.NotNil(t, o2) 287 | 288 | txHash, err := w1.SyncSwap(o1, o2, o1.Amount, o2.Amount, fee.GetTxFee(zksync.CreateETH()), state1.Committed.Nonce) 289 | require.NoError(t, err) 290 | require.NotEmpty(t, txHash) 291 | err = waitZkTx(w1.GetProvider(), txHash, "SyncSwap") 292 | require.NoError(t, err) 293 | }) 294 | 295 | // can be tested due to requirements: Target account exists less than required minimum amount (1 hours) 296 | //t.Run("forced exit", func(t *testing.T) { 297 | // state, err := w1.GetState() 298 | // require.NoError(t, err) 299 | // require.NotNil(t, state) 300 | // require.NotNil(t, state.Committed) 301 | // fee, err := w1.GetProvider().GetTransactionFee(zksync.TransactionTypeForcedExit, w1.GetAddress(), zksync.CreateETH()) 302 | // require.NoError(t, err) 303 | // require.NotNil(t, fee) 304 | // txHash, err := w1.SyncForcedExit(w1.GetAddress(), fee.GetTxFee(zksync.CreateETH()), state.Committed.Nonce, zksync.DefaultTimeRange()) 305 | // require.NoError(t, err) 306 | // require.NotEmpty(t, txHash) 307 | // waitZkTx(t, w1.GetProvider(), txHash, "ForcedExit") 308 | //}) 309 | 310 | t.Run("full exit", func(t *testing.T) { 311 | tx, err := ep1.FullExit(zksync.CreateETH(), 0, &zksync.GasOptions{GasLimit: 300000}) 312 | require.NoError(t, err) 313 | require.NotNil(t, tx) 314 | waitEthTx(t, context.Background(), tx.Hash(), "FullExit") 315 | }) 316 | 317 | } 318 | 319 | func newWallet(t *testing.T, mnemonic string) (*zksync.Wallet, *zksync.ZkSigner) { 320 | var err error 321 | if len(mnemonic) == 0 { 322 | mnemonic, err = hdwallet.NewMnemonic(160) 323 | require.NoError(t, err) 324 | require.NotEmpty(t, mnemonic) 325 | //mnemonic = "actor feature blade risk rocket behind wide indicate frequent upset session crane tape dentist hundred" 326 | } 327 | fmt.Printf("mnemonic - %s\n", mnemonic) 328 | 329 | seed, err := hdwallet.NewSeedFromMnemonic(mnemonic) 330 | require.NoError(t, err) 331 | require.NotEmpty(t, seed) 332 | 333 | // create zkSync signer from seed 334 | zs, err := zksync.NewZkSignerFromSeed(seed) 335 | require.NoError(t, err) 336 | require.NotNil(t, zs.GetPublicKeyHash()) 337 | //fmt.Printf("zkSigner pubkey - %+v\n", zs.GetPublicKey()) 338 | //fmt.Printf("zkSigner pkh - %+v\n", zs.GetPublicKeyHash()) 339 | 340 | // create ethereum signer from mnemonic 341 | es, err := zksync.NewEthSignerFromMnemonic(mnemonic) 342 | require.NoError(t, err) 343 | require.NotNil(t, es) 344 | fmt.Printf("ethSigner address - %+v\n", es.GetAddress().String()) 345 | 346 | // create zkSync provider for specified chainId 347 | zp, err := zksync.NewDefaultProviderFor(zksync.ChainIdRinkeby) 348 | require.NoError(t, err) 349 | require.NotNil(t, zp) 350 | 351 | // create wallet 352 | w, err := zksync.NewWallet(es, zs, zp) 353 | require.NoError(t, err) 354 | require.NotNil(t, w) 355 | return w, zs 356 | } 357 | 358 | func fulfillment(t *testing.T, toAddress common.Address) { 359 | ctx, cancel := context.WithTimeout(context.Background(), time.Minute) 360 | defer cancel() 361 | masterPrivateKey, err := crypto.HexToECDSA(masterPkHex) 362 | require.NoError(t, err) 363 | masterPublicKey := masterPrivateKey.Public() 364 | masterPublicKeyECDSA, ok := masterPublicKey.(*ecdsa.PublicKey) 365 | require.True(t, ok) 366 | masterAddress := crypto.PubkeyToAddress(*masterPublicKeyECDSA) 367 | nonce, err := ethClient.PendingNonceAt(ctx, masterAddress) 368 | require.NoError(t, err) 369 | 370 | value := big.NewInt(testAmount) 371 | gasLimit := uint64(21000) // in units 372 | gasPrice, err := ethClient.SuggestGasPrice(ctx) 373 | require.NoError(t, err) 374 | 375 | var data []byte 376 | tx := types.NewTransaction(nonce, toAddress, value, gasLimit, gasPrice, data) 377 | 378 | chainID, err := ethClient.NetworkID(ctx) 379 | require.NoError(t, err) 380 | 381 | signedTx, err := types.SignTx(tx, types.NewEIP155Signer(chainID), masterPrivateKey) 382 | require.NoError(t, err) 383 | 384 | err = ethClient.SendTransaction(ctx, signedTx) 385 | require.NoError(t, err) 386 | 387 | waitEthTx(t, ctx, signedTx.Hash(), "fulfillment") 388 | } 389 | 390 | func fulfillmentUSDC(t *testing.T, toAddress common.Address) { 391 | ctx, cancel := context.WithTimeout(context.Background(), time.Minute) 392 | defer cancel() 393 | masterPrivateKey, err := crypto.HexToECDSA(masterPkHex) 394 | require.NoError(t, err) 395 | masterPublicKey := masterPrivateKey.Public() 396 | masterPublicKeyECDSA, ok := masterPublicKey.(*ecdsa.PublicKey) 397 | require.True(t, ok) 398 | masterAddress := crypto.PubkeyToAddress(*masterPublicKeyECDSA) 399 | nonce, err := ethClient.PendingNonceAt(ctx, masterAddress) 400 | require.NoError(t, err) 401 | 402 | value := big.NewInt(0) // 0 ETH 403 | gasPrice, err := ethClient.SuggestGasPrice(ctx) 404 | require.NoError(t, err) 405 | 406 | transferFnSignature := []byte("transfer(address,uint256)") 407 | methodID := crypto.Keccak256(transferFnSignature)[:4] 408 | 409 | paddedAddress := common.LeftPadBytes(toAddress.Bytes(), 32) 410 | valueUSDC := big.NewInt(testAmountUSDC) 411 | paddedAmount := common.LeftPadBytes(valueUSDC.Bytes(), 32) 412 | 413 | var data []byte 414 | data = append(data, methodID...) 415 | data = append(data, paddedAddress...) 416 | data = append(data, paddedAmount...) 417 | 418 | tx := types.NewTransaction(nonce, common.HexToAddress(rinkebyUSDCsc), value, uint64(75000), gasPrice, data) 419 | 420 | chainID, err := ethClient.NetworkID(ctx) 421 | require.NoError(t, err) 422 | 423 | signedTx, err := types.SignTx(tx, types.NewEIP155Signer(chainID), masterPrivateKey) 424 | require.NoError(t, err) 425 | 426 | err = ethClient.SendTransaction(ctx, signedTx) 427 | require.NoError(t, err) 428 | 429 | waitEthTx(t, ctx, signedTx.Hash(), "fulfillment USDC") 430 | } 431 | 432 | func deposit(t *testing.T, w *zksync.Wallet, ep zksync.EthProvider) { 433 | tx, err := ep.Deposit(zksync.CreateETH(), big.NewInt(depositAmount), w.GetAddress(), nil) 434 | require.NoError(t, err) 435 | require.NotNil(t, tx) 436 | 437 | waitEthTx(t, context.Background(), tx.Hash(), "deposit") 438 | } 439 | 440 | func depositUSDC(t *testing.T, w *zksync.Wallet, ep zksync.EthProvider, token *zksync.Token) { 441 | tx1, err := ep.ApproveDeposits(token, big.NewInt(testAmountUSDC), &zksync.GasOptions{GasLimit: 300000}) 442 | require.NoError(t, err) 443 | require.NotNil(t, tx1) 444 | waitEthTx(t, context.Background(), tx1.Hash(), "approve deposit USDC") 445 | 446 | tx2, err := ep.Deposit(token, big.NewInt(depositAmountUSDC), w.GetAddress(), &zksync.GasOptions{GasLimit: 300000}) 447 | require.NoError(t, err) 448 | require.NotNil(t, tx2) 449 | waitEthTx(t, context.Background(), tx2.Hash(), "deposit USDC") 450 | } 451 | 452 | func waitEthTx(t *testing.T, ctx context.Context, txHash common.Hash, title string) { 453 | fmt.Print("Waiting for ", title, " Eth Tx ", txHash.String()) 454 | ctx2, cancel := context.WithTimeout(ctx, txWaitTimeout) 455 | defer cancel() 456 | isPending := true 457 | retries := 10 458 | for isPending && err == nil && retries > 0 { 459 | select { 460 | case <-time.After(txCheckInterval): 461 | fmt.Print(".") 462 | _, isPending, err = ethClient.TransactionByHash(ctx2, txHash) 463 | if err == ethereum.NotFound { 464 | // sometimes it returns this false error, so try again 465 | retries-- 466 | continue 467 | } 468 | retries = 10 469 | case <-ctx2.Done(): 470 | err = errors.New("context timeout") 471 | } 472 | } 473 | require.NoError(t, err) 474 | tr, err := ethClient.TransactionReceipt(ctx2, txHash) 475 | if err == ethereum.NotFound { 476 | // wait a bit and retry once 477 | time.Sleep(txCheckInterval) 478 | tr, err = ethClient.TransactionReceipt(ctx2, txHash) 479 | } 480 | require.NoError(t, err) 481 | require.EqualValues(t, 1, tr.Status) 482 | fmt.Print("DONE\n") 483 | } 484 | 485 | func waitZkTx(zp zksync.Provider, txHash string, title string) error { 486 | fmt.Print("Waiting for ", title, " ZkSync Tx ", txHash) 487 | ctx, cancel := context.WithTimeout(context.Background(), txWaitTimeout) 488 | defer cancel() 489 | var err error 490 | var td *zksync.TransactionDetails 491 | var isCommitted bool 492 | for !isCommitted && err == nil { 493 | select { 494 | case <-time.After(txCheckInterval): 495 | fmt.Print(".") 496 | td, err = zp.GetTransactionDetails(txHash) 497 | if err != nil { 498 | break 499 | } 500 | if td.Block != nil && td.Block.Committed { 501 | isCommitted = true 502 | } 503 | case <-ctx.Done(): 504 | err = errors.New("context timeout") 505 | } 506 | } 507 | if err == nil && td != nil && !td.Success && len(td.FailReason) > 0 { 508 | err = errors.New(td.FailReason) 509 | } 510 | if err != nil { 511 | fmt.Println("FAIL") 512 | } else { 513 | fmt.Println("DONE") 514 | } 515 | return err 516 | } 517 | 518 | func waitZkAccount(t *testing.T, zp zksync.Provider, address common.Address) { 519 | fmt.Print("Waiting for new ZkSync account ", address) 520 | ctx, cancel := context.WithTimeout(context.Background(), txWaitTimeout) 521 | defer cancel() 522 | var err error 523 | var as *zksync.AccountState 524 | var isCreated bool 525 | for !isCreated && err == nil { 526 | select { 527 | case <-time.After(txCheckInterval): 528 | fmt.Print(".") 529 | as, err = zp.GetState(address) 530 | require.NoError(t, err) 531 | if as.Id > 0 { 532 | isCreated = true 533 | } 534 | case <-ctx.Done(): 535 | err = errors.New("context timeout") 536 | } 537 | } 538 | require.NoError(t, err) 539 | fmt.Print("DONE\n") 540 | } 541 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | all: tests 2 | 3 | download: 4 | mkdir -p libs 5 | ifeq ($(shell uname -s),Darwin) 6 | test -f ./libs/libzks-crypto.a || curl -L https://github.com/zksync-sdk/zksync-crypto-c/releases/download/v0.1.2/zks-crypto-x86_64-apple-darwin.a --output ./libs/libzks-crypto.a 7 | else 8 | test -f ./libs/libzks-crypto.so || curl -L https://github.com/zksync-sdk/zksync-crypto-c/releases/download/v0.1.2/zks-crypto-x86_64-unknown-linux-gnu.so --output ./libs/libzks-crypto.so 9 | endif 10 | 11 | tests: download 12 | CGO_LDFLAGS="-L./libs" LD_LIBRARY_PATH="./libs" go test -race -v -count=1 . 13 | 14 | integration-test: download 15 | CGO_LDFLAGS="-L./libs" LD_LIBRARY_PATH="../libs" go test -race -v -count=1 -timeout=30m ./IntegrationTests 16 | 17 | generate: 18 | go install github.com/vektra/mockery/cmd/mockery 19 | go install github.com/golang/mock/mockgen 20 | go generate . 21 | -------------------------------------------------------------------------------- /NFT.go: -------------------------------------------------------------------------------- 1 | package zksync 2 | 3 | import ( 4 | "github.com/ethereum/go-ethereum/common" 5 | ) 6 | 7 | const ( 8 | TransactionTypeMintNFT TransactionType = "MintNFT" 9 | TransactionTypeWithdrawNFT TransactionType = "WithdrawNFT" 10 | ) 11 | 12 | type NFT struct { 13 | Id uint32 `json:"id"` 14 | Symbol string `json:"symbol"` 15 | CreatorId uint32 `json:"creatorId"` 16 | ContentHash common.Hash `json:"contentHash"` 17 | CreatorAddress common.Address `json:"creatorAddress"` 18 | SerialId uint32 `json:"serialId"` 19 | Address string `json:"address"` 20 | } 21 | 22 | func (t *NFT) ToToken() *Token { 23 | return &Token{ 24 | Id: t.Id, 25 | Address: t.Address, 26 | Symbol: t.Symbol, 27 | Decimals: 0, 28 | IsNft: true, 29 | } 30 | } 31 | 32 | type MintNFT struct { 33 | Type string `json:"type"` 34 | CreatorId uint32 `json:"creatorId"` 35 | CreatorAddress common.Address `json:"creatorAddress"` 36 | ContentHash common.Hash `json:"contentHash"` 37 | Recipient common.Address `json:"recipient"` 38 | Fee string `json:"fee"` 39 | FeeToken uint32 `json:"feeToken"` 40 | Nonce uint32 `json:"nonce"` 41 | Signature *Signature `json:"signature"` 42 | } 43 | 44 | func (t *MintNFT) getType() string { 45 | return "MintNFT" 46 | } 47 | 48 | type WithdrawNFT struct { 49 | Type string `json:"type"` 50 | AccountId uint32 `json:"accountId"` 51 | From common.Address `json:"from"` 52 | To common.Address `json:"to"` 53 | Token uint32 `json:"token"` 54 | FeeToken uint32 `json:"feeToken"` 55 | Fee string `json:"fee"` 56 | Nonce uint32 `json:"nonce"` 57 | Signature *Signature `json:"signature"` 58 | *TimeRange 59 | } 60 | 61 | func (t *WithdrawNFT) getType() string { 62 | return "WithdrawNFT" 63 | } 64 | -------------------------------------------------------------------------------- /Provider.go: -------------------------------------------------------------------------------- 1 | package zksync 2 | 3 | import ( 4 | "github.com/ethereum/go-ethereum/common" 5 | "github.com/ethereum/go-ethereum/rpc" 6 | "github.com/pkg/errors" 7 | "math/big" 8 | ) 9 | 10 | //go:generate mockery -name=Provider -output=./ -outpkg=zksync -filename=ProviderMock_test.go -structname=ProviderMock -inpkg 11 | 12 | type Provider interface { 13 | GetTokens() (*Tokens, error) 14 | UpdateTokenSet() error 15 | GetTokenPrice(token *Token) (*big.Float, error) 16 | ContractAddress() (*ContractAddress, error) 17 | GetState(address common.Address) (*AccountState, error) 18 | GetTransactionFee(txType TransactionType, address common.Address, token *Token) (*TransactionFeeDetails, error) 19 | GetTransactionsBatchFee(txTypes []TransactionType, addresses []common.Address, token *Token) (*TransactionFeeDetails, error) 20 | SubmitTx(signedTx ZksTransaction, ethSignature *EthSignature, fastProcessing bool) (string, error) 21 | SubmitTxMultiSig(signedTx ZksTransaction, ethSignatures ...*EthSignature) (string, error) 22 | SubmitTxsBatch(signedTxs []*SignedTransaction, ethSignature *EthSignature) ([]string, error) 23 | GetTransactionDetails(txHash string) (*TransactionDetails, error) 24 | GetConfirmationsForEthOpAmount() (*big.Int, error) 25 | GetEthOpInfo(priority uint64) (*EthOpInfo, error) 26 | } 27 | 28 | func NewDefaultProvider(rawUrl string) (*DefaultProvider, error) { 29 | client, err := rpc.Dial(rawUrl) 30 | if err != nil { 31 | return nil, errors.Wrap(err, "failed to dial client") 32 | } 33 | return &DefaultProvider{ 34 | client: client, 35 | }, nil 36 | } 37 | 38 | func NewDefaultProviderFor(cid ChainId) (*DefaultProvider, error) { 39 | var rawUrl string 40 | switch cid { 41 | case ChainIdMainnet: 42 | rawUrl = `https://api.zksync.io/jsrpc` 43 | case ChainIdRinkeby: 44 | rawUrl = `https://rinkeby-api.zksync.io/jsrpc` 45 | case ChainIdRopsten: 46 | rawUrl = `https://ropsten-api.zksync.io/jsrpc` 47 | case ChainIdLocalhost: 48 | rawUrl = `http://127.0.0.1:3030` 49 | } 50 | return NewDefaultProvider(rawUrl) 51 | } 52 | 53 | type DefaultProvider struct { 54 | client *rpc.Client 55 | tokens *Tokens 56 | } 57 | 58 | func (p *DefaultProvider) GetTokens() (*Tokens, error) { 59 | if p.tokens == nil { 60 | if err := p.UpdateTokenSet(); err != nil { 61 | return nil, errors.Wrap(err, "failed to get tokens") 62 | } 63 | } 64 | return p.tokens, nil 65 | } 66 | 67 | func (p *DefaultProvider) UpdateTokenSet() error { 68 | res := make(map[string]*Token) 69 | err := p.client.Call(&res, "tokens") 70 | if err != nil { 71 | return errors.Wrap(err, "failed to call `tokens` method") 72 | } 73 | p.tokens = &Tokens{ 74 | Tokens: res, 75 | } 76 | return nil 77 | } 78 | 79 | func (p *DefaultProvider) GetTokenPrice(token *Token) (*big.Float, error) { 80 | var resp string 81 | err := p.client.Call(&resp, "get_token_price", token.Symbol) 82 | if err != nil { 83 | return nil, errors.Wrap(err, "failed to call `get_token_price` method") 84 | } 85 | res, ok := big.NewFloat(0).SetString(resp) 86 | if !ok { 87 | return nil, errors.Wrap(err, "failed to parse response") 88 | } 89 | return res, nil 90 | } 91 | 92 | func (p *DefaultProvider) ContractAddress() (*ContractAddress, error) { 93 | res := new(ContractAddress) 94 | err := p.client.Call(&res, "contract_address") 95 | if err != nil { 96 | return nil, errors.Wrap(err, "failed to call `contract_address` method") 97 | } 98 | return res, nil 99 | } 100 | 101 | func (p *DefaultProvider) GetState(address common.Address) (*AccountState, error) { 102 | res := new(AccountState) 103 | err := p.client.Call(&res, "account_info", address.String()) 104 | if err != nil { 105 | return nil, errors.Wrap(err, "failed to call `account_info` method") 106 | } 107 | return res, nil 108 | } 109 | 110 | func (p *DefaultProvider) GetTransactionFee(txType TransactionType, address common.Address, token *Token) (*TransactionFeeDetails, error) { 111 | res := new(TransactionFeeDetails) 112 | err := p.client.Call(&res, "get_tx_fee", txType.getType(), address.String(), token.Symbol) 113 | if err != nil { 114 | return nil, errors.Wrap(err, "failed to call `get_tx_fee` method") 115 | } 116 | return res, nil 117 | } 118 | 119 | func (p *DefaultProvider) GetTransactionsBatchFee(txTypes []TransactionType, addresses []common.Address, token *Token) (*TransactionFeeDetails, error) { 120 | res := new(TransactionFeeDetails) 121 | if len(txTypes) != len(addresses) { 122 | return nil, errors.New("count of Transaction Types and addresses is mismatch") 123 | } 124 | txTypesList := make([]string, len(txTypes)) 125 | addressesList := make([]string, len(addresses)) 126 | for i, t := range txTypes { 127 | if txType, ok := t.getType().(string); ok { 128 | txTypesList[i] = txType 129 | } else { 130 | return nil, errors.New("invalid transaction Type for batch fee request") 131 | } 132 | addressesList[i] = addresses[i].String() 133 | } 134 | err := p.client.Call(&res, "get_txs_batch_fee_in_wei", txTypesList, addressesList, token.Symbol) 135 | if err != nil { 136 | return nil, errors.Wrap(err, "failed to call `get_txs_batch_fee_in_wei` method") 137 | } 138 | return res, nil 139 | } 140 | 141 | func (p *DefaultProvider) GetTransactionDetails(txHash string) (*TransactionDetails, error) { 142 | res := new(TransactionDetails) 143 | err := p.client.Call(&res, "tx_info", txHash) 144 | if err != nil { 145 | return nil, errors.Wrap(err, "failed to call `tx_info` method") 146 | } 147 | return res, nil 148 | } 149 | 150 | func (p *DefaultProvider) SubmitTx(signedTx ZksTransaction, ethSignature *EthSignature, fastProcessing bool) (string, error) { 151 | var res string 152 | err := p.client.Call(&res, "tx_submit", signedTx, ethSignature, fastProcessing) 153 | if err != nil { 154 | return "", errors.Wrap(err, "failed to call `tx_submit` method") 155 | } 156 | return res, nil 157 | } 158 | 159 | func (p *DefaultProvider) SubmitTxMultiSig(signedTx ZksTransaction, ethSignatures ...*EthSignature) (string, error) { 160 | var res string 161 | err := p.client.Call(&res, "tx_submit", signedTx, ethSignatures) 162 | if err != nil { 163 | return "", errors.Wrap(err, "failed to call `tx_submit` method") 164 | } 165 | return res, nil 166 | } 167 | 168 | func (p *DefaultProvider) SubmitTxsBatch(signedTxs []*SignedTransaction, ethSignature *EthSignature) ([]string, error) { 169 | res := make([]string, len(signedTxs)) 170 | signatures := make([]*EthSignature, 0) 171 | if ethSignature != nil { 172 | signatures = append(signatures, ethSignature) 173 | } 174 | err := p.client.Call(&res, "submit_txs_batch", signedTxs, signatures) 175 | if err != nil { 176 | return nil, errors.Wrap(err, "failed to call `submit_txs_batch` method") 177 | } 178 | return res, nil 179 | } 180 | 181 | func (p *DefaultProvider) GetConfirmationsForEthOpAmount() (*big.Int, error) { 182 | var resp int64 183 | err := p.client.Call(&resp, "get_confirmations_for_eth_op_amount") 184 | if err != nil { 185 | return nil, errors.Wrap(err, "failed to call `get_confirmations_for_eth_op_amount` method") 186 | } 187 | return big.NewInt(resp), nil 188 | } 189 | 190 | func (p *DefaultProvider) GetEthOpInfo(priority uint64) (*EthOpInfo, error) { 191 | res := new(EthOpInfo) 192 | err := p.client.Call(&res, "ethop_info", priority) 193 | if err != nil { 194 | return nil, errors.Wrap(err, "failed to call `ethop_info` method") 195 | } 196 | return res, nil 197 | } 198 | -------------------------------------------------------------------------------- /ProviderMock_test.go: -------------------------------------------------------------------------------- 1 | // Code generated by mockery v1.0.0. DO NOT EDIT. 2 | 3 | package zksync 4 | 5 | import ( 6 | big "math/big" 7 | 8 | common "github.com/ethereum/go-ethereum/common" 9 | mock "github.com/stretchr/testify/mock" 10 | ) 11 | 12 | // ProviderMock is an autogenerated mock type for the Provider type 13 | type ProviderMock struct { 14 | mock.Mock 15 | } 16 | 17 | // ContractAddress provides a mock function with given fields: 18 | func (_m *ProviderMock) ContractAddress() (*ContractAddress, error) { 19 | ret := _m.Called() 20 | 21 | var r0 *ContractAddress 22 | if rf, ok := ret.Get(0).(func() *ContractAddress); ok { 23 | r0 = rf() 24 | } else { 25 | if ret.Get(0) != nil { 26 | r0 = ret.Get(0).(*ContractAddress) 27 | } 28 | } 29 | 30 | var r1 error 31 | if rf, ok := ret.Get(1).(func() error); ok { 32 | r1 = rf() 33 | } else { 34 | r1 = ret.Error(1) 35 | } 36 | 37 | return r0, r1 38 | } 39 | 40 | // GetConfirmationsForEthOpAmount provides a mock function with given fields: 41 | func (_m *ProviderMock) GetConfirmationsForEthOpAmount() (*big.Int, error) { 42 | ret := _m.Called() 43 | 44 | var r0 *big.Int 45 | if rf, ok := ret.Get(0).(func() *big.Int); ok { 46 | r0 = rf() 47 | } else { 48 | if ret.Get(0) != nil { 49 | r0 = ret.Get(0).(*big.Int) 50 | } 51 | } 52 | 53 | var r1 error 54 | if rf, ok := ret.Get(1).(func() error); ok { 55 | r1 = rf() 56 | } else { 57 | r1 = ret.Error(1) 58 | } 59 | 60 | return r0, r1 61 | } 62 | 63 | // GetEthOpInfo provides a mock function with given fields: priority 64 | func (_m *ProviderMock) GetEthOpInfo(priority uint64) (*EthOpInfo, error) { 65 | ret := _m.Called(priority) 66 | 67 | var r0 *EthOpInfo 68 | if rf, ok := ret.Get(0).(func(uint64) *EthOpInfo); ok { 69 | r0 = rf(priority) 70 | } else { 71 | if ret.Get(0) != nil { 72 | r0 = ret.Get(0).(*EthOpInfo) 73 | } 74 | } 75 | 76 | var r1 error 77 | if rf, ok := ret.Get(1).(func(uint64) error); ok { 78 | r1 = rf(priority) 79 | } else { 80 | r1 = ret.Error(1) 81 | } 82 | 83 | return r0, r1 84 | } 85 | 86 | // GetState provides a mock function with given fields: address 87 | func (_m *ProviderMock) GetState(address common.Address) (*AccountState, error) { 88 | ret := _m.Called(address) 89 | 90 | var r0 *AccountState 91 | if rf, ok := ret.Get(0).(func(common.Address) *AccountState); ok { 92 | r0 = rf(address) 93 | } else { 94 | if ret.Get(0) != nil { 95 | r0 = ret.Get(0).(*AccountState) 96 | } 97 | } 98 | 99 | var r1 error 100 | if rf, ok := ret.Get(1).(func(common.Address) error); ok { 101 | r1 = rf(address) 102 | } else { 103 | r1 = ret.Error(1) 104 | } 105 | 106 | return r0, r1 107 | } 108 | 109 | // GetTokenPrice provides a mock function with given fields: token 110 | func (_m *ProviderMock) GetTokenPrice(token *Token) (*big.Float, error) { 111 | ret := _m.Called(token) 112 | 113 | var r0 *big.Float 114 | if rf, ok := ret.Get(0).(func(*Token) *big.Float); ok { 115 | r0 = rf(token) 116 | } else { 117 | if ret.Get(0) != nil { 118 | r0 = ret.Get(0).(*big.Float) 119 | } 120 | } 121 | 122 | var r1 error 123 | if rf, ok := ret.Get(1).(func(*Token) error); ok { 124 | r1 = rf(token) 125 | } else { 126 | r1 = ret.Error(1) 127 | } 128 | 129 | return r0, r1 130 | } 131 | 132 | // GetTokens provides a mock function with given fields: 133 | func (_m *ProviderMock) GetTokens() (*Tokens, error) { 134 | ret := _m.Called() 135 | 136 | var r0 *Tokens 137 | if rf, ok := ret.Get(0).(func() *Tokens); ok { 138 | r0 = rf() 139 | } else { 140 | if ret.Get(0) != nil { 141 | r0 = ret.Get(0).(*Tokens) 142 | } 143 | } 144 | 145 | var r1 error 146 | if rf, ok := ret.Get(1).(func() error); ok { 147 | r1 = rf() 148 | } else { 149 | r1 = ret.Error(1) 150 | } 151 | 152 | return r0, r1 153 | } 154 | 155 | // GetTransactionDetails provides a mock function with given fields: txHash 156 | func (_m *ProviderMock) GetTransactionDetails(txHash string) (*TransactionDetails, error) { 157 | ret := _m.Called(txHash) 158 | 159 | var r0 *TransactionDetails 160 | if rf, ok := ret.Get(0).(func(string) *TransactionDetails); ok { 161 | r0 = rf(txHash) 162 | } else { 163 | if ret.Get(0) != nil { 164 | r0 = ret.Get(0).(*TransactionDetails) 165 | } 166 | } 167 | 168 | var r1 error 169 | if rf, ok := ret.Get(1).(func(string) error); ok { 170 | r1 = rf(txHash) 171 | } else { 172 | r1 = ret.Error(1) 173 | } 174 | 175 | return r0, r1 176 | } 177 | 178 | // GetTransactionFee provides a mock function with given fields: txType, address, token 179 | func (_m *ProviderMock) GetTransactionFee(txType TransactionType, address common.Address, token *Token) (*TransactionFeeDetails, error) { 180 | ret := _m.Called(txType, address, token) 181 | 182 | var r0 *TransactionFeeDetails 183 | if rf, ok := ret.Get(0).(func(TransactionType, common.Address, *Token) *TransactionFeeDetails); ok { 184 | r0 = rf(txType, address, token) 185 | } else { 186 | if ret.Get(0) != nil { 187 | r0 = ret.Get(0).(*TransactionFeeDetails) 188 | } 189 | } 190 | 191 | var r1 error 192 | if rf, ok := ret.Get(1).(func(TransactionType, common.Address, *Token) error); ok { 193 | r1 = rf(txType, address, token) 194 | } else { 195 | r1 = ret.Error(1) 196 | } 197 | 198 | return r0, r1 199 | } 200 | 201 | // GetTransactionsBatchFee provides a mock function with given fields: txTypes, addresses, token 202 | func (_m *ProviderMock) GetTransactionsBatchFee(txTypes []TransactionType, addresses []common.Address, token *Token) (*TransactionFeeDetails, error) { 203 | ret := _m.Called(txTypes, addresses, token) 204 | 205 | var r0 *TransactionFeeDetails 206 | if rf, ok := ret.Get(0).(func([]TransactionType, []common.Address, *Token) *TransactionFeeDetails); ok { 207 | r0 = rf(txTypes, addresses, token) 208 | } else { 209 | if ret.Get(0) != nil { 210 | r0 = ret.Get(0).(*TransactionFeeDetails) 211 | } 212 | } 213 | 214 | var r1 error 215 | if rf, ok := ret.Get(1).(func([]TransactionType, []common.Address, *Token) error); ok { 216 | r1 = rf(txTypes, addresses, token) 217 | } else { 218 | r1 = ret.Error(1) 219 | } 220 | 221 | return r0, r1 222 | } 223 | 224 | // SubmitTx provides a mock function with given fields: signedTx, ethSignature, fastProcessing 225 | func (_m *ProviderMock) SubmitTx(signedTx ZksTransaction, ethSignature *EthSignature, fastProcessing bool) (string, error) { 226 | ret := _m.Called(signedTx, ethSignature, fastProcessing) 227 | 228 | var r0 string 229 | if rf, ok := ret.Get(0).(func(ZksTransaction, *EthSignature, bool) string); ok { 230 | r0 = rf(signedTx, ethSignature, fastProcessing) 231 | } else { 232 | r0 = ret.Get(0).(string) 233 | } 234 | 235 | var r1 error 236 | if rf, ok := ret.Get(1).(func(ZksTransaction, *EthSignature, bool) error); ok { 237 | r1 = rf(signedTx, ethSignature, fastProcessing) 238 | } else { 239 | r1 = ret.Error(1) 240 | } 241 | 242 | return r0, r1 243 | } 244 | 245 | // SubmitTxMultiSig provides a mock function with given fields: signedTx, ethSignatures 246 | func (_m *ProviderMock) SubmitTxMultiSig(signedTx ZksTransaction, ethSignatures ...*EthSignature) (string, error) { 247 | _va := make([]interface{}, len(ethSignatures)) 248 | for _i := range ethSignatures { 249 | _va[_i] = ethSignatures[_i] 250 | } 251 | var _ca []interface{} 252 | _ca = append(_ca, signedTx) 253 | _ca = append(_ca, _va...) 254 | ret := _m.Called(_ca...) 255 | 256 | var r0 string 257 | if rf, ok := ret.Get(0).(func(ZksTransaction, ...*EthSignature) string); ok { 258 | r0 = rf(signedTx, ethSignatures...) 259 | } else { 260 | r0 = ret.Get(0).(string) 261 | } 262 | 263 | var r1 error 264 | if rf, ok := ret.Get(1).(func(ZksTransaction, ...*EthSignature) error); ok { 265 | r1 = rf(signedTx, ethSignatures...) 266 | } else { 267 | r1 = ret.Error(1) 268 | } 269 | 270 | return r0, r1 271 | } 272 | 273 | // SubmitTxsBatch provides a mock function with given fields: signedTxs, ethSignature 274 | func (_m *ProviderMock) SubmitTxsBatch(signedTxs []*SignedTransaction, ethSignature *EthSignature) ([]string, error) { 275 | ret := _m.Called(signedTxs, ethSignature) 276 | 277 | var r0 []string 278 | if rf, ok := ret.Get(0).(func([]*SignedTransaction, *EthSignature) []string); ok { 279 | r0 = rf(signedTxs, ethSignature) 280 | } else { 281 | if ret.Get(0) != nil { 282 | r0 = ret.Get(0).([]string) 283 | } 284 | } 285 | 286 | var r1 error 287 | if rf, ok := ret.Get(1).(func([]*SignedTransaction, *EthSignature) error); ok { 288 | r1 = rf(signedTxs, ethSignature) 289 | } else { 290 | r1 = ret.Error(1) 291 | } 292 | 293 | return r0, r1 294 | } 295 | 296 | // UpdateTokenSet provides a mock function with given fields: 297 | func (_m *ProviderMock) UpdateTokenSet() error { 298 | ret := _m.Called() 299 | 300 | var r0 error 301 | if rf, ok := ret.Get(0).(func() error); ok { 302 | r0 = rf() 303 | } else { 304 | r0 = ret.Error(0) 305 | } 306 | 307 | return r0 308 | } 309 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # ZKSync Golang SDK 2 | -------------------------------------------------------------------------------- /Receipt.go: -------------------------------------------------------------------------------- 1 | package zksync 2 | 3 | import ( 4 | "github.com/pkg/errors" 5 | "time" 6 | ) 7 | 8 | type TransactionReceiptProcessor struct { 9 | provider Provider 10 | checkInterval time.Duration 11 | timeout time.Duration 12 | } 13 | 14 | func NewTransactionReceiptProcessor(provider Provider) *TransactionReceiptProcessor { 15 | return &TransactionReceiptProcessor{ 16 | provider: provider, 17 | checkInterval: time.Second, 18 | timeout: 5 * time.Minute, 19 | } 20 | } 21 | 22 | func NewTransactionReceiptProcessorDurations(provider Provider, 23 | checkInterval time.Duration, timeout time.Duration) *TransactionReceiptProcessor { 24 | return &TransactionReceiptProcessor{ 25 | provider: provider, 26 | checkInterval: checkInterval, 27 | timeout: timeout, 28 | } 29 | } 30 | 31 | func (rp *TransactionReceiptProcessor) WaitForTransaction(txHash string, status TransactionStatus) (*TransactionDetails, error) { 32 | checkTransactionDetails := func(td *TransactionDetails, err error) (*TransactionDetails, error) { 33 | if err != nil { 34 | return nil, errors.Wrap(err, "failed to get transaction details by txHash") 35 | } 36 | if td.Executed { 37 | switch status { 38 | case TransactionStatusSent: 39 | if td.Block != nil { 40 | return td, nil 41 | } 42 | case TransactionStatusCommitted: 43 | if td.Block != nil && td.Block.Committed { 44 | return td, nil 45 | } 46 | case TransactionStatusVerified: 47 | if td.Block != nil && td.Block.Verified { 48 | return td, nil 49 | } 50 | } 51 | } 52 | return nil, nil 53 | } 54 | td, err := checkTransactionDetails(rp.provider.GetTransactionDetails(txHash)) 55 | if err != nil { 56 | return nil, errors.Wrap(err, "failed to get transaction details by txHash") 57 | } else if td != nil { 58 | return td, nil 59 | } 60 | for { 61 | select { 62 | case <-time.After(rp.checkInterval): 63 | td, err = checkTransactionDetails(rp.provider.GetTransactionDetails(txHash)) 64 | if err != nil { 65 | return nil, errors.Wrap(err, "failed to get transaction details by txHash") 66 | } else if td != nil { 67 | return td, nil 68 | } 69 | case <-time.After(rp.timeout): 70 | return nil, errors.New("Transaction was not generated, waiting timeout reached") 71 | } 72 | } 73 | } 74 | -------------------------------------------------------------------------------- /SigningUtils.go: -------------------------------------------------------------------------------- 1 | package zksync 2 | 3 | import ( 4 | "bytes" 5 | "encoding/binary" 6 | "encoding/hex" 7 | "fmt" 8 | "github.com/ethereum/go-ethereum/common" 9 | "github.com/pkg/errors" 10 | "math/big" 11 | "strings" 12 | ) 13 | 14 | const ( 15 | AmountExponentBitWidth int64 = 5 16 | AmountMantissaBitWidth int64 = 35 17 | FeeExponentBitWidth int64 = 5 18 | FeeMantissaBitWidth int64 = 11 19 | ) 20 | 21 | func Uint32ToBytes(v uint32) []byte { 22 | res := make([]byte, 4) 23 | binary.BigEndian.PutUint32(res, v) 24 | return res 25 | } 26 | 27 | func Uint64ToBytes(v uint64) []byte { 28 | res := make([]byte, 8) 29 | binary.BigEndian.PutUint64(res, v) 30 | return res 31 | } 32 | 33 | func BigIntToBytesBE(v *big.Int, numBytes int) []byte { 34 | val := v.Bytes() 35 | res := make([]byte, numBytes-len(val)) // left padded with 0 bytes to target length 36 | return append(res, val...) 37 | } 38 | 39 | func pkhToBytes(pkh string) ([]byte, error) { 40 | if pkh[:5] != "sync:" { 41 | return nil, errors.New("PubKeyHash must start with 'sync:'") 42 | } 43 | res, err := hex.DecodeString(pkh[5:]) 44 | if err != nil { 45 | return nil, err 46 | } 47 | if len(res) != 20 { 48 | return nil, errors.New("pkh must be 20 bytes long") 49 | } 50 | return res, nil 51 | } 52 | 53 | func packFee(fee *big.Int) ([]byte, error) { 54 | packedFee, err := integerToDecimalByteArray(fee, FeeExponentBitWidth, FeeMantissaBitWidth, 10) 55 | if err != nil { 56 | return nil, errors.Wrap(err, "failed to pack fee") 57 | } 58 | // check that unpacked fee still has same value 59 | if unpackedFee, err := decimalByteArrayToInteger(packedFee, FeeExponentBitWidth, FeeMantissaBitWidth, 10); err != nil { 60 | return nil, errors.Wrap(err, "failed to unpack fee") 61 | } else if unpackedFee.Cmp(fee) != 0 { 62 | return nil, errors.New("fee Amount is not packable") 63 | } 64 | return packedFee, nil 65 | } 66 | 67 | func packAmount(amount *big.Int) ([]byte, error) { 68 | packedAmount, err := integerToDecimalByteArray(amount, AmountExponentBitWidth, AmountMantissaBitWidth, 10) 69 | if err != nil { 70 | return nil, errors.Wrap(err, "failed to pack amount") 71 | } 72 | // check that unpacked amount still has same value 73 | if unpackedFee, err := decimalByteArrayToInteger(packedAmount, AmountExponentBitWidth, AmountMantissaBitWidth, 10); err != nil { 74 | return nil, errors.Wrap(err, "failed to unpack amount") 75 | } else if unpackedFee.Cmp(amount) != 0 { 76 | return nil, errors.New("amount Amount is not packable") 77 | } 78 | return packedAmount, nil 79 | } 80 | 81 | func integerToDecimalByteArray(value *big.Int, expBits, mantissaBits, expBase int64) ([]byte, error) { 82 | bigExpBase := big.NewInt(expBase) 83 | // maxExponent = expBase ^ ((2 ^ expBits) - 1) 84 | maxExpPow := big.NewInt(0).Sub(big.NewInt(0).Exp(big.NewInt(2), big.NewInt(expBits), nil), big.NewInt(1)) 85 | maxExponent := big.NewInt(0).Exp(bigExpBase, maxExpPow, nil) 86 | // maxMantissa = (2 ^ mantissaBits) - 1 87 | maxMantissa := big.NewInt(0).Sub(big.NewInt(0).Exp(big.NewInt(2), big.NewInt(mantissaBits), nil), big.NewInt(1)) 88 | // check for max possible value 89 | if value.Cmp(big.NewInt(0).Mul(maxMantissa, maxExponent)) > 0 { 90 | return nil, errors.New("Integer is too big") 91 | } 92 | exponent := uint64(0) 93 | mantissa := big.NewInt(0).Set(value) 94 | for mantissa.Cmp(maxMantissa) > 0 { 95 | mantissa.Div(mantissa, bigExpBase) 96 | exponent++ 97 | } 98 | 99 | exponentData := uint64ToBitsLE(exponent, uint(expBits)) 100 | mantissaData := uint64ToBitsLE(mantissa.Uint64(), uint(mantissaBits)) 101 | combined := exponentData.Clone().Append(mantissaData) 102 | reversed := combined.Reverse() 103 | bytes, err := reversed.ToBytesBE() 104 | if err != nil { 105 | return nil, errors.Wrap(err, "failed to convert bits to bytes BE") 106 | } 107 | return bytes, nil 108 | } 109 | 110 | func decimalByteArrayToInteger(value []byte, expBits, mantissaBits, expBase int64) (*big.Int, error) { 111 | if int64(len(value)*8) != expBits+mantissaBits { 112 | return nil, errors.New("Decimal unpacking, incorrect input length") 113 | } 114 | bits := NewBits(uint(expBits + mantissaBits)) 115 | bits.FromBytesBE(value).Reverse() 116 | exponent := big.NewInt(0) 117 | expPow2 := big.NewInt(1) 118 | for i := uint(0); i < uint(expBits); i++ { 119 | if bits.GetBit(i) { 120 | exponent.Add(exponent, expPow2) 121 | } 122 | expPow2.Mul(expPow2, big.NewInt(2)) 123 | } 124 | exponent.Exp(big.NewInt(expBase), exponent, nil) 125 | 126 | mantissa := big.NewInt(0) 127 | mantissaPow2 := big.NewInt(1) 128 | for i := uint(expBits); i < uint(expBits+mantissaBits); i++ { 129 | if bits.GetBit(i) { 130 | mantissa.Add(mantissa, mantissaPow2) 131 | } 132 | mantissaPow2.Mul(mantissaPow2, big.NewInt(2)) 133 | } 134 | return exponent.Mul(exponent, mantissa), nil 135 | } 136 | 137 | func uint64ToBitsLE(v uint64, size uint) *Bits { 138 | res := NewBits(size) 139 | for i := uint(0); i < size; i++ { 140 | res.SetBit(i, v&1 == 1) 141 | v /= 2 142 | } 143 | return res 144 | } 145 | 146 | func GetSignMessage(tx ZksTransaction, token *Token, fee *big.Int) (string, error) { 147 | switch tx.getType() { 148 | case "Transfer": 149 | if txData, ok := tx.(*Transfer); ok { 150 | var tokenToUse *Token 151 | if txData.Token != nil { 152 | tokenToUse = txData.Token 153 | } else { 154 | tokenToUse = token 155 | } 156 | fee, ok := big.NewInt(0).SetString(txData.Fee, 10) 157 | if !ok { 158 | return "", errors.New("failed to convert string fee to big.Int") 159 | } 160 | msg, err := getTransferMessagePart(txData.To.String(), txData.Amount, fee, tokenToUse) 161 | if err != nil { 162 | return "", errors.Wrap(err, "failed to get Transfer message part") 163 | } 164 | return msg, nil 165 | } 166 | case "Withdraw": 167 | if txData, ok := tx.(*Withdraw); ok { 168 | msg, err := getWithdrawMessagePart(txData.To.String(), txData.Amount, fee, token) 169 | if err != nil { 170 | return "", errors.Wrap(err, "failed to get Withdraw message part") 171 | } 172 | return msg, nil 173 | } 174 | case "ForcedExit": 175 | if txData, ok := tx.(*ForcedExit); ok { 176 | msg, err := getForcedExitMessagePart(txData.Target.String(), fee, token) 177 | if err != nil { 178 | return "", errors.Wrap(err, "failed to get ForcedExit message part") 179 | } 180 | return msg, nil 181 | } 182 | case "MintNFT": 183 | if txData, ok := tx.(*MintNFT); ok { 184 | msg, err := getMintNFTMessagePart(txData.ContentHash, txData.Recipient.String(), fee, token) 185 | if err != nil { 186 | return "", errors.Wrap(err, "failed to get MintNFT message part") 187 | } 188 | return msg, nil 189 | } 190 | case "WithdrawNFT": 191 | if txData, ok := tx.(*WithdrawNFT); ok { 192 | msg, err := getWithdrawNFTMessagePart(txData.To.String(), txData.Token, fee, token) 193 | if err != nil { 194 | return "", errors.Wrap(err, "failed to get WithdrawNFT message part") 195 | } 196 | return msg, nil 197 | } 198 | case "Swap": 199 | msg := getSwapMessagePart(token, fee) 200 | return msg, nil 201 | } 202 | return "", errors.New("unknown tx type") 203 | } 204 | 205 | func GetChangePubKeyData(txData *ChangePubKey) ([]byte, error) { 206 | buf := bytes.Buffer{} 207 | pkhBytes, err := pkhToBytes(txData.NewPkHash) 208 | if err != nil { 209 | return nil, errors.Wrap(err, "failed to get pkh bytes") 210 | } 211 | buf.Write(pkhBytes) 212 | buf.Write(Uint32ToBytes(txData.Nonce)) 213 | buf.Write(Uint32ToBytes(txData.AccountId)) 214 | buf.Write(txData.EthAuthData.getBytes()) 215 | return buf.Bytes(), nil 216 | } 217 | 218 | func getTransferMessagePart(to string, amount, fee *big.Int, token *Token) (string, error) { 219 | var res string 220 | if big.NewInt(0).Cmp(amount) != 0 { 221 | res = fmt.Sprintf("Transfer %s %s to: %s", token.ToDecimalString(amount), token.Symbol, strings.ToLower(to)) 222 | } 223 | if fee.Cmp(big.NewInt(0)) > 0 { 224 | if len(res) > 0 { 225 | res += "\n" 226 | } 227 | res += fmt.Sprintf("Fee: %s %s", token.ToDecimalString(fee), token.Symbol) 228 | } 229 | return res, nil 230 | } 231 | 232 | func getWithdrawMessagePart(to string, amount, fee *big.Int, token *Token) (string, error) { 233 | var res string 234 | if big.NewInt(0).Cmp(amount) != 0 { 235 | res = fmt.Sprintf("Withdraw %s %s to: %s", token.ToDecimalString(amount), token.Symbol, strings.ToLower(to)) 236 | } 237 | if fee.Cmp(big.NewInt(0)) > 0 { 238 | if len(res) > 0 { 239 | res += "\n" 240 | } 241 | res += fmt.Sprintf("Fee: %s %s", token.ToDecimalString(fee), token.Symbol) 242 | } 243 | return res, nil 244 | } 245 | 246 | func getForcedExitMessagePart(to string, fee *big.Int, token *Token) (string, error) { 247 | var res string 248 | res = fmt.Sprintf("ForcedExit %s to: %s", token.Symbol, strings.ToLower(to)) 249 | if fee.Cmp(big.NewInt(0)) > 0 { 250 | res += fmt.Sprintf("\nFee: %s %s", token.ToDecimalString(fee), token.Symbol) 251 | } 252 | return res, nil 253 | } 254 | 255 | func getMintNFTMessagePart(contentHash common.Hash, to string, fee *big.Int, token *Token) (string, error) { 256 | var res string 257 | res = fmt.Sprintf("MintNFT %s for: %s", contentHash.String(), strings.ToLower(to)) 258 | if fee.Cmp(big.NewInt(0)) > 0 { 259 | res += fmt.Sprintf("\nFee: %s %s", token.ToDecimalString(fee), token.Symbol) 260 | } 261 | return res, nil 262 | } 263 | 264 | func getWithdrawNFTMessagePart(to string, tokenId uint32, fee *big.Int, token *Token) (string, error) { 265 | var res string 266 | res = fmt.Sprintf("WithdrawNFT %d to: %s", tokenId, strings.ToLower(to)) 267 | if fee.Cmp(big.NewInt(0)) > 0 { 268 | res += fmt.Sprintf("\nFee: %s %s", token.ToDecimalString(fee), token.Symbol) 269 | } 270 | return res, nil 271 | } 272 | 273 | func GetOrderMessagePart(recipient string, amount *big.Int, sell, buy *Token, ratio []*big.Int) (string, error) { 274 | if len(ratio) != 2 { 275 | return "", errors.New("invalid ratio") 276 | } 277 | var res string 278 | if amount.Cmp(big.NewInt(0)) == 0 { 279 | res = fmt.Sprintf("Limit order for %s -> %s", sell.Symbol, buy.Symbol) 280 | } else { 281 | res = fmt.Sprintf("Order for %s %s -> %s", sell.ToDecimalString(amount), sell.Symbol, buy.Symbol) 282 | } 283 | res += fmt.Sprintf("\nRatio: %s:%s\nAddress: %s", ratio[0].String(), ratio[1].String(), strings.ToLower(recipient)) 284 | return res, nil 285 | } 286 | 287 | func getSwapMessagePart(token *Token, fee *big.Int) string { 288 | return fmt.Sprintf("Swap fee: %s %s", token.ToDecimalString(fee), token.Symbol) 289 | } 290 | 291 | func GetNonceMessagePart(nonce uint32) string { 292 | return fmt.Sprintf("Nonce: %d", nonce) 293 | } 294 | -------------------------------------------------------------------------------- /State.go: -------------------------------------------------------------------------------- 1 | package zksync 2 | 3 | import "math/big" 4 | 5 | type AccountState struct { 6 | Address string `json:"address"` 7 | Id uint32 `json:"id"` 8 | Depositing *DepositingState `json:"depositing"` 9 | Committed *State `json:"committed"` 10 | Verified *State `json:"verified"` 11 | } 12 | 13 | type DepositingState struct { 14 | Balances map[string]*DepositingBalance `json:"balances"` 15 | } 16 | 17 | type DepositingBalance struct { 18 | Amount string `json:"amount"` 19 | ExpectedBlockNumber string `json:"expectedBlockNumber"` // to *big.Int 20 | } 21 | 22 | type State struct { 23 | Balances map[string]string `json:"balances"` 24 | Nonce uint32 `json:"nonce"` 25 | PubKeyHash string `json:"pubKeyHash"` 26 | Nfts map[string]*NFT `json:"nfts"` 27 | MintedNfts map[string]*NFT `json:"mintedNfts"` 28 | } 29 | 30 | func (s *State) GetBalanceOf(token string) (*big.Int, bool) { 31 | n := new(big.Int) 32 | if v, ok := s.Balances[token]; ok { 33 | if n, ok := n.SetString(v, 10); ok { 34 | return n, true 35 | } 36 | return new(big.Int), false 37 | } 38 | return n, false 39 | } 40 | -------------------------------------------------------------------------------- /Swap.go: -------------------------------------------------------------------------------- 1 | package zksync 2 | 3 | import ( 4 | "github.com/ethereum/go-ethereum/common" 5 | "math/big" 6 | ) 7 | 8 | type Order struct { 9 | AccountId uint32 `json:"accountId"` 10 | RecipientAddress common.Address `json:"recipient"` 11 | Nonce uint32 `json:"nonce"` 12 | TokenBuy uint32 `json:"tokenBuy"` 13 | TokenSell uint32 `json:"tokenSell"` 14 | Ratio []*big.Int `json:"ratio"` 15 | Amount *big.Int `json:"amount"` 16 | Signature *Signature `json:"signature"` 17 | EthereumSignature *EthSignature `json:"ethereumSignature"` 18 | *TimeRange 19 | } 20 | 21 | const ( 22 | TransactionTypeSwap TransactionType = "Swap" 23 | ) 24 | 25 | type Swap struct { 26 | Type string `json:"type"` 27 | SubmitterId uint32 `json:"submitterId"` 28 | SubmitterAddress common.Address `json:"submitterAddress"` 29 | Nonce uint32 `json:"nonce"` 30 | Orders []*Order `json:"orders"` 31 | Amounts []*big.Int `json:"amounts"` 32 | Fee string `json:"fee"` 33 | FeeToken uint32 `json:"feeToken"` 34 | Signature *Signature `json:"signature"` 35 | *TimeRange 36 | } 37 | 38 | func (t *Swap) getType() string { 39 | return "Swap" 40 | } 41 | -------------------------------------------------------------------------------- /TimeRange.go: -------------------------------------------------------------------------------- 1 | package zksync 2 | 3 | type TimeRange struct { 4 | ValidFrom uint64 `json:"validFrom"` 5 | ValidUntil uint64 `json:"validUntil"` 6 | } 7 | 8 | func DefaultTimeRange() *TimeRange { 9 | return &TimeRange{ 10 | ValidFrom: 0, 11 | ValidUntil: 4294967295, 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /Token.go: -------------------------------------------------------------------------------- 1 | package zksync 2 | 3 | import ( 4 | "github.com/ethereum/go-ethereum/common" 5 | "github.com/pkg/errors" 6 | "math/big" 7 | ) 8 | 9 | type Token struct { 10 | Id uint32 `json:"id"` 11 | Address string `json:"address"` 12 | Symbol string `json:"symbol"` 13 | Decimals uint `json:"decimals"` 14 | IsNft bool `json:"is_nft"` 15 | } 16 | 17 | func CreateETH() *Token { 18 | return &Token{ 19 | Id: 0, 20 | Address: common.Address{}.String(), 21 | Symbol: `ETH`, 22 | Decimals: 18, 23 | } 24 | } 25 | 26 | func (t Token) IsETH() bool { 27 | return t.Address == common.Address{}.String() && t.Symbol == `ETH` 28 | } 29 | 30 | func (t Token) GetAddress() common.Address { 31 | return common.HexToAddress(t.Address) 32 | } 33 | 34 | func (t Token) ToDecimalString(amount *big.Int) string { 35 | amountFloat := big.NewFloat(0).SetInt(amount) 36 | if t.IsNft { 37 | // return origin int value in "XXX.0" format 38 | return amountFloat.Text('f', 1) 39 | } 40 | // convert to pointed value considering decimals scale, like wei => ETH (10^18 wei == 1 ETH) 41 | divider := big.NewFloat(0).SetInt(big.NewInt(0).Exp(big.NewInt(10), big.NewInt(int64(t.Decimals)), nil)) // = 10^decimals 42 | res := big.NewFloat(0).SetPrec(t.Decimals*8).Quo(amountFloat, divider) // = amount / 10^decimals 43 | if res.IsInt() { 44 | return res.Text('f', 1) // return int value in "XXX.0" format 45 | } 46 | return res.Text('f', -int(t.Decimals)) // format as numeric with specified decimals limit 47 | } 48 | 49 | type Tokens struct { 50 | Tokens map[string]*Token 51 | } 52 | 53 | func (ts *Tokens) GetToken(id string) (*Token, error) { 54 | if t, ok := ts.Tokens[id]; ok { 55 | return t, nil 56 | } 57 | // suppose id is address 58 | for _, t := range ts.Tokens { 59 | if t.Address == id { 60 | return t, nil 61 | } 62 | } 63 | return nil, errors.New("token not found") 64 | } 65 | -------------------------------------------------------------------------------- /Transaction.go: -------------------------------------------------------------------------------- 1 | package zksync 2 | 3 | type TransactionType string 4 | 5 | type TransactionStatus int 6 | 7 | const ( 8 | TransactionStatusSent TransactionStatus = iota 9 | TransactionStatusCommitted 10 | TransactionStatusVerified 11 | ) 12 | 13 | func (t TransactionType) getType() interface{} { 14 | switch t { 15 | case TransactionTypeChangePubKeyOnchain, TransactionTypeChangePubKeyECDSA, TransactionTypeChangePubKeyCREATE2: 16 | // custom object instead of string 17 | return TransactionTypeChangePubKey{ChangePubKey: string(t)} 18 | default: 19 | return string(t) 20 | } 21 | } 22 | 23 | type ZksTransaction interface { 24 | getType() string 25 | } 26 | 27 | type SignedTransaction struct { 28 | Transaction ZksTransaction `json:"tx"` 29 | EthereumSignature *EthSignature `json:"signature"` 30 | } 31 | 32 | type TransactionDetails struct { 33 | Executed bool `json:"executed"` 34 | Success bool `json:"success"` 35 | FailReason string `json:"failReason"` 36 | Block *BlockInfo `json:"block"` 37 | } 38 | 39 | type BlockInfo struct { 40 | BlockNumber uint64 `json:"blockNumber"` 41 | Committed bool `json:"committed"` 42 | Verified bool `json:"verified"` 43 | } 44 | 45 | type EthOpInfo struct { 46 | Executed bool `json:"executed"` 47 | Block *BlockInfo `json:"block"` 48 | } 49 | -------------------------------------------------------------------------------- /Transfer.go: -------------------------------------------------------------------------------- 1 | package zksync 2 | 3 | import ( 4 | "github.com/ethereum/go-ethereum/common" 5 | "math/big" 6 | ) 7 | 8 | const ( 9 | TransactionTypeTransfer TransactionType = "Transfer" 10 | ) 11 | 12 | type Transfer struct { 13 | Type string `json:"type"` 14 | AccountId uint32 `json:"accountId"` 15 | From common.Address `json:"from"` 16 | To common.Address `json:"to"` 17 | Token *Token `json:"-"` 18 | TokenId uint32 `json:"token"` 19 | Amount *big.Int `json:"amount"` 20 | Fee string `json:"fee"` 21 | Nonce uint32 `json:"nonce"` 22 | Signature *Signature `json:"signature"` 23 | *TimeRange 24 | } 25 | 26 | func (t *Transfer) getType() string { 27 | return "Transfer" 28 | } 29 | -------------------------------------------------------------------------------- /Wallet.go: -------------------------------------------------------------------------------- 1 | package zksync 2 | 3 | import ( 4 | "context" 5 | "github.com/ethereum/go-ethereum/accounts/abi/bind" 6 | "github.com/ethereum/go-ethereum/common" 7 | "github.com/ethereum/go-ethereum/core/types" 8 | "github.com/ethereum/go-ethereum/ethclient" 9 | "github.com/pkg/errors" 10 | "github.com/zksync-sdk/zksync-go/contracts/ZkSync" 11 | "math/big" 12 | ) 13 | 14 | type Wallet struct { 15 | accountId uint32 16 | pubKeyHash string 17 | zkSigner *ZkSigner 18 | ethSigner EthSigner 19 | provider Provider 20 | } 21 | 22 | func NewWallet(ethSigner EthSigner, zkSigner *ZkSigner, provider Provider) (*Wallet, error) { 23 | state, err := provider.GetState(ethSigner.GetAddress()) 24 | if err != nil { 25 | return nil, errors.Wrap(err, "failed to get account state") 26 | } 27 | return &Wallet{ 28 | accountId: state.Id, 29 | pubKeyHash: state.Committed.PubKeyHash, 30 | zkSigner: zkSigner, 31 | ethSigner: ethSigner, 32 | provider: provider, 33 | }, nil 34 | } 35 | 36 | func (w *Wallet) GetAccountId() (uint32, error) { 37 | return w.accountId, nil 38 | } 39 | 40 | func (w *Wallet) GetPubKeyHash() string { 41 | return w.pubKeyHash 42 | } 43 | 44 | func (w *Wallet) GetAddress() common.Address { 45 | return w.ethSigner.GetAddress() 46 | } 47 | 48 | func (w *Wallet) GetTokens() (*Tokens, error) { 49 | return w.provider.GetTokens() 50 | } 51 | 52 | func (w *Wallet) GetState() (*AccountState, error) { 53 | state, err := w.provider.GetState(w.ethSigner.GetAddress()) 54 | if err != nil { 55 | return nil, errors.Wrap(err, "failed to get account state") 56 | } 57 | w.accountId = state.Id 58 | w.pubKeyHash = state.Committed.PubKeyHash 59 | return state, nil 60 | } 61 | 62 | func (w *Wallet) GetProvider() Provider { 63 | return w.provider 64 | } 65 | 66 | func (w *Wallet) CreateEthereumProvider(client *ethclient.Client) (*DefaultEthProvider, error) { 67 | contractAddress, err := w.provider.ContractAddress() 68 | if err != nil { 69 | return nil, errors.Wrap(err, "failed to get contract address") 70 | } 71 | contract, err := ZkSync.NewZkSync(contractAddress.GetMainAddress(), client) 72 | if err != nil { 73 | return nil, errors.Wrap(err, "failed to init ZkSync contract instance") 74 | } 75 | chainId, err := client.ChainID(context.Background()) 76 | if err != nil { 77 | return nil, errors.Wrap(err, "failed to get chain Id") 78 | } 79 | auth, err := w.newTransactorWithSigner(w.ethSigner, chainId) 80 | if err != nil { 81 | return nil, errors.Wrap(err, "failed to init TransactOpts") 82 | } 83 | return &DefaultEthProvider{ 84 | client: client, 85 | contract: contract, 86 | address: contractAddress.GetMainAddress(), 87 | auth: auth, 88 | }, nil 89 | } 90 | 91 | func (w *Wallet) newTransactorWithSigner(ethSigner EthSigner, chainID *big.Int) (*bind.TransactOpts, error) { 92 | if chainID == nil { 93 | return nil, bind.ErrNoChainID 94 | } 95 | keyAddr := ethSigner.GetAddress() 96 | signer := types.LatestSignerForChainID(chainID) 97 | return &bind.TransactOpts{ 98 | From: keyAddr, 99 | Signer: func(address common.Address, tx *types.Transaction) (*types.Transaction, error) { 100 | if address != keyAddr { 101 | return nil, bind.ErrNotAuthorized 102 | } 103 | signature, err := ethSigner.SignHash(signer.Hash(tx).Bytes()) 104 | if err != nil { 105 | return nil, err 106 | } 107 | return tx.WithSignature(signer, signature) 108 | }, 109 | }, nil 110 | } 111 | 112 | func (w *Wallet) SetSigningKey(fee *TransactionFee, nonce uint32, onchainAuth bool, timeRange *TimeRange) (string, error) { 113 | if w.IsSigningKeySet() { 114 | return "", errors.New("current signing key is already set") 115 | } 116 | if onchainAuth { 117 | signedTx, err := w.buildSignedChangePubKeyTxOnchain(fee, nonce, timeRange) 118 | if err != nil { 119 | return "", errors.Wrap(err, "failed to build signed ChangePubKeyOnchain tx") 120 | } 121 | return w.submitSignedTransaction(signedTx.Transaction, nil, false) 122 | } else { 123 | signedTx, err := w.buildSignedChangePubKeyTxSigned(fee, nonce, timeRange) 124 | if err != nil { 125 | return "", errors.Wrap(err, "failed to build signed ChangePubKeySigned tx") 126 | } 127 | return w.submitSignedTransaction(signedTx.Transaction, nil, false) 128 | } 129 | } 130 | 131 | func (w *Wallet) IsSigningKeySet() bool { 132 | return w.pubKeyHash == w.zkSigner.GetPublicKeyHash() 133 | } 134 | 135 | func (w *Wallet) SyncTransfer(to common.Address, amount *big.Int, fee *TransactionFee, nonce uint32, timeRange *TimeRange) (string, error) { 136 | signedTx, err := w.buildSignedTransferTx(to, amount, fee, nonce, timeRange) 137 | if err != nil { 138 | return "", errors.Wrap(err, "failed to build signed Transfer tx") 139 | } 140 | return w.submitSignedTransaction(signedTx.Transaction, signedTx.EthereumSignature, false) 141 | } 142 | 143 | func (w *Wallet) SyncWithdraw(ethAddress common.Address, amount *big.Int, fee *TransactionFee, nonce uint32, fastProcessing bool, timeRange *TimeRange) (string, error) { 144 | signedTx, err := w.buildSignedWithdrawTx(ethAddress, fee.FeeToken, amount, fee, nonce, timeRange) 145 | if err != nil { 146 | return "", errors.Wrap(err, "failed to build signed Withdraw tx") 147 | } 148 | return w.submitSignedTransaction(signedTx.Transaction, signedTx.EthereumSignature, fastProcessing) 149 | } 150 | 151 | func (w *Wallet) SyncForcedExit(target common.Address, fee *TransactionFee, nonce uint32, timeRange *TimeRange) (string, error) { 152 | signedTx, err := w.buildSignedForcedExitTx(target, fee.FeeToken, fee, nonce, timeRange) 153 | if err != nil { 154 | return "", errors.Wrap(err, "failed to build signed Withdraw tx") 155 | } 156 | return w.submitSignedTransaction(signedTx.Transaction, signedTx.EthereumSignature, false) 157 | } 158 | 159 | func (w *Wallet) SyncMintNFT(recipient common.Address, contentHash common.Hash, fee *TransactionFee, nonce uint32) (string, error) { 160 | signedTx, err := w.buildSignedMintNFTTx(recipient, contentHash, fee.FeeToken, fee, nonce) 161 | if err != nil { 162 | return "", errors.Wrap(err, "failed to build signed MintNFT tx") 163 | } 164 | return w.submitSignedTransaction(signedTx.Transaction, signedTx.EthereumSignature, false) 165 | } 166 | 167 | func (w *Wallet) SyncWithdrawNFT(to common.Address, token *NFT, fee *TransactionFee, nonce uint32, timeRange *TimeRange) (string, error) { 168 | signedTx, err := w.buildSignedWithdrawNFTTx(to, token, fee.FeeToken, fee, nonce, timeRange) 169 | if err != nil { 170 | return "", errors.Wrap(err, "failed to build signed WithdrawNFT tx") 171 | } 172 | return w.submitSignedTransaction(signedTx.Transaction, signedTx.EthereumSignature, false) 173 | } 174 | 175 | func (w *Wallet) SyncTransferNFT(to common.Address, NFToken *NFT, fee *TransactionFee, nonce uint32, timeRange *TimeRange) ([]string, error) { 176 | tokens, err := w.provider.GetTokens() 177 | if err != nil { 178 | return nil, errors.Wrap(err, "failed to get tokens") 179 | } 180 | feeToken, err := tokens.GetToken(fee.FeeToken) 181 | if err != nil { 182 | return nil, errors.Wrap(err, "failed to get fee Token") 183 | } 184 | // 1 transfer NFT 185 | transferTx := &Transfer{ 186 | Type: "Transfer", 187 | AccountId: w.accountId, 188 | From: w.ethSigner.GetAddress(), 189 | To: to, 190 | Token: NFToken.ToToken(), 191 | TokenId: NFToken.Id, 192 | Amount: big.NewInt(1), 193 | Nonce: nonce, 194 | Fee: big.NewInt(0).String(), 195 | TimeRange: timeRange, 196 | } 197 | transferTx.Signature, err = w.zkSigner.SignTransfer(transferTx) 198 | if err != nil { 199 | return nil, errors.Wrap(err, "failed to get sign of Transfer") 200 | } 201 | // 2 transfer fee 202 | feeTx := &Transfer{ 203 | Type: "Transfer", 204 | AccountId: w.accountId, 205 | From: w.ethSigner.GetAddress(), 206 | To: w.ethSigner.GetAddress(), 207 | Token: feeToken, 208 | TokenId: feeToken.Id, 209 | Amount: big.NewInt(0), 210 | Nonce: nonce + 1, 211 | Fee: fee.Fee.String(), 212 | TimeRange: timeRange, 213 | } 214 | feeTx.Signature, err = w.zkSigner.SignTransfer(feeTx) 215 | if err != nil { 216 | return nil, errors.Wrap(err, "failed to get sign of Transfer") 217 | } 218 | // common eth signature 219 | ethSig, err := w.ethSigner.SignBatch([]ZksTransaction{transferTx, feeTx}, nonce, feeToken, fee.Fee) 220 | if err != nil { 221 | return nil, errors.Wrap(err, "failed to get sign of transactions batch") 222 | } 223 | return w.submitSignedTransactionsBatch([]*SignedTransaction{{Transaction: transferTx}, {Transaction: feeTx}}, ethSig) 224 | } 225 | 226 | func (w *Wallet) SyncSwap(order1, order2 *Order, amount1, amount2 *big.Int, fee *TransactionFee, nonce uint32) (string, error) { 227 | signedTx, err := w.buildSignedSwapTx(order1, order2, amount1, amount2, fee, nonce) 228 | if err != nil { 229 | return "", errors.Wrap(err, "failed to build signed Swap tx") 230 | } 231 | return w.submitMultiSignedTransaction(signedTx.Transaction, 232 | signedTx.EthereumSignature, order1.EthereumSignature, order2.EthereumSignature) 233 | } 234 | 235 | func (w *Wallet) BuildSignedOrder(recipient common.Address, sell, buy *Token, ratio []*big.Int, amount *big.Int, 236 | nonce uint32, timeRange *TimeRange) (*Order, error) { 237 | if len(ratio) != 2 { 238 | return nil, errors.New("invalid ratio") 239 | } 240 | order := &Order{ 241 | AccountId: w.accountId, 242 | Amount: amount, 243 | RecipientAddress: recipient, 244 | TokenSell: sell.Id, 245 | TokenBuy: buy.Id, 246 | Ratio: ratio, 247 | Nonce: nonce, 248 | TimeRange: timeRange, 249 | } 250 | var err error 251 | order.EthereumSignature, err = w.ethSigner.SignOrder(order, sell, buy) 252 | if err != nil { 253 | return nil, errors.Wrap(err, "failed to get eth sign of Order") 254 | } 255 | order.Signature, err = w.zkSigner.SignOrder(order) 256 | if err != nil { 257 | return nil, errors.Wrap(err, "failed to get sign of Order") 258 | } 259 | return order, nil 260 | } 261 | 262 | func (w *Wallet) BuildSignedLimitOrder(recipient common.Address, sell, buy *Token, ratio []*big.Int, 263 | nonce uint32, timeRange *TimeRange) (*Order, error) { 264 | return w.BuildSignedOrder(recipient, sell, buy, ratio, big.NewInt(0), nonce, timeRange) 265 | } 266 | 267 | func (w *Wallet) buildSignedChangePubKeyTxOnchain(fee *TransactionFee, nonce uint32, timeRange *TimeRange) (*SignedTransaction, error) { 268 | tokens, err := w.provider.GetTokens() 269 | if err != nil { 270 | return nil, errors.Wrap(err, "failed to get tokens") 271 | } 272 | token, err := tokens.GetToken(fee.FeeToken) 273 | if err != nil { 274 | return nil, errors.Wrap(err, "failed to get fee token") 275 | } 276 | txData := &ChangePubKey{ 277 | Type: "ChangePubKey", 278 | AccountId: w.accountId, 279 | Account: w.ethSigner.GetAddress(), 280 | NewPkHash: w.zkSigner.GetPublicKeyHash(), 281 | Nonce: nonce, 282 | FeeToken: token.Id, 283 | Fee: fee.Fee.String(), 284 | EthAuthData: &ChangePubKeyOnchain{Type: ChangePubKeyAuthTypeOnchain}, 285 | TimeRange: timeRange, 286 | } 287 | txData.Signature, err = w.zkSigner.SignChangePubKey(txData) 288 | if err != nil { 289 | return nil, errors.Wrap(err, "failed to get sign of ChangePubKey") 290 | } 291 | return &SignedTransaction{ 292 | Transaction: txData, 293 | }, nil 294 | } 295 | 296 | func (w *Wallet) buildSignedChangePubKeyTxSigned(fee *TransactionFee, nonce uint32, timeRange *TimeRange) (*SignedTransaction, error) { 297 | tokens, err := w.provider.GetTokens() 298 | if err != nil { 299 | return nil, errors.Wrap(err, "failed to get tokens") 300 | } 301 | token, err := tokens.GetToken(fee.FeeToken) 302 | if err != nil { 303 | return nil, errors.Wrap(err, "failed to get fee token") 304 | } 305 | txData := &ChangePubKey{ 306 | Type: "ChangePubKey", 307 | AccountId: w.accountId, 308 | Account: w.ethSigner.GetAddress(), 309 | NewPkHash: w.zkSigner.GetPublicKeyHash(), 310 | Nonce: nonce, 311 | FeeToken: token.Id, 312 | Fee: fee.Fee.String(), 313 | TimeRange: timeRange, 314 | } 315 | auth, err := w.ethSigner.SignAuth(txData) 316 | if err != nil { 317 | return nil, errors.Wrap(err, "failed to get eth sign of auth data") 318 | } 319 | txData.EthAuthData = auth 320 | txData.Signature, err = w.zkSigner.SignChangePubKey(txData) 321 | if err != nil { 322 | return nil, errors.Wrap(err, "failed to get sign of ChangePubKey") 323 | } 324 | return &SignedTransaction{ 325 | Transaction: txData, 326 | }, nil 327 | } 328 | 329 | func (w *Wallet) buildSignedTransferTx(to common.Address, amount *big.Int, fee *TransactionFee, nonce uint32, timeRange *TimeRange) (*SignedTransaction, error) { 330 | tokens, err := w.provider.GetTokens() 331 | if err != nil { 332 | return nil, errors.Wrap(err, "failed to get tokens") 333 | } 334 | token, err := tokens.GetToken(fee.FeeToken) 335 | if err != nil { 336 | return nil, errors.Wrap(err, "failed to get fee token") 337 | } 338 | txData := &Transfer{ 339 | Type: "Transfer", 340 | AccountId: w.accountId, 341 | From: w.ethSigner.GetAddress(), 342 | To: to, 343 | Token: token, 344 | TokenId: token.Id, 345 | Amount: amount, 346 | Nonce: nonce, 347 | Fee: fee.Fee.String(), 348 | TimeRange: timeRange, 349 | } 350 | ethSig, err := w.ethSigner.SignTransaction(txData, nonce, token, fee.Fee) 351 | if err != nil { 352 | return nil, errors.Wrap(err, "failed to get sign of transaction") 353 | } 354 | txData.Signature, err = w.zkSigner.SignTransfer(txData) 355 | if err != nil { 356 | return nil, errors.Wrap(err, "failed to get sign of Transfer") 357 | } 358 | return &SignedTransaction{ 359 | Transaction: txData, 360 | EthereumSignature: ethSig, 361 | }, nil 362 | } 363 | 364 | func (w *Wallet) buildSignedWithdrawTx(to common.Address, tokenId string, amount *big.Int, fee *TransactionFee, nonce uint32, timeRange *TimeRange) (*SignedTransaction, error) { 365 | tokens, err := w.provider.GetTokens() 366 | if err != nil { 367 | return nil, errors.Wrap(err, "failed to get tokens") 368 | } 369 | token, err := tokens.GetToken(tokenId) 370 | if err != nil { 371 | return nil, errors.Wrap(err, "failed to get fee token") 372 | } 373 | txData := &Withdraw{ 374 | Type: "Withdraw", 375 | AccountId: w.accountId, 376 | From: w.ethSigner.GetAddress(), 377 | To: to, 378 | TokenId: token.Id, 379 | Amount: amount, 380 | Nonce: nonce, 381 | Fee: fee.Fee.String(), 382 | TimeRange: timeRange, 383 | } 384 | ethSig, err := w.ethSigner.SignTransaction(txData, nonce, token, fee.Fee) 385 | if err != nil { 386 | return nil, errors.Wrap(err, "failed to get sign of transaction") 387 | } 388 | txData.Signature, err = w.zkSigner.SignWithdraw(txData) 389 | if err != nil { 390 | return nil, errors.Wrap(err, "failed to get sign of Withdraw") 391 | } 392 | return &SignedTransaction{ 393 | Transaction: txData, 394 | EthereumSignature: ethSig, 395 | }, nil 396 | } 397 | 398 | func (w *Wallet) buildSignedForcedExitTx(target common.Address, tokenId string, fee *TransactionFee, nonce uint32, timeRange *TimeRange) (*SignedTransaction, error) { 399 | tokens, err := w.provider.GetTokens() 400 | if err != nil { 401 | return nil, errors.Wrap(err, "failed to get tokens") 402 | } 403 | token, err := tokens.GetToken(tokenId) 404 | if err != nil { 405 | return nil, errors.Wrap(err, "failed to get fee token") 406 | } 407 | txData := &ForcedExit{ 408 | Type: "ForcedExit", 409 | AccountId: w.accountId, 410 | Target: target, 411 | TokenId: token.Id, 412 | Nonce: nonce, 413 | Fee: fee.Fee.String(), 414 | TimeRange: timeRange, 415 | } 416 | ethSig, err := w.ethSigner.SignTransaction(txData, nonce, token, fee.Fee) 417 | if err != nil { 418 | return nil, errors.Wrap(err, "failed to get sign of transaction") 419 | } 420 | txData.Signature, err = w.zkSigner.SignForcedExit(txData) 421 | if err != nil { 422 | return nil, errors.Wrap(err, "failed to get sign of ForcedExit") 423 | } 424 | return &SignedTransaction{ 425 | Transaction: txData, 426 | EthereumSignature: ethSig, 427 | }, nil 428 | } 429 | 430 | func (w *Wallet) buildSignedMintNFTTx(to common.Address, contentHash common.Hash, feeTokenId string, fee *TransactionFee, nonce uint32) (*SignedTransaction, error) { 431 | tokens, err := w.provider.GetTokens() 432 | if err != nil { 433 | return nil, errors.Wrap(err, "failed to get tokens") 434 | } 435 | feeToken, err := tokens.GetToken(feeTokenId) 436 | if err != nil { 437 | return nil, errors.Wrap(err, "failed to get fee Token") 438 | } 439 | txData := &MintNFT{ 440 | Type: "MintNFT", 441 | CreatorId: w.accountId, 442 | CreatorAddress: w.GetAddress(), 443 | ContentHash: contentHash, 444 | Recipient: to, 445 | Nonce: nonce, 446 | Fee: fee.Fee.String(), 447 | FeeToken: feeToken.Id, 448 | } 449 | ethSig, err := w.ethSigner.SignTransaction(txData, nonce, feeToken, fee.Fee) 450 | if err != nil { 451 | return nil, errors.Wrap(err, "failed to get sign of transaction") 452 | } 453 | txData.Signature, err = w.zkSigner.SignMintNFT(txData) 454 | if err != nil { 455 | return nil, errors.Wrap(err, "failed to get sign of MintNFT") 456 | } 457 | return &SignedTransaction{ 458 | Transaction: txData, 459 | EthereumSignature: ethSig, 460 | }, nil 461 | } 462 | 463 | func (w *Wallet) buildSignedWithdrawNFTTx(to common.Address, NFToken *NFT, feeTokenId string, fee *TransactionFee, nonce uint32, timeRange *TimeRange) (*SignedTransaction, error) { 464 | tokens, err := w.provider.GetTokens() 465 | if err != nil { 466 | return nil, errors.Wrap(err, "failed to get tokens") 467 | } 468 | feeToken, err := tokens.GetToken(feeTokenId) 469 | if err != nil { 470 | return nil, errors.Wrap(err, "failed to get fee Token") 471 | } 472 | txData := &WithdrawNFT{ 473 | Type: "WithdrawNFT", 474 | AccountId: w.accountId, 475 | From: w.GetAddress(), 476 | To: to, 477 | Token: NFToken.Id, 478 | Nonce: nonce, 479 | Fee: fee.Fee.String(), 480 | FeeToken: feeToken.Id, 481 | TimeRange: timeRange, 482 | } 483 | ethSig, err := w.ethSigner.SignTransaction(txData, nonce, feeToken, fee.Fee) 484 | if err != nil { 485 | return nil, errors.Wrap(err, "failed to get sign of transaction") 486 | } 487 | txData.Signature, err = w.zkSigner.SignWithdrawNFT(txData) 488 | if err != nil { 489 | return nil, errors.Wrap(err, "failed to get sign of WithdrawNFT") 490 | } 491 | return &SignedTransaction{ 492 | Transaction: txData, 493 | EthereumSignature: ethSig, 494 | }, nil 495 | } 496 | 497 | func (w *Wallet) buildSignedSwapTx(order1, order2 *Order, amount1, amount2 *big.Int, fee *TransactionFee, nonce uint32) (*SignedTransaction, error) { 498 | tokens, err := w.provider.GetTokens() 499 | if err != nil { 500 | return nil, errors.Wrap(err, "failed to get tokens") 501 | } 502 | token, err := tokens.GetToken(fee.FeeToken) 503 | if err != nil { 504 | return nil, errors.Wrap(err, "failed to get fee token") 505 | } 506 | txData := &Swap{ 507 | Type: "Swap", 508 | Orders: []*Order{order1, order2}, 509 | SubmitterId: w.accountId, 510 | SubmitterAddress: w.ethSigner.GetAddress(), 511 | Amounts: []*big.Int{amount1, amount2}, 512 | Nonce: nonce, 513 | Fee: fee.Fee.String(), 514 | FeeToken: token.Id, 515 | } 516 | ethSig, err := w.ethSigner.SignTransaction(txData, nonce, token, fee.Fee) 517 | if err != nil { 518 | return nil, errors.Wrap(err, "failed to get eth sign of swap transaction") 519 | } 520 | txData.Signature, err = w.zkSigner.SignSwap(txData) 521 | if err != nil { 522 | return nil, errors.Wrap(err, "failed to get sign of Swap tx") 523 | } 524 | return &SignedTransaction{ 525 | Transaction: txData, 526 | EthereumSignature: ethSig, 527 | }, nil 528 | } 529 | 530 | func (w *Wallet) submitSignedTransaction(tx ZksTransaction, ethSignature *EthSignature, fastProcessing bool) (string, error) { 531 | return w.provider.SubmitTx(tx, ethSignature, fastProcessing) 532 | } 533 | 534 | func (w *Wallet) submitMultiSignedTransaction(tx ZksTransaction, ethSignatures ...*EthSignature) (string, error) { 535 | return w.provider.SubmitTxMultiSig(tx, ethSignatures...) 536 | } 537 | 538 | func (w *Wallet) submitSignedTransactionsBatch(txs []*SignedTransaction, ethSignature *EthSignature) ([]string, error) { 539 | return w.provider.SubmitTxsBatch(txs, ethSignature) 540 | } 541 | -------------------------------------------------------------------------------- /Wallet_test.go: -------------------------------------------------------------------------------- 1 | package zksync 2 | 3 | import ( 4 | "encoding/hex" 5 | "github.com/ethereum/go-ethereum/common" 6 | "github.com/stretchr/testify/assert" 7 | "github.com/stretchr/testify/mock" 8 | "github.com/stretchr/testify/require" 9 | "math/big" 10 | "testing" 11 | ) 12 | 13 | var ( 14 | providerMock *ProviderMock 15 | wallet *Wallet 16 | 17 | expAccountId = uint32(185689) 18 | toAccountId = uint32(189095) 19 | nonce = uint32(8) 20 | nonce2 = uint32(3) 21 | accountState = &AccountState{ 22 | Address: expAddress, 23 | Id: expAccountId, 24 | Depositing: &DepositingState{ 25 | Balances: map[string]*DepositingBalance{}, 26 | }, 27 | Committed: &State{ 28 | Balances: map[string]string{"ETH": "98501901000000000"}, 29 | Nonce: nonce, 30 | PubKeyHash: "", //expPubKeyHash, 31 | Nfts: map[string]*NFT{}, 32 | MintedNfts: map[string]*NFT{}, 33 | }, 34 | Verified: &State{ 35 | Balances: map[string]string{"ETH": "98501901000000000"}, 36 | Nonce: nonce, 37 | PubKeyHash: "", //expPubKeyHash, 38 | Nfts: map[string]*NFT{}, 39 | MintedNfts: map[string]*NFT{}, 40 | }, 41 | } 42 | account2State = &AccountState{ 43 | Address: toAddress, 44 | Id: toAccountId, 45 | Depositing: &DepositingState{ 46 | Balances: map[string]*DepositingBalance{}, 47 | }, 48 | Committed: &State{ 49 | Balances: map[string]string{"ETH": "197846750000000000"}, 50 | Nonce: nonce2, 51 | PubKeyHash: "", //expPubKeyHash, 52 | Nfts: map[string]*NFT{}, 53 | MintedNfts: map[string]*NFT{}, 54 | }, 55 | Verified: &State{ 56 | Balances: map[string]string{"ETH": "197846750000000000"}, 57 | Nonce: nonce2, 58 | PubKeyHash: "", //expPubKeyHash, 59 | Nfts: map[string]*NFT{}, 60 | MintedNfts: map[string]*NFT{}, 61 | }, 62 | } 63 | 64 | tokens = &Tokens{ 65 | Tokens: map[string]*Token{ 66 | "ETH": { 67 | Id: 0, 68 | Address: "0x0000000000000000000000000000000000000000", 69 | Symbol: "ETH", 70 | Decimals: 18, 71 | IsNft: false, 72 | }, 73 | }, 74 | } 75 | 76 | defaultTimeRange = DefaultTimeRange() 77 | 78 | totalFee = "45400000000000" 79 | txFeeDetails = &TransactionFeeDetails{ 80 | GasTxAmount: "13900", 81 | GasPriceWei: "1000000011", 82 | GasFee: "18070000194600", 83 | ZkpFee: "27404918495870", 84 | TotalFee: totalFee, 85 | } 86 | 87 | NFTContentHash = common.HexToHash("5555") 88 | expNFT = &NFT{ 89 | Id: 70231, 90 | Symbol: "NFT-70231", 91 | CreatorId: 185689, 92 | ContentHash: NFTContentHash, 93 | CreatorAddress: common.HexToAddress(expAddress), 94 | SerialId: 0, 95 | Address: "0xb75ec5d9b1671ede6159ac5b8c16f26ae8335abd", 96 | } 97 | ) 98 | 99 | func createWallet(t *testing.T) { 100 | if wallet == nil { 101 | // create EthSigner 102 | ethSigner, err := NewEthSignerFromMnemonic(mnemonic) 103 | require.NoError(t, err) 104 | assert.NotNil(t, ethSigner) 105 | // create ZkSigner 106 | seed, _ := hex.DecodeString(seedHex) 107 | zkSigner, err := NewZkSignerFromSeed(seed) 108 | require.NoError(t, err) 109 | assert.NotNil(t, zkSigner) 110 | // setup Provider mock 111 | providerMock = &ProviderMock{} 112 | providerMock.On("GetState", common.HexToAddress(expAddress)). 113 | Return(accountState, nil).Once() 114 | 115 | wallet, err = NewWallet(ethSigner, zkSigner, providerMock) 116 | require.NoError(t, err) 117 | assert.NotNil(t, wallet) 118 | 119 | providerMock.On("GetTokens").Return(tokens, nil) 120 | } 121 | } 122 | 123 | func createWallet2(t *testing.T) *Wallet { 124 | // create EthSigner 125 | ethSigner, err := NewEthSignerFromMnemonic(mnemonic2) 126 | require.NoError(t, err) 127 | assert.NotNil(t, ethSigner) 128 | // create ZkSigner 129 | seed2, _ := hex.DecodeString(seed2Hex) 130 | zkSigner, err := NewZkSignerFromSeed(seed2) 131 | require.NoError(t, err) 132 | assert.NotNil(t, zkSigner) 133 | // setup Provider mock 134 | providerMock.On("GetState", common.HexToAddress(toAddress)). 135 | Return(account2State, nil).Once() 136 | 137 | wallet2, err := NewWallet(ethSigner, zkSigner, providerMock) 138 | require.NoError(t, err) 139 | assert.NotNil(t, wallet2) 140 | 141 | return wallet2 142 | } 143 | 144 | func TestNewWallet(t *testing.T) { 145 | createWallet(t) 146 | 147 | address := wallet.GetAddress() 148 | assert.NotNil(t, address) 149 | assert.IsType(t, common.Address{}, address) 150 | assert.EqualValues(t, expAddress, address.String()) 151 | 152 | accountId, err := wallet.GetAccountId() 153 | require.NoError(t, err) 154 | assert.EqualValues(t, expAccountId, accountId) 155 | 156 | providerMock.On("GetState", common.HexToAddress(expAddress)). 157 | Return(accountState, nil).Once() 158 | state, err := wallet.GetState() 159 | require.NoError(t, err) 160 | assert.Equal(t, accountState, state) 161 | 162 | pr := wallet.GetProvider() 163 | require.NoError(t, err) 164 | assert.EqualValues(t, providerMock, pr) 165 | } 166 | 167 | func TestChangePubKey(t *testing.T) { 168 | createWallet(t) 169 | 170 | t.Run("ECDSA", func(t *testing.T) { 171 | // expect for invoking of mocked provider.SubmitTx() method with properly prepared tx and valid signatures 172 | providerMock.On("SubmitTx", mock.AnythingOfType("*zksync.ChangePubKey"), mock.AnythingOfType("*zksync.EthSignature"), false). 173 | Run(func(args mock.Arguments) { 174 | // assert arguments 175 | assert.EqualValues(t, &ChangePubKey{ 176 | Type: "ChangePubKey", 177 | AccountId: expAccountId, 178 | Account: common.HexToAddress(expAddress), 179 | NewPkHash: expPubKeyHash, 180 | FeeToken: 0, 181 | Fee: totalFee, 182 | Nonce: nonce, 183 | Signature: &Signature{ 184 | PubKey: expPubKey, 185 | Signature: "3807301038f4edeaad14c39cd27e2f112a1edcdc69f7de714acd48301c340a1447606e546246167e4a6c5b3a0da740525c6c6698989c6529c749898037333c05", 186 | }, 187 | EthAuthData: &ChangePubKeyECDSA{ 188 | Type: "ECDSA", 189 | EthSignature: "0x50904be19b8dd300fd60d73325c183293b9ee21ee8dced7b462d257368bd60180666dac29b4b83fac9efa6b69619e181ad82b3ea9255a50589b2460d3173db311b", 190 | BatchHash: "0x0000000000000000000000000000000000000000000000000000000000000000", 191 | }, 192 | TimeRange: defaultTimeRange, 193 | }, args.Get(0)) 194 | assert.EqualValues(t, (*EthSignature)(nil), args.Get(1)) 195 | assert.EqualValues(t, false, args.Get(2)) 196 | }). 197 | Return("txHash", nil).Once() 198 | txHash, err := wallet.SetSigningKey(txFeeDetails.GetTxFee(CreateETH()), accountState.Committed.Nonce, false, defaultTimeRange) 199 | require.NoError(t, err) 200 | assert.EqualValues(t, "txHash", txHash) 201 | }) 202 | 203 | t.Run("OnChain", func(t *testing.T) { 204 | // expect for invoking of mocked provider.SubmitTx() method with properly prepared tx and valid signatures 205 | providerMock.On("SubmitTx", mock.AnythingOfType("*zksync.ChangePubKey"), mock.AnythingOfType("*zksync.EthSignature"), false). 206 | Run(func(args mock.Arguments) { 207 | // assert arguments 208 | assert.EqualValues(t, &ChangePubKey{ 209 | Type: "ChangePubKey", 210 | AccountId: expAccountId, 211 | Account: common.HexToAddress(expAddress), 212 | NewPkHash: expPubKeyHash, 213 | FeeToken: 0, 214 | Fee: totalFee, 215 | Nonce: nonce, 216 | Signature: &Signature{ 217 | PubKey: expPubKey, 218 | Signature: "3807301038f4edeaad14c39cd27e2f112a1edcdc69f7de714acd48301c340a1447606e546246167e4a6c5b3a0da740525c6c6698989c6529c749898037333c05", 219 | }, 220 | EthAuthData: &ChangePubKeyOnchain{ 221 | Type: "Onchain", 222 | }, 223 | TimeRange: defaultTimeRange, 224 | }, args.Get(0)) 225 | assert.EqualValues(t, (*EthSignature)(nil), args.Get(1)) 226 | assert.EqualValues(t, false, args.Get(2)) 227 | }). 228 | Return("txHash", nil).Once() 229 | txHash, err := wallet.SetSigningKey(txFeeDetails.GetTxFee(CreateETH()), accountState.Committed.Nonce, true, defaultTimeRange) 230 | require.NoError(t, err) 231 | assert.EqualValues(t, "txHash", txHash) 232 | }) 233 | } 234 | 235 | func TestTransfer(t *testing.T) { 236 | createWallet(t) 237 | 238 | amount := big.NewInt(12312124) 239 | // expect for invoking of mocked provider.SubmitTx() method with properly prepared tx and valid signatures 240 | providerMock.On("SubmitTx", mock.AnythingOfType("*zksync.Transfer"), mock.AnythingOfType("*zksync.EthSignature"), false). 241 | Run(func(args mock.Arguments) { 242 | // assert arguments 243 | assert.EqualValues(t, &Transfer{ 244 | Type: "Transfer", 245 | AccountId: expAccountId, 246 | From: common.HexToAddress(expAddress), 247 | To: common.HexToAddress(toAddress), 248 | Amount: amount, 249 | Token: CreateETH(), 250 | TokenId: 0, 251 | Fee: totalFee, 252 | Nonce: nonce, 253 | Signature: &Signature{ 254 | PubKey: expPubKey, 255 | Signature: "8a215f7ce352471542a1de78d011558765ead7fdfb172a65b06fdadfac23309e9f1957d51090bc390fbc62791bbb5691f3b3078de3d5426712d5d2c570bc5000", 256 | }, 257 | TimeRange: defaultTimeRange, 258 | }, args.Get(0)) 259 | assert.EqualValues(t, &EthSignature{ 260 | Type: "EthereumSignature", 261 | Signature: "0xfe1af278836c4556fd08a7b1b56c77a2f9e7ee85f34b1498067c1277c752268213bab629c4cb3edb25818b005545accd04f4d03e05afd4fd730b4c4cc1c27cd91b", 262 | }, args.Get(1)) 263 | assert.EqualValues(t, false, args.Get(2)) 264 | }). 265 | Return("txHash", nil).Once() 266 | txHash, err := wallet.SyncTransfer(common.HexToAddress(toAddress), amount, txFeeDetails.GetTxFee(CreateETH()), accountState.Committed.Nonce, defaultTimeRange) 267 | require.NoError(t, err) 268 | assert.EqualValues(t, "txHash", txHash) 269 | } 270 | 271 | func TestWithdraw(t *testing.T) { 272 | createWallet(t) 273 | 274 | amount := big.NewInt(12312124) 275 | // expect for invoking of mocked provider.SubmitTx() method with properly prepared tx and valid signatures 276 | providerMock.On("SubmitTx", mock.AnythingOfType("*zksync.Withdraw"), mock.AnythingOfType("*zksync.EthSignature"), false). 277 | Run(func(args mock.Arguments) { 278 | // assert arguments 279 | assert.EqualValues(t, &Withdraw{ 280 | Type: "Withdraw", 281 | AccountId: expAccountId, 282 | From: common.HexToAddress(expAddress), 283 | To: common.HexToAddress(toAddress), 284 | Amount: amount, 285 | TokenId: 0, 286 | Fee: totalFee, 287 | Nonce: nonce, 288 | Signature: &Signature{ 289 | PubKey: expPubKey, 290 | Signature: "e3ee5a13c147cd70579c17b261e057a41ed4719932e782eb69483aba0a79eb09b4517a16b1e1dbe6dc97f31f1fd38ae211d4b9afdcd3be59d0f9e4df65f88901", 291 | }, 292 | TimeRange: defaultTimeRange, 293 | }, args.Get(0)) 294 | assert.EqualValues(t, &EthSignature{ 295 | Type: "EthereumSignature", 296 | Signature: "0x3da4e0fb87b97886099113c88e9f15858455a5047e22ec8bd043d559da8d1e9f492751bd33ccdd1fa51ebe95f0d633cbbcaeb8591a0ec4aaf8e70c7bbae529fb1c", 297 | }, args.Get(1)) 298 | assert.EqualValues(t, false, args.Get(2)) 299 | }). 300 | Return("txHash", nil).Once() 301 | txHash, err := wallet.SyncWithdraw(common.HexToAddress(toAddress), amount, txFeeDetails.GetTxFee(CreateETH()), accountState.Committed.Nonce, false, defaultTimeRange) 302 | require.NoError(t, err) 303 | assert.EqualValues(t, "txHash", txHash) 304 | } 305 | 306 | func TestForcedExit(t *testing.T) { 307 | createWallet(t) 308 | 309 | // expect for invoking of mocked provider.SubmitTx() method with properly prepared tx and valid signatures 310 | providerMock.On("SubmitTx", mock.AnythingOfType("*zksync.ForcedExit"), mock.AnythingOfType("*zksync.EthSignature"), false). 311 | Run(func(args mock.Arguments) { 312 | // assert arguments 313 | assert.EqualValues(t, &ForcedExit{ 314 | Type: "ForcedExit", 315 | AccountId: expAccountId, 316 | Target: common.HexToAddress(toAddress), 317 | Amount: nil, 318 | TokenId: 0, 319 | Fee: totalFee, 320 | Nonce: nonce, 321 | Signature: &Signature{ 322 | PubKey: expPubKey, 323 | Signature: "cf72d635211d6d9105c9da6d249d325134278b865d64889f5d586722d8bfa92c8bd6b2a865ab881bf6cd63089b7cb875927f12c6469189f455562901f57ea202", 324 | }, 325 | TimeRange: defaultTimeRange, 326 | }, args.Get(0)) 327 | assert.EqualValues(t, &EthSignature{ 328 | Type: "EthereumSignature", 329 | Signature: "0x09f55237cb0056f4fc056b76568b97f17742d3ff7e82598c88e533a0dd118df544d625682ad8a1570f6571aac94aadc4ed50531248473292292abaf5950ca4fa1b", 330 | }, args.Get(1)) 331 | assert.EqualValues(t, false, args.Get(2)) 332 | }). 333 | Return("txHash", nil).Once() 334 | txHash, err := wallet.SyncForcedExit(common.HexToAddress(toAddress), txFeeDetails.GetTxFee(CreateETH()), accountState.Committed.Nonce, defaultTimeRange) 335 | require.NoError(t, err) 336 | assert.EqualValues(t, "txHash", txHash) 337 | } 338 | 339 | func TestMintNFT(t *testing.T) { 340 | createWallet(t) 341 | 342 | // expect for invoking of mocked provider.SubmitTx() method with properly prepared tx and valid signatures 343 | providerMock.On("SubmitTx", mock.AnythingOfType("*zksync.MintNFT"), mock.AnythingOfType("*zksync.EthSignature"), false). 344 | Run(func(args mock.Arguments) { 345 | // assert arguments 346 | assert.EqualValues(t, &MintNFT{ 347 | Type: "MintNFT", 348 | CreatorId: expAccountId, 349 | CreatorAddress: common.HexToAddress(expAddress), 350 | Recipient: common.HexToAddress(toAddress), 351 | ContentHash: NFTContentHash, 352 | Fee: totalFee, 353 | Nonce: nonce, 354 | Signature: &Signature{ 355 | PubKey: expPubKey, 356 | Signature: "908eb8f6337676b0561b99d6ae83086f3e93d4900cbdf2f8b9234038dd08801590a4d301f71e5c77680db7a2ab10c65dee18945267a373c75015eb67879a5003", 357 | }, 358 | }, args.Get(0)) 359 | assert.EqualValues(t, &EthSignature{ 360 | Type: "EthereumSignature", 361 | Signature: "0x1651b964f061df09f7d710020d0bbfd05c631216a1e29db2abfc1431a52dc6696a5cb736a4bdc0e45f22be438f97ed71ffd47a46e3dc6710549962e7be31b5e71b", 362 | }, args.Get(1)) 363 | assert.EqualValues(t, false, args.Get(2)) 364 | }). 365 | Return("txHash", nil).Once() 366 | txHash, err := wallet.SyncMintNFT(common.HexToAddress(toAddress), NFTContentHash, txFeeDetails.GetTxFee(CreateETH()), accountState.Committed.Nonce) 367 | require.NoError(t, err) 368 | assert.EqualValues(t, "txHash", txHash) 369 | } 370 | 371 | func TestWithdrawNFT(t *testing.T) { 372 | createWallet(t) 373 | 374 | // expect for invoking of mocked provider.SubmitTx() method with properly prepared tx and valid signatures 375 | providerMock.On("SubmitTx", mock.AnythingOfType("*zksync.WithdrawNFT"), mock.AnythingOfType("*zksync.EthSignature"), false). 376 | Run(func(args mock.Arguments) { 377 | // assert arguments 378 | assert.EqualValues(t, &WithdrawNFT{ 379 | Type: "WithdrawNFT", 380 | AccountId: expAccountId, 381 | From: common.HexToAddress(expAddress), 382 | To: common.HexToAddress(toAddress), 383 | Token: expNFT.Id, 384 | FeeToken: 0, 385 | Fee: totalFee, 386 | Nonce: nonce, 387 | Signature: &Signature{ 388 | PubKey: expPubKey, 389 | Signature: "f8b164dd54e2be0239bcefc9bea69b26e74a6b4126ae186da2277c0cf76a43a2cb6002a86cc3e2fbce62646550ef2f7753d787e68e8d0e3baf1f6d52508de500", 390 | }, 391 | TimeRange: defaultTimeRange, 392 | }, args.Get(0)) 393 | assert.EqualValues(t, &EthSignature{ 394 | Type: "EthereumSignature", 395 | Signature: "0xe1c842e393bc6db5cf45fc2e99144c66e2db6e79b1715e554f1304bf67d0e8220b84dcd724b382ff4d8d5ba3e7d5cbfc72b1c11f31bd2c1317ec8f6b958754691c", 396 | }, args.Get(1)) 397 | assert.EqualValues(t, false, args.Get(2)) 398 | }). 399 | Return("txHash", nil).Once() 400 | txHash, err := wallet.SyncWithdrawNFT(common.HexToAddress(toAddress), expNFT, txFeeDetails.GetTxFee(CreateETH()), accountState.Committed.Nonce, defaultTimeRange) 401 | require.NoError(t, err) 402 | assert.EqualValues(t, "txHash", txHash) 403 | } 404 | 405 | func TestTransferNFT(t *testing.T) { 406 | createWallet(t) 407 | 408 | // expect for invoking of provider.SubmitTxsBatch() method with properly prepared txs and valid signatures 409 | providerMock.On("SubmitTxsBatch", mock.AnythingOfType("[]*zksync.SignedTransaction"), mock.AnythingOfType("*zksync.EthSignature")). 410 | Run(func(args mock.Arguments) { 411 | // assert arguments 412 | assert.EqualValues(t, []*SignedTransaction{ 413 | { 414 | Transaction: &Transfer{ 415 | Type: "Transfer", 416 | AccountId: expAccountId, 417 | From: common.HexToAddress(expAddress), 418 | To: common.HexToAddress(toAddress), 419 | Token: expNFT.ToToken(), 420 | TokenId: expNFT.Id, 421 | Amount: big.NewInt(1), 422 | Nonce: nonce, 423 | Fee: big.NewInt(0).String(), 424 | Signature: &Signature{ 425 | PubKey: expPubKey, 426 | Signature: "8d89c37a4586215622b27a46561f313779e0ab4df0a3370c5c032ede15bedb9ef92d6aa92d135bd1e305d24b0b24cccc6939c2fe901a36f312796ae05b8c4103", 427 | }, 428 | TimeRange: defaultTimeRange, 429 | }, 430 | }, 431 | { 432 | Transaction: &Transfer{ 433 | Type: "Transfer", 434 | AccountId: expAccountId, 435 | From: common.HexToAddress(expAddress), 436 | To: common.HexToAddress(expAddress), 437 | Token: CreateETH(), 438 | TokenId: 0, 439 | Amount: big.NewInt(0), 440 | Nonce: nonce + 1, 441 | Fee: totalFee, 442 | Signature: &Signature{ 443 | PubKey: expPubKey, 444 | Signature: "cb4ce668c30b5b65a8cdfa8bb4260c619ba975e8593d6d169dd57ad3a6554b119f3d07d8125814b5b1970ee22d69e9ed30b9410e65df5a333cda1daf0da79704", 445 | }, 446 | TimeRange: defaultTimeRange, 447 | }, 448 | }, 449 | }, args.Get(0)) 450 | assert.EqualValues(t, &EthSignature{ 451 | Type: "EthereumSignature", 452 | Signature: "0x6006560738fe3b90022a7dac270c35ab79c224021d04fa18ec130d266be131dd0d54aac3663ef0a3f4e6ec69dd904725fe1bb051e87b4bdcf5b30fd2f168ec1f1c", 453 | }, args.Get(1)) 454 | 455 | }). 456 | Return([]string{"tx1Hash", "tx2Hash"}, nil).Once() 457 | txs, err := wallet.SyncTransferNFT(common.HexToAddress(toAddress), expNFT, txFeeDetails.GetTxFee(CreateETH()), accountState.Committed.Nonce, defaultTimeRange) 458 | require.NoError(t, err) 459 | assert.EqualValues(t, 2, len(txs)) 460 | assert.EqualValues(t, "tx1Hash", txs[0]) 461 | assert.EqualValues(t, "tx2Hash", txs[1]) 462 | } 463 | 464 | func TestSwap(t *testing.T) { 465 | createWallet(t) 466 | amount := big.NewInt(100000000000000) 467 | 468 | order1, err := wallet.BuildSignedOrder(common.HexToAddress(expAddress), 469 | CreateETH(), CreateETH(), []*big.Int{big.NewInt(1), big.NewInt(1)}, amount, 470 | accountState.Committed.Nonce, defaultTimeRange) 471 | require.NoError(t, err) 472 | require.NotNil(t, order1) 473 | assert.EqualValues(t, &Order{ 474 | AccountId: expAccountId, 475 | RecipientAddress: common.HexToAddress(expAddress), 476 | Nonce: nonce, 477 | TokenBuy: 0, 478 | TokenSell: 0, 479 | Ratio: []*big.Int{big.NewInt(1), big.NewInt(1)}, 480 | Amount: amount, 481 | Signature: &Signature{ 482 | PubKey: expPubKey, 483 | Signature: "0021f5b88420ee5fba86ae31ae03b22c56b61cf13fc14a73ce04fee40e46b68e2e086271513844059a0d079ad37e5ad1b7dd22d19290c8d26d22d480fb070805", 484 | }, 485 | EthereumSignature: &EthSignature{ 486 | Type: "EthereumSignature", 487 | Signature: "0xc101d1eecf8f35b74efc9d296f5d236389178f917a33416a15a85795f3b2c88d4e2306edde31106b16f7ffdc008451f57c9334a38bdbda81e608dc01b3c2a6831c", 488 | }, 489 | TimeRange: defaultTimeRange, 490 | }, order1) 491 | 492 | wallet2 := createWallet2(t) 493 | order2, err := wallet2.BuildSignedOrder(common.HexToAddress(toAddress), 494 | CreateETH(), CreateETH(), []*big.Int{big.NewInt(1), big.NewInt(1)}, amount, 495 | account2State.Committed.Nonce, defaultTimeRange) 496 | require.NoError(t, err) 497 | require.NotNil(t, order2) 498 | assert.EqualValues(t, &Order{ 499 | AccountId: toAccountId, 500 | RecipientAddress: common.HexToAddress(toAddress), 501 | Nonce: nonce2, 502 | TokenBuy: 0, 503 | TokenSell: 0, 504 | Ratio: []*big.Int{big.NewInt(1), big.NewInt(1)}, 505 | Amount: amount, 506 | Signature: &Signature{ 507 | PubKey: toPubKey, 508 | Signature: "3f01d85af2339b1d800ff727c2c9b66af29d6e449cb1fbe25af8643b57fc0f2401a3aa2e1ef236284919fa7edd7423acefab78aa46503f7399a6a06da95a2d05", 509 | }, 510 | EthereumSignature: &EthSignature{ 511 | Type: "EthereumSignature", 512 | Signature: "0xbc74f5046d9e30cc8faa0e4ff39dae207d112aa3e17b93ae12ea20f39928d12548b78986a707aa157345b517e8081a3b25c7223761c419fcf1abc66ef8d9623a1b", 513 | }, 514 | TimeRange: defaultTimeRange, 515 | }, order2) 516 | 517 | // expect for invoking of provider.SubmitTxMultiSig() method with properly prepared swap tx and three valid signatures 518 | providerMock.On("SubmitTxMultiSig", mock.AnythingOfType("*zksync.Swap"), 519 | mock.AnythingOfType("*zksync.EthSignature"), 520 | mock.AnythingOfType("*zksync.EthSignature"), 521 | mock.AnythingOfType("*zksync.EthSignature")). 522 | Run(func(args mock.Arguments) { 523 | // assert arguments 524 | assert.EqualValues(t, &Swap{ 525 | Type: "Swap", 526 | SubmitterId: expAccountId, 527 | SubmitterAddress: common.HexToAddress(expAddress), 528 | Orders: []*Order{order1, order2}, 529 | Amounts: []*big.Int{amount, amount}, 530 | FeeToken: 0, 531 | Fee: totalFee, 532 | Nonce: nonce, 533 | Signature: &Signature{ 534 | PubKey: expPubKey, 535 | Signature: "169a3e96a63c983a7c7dc72c0882fb0c563d69803e11ccc9b9a0f0d00855c19dc3ea9726f94b32a1ce5d629ae9132a35e33d63088662e32600bfcd0ea8404e04", 536 | }, 537 | }, args.Get(0)) 538 | assert.EqualValues(t, &EthSignature{ 539 | Type: "EthereumSignature", 540 | Signature: "0x0a6d3a68c54306e6e4739ab6d7c3144460acaffa31ca6931cdc94478d6aa14443875a986c4c3695219bc3bb14d5e1de9030dccb73deb2150bbf405ce650f43f91b", 541 | }, args.Get(1)) 542 | assert.EqualValues(t, &EthSignature{ 543 | Type: "EthereumSignature", 544 | Signature: "0xc101d1eecf8f35b74efc9d296f5d236389178f917a33416a15a85795f3b2c88d4e2306edde31106b16f7ffdc008451f57c9334a38bdbda81e608dc01b3c2a6831c", 545 | }, args.Get(2)) 546 | assert.EqualValues(t, &EthSignature{ 547 | Type: "EthereumSignature", 548 | Signature: "0xbc74f5046d9e30cc8faa0e4ff39dae207d112aa3e17b93ae12ea20f39928d12548b78986a707aa157345b517e8081a3b25c7223761c419fcf1abc66ef8d9623a1b", 549 | }, args.Get(3)) 550 | }). 551 | Return("txHash", nil).Once() 552 | 553 | txHash, err := wallet.SyncSwap(order1, order2, amount, amount, txFeeDetails.GetTxFee(CreateETH()), accountState.Committed.Nonce) 554 | require.NoError(t, err) 555 | assert.EqualValues(t, "txHash", txHash) 556 | } 557 | -------------------------------------------------------------------------------- /Withdraw.go: -------------------------------------------------------------------------------- 1 | package zksync 2 | 3 | import ( 4 | "github.com/ethereum/go-ethereum/common" 5 | "math/big" 6 | ) 7 | 8 | const ( 9 | TransactionTypeWithdraw TransactionType = "Withdraw" 10 | TransactionTypeForcedExit TransactionType = "ForcedExit" 11 | ) 12 | 13 | type Withdraw struct { 14 | Type string `json:"type"` 15 | AccountId uint32 `json:"accountId"` 16 | From common.Address `json:"from"` 17 | To common.Address `json:"to"` 18 | TokenId uint32 `json:"token"` 19 | Amount *big.Int `json:"amount"` 20 | Fee string `json:"fee"` 21 | Nonce uint32 `json:"nonce"` 22 | Signature *Signature `json:"signature"` 23 | *TimeRange 24 | } 25 | 26 | func (t *Withdraw) getType() string { 27 | return "Withdraw" 28 | } 29 | 30 | type ForcedExit struct { 31 | Type string `json:"type"` 32 | AccountId uint32 `json:"initiatorAccountId"` 33 | Target common.Address `json:"target"` 34 | TokenId uint32 `json:"token"` 35 | Amount *big.Int `json:"amount"` 36 | Fee string `json:"fee"` 37 | Nonce uint32 `json:"nonce"` 38 | Signature *Signature `json:"signature"` 39 | *TimeRange 40 | } 41 | 42 | func (t *ForcedExit) getType() string { 43 | return "ForcedExit" 44 | } 45 | -------------------------------------------------------------------------------- /ZkSigner.go: -------------------------------------------------------------------------------- 1 | package zksync 2 | 3 | import ( 4 | "bytes" 5 | "fmt" 6 | "github.com/pkg/errors" 7 | "github.com/zksync-sdk/zksync-sdk-go" 8 | "math/big" 9 | ) 10 | 11 | const ( 12 | Message = "Access zkSync account.\n\nOnly sign this message for a trusted client!" 13 | TransactionVersion byte = 0x01 14 | ) 15 | 16 | func NewZkSignerFromSeed(seed []byte) (*ZkSigner, error) { 17 | privateKey, err := zkscrypto.NewPrivateKey(seed) 18 | if err != nil { 19 | return nil, errors.Wrap(err, "failed to create private key") 20 | } 21 | return newZkSignerFromPrivateKey(privateKey) 22 | } 23 | 24 | func NewZkSignerFromRawPrivateKey(rawPk []byte) (*ZkSigner, error) { 25 | privateKey, err := zkscrypto.NewPrivateKeyRaw(rawPk) 26 | if err != nil { 27 | return nil, errors.Wrap(err, "failed to create private key from raw bytes") 28 | } 29 | return newZkSignerFromPrivateKey(privateKey) 30 | } 31 | 32 | func NewZkSignerFromEthSigner(es EthSigner, cid ChainId) (*ZkSigner, error) { 33 | signMsg := Message 34 | if cid != ChainIdMainnet { 35 | signMsg = fmt.Sprintf("%s\nChain ID: %d.", Message, cid) 36 | } 37 | sig, err := es.SignMessage([]byte(signMsg)) 38 | if err != nil { 39 | return nil, errors.Wrap(err, "failed to sign special message") 40 | } 41 | return NewZkSignerFromSeed(sig) 42 | } 43 | 44 | func newZkSignerFromPrivateKey(privateKey *zkscrypto.PrivateKey) (*ZkSigner, error) { 45 | publicKey, err := privateKey.PublicKey() 46 | if err != nil { 47 | return nil, errors.Wrap(err, "failed to create public key") 48 | } 49 | publicKeyHash, err := publicKey.Hash() 50 | if err != nil { 51 | return nil, errors.Wrap(err, "failed to get public key hash") 52 | } 53 | return &ZkSigner{ 54 | privateKey: privateKey, 55 | publicKey: publicKey, 56 | publicKeyHash: publicKeyHash, 57 | }, nil 58 | } 59 | 60 | type ZkSigner struct { 61 | privateKey *zkscrypto.PrivateKey 62 | publicKey *zkscrypto.PublicKey 63 | publicKeyHash *zkscrypto.PublicKeyHash 64 | } 65 | 66 | func (s *ZkSigner) Sign(message []byte) (*zkscrypto.Signature, error) { 67 | signature, err := s.privateKey.Sign(message) 68 | if err != nil { 69 | return nil, errors.Wrap(err, "failed to sign message") 70 | } 71 | return signature, nil 72 | } 73 | 74 | func (s *ZkSigner) SignChangePubKey(txData *ChangePubKey) (*Signature, error) { 75 | buf := bytes.Buffer{} 76 | buf.WriteByte(0xff - 0x07) 77 | buf.WriteByte(TransactionVersion) 78 | buf.Write(Uint32ToBytes(txData.AccountId)) 79 | buf.Write(txData.Account[:]) 80 | pkhBytes, err := pkhToBytes(txData.NewPkHash) 81 | if err != nil { 82 | return nil, errors.Wrap(err, "failed to get pkh bytes") 83 | } 84 | buf.Write(pkhBytes) 85 | buf.Write(Uint32ToBytes(txData.FeeToken)) 86 | fee, ok := big.NewInt(0).SetString(txData.Fee, 10) 87 | if !ok { 88 | return nil, errors.New("failed to convert string fee to big.Int") 89 | } 90 | packedFee, err := packFee(fee) 91 | if err != nil { 92 | return nil, errors.Wrap(err, "failed to pack fee") 93 | } 94 | buf.Write(packedFee) 95 | buf.Write(Uint32ToBytes(txData.Nonce)) 96 | buf.Write(Uint64ToBytes(txData.TimeRange.ValidFrom)) 97 | buf.Write(Uint64ToBytes(txData.TimeRange.ValidUntil)) 98 | sig, err := s.Sign(buf.Bytes()) 99 | if err != nil { 100 | return nil, errors.Wrap(err, "failed to sign ChangePubKey tx data") 101 | } 102 | res := &Signature{ 103 | PubKey: s.GetPublicKey(), 104 | Signature: sig.HexString(), 105 | } 106 | return res, nil 107 | } 108 | 109 | func (s *ZkSigner) SignTransfer(txData *Transfer) (*Signature, error) { 110 | buf := bytes.Buffer{} 111 | buf.WriteByte(0xff - 0x05) 112 | buf.WriteByte(TransactionVersion) 113 | buf.Write(Uint32ToBytes(txData.AccountId)) 114 | buf.Write(txData.From[:]) 115 | buf.Write(txData.To[:]) 116 | buf.Write(Uint32ToBytes(txData.Token.Id)) 117 | packedAmount, err := packAmount(txData.Amount) 118 | if err != nil { 119 | return nil, errors.Wrap(err, "failed to pack amount") 120 | } 121 | buf.Write(packedAmount) 122 | fee, ok := big.NewInt(0).SetString(txData.Fee, 10) 123 | if !ok { 124 | return nil, errors.New("failed to convert string fee to big.Int") 125 | } 126 | packedFee, err := packFee(fee) 127 | if err != nil { 128 | return nil, errors.Wrap(err, "failed to pack fee") 129 | } 130 | buf.Write(packedFee) 131 | buf.Write(Uint32ToBytes(txData.Nonce)) 132 | buf.Write(Uint64ToBytes(txData.TimeRange.ValidFrom)) 133 | buf.Write(Uint64ToBytes(txData.TimeRange.ValidUntil)) 134 | sig, err := s.Sign(buf.Bytes()) 135 | if err != nil { 136 | return nil, errors.Wrap(err, "failed to sign Transfer tx data") 137 | } 138 | res := &Signature{ 139 | PubKey: s.GetPublicKey(), 140 | Signature: sig.HexString(), 141 | } 142 | return res, nil 143 | } 144 | 145 | func (s *ZkSigner) SignWithdraw(txData *Withdraw) (*Signature, error) { 146 | buf := bytes.Buffer{} 147 | buf.WriteByte(0xff - 0x03) 148 | buf.WriteByte(TransactionVersion) 149 | buf.Write(Uint32ToBytes(txData.AccountId)) 150 | buf.Write(txData.From[:]) 151 | buf.Write(txData.To[:]) 152 | buf.Write(Uint32ToBytes(txData.TokenId)) 153 | amountBytes := txData.Amount.Bytes() 154 | buf.Write(make([]byte, 16-len(amountBytes))) // total amount slot is 16 bytes BE 155 | buf.Write(amountBytes) 156 | fee, ok := big.NewInt(0).SetString(txData.Fee, 10) 157 | if !ok { 158 | return nil, errors.New("failed to convert string fee to big.Int") 159 | } 160 | packedFee, err := packFee(fee) 161 | if err != nil { 162 | return nil, errors.Wrap(err, "failed to pack fee") 163 | } 164 | buf.Write(packedFee) 165 | buf.Write(Uint32ToBytes(txData.Nonce)) 166 | buf.Write(Uint64ToBytes(txData.TimeRange.ValidFrom)) 167 | buf.Write(Uint64ToBytes(txData.TimeRange.ValidUntil)) 168 | sig, err := s.Sign(buf.Bytes()) 169 | if err != nil { 170 | return nil, errors.Wrap(err, "failed to sign Withdraw tx data") 171 | } 172 | res := &Signature{ 173 | PubKey: s.GetPublicKey(), 174 | Signature: sig.HexString(), 175 | } 176 | return res, nil 177 | } 178 | 179 | func (s *ZkSigner) SignForcedExit(txData *ForcedExit) (*Signature, error) { 180 | buf := bytes.Buffer{} 181 | buf.WriteByte(0xff - 0x08) 182 | buf.WriteByte(TransactionVersion) 183 | buf.Write(Uint32ToBytes(txData.AccountId)) 184 | buf.Write(txData.Target[:]) 185 | buf.Write(Uint32ToBytes(txData.TokenId)) 186 | fee, ok := big.NewInt(0).SetString(txData.Fee, 10) 187 | if !ok { 188 | return nil, errors.New("failed to convert string fee to big.Int") 189 | } 190 | packedFee, err := packFee(fee) 191 | if err != nil { 192 | return nil, errors.Wrap(err, "failed to pack fee") 193 | } 194 | buf.Write(packedFee) 195 | buf.Write(Uint32ToBytes(txData.Nonce)) 196 | buf.Write(Uint64ToBytes(txData.TimeRange.ValidFrom)) 197 | buf.Write(Uint64ToBytes(txData.TimeRange.ValidUntil)) 198 | sig, err := s.Sign(buf.Bytes()) 199 | if err != nil { 200 | return nil, errors.Wrap(err, "failed to sign ForcedExit tx data") 201 | } 202 | res := &Signature{ 203 | PubKey: s.GetPublicKey(), 204 | Signature: sig.HexString(), 205 | } 206 | return res, nil 207 | } 208 | 209 | func (s *ZkSigner) SignMintNFT(txData *MintNFT) (*Signature, error) { 210 | buf := bytes.Buffer{} 211 | buf.WriteByte(0xff - 0x09) 212 | buf.WriteByte(TransactionVersion) 213 | buf.Write(Uint32ToBytes(txData.CreatorId)) 214 | buf.Write(txData.CreatorAddress[:]) 215 | buf.Write(txData.ContentHash.Bytes()) 216 | buf.Write(txData.Recipient[:]) 217 | buf.Write(Uint32ToBytes(txData.FeeToken)) 218 | fee, ok := big.NewInt(0).SetString(txData.Fee, 10) 219 | if !ok { 220 | return nil, errors.New("failed to convert string fee to big.Int") 221 | } 222 | packedFee, err := packFee(fee) 223 | if err != nil { 224 | return nil, errors.Wrap(err, "failed to pack fee") 225 | } 226 | buf.Write(packedFee) 227 | buf.Write(Uint32ToBytes(txData.Nonce)) 228 | sig, err := s.Sign(buf.Bytes()) 229 | if err != nil { 230 | return nil, errors.Wrap(err, "failed to sign MintNFT tx data") 231 | } 232 | res := &Signature{ 233 | PubKey: s.GetPublicKey(), 234 | Signature: sig.HexString(), 235 | } 236 | return res, nil 237 | } 238 | 239 | func (s *ZkSigner) SignWithdrawNFT(txData *WithdrawNFT) (*Signature, error) { 240 | buf := bytes.Buffer{} 241 | buf.WriteByte(0xff - 0x0a) 242 | buf.WriteByte(TransactionVersion) 243 | buf.Write(Uint32ToBytes(txData.AccountId)) 244 | buf.Write(txData.From[:]) 245 | buf.Write(txData.To[:]) 246 | buf.Write(Uint32ToBytes(txData.Token)) 247 | buf.Write(Uint32ToBytes(txData.FeeToken)) 248 | fee, ok := big.NewInt(0).SetString(txData.Fee, 10) 249 | if !ok { 250 | return nil, errors.New("failed to convert string fee to big.Int") 251 | } 252 | packedFee, err := packFee(fee) 253 | if err != nil { 254 | return nil, errors.Wrap(err, "failed to pack fee") 255 | } 256 | buf.Write(packedFee) 257 | buf.Write(Uint32ToBytes(txData.Nonce)) 258 | buf.Write(Uint64ToBytes(txData.TimeRange.ValidFrom)) 259 | buf.Write(Uint64ToBytes(txData.TimeRange.ValidUntil)) 260 | sig, err := s.Sign(buf.Bytes()) 261 | if err != nil { 262 | return nil, errors.Wrap(err, "failed to sign WithdrawNFT tx data") 263 | } 264 | res := &Signature{ 265 | PubKey: s.GetPublicKey(), 266 | Signature: sig.HexString(), 267 | } 268 | return res, nil 269 | } 270 | 271 | func (s *ZkSigner) SignOrder(order *Order) (*Signature, error) { 272 | message, err := s.getOrderBytes(order) 273 | if err != nil { 274 | return nil, errors.Wrap(err, "failed to get order bytes") 275 | } 276 | sig, err := s.Sign(message) 277 | if err != nil { 278 | return nil, errors.Wrap(err, "failed to sign Order data") 279 | } 280 | res := &Signature{ 281 | PubKey: s.GetPublicKey(), 282 | Signature: sig.HexString(), 283 | } 284 | return res, nil 285 | } 286 | func (s *ZkSigner) getOrderBytes(order *Order) ([]byte, error) { 287 | buf := bytes.Buffer{} 288 | buf.WriteByte(0x6f) // ASCII 'o' in hex for (o)rder 289 | buf.WriteByte(TransactionVersion) 290 | buf.Write(Uint32ToBytes(order.AccountId)) 291 | buf.Write(order.RecipientAddress[:]) 292 | buf.Write(Uint32ToBytes(order.Nonce)) 293 | buf.Write(Uint32ToBytes(order.TokenSell)) 294 | buf.Write(Uint32ToBytes(order.TokenBuy)) 295 | if len(order.Ratio) != 2 { 296 | return nil, errors.New("invalid ratio") 297 | } 298 | buf.Write(BigIntToBytesBE(order.Ratio[0], 15)) 299 | buf.Write(BigIntToBytesBE(order.Ratio[1], 15)) 300 | packedAmount, err := packAmount(order.Amount) 301 | if err != nil { 302 | return nil, errors.Wrap(err, "failed to pack amount") 303 | } 304 | buf.Write(packedAmount) 305 | buf.Write(Uint64ToBytes(order.TimeRange.ValidFrom)) 306 | buf.Write(Uint64ToBytes(order.TimeRange.ValidUntil)) 307 | return buf.Bytes(), nil 308 | } 309 | 310 | func (s *ZkSigner) SignSwap(txData *Swap) (*Signature, error) { 311 | buf := bytes.Buffer{} 312 | buf.WriteByte(0xff - 0x0b) 313 | buf.WriteByte(TransactionVersion) 314 | buf.Write(Uint32ToBytes(txData.SubmitterId)) 315 | buf.Write(txData.SubmitterAddress[:]) 316 | buf.Write(Uint32ToBytes(txData.Nonce)) 317 | if len(txData.Orders) != 2 { 318 | return nil, errors.New("invalid orders in Swap tx") 319 | } 320 | order1, err := s.getOrderBytes(txData.Orders[0]) 321 | if err != nil { 322 | return nil, errors.Wrap(err, "failed to get order1 bytes") 323 | } 324 | order2, err := s.getOrderBytes(txData.Orders[1]) 325 | if err != nil { 326 | return nil, errors.Wrap(err, "failed to get order2 bytes") 327 | } 328 | buf.Write(zkscrypto.ResqueHashOrders(append(order1, order2...)).GetBytes()) 329 | buf.Write(Uint32ToBytes(txData.FeeToken)) 330 | fee, ok := big.NewInt(0).SetString(txData.Fee, 10) 331 | if !ok { 332 | return nil, errors.New("failed to convert string fee to big.Int") 333 | } 334 | packedFee, err := packFee(fee) 335 | if err != nil { 336 | return nil, errors.Wrap(err, "failed to pack fee") 337 | } 338 | buf.Write(packedFee) 339 | if len(txData.Amounts) != 2 { 340 | return nil, errors.New("invalid amounts in Swap tx") 341 | } 342 | packedAmount, err := packAmount(txData.Amounts[0]) 343 | if err != nil { 344 | return nil, errors.Wrap(err, "failed to pack amount1") 345 | } 346 | buf.Write(packedAmount) 347 | packedAmount, err = packAmount(txData.Amounts[1]) 348 | if err != nil { 349 | return nil, errors.Wrap(err, "failed to pack amount2") 350 | } 351 | buf.Write(packedAmount) 352 | 353 | sig, err := s.Sign(buf.Bytes()) 354 | if err != nil { 355 | return nil, errors.Wrap(err, "failed to sign Swap tx data") 356 | } 357 | res := &Signature{ 358 | PubKey: s.GetPublicKey(), 359 | Signature: sig.HexString(), 360 | } 361 | return res, nil 362 | } 363 | 364 | func (s *ZkSigner) GetPublicKeyHash() string { 365 | return "sync:" + s.publicKeyHash.HexString() 366 | } 367 | 368 | func (s *ZkSigner) GetPublicKey() string { 369 | return s.publicKey.HexString() 370 | } 371 | -------------------------------------------------------------------------------- /ZkSigner_test.go: -------------------------------------------------------------------------------- 1 | package zksync 2 | 3 | import ( 4 | "encoding/hex" 5 | "github.com/stretchr/testify/assert" 6 | "github.com/stretchr/testify/require" 7 | zkscrypto "github.com/zksync-sdk/zksync-sdk-go" 8 | "testing" 9 | ) 10 | 11 | var ( 12 | err error 13 | zkSigner *ZkSigner 14 | 15 | seedHex = "c639503770c4b61f69185a5491b37caafa95f307ecceff5e1c335108308118b7e3a08276dc65a8794b1b2baa160e1cc8f16dce35706d437c13de26fb3b88303e" 16 | seed2Hex = "e60ff3eecbdff618a387ac8d6e5dd335b95424f5a7abbf141a0f749c8e806d97fdbafb3a97bcbff8e6fa23cad70dfd6e31cfec442fb5c06cc766f39370089a98" 17 | pkHex = "0422e258833908d5e3fdd12832545e0b121d9c4839cf8e719c8d1c318ddcb5a9" 18 | expPubKey = "fd1a3106571e6707280c7358a9bc8961630a809fb70611467a12df2dc823769a" 19 | toPubKey = "92401b49f36b6baf1a4d517681afdcfe671e7e0fbdbd709553e0d06a9979e01b" 20 | expPubKeyHash = "sync:d4ed534abc68bced79f5fc507dfcda2ca814f3dd" 21 | 22 | expMsgZkSignature = "bd808d99e92365d2ccbd35ffb04034638979015328febbfa4ccfec9482a5de16897599bab7844d4e6706dbfc47efd28237d391d04d49650dd54833c62ece8704" 23 | ) 24 | 25 | func TestNewZkSignerFromSeed(t *testing.T) { 26 | t.Run("success", func(t *testing.T) { 27 | seed, _ := hex.DecodeString(seedHex) 28 | zkSigner, err = NewZkSignerFromSeed(seed) 29 | require.NoError(t, err) 30 | assert.NotNil(t, zkSigner) 31 | assert.IsType(t, &ZkSigner{}, zkSigner) 32 | pubKey := zkSigner.GetPublicKey() 33 | assert.EqualValues(t, expPubKey, pubKey) 34 | pubKeyHash := zkSigner.GetPublicKeyHash() 35 | assert.EqualValues(t, expPubKeyHash, pubKeyHash) 36 | }) 37 | 38 | t.Run("invalid seed", func(t *testing.T) { 39 | zkSigner, err := NewZkSignerFromSeed([]byte("invalid seed bytes")) 40 | require.Error(t, err) 41 | assert.Nil(t, zkSigner) 42 | }) 43 | 44 | } 45 | 46 | func TestNewZkSignerFromRawPrivateKey(t *testing.T) { 47 | t.Run("success", func(t *testing.T) { 48 | pk, _ := hex.DecodeString(pkHex) 49 | zkSigner, err := NewZkSignerFromRawPrivateKey(pk) 50 | require.NoError(t, err) 51 | assert.NotNil(t, zkSigner) 52 | assert.IsType(t, &ZkSigner{}, zkSigner) 53 | pubKey := zkSigner.GetPublicKey() 54 | assert.EqualValues(t, expPubKey, pubKey) 55 | pubKeyHash := zkSigner.GetPublicKeyHash() 56 | assert.EqualValues(t, expPubKeyHash, pubKeyHash) 57 | }) 58 | 59 | t.Run("invalid pk", func(t *testing.T) { 60 | zkSigner, err := NewZkSignerFromRawPrivateKey([]byte("invalid pk bytes")) 61 | require.Error(t, err) 62 | assert.Nil(t, zkSigner) 63 | }) 64 | 65 | } 66 | 67 | func TestNewZkSignerFromEthSigner(t *testing.T) { 68 | expPubKey := "5c0ab5cac38c912773430be185789edc8b48dc2a1f8f13cbba8dbac1ca589e89" 69 | expPubKeyHash := "sync:e30e8868fca3e432358420cd22d4a6242ce9377f" 70 | 71 | t.Run("success", func(t *testing.T) { 72 | ethSigner, err := NewEthSignerFromMnemonic(mnemonic) 73 | zkSigner, err := NewZkSignerFromEthSigner(ethSigner, ChainIdMainnet) 74 | require.NoError(t, err) 75 | assert.NotNil(t, zkSigner) 76 | assert.IsType(t, &ZkSigner{}, zkSigner) 77 | pubKey := zkSigner.GetPublicKey() 78 | assert.EqualValues(t, expPubKey, pubKey) 79 | pubKeyHash := zkSigner.GetPublicKeyHash() 80 | assert.EqualValues(t, expPubKeyHash, pubKeyHash) 81 | }) 82 | } 83 | 84 | func TestSign(t *testing.T) { 85 | signature, err := zkSigner.Sign(signMsg) 86 | require.NoError(t, err) 87 | assert.NotNil(t, signature) 88 | assert.IsType(t, &zkscrypto.Signature{}, signature) 89 | assert.EqualValues(t, expMsgZkSignature, signature.HexString()) 90 | } 91 | -------------------------------------------------------------------------------- /contracts/ERC20/ERC20.go: -------------------------------------------------------------------------------- 1 | // Code generated - DO NOT EDIT. 2 | // This file is a generated binding and any manual changes will be lost. 3 | 4 | package ERC20 5 | 6 | import ( 7 | "math/big" 8 | "strings" 9 | 10 | ethereum "github.com/ethereum/go-ethereum" 11 | "github.com/ethereum/go-ethereum/accounts/abi" 12 | "github.com/ethereum/go-ethereum/accounts/abi/bind" 13 | "github.com/ethereum/go-ethereum/common" 14 | "github.com/ethereum/go-ethereum/core/types" 15 | "github.com/ethereum/go-ethereum/event" 16 | ) 17 | 18 | // Reference imports to suppress errors if they are not otherwise used. 19 | var ( 20 | _ = big.NewInt 21 | _ = strings.NewReader 22 | _ = ethereum.NotFound 23 | _ = bind.Bind 24 | _ = common.Big1 25 | _ = types.BloomLookup 26 | _ = event.NewSubscription 27 | ) 28 | 29 | // ERC20ABI is the input ABI used to generate the binding from. 30 | const ERC20ABI = "[{\"anonymous\":false,\"inputs\":[{\"indexed\":true,\"internalType\":\"address\",\"name\":\"owner\",\"type\":\"address\"},{\"indexed\":true,\"internalType\":\"address\",\"name\":\"spender\",\"type\":\"address\"},{\"indexed\":false,\"internalType\":\"uint256\",\"name\":\"value\",\"type\":\"uint256\"}],\"name\":\"Approval\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":true,\"internalType\":\"address\",\"name\":\"from\",\"type\":\"address\"},{\"indexed\":true,\"internalType\":\"address\",\"name\":\"to\",\"type\":\"address\"},{\"indexed\":false,\"internalType\":\"uint256\",\"name\":\"value\",\"type\":\"uint256\"}],\"name\":\"Transfer\",\"type\":\"event\"},{\"constant\":true,\"inputs\":[{\"internalType\":\"address\",\"name\":\"owner\",\"type\":\"address\"},{\"internalType\":\"address\",\"name\":\"spender\",\"type\":\"address\"}],\"name\":\"allowance\",\"outputs\":[{\"internalType\":\"uint256\",\"name\":\"\",\"type\":\"uint256\"}],\"payable\":false,\"stateMutability\":\"view\",\"type\":\"function\"},{\"constant\":false,\"inputs\":[{\"internalType\":\"address\",\"name\":\"spender\",\"type\":\"address\"},{\"internalType\":\"uint256\",\"name\":\"amount\",\"type\":\"uint256\"}],\"name\":\"approve\",\"outputs\":[{\"internalType\":\"bool\",\"name\":\"\",\"type\":\"bool\"}],\"payable\":false,\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"constant\":true,\"inputs\":[{\"internalType\":\"address\",\"name\":\"account\",\"type\":\"address\"}],\"name\":\"balanceOf\",\"outputs\":[{\"internalType\":\"uint256\",\"name\":\"\",\"type\":\"uint256\"}],\"payable\":false,\"stateMutability\":\"view\",\"type\":\"function\"},{\"constant\":true,\"inputs\":[],\"name\":\"totalSupply\",\"outputs\":[{\"internalType\":\"uint256\",\"name\":\"\",\"type\":\"uint256\"}],\"payable\":false,\"stateMutability\":\"view\",\"type\":\"function\"},{\"constant\":false,\"inputs\":[{\"internalType\":\"address\",\"name\":\"recipient\",\"type\":\"address\"},{\"internalType\":\"uint256\",\"name\":\"amount\",\"type\":\"uint256\"}],\"name\":\"transfer\",\"outputs\":[{\"internalType\":\"bool\",\"name\":\"\",\"type\":\"bool\"}],\"payable\":false,\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"constant\":false,\"inputs\":[{\"internalType\":\"address\",\"name\":\"sender\",\"type\":\"address\"},{\"internalType\":\"address\",\"name\":\"recipient\",\"type\":\"address\"},{\"internalType\":\"uint256\",\"name\":\"amount\",\"type\":\"uint256\"}],\"name\":\"transferFrom\",\"outputs\":[{\"internalType\":\"bool\",\"name\":\"\",\"type\":\"bool\"}],\"payable\":false,\"stateMutability\":\"nonpayable\",\"type\":\"function\"}]" 31 | 32 | // ERC20 is an auto generated Go binding around an Ethereum contract. 33 | type ERC20 struct { 34 | ERC20Caller // Read-only binding to the contract 35 | ERC20Transactor // Write-only binding to the contract 36 | ERC20Filterer // Log filterer for contract events 37 | } 38 | 39 | // ERC20Caller is an auto generated read-only Go binding around an Ethereum contract. 40 | type ERC20Caller struct { 41 | contract *bind.BoundContract // Generic contract wrapper for the low level calls 42 | } 43 | 44 | // ERC20Transactor is an auto generated write-only Go binding around an Ethereum contract. 45 | type ERC20Transactor struct { 46 | contract *bind.BoundContract // Generic contract wrapper for the low level calls 47 | } 48 | 49 | // ERC20Filterer is an auto generated log filtering Go binding around an Ethereum contract events. 50 | type ERC20Filterer struct { 51 | contract *bind.BoundContract // Generic contract wrapper for the low level calls 52 | } 53 | 54 | // ERC20Session is an auto generated Go binding around an Ethereum contract, 55 | // with pre-set call and transact options. 56 | type ERC20Session struct { 57 | Contract *ERC20 // Generic contract binding to set the session for 58 | CallOpts bind.CallOpts // Call options to use throughout this session 59 | TransactOpts bind.TransactOpts // Transaction auth options to use throughout this session 60 | } 61 | 62 | // ERC20CallerSession is an auto generated read-only Go binding around an Ethereum contract, 63 | // with pre-set call options. 64 | type ERC20CallerSession struct { 65 | Contract *ERC20Caller // Generic contract caller binding to set the session for 66 | CallOpts bind.CallOpts // Call options to use throughout this session 67 | } 68 | 69 | // ERC20TransactorSession is an auto generated write-only Go binding around an Ethereum contract, 70 | // with pre-set transact options. 71 | type ERC20TransactorSession struct { 72 | Contract *ERC20Transactor // Generic contract transactor binding to set the session for 73 | TransactOpts bind.TransactOpts // Transaction auth options to use throughout this session 74 | } 75 | 76 | // ERC20Raw is an auto generated low-level Go binding around an Ethereum contract. 77 | type ERC20Raw struct { 78 | Contract *ERC20 // Generic contract binding to access the raw methods on 79 | } 80 | 81 | // ERC20CallerRaw is an auto generated low-level read-only Go binding around an Ethereum contract. 82 | type ERC20CallerRaw struct { 83 | Contract *ERC20Caller // Generic read-only contract binding to access the raw methods on 84 | } 85 | 86 | // ERC20TransactorRaw is an auto generated low-level write-only Go binding around an Ethereum contract. 87 | type ERC20TransactorRaw struct { 88 | Contract *ERC20Transactor // Generic write-only contract binding to access the raw methods on 89 | } 90 | 91 | // NewERC20 creates a new instance of ERC20, bound to a specific deployed contract. 92 | func NewERC20(address common.Address, backend bind.ContractBackend) (*ERC20, error) { 93 | contract, err := bindERC20(address, backend, backend, backend) 94 | if err != nil { 95 | return nil, err 96 | } 97 | return &ERC20{ERC20Caller: ERC20Caller{contract: contract}, ERC20Transactor: ERC20Transactor{contract: contract}, ERC20Filterer: ERC20Filterer{contract: contract}}, nil 98 | } 99 | 100 | // NewERC20Caller creates a new read-only instance of ERC20, bound to a specific deployed contract. 101 | func NewERC20Caller(address common.Address, caller bind.ContractCaller) (*ERC20Caller, error) { 102 | contract, err := bindERC20(address, caller, nil, nil) 103 | if err != nil { 104 | return nil, err 105 | } 106 | return &ERC20Caller{contract: contract}, nil 107 | } 108 | 109 | // NewERC20Transactor creates a new write-only instance of ERC20, bound to a specific deployed contract. 110 | func NewERC20Transactor(address common.Address, transactor bind.ContractTransactor) (*ERC20Transactor, error) { 111 | contract, err := bindERC20(address, nil, transactor, nil) 112 | if err != nil { 113 | return nil, err 114 | } 115 | return &ERC20Transactor{contract: contract}, nil 116 | } 117 | 118 | // NewERC20Filterer creates a new log filterer instance of ERC20, bound to a specific deployed contract. 119 | func NewERC20Filterer(address common.Address, filterer bind.ContractFilterer) (*ERC20Filterer, error) { 120 | contract, err := bindERC20(address, nil, nil, filterer) 121 | if err != nil { 122 | return nil, err 123 | } 124 | return &ERC20Filterer{contract: contract}, nil 125 | } 126 | 127 | // bindERC20 binds a generic wrapper to an already deployed contract. 128 | func bindERC20(address common.Address, caller bind.ContractCaller, transactor bind.ContractTransactor, filterer bind.ContractFilterer) (*bind.BoundContract, error) { 129 | parsed, err := abi.JSON(strings.NewReader(ERC20ABI)) 130 | if err != nil { 131 | return nil, err 132 | } 133 | return bind.NewBoundContract(address, parsed, caller, transactor, filterer), nil 134 | } 135 | 136 | // Call invokes the (constant) contract method with params as input values and 137 | // sets the output to result. The result type might be a single field for simple 138 | // returns, a slice of interfaces for anonymous returns and a struct for named 139 | // returns. 140 | func (_ERC20 *ERC20Raw) Call(opts *bind.CallOpts, result *[]interface{}, method string, params ...interface{}) error { 141 | return _ERC20.Contract.ERC20Caller.contract.Call(opts, result, method, params...) 142 | } 143 | 144 | // Transfer initiates a plain transaction to move funds to the contract, calling 145 | // its default method if one is available. 146 | func (_ERC20 *ERC20Raw) Transfer(opts *bind.TransactOpts) (*types.Transaction, error) { 147 | return _ERC20.Contract.ERC20Transactor.contract.Transfer(opts) 148 | } 149 | 150 | // Transact invokes the (paid) contract method with params as input values. 151 | func (_ERC20 *ERC20Raw) Transact(opts *bind.TransactOpts, method string, params ...interface{}) (*types.Transaction, error) { 152 | return _ERC20.Contract.ERC20Transactor.contract.Transact(opts, method, params...) 153 | } 154 | 155 | // Call invokes the (constant) contract method with params as input values and 156 | // sets the output to result. The result type might be a single field for simple 157 | // returns, a slice of interfaces for anonymous returns and a struct for named 158 | // returns. 159 | func (_ERC20 *ERC20CallerRaw) Call(opts *bind.CallOpts, result *[]interface{}, method string, params ...interface{}) error { 160 | return _ERC20.Contract.contract.Call(opts, result, method, params...) 161 | } 162 | 163 | // Transfer initiates a plain transaction to move funds to the contract, calling 164 | // its default method if one is available. 165 | func (_ERC20 *ERC20TransactorRaw) Transfer(opts *bind.TransactOpts) (*types.Transaction, error) { 166 | return _ERC20.Contract.contract.Transfer(opts) 167 | } 168 | 169 | // Transact invokes the (paid) contract method with params as input values. 170 | func (_ERC20 *ERC20TransactorRaw) Transact(opts *bind.TransactOpts, method string, params ...interface{}) (*types.Transaction, error) { 171 | return _ERC20.Contract.contract.Transact(opts, method, params...) 172 | } 173 | 174 | // Allowance is a free data retrieval call binding the contract method 0xdd62ed3e. 175 | // 176 | // Solidity: function allowance(address owner, address spender) view returns(uint256) 177 | func (_ERC20 *ERC20Caller) Allowance(opts *bind.CallOpts, owner common.Address, spender common.Address) (*big.Int, error) { 178 | var out []interface{} 179 | err := _ERC20.contract.Call(opts, &out, "allowance", owner, spender) 180 | 181 | if err != nil { 182 | return *new(*big.Int), err 183 | } 184 | 185 | out0 := *abi.ConvertType(out[0], new(*big.Int)).(**big.Int) 186 | 187 | return out0, err 188 | 189 | } 190 | 191 | // Allowance is a free data retrieval call binding the contract method 0xdd62ed3e. 192 | // 193 | // Solidity: function allowance(address owner, address spender) view returns(uint256) 194 | func (_ERC20 *ERC20Session) Allowance(owner common.Address, spender common.Address) (*big.Int, error) { 195 | return _ERC20.Contract.Allowance(&_ERC20.CallOpts, owner, spender) 196 | } 197 | 198 | // Allowance is a free data retrieval call binding the contract method 0xdd62ed3e. 199 | // 200 | // Solidity: function allowance(address owner, address spender) view returns(uint256) 201 | func (_ERC20 *ERC20CallerSession) Allowance(owner common.Address, spender common.Address) (*big.Int, error) { 202 | return _ERC20.Contract.Allowance(&_ERC20.CallOpts, owner, spender) 203 | } 204 | 205 | // BalanceOf is a free data retrieval call binding the contract method 0x70a08231. 206 | // 207 | // Solidity: function balanceOf(address account) view returns(uint256) 208 | func (_ERC20 *ERC20Caller) BalanceOf(opts *bind.CallOpts, account common.Address) (*big.Int, error) { 209 | var out []interface{} 210 | err := _ERC20.contract.Call(opts, &out, "balanceOf", account) 211 | 212 | if err != nil { 213 | return *new(*big.Int), err 214 | } 215 | 216 | out0 := *abi.ConvertType(out[0], new(*big.Int)).(**big.Int) 217 | 218 | return out0, err 219 | 220 | } 221 | 222 | // BalanceOf is a free data retrieval call binding the contract method 0x70a08231. 223 | // 224 | // Solidity: function balanceOf(address account) view returns(uint256) 225 | func (_ERC20 *ERC20Session) BalanceOf(account common.Address) (*big.Int, error) { 226 | return _ERC20.Contract.BalanceOf(&_ERC20.CallOpts, account) 227 | } 228 | 229 | // BalanceOf is a free data retrieval call binding the contract method 0x70a08231. 230 | // 231 | // Solidity: function balanceOf(address account) view returns(uint256) 232 | func (_ERC20 *ERC20CallerSession) BalanceOf(account common.Address) (*big.Int, error) { 233 | return _ERC20.Contract.BalanceOf(&_ERC20.CallOpts, account) 234 | } 235 | 236 | // TotalSupply is a free data retrieval call binding the contract method 0x18160ddd. 237 | // 238 | // Solidity: function totalSupply() view returns(uint256) 239 | func (_ERC20 *ERC20Caller) TotalSupply(opts *bind.CallOpts) (*big.Int, error) { 240 | var out []interface{} 241 | err := _ERC20.contract.Call(opts, &out, "totalSupply") 242 | 243 | if err != nil { 244 | return *new(*big.Int), err 245 | } 246 | 247 | out0 := *abi.ConvertType(out[0], new(*big.Int)).(**big.Int) 248 | 249 | return out0, err 250 | 251 | } 252 | 253 | // TotalSupply is a free data retrieval call binding the contract method 0x18160ddd. 254 | // 255 | // Solidity: function totalSupply() view returns(uint256) 256 | func (_ERC20 *ERC20Session) TotalSupply() (*big.Int, error) { 257 | return _ERC20.Contract.TotalSupply(&_ERC20.CallOpts) 258 | } 259 | 260 | // TotalSupply is a free data retrieval call binding the contract method 0x18160ddd. 261 | // 262 | // Solidity: function totalSupply() view returns(uint256) 263 | func (_ERC20 *ERC20CallerSession) TotalSupply() (*big.Int, error) { 264 | return _ERC20.Contract.TotalSupply(&_ERC20.CallOpts) 265 | } 266 | 267 | // Approve is a paid mutator transaction binding the contract method 0x095ea7b3. 268 | // 269 | // Solidity: function approve(address spender, uint256 amount) returns(bool) 270 | func (_ERC20 *ERC20Transactor) Approve(opts *bind.TransactOpts, spender common.Address, amount *big.Int) (*types.Transaction, error) { 271 | return _ERC20.contract.Transact(opts, "approve", spender, amount) 272 | } 273 | 274 | // Approve is a paid mutator transaction binding the contract method 0x095ea7b3. 275 | // 276 | // Solidity: function approve(address spender, uint256 amount) returns(bool) 277 | func (_ERC20 *ERC20Session) Approve(spender common.Address, amount *big.Int) (*types.Transaction, error) { 278 | return _ERC20.Contract.Approve(&_ERC20.TransactOpts, spender, amount) 279 | } 280 | 281 | // Approve is a paid mutator transaction binding the contract method 0x095ea7b3. 282 | // 283 | // Solidity: function approve(address spender, uint256 amount) returns(bool) 284 | func (_ERC20 *ERC20TransactorSession) Approve(spender common.Address, amount *big.Int) (*types.Transaction, error) { 285 | return _ERC20.Contract.Approve(&_ERC20.TransactOpts, spender, amount) 286 | } 287 | 288 | // Transfer is a paid mutator transaction binding the contract method 0xa9059cbb. 289 | // 290 | // Solidity: function transfer(address recipient, uint256 amount) returns(bool) 291 | func (_ERC20 *ERC20Transactor) Transfer(opts *bind.TransactOpts, recipient common.Address, amount *big.Int) (*types.Transaction, error) { 292 | return _ERC20.contract.Transact(opts, "transfer", recipient, amount) 293 | } 294 | 295 | // Transfer is a paid mutator transaction binding the contract method 0xa9059cbb. 296 | // 297 | // Solidity: function transfer(address recipient, uint256 amount) returns(bool) 298 | func (_ERC20 *ERC20Session) Transfer(recipient common.Address, amount *big.Int) (*types.Transaction, error) { 299 | return _ERC20.Contract.Transfer(&_ERC20.TransactOpts, recipient, amount) 300 | } 301 | 302 | // Transfer is a paid mutator transaction binding the contract method 0xa9059cbb. 303 | // 304 | // Solidity: function transfer(address recipient, uint256 amount) returns(bool) 305 | func (_ERC20 *ERC20TransactorSession) Transfer(recipient common.Address, amount *big.Int) (*types.Transaction, error) { 306 | return _ERC20.Contract.Transfer(&_ERC20.TransactOpts, recipient, amount) 307 | } 308 | 309 | // TransferFrom is a paid mutator transaction binding the contract method 0x23b872dd. 310 | // 311 | // Solidity: function transferFrom(address sender, address recipient, uint256 amount) returns(bool) 312 | func (_ERC20 *ERC20Transactor) TransferFrom(opts *bind.TransactOpts, sender common.Address, recipient common.Address, amount *big.Int) (*types.Transaction, error) { 313 | return _ERC20.contract.Transact(opts, "transferFrom", sender, recipient, amount) 314 | } 315 | 316 | // TransferFrom is a paid mutator transaction binding the contract method 0x23b872dd. 317 | // 318 | // Solidity: function transferFrom(address sender, address recipient, uint256 amount) returns(bool) 319 | func (_ERC20 *ERC20Session) TransferFrom(sender common.Address, recipient common.Address, amount *big.Int) (*types.Transaction, error) { 320 | return _ERC20.Contract.TransferFrom(&_ERC20.TransactOpts, sender, recipient, amount) 321 | } 322 | 323 | // TransferFrom is a paid mutator transaction binding the contract method 0x23b872dd. 324 | // 325 | // Solidity: function transferFrom(address sender, address recipient, uint256 amount) returns(bool) 326 | func (_ERC20 *ERC20TransactorSession) TransferFrom(sender common.Address, recipient common.Address, amount *big.Int) (*types.Transaction, error) { 327 | return _ERC20.Contract.TransferFrom(&_ERC20.TransactOpts, sender, recipient, amount) 328 | } 329 | 330 | // ERC20ApprovalIterator is returned from FilterApproval and is used to iterate over the raw logs and unpacked data for Approval events raised by the ERC20 contract. 331 | type ERC20ApprovalIterator struct { 332 | Event *ERC20Approval // Event containing the contract specifics and raw log 333 | 334 | contract *bind.BoundContract // Generic contract to use for unpacking event data 335 | event string // Event name to use for unpacking event data 336 | 337 | logs chan types.Log // Log channel receiving the found contract events 338 | sub ethereum.Subscription // Subscription for errors, completion and termination 339 | done bool // Whether the subscription completed delivering logs 340 | fail error // Occurred error to stop iteration 341 | } 342 | 343 | // Next advances the iterator to the subsequent event, returning whether there 344 | // are any more events found. In case of a retrieval or parsing error, false is 345 | // returned and Error() can be queried for the exact failure. 346 | func (it *ERC20ApprovalIterator) Next() bool { 347 | // If the iterator failed, stop iterating 348 | if it.fail != nil { 349 | return false 350 | } 351 | // If the iterator completed, deliver directly whatever's available 352 | if it.done { 353 | select { 354 | case log := <-it.logs: 355 | it.Event = new(ERC20Approval) 356 | if err := it.contract.UnpackLog(it.Event, it.event, log); err != nil { 357 | it.fail = err 358 | return false 359 | } 360 | it.Event.Raw = log 361 | return true 362 | 363 | default: 364 | return false 365 | } 366 | } 367 | // Iterator still in progress, wait for either a data or an error event 368 | select { 369 | case log := <-it.logs: 370 | it.Event = new(ERC20Approval) 371 | if err := it.contract.UnpackLog(it.Event, it.event, log); err != nil { 372 | it.fail = err 373 | return false 374 | } 375 | it.Event.Raw = log 376 | return true 377 | 378 | case err := <-it.sub.Err(): 379 | it.done = true 380 | it.fail = err 381 | return it.Next() 382 | } 383 | } 384 | 385 | // Error returns any retrieval or parsing error occurred during filtering. 386 | func (it *ERC20ApprovalIterator) Error() error { 387 | return it.fail 388 | } 389 | 390 | // Close terminates the iteration process, releasing any pending underlying 391 | // resources. 392 | func (it *ERC20ApprovalIterator) Close() error { 393 | it.sub.Unsubscribe() 394 | return nil 395 | } 396 | 397 | // ERC20Approval represents a Approval event raised by the ERC20 contract. 398 | type ERC20Approval struct { 399 | Owner common.Address 400 | Spender common.Address 401 | Value *big.Int 402 | Raw types.Log // Blockchain specific contextual infos 403 | } 404 | 405 | // FilterApproval is a free log retrieval operation binding the contract event 0x8c5be1e5ebec7d5bd14f71427d1e84f3dd0314c0f7b2291e5b200ac8c7c3b925. 406 | // 407 | // Solidity: event Approval(address indexed owner, address indexed spender, uint256 value) 408 | func (_ERC20 *ERC20Filterer) FilterApproval(opts *bind.FilterOpts, owner []common.Address, spender []common.Address) (*ERC20ApprovalIterator, error) { 409 | 410 | var ownerRule []interface{} 411 | for _, ownerItem := range owner { 412 | ownerRule = append(ownerRule, ownerItem) 413 | } 414 | var spenderRule []interface{} 415 | for _, spenderItem := range spender { 416 | spenderRule = append(spenderRule, spenderItem) 417 | } 418 | 419 | logs, sub, err := _ERC20.contract.FilterLogs(opts, "Approval", ownerRule, spenderRule) 420 | if err != nil { 421 | return nil, err 422 | } 423 | return &ERC20ApprovalIterator{contract: _ERC20.contract, event: "Approval", logs: logs, sub: sub}, nil 424 | } 425 | 426 | // WatchApproval is a free log subscription operation binding the contract event 0x8c5be1e5ebec7d5bd14f71427d1e84f3dd0314c0f7b2291e5b200ac8c7c3b925. 427 | // 428 | // Solidity: event Approval(address indexed owner, address indexed spender, uint256 value) 429 | func (_ERC20 *ERC20Filterer) WatchApproval(opts *bind.WatchOpts, sink chan<- *ERC20Approval, owner []common.Address, spender []common.Address) (event.Subscription, error) { 430 | 431 | var ownerRule []interface{} 432 | for _, ownerItem := range owner { 433 | ownerRule = append(ownerRule, ownerItem) 434 | } 435 | var spenderRule []interface{} 436 | for _, spenderItem := range spender { 437 | spenderRule = append(spenderRule, spenderItem) 438 | } 439 | 440 | logs, sub, err := _ERC20.contract.WatchLogs(opts, "Approval", ownerRule, spenderRule) 441 | if err != nil { 442 | return nil, err 443 | } 444 | return event.NewSubscription(func(quit <-chan struct{}) error { 445 | defer sub.Unsubscribe() 446 | for { 447 | select { 448 | case log := <-logs: 449 | // New log arrived, parse the event and forward to the user 450 | event := new(ERC20Approval) 451 | if err := _ERC20.contract.UnpackLog(event, "Approval", log); err != nil { 452 | return err 453 | } 454 | event.Raw = log 455 | 456 | select { 457 | case sink <- event: 458 | case err := <-sub.Err(): 459 | return err 460 | case <-quit: 461 | return nil 462 | } 463 | case err := <-sub.Err(): 464 | return err 465 | case <-quit: 466 | return nil 467 | } 468 | } 469 | }), nil 470 | } 471 | 472 | // ParseApproval is a log parse operation binding the contract event 0x8c5be1e5ebec7d5bd14f71427d1e84f3dd0314c0f7b2291e5b200ac8c7c3b925. 473 | // 474 | // Solidity: event Approval(address indexed owner, address indexed spender, uint256 value) 475 | func (_ERC20 *ERC20Filterer) ParseApproval(log types.Log) (*ERC20Approval, error) { 476 | event := new(ERC20Approval) 477 | if err := _ERC20.contract.UnpackLog(event, "Approval", log); err != nil { 478 | return nil, err 479 | } 480 | return event, nil 481 | } 482 | 483 | // ERC20TransferIterator is returned from FilterTransfer and is used to iterate over the raw logs and unpacked data for Transfer events raised by the ERC20 contract. 484 | type ERC20TransferIterator struct { 485 | Event *ERC20Transfer // Event containing the contract specifics and raw log 486 | 487 | contract *bind.BoundContract // Generic contract to use for unpacking event data 488 | event string // Event name to use for unpacking event data 489 | 490 | logs chan types.Log // Log channel receiving the found contract events 491 | sub ethereum.Subscription // Subscription for errors, completion and termination 492 | done bool // Whether the subscription completed delivering logs 493 | fail error // Occurred error to stop iteration 494 | } 495 | 496 | // Next advances the iterator to the subsequent event, returning whether there 497 | // are any more events found. In case of a retrieval or parsing error, false is 498 | // returned and Error() can be queried for the exact failure. 499 | func (it *ERC20TransferIterator) Next() bool { 500 | // If the iterator failed, stop iterating 501 | if it.fail != nil { 502 | return false 503 | } 504 | // If the iterator completed, deliver directly whatever's available 505 | if it.done { 506 | select { 507 | case log := <-it.logs: 508 | it.Event = new(ERC20Transfer) 509 | if err := it.contract.UnpackLog(it.Event, it.event, log); err != nil { 510 | it.fail = err 511 | return false 512 | } 513 | it.Event.Raw = log 514 | return true 515 | 516 | default: 517 | return false 518 | } 519 | } 520 | // Iterator still in progress, wait for either a data or an error event 521 | select { 522 | case log := <-it.logs: 523 | it.Event = new(ERC20Transfer) 524 | if err := it.contract.UnpackLog(it.Event, it.event, log); err != nil { 525 | it.fail = err 526 | return false 527 | } 528 | it.Event.Raw = log 529 | return true 530 | 531 | case err := <-it.sub.Err(): 532 | it.done = true 533 | it.fail = err 534 | return it.Next() 535 | } 536 | } 537 | 538 | // Error returns any retrieval or parsing error occurred during filtering. 539 | func (it *ERC20TransferIterator) Error() error { 540 | return it.fail 541 | } 542 | 543 | // Close terminates the iteration process, releasing any pending underlying 544 | // resources. 545 | func (it *ERC20TransferIterator) Close() error { 546 | it.sub.Unsubscribe() 547 | return nil 548 | } 549 | 550 | // ERC20Transfer represents a Transfer event raised by the ERC20 contract. 551 | type ERC20Transfer struct { 552 | From common.Address 553 | To common.Address 554 | Value *big.Int 555 | Raw types.Log // Blockchain specific contextual infos 556 | } 557 | 558 | // FilterTransfer is a free log retrieval operation binding the contract event 0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef. 559 | // 560 | // Solidity: event Transfer(address indexed from, address indexed to, uint256 value) 561 | func (_ERC20 *ERC20Filterer) FilterTransfer(opts *bind.FilterOpts, from []common.Address, to []common.Address) (*ERC20TransferIterator, error) { 562 | 563 | var fromRule []interface{} 564 | for _, fromItem := range from { 565 | fromRule = append(fromRule, fromItem) 566 | } 567 | var toRule []interface{} 568 | for _, toItem := range to { 569 | toRule = append(toRule, toItem) 570 | } 571 | 572 | logs, sub, err := _ERC20.contract.FilterLogs(opts, "Transfer", fromRule, toRule) 573 | if err != nil { 574 | return nil, err 575 | } 576 | return &ERC20TransferIterator{contract: _ERC20.contract, event: "Transfer", logs: logs, sub: sub}, nil 577 | } 578 | 579 | // WatchTransfer is a free log subscription operation binding the contract event 0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef. 580 | // 581 | // Solidity: event Transfer(address indexed from, address indexed to, uint256 value) 582 | func (_ERC20 *ERC20Filterer) WatchTransfer(opts *bind.WatchOpts, sink chan<- *ERC20Transfer, from []common.Address, to []common.Address) (event.Subscription, error) { 583 | 584 | var fromRule []interface{} 585 | for _, fromItem := range from { 586 | fromRule = append(fromRule, fromItem) 587 | } 588 | var toRule []interface{} 589 | for _, toItem := range to { 590 | toRule = append(toRule, toItem) 591 | } 592 | 593 | logs, sub, err := _ERC20.contract.WatchLogs(opts, "Transfer", fromRule, toRule) 594 | if err != nil { 595 | return nil, err 596 | } 597 | return event.NewSubscription(func(quit <-chan struct{}) error { 598 | defer sub.Unsubscribe() 599 | for { 600 | select { 601 | case log := <-logs: 602 | // New log arrived, parse the event and forward to the user 603 | event := new(ERC20Transfer) 604 | if err := _ERC20.contract.UnpackLog(event, "Transfer", log); err != nil { 605 | return err 606 | } 607 | event.Raw = log 608 | 609 | select { 610 | case sink <- event: 611 | case err := <-sub.Err(): 612 | return err 613 | case <-quit: 614 | return nil 615 | } 616 | case err := <-sub.Err(): 617 | return err 618 | case <-quit: 619 | return nil 620 | } 621 | } 622 | }), nil 623 | } 624 | 625 | // ParseTransfer is a log parse operation binding the contract event 0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef. 626 | // 627 | // Solidity: event Transfer(address indexed from, address indexed to, uint256 value) 628 | func (_ERC20 *ERC20Filterer) ParseTransfer(log types.Log) (*ERC20Transfer, error) { 629 | event := new(ERC20Transfer) 630 | if err := _ERC20.contract.UnpackLog(event, "Transfer", log); err != nil { 631 | return nil, err 632 | } 633 | return event, nil 634 | } 635 | -------------------------------------------------------------------------------- /contracts/README.md: -------------------------------------------------------------------------------- 1 | # Generated wrappers 2 | [ZkSync/ZkSynk.go](ZkSync/ZkSynk.go) package contain generated wrapper for zkSync smart contract. 3 | 4 | [ERC20/ERC20.go](ERC20/ERC20.go) package contain generated wrapper for default ERC20 smart contract. 5 | 6 | This code was generated by `abigen` tool, which included in [github.com/ethereum/go-ethereum](github.com/ethereum/go-ethereum) devtools. 7 | 8 | Sample of generating commands from this dir is: 9 | ``` 10 | abigen --abi=./ZkSync.json --pkg=ZkSync --out=./ZkSync/ZkSync.go 11 | abigen --abi=./ERC20.json --pkg=ERC20 --out=./ERC20/ERC20.go 12 | ``` 13 | where files `ZkSync.json` and `ERC20.json` must be an appropriate ABI's in JSON format. -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/zksync-sdk/zksync-go 2 | 3 | go 1.16 4 | 5 | require ( 6 | github.com/ethereum/go-ethereum v1.10.3 7 | github.com/miguelmota/go-ethereum-hdwallet v0.0.1 8 | github.com/pkg/errors v0.9.1 9 | github.com/stretchr/testify v1.7.0 10 | github.com/zksync-sdk/zksync-sdk-go v0.0.0-20210830110931-e2635cfaf5af 11 | ) 12 | --------------------------------------------------------------------------------