From 2fb84d6341cc1fed13673a061f32f9d83c61f50b Mon Sep 17 00:00:00 2001 From: lmxdawn Date: Tue, 18 Jan 2022 14:07:32 +0800 Subject: [PATCH] =?UTF-8?q?=E4=BC=98=E5=8C=96=E4=BB=A3=E7=A0=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- client/http.go | 57 ++++- config/config-example.yml | 48 ++--- config/config.go | 2 +- contracts/erc20.sol | 5 - contracts/erc20_sol_ERC20.abi | 1 - contracts/eth/erc20.go | 334 +++++++++++++++++++++++++++++ contracts/eth/erc20.sol | 79 +++++++ contracts/eth/erc20_sol_IERC20.abi | 1 + doc.go | 2 +- docs/docs.go | 22 +- docs/swagger.json | 22 +- docs/swagger.yaml | 18 +- engine/concurrent.go | 11 +- engine/eth_worker.go | 90 ++++---- server/err_code.go | 3 +- server/handler.go | 36 +++- server/middleware.go | 4 +- server/req.go | 4 + server/rpc.go | 12 +- 19 files changed, 651 insertions(+), 100 deletions(-) delete mode 100644 contracts/erc20.sol delete mode 100644 contracts/erc20_sol_ERC20.abi create mode 100644 contracts/eth/erc20.go create mode 100644 contracts/eth/erc20.sol create mode 100644 contracts/eth/erc20_sol_IERC20.abi diff --git a/client/http.go b/client/http.go index a1c5ffd..c4e9197 100644 --- a/client/http.go +++ b/client/http.go @@ -7,6 +7,7 @@ import ( "io/ioutil" "net/http" "net/url" + "time" ) // Response ... @@ -21,30 +22,36 @@ func (br Response) Success() bool { type HttpClient struct { protocol string + coinName string rechargeNotifyUrl string withdrawNotifyUrl string + timeout int // 超时时间,毫秒单位 } // NewHttpClient 创建 -func NewHttpClient(protocol, rechargeNotifyUrl, withdrawNotifyUrl string) *HttpClient { +func NewHttpClient(protocol, coinName, rechargeNotifyUrl, withdrawNotifyUrl string) *HttpClient { return &HttpClient{ protocol, + coinName, rechargeNotifyUrl, withdrawNotifyUrl, + 1000 * 10, } } // RechargeSuccess 充值成功通知 -func (h *HttpClient) RechargeSuccess(hash string, address string, value int64) error { +func (h *HttpClient) RechargeSuccess(hash string, status uint, address string, value int64) error { data := make(map[string]interface{}) data["protocol"] = h.protocol + data["coinName"] = h.coinName data["hash"] = hash + data["status"] = status data["address"] = address data["value"] = value var res Response - err := post(h.withdrawNotifyUrl, data, &res) + err := h.post(h.withdrawNotifyUrl, data, &res) if err != nil { return err } @@ -57,17 +64,20 @@ func (h *HttpClient) RechargeSuccess(hash string, address string, value int64) e } // WithdrawSuccess 提现成功通知 -func (h *HttpClient) WithdrawSuccess(hash string, orderId string, address string, value int64) error { +func (h *HttpClient) WithdrawSuccess(hash string, status uint, orderId string, address string, value int64) error { data := make(map[string]interface{}) data["protocol"] = h.protocol + data["coinName"] = h.coinName data["hash"] = hash + data["status"] = status data["orderId"] = orderId data["address"] = address data["value"] = value var res Response - err := post(h.withdrawNotifyUrl, data, &res) + err := h.post(h.withdrawNotifyUrl, data, &res) + if err != nil { return err } @@ -80,7 +90,7 @@ func (h *HttpClient) WithdrawSuccess(hash string, orderId string, address string } // get 请求 -func get(urlStr string, params url.Values, res interface{}) error { +func (h *HttpClient) get(urlStr string, params url.Values, res interface{}) error { Url, err := url.Parse(urlStr) if err != nil { @@ -89,12 +99,26 @@ func get(urlStr string, params url.Values, res interface{}) error { //如果参数中有中文参数,这个方法会进行URLEncode Url.RawQuery = params.Encode() urlPath := Url.String() - resp, err := http.Get(urlPath) + + client := &http.Client{ + Timeout: time.Millisecond * time.Duration(h.timeout), + } + req, err := http.NewRequest(http.MethodGet, urlPath, nil) if err != nil { + // handle error + return err + } + resp, err := client.Do(req) + if err != nil { + // handle error return err } defer resp.Body.Close() - body, _ := ioutil.ReadAll(resp.Body) + + body, err := ioutil.ReadAll(resp.Body) + if err != nil { + return err + } err = json.Unmarshal(body, res) if err != nil { @@ -105,18 +129,31 @@ func get(urlStr string, params url.Values, res interface{}) error { } // post 请求 -func post(urlStr string, data map[string]interface{}, res interface{}) error { +func (h *HttpClient) post(urlStr string, data map[string]interface{}, res interface{}) error { bytesData, err := json.Marshal(data) if err != nil { return err } - resp, err := http.Post(urlStr, "application/json", bytes.NewReader(bytesData)) + + client := &http.Client{ + Timeout: time.Millisecond * time.Duration(h.timeout), + } + req, err := http.NewRequest(http.MethodPost, urlStr, bytes.NewReader(bytesData)) + if err != nil { + // handle error + return err + } + req.Header.Set("Content-Type", "application/json") + resp, err := client.Do(req) if err != nil { + // handle error return err } defer resp.Body.Close() + body, err := ioutil.ReadAll(resp.Body) if err != nil { + // handle error return err } diff --git a/config/config-example.yml b/config/config-example.yml index 6b64b91..829dbda 100644 --- a/config/config-example.yml +++ b/config/config-example.yml @@ -1,37 +1,37 @@ app: port: 10001 engines: - - name: ETH + - coin_name: ETH contract: protocol: eth - rpc: https://mainnet.infura.io/v3/adee4ead47844d238802431fcb7683c6 + rpc: https://ropsten.infura.io/v3/adee4ead47844d238802431fcb7683c6 file: data/eth wallet_prefix: wallet- hash_prefix: hash- - block_init: 14000513 + block_init: 11828416 block_count: 1 block_after_time: 1 - receipt_count: 1 + receipt_count: 20 receipt_after_time: 1 confirms: 1 - recharge_notify_url: https://mainnet.infura.io - withdraw_notify_url: https://mainnet.infura.io - withdraw_private_key: dfxdsfsdf + recharge_notify_url: http://localhost:10001/api/withdraw + withdraw_notify_url: http://localhost:10001/api/withdraw + withdraw_private_key: 4006894ada31cfc87211e0b428c7f812789636cf6f79efef5d2acc2b60617cf1 decimals: 18 - - name: Tether USD - contract: 0xdac17f958d2ee523a2206206994597c13d831ec7 - protocol: eth - rpc: https://mainnet.infura.io/v3/adee4ead47844d238802431fcb7683c6 - file: data/usdt - wallet_prefix: wallet- - hash_prefix: hash- - block_init: 14000513 - block_count: 1 - block_after_time: 1 - receipt_count: 1 - receipt_after_time: 1 - confirms: 1 - recharge_notify_url: https://mainnet.infura.io - withdraw_notify_url: https://mainnet.infura.io - withdraw_private_key: dfxdsfsdf - decimals: 18 \ No newline at end of file +# - coin_name: TEST +# contract: 0xeDc4019BCBdA0f7acCBB4833ffF54d618d9b7f82 +# protocol: eth +# rpc: https://ropsten.infura.io/v3/adee4ead47844d238802431fcb7683c6 +# file: data/usdt +# wallet_prefix: wallet- +# hash_prefix: hash- +# block_init: 11824838 +# block_count: 1 +# block_after_time: 1 +# receipt_count: 1 +# receipt_after_time: 1 +# confirms: 1 +# recharge_notify_url: https://mainnet.infura.io +# withdraw_notify_url: https://mainnet.infura.io +# withdraw_private_key: 4006894ada31cfc87211e0b428c7f812789636cf6f79efef5d2acc2b60617cf1 +# decimals: 18 \ No newline at end of file diff --git a/config/config.go b/config/config.go index 49defc3..8017411 100644 --- a/config/config.go +++ b/config/config.go @@ -9,9 +9,9 @@ type AppConfig struct { } type EngineConfig struct { - Name string `yaml:"name"` // 名称 Contract string `yaml:"contract"` // 合约地址(为空表示主币) Protocol string `yaml:"protocol"` // 协议名称 + CoinName string `yaml:"coin_name"` // 币种名称 Rpc string `yaml:"rpc"` // rpc配置 File string `yaml:"file"` // db文件配置 WalletPrefix string `yaml:"wallet_prefix"` // 钱包的存储前缀 diff --git a/contracts/erc20.sol b/contracts/erc20.sol deleted file mode 100644 index 046e292..0000000 --- a/contracts/erc20.sol +++ /dev/null @@ -1,5 +0,0 @@ -pragma solidity ^0.4.17; - -contract ERC20 { - event Transfer(address indexed from, address indexed to, uint256 value); -} \ No newline at end of file diff --git a/contracts/erc20_sol_ERC20.abi b/contracts/erc20_sol_ERC20.abi deleted file mode 100644 index 56b5941..0000000 --- a/contracts/erc20_sol_ERC20.abi +++ /dev/null @@ -1 +0,0 @@ -[{"anonymous":false,"inputs":[{"indexed":true,"name":"from","type":"address"},{"indexed":true,"name":"to","type":"address"},{"indexed":false,"name":"value","type":"uint256"}],"name":"Transfer","type":"event"}] \ No newline at end of file diff --git a/contracts/eth/erc20.go b/contracts/eth/erc20.go new file mode 100644 index 0000000..5cd8d12 --- /dev/null +++ b/contracts/eth/erc20.go @@ -0,0 +1,334 @@ +// Code generated - DO NOT EDIT. +// This file is a generated binding and any manual changes will be lost. + +package eth + +import ( + "errors" + "math/big" + "strings" + + ethereum "github.com/ethereum/go-ethereum" + "github.com/ethereum/go-ethereum/accounts/abi" + "github.com/ethereum/go-ethereum/accounts/abi/bind" + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/core/types" + "github.com/ethereum/go-ethereum/event" +) + +// Reference imports to suppress errors if they are not otherwise used. +var ( + _ = errors.New + _ = big.NewInt + _ = strings.NewReader + _ = ethereum.NotFound + _ = bind.Bind + _ = common.Big1 + _ = types.BloomLookup + _ = event.NewSubscription +) + +// TokenMetaData contains all meta data concerning the Token contract. +var TokenMetaData = &bind.MetaData{ + ABI: "[{\"anonymous\":false,\"inputs\":[{\"indexed\":true,\"name\":\"from\",\"type\":\"address\"},{\"indexed\":true,\"name\":\"to\",\"type\":\"address\"},{\"indexed\":false,\"name\":\"value\",\"type\":\"uint256\"}],\"name\":\"Transfer\",\"type\":\"event\"}]", +} + +// TokenABI is the input ABI used to generate the binding from. +// Deprecated: Use TokenMetaData.ABI instead. +var TokenABI = TokenMetaData.ABI + +// Token is an auto generated Go binding around an Ethereum contract. +type Token struct { + TokenCaller // Read-only binding to the contract + TokenTransactor // Write-only binding to the contract + TokenFilterer // Log filterer for contract events +} + +// TokenCaller is an auto generated read-only Go binding around an Ethereum contract. +type TokenCaller struct { + contract *bind.BoundContract // Generic contract wrapper for the low level calls +} + +// TokenTransactor is an auto generated write-only Go binding around an Ethereum contract. +type TokenTransactor struct { + contract *bind.BoundContract // Generic contract wrapper for the low level calls +} + +// TokenFilterer is an auto generated log filtering Go binding around an Ethereum contract events. +type TokenFilterer struct { + contract *bind.BoundContract // Generic contract wrapper for the low level calls +} + +// TokenSession is an auto generated Go binding around an Ethereum contract, +// with pre-set call and transact options. +type TokenSession struct { + Contract *Token // Generic contract binding to set the session for + CallOpts bind.CallOpts // Call options to use throughout this session + TransactOpts bind.TransactOpts // Transaction auth options to use throughout this session +} + +// TokenCallerSession is an auto generated read-only Go binding around an Ethereum contract, +// with pre-set call options. +type TokenCallerSession struct { + Contract *TokenCaller // Generic contract caller binding to set the session for + CallOpts bind.CallOpts // Call options to use throughout this session +} + +// TokenTransactorSession is an auto generated write-only Go binding around an Ethereum contract, +// with pre-set transact options. +type TokenTransactorSession struct { + Contract *TokenTransactor // Generic contract transactor binding to set the session for + TransactOpts bind.TransactOpts // Transaction auth options to use throughout this session +} + +// TokenRaw is an auto generated low-level Go binding around an Ethereum contract. +type TokenRaw struct { + Contract *Token // Generic contract binding to access the raw methods on +} + +// TokenCallerRaw is an auto generated low-level read-only Go binding around an Ethereum contract. +type TokenCallerRaw struct { + Contract *TokenCaller // Generic read-only contract binding to access the raw methods on +} + +// TokenTransactorRaw is an auto generated low-level write-only Go binding around an Ethereum contract. +type TokenTransactorRaw struct { + Contract *TokenTransactor // Generic write-only contract binding to access the raw methods on +} + +// NewToken creates a new instance of Token, bound to a specific deployed contract. +func NewToken(address common.Address, backend bind.ContractBackend) (*Token, error) { + contract, err := bindToken(address, backend, backend, backend) + if err != nil { + return nil, err + } + return &Token{TokenCaller: TokenCaller{contract: contract}, TokenTransactor: TokenTransactor{contract: contract}, TokenFilterer: TokenFilterer{contract: contract}}, nil +} + +// NewTokenCaller creates a new read-only instance of Token, bound to a specific deployed contract. +func NewTokenCaller(address common.Address, caller bind.ContractCaller) (*TokenCaller, error) { + contract, err := bindToken(address, caller, nil, nil) + if err != nil { + return nil, err + } + return &TokenCaller{contract: contract}, nil +} + +// NewTokenTransactor creates a new write-only instance of Token, bound to a specific deployed contract. +func NewTokenTransactor(address common.Address, transactor bind.ContractTransactor) (*TokenTransactor, error) { + contract, err := bindToken(address, nil, transactor, nil) + if err != nil { + return nil, err + } + return &TokenTransactor{contract: contract}, nil +} + +// NewTokenFilterer creates a new log filterer instance of Token, bound to a specific deployed contract. +func NewTokenFilterer(address common.Address, filterer bind.ContractFilterer) (*TokenFilterer, error) { + contract, err := bindToken(address, nil, nil, filterer) + if err != nil { + return nil, err + } + return &TokenFilterer{contract: contract}, nil +} + +// bindToken binds a generic wrapper to an already deployed contract. +func bindToken(address common.Address, caller bind.ContractCaller, transactor bind.ContractTransactor, filterer bind.ContractFilterer) (*bind.BoundContract, error) { + parsed, err := abi.JSON(strings.NewReader(TokenABI)) + if err != nil { + return nil, err + } + return bind.NewBoundContract(address, parsed, caller, transactor, filterer), nil +} + +// Call invokes the (constant) contract method with params as input values and +// sets the output to result. The result type might be a single field for simple +// returns, a slice of interfaces for anonymous returns and a struct for named +// returns. +func (_Token *TokenRaw) Call(opts *bind.CallOpts, result *[]interface{}, method string, params ...interface{}) error { + return _Token.Contract.TokenCaller.contract.Call(opts, result, method, params...) +} + +// Transfer initiates a plain transaction to move funds to the contract, calling +// its default method if one is available. +func (_Token *TokenRaw) Transfer(opts *bind.TransactOpts) (*types.Transaction, error) { + return _Token.Contract.TokenTransactor.contract.Transfer(opts) +} + +// Transact invokes the (paid) contract method with params as input values. +func (_Token *TokenRaw) Transact(opts *bind.TransactOpts, method string, params ...interface{}) (*types.Transaction, error) { + return _Token.Contract.TokenTransactor.contract.Transact(opts, method, params...) +} + +// Call invokes the (constant) contract method with params as input values and +// sets the output to result. The result type might be a single field for simple +// returns, a slice of interfaces for anonymous returns and a struct for named +// returns. +func (_Token *TokenCallerRaw) Call(opts *bind.CallOpts, result *[]interface{}, method string, params ...interface{}) error { + return _Token.Contract.contract.Call(opts, result, method, params...) +} + +// Transfer initiates a plain transaction to move funds to the contract, calling +// its default method if one is available. +func (_Token *TokenTransactorRaw) Transfer(opts *bind.TransactOpts) (*types.Transaction, error) { + return _Token.Contract.contract.Transfer(opts) +} + +// Transact invokes the (paid) contract method with params as input values. +func (_Token *TokenTransactorRaw) Transact(opts *bind.TransactOpts, method string, params ...interface{}) (*types.Transaction, error) { + return _Token.Contract.contract.Transact(opts, method, params...) +} + +// TokenTransferIterator is returned from FilterTransfer and is used to iterate over the raw logs and unpacked data for Transfer events raised by the Token contract. +type TokenTransferIterator struct { + Event *TokenTransfer // Event containing the contract specifics and raw log + + contract *bind.BoundContract // Generic contract to use for unpacking event data + event string // Event name to use for unpacking event data + + logs chan types.Log // Log channel receiving the found contract events + sub ethereum.Subscription // Subscription for errors, completion and termination + done bool // Whether the subscription completed delivering logs + fail error // Occurred error to stop iteration +} + +// Next advances the iterator to the subsequent event, returning whether there +// are any more events found. In case of a retrieval or parsing error, false is +// returned and Error() can be queried for the exact failure. +func (it *TokenTransferIterator) Next() bool { + // If the iterator failed, stop iterating + if it.fail != nil { + return false + } + // If the iterator completed, deliver directly whatever's available + if it.done { + select { + case log := <-it.logs: + it.Event = new(TokenTransfer) + if err := it.contract.UnpackLog(it.Event, it.event, log); err != nil { + it.fail = err + return false + } + it.Event.Raw = log + return true + + default: + return false + } + } + // Iterator still in progress, wait for either a data or an error event + select { + case log := <-it.logs: + it.Event = new(TokenTransfer) + if err := it.contract.UnpackLog(it.Event, it.event, log); err != nil { + it.fail = err + return false + } + it.Event.Raw = log + return true + + case err := <-it.sub.Err(): + it.done = true + it.fail = err + return it.Next() + } +} + +// Error returns any retrieval or parsing error occurred during filtering. +func (it *TokenTransferIterator) Error() error { + return it.fail +} + +// Close terminates the iteration process, releasing any pending underlying +// resources. +func (it *TokenTransferIterator) Close() error { + it.sub.Unsubscribe() + return nil +} + +// TokenTransfer represents a Transfer event raised by the Token contract. +type TokenTransfer struct { + From common.Address + To common.Address + Value *big.Int + Raw types.Log // Blockchain specific contextual infos +} + +// FilterTransfer is a free log retrieval operation binding the contract event 0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef. +// +// Solidity: event Transfer(address indexed from, address indexed to, uint256 value) +func (_Token *TokenFilterer) FilterTransfer(opts *bind.FilterOpts, from []common.Address, to []common.Address) (*TokenTransferIterator, error) { + + var fromRule []interface{} + for _, fromItem := range from { + fromRule = append(fromRule, fromItem) + } + var toRule []interface{} + for _, toItem := range to { + toRule = append(toRule, toItem) + } + + logs, sub, err := _Token.contract.FilterLogs(opts, "Transfer", fromRule, toRule) + if err != nil { + return nil, err + } + return &TokenTransferIterator{contract: _Token.contract, event: "Transfer", logs: logs, sub: sub}, nil +} + +// WatchTransfer is a free log subscription operation binding the contract event 0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef. +// +// Solidity: event Transfer(address indexed from, address indexed to, uint256 value) +func (_Token *TokenFilterer) WatchTransfer(opts *bind.WatchOpts, sink chan<- *TokenTransfer, from []common.Address, to []common.Address) (event.Subscription, error) { + + var fromRule []interface{} + for _, fromItem := range from { + fromRule = append(fromRule, fromItem) + } + var toRule []interface{} + for _, toItem := range to { + toRule = append(toRule, toItem) + } + + logs, sub, err := _Token.contract.WatchLogs(opts, "Transfer", fromRule, toRule) + if err != nil { + return nil, err + } + return event.NewSubscription(func(quit <-chan struct{}) error { + defer sub.Unsubscribe() + for { + select { + case log := <-logs: + // New log arrived, parse the event and forward to the user + event := new(TokenTransfer) + if err := _Token.contract.UnpackLog(event, "Transfer", log); err != nil { + return err + } + event.Raw = log + + select { + case sink <- event: + case err := <-sub.Err(): + return err + case <-quit: + return nil + } + case err := <-sub.Err(): + return err + case <-quit: + return nil + } + } + }), nil +} + +// ParseTransfer is a log parse operation binding the contract event 0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef. +// +// Solidity: event Transfer(address indexed from, address indexed to, uint256 value) +func (_Token *TokenFilterer) ParseTransfer(log types.Log) (*TokenTransfer, error) { + event := new(TokenTransfer) + if err := _Token.contract.UnpackLog(event, "Transfer", log); err != nil { + return nil, err + } + event.Raw = log + return event, nil +} diff --git a/contracts/eth/erc20.sol b/contracts/eth/erc20.sol new file mode 100644 index 0000000..06dad9a --- /dev/null +++ b/contracts/eth/erc20.sol @@ -0,0 +1,79 @@ +pragma solidity ^0.4.17; + +/** + * @dev Interface of the ERC20 standard as defined in the EIP. + */ +interface IERC20 { + /** + * @dev Returns the amount of tokens in existence. + */ + function totalSupply() external view returns (uint256); + + /** + * @dev Returns the amount of tokens owned by `account`. + */ + function balanceOf(address account) external view returns (uint256); + + /** + * @dev Moves `amount` tokens from the caller's account to `recipient`. + * + * Returns a boolean value indicating whether the operation succeeded. + * + * Emits a {Transfer} event. + */ + function transfer(address recipient, uint256 amount) external returns (bool); + + /** + * @dev Returns the remaining number of tokens that `spender` will be + * allowed to spend on behalf of `owner` through {transferFrom}. This is + * zero by default. + * + * This value changes when {approve} or {transferFrom} are called. + */ + function allowance(address owner, address spender) external view returns (uint256); + + /** + * @dev Sets `amount` as the allowance of `spender` over the caller's tokens. + * + * Returns a boolean value indicating whether the operation succeeded. + * + * IMPORTANT: Beware that changing an allowance with this method brings the risk + * that someone may use both the old and the new allowance by unfortunate + * transaction ordering. One possible solution to mitigate this race + * condition is to first reduce the spender's allowance to 0 and set the + * desired value afterwards: + * https://github.com/ethereum/EIPs/issues/20#issuecomment-263524729 + * + * Emits an {Approval} event. + */ + function approve(address spender, uint256 amount) external returns (bool); + + /** + * @dev Moves `amount` tokens from `sender` to `recipient` using the + * allowance mechanism. `amount` is then deducted from the caller's + * allowance. + * + * Returns a boolean value indicating whether the operation succeeded. + * + * Emits a {Transfer} event. + */ + function transferFrom( + address sender, + address recipient, + uint256 amount + ) external returns (bool); + + /** + * @dev Emitted when `value` tokens are moved from one account (`from`) to + * another (`to`). + * + * Note that `value` may be zero. + */ + event Transfer(address indexed from, address indexed to, uint256 value); + + /** + * @dev Emitted when the allowance of a `spender` for an `owner` is set by + * a call to {approve}. `value` is the new allowance. + */ + event Approval(address indexed owner, address indexed spender, uint256 value); +} \ No newline at end of file diff --git a/contracts/eth/erc20_sol_IERC20.abi b/contracts/eth/erc20_sol_IERC20.abi new file mode 100644 index 0000000..6013e8d --- /dev/null +++ b/contracts/eth/erc20_sol_IERC20.abi @@ -0,0 +1 @@ +[{"constant":false,"inputs":[{"name":"spender","type":"address"},{"name":"amount","type":"uint256"}],"name":"approve","outputs":[{"name":"","type":"bool"}],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[],"name":"totalSupply","outputs":[{"name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[{"name":"sender","type":"address"},{"name":"recipient","type":"address"},{"name":"amount","type":"uint256"}],"name":"transferFrom","outputs":[{"name":"","type":"bool"}],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[{"name":"account","type":"address"}],"name":"balanceOf","outputs":[{"name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[{"name":"recipient","type":"address"},{"name":"amount","type":"uint256"}],"name":"transfer","outputs":[{"name":"","type":"bool"}],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[{"name":"owner","type":"address"},{"name":"spender","type":"address"}],"name":"allowance","outputs":[{"name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"anonymous":false,"inputs":[{"indexed":true,"name":"from","type":"address"},{"indexed":true,"name":"to","type":"address"},{"indexed":false,"name":"value","type":"uint256"}],"name":"Transfer","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"name":"owner","type":"address"},{"indexed":true,"name":"spender","type":"address"},{"indexed":false,"name":"value","type":"uint256"}],"name":"Approval","type":"event"}] \ No newline at end of file diff --git a/doc.go b/doc.go index dfce40b..a0d27a7 100644 --- a/doc.go +++ b/doc.go @@ -7,5 +7,5 @@ import ( ) func init() { - isSwag = ginSwagger.WrapHandler(swaggerFiles.Handler) + isSwag = true } diff --git a/docs/docs.go b/docs/docs.go index b27865c..0e456dd 100644 --- a/docs/docs.go +++ b/docs/docs.go @@ -115,7 +115,7 @@ var doc = `{ } }, "/api/getTransactionReceipt": { - "post": { + "get": { "security": [ { "ApiKeyAuth": [] @@ -213,9 +213,14 @@ var doc = `{ "server.CreateWalletReq": { "type": "object", "required": [ + "coinName", "protocol" ], "properties": { + "coinName": { + "description": "币种名称", + "type": "string" + }, "protocol": { "description": "协议", "type": "string" @@ -235,6 +240,7 @@ var doc = `{ "type": "object", "required": [ "address", + "coinName", "protocol" ], "properties": { @@ -242,6 +248,10 @@ var doc = `{ "description": "地址", "type": "string" }, + "coinName": { + "description": "币种名称", + "type": "string" + }, "protocol": { "description": "协议", "type": "string" @@ -267,10 +277,15 @@ var doc = `{ "server.TransactionReceiptReq": { "type": "object", "required": [ + "coinName", "hash", "protocol" ], "properties": { + "coinName": { + "description": "币种名称", + "type": "string" + }, "hash": { "description": "交易哈希", "type": "string" @@ -294,6 +309,7 @@ var doc = `{ "type": "object", "required": [ "address", + "coinName", "orderId", "protocol", "value" @@ -303,6 +319,10 @@ var doc = `{ "description": "提现地址", "type": "string" }, + "coinName": { + "description": "币种名称", + "type": "string" + }, "orderId": { "description": "订单号", "type": "string" diff --git a/docs/swagger.json b/docs/swagger.json index 1ecdd63..e93a54d 100644 --- a/docs/swagger.json +++ b/docs/swagger.json @@ -99,7 +99,7 @@ } }, "/api/getTransactionReceipt": { - "post": { + "get": { "security": [ { "ApiKeyAuth": [] @@ -197,9 +197,14 @@ "server.CreateWalletReq": { "type": "object", "required": [ + "coinName", "protocol" ], "properties": { + "coinName": { + "description": "币种名称", + "type": "string" + }, "protocol": { "description": "协议", "type": "string" @@ -219,6 +224,7 @@ "type": "object", "required": [ "address", + "coinName", "protocol" ], "properties": { @@ -226,6 +232,10 @@ "description": "地址", "type": "string" }, + "coinName": { + "description": "币种名称", + "type": "string" + }, "protocol": { "description": "协议", "type": "string" @@ -251,10 +261,15 @@ "server.TransactionReceiptReq": { "type": "object", "required": [ + "coinName", "hash", "protocol" ], "properties": { + "coinName": { + "description": "币种名称", + "type": "string" + }, "hash": { "description": "交易哈希", "type": "string" @@ -278,6 +293,7 @@ "type": "object", "required": [ "address", + "coinName", "orderId", "protocol", "value" @@ -287,6 +303,10 @@ "description": "提现地址", "type": "string" }, + "coinName": { + "description": "币种名称", + "type": "string" + }, "orderId": { "description": "订单号", "type": "string" diff --git a/docs/swagger.yaml b/docs/swagger.yaml index c815e80..d6e1525 100644 --- a/docs/swagger.yaml +++ b/docs/swagger.yaml @@ -1,10 +1,14 @@ definitions: server.CreateWalletReq: properties: + coinName: + description: 币种名称 + type: string protocol: description: 协议 type: string required: + - coinName - protocol type: object server.CreateWalletRes: @@ -18,11 +22,15 @@ definitions: address: description: 地址 type: string + coinName: + description: 币种名称 + type: string protocol: description: 协议 type: string required: - address + - coinName - protocol type: object server.Response: @@ -38,6 +46,9 @@ definitions: type: object server.TransactionReceiptReq: properties: + coinName: + description: 币种名称 + type: string hash: description: 交易哈希 type: string @@ -45,6 +56,7 @@ definitions: description: 协议 type: string required: + - coinName - hash - protocol type: object @@ -59,6 +71,9 @@ definitions: address: description: 提现地址 type: string + coinName: + description: 币种名称 + type: string orderId: description: 订单号 type: string @@ -70,6 +85,7 @@ definitions: type: integer required: - address + - coinName - orderId - protocol - value @@ -141,7 +157,7 @@ paths: tags: - 钱包 /api/getTransactionReceipt: - post: + get: parameters: - description: 参数 in: body diff --git a/engine/concurrent.go b/engine/concurrent.go index 9f41ecf..1258a4a 100644 --- a/engine/concurrent.go +++ b/engine/concurrent.go @@ -33,6 +33,7 @@ type ConCurrentEngine struct { Worker Worker config config.EngineConfig Protocol string + CoinName string db db.Database http *client.HttpClient } @@ -113,6 +114,7 @@ func (c *ConCurrentEngine) createReceiptWorker() { // 判断是否存在 if ok, err := c.db.Has(c.config.HashPrefix + transaction.Hash); err == nil && ok { + log.Info().Msgf("当前哈希存在:%v", transaction.Hash) orderId, err := c.db.Get(c.config.HashPrefix + transaction.Hash) if err != nil { log.Error().Msgf("未查询到订单:%v, %v", transaction.Hash, err) @@ -120,7 +122,7 @@ func (c *ConCurrentEngine) createReceiptWorker() { c.scheduler.ReceiptSubmit(transaction) continue } - err = c.http.WithdrawSuccess(transaction.Hash, orderId, transaction.To, transaction.Value.Int64()) + err = c.http.WithdrawSuccess(transaction.Hash, transaction.Status, orderId, transaction.To, transaction.Value.Int64()) if err != nil { log.Error().Msgf("提现回调通知失败:%v, %v", transaction.Hash, err) // 重新提交 @@ -129,7 +131,8 @@ func (c *ConCurrentEngine) createReceiptWorker() { } _ = c.db.Delete(transaction.Hash) } else if ok, err := c.db.Has(c.config.WalletPrefix + transaction.To); err == nil && ok { - err = c.http.RechargeSuccess(transaction.Hash, transaction.To, transaction.Value.Int64()) + log.Info().Msgf("当前地址存在:%v", transaction.To) + err = c.http.RechargeSuccess(transaction.Hash, transaction.Status, transaction.To, transaction.Value.Int64()) if err != nil { log.Error().Msgf("充值回调通知失败:%v, %v", transaction.Hash, err) // 重新提交 @@ -148,6 +151,7 @@ func (c *ConCurrentEngine) CreateWallet() (string, error) { return "", err } _ = c.db.Put(c.config.WalletPrefix+wallet.Address, wallet.PrivateKey) + log.Info().Msgf("创建钱包成功,地址:%v,私钥:%v", wallet.Address, wallet.PrivateKey) return wallet.Address, nil } @@ -203,7 +207,7 @@ func NewEngine(config config.EngineConfig) (*ConCurrentEngine, error) { } } - http := client.NewHttpClient(config.Protocol, config.RechargeNotifyUrl, config.WithdrawNotifyUrl) + http := client.NewHttpClient(config.Protocol, config.CoinName, config.RechargeNotifyUrl, config.WithdrawNotifyUrl) return &ConCurrentEngine{ //scheduler: scheduler.NewSimpleScheduler(), // 简单的任务调度器 @@ -211,6 +215,7 @@ func NewEngine(config config.EngineConfig) (*ConCurrentEngine, error) { Worker: worker, config: config, Protocol: config.Protocol, + CoinName: config.CoinName, db: keyDB, http: http, }, nil diff --git a/engine/eth_worker.go b/engine/eth_worker.go index 6cca591..6a55074 100644 --- a/engine/eth_worker.go +++ b/engine/eth_worker.go @@ -3,7 +3,6 @@ package engine import ( "context" "crypto/ecdsa" - "encoding/hex" "errors" "github.com/ethereum/go-ethereum" "github.com/ethereum/go-ethereum/accounts/abi" @@ -12,7 +11,6 @@ import ( ethTypes "github.com/ethereum/go-ethereum/core/types" "github.com/ethereum/go-ethereum/crypto" "github.com/ethereum/go-ethereum/ethclient" - "github.com/ethereum/go-ethereum/rlp" "github.com/lmxdawn/wallet/client" "github.com/lmxdawn/wallet/types" "math/big" @@ -20,18 +18,21 @@ import ( ) type EthWorker struct { - confirms uint64 // 需要的确认数 - http *ethclient.Client - contractTransferHash common.Hash - contract string // 代币合约地址,为空表示主币 - contractAbi abi.ABI // 合约的abi + confirms uint64 // 需要的确认数 + http *ethclient.Client + contractTransferEventHash common.Hash + contractTransferHash common.Hash + contract string // 代币合约地址,为空表示主币 + contractAbi abi.ABI // 合约的abi } func NewEthWorker(confirms uint64, contract string, url string) (*EthWorker, error) { http := client.NewEthClient(url) - contractTransferSig := []byte("Transfer(address,address,uint256)") - contractTransferHash := crypto.Keccak256Hash(contractTransferSig) + contractTransferHashSig := []byte("transfer(address,uint256)") + contractTransferHash := crypto.Keccak256Hash(contractTransferHashSig) + contractTransferEventHashSig := []byte("Transfer(address,address,uint256)") + contractTransferEventHash := crypto.Keccak256Hash(contractTransferEventHashSig) tokenABI := "[{\"anonymous\":false,\"inputs\":[{\"indexed\":true,\"name\":\"from\",\"type\":\"address\"},{\"indexed\":true,\"name\":\"to\",\"type\":\"address\"},{\"indexed\":false,\"name\":\"value\",\"type\":\"uint256\"}],\"name\":\"Transfer\",\"type\":\"event\"}]" contractAbi, err := abi.JSON(strings.NewReader(tokenABI)) if err != nil { @@ -39,11 +40,12 @@ func NewEthWorker(confirms uint64, contract string, url string) (*EthWorker, err } return &EthWorker{ - confirms: confirms, - contract: contract, - http: http, - contractTransferHash: contractTransferHash, - contractAbi: contractAbi, + confirms: confirms, + contract: contract, + http: http, + contractTransferHash: contractTransferHash, + contractTransferEventHash: contractTransferEventHash, + contractAbi: contractAbi, }, nil } @@ -81,8 +83,7 @@ func (e *EthWorker) getTransactionReceipt(transaction *types.Transaction) error } func (e *EthWorker) getTransaction(num uint64) ([]types.Transaction, uint64, error) { - - if e.contract != "" { + if e.contract == "" { return e.getBlockTransaction(num) } else { return e.getTokenTransaction(num) @@ -116,12 +117,12 @@ func (e *EthWorker) getBlockTransaction(num uint64) ([]types.Transaction, uint64 BlockNumber: big.NewInt(int64(num)), BlockHash: block.Hash().Hex(), Hash: tx.Hash().Hex(), - From: msg.From().Hex(), - To: tx.To().Hex(), + From: strings.ToLower(msg.From().Hex()), + To: strings.ToLower(tx.To().Hex()), Value: tx.Value(), }) } - return transactions, num, nil + return transactions, num + 1, nil } func (e *EthWorker) getTokenTransaction(num uint64) ([]types.Transaction, uint64, error) { @@ -142,7 +143,7 @@ func (e *EthWorker) getTokenTransaction(num uint64) ([]types.Transaction, uint64 var transactions []types.Transaction for _, vLog := range logs { switch vLog.Topics[0].Hex() { - case e.contractTransferHash.Hex(): + case e.contractTransferEventHash.Hex(): var transferEvent LogTransfer @@ -155,8 +156,8 @@ func (e *EthWorker) getTokenTransaction(num uint64) ([]types.Transaction, uint64 BlockNumber: big.NewInt(int64(num)), BlockHash: vLog.BlockHash.Hex(), Hash: vLog.TxHash.Hex(), - From: vLog.Topics[1].Hex(), - To: vLog.Topics[2].Hex(), + From: strings.ToLower(common.HexToAddress(vLog.Topics[1].Hex()).Hex()), + To: strings.ToLower(common.HexToAddress(vLog.Topics[2].Hex()).Hex()), Value: transferEvent.Value, }) } @@ -196,8 +197,6 @@ func (e *EthWorker) createWallet() (*types.Wallet, error) { // sendTransaction 创建并发送裸交易 func (e *EthWorker) sendTransaction(privateKeyStr string, toAddress string, amount int64) (string, error) { - // TODO 判断是否为代币转账 - privateKey, err := crypto.HexToECDSA(privateKeyStr) if err != nil { return "", err @@ -216,14 +215,28 @@ func (e *EthWorker) sendTransaction(privateKeyStr string, toAddress string, amou } value := big.NewInt(amount) // in wei (1 eth) - gasLimit := uint64(21000) // in units + var gasLimit uint64 + gasLimit = uint64(80000) // in units gasPrice, err := e.http.SuggestGasPrice(context.Background()) if err != nil { return "", err } - - toAddressHex := common.HexToAddress(toAddress) var data []byte + toAddressHex := common.HexToAddress(toAddress) + if e.contract != "" { + data, err = makeERC20TransferData(e.contractTransferHash, toAddressHex, value) + if err != nil { + return "", err + } + if err != nil { + return "", err + } + value = big.NewInt(0) + toAddressHex = common.HexToAddress(e.contract) + // 代币转账把 gasPrice 乘以 2 + //gasPrice = gasPrice.Mul(gasPrice,big.NewInt(2)) + } + txData := ðTypes.LegacyTx{ Nonce: nonce, To: &toAddressHex, @@ -244,21 +257,20 @@ func (e *EthWorker) sendTransaction(privateKeyStr string, toAddress string, amou return "", err } - rawTxBytes, err := rlp.EncodeToBytes(signTx) + err = e.http.SendTransaction(context.Background(), signTx) if err != nil { return "", err } - rawTxHex := hex.EncodeToString(rawTxBytes) - txSend := new(ethTypes.Transaction) - err = rlp.DecodeBytes(rawTxBytes, &txSend) - if err != nil { - return "", err - } - err = e.http.SendTransaction(context.Background(), txSend) - if err != nil { - return "", err - } + return signTx.Hash().Hex(), nil +} - return rawTxHex, nil +func makeERC20TransferData(contractTransferHash common.Hash, toAddress common.Address, amount *big.Int) ([]byte, error) { + var data []byte + data = append(data, contractTransferHash[:4]...) + paddedAddress := common.LeftPadBytes(toAddress.Bytes(), 32) + data = append(data, paddedAddress...) + paddedAmount := common.LeftPadBytes(amount.Bytes(), 32) + data = append(data, paddedAmount...) + return data, nil } diff --git a/server/err_code.go b/server/err_code.go index 190fd22..83afd6e 100644 --- a/server/err_code.go +++ b/server/err_code.go @@ -11,7 +11,8 @@ var ( ErrNotData = &Errno{Code: 10004, Message: "没有数据"} ErrNotChangeData = &Errno{Code: 10005, Message: "数据没有更改"} ErrNotRepeatData = &Errno{Code: 10006, Message: "数据已存在"} - ErrCreateWallet = &Errno{Code: 10007, Message: "创建钱包失败"} + ErrEngine = &Errno{Code: 10007, Message: "Engine Not"} + ErrCreateWallet = &Errno{Code: 10008, Message: "创建钱包失败"} ) // Errno ... diff --git a/server/handler.go b/server/handler.go index 5706138..90559d7 100644 --- a/server/handler.go +++ b/server/handler.go @@ -22,7 +22,13 @@ func CreateWallet(c *gin.Context) { return } - currentEngine := c.MustGet(q.Protocol).(engine.ConCurrentEngine) + v, ok := c.Get(q.Protocol + q.CoinName) + if !ok { + APIResponse(c, ErrEngine, nil) + return + } + + currentEngine := v.(*engine.ConCurrentEngine) // 创建钱包 address, err := currentEngine.CreateWallet() @@ -53,7 +59,13 @@ func DelWallet(c *gin.Context) { return } - currentEngine := c.MustGet(q.Protocol).(engine.ConCurrentEngine) + v, ok := c.Get(q.Protocol + q.CoinName) + if !ok { + APIResponse(c, ErrEngine, nil) + return + } + + currentEngine := v.(*engine.ConCurrentEngine) err := currentEngine.DeleteWallet(q.Address) if err != nil { @@ -81,11 +93,17 @@ func Withdraw(c *gin.Context) { return } - currentEngine := c.MustGet(q.Protocol).(engine.ConCurrentEngine) + v, ok := c.Get(q.Protocol + q.CoinName) + if !ok { + APIResponse(c, ErrEngine, nil) + return + } + + currentEngine := v.(*engine.ConCurrentEngine) hash, err := currentEngine.Withdraw(q.OrderId, q.Address, q.Value) if err != nil { - APIResponse(c, nil, nil) + APIResponse(c, err, nil) return } @@ -101,7 +119,7 @@ func Withdraw(c *gin.Context) { // @Security ApiKeyAuth // @Param login body TransactionReceiptReq true "参数" // @Success 200 {object} Response{data=server.TransactionReceiptRes} -// @Router /api/getTransactionReceipt [post] +// @Router /api/getTransactionReceipt [get] func GetTransactionReceipt(c *gin.Context) { var q TransactionReceiptReq @@ -111,7 +129,13 @@ func GetTransactionReceipt(c *gin.Context) { return } - currentEngine := c.MustGet(q.Protocol).(engine.ConCurrentEngine) + v, ok := c.Get(q.Protocol + q.CoinName) + if !ok { + APIResponse(c, ErrEngine, nil) + return + } + + currentEngine := v.(*engine.ConCurrentEngine) status, err := currentEngine.GetTransactionReceipt(q.Hash) if err != nil { diff --git a/server/middleware.go b/server/middleware.go index 69dac38..7c10ac5 100644 --- a/server/middleware.go +++ b/server/middleware.go @@ -11,7 +11,7 @@ func AuthRequired() gin.HandlerFunc { return func(c *gin.Context) { token := c.GetHeader("x-token") - if token != "" { + if token == "" { c.Abort() APIResponse(c, ErrToken, nil) } @@ -25,7 +25,7 @@ func SetDB(engines ...*engine.ConCurrentEngine) gin.HandlerFunc { return func(c *gin.Context) { for _, currentEngine := range engines { - c.Set(currentEngine.Protocol, currentEngine) + c.Set(currentEngine.Protocol+currentEngine.CoinName, currentEngine) } } diff --git a/server/req.go b/server/req.go index f478ba4..00ee950 100644 --- a/server/req.go +++ b/server/req.go @@ -2,15 +2,18 @@ package server type CreateWalletReq struct { Protocol string `json:"protocol" binding:"required"` // 协议 + CoinName string `json:"coinName" binding:"required"` // 币种名称 } type DelWalletReq struct { Protocol string `json:"protocol" binding:"required"` // 协议 + CoinName string `json:"coinName" binding:"required"` // 币种名称 Address string `json:"address" binding:"required"` // 地址 } type WithdrawReq struct { Protocol string `json:"protocol" binding:"required"` // 协议 + CoinName string `json:"coinName" binding:"required"` // 币种名称 OrderId string `json:"orderId" binding:"required"` // 订单号 Address string `json:"address" binding:"required"` // 提现地址 Value int64 `json:"value" binding:"required"` // 金额 @@ -18,5 +21,6 @@ type WithdrawReq struct { type TransactionReceiptReq struct { Protocol string `json:"protocol" binding:"required"` // 协议 + CoinName string `json:"coinName" binding:"required"` // 币种名称 Hash string `json:"hash" binding:"required"` // 交易哈希 } diff --git a/server/rpc.go b/server/rpc.go index d21db31..68f6496 100644 --- a/server/rpc.go +++ b/server/rpc.go @@ -5,6 +5,7 @@ import ( "github.com/gin-gonic/gin" "github.com/lmxdawn/wallet/config" "github.com/lmxdawn/wallet/engine" + "github.com/rs/zerolog/log" ginSwagger "github.com/swaggo/gin-swagger" "github.com/swaggo/gin-swagger/swaggerFiles" ) @@ -27,8 +28,9 @@ func Start(isSwag bool, configPath string) { engines = append(engines, eth) } + // 启动监听器 for _, currentEngine := range engines { - currentEngine.Run() + go currentEngine.Run() } server := gin.Default() @@ -40,9 +42,9 @@ func Start(isSwag bool, configPath string) { auth := server.Group("/api", AuthRequired()) { - auth.GET("/createWallet", CreateWallet) - auth.GET("/delWallet", DelWallet) - auth.GET("/withdraw", Withdraw) + auth.POST("/createWallet", CreateWallet) + auth.POST("/delWallet", DelWallet) + auth.POST("/withdraw", Withdraw) auth.GET("/getTransactionReceipt", GetTransactionReceipt) } @@ -56,4 +58,6 @@ func Start(isSwag bool, configPath string) { panic("start error") } + log.Info().Msgf("start success") + }