├── .gitignore ├── Dockerfile ├── Gopkg.lock ├── Gopkg.toml ├── LICENSE ├── README.md ├── WEBSOCKET_API.md ├── app ├── config.go ├── init.go ├── logger.go ├── scope.go └── version.go ├── cmd ├── root.go └── serve.go ├── config ├── config.docker.yaml ├── config.prod.yaml ├── config.test.yaml ├── config.yaml └── errors.yaml ├── contracts ├── contractsinterfaces │ ├── exchange.go │ └── token.go ├── exchange.go ├── exchange_test.go ├── main.go ├── token.go └── token_test.go ├── crons ├── crons.go └── tickStreaming.go ├── daos ├── account.go ├── account_test.go ├── main.go ├── order.go ├── order_test.go ├── pair.go ├── pair_test.go ├── session.go ├── token.go ├── token_test.go ├── trade.go ├── trade_test.go ├── wallet.go └── wallet_test.go ├── docker-compose.yml ├── dump.rdb ├── e2e ├── account.go ├── init.go ├── order_test.go ├── pair.go ├── token.go └── ws.go ├── endpoints ├── accounts.go ├── main.go ├── ohlcv.go ├── order.go ├── orderbook.go ├── pair.go ├── pair_test.go ├── token.go ├── token_test.go └── trade.go ├── engine ├── deprecated.go ├── engine.go ├── engine_test.go ├── orderbook.go ├── orderbook_test.go └── store.go ├── errors ├── api_error.go ├── api_error_test.go ├── errors.go ├── errors_test.go ├── template.go └── template_test.go ├── ethereum ├── client.go ├── config.go └── provider.go ├── gin-bin ├── interfaces └── interfaces.go ├── operator ├── operator.go ├── operator_test.go └── txqueue.go ├── rabbitmq ├── engine.go ├── operator.go ├── orders.go └── rabbitmq.go ├── redis └── redis.go ├── server.go ├── server_test.go ├── services ├── account.go ├── deprecated.go ├── main.go ├── ohlcv.go ├── ohlcv_test.go ├── order.go ├── order_test.go ├── orderbook.go ├── pair.go ├── services_test.go ├── token.go ├── trade.go ├── tx.go └── wallet.go ├── testdata └── init.go ├── types ├── account.go ├── account_test.go ├── engine.go ├── main.go ├── ohlcv.go ├── operator.go ├── order.go ├── order_cancel.go ├── order_test.go ├── pair.go ├── pair_test.go ├── payloads.go ├── payloads_test.go ├── signature.go ├── token.go ├── trade.go ├── trade_test.go ├── wallet.go ├── wallet_test.go ├── wsmsg.go └── wsmsg_test.go ├── utils ├── common.go ├── httputils │ └── httputils.go ├── log.go ├── math │ └── big.go ├── seed-data │ ├── accounts.json │ ├── pairs.json │ └── tokens.json ├── testutils │ ├── address.go │ ├── client.go │ ├── compare.go │ ├── contracts.go │ ├── factory.go │ ├── factory_test.go │ ├── mocks │ │ ├── account_dao.go │ │ ├── account_service.go │ │ ├── engine.go │ │ ├── ethereum.go │ │ ├── ethereum_client.go │ │ ├── ethereum_service.go │ │ ├── exchange.go │ │ ├── ohlcv_service.go │ │ ├── order_dao.go │ │ ├── order_service.go │ │ ├── orderbook_service.go │ │ ├── pair_dao.go │ │ ├── pair_service.go │ │ ├── provider.go │ │ ├── token_dao.go │ │ ├── token_service.go │ │ ├── trade_dao.go │ │ ├── trade_service.go │ │ ├── tx_service.go │ │ ├── wallet_dao.go │ │ └── wallet_service.go │ ├── orders.go │ ├── pair.go │ ├── testutils.go │ ├── tokens.go │ ├── trade.go │ └── wallet.go ├── units.go └── units │ └── units.go └── ws ├── connection.go ├── ohlcv.go ├── orderbook.go ├── orders.go ├── raw_orderbook.go └── trades.go /.gitignore: -------------------------------------------------------------------------------- 1 | /logs/* 2 | 3 | .DS_Store 4 | .vscode 5 | .idea 6 | .vendor-new 7 | *.[56789ao] 8 | *.a[56789o] 9 | *.so 10 | *.pyc 11 | ._* 12 | .nfs.* 13 | [56789a].out 14 | *~ 15 | *.orig 16 | *.rej 17 | *.exe 18 | .*.swp 19 | core 20 | *.cgo*.go 21 | *.cgo*.c 22 | _cgo_* 23 | _obj 24 | _test 25 | _testmain.go 26 | 27 | /VERSION.cache 28 | /bin/ 29 | .vscode/ 30 | vendor/ 31 | /build.out 32 | /doc/articles/wiki/*.bin 33 | /goinstall.log 34 | /last-change 35 | /misc/cgo/life/run.out 36 | /misc/cgo/stdio/run.out 37 | /misc/cgo/testso/main 38 | /pkg/ 39 | /src/*.*/ 40 | /src/cmd/cgo/zdefaultcc.go 41 | /src/cmd/dist/dist 42 | /src/cmd/go/internal/cfg/zdefaultcc.go 43 | /src/cmd/go/internal/cfg/zosarch.go 44 | /src/cmd/internal/objabi/zbootstrap.go 45 | /src/go/build/zcgo.go 46 | /src/go/doc/headscan 47 | /src/runtime/internal/sys/zversion.go 48 | /src/unicode/maketables 49 | /test.out 50 | /test/garbage/*.out 51 | /test/pass.out 52 | /test/run.out 53 | /test/times.out 54 | 55 | # This file includes artifacts of Go build that should not be checked in. 56 | # For files created by specific development environment (e.g. editor), 57 | # use alternative ways to exclude files from git. 58 | # For example, set up .git/info/exclude or use a global .gitignore. -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | FROM golang 2 | 3 | # Download dep for dependency management 4 | RUN go get github.com/golang/dep/cmd/dep 5 | 6 | # Download gin for live reload (Usage: gin --path src --port 8081 run server.go serve) 7 | RUN go get github.com/codegangsta/gin 8 | 9 | WORKDIR /go/src/app 10 | 11 | ADD Gopkg.toml Gopkg.toml 12 | ADD Gopkg.lock Gopkg.lock 13 | 14 | COPY ./ ./ 15 | 16 | RUN dep ensure -v 17 | CMD ["go", "run", "server.go", "serve", "--env", "docker"] 18 | 19 | EXPOSE 8081 -------------------------------------------------------------------------------- /Gopkg.toml: -------------------------------------------------------------------------------- 1 | # Gopkg.toml example 2 | # 3 | # Refer to https://golang.github.io/dep/docs/Gopkg.toml.html 4 | # for detailed Gopkg.toml documentation. 5 | # 6 | # required = ["github.com/user/thing/cmd/thing"] 7 | # ignored = ["github.com/user/project/pkgX", "bitbucket.org/user/project/pkgA/pkgY"] 8 | # 9 | # [[constraint]] 10 | # name = "github.com/user/project" 11 | # version = "1.0.0" 12 | # 13 | # [[constraint]] 14 | # name = "github.com/user/project2" 15 | # branch = "dev" 16 | # source = "github.com/myfork/project2" 17 | # 18 | # [[override]] 19 | # name = "github.com/x/y" 20 | # version = "2.4.0" 21 | # 22 | # [prune] 23 | # non-go = false 24 | # go-tests = true 25 | # unused-packages = true 26 | 27 | 28 | [[constraint]] 29 | name = "github.com/Sirupsen/logrus" 30 | version = "1.0.6" 31 | 32 | [[constraint]] 33 | name = "github.com/ethereum/go-ethereum" 34 | version = "1.8.13" 35 | 36 | [[constraint]] 37 | name = "github.com/go-ozzo/ozzo-dbx" 38 | version = "1.1.0" 39 | 40 | [[constraint]] 41 | name = "github.com/go-ozzo/ozzo-routing" 42 | version = "2.1.4" 43 | 44 | [[constraint]] 45 | name = "github.com/go-ozzo/ozzo-validation" 46 | version = "3.4.0" 47 | 48 | [[constraint]] 49 | name = "github.com/go-test/deep" 50 | version = "1.0.1" 51 | 52 | [[constraint]] 53 | name = "github.com/gomodule/redigo" 54 | version = "2.0.0" 55 | 56 | [[constraint]] 57 | name = "github.com/gorilla/websocket" 58 | version = "1.2.0" 59 | 60 | [[constraint]] 61 | name = "github.com/op/go-logging" 62 | branch = "master" 63 | 64 | # [[constraint]] 65 | # name = "github.com/kr/pretty" 66 | # version = "0.1.0" 67 | 68 | [[constraint]] 69 | name = "github.com/posener/wstest" 70 | version = "1.1.0" 71 | 72 | [[constraint]] 73 | name = "github.com/robfig/cron" 74 | version = "1.1.0" 75 | 76 | [[constraint]] 77 | name = "github.com/spf13/viper" 78 | version = "1.0.2" 79 | 80 | [[constraint]] 81 | branch = "master" 82 | name = "github.com/streadway/amqp" 83 | 84 | [[constraint]] 85 | name = "github.com/stretchr/testify" 86 | version = "1.2.2" 87 | 88 | [[constraint]] 89 | branch = "v2" 90 | name = "gopkg.in/mgo.v2" 91 | 92 | [[constraint]] 93 | name = "gopkg.in/yaml.v2" 94 | version = "2.2.1" 95 | 96 | [prune] 97 | go-tests = true 98 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2018 Proof 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /app/config.go: -------------------------------------------------------------------------------- 1 | package app 2 | 3 | import ( 4 | "fmt" 5 | 6 | "github.com/go-ozzo/ozzo-validation" 7 | "github.com/spf13/viper" 8 | ) 9 | 10 | // Config stores the application-wide configurations 11 | var Config appConfig 12 | 13 | type appConfig struct { 14 | // the path to the error message file. Defaults to "config/errors.yaml" 15 | ErrorFile string `mapstructure:"error_file"` 16 | // the server port. Defaults to 8080 17 | ServerPort int `mapstructure:"server_port"` 18 | // the data source name (DSN) for connecting to the database. required. 19 | DSN string `mapstructure:"dsn"` 20 | // the data source name (DSN) for connecting to the database. required. 21 | DBName string `mapstructure:"db_name"` 22 | // the make fee is the percentage to charged from maker 23 | MakeFee float64 `mapstructure:"make_fee"` 24 | // the take fee is the percentage to charged from maker 25 | TakeFee float64 `mapstructure:"take_fee"` 26 | // the Rabbitmq is the URI of rabbitmq to use 27 | Rabbitmq string `mapstructure:"rabbitmq"` 28 | // the redis is the URI of redis to use 29 | Redis string `mapstructure:"redis"` 30 | // the signing method for JWT. Defaults to "HS256" 31 | JWTSigningMethod string `mapstructure:"jwt_signing_method"` 32 | // JWT signing key. required. 33 | JWTSigningKey string `mapstructure:"jwt_signing_key"` 34 | // JWT verification key. required. 35 | JWTVerificationKey string `mapstructure:"jwt_verification_key"` 36 | // TickDuration is user by tick streaming cron 37 | TickDuration map[string][]int64 `mapstructure:"tick_duration"` 38 | 39 | Logs map[string]string `mapstructure:"logs"` 40 | 41 | Ethereum map[string]string `mapstructure:"ethereum"` 42 | } 43 | 44 | func (config appConfig) Validate() error { 45 | return validation.ValidateStruct(&config, 46 | validation.Field(&config.DSN, validation.Required), 47 | validation.Field(&config.JWTSigningKey, validation.Required), 48 | validation.Field(&config.JWTVerificationKey, validation.Required), 49 | ) 50 | } 51 | 52 | // LoadConfig loads configuration from the given list of paths and populates it into the Config variable. 53 | // The configuration file(s) should be named as app.yaml. 54 | // Environment variables with the prefix "RESTFUL_" in their names are also read automatically. 55 | func LoadConfig(configPath string, env string) error { 56 | v := viper.New() 57 | if env != "" { 58 | v.SetConfigName("config." + env) 59 | } 60 | 61 | v.SetConfigType("yaml") 62 | v.SetEnvPrefix("restful") 63 | v.AutomaticEnv() 64 | v.SetDefault("error_file", "config/errors.yaml") 65 | v.SetDefault("server_port", 8081) 66 | v.SetDefault("jwt_signing_method", "HS256") 67 | v.AddConfigPath(configPath) 68 | 69 | if err := v.ReadInConfig(); err != nil { 70 | return fmt.Errorf("Failed to read the configuration file: %s", err) 71 | } 72 | 73 | if err := v.Unmarshal(&Config); err != nil { 74 | return err 75 | } 76 | 77 | return Config.Validate() 78 | } 79 | -------------------------------------------------------------------------------- /app/init.go: -------------------------------------------------------------------------------- 1 | package app 2 | 3 | import ( 4 | "database/sql" 5 | "fmt" 6 | "net/http" 7 | "time" 8 | 9 | "github.com/Proofsuite/amp-matching-engine/errors" 10 | "github.com/Sirupsen/logrus" 11 | "github.com/go-ozzo/ozzo-routing" 12 | "github.com/go-ozzo/ozzo-routing/access" 13 | "github.com/go-ozzo/ozzo-routing/fault" 14 | "github.com/go-ozzo/ozzo-validation" 15 | ) 16 | 17 | // Init returns a middleware that prepares the request context and processing environment. 18 | // The middleware will populate RequestContext, handle possible panics and errors from the processing 19 | // handlers, and add an access log entry. 20 | func Init(logger *logrus.Logger) routing.Handler { 21 | return func(rc *routing.Context) error { 22 | now := time.Now() 23 | 24 | rc.Response = &access.LogResponseWriter{rc.Response, http.StatusOK, 0} 25 | 26 | ac := newRequestScope(now, logger, rc.Request) 27 | rc.Set("Context", ac) 28 | 29 | fault.Recovery(ac.Errorf, convertError)(rc) 30 | logAccess(rc, ac.Infof, ac.Now()) 31 | 32 | return nil 33 | } 34 | } 35 | 36 | // GetRequestScope returns the RequestScope of the current request. 37 | func GetRequestScope(c *routing.Context) RequestScope { 38 | return c.Get("Context").(RequestScope) 39 | } 40 | 41 | // logAccess logs a message describing the current request. 42 | func logAccess(c *routing.Context, logFunc access.LogFunc, start time.Time) { 43 | rw := c.Response.(*access.LogResponseWriter) 44 | elapsed := float64(time.Now().Sub(start).Nanoseconds()) / 1e6 45 | requestLine := fmt.Sprintf("%s %s %s", c.Request.Method, c.Request.URL.Path, c.Request.Proto) 46 | logFunc(`[%.3fms] %s %d %d`, elapsed, requestLine, rw.Status, rw.BytesWritten) 47 | } 48 | 49 | // convertError converts an error into an APIError so that it can be properly sent to the response. 50 | // You may need to customize this method by adding conversion logic for more error types. 51 | func convertError(c *routing.Context, err error) error { 52 | if err == sql.ErrNoRows { 53 | return errors.NotFound("the requested resource") 54 | } 55 | switch err.(type) { 56 | case *errors.APIError: 57 | return err 58 | case validation.Errors: 59 | return errors.InvalidData(err.(validation.Errors)) 60 | case routing.HTTPError: 61 | switch err.(routing.HTTPError).StatusCode() { 62 | case http.StatusUnauthorized: 63 | return errors.Unauthorized(err.Error()) 64 | case http.StatusNotFound: 65 | return errors.NotFound("the requested resource") 66 | } 67 | } 68 | return errors.InternalServerError(err) 69 | } 70 | -------------------------------------------------------------------------------- /app/logger.go: -------------------------------------------------------------------------------- 1 | package app 2 | 3 | import "github.com/Sirupsen/logrus" 4 | 5 | // Logger defines the logger interface that is exposed via RequestScope. 6 | type Logger interface { 7 | // adds a field that should be added to every message being logged 8 | SetField(name, value string) 9 | 10 | Debugf(format string, args ...interface{}) 11 | Infof(format string, args ...interface{}) 12 | Warnf(format string, args ...interface{}) 13 | Errorf(format string, args ...interface{}) 14 | Debug(args ...interface{}) 15 | Info(args ...interface{}) 16 | Warn(args ...interface{}) 17 | Error(args ...interface{}) 18 | } 19 | 20 | // logger wraps logrus.Logger so that it can log messages sharing a common set of fields. 21 | type logger struct { 22 | logger *logrus.Logger 23 | fields logrus.Fields 24 | } 25 | 26 | // NewLogger creates a logger object with the specified logrus.Logger and the fields that should be added to every message. 27 | func NewLogger(l *logrus.Logger, fields logrus.Fields) Logger { 28 | return &logger{ 29 | logger: l, 30 | fields: fields, 31 | } 32 | } 33 | 34 | func (l *logger) SetField(name, value string) { 35 | l.fields[name] = value 36 | } 37 | 38 | func (l *logger) Debugf(format string, args ...interface{}) { 39 | l.tagged().Debugf(format, args...) 40 | } 41 | 42 | func (l *logger) Infof(format string, args ...interface{}) { 43 | l.tagged().Infof(format, args...) 44 | } 45 | 46 | func (l *logger) Warnf(format string, args ...interface{}) { 47 | l.tagged().Warnf(format, args...) 48 | } 49 | 50 | func (l *logger) Errorf(format string, args ...interface{}) { 51 | l.tagged().Errorf(format, args...) 52 | } 53 | 54 | func (l *logger) Debug(args ...interface{}) { 55 | l.tagged().Debug(args...) 56 | } 57 | 58 | func (l *logger) Info(args ...interface{}) { 59 | l.tagged().Info(args...) 60 | } 61 | 62 | func (l *logger) Warn(args ...interface{}) { 63 | l.tagged().Warn(args...) 64 | } 65 | 66 | func (l *logger) Error(args ...interface{}) { 67 | l.tagged().Error(args...) 68 | } 69 | 70 | func (l *logger) tagged() *logrus.Entry { 71 | return l.logger.WithFields(l.fields) 72 | } 73 | -------------------------------------------------------------------------------- /app/scope.go: -------------------------------------------------------------------------------- 1 | package app 2 | 3 | import ( 4 | "net/http" 5 | "time" 6 | 7 | "github.com/Sirupsen/logrus" 8 | "github.com/go-ozzo/ozzo-dbx" 9 | ) 10 | 11 | // RequestScope contains the application-specific information that are carried around in a request. 12 | type RequestScope interface { 13 | Logger 14 | // UserID returns the ID of the user for the current request 15 | UserID() string 16 | // SetUserID sets the ID of the currently authenticated user 17 | SetUserID(id string) 18 | // RequestID returns the ID of the current request 19 | RequestID() string 20 | // Tx returns the currently active database transaction that can be used for DB query purpose 21 | Tx() *dbx.Tx 22 | // SetTx sets the database transaction 23 | SetTx(tx *dbx.Tx) 24 | // Now returns the timestamp representing the time when the request is being processed 25 | Now() time.Time 26 | } 27 | 28 | type requestScope struct { 29 | Logger // the logger tagged with the current request information 30 | now time.Time // the time when the request is being processed 31 | requestID string // an ID identifying one or multiple correlated HTTP requests 32 | userID string // an ID identifying the current user 33 | tx *dbx.Tx // the currently active transaction 34 | } 35 | 36 | func (rs *requestScope) UserID() string { 37 | return rs.userID 38 | } 39 | 40 | func (rs *requestScope) SetUserID(id string) { 41 | rs.Logger.SetField("UserID", id) 42 | rs.userID = id 43 | } 44 | 45 | func (rs *requestScope) RequestID() string { 46 | return rs.requestID 47 | } 48 | 49 | func (rs *requestScope) Tx() *dbx.Tx { 50 | return rs.tx 51 | } 52 | 53 | func (rs *requestScope) SetTx(tx *dbx.Tx) { 54 | rs.tx = tx 55 | } 56 | 57 | func (rs *requestScope) Now() time.Time { 58 | return rs.now 59 | } 60 | 61 | // newRequestScope creates a new RequestScope with the current request information. 62 | func newRequestScope(now time.Time, logger *logrus.Logger, request *http.Request) RequestScope { 63 | l := NewLogger(logger, logrus.Fields{}) 64 | requestID := request.Header.Get("X-Request-Id") 65 | if requestID != "" { 66 | l.SetField("RequestID", requestID) 67 | } 68 | return &requestScope{ 69 | Logger: l, 70 | now: now, 71 | requestID: requestID, 72 | } 73 | } 74 | -------------------------------------------------------------------------------- /app/version.go: -------------------------------------------------------------------------------- 1 | package app 2 | 3 | // Version specifies the current version of the application. 4 | // The value of this variable is replaced with the latest git tag 5 | // by "make" while building or running the application. 6 | var Version = "1.0" 7 | -------------------------------------------------------------------------------- /cmd/root.go: -------------------------------------------------------------------------------- 1 | package cmd 2 | 3 | import ( 4 | "fmt" 5 | "os" 6 | 7 | "github.com/Proofsuite/amp-matching-engine/app" 8 | "github.com/Proofsuite/amp-matching-engine/errors" 9 | "github.com/spf13/cobra" 10 | ) 11 | 12 | var cfgDir string 13 | var env string 14 | 15 | // rootCmd represents the base command when called without any subcommands 16 | var rootCmd = &cobra.Command{} 17 | 18 | // Execute adds all child commands to the root command and sets flags appropriately. 19 | // This is called by main.main(). It only needs to happen once to the rootCmd. 20 | func Execute() { 21 | if err := rootCmd.Execute(); err != nil { 22 | fmt.Println(err) 23 | os.Exit(1) 24 | } 25 | } 26 | 27 | func init() { 28 | // Here you will define your flags and configuration settings. 29 | // Cobra supports persistent flags, which, if defined here, 30 | // will be global for your application. 31 | rootCmd.PersistentFlags().StringVar(&cfgDir, "configDir", "./config", "config directory (default is $PROJECT_PATH/config)") 32 | rootCmd.PersistentFlags().StringVar(&env, "env", "", "Environment to use for deployment(default is '')") 33 | 34 | cobra.OnInitialize(initConfig) 35 | 36 | } 37 | 38 | func initConfig() { 39 | if err := app.LoadConfig(cfgDir, env); err != nil { 40 | panic(err) 41 | } 42 | 43 | if err := errors.LoadMessages(app.Config.ErrorFile); err != nil { 44 | panic(err) 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /cmd/serve.go: -------------------------------------------------------------------------------- 1 | package cmd 2 | 3 | import ( 4 | "fmt" 5 | "net/http" 6 | 7 | "github.com/Proofsuite/amp-matching-engine/app" 8 | "github.com/Proofsuite/amp-matching-engine/contracts" 9 | "github.com/Proofsuite/amp-matching-engine/crons" 10 | "github.com/Proofsuite/amp-matching-engine/daos" 11 | "github.com/Proofsuite/amp-matching-engine/endpoints" 12 | "github.com/Proofsuite/amp-matching-engine/ethereum" 13 | "github.com/Proofsuite/amp-matching-engine/operator" 14 | "github.com/Proofsuite/amp-matching-engine/rabbitmq" 15 | "github.com/Proofsuite/amp-matching-engine/redis" 16 | "github.com/Proofsuite/amp-matching-engine/services" 17 | "github.com/Proofsuite/amp-matching-engine/ws" 18 | "github.com/Proofsuite/go-ethereum/log" 19 | "github.com/ethereum/go-ethereum/common" 20 | "github.com/gorilla/mux" 21 | "github.com/spf13/cobra" 22 | 23 | "github.com/Proofsuite/amp-matching-engine/engine" 24 | ) 25 | 26 | // serveCmd represents the serve command 27 | var serveCmd = &cobra.Command{ 28 | Use: "serve", 29 | Short: "Get application up and running", 30 | Long: `Get application up and running`, 31 | Run: run, 32 | } 33 | 34 | func init() { 35 | rootCmd.AddCommand(serveCmd) 36 | } 37 | 38 | func run(cmd *cobra.Command, args []string) { 39 | // connect to the database 40 | _, err := daos.InitSession(nil) 41 | if err != nil { 42 | panic(err) 43 | } 44 | 45 | rabbitConn := rabbitmq.InitConnection(app.Config.Rabbitmq) 46 | redisConn := redis.NewRedisConnection(app.Config.Redis) 47 | provider := ethereum.NewWebsocketProvider() 48 | 49 | router := NewRouter(provider, redisConn, rabbitConn) 50 | http.Handle("/", router) 51 | http.HandleFunc("/socket", ws.ConnectionEndpoint) 52 | 53 | // start the server 54 | address := fmt.Sprintf(":%v", app.Config.ServerPort) 55 | log.Info("server %v is started at %v\n", app.Version, address) 56 | panic(http.ListenAndServe(address, nil)) 57 | } 58 | 59 | func NewRouter( 60 | provider *ethereum.EthereumProvider, 61 | redisConn *redis.RedisConnection, 62 | rabbitConn *rabbitmq.Connection, 63 | ) *mux.Router { 64 | 65 | r := mux.NewRouter() 66 | 67 | // get daos for dependency injection 68 | orderDao := daos.NewOrderDao() 69 | tokenDao := daos.NewTokenDao() 70 | pairDao := daos.NewPairDao() 71 | tradeDao := daos.NewTradeDao() 72 | accountDao := daos.NewAccountDao() 73 | walletDao := daos.NewWalletDao() 74 | 75 | // instantiate engine 76 | eng := engine.NewEngine(redisConn, rabbitConn, pairDao) 77 | 78 | // get services for injection 79 | accountService := services.NewAccountService(accountDao, tokenDao) 80 | ohlcvService := services.NewOHLCVService(tradeDao) 81 | tokenService := services.NewTokenService(tokenDao) 82 | tradeService := services.NewTradeService(tradeDao) 83 | pairService := services.NewPairService(pairDao, tokenDao, eng, tradeService) 84 | orderService := services.NewOrderService(orderDao, pairDao, accountDao, tradeDao, eng, provider, rabbitConn) 85 | orderBookService := services.NewOrderBookService(pairDao, tokenDao, orderDao, eng) 86 | walletService := services.NewWalletService(walletDao) 87 | cronService := crons.NewCronService(ohlcvService) 88 | 89 | // get exchange contract instance 90 | exchangeAddress := common.HexToAddress(app.Config.Ethereum["exchange_address"]) 91 | exchange, err := contracts.NewExchange( 92 | walletService, 93 | exchangeAddress, 94 | provider.Client, 95 | ) 96 | 97 | if err != nil { 98 | panic(err) 99 | } 100 | 101 | // deploy operator 102 | op, err := operator.NewOperator( 103 | walletService, 104 | tradeService, 105 | orderService, 106 | provider, 107 | exchange, 108 | rabbitConn, 109 | ) 110 | 111 | if err != nil { 112 | panic(err) 113 | } 114 | 115 | // deploy http and ws endpoints 116 | endpoints.ServeAccountResource(r, accountService) 117 | endpoints.ServeTokenResource(r, tokenService) 118 | endpoints.ServePairResource(r, pairService) 119 | endpoints.ServeOrderBookResource(r, orderBookService) 120 | endpoints.ServeOHLCVResource(r, ohlcvService) 121 | endpoints.ServeTradeResource(r, tradeService) 122 | endpoints.ServeOrderResource(r, orderService, eng) 123 | 124 | //initialize rabbitmq subscriptions 125 | rabbitConn.SubscribeOrders(eng.HandleOrders) 126 | rabbitConn.SubscribeTrades(op.HandleTrades) 127 | rabbitConn.SubscribeOperator(orderService.HandleOperatorMessages) 128 | rabbitConn.SubscribeEngineResponses(orderService.HandleEngineResponse) 129 | 130 | cronService.InitCrons() 131 | return r 132 | } 133 | -------------------------------------------------------------------------------- /config/config.docker.yaml: -------------------------------------------------------------------------------- 1 | # The Data Source Name for the database 2 | # Make sure you override this in production with the environment variable: RESTFUL_DSN 3 | dsn: "mongodb://mongodb:27017/" 4 | db_name: proofdex 5 | 6 | rabbitmq: amqp://guest:guest@rabbitmq:5672/ 7 | redis: redis://redis:6379 8 | 9 | ethereum: 10 | http_url: http://localhost:8545 11 | ws_url: ws://localhost:8546 12 | exchange_address: "0xfc074fd5702e6becb78d64acd4126a0079f42d85" 13 | weth_address: "0x2EB24432177e82907dE24b7c5a6E0a5c03226135" 14 | fee_account: "0xe8e84ee367bc63ddb38d3d01bccef106c194dc47" 15 | decimal: 8 16 | 17 | logs: 18 | main: './main.log' 19 | engine: './engine.log' 20 | operator: './operator.log' 21 | 22 | tick_duration: 23 | sec: [5, 30] 24 | min: [1, 5, 15] 25 | hour: [1] 26 | day: [1] 27 | week: [1] 28 | month: [1, 3, 6, 9] 29 | year: [1] 30 | 31 | # These are secret keys used for JWT signing and verification. 32 | # Make sure you override these keys in production by the following environment variables: 33 | # RESTFUL_JWT_VERIFICATION_KEY 34 | # RESTFUL_JWT_SIGNING_KEY 35 | jwt_verification_key: "QfCAH04Cob7b71QCqy738vw5XGSnFZ9d" 36 | jwt_signing_key: "QfCAH04Cob7b71QCqy738vw5XGSnFZ9d" 37 | # Uncomment the following line and set an appropriate JWT signing method, if needed 38 | # The default signing method is HS256. 39 | #jwt_signing_method: "HS256" -------------------------------------------------------------------------------- /config/config.prod.yaml: -------------------------------------------------------------------------------- 1 | # The Data Source Name for the database 2 | # Make sure you override this in production with the environment variable: RESTFUL_DSN 3 | dsn: "127.0.0.1" 4 | 5 | db_name: proofdex 6 | 7 | rabbitmq: amqp://guest:guest@localhost:5672/ 8 | redis: redis://localhost:6379 9 | 10 | ethereum: 11 | http_url: http://localhost:8545 12 | ws_url: ws://localhost:8546 13 | exchange_address: "0xfc074fd5702e6becb78d64acd4126a0079f42d85" 14 | weth_address: "0x2EB24432177e82907dE24b7c5a6E0a5c03226135" 15 | fee_account: "0xe8e84ee367bc63ddb38d3d01bccef106c194dc47" 16 | decimal: 8 17 | 18 | logs: 19 | main: './main.log' 20 | engine: './engine.log' 21 | operator: './operator.log' 22 | 23 | tick_duration: 24 | sec: [5, 30] 25 | min: [1, 5, 15] 26 | hour: [1] 27 | day: [1] 28 | week: [1] 29 | month: [1, 3, 6, 9] 30 | year: [1] 31 | 32 | # These are secret keys used for JWT signing and verification. 33 | # Make sure you override these keys in production by the following environment variables: 34 | # RESTFUL_JWT_VERIFICATION_KEY 35 | # RESTFUL_JWT_SIGNING_KEY 36 | jwt_verification_key: "QfCAH04Cob7b71QCqy738vw5XGSnFZ9d" 37 | jwt_signing_key: "QfCAH04Cob7b71QCqy738vw5XGSnFZ9d" 38 | # Uncomment the following line and set an appropriate JWT signing method, if needed 39 | # The default signing method is HS256. 40 | #jwt_signing_method: "HS256" -------------------------------------------------------------------------------- /config/config.test.yaml: -------------------------------------------------------------------------------- 1 | # The Data Source Name for the database 2 | # Make sure you override this in production with the environment variable: RESTFUL_DSN 3 | dsn: "127.0.0.1" 4 | 5 | db_name: proofdex 6 | 7 | rabbitmq: amqp://guest:guest@localhost:5672/ 8 | redis: redis://localhost:6379 9 | 10 | ethereum: 11 | http_url: http://localhost:8545 12 | ws_url: ws://localhost:8546 13 | exchange_address: "0x5d0e9f8d3f66bcb133e1f97aaa44937be5a48920" 14 | weth_address: "0x88facf1096d13a05f30ffe34bedf8477a8582ffd" 15 | fee_account: "0xe8e84ee367bc63ddb38d3d01bccef106c194dc47" 16 | decimal: 8 17 | 18 | logs: 19 | main: '.logs/main.log' 20 | engine: '.logs/engine.log' 21 | operator: '.logs/operator.log' 22 | 23 | tick_duration: 24 | sec: [5, 30] 25 | min: [1, 5, 15] 26 | hour: [1] 27 | day: [1] 28 | week: [1] 29 | month: [1, 3, 6, 9] 30 | year: [1] 31 | 32 | # These are secret keys used for JWT signing and verification. 33 | # Make sure you override these keys in production by the following environment variables: 34 | # RESTFUL_JWT_VERIFICATION_KEY 35 | # RESTFUL_JWT_SIGNING_KEY 36 | jwt_verification_key: "QfCAH04Cob7b71QCqy738vw5XGSnFZ9d" 37 | jwt_signing_key: "QfCAH04Cob7b71QCqy738vw5XGSnFZ9d" 38 | # Uncomment the following line and set an appropriate JWT signing method, if needed 39 | # The default signing method is HS256. 40 | #jwt_signing_method: "HS256" -------------------------------------------------------------------------------- /config/config.yaml: -------------------------------------------------------------------------------- 1 | # The Data Source Name for the database 2 | # Make sure you override this in production with the environment variable: RESTFUL_DSN 3 | dsn: "127.0.0.1" 4 | 5 | db_name: proofdex 6 | 7 | rabbitmq: amqp://guest:guest@localhost:5672/ 8 | redis: redis://localhost:6379 9 | 10 | tick_duration: 11 | sec: [5, 30] 12 | min: [1, 5, 15] 13 | hour: [1] 14 | day: [1] 15 | week: [1] 16 | month: [1, 3, 6, 9] 17 | year: [1] 18 | 19 | logs: 20 | main: './main.log' 21 | engine: './engine.log' 22 | operator: './operator.log' 23 | 24 | ethereum: 25 | http_url: http://localhost:8545 26 | ws_url: ws://localhost:8546 27 | exchange_address: "0xfc074fd5702e6becb78d64acd4126a0079f42d85" 28 | weth_address: "0x2EB24432177e82907dE24b7c5a6E0a5c03226135" 29 | fee_account: "0xe8e84ee367bc63ddb38d3d01bccef106c194dc47" 30 | decimal: 8 31 | 32 | # These are secret keys used for JWT signing and verification. 33 | # Make sure you override these keys in production by the following environment variables: 34 | # RESTFUL_JWT_VERIFICATION_KEY 35 | # RESTFUL_JWT_SIGNING_KEY 36 | jwt_verification_key: "QfCAH04Cob7b71QCqy738vw5XGSnFZ9d" 37 | jwt_signing_key: "QfCAH04Cob7b71QCqy738vw5XGSnFZ9d" 38 | # Uncomment the following line and set an appropriate JWT signing method, if needed 39 | # The default signing method is HS256. 40 | #jwt_signing_method: "HS256" -------------------------------------------------------------------------------- /config/errors.yaml: -------------------------------------------------------------------------------- 1 | INTERNAL_SERVER_ERROR: 2 | message: "We have encountered an internal server error." 3 | developer_message: "Internal server error: {error}" 4 | 5 | NOT_FOUND: 6 | message: "{resource} was not found." 7 | 8 | UNAUTHORIZED: 9 | message: "Authentication failed." 10 | developer_message: "Authentication failed: {error}" 11 | 12 | INVALID_DATA: 13 | message: "There is some problem with the data you submitted. See \"details\" for more information." 14 | -------------------------------------------------------------------------------- /contracts/main.go: -------------------------------------------------------------------------------- 1 | package contracts 2 | 3 | import "github.com/Proofsuite/amp-matching-engine/utils" 4 | 5 | var logger = utils.Logger 6 | -------------------------------------------------------------------------------- /crons/crons.go: -------------------------------------------------------------------------------- 1 | package crons 2 | 3 | import ( 4 | "github.com/Proofsuite/amp-matching-engine/services" 5 | "github.com/robfig/cron" 6 | ) 7 | 8 | // CronService contains the services required to initialize crons 9 | type CronService struct { 10 | ohlcvService *services.OHLCVService 11 | } 12 | 13 | // NewCronService returns a new instance of CronService 14 | func NewCronService(ohlcvService *services.OHLCVService) *CronService { 15 | return &CronService{ohlcvService} 16 | } 17 | 18 | // InitCrons is responsible for initializing all the crons in the system 19 | func (s *CronService) InitCrons() { 20 | c := cron.New() 21 | s.tickStreamingCron(c) 22 | c.Start() 23 | } 24 | -------------------------------------------------------------------------------- /crons/tickStreaming.go: -------------------------------------------------------------------------------- 1 | package crons 2 | 3 | import ( 4 | "fmt" 5 | "log" 6 | 7 | "github.com/Proofsuite/amp-matching-engine/types" 8 | "github.com/Proofsuite/amp-matching-engine/utils" 9 | "github.com/Proofsuite/amp-matching-engine/ws" 10 | 11 | "github.com/Proofsuite/amp-matching-engine/app" 12 | "github.com/robfig/cron" 13 | ) 14 | 15 | // tickStreamingCron takes instance of cron.Cron and adds tickStreaming 16 | // crons according to the durations mentioned in config/app.yaml file 17 | func (s *CronService) tickStreamingCron(c *cron.Cron) { 18 | for unit, durations := range app.Config.TickDuration { 19 | for _, duration := range durations { 20 | schedule := getCronScheduleString(unit, duration) 21 | c.AddFunc(schedule, s.tickStream(unit, duration)) 22 | } 23 | } 24 | } 25 | 26 | // tickStream function fetches latest tick based on unit and duration for each pair 27 | // and broadcasts the tick to the client subscribed to pair's respective channel 28 | func (s *CronService) tickStream(unit string, duration int64) func() { 29 | return func() { 30 | p := make([]types.PairSubDoc, 0) 31 | ticks, err := s.ohlcvService.GetOHLCV(p, duration, unit) 32 | if err != nil { 33 | log.Printf("%s", err) 34 | return 35 | } 36 | 37 | for _, tick := range ticks { 38 | baseTokenAddress := tick.ID.BaseToken 39 | quoteTokenAddress := tick.ID.QuoteToken 40 | id := utils.GetTickChannelID(baseTokenAddress, quoteTokenAddress, unit, duration) 41 | ws.GetOHLCVSocket().BroadcastOHLCV(id, tick) 42 | } 43 | } 44 | } 45 | 46 | // getCronScheduleString converts unit and duration to schedule string used for 47 | // cron addFunc to schedule crons 48 | func getCronScheduleString(unit string, duration int64) string { 49 | switch unit { 50 | 51 | case "sec": 52 | return fmt.Sprintf("*/%d * * * * *", duration) 53 | 54 | case "min": 55 | return fmt.Sprintf("0 */%d * * * *", duration) 56 | 57 | case "hour": 58 | return fmt.Sprintf("0 0 %d * * *", duration) 59 | 60 | case "day": 61 | return fmt.Sprintf("@daily") 62 | 63 | case "week": 64 | return fmt.Sprintf("0 0 0 * * */%d", duration) 65 | 66 | case "month": 67 | return fmt.Sprintf("0 0 0 */%d * *", duration) 68 | 69 | case "year": 70 | return fmt.Sprintf("@yearly") 71 | 72 | default: 73 | panic(fmt.Errorf("Invalid unit please try again")) 74 | } 75 | } 76 | -------------------------------------------------------------------------------- /daos/main.go: -------------------------------------------------------------------------------- 1 | package daos 2 | -------------------------------------------------------------------------------- /daos/pair_test.go: -------------------------------------------------------------------------------- 1 | package daos 2 | 3 | import ( 4 | "io/ioutil" 5 | "math/big" 6 | "testing" 7 | 8 | "github.com/Proofsuite/amp-matching-engine/types" 9 | "github.com/Proofsuite/amp-matching-engine/utils/testutils" 10 | "github.com/ethereum/go-ethereum/common" 11 | "gopkg.in/mgo.v2/bson" 12 | ) 13 | 14 | func init() { 15 | temp, _ := ioutil.TempDir("", "test") 16 | server.SetPath(temp) 17 | 18 | session := server.Session() 19 | db = &Database{session} 20 | } 21 | 22 | func TestPairDao(t *testing.T) { 23 | dao := NewPairDao() 24 | 25 | pair := &types.Pair{ 26 | ID: bson.NewObjectId(), 27 | BaseTokenSymbol: "REQ", 28 | BaseTokenAddress: common.HexToAddress("0xcf7389dc6c63637598402907d5431160ec8972a5"), 29 | QuoteTokenSymbol: "WETH", 30 | QuoteTokenAddress: common.HexToAddress("0x7a9f3cd060ab180f36c17fe6bdf9974f577d77aa"), 31 | Active: true, 32 | MakeFee: big.NewInt(10000), 33 | TakeFee: big.NewInt(10000), 34 | } 35 | 36 | err := dao.Create(pair) 37 | if err != nil { 38 | t.Errorf("Could not create pair object: %+v", err) 39 | } 40 | 41 | all, err := dao.GetAll() 42 | if err != nil { 43 | t.Errorf("Could not get pairs: %+v", err) 44 | } 45 | 46 | testutils.ComparePair(t, pair, &all[0]) 47 | 48 | byID, err := dao.GetByID(pair.ID) 49 | if err != nil { 50 | t.Errorf("Could not get pair by ID: %v", err) 51 | } 52 | 53 | testutils.ComparePair(t, pair, byID) 54 | 55 | byAddress, err := dao.GetByTokenAddress(pair.BaseTokenAddress, pair.QuoteTokenAddress) 56 | if err != nil { 57 | t.Errorf("Could not get pair by address: %v", err) 58 | } 59 | 60 | testutils.ComparePair(t, pair, byAddress) 61 | } 62 | -------------------------------------------------------------------------------- /daos/token.go: -------------------------------------------------------------------------------- 1 | package daos 2 | 3 | import ( 4 | "time" 5 | 6 | "github.com/Proofsuite/amp-matching-engine/app" 7 | "github.com/Proofsuite/amp-matching-engine/types" 8 | "github.com/ethereum/go-ethereum/common" 9 | mgo "gopkg.in/mgo.v2" 10 | "gopkg.in/mgo.v2/bson" 11 | ) 12 | 13 | // TokenDao contains: 14 | // collectionName: MongoDB collection name 15 | // dbName: name of mongodb to interact with 16 | type TokenDao struct { 17 | collectionName string 18 | dbName string 19 | } 20 | 21 | // NewTokenDao returns a new instance of TokenDao. 22 | func NewTokenDao() *TokenDao { 23 | dbName := app.Config.DBName 24 | collection := "tokens" 25 | index := mgo.Index{ 26 | Key: []string{"contractAddress"}, 27 | Unique: true, 28 | } 29 | 30 | err := db.Session.DB(dbName).C(collection).EnsureIndex(index) 31 | if err != nil { 32 | panic(err) 33 | } 34 | return &TokenDao{collection, dbName} 35 | } 36 | 37 | // Create function performs the DB insertion task for token collection 38 | func (dao *TokenDao) Create(token *types.Token) error { 39 | if err := token.Validate(); err != nil { 40 | logger.Error(err) 41 | return err 42 | } 43 | 44 | token.ID = bson.NewObjectId() 45 | token.CreatedAt = time.Now() 46 | token.UpdatedAt = time.Now() 47 | 48 | err := db.Create(dao.dbName, dao.collectionName, token) 49 | if err != nil { 50 | logger.Error(err) 51 | return err 52 | } 53 | 54 | return nil 55 | } 56 | 57 | // GetAll function fetches all the tokens in the token collection of mongodb. 58 | func (dao *TokenDao) GetAll() ([]types.Token, error) { 59 | var response []types.Token 60 | err := db.Get(dao.dbName, dao.collectionName, bson.M{}, 0, 0, &response) 61 | if err != nil { 62 | logger.Error(err) 63 | return nil, err 64 | } 65 | 66 | return response, nil 67 | } 68 | 69 | // GetQuote function fetches all the quote tokens in the token collection of mongodb. 70 | func (dao *TokenDao) GetQuoteTokens() ([]types.Token, error) { 71 | var response []types.Token 72 | err := db.Get(dao.dbName, dao.collectionName, bson.M{"quote": true}, 0, 0, &response) 73 | if err != nil { 74 | logger.Error(err) 75 | return nil, err 76 | } 77 | 78 | return response, nil 79 | } 80 | 81 | // GetBase function fetches all the base tokens in the token collection of mongodb. 82 | func (dao *TokenDao) GetBaseTokens() ([]types.Token, error) { 83 | var response []types.Token 84 | err := db.Get(dao.dbName, dao.collectionName, bson.M{"quote": false}, 0, 0, &response) 85 | if err != nil { 86 | logger.Error(err) 87 | return nil, err 88 | } 89 | 90 | return response, nil 91 | } 92 | 93 | // GetByID function fetches details of a token based on its mongo id 94 | func (dao *TokenDao) GetByID(id bson.ObjectId) (*types.Token, error) { 95 | var response *types.Token 96 | err := db.GetByID(dao.dbName, dao.collectionName, id, &response) 97 | if err != nil { 98 | logger.Error(err) 99 | return nil, err 100 | } 101 | 102 | return response, nil 103 | } 104 | 105 | // GetByAddress function fetches details of a token based on its contract address 106 | func (dao *TokenDao) GetByAddress(addr common.Address) (*types.Token, error) { 107 | q := bson.M{"contractAddress": addr.Hex()} 108 | var resp []types.Token 109 | 110 | err := db.Get(dao.dbName, dao.collectionName, q, 0, 1, &resp) 111 | if err != nil { 112 | logger.Error(err) 113 | return nil, err 114 | } 115 | 116 | if len(resp) == 0 { 117 | return nil, nil 118 | } 119 | 120 | return &resp[0], nil 121 | } 122 | 123 | // Drop drops all the order documents in the current database 124 | func (dao *TokenDao) Drop() error { 125 | err := db.DropCollection(dao.dbName, dao.collectionName) 126 | if err != nil { 127 | logger.Error(err) 128 | return err 129 | } 130 | 131 | return nil 132 | } 133 | -------------------------------------------------------------------------------- /daos/token_test.go: -------------------------------------------------------------------------------- 1 | package daos 2 | 3 | import ( 4 | "io/ioutil" 5 | "testing" 6 | 7 | "github.com/Proofsuite/amp-matching-engine/types" 8 | "github.com/Proofsuite/amp-matching-engine/utils/testutils" 9 | "github.com/ethereum/go-ethereum/common" 10 | ) 11 | 12 | func init() { 13 | server := testutils.NewDBTestServer() 14 | temp, _ := ioutil.TempDir("", "test") 15 | server.SetPath(temp) 16 | 17 | session := server.Session() 18 | db = &Database{Session: session} 19 | } 20 | 21 | func TestTokenDao(t *testing.T) { 22 | dao := NewTokenDao() 23 | dao.Drop() 24 | 25 | token := &types.Token{ 26 | Name: "PRFT", 27 | Symbol: "PRFT", 28 | ContractAddress: common.HexToAddress("0x6e9a406696617ec5105f9382d33ba3360fcfabcc"), 29 | Decimal: 18, 30 | Active: true, 31 | Quote: true, 32 | } 33 | 34 | err := dao.Create(token) 35 | if err != nil { 36 | t.Errorf("Could not create token object: %+v", err) 37 | } 38 | 39 | all, err := dao.GetAll() 40 | if err != nil { 41 | t.Errorf("Could not get wallets: %+v", err) 42 | } 43 | 44 | testutils.CompareToken(t, token, &all[0]) 45 | 46 | byId, err := dao.GetByID(token.ID) 47 | if err != nil { 48 | t.Errorf("Could not get token by ID: %+v", err) 49 | } 50 | 51 | testutils.CompareToken(t, token, byId) 52 | 53 | byAddress, err := dao.GetByAddress(common.HexToAddress("0x6e9a406696617ec5105f9382d33ba3360fcfabcc")) 54 | if err != nil { 55 | t.Errorf("Could not get token by address: %+v", err) 56 | } 57 | 58 | testutils.CompareToken(t, token, byAddress) 59 | } 60 | -------------------------------------------------------------------------------- /daos/wallet.go: -------------------------------------------------------------------------------- 1 | package daos 2 | 3 | import ( 4 | "github.com/Proofsuite/amp-matching-engine/app" 5 | "github.com/Proofsuite/amp-matching-engine/types" 6 | "github.com/ethereum/go-ethereum/common" 7 | "gopkg.in/mgo.v2/bson" 8 | ) 9 | 10 | // TokenDao contains: 11 | // collectionName: MongoDB collection name 12 | // dbName: name of mongodb to interact with 13 | type WalletDao struct { 14 | collectionName string 15 | dbName string 16 | } 17 | 18 | func NewWalletDao() *WalletDao { 19 | return &WalletDao{"wallets", app.Config.DBName} 20 | } 21 | 22 | func (dao *WalletDao) Create(wallet *types.Wallet) error { 23 | err := wallet.Validate() 24 | if err != nil { 25 | logger.Error(err) 26 | return err 27 | } 28 | 29 | wallet.ID = bson.NewObjectId() 30 | err = db.Create(dao.dbName, dao.collectionName, wallet) 31 | if err != nil { 32 | logger.Error(err) 33 | return err 34 | } 35 | 36 | return nil 37 | } 38 | 39 | func (dao *WalletDao) GetAll() ([]types.Wallet, error) { 40 | var response []types.Wallet 41 | 42 | err := db.Get(dao.dbName, dao.collectionName, bson.M{}, 0, 0, &response) 43 | if err != nil { 44 | logger.Error(err) 45 | return nil, err 46 | } 47 | 48 | return response, nil 49 | } 50 | 51 | // GetByID function fetches details of a token based on its mongo id 52 | func (dao *WalletDao) GetByID(id bson.ObjectId) (*types.Wallet, error) { 53 | var response *types.Wallet 54 | 55 | err := db.GetByID(dao.dbName, dao.collectionName, id, &response) 56 | if err != nil { 57 | logger.Error(err) 58 | return nil, err 59 | } 60 | 61 | return response, nil 62 | } 63 | 64 | // GetByAddress function fetches details of a token based on its contract address 65 | func (dao *WalletDao) GetByAddress(a common.Address) (*types.Wallet, error) { 66 | q := bson.M{"address": a.Hex()} 67 | var resp []types.Wallet 68 | 69 | err := db.Get(dao.dbName, dao.collectionName, q, 0, 1, &resp) 70 | if err != nil { 71 | logger.Error(err) 72 | return nil, err 73 | } 74 | 75 | if len(resp) == 0 { 76 | logger.Info("No wallets found") 77 | return nil, nil 78 | } 79 | 80 | return &resp[0], nil 81 | } 82 | 83 | func (dao *WalletDao) GetDefaultAdminWallet() (*types.Wallet, error) { 84 | q := bson.M{"admin": true} 85 | var resp []types.Wallet 86 | 87 | err := db.Get(dao.dbName, dao.collectionName, q, 0, 1, &resp) 88 | if err != nil { 89 | logger.Error(err) 90 | return nil, err 91 | } 92 | 93 | if len(resp) == 0 { 94 | logger.Info("No default admin wallet") 95 | return nil, nil 96 | } 97 | 98 | return &resp[0], nil 99 | } 100 | 101 | func (dao *WalletDao) GetOperatorWallets() ([]*types.Wallet, error) { 102 | q := bson.M{"operator": true} 103 | res := []*types.Wallet{} 104 | 105 | err := db.Get(dao.dbName, dao.collectionName, q, 0, 1, &res) 106 | if err != nil || len(res) == 0 { 107 | logger.Error(err) 108 | return nil, err 109 | } 110 | 111 | return res, nil 112 | } 113 | -------------------------------------------------------------------------------- /daos/wallet_test.go: -------------------------------------------------------------------------------- 1 | package daos 2 | 3 | import ( 4 | "io/ioutil" 5 | "reflect" 6 | "testing" 7 | 8 | "github.com/Proofsuite/amp-matching-engine/types" 9 | "gopkg.in/mgo.v2/dbtest" 10 | ) 11 | 12 | var server dbtest.DBServer 13 | 14 | func init() { 15 | temp, _ := ioutil.TempDir("", "test") 16 | server.SetPath(temp) 17 | 18 | session := server.Session() 19 | db = &Database{session} 20 | } 21 | 22 | func TestWalletDao(t *testing.T) { 23 | key := "7c78c6e2f65d0d84c44ac0f7b53d6e4dd7a82c35f51b251d387c2a69df712660" 24 | w := types.NewWalletFromPrivateKey(key) 25 | dao := NewWalletDao() 26 | 27 | err := dao.Create(w) 28 | if err != nil { 29 | t.Errorf("Could not create wallet object") 30 | } 31 | 32 | all, err := dao.GetAll() 33 | if err != nil { 34 | t.Errorf("Could not get wallets: %v", err) 35 | } 36 | 37 | if !reflect.DeepEqual(w, &all[0]) { 38 | t.Errorf("Could not retrieve correct wallets:\n Expected: %v\n, Got: %v\n", w, &all[0]) 39 | } 40 | 41 | byId, err := dao.GetByID(w.ID) 42 | if err != nil { 43 | t.Errorf("Could not get wallet by ID: %v", err) 44 | } 45 | 46 | if !reflect.DeepEqual(w, byId) { 47 | t.Errorf("Could not correct walley by ID:\n Expected: %v\n, Got: %v\n", w, byId) 48 | } 49 | 50 | byAddress, err := dao.GetByAddress(w.Address) 51 | if err != nil { 52 | t.Errorf("Could not get wallet by address: %v", err) 53 | } 54 | 55 | if !reflect.DeepEqual(w, byAddress) { 56 | t.Errorf("Could not get correct wallet by address:\n Expected: %v\n, Got: %v\n", w, byAddress) 57 | } 58 | } 59 | 60 | func TestDefaultAdminWallet(t *testing.T) { 61 | key := "7c78c6e2f65d0d84c44ac0f7b53d6e4dd7a82c35f51b251d387c2a69df712660" 62 | w := types.NewWalletFromPrivateKey(key) 63 | w.Admin = true 64 | dao := NewWalletDao() 65 | 66 | err := dao.Create(w) 67 | if err != nil { 68 | t.Errorf("Could not create wallet object") 69 | } 70 | 71 | wallet, err := dao.GetDefaultAdminWallet() 72 | if err != nil { 73 | t.Errorf("Could not get default admin wallet") 74 | } 75 | 76 | if !reflect.DeepEqual(w, wallet) { 77 | t.Errorf("Could not get correct admin wallet:\n Expected: %v\n, Got: %v\n", w, wallet) 78 | } 79 | } 80 | -------------------------------------------------------------------------------- /docker-compose.yml: -------------------------------------------------------------------------------- 1 | version: '3' 2 | services: 3 | matching-engine: 4 | container_name: 'matching-engine' 5 | volumes: 6 | - ./logs:/go/src/app/vendor/github.com/Proofsuite/amp-matching-engine/logs 7 | build: 8 | context: . 9 | dockerfile: Dockerfile 10 | ports: 11 | - '8081:8081' 12 | links: 13 | - redis 14 | - rabbitmq 15 | - mongodb 16 | depends_on: 17 | - redis 18 | - rabbitmq 19 | - mongodb 20 | 21 | rabbitmq: 22 | container_name: 'rabbitmq' 23 | image: rabbitmq 24 | ports: 25 | - '5672:5672' 26 | 27 | redis: 28 | container_name: 'redis' 29 | image: redis 30 | ports: 31 | - '6379:6379' 32 | 33 | mongodb: 34 | image: mongo:latest 35 | container_name: 'mongodb' 36 | ports: 37 | - '27017:27017' 38 | 39 | mongodb-seed: 40 | image: mongo:latest 41 | container_name: 'mongodb-seed' 42 | links: 43 | - mongodb 44 | volumes: 45 | - ./utils/seed-data:/utils/seed-data 46 | depends_on: 47 | - mongodb 48 | command: 49 | - /bin/sh 50 | - -c 51 | - | 52 | mongo proofdex --eval "db.dropDatabase()" 53 | mongoimport --host mongodb --db proofdex --type json --file utils/seed-data/tokens.json 54 | mongoimport --host mongodb --db proofdex --type json --file utils/seed-data/pairs.json 55 | mongoimport --host mongodb --db proofdex --type json --file utils/seed-data/accounts.json 56 | 57 | # USEFUL COMMANDS TO BE REINCLUDED LATER 58 | 59 | # environment: 60 | # - MONGO_DATA_DIR=/data/db 61 | # - MONGO_LOG_DIR=/dev/null 62 | 63 | # command: mongod --smallfiles /--logpath=/dev/null 64 | -------------------------------------------------------------------------------- /dump.rdb: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/agiletechvn/orderbook-matching-engine/3b178d5e70dc6f07d862944d4c74edc7befcf756/dump.rdb -------------------------------------------------------------------------------- /e2e/account.go: -------------------------------------------------------------------------------- 1 | package e2e 2 | 3 | // func testAccount(t *testing.T, tokens []types.Token) map[*ecdsa.PrivateKey]types.Account { 4 | // fmt.Printf("\n=== Starting Account test ===\n") 5 | // router := NewRouter() 6 | // response := make(map[*ecdsa.PrivateKey]types.Account) 7 | 8 | // pk1, _ := crypto.GenerateKey() 9 | // pk2, _ := crypto.GenerateKey() 10 | 11 | // account1 := types.Account{ 12 | // Address: crypto.PubkeyToAddress(pk1.PublicKey), 13 | // IsBlocked: false, 14 | // TokenBalances: make(map[common.Address]*types.TokenBalance), 15 | // } 16 | 17 | // account2 := types.Account{ 18 | // Address: crypto.PubkeyToAddress(pk2.PublicKey), 19 | // IsBlocked: false, 20 | // TokenBalances: make(map[common.Address]*types.TokenBalance), 21 | // } 22 | 23 | // initBalance := big.NewInt(10000000000000000) 24 | 25 | // for _, token := range tokens { 26 | // account1.TokenBalances[token.ContractAddress] = &types.TokenBalance{ 27 | // Address: token.ContractAddress, 28 | // Symbol: token.Symbol, 29 | // Balance: initBalance, 30 | // Allowance: initBalance, 31 | // LockedBalance: big.NewInt(0), 32 | // } 33 | // account2.TokenBalances[token.ContractAddress] = &types.TokenBalance{ 34 | // Address: token.ContractAddress, 35 | // Symbol: token.Symbol, 36 | // Balance: initBalance, 37 | // Allowance: initBalance, 38 | // LockedBalance: big.NewInt(0), 39 | // } 40 | // } 41 | // // create account test 42 | // res := testAPI(router, "POST", "/account", "{\"address\":\""+account1.Address.Hex()+"\"}") 43 | // assert.Equal(t, http.StatusOK, res.Code, "t1 - create account") 44 | 45 | // var resp types.Account 46 | // if err := json.Unmarshal(res.Body.Bytes(), &resp); err != nil { 47 | // fmt.Printf("%v", err) 48 | // } 49 | // if compareAccount(t, resp, account1) { 50 | // fmt.Println("PASS 't1 - create account'") 51 | // account1 = resp 52 | // response[pk1] = account1 53 | // } else { 54 | // fmt.Println("FAIL 't1 - create account'") 55 | // } 56 | // // Duplicate account test 57 | // res1 := testAPI(router, "POST", "/account", "{\"address\":\""+account1.Address.Hex()+"\"}") 58 | 59 | // if err := json.Unmarshal(res1.Body.Bytes(), &resp); err != nil { 60 | // fmt.Printf("%v", err) 61 | // } 62 | 63 | // if assert.Equal(t, account1.ID.Hex(), resp.ID.Hex(), "t2 - create duplicate account") { 64 | // fmt.Println("PASS 't2 - create duplicate account'") 65 | // } else { 66 | // fmt.Println("FAIL 't2 - create duplicate account'") 67 | // } 68 | 69 | // // create 2nd account test 70 | // res = testAPI(router, "POST", "/account", "{\"address\":\""+account2.Address.Hex()+"\"}") 71 | // assert.Equal(t, http.StatusOK, res.Code, "t3 - create 2nd account") 72 | 73 | // if err := json.Unmarshal(res.Body.Bytes(), &resp); err != nil { 74 | // fmt.Printf("%v", err) 75 | // } 76 | // if compareAccount(t, resp, account2) { 77 | // fmt.Println("PASS 't3 - create 2nd account'") 78 | // response[pk2] = account2 79 | // } else { 80 | // fmt.Println("FAIL 't3 - create 2nd account'") 81 | // } 82 | 83 | // // Get Account 84 | // res2 := testAPI(router, "GET", "/account/"+account1.Address.Hex(), "") 85 | 86 | // assert.Equal(t, http.StatusOK, res2.Code, "t4 - fetch account") 87 | // if err := json.Unmarshal(res2.Body.Bytes(), &resp); err != nil { 88 | // fmt.Printf("%v", err) 89 | // } 90 | // if compareAccount(t, resp, account1) { 91 | // fmt.Println("PASS 't4 - create account'") 92 | // } else { 93 | // fmt.Println("FAIL 't4 - create account'") 94 | // } 95 | // return response 96 | // } 97 | 98 | // func compareAccount(t *testing.T, actual, expected types.Account, msgs ...string) bool { 99 | // for _, msg := range msgs { 100 | // fmt.Println(msg) 101 | // } 102 | // response := true 103 | // response = response && assert.Equalf(t, actual.Address, expected.Address, fmt.Sprintf("Address doesn't match. Expected: %v , Got: %v", expected.Address, actual.Address)) 104 | // response = response && assert.Equalf(t, actual.IsBlocked, expected.IsBlocked, fmt.Sprintf("Address IsBlocked doesn't match. Expected: %v , Got: %v", expected.IsBlocked, actual.IsBlocked)) 105 | // response = response && assert.Equalf(t, actual.TokenBalances, expected.TokenBalances, fmt.Sprintf("Balance Tokens doesn't match. Expected: %v , Got: %v", expected.TokenBalances, actual.TokenBalances)) 106 | 107 | // return response 108 | // } 109 | -------------------------------------------------------------------------------- /endpoints/accounts.go: -------------------------------------------------------------------------------- 1 | package endpoints 2 | 3 | import ( 4 | "encoding/json" 5 | "net/http" 6 | 7 | "github.com/Proofsuite/amp-matching-engine/interfaces" 8 | "github.com/Proofsuite/amp-matching-engine/types" 9 | "github.com/Proofsuite/amp-matching-engine/utils/httputils" 10 | "github.com/ethereum/go-ethereum/common" 11 | "github.com/gorilla/mux" 12 | ) 13 | 14 | type accountEndpoint struct { 15 | accountService interfaces.AccountService 16 | } 17 | 18 | func ServeAccountResource( 19 | r *mux.Router, 20 | accountService interfaces.AccountService, 21 | ) { 22 | 23 | e := &accountEndpoint{accountService} 24 | r.HandleFunc("/account", e.handleCreateAccount).Methods("POST") 25 | r.HandleFunc("/account/
", e.handleGetAccount).Methods("GET") 26 | r.HandleFunc("/account/{address}/{token}", e.handleGetAccountTokenBalance).Methods("GET") 27 | } 28 | 29 | func (e *accountEndpoint) handleCreateAccount(w http.ResponseWriter, r *http.Request) { 30 | a := &types.Account{} 31 | decoder := json.NewDecoder(r.Body) 32 | 33 | err := decoder.Decode(a) 34 | if err != nil { 35 | logger.Error(err) 36 | httputils.WriteError(w, http.StatusBadRequest, "Invalid payload") 37 | return 38 | } 39 | 40 | defer r.Body.Close() 41 | 42 | err = a.Validate() 43 | if err != nil { 44 | logger.Error(err) 45 | httputils.WriteError(w, http.StatusBadRequest, "Invalid payload") 46 | return 47 | } 48 | 49 | err = e.accountService.Create(a) 50 | if err != nil { 51 | logger.Error(err) 52 | httputils.WriteError(w, http.StatusInternalServerError, "") 53 | return 54 | } 55 | 56 | httputils.WriteJSON(w, http.StatusCreated, a) 57 | } 58 | 59 | func (e *accountEndpoint) handleGetAccount(w http.ResponseWriter, r *http.Request) { 60 | vars := mux.Vars(r) 61 | 62 | addr := vars["address"] 63 | if !common.IsHexAddress(addr) { 64 | httputils.WriteError(w, http.StatusBadRequest, "Invalid Address") 65 | return 66 | } 67 | 68 | address := common.HexToAddress(addr) 69 | a, err := e.accountService.GetByAddress(address) 70 | if err != nil { 71 | logger.Error(err) 72 | httputils.WriteError(w, http.StatusInternalServerError, "") 73 | return 74 | } 75 | 76 | httputils.WriteJSON(w, http.StatusOK, a) 77 | } 78 | 79 | func (e *accountEndpoint) handleGetAccountTokenBalance(w http.ResponseWriter, r *http.Request) { 80 | vars := mux.Vars(r) 81 | 82 | a := vars["address"] 83 | if !common.IsHexAddress(a) { 84 | httputils.WriteError(w, http.StatusBadRequest, "Invalid Address") 85 | } 86 | 87 | t := vars["token"] 88 | if !common.IsHexAddress(a) { 89 | httputils.WriteError(w, http.StatusBadRequest, "Invalid Token Address") 90 | } 91 | 92 | addr := common.HexToAddress(a) 93 | tokenAddr := common.HexToAddress(t) 94 | 95 | b, err := e.accountService.GetTokenBalance(addr, tokenAddr) 96 | if err != nil { 97 | logger.Error(err) 98 | httputils.WriteError(w, http.StatusInternalServerError, "") 99 | } 100 | 101 | httputils.WriteJSON(w, http.StatusOK, b) 102 | } 103 | -------------------------------------------------------------------------------- /endpoints/main.go: -------------------------------------------------------------------------------- 1 | package endpoints 2 | 3 | import "github.com/Proofsuite/amp-matching-engine/utils" 4 | 5 | var logger = utils.Logger 6 | -------------------------------------------------------------------------------- /endpoints/ohlcv.go: -------------------------------------------------------------------------------- 1 | package endpoints 2 | 3 | import ( 4 | "encoding/json" 5 | "net/http" 6 | "time" 7 | 8 | "github.com/Proofsuite/amp-matching-engine/interfaces" 9 | "github.com/Proofsuite/amp-matching-engine/types" 10 | "github.com/Proofsuite/amp-matching-engine/utils/httputils" 11 | "github.com/Proofsuite/amp-matching-engine/ws" 12 | "github.com/ethereum/go-ethereum/common" 13 | "github.com/gorilla/mux" 14 | ) 15 | 16 | type OHLCVEndpoint struct { 17 | ohlcvService interfaces.OHLCVService 18 | } 19 | 20 | func ServeOHLCVResource( 21 | r *mux.Router, 22 | ohlcvService interfaces.OHLCVService, 23 | ) { 24 | e := &OHLCVEndpoint{ohlcvService} 25 | r.HandleFunc("/ohlcv", e.handleGetOHLCV).Methods("POST") 26 | ws.RegisterChannel(ws.OHLCVChannel, e.ohlcvWebSocket) 27 | } 28 | 29 | func (e *OHLCVEndpoint) handleGetOHLCV(w http.ResponseWriter, r *http.Request) { 30 | var model types.TickRequest 31 | 32 | decoder := json.NewDecoder(r.Body) 33 | err := decoder.Decode(model) 34 | if err != nil { 35 | logger.Error(err) 36 | httputils.WriteError(w, http.StatusBadRequest, "Invalid payload") 37 | return 38 | } 39 | 40 | if model.Units == "" { 41 | model.Units = "hour" 42 | } 43 | 44 | if model.Duration == 0 { 45 | model.Duration = 24 46 | } 47 | 48 | if model.To == 0 { 49 | model.To = time.Now().Unix() 50 | } 51 | 52 | res, err := e.ohlcvService.GetOHLCV(model.Pair, model.Duration, model.Units, model.From, model.To) 53 | if err != nil { 54 | logger.Error(err) 55 | httputils.WriteError(w, http.StatusInternalServerError, "") 56 | return 57 | } 58 | 59 | httputils.WriteJSON(w, http.StatusOK, res) 60 | } 61 | 62 | func (e *OHLCVEndpoint) ohlcvWebSocket(input interface{}, conn *ws.Conn) { 63 | startTs := time.Date(1970, time.January, 1, 0, 0, 0, 0, time.UTC) 64 | 65 | mab, _ := json.Marshal(input) 66 | var payload *types.WebSocketPayload 67 | 68 | err := json.Unmarshal(mab, &payload) 69 | if err != nil { 70 | logger.Error(err) 71 | } 72 | 73 | socket := ws.GetOHLCVSocket() 74 | 75 | if payload.Type != "subscription" { 76 | socket.SendErrorMessage(conn, "Invalid payload") 77 | return 78 | } 79 | 80 | dab, _ := json.Marshal(payload.Data) 81 | var msg *types.WebSocketSubscription 82 | 83 | err = json.Unmarshal(dab, &msg) 84 | if err != nil { 85 | logger.Error(err) 86 | } 87 | 88 | if (msg.Pair.BaseToken == common.Address{}) { 89 | socket.SendErrorMessage(conn, "Invalid base token") 90 | return 91 | } 92 | 93 | if (msg.Pair.QuoteToken == common.Address{}) { 94 | socket.SendErrorMessage(conn, "Invalid Quote Token") 95 | return 96 | } 97 | 98 | if msg.Params.From == 0 { 99 | msg.Params.From = startTs.Unix() 100 | } 101 | 102 | if msg.Params.To == 0 { 103 | msg.Params.To = time.Now().Unix() 104 | } 105 | 106 | if msg.Params.Duration == 0 { 107 | msg.Params.Duration = 24 108 | } 109 | 110 | if msg.Params.Units == "" { 111 | msg.Params.Units = "hour" 112 | } 113 | 114 | if msg.Event == types.SUBSCRIBE { 115 | e.ohlcvService.Subscribe(conn, msg.Pair.BaseToken, msg.Pair.QuoteToken, &msg.Params) 116 | } 117 | 118 | if msg.Event == types.UNSUBSCRIBE { 119 | e.ohlcvService.Unsubscribe(conn, msg.Pair.BaseToken, msg.Pair.QuoteToken, &msg.Params) 120 | } 121 | } 122 | -------------------------------------------------------------------------------- /endpoints/pair.go: -------------------------------------------------------------------------------- 1 | package endpoints 2 | 3 | import ( 4 | "encoding/json" 5 | "net/http" 6 | 7 | "github.com/ethereum/go-ethereum/common" 8 | 9 | "github.com/Proofsuite/amp-matching-engine/interfaces" 10 | "github.com/Proofsuite/amp-matching-engine/services" 11 | "github.com/Proofsuite/amp-matching-engine/types" 12 | "github.com/Proofsuite/amp-matching-engine/utils/httputils" 13 | "github.com/gorilla/mux" 14 | ) 15 | 16 | type pairEndpoint struct { 17 | pairService interfaces.PairService 18 | } 19 | 20 | // ServePairResource sets up the routing of pair endpoints and the corresponding handlers. 21 | func ServePairResource( 22 | r *mux.Router, 23 | p interfaces.PairService, 24 | ) { 25 | e := &pairEndpoint{p} 26 | r.HandleFunc("/pairs", e.HandleCreatePair).Methods("POST") 27 | r.HandleFunc("/pairs/{baseToken}/{quoteToken}", e.HandleGetPair).Methods("GET") 28 | r.HandleFunc("/pairs", e.HandleGetAllPairs).Methods("GET") 29 | } 30 | 31 | func (e *pairEndpoint) HandleCreatePair(w http.ResponseWriter, r *http.Request) { 32 | p := &types.Pair{} 33 | 34 | decoder := json.NewDecoder(r.Body) 35 | err := decoder.Decode(p) 36 | if err != nil { 37 | httputils.WriteError(w, http.StatusBadRequest, "Invalid payload") 38 | return 39 | } 40 | 41 | defer r.Body.Close() 42 | 43 | err = p.Validate() 44 | if err != nil { 45 | httputils.WriteError(w, http.StatusBadRequest, err.Error()) 46 | return 47 | } 48 | 49 | err = e.pairService.Create(p) 50 | if err != nil { 51 | switch err { 52 | case services.ErrPairExists: 53 | httputils.WriteError(w, http.StatusBadRequest, "Pair exists") 54 | return 55 | case services.ErrBaseTokenNotFound: 56 | httputils.WriteError(w, http.StatusBadRequest, "Base token not found") 57 | return 58 | case services.ErrQuoteTokenNotFound: 59 | httputils.WriteError(w, http.StatusBadRequest, "Quote token not found") 60 | return 61 | case services.ErrQuoteTokenInvalid: 62 | httputils.WriteError(w, http.StatusBadRequest, "Quote token invalid (token is not registered as quote") 63 | return 64 | default: 65 | logger.Error(err) 66 | httputils.WriteError(w, http.StatusInternalServerError, "") 67 | return 68 | } 69 | } 70 | 71 | httputils.WriteJSON(w, http.StatusCreated, p) 72 | } 73 | 74 | func (e *pairEndpoint) HandleGetAllPairs(w http.ResponseWriter, r *http.Request) { 75 | res, err := e.pairService.GetAll() 76 | if err != nil { 77 | logger.Error(err) 78 | httputils.WriteError(w, http.StatusInternalServerError, "") 79 | return 80 | } 81 | 82 | httputils.WriteJSON(w, http.StatusOK, res) 83 | } 84 | 85 | func (e *pairEndpoint) HandleGetPair(w http.ResponseWriter, r *http.Request) { 86 | vars := mux.Vars(r) 87 | 88 | baseToken := vars["baseToken"] 89 | quoteToken := vars["quoteToken"] 90 | 91 | if !common.IsHexAddress(baseToken) { 92 | httputils.WriteError(w, http.StatusBadRequest, "Invalid Address") 93 | } 94 | 95 | if !common.IsHexAddress(quoteToken) { 96 | httputils.WriteError(w, http.StatusBadRequest, "Invalid Address") 97 | } 98 | 99 | baseTokenAddress := common.HexToAddress(baseToken) 100 | quoteTokenAddress := common.HexToAddress(quoteToken) 101 | res, err := e.pairService.GetByTokenAddress(baseTokenAddress, quoteTokenAddress) 102 | if err != nil { 103 | logger.Error(err) 104 | httputils.WriteError(w, http.StatusInternalServerError, "") 105 | return 106 | } 107 | 108 | httputils.WriteJSON(w, http.StatusOK, res) 109 | } 110 | -------------------------------------------------------------------------------- /endpoints/token.go: -------------------------------------------------------------------------------- 1 | package endpoints 2 | 3 | import ( 4 | "encoding/json" 5 | "net/http" 6 | 7 | "github.com/Proofsuite/amp-matching-engine/interfaces" 8 | "github.com/Proofsuite/amp-matching-engine/services" 9 | "github.com/Proofsuite/amp-matching-engine/types" 10 | "github.com/Proofsuite/amp-matching-engine/utils/httputils" 11 | "github.com/ethereum/go-ethereum/common" 12 | "github.com/gorilla/mux" 13 | ) 14 | 15 | type tokenEndpoint struct { 16 | tokenService interfaces.TokenService 17 | } 18 | 19 | // ServeTokenResource sets up the routing of token endpoints and the corresponding handlers. 20 | func ServeTokenResource( 21 | r *mux.Router, 22 | tokenService interfaces.TokenService, 23 | ) { 24 | e := &tokenEndpoint{tokenService} 25 | r.HandleFunc("/tokens/base", e.HandleGetBaseTokens).Methods("GET") 26 | r.HandleFunc("/tokens/quote", e.HandleGetQuoteTokens).Methods("GET") 27 | r.HandleFunc("/tokens/{address}", e.HandleGetToken).Methods("GET") 28 | r.HandleFunc("/tokens", e.HandleGetTokens).Methods("GET") 29 | r.HandleFunc("/tokens", e.HandleCreateTokens).Methods("POST") 30 | } 31 | 32 | func (e *tokenEndpoint) HandleCreateTokens(w http.ResponseWriter, r *http.Request) { 33 | var t types.Token 34 | decoder := json.NewDecoder(r.Body) 35 | 36 | err := decoder.Decode(&t) 37 | if err != nil { 38 | logger.Error(err) 39 | httputils.WriteError(w, http.StatusBadRequest, "Invalid payload") 40 | } 41 | 42 | defer r.Body.Close() 43 | 44 | err = e.tokenService.Create(&t) 45 | if err != nil { 46 | if err == services.ErrTokenExists { 47 | httputils.WriteError(w, http.StatusBadRequest, "") 48 | return 49 | } else { 50 | logger.Error(err) 51 | httputils.WriteError(w, http.StatusInternalServerError, "") 52 | return 53 | } 54 | } 55 | 56 | httputils.WriteJSON(w, http.StatusCreated, t) 57 | } 58 | 59 | func (e *tokenEndpoint) HandleGetTokens(w http.ResponseWriter, r *http.Request) { 60 | res, err := e.tokenService.GetAll() 61 | if err != nil { 62 | logger.Error(err) 63 | httputils.WriteError(w, http.StatusInternalServerError, "") 64 | return 65 | } 66 | 67 | httputils.WriteJSON(w, http.StatusOK, res) 68 | } 69 | 70 | func (e *tokenEndpoint) HandleGetQuoteTokens(w http.ResponseWriter, r *http.Request) { 71 | res, err := e.tokenService.GetQuoteTokens() 72 | if err != nil { 73 | logger.Error(err) 74 | httputils.WriteError(w, http.StatusInternalServerError, "") 75 | } 76 | 77 | httputils.WriteJSON(w, http.StatusOK, res) 78 | } 79 | 80 | func (e *tokenEndpoint) HandleGetBaseTokens(w http.ResponseWriter, r *http.Request) { 81 | res, err := e.tokenService.GetBaseTokens() 82 | if err != nil { 83 | logger.Error(err) 84 | httputils.WriteError(w, http.StatusInternalServerError, "") 85 | return 86 | } 87 | 88 | httputils.WriteJSON(w, http.StatusOK, res) 89 | } 90 | 91 | func (e *tokenEndpoint) HandleGetToken(w http.ResponseWriter, r *http.Request) { 92 | vars := mux.Vars(r) 93 | 94 | a := vars["address"] 95 | if !common.IsHexAddress(a) { 96 | httputils.WriteError(w, http.StatusBadRequest, "Invalid Address") 97 | } 98 | 99 | tokenAddress := common.HexToAddress(a) 100 | res, err := e.tokenService.GetByAddress(tokenAddress) 101 | if err != nil { 102 | logger.Error(err) 103 | httputils.WriteError(w, http.StatusInternalServerError, "") 104 | return 105 | } 106 | 107 | httputils.WriteJSON(w, http.StatusOK, res) 108 | } 109 | -------------------------------------------------------------------------------- /endpoints/trade.go: -------------------------------------------------------------------------------- 1 | package endpoints 2 | 3 | import ( 4 | "encoding/json" 5 | "net/http" 6 | 7 | "github.com/Proofsuite/amp-matching-engine/interfaces" 8 | "github.com/Proofsuite/amp-matching-engine/types" 9 | "github.com/Proofsuite/amp-matching-engine/utils/httputils" 10 | "github.com/Proofsuite/amp-matching-engine/ws" 11 | "github.com/ethereum/go-ethereum/common" 12 | "github.com/gorilla/mux" 13 | ) 14 | 15 | type tradeEndpoint struct { 16 | tradeService interfaces.TradeService 17 | } 18 | 19 | // ServeTradeResource sets up the routing of trade endpoints and the corresponding handlers. 20 | func ServeTradeResource( 21 | r *mux.Router, 22 | tradeService interfaces.TradeService, 23 | ) { 24 | e := &tradeEndpoint{tradeService} 25 | r.HandleFunc("/trades/history/{baseToken}/{quoteToken}", e.HandleGetTradeHistory) 26 | r.HandleFunc("/trades/{address}", e.HandleGetTrades) 27 | ws.RegisterChannel(ws.TradeChannel, e.tradeWebSocket) 28 | } 29 | 30 | // history is reponsible for handling pair's trade history requests 31 | func (e *tradeEndpoint) HandleGetTradeHistory(w http.ResponseWriter, r *http.Request) { 32 | vars := mux.Vars(r) 33 | bt := vars["baseToken"] 34 | qt := vars["quoteToken"] 35 | 36 | if !common.IsHexAddress(bt) { 37 | httputils.WriteError(w, http.StatusBadRequest, "Invalid base token address") 38 | return 39 | } 40 | 41 | if !common.IsHexAddress(qt) { 42 | httputils.WriteError(w, http.StatusBadRequest, "Invalid quote token address") 43 | return 44 | } 45 | 46 | baseToken := common.HexToAddress(bt) 47 | quoteToken := common.HexToAddress(qt) 48 | res, err := e.tradeService.GetByPairAddress(baseToken, quoteToken) 49 | if err != nil { 50 | logger.Error(err) 51 | httputils.WriteError(w, http.StatusInternalServerError, "") 52 | return 53 | } 54 | 55 | httputils.WriteJSON(w, http.StatusOK, res) 56 | } 57 | 58 | // get is reponsible for handling user's trade history requests 59 | func (e *tradeEndpoint) HandleGetTrades(w http.ResponseWriter, r *http.Request) { 60 | vars := mux.Vars(r) 61 | addr := vars["address"] 62 | 63 | if !common.IsHexAddress(addr) { 64 | httputils.WriteError(w, http.StatusBadRequest, "Invalid Address") 65 | return 66 | } 67 | 68 | address := common.HexToAddress(addr) 69 | res, err := e.tradeService.GetByUserAddress(address) 70 | if err != nil { 71 | logger.Error(err) 72 | httputils.WriteError(w, http.StatusInternalServerError, "") 73 | return 74 | } 75 | 76 | httputils.WriteJSON(w, http.StatusOK, res) 77 | } 78 | 79 | func (e *tradeEndpoint) tradeWebSocket(input interface{}, conn *ws.Conn) { 80 | bytes, _ := json.Marshal(input) 81 | var payload *types.WebSocketPayload 82 | if err := json.Unmarshal(bytes, &payload); err != nil { 83 | logger.Error(err) 84 | } 85 | 86 | socket := ws.GetTradeSocket() 87 | if payload.Type != "subscription" { 88 | err := map[string]string{"Message": "Invalid payload"} 89 | socket.SendErrorMessage(conn, err) 90 | return 91 | } 92 | 93 | bytes, _ = json.Marshal(payload.Data) 94 | var msg *types.WebSocketSubscription 95 | err := json.Unmarshal(bytes, &msg) 96 | if err != nil { 97 | logger.Error(err) 98 | } 99 | 100 | if (msg.Pair.BaseToken == common.Address{}) { 101 | err := map[string]string{"Message": "Invalid base token"} 102 | socket.SendErrorMessage(conn, err) 103 | return 104 | } 105 | 106 | if (msg.Pair.QuoteToken == common.Address{}) { 107 | err := map[string]string{"Message": "Invalid quote token"} 108 | socket.SendErrorMessage(conn, err) 109 | return 110 | } 111 | 112 | if msg.Event == types.SUBSCRIBE { 113 | e.tradeService.Subscribe(conn, msg.Pair.BaseToken, msg.Pair.QuoteToken) 114 | } 115 | 116 | if msg.Event == types.UNSUBSCRIBE { 117 | e.tradeService.Unsubscribe(conn, msg.Pair.BaseToken, msg.Pair.QuoteToken) 118 | } 119 | } 120 | -------------------------------------------------------------------------------- /engine/engine_test.go: -------------------------------------------------------------------------------- 1 | package engine 2 | 3 | import ( 4 | "os" 5 | ) 6 | 7 | var redisServer int 8 | 9 | func init() { 10 | if os.Args[1] == "live" { 11 | redisServer = 1 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /engine/store.go: -------------------------------------------------------------------------------- 1 | package engine 2 | 3 | import ( 4 | "encoding/json" 5 | "fmt" 6 | "time" 7 | 8 | "github.com/Proofsuite/amp-matching-engine/types" 9 | "github.com/Proofsuite/amp-matching-engine/utils" 10 | "github.com/ethereum/go-ethereum/common" 11 | ) 12 | 13 | // GetPricePoints returns the pricepoints matching a certain (pair, pricepoint) 14 | func (ob *OrderBook) GetMatchingBuyPricePoints(obKey string, pricePoint int64) ([]int64, error) { 15 | pps, err := ob.redisConn.ZRangeByLexInt(obKey, "-", "["+utils.UintToPaddedString(pricePoint)) 16 | if err != nil { 17 | logger.Error(err) 18 | return nil, err 19 | } 20 | 21 | return pps, nil 22 | } 23 | 24 | func (ob *OrderBook) GetMatchingSellPricePoints(obkv string, pricePoint int64) ([]int64, error) { 25 | pps, err := ob.redisConn.ZRevRangeByLexInt(obkv, "+", "["+utils.UintToPaddedString(pricePoint)) 26 | if err != nil { 27 | logger.Error(err) 28 | return nil, err 29 | } 30 | 31 | return pps, nil 32 | } 33 | 34 | func (ob *OrderBook) GetFromOrderMap(hash common.Hash) (*types.Order, error) { 35 | o := &types.Order{} 36 | keys, err := ob.redisConn.Keys("*::" + hash.Hex()) 37 | if err != nil { 38 | return nil, err 39 | } 40 | 41 | if len(keys) == 0 { 42 | return nil, fmt.Errorf("Key doesn't exists") 43 | } 44 | 45 | serialized, err := ob.redisConn.GetValue(keys[0]) 46 | if err != nil { 47 | logger.Error(err) 48 | return nil, err 49 | } 50 | 51 | err = json.Unmarshal([]byte(serialized), &o) 52 | if err != nil { 53 | return nil, err 54 | } 55 | 56 | return o, nil 57 | } 58 | 59 | // GetPricePointOrders returns the orders hashes for a (pair, pricepoint) 60 | func (ob *OrderBook) GetMatchingOrders(obKey string, pricePoint int64) ([][]byte, error) { 61 | k := obKey + "::" + utils.UintToPaddedString(pricePoint) 62 | orders, err := ob.redisConn.Sort(k, "", true, false, k+"::orders::*") 63 | if err != nil { 64 | return nil, err 65 | } 66 | 67 | return orders, nil 68 | } 69 | 70 | // AddPricePointToSet 71 | func (ob *OrderBook) AddToPricePointSet(pricePointSetKey string, pricePoint int64) error { 72 | err := ob.redisConn.ZAdd(pricePointSetKey, 0, utils.UintToPaddedString(pricePoint)) 73 | if err != nil { 74 | logger.Error(err) 75 | return err 76 | } 77 | 78 | return nil 79 | } 80 | 81 | // RemoveFromPricePointSet 82 | func (ob *OrderBook) RemoveFromPricePointSet(pricePointSetKey string, pricePoint int64) error { 83 | err := ob.redisConn.ZRem(pricePointSetKey, utils.UintToPaddedString(pricePoint)) 84 | if err != nil { 85 | logger.Error(err) 86 | return err 87 | } 88 | 89 | return nil 90 | } 91 | 92 | func (ob *OrderBook) GetPricePointSetLength(pricePointSetKey string) (int64, error) { 93 | count, err := ob.redisConn.ZCount(pricePointSetKey) 94 | if err != nil { 95 | logger.Error(err) 96 | return 0, err 97 | } 98 | 99 | return count, nil 100 | } 101 | 102 | // AddPricePointHashesSet 103 | func (ob *OrderBook) AddToPricePointHashesSet(orderHashListKey string, createdAt time.Time, hash common.Hash) error { 104 | err := ob.redisConn.ZAdd(orderHashListKey, createdAt.Unix(), hash.Hex()) 105 | if err != nil { 106 | logger.Error(err) 107 | return err 108 | } 109 | 110 | return nil 111 | } 112 | 113 | // RemoveFromPricePointHashesSet 114 | func (ob *OrderBook) RemoveFromPricePointHashesSet(orderHashListKey string, hash common.Hash) error { 115 | err := ob.redisConn.ZRem(orderHashListKey, hash.Hex()) 116 | if err != nil { 117 | logger.Error(err) 118 | return err 119 | } 120 | 121 | return nil 122 | } 123 | 124 | func (ob *OrderBook) GetPricePointHashesSetLength(orderHashListKey string) (int64, error) { 125 | count, err := ob.redisConn.ZCount(orderHashListKey) 126 | if err != nil { 127 | logger.Error(err) 128 | return 0, err 129 | } 130 | 131 | return count, nil 132 | } 133 | 134 | // AddToOrderMap 135 | func (ob *OrderBook) AddToOrderMap(o *types.Order) error { 136 | bytes, err := json.Marshal(o) 137 | if err != nil { 138 | logger.Error(err) 139 | return err 140 | } 141 | 142 | decoded := &types.Order{} 143 | json.Unmarshal(bytes, decoded) 144 | _, orderHashListKey := o.GetOBKeys() 145 | err = ob.redisConn.Set(orderHashListKey+"::orders::"+o.Hash.Hex(), string(bytes)) 146 | if err != nil { 147 | logger.Error(err) 148 | return err 149 | } 150 | 151 | return nil 152 | } 153 | 154 | // RemoveFromOrderMap 155 | func (ob *OrderBook) RemoveFromOrderMap(hash common.Hash) error { 156 | keys, _ := ob.redisConn.Keys("*::" + hash.Hex()) 157 | err := ob.redisConn.Del(keys[0]) 158 | if err != nil { 159 | logger.Error(err) 160 | return err 161 | } 162 | 163 | return nil 164 | } 165 | -------------------------------------------------------------------------------- /errors/api_error.go: -------------------------------------------------------------------------------- 1 | package errors 2 | 3 | // APIError represents an error that can be sent in an error response. 4 | type APIError struct { 5 | // Status represents the HTTP status code 6 | Status int `json:"-"` 7 | // ErrorCode is the code uniquely identifying an error 8 | ErrorCode string `json:"error_code"` 9 | // Message is the error message that may be displayed to end users 10 | Message string `json:"message"` 11 | // DeveloperMessage is the error message that is mainly meant for developers 12 | DeveloperMessage string `json:"developer_message,omitempty"` 13 | // Details specifies the additional error information 14 | Details interface{} `json:"details,omitempty"` 15 | } 16 | 17 | // Error returns the error message. 18 | func (e APIError) Error() string { 19 | return e.Message 20 | } 21 | 22 | // StatusCode returns the HTTP status code. 23 | func (e APIError) StatusCode() int { 24 | return e.Status 25 | } 26 | -------------------------------------------------------------------------------- /errors/api_error_test.go: -------------------------------------------------------------------------------- 1 | package errors 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/stretchr/testify/assert" 7 | ) 8 | 9 | func TestAPIError_Error(t *testing.T) { 10 | e := APIError{ 11 | Message: "abc", 12 | } 13 | assert.Equal(t, "abc", e.Error()) 14 | } 15 | 16 | func TestAPIError_StatusCode(t *testing.T) { 17 | e := APIError{ 18 | Status: 400, 19 | } 20 | assert.Equal(t, 400, e.StatusCode()) 21 | } 22 | -------------------------------------------------------------------------------- /errors/errors.go: -------------------------------------------------------------------------------- 1 | package errors 2 | 3 | import ( 4 | "net/http" 5 | "sort" 6 | 7 | "github.com/go-ozzo/ozzo-validation" 8 | ) 9 | 10 | type validationError struct { 11 | Field string `json:"field"` 12 | Error string `json:"error"` 13 | } 14 | 15 | // InternalServerError creates a new API error representing an internal server error (HTTP 500) 16 | func InternalServerError(err error) *APIError { 17 | return NewHTTPError(http.StatusInternalServerError, "INTERNAL_SERVER_ERROR", Params{"error": err.Error()}) 18 | } 19 | 20 | // NotFound creates a new API error representing a resource-not-found error (HTTP 404) 21 | func NotFound(resource string) *APIError { 22 | return NewHTTPError(http.StatusNotFound, "NOT_FOUND", Params{"resource": resource}) 23 | } 24 | 25 | // Unauthorized creates a new API error representing an authentication failure (HTTP 401) 26 | func Unauthorized(err string) *APIError { 27 | return NewHTTPError(http.StatusUnauthorized, "UNAUTHORIZED", Params{"error": err}) 28 | } 29 | 30 | // InvalidData converts a data validation error into an API error (HTTP 400) 31 | func InvalidData(errs validation.Errors) *APIError { 32 | result := []validationError{} 33 | fields := []string{} 34 | for field := range errs { 35 | fields = append(fields, field) 36 | } 37 | sort.Strings(fields) 38 | for _, field := range fields { 39 | err := errs[field] 40 | result = append(result, validationError{ 41 | Field: field, 42 | Error: err.Error(), 43 | }) 44 | } 45 | 46 | err := NewHTTPError(http.StatusBadRequest, "INVALID_DATA", nil) 47 | err.Details = result 48 | 49 | return err 50 | } 51 | -------------------------------------------------------------------------------- /errors/errors_test.go: -------------------------------------------------------------------------------- 1 | package errors 2 | 3 | import ( 4 | errs "errors" 5 | "net/http" 6 | "testing" 7 | 8 | "github.com/go-ozzo/ozzo-validation" 9 | "github.com/stretchr/testify/assert" 10 | ) 11 | 12 | func TestInternalServerError(t *testing.T) { 13 | assert.Equal(t, http.StatusInternalServerError, InternalServerError(errs.New("")).Status) 14 | } 15 | 16 | func TestUnauthorized(t *testing.T) { 17 | assert.Equal(t, http.StatusUnauthorized, Unauthorized("t").Status) 18 | } 19 | 20 | func TestInvalidData(t *testing.T) { 21 | err := InvalidData(validation.Errors{ 22 | "abc": errs.New("1"), 23 | "xyz": errs.New("2"), 24 | }) 25 | assert.Equal(t, http.StatusBadRequest, err.Status) 26 | assert.NotNil(t, err.Details) 27 | } 28 | 29 | func TestNotFound(t *testing.T) { 30 | assert.Equal(t, http.StatusNotFound, NotFound("abc").Status) 31 | } 32 | -------------------------------------------------------------------------------- /errors/template.go: -------------------------------------------------------------------------------- 1 | package errors 2 | 3 | import ( 4 | "fmt" 5 | "io/ioutil" 6 | "strings" 7 | 8 | "gopkg.in/yaml.v2" 9 | ) 10 | 11 | type ( 12 | // Params is used to replace placeholders in an error template with the corresponding values. 13 | Params map[string]interface{} 14 | 15 | errorTemplate struct { 16 | Message string `yaml:"message"` 17 | DeveloperMessage string `yaml:"developer_message"` 18 | } 19 | ) 20 | 21 | var templates map[string]errorTemplate 22 | 23 | // LoadMessages reads a YAML file containing error templates. 24 | func LoadMessages(file string) error { 25 | bytes, err := ioutil.ReadFile(file) 26 | if err != nil { 27 | return err 28 | } 29 | templates = map[string]errorTemplate{} 30 | return yaml.Unmarshal(bytes, &templates) 31 | } 32 | 33 | // NewHTTPError creates a new APIError with the given HTTP status code, error code, and parameters for replacing placeholders in the error template. 34 | // The param can be nil, indicating there is no need for placeholder replacement. 35 | func NewHTTPError(status int, code string, params Params) *APIError { 36 | err := &APIError{ 37 | Status: status, 38 | ErrorCode: code, 39 | Message: code, 40 | } 41 | 42 | if template, ok := templates[code]; ok { 43 | err.Message = template.getMessage(params) 44 | err.DeveloperMessage = template.getDeveloperMessage(params) 45 | } 46 | 47 | return err 48 | } 49 | 50 | // getMessage returns the error message by replacing placeholders in the error template with the actual parameters. 51 | func (e errorTemplate) getMessage(params Params) string { 52 | return replacePlaceholders(e.Message, params) 53 | } 54 | 55 | // getDeveloperMessage returns the developer message by replacing placeholders in the error template with the actual parameters. 56 | func (e errorTemplate) getDeveloperMessage(params Params) string { 57 | return replacePlaceholders(e.DeveloperMessage, params) 58 | } 59 | 60 | func replacePlaceholders(message string, params Params) string { 61 | if len(message) == 0 { 62 | return "" 63 | } 64 | for key, value := range params { 65 | message = strings.Replace(message, "{"+key+"}", fmt.Sprint(value), -1) 66 | } 67 | return message 68 | } 69 | -------------------------------------------------------------------------------- /errors/template_test.go: -------------------------------------------------------------------------------- 1 | package errors 2 | 3 | import ( 4 | "testing" 5 | 6 | "net/http" 7 | 8 | "github.com/stretchr/testify/assert" 9 | ) 10 | 11 | const MESSAGE_FILE = "../config/errors.yaml" 12 | 13 | func TestNewHTTPError(t *testing.T) { 14 | defer func() { 15 | templates = nil 16 | }() 17 | 18 | assert.Nil(t, LoadMessages(MESSAGE_FILE)) 19 | 20 | e := NewHTTPError(http.StatusContinue, "xyz", nil) 21 | assert.Equal(t, http.StatusContinue, e.Status) 22 | assert.Equal(t, "xyz", e.Message) 23 | 24 | e = NewHTTPError(http.StatusNotFound, "NOT_FOUND", nil) 25 | assert.Equal(t, http.StatusNotFound, e.Status) 26 | assert.NotEqual(t, "NOT_FOUND", e.Message) 27 | } 28 | 29 | func TestLoadMessages(t *testing.T) { 30 | defer func() { 31 | templates = nil 32 | }() 33 | 34 | assert.Nil(t, LoadMessages(MESSAGE_FILE)) 35 | assert.NotNil(t, LoadMessages("xyz")) 36 | } 37 | 38 | func Test_replacePlaceholders(t *testing.T) { 39 | message := replacePlaceholders("abc", nil) 40 | assert.Equal(t, "abc", message) 41 | 42 | message = replacePlaceholders("abc", Params{"abc": 1}) 43 | assert.Equal(t, "abc", message) 44 | 45 | message = replacePlaceholders("{abc}", Params{"abc": 1}) 46 | assert.Equal(t, "1", message) 47 | 48 | message = replacePlaceholders("123 {abc} xyz {abc} d {xyz}", Params{"abc": 1, "xyz": "t"}) 49 | assert.Equal(t, "123 1 xyz 1 d t", message) 50 | } 51 | 52 | func Test_errorTemplate_newAPIError(t *testing.T) { 53 | 54 | } 55 | -------------------------------------------------------------------------------- /ethereum/client.go: -------------------------------------------------------------------------------- 1 | package ethereum 2 | 3 | import ( 4 | "context" 5 | "errors" 6 | "math/big" 7 | 8 | "github.com/ethereum/go-ethereum/accounts/abi/bind/backends" 9 | "github.com/ethereum/go-ethereum/common" 10 | "github.com/ethereum/go-ethereum/core" 11 | ) 12 | 13 | type SimulatedClient struct { 14 | *backends.SimulatedBackend 15 | } 16 | 17 | func (b *SimulatedClient) PendingBalanceAt(ctx context.Context, acc common.Address) (*big.Int, error) { 18 | return nil, errors.New("PendingBalanceAt is not implemented on the simulated backend") 19 | } 20 | 21 | func NewSimulatedClient(accs []common.Address) *SimulatedClient { 22 | weiBalance := &big.Int{} 23 | ether := big.NewInt(1e18) 24 | etherBalance := big.NewInt(1000) 25 | 26 | alloc := make(core.GenesisAlloc) 27 | weiBalance.Mul(etherBalance, ether) 28 | 29 | for _, a := range accs { 30 | (alloc)[a] = core.GenesisAccount{Balance: weiBalance} 31 | } 32 | 33 | client := backends.NewSimulatedBackend(alloc, 5e6) 34 | return &SimulatedClient{client} 35 | } 36 | -------------------------------------------------------------------------------- /ethereum/config.go: -------------------------------------------------------------------------------- 1 | package ethereum 2 | 3 | import ( 4 | "github.com/Proofsuite/amp-matching-engine/utils" 5 | "github.com/ethereum/go-ethereum/common" 6 | ) 7 | 8 | var logger = utils.Logger 9 | 10 | type EthereumConfig struct { 11 | url string 12 | exchangeAddress common.Address 13 | wethAddress common.Address 14 | } 15 | 16 | func NewEthereumConfig(url string, exchange, weth common.Address) *EthereumConfig { 17 | return &EthereumConfig{ 18 | url: url, 19 | exchangeAddress: exchange, 20 | wethAddress: weth, 21 | } 22 | } 23 | 24 | func (c *EthereumConfig) GetURL() string { 25 | return c.url 26 | } 27 | 28 | func (c *EthereumConfig) ExchangeAddress() common.Address { 29 | return c.exchangeAddress 30 | } 31 | 32 | func (c *EthereumConfig) WethAddress() common.Address { 33 | return c.wethAddress 34 | } 35 | -------------------------------------------------------------------------------- /gin-bin: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/agiletechvn/orderbook-matching-engine/3b178d5e70dc6f07d862944d4c74edc7befcf756/gin-bin -------------------------------------------------------------------------------- /rabbitmq/engine.go: -------------------------------------------------------------------------------- 1 | package rabbitmq 2 | 3 | import ( 4 | "encoding/json" 5 | 6 | "github.com/Proofsuite/amp-matching-engine/types" 7 | ) 8 | 9 | func (c *Connection) SubscribeEngineResponses(fn func(*types.EngineResponse) error) error { 10 | ch := c.GetChannel("erSub") 11 | q := c.GetQueue(ch, "engineResponse") 12 | 13 | go func() { 14 | msgs, err := ch.Consume( 15 | q.Name, // queue 16 | "", // consumer 17 | true, // auto-ack 18 | false, // exclusive 19 | false, // no-local 20 | false, // no-wait 21 | nil, // args 22 | ) 23 | 24 | if err != nil { 25 | logger.Fatal("Failed to register a consumer:", err) 26 | } 27 | 28 | forever := make(chan bool) 29 | 30 | go func() { 31 | for d := range msgs { 32 | var res *types.EngineResponse 33 | err := json.Unmarshal(d.Body, &res) 34 | if err != nil { 35 | logger.Error(err) 36 | continue 37 | } 38 | go fn(res) 39 | } 40 | }() 41 | 42 | <-forever 43 | }() 44 | return nil 45 | } 46 | 47 | func (c *Connection) PublishEngineResponse(res *types.EngineResponse) error { 48 | ch := c.GetChannel("erPub") 49 | q := c.GetQueue(ch, "engineResponse") 50 | 51 | bytes, err := json.Marshal(res) 52 | if err != nil { 53 | logger.Error("Failed to marshal engine response: ", err) 54 | return err 55 | } 56 | 57 | err = c.Publish(ch, q, bytes) 58 | if err != nil { 59 | logger.Error("Failed to publish order: ", err) 60 | return err 61 | } 62 | 63 | return nil 64 | } 65 | -------------------------------------------------------------------------------- /rabbitmq/orders.go: -------------------------------------------------------------------------------- 1 | package rabbitmq 2 | 3 | import ( 4 | "encoding/json" 5 | "errors" 6 | "log" 7 | 8 | "github.com/Proofsuite/amp-matching-engine/types" 9 | ) 10 | 11 | func (c *Connection) SubscribeOrders(fn func(*Message) error) error { 12 | ch := c.GetChannel("orderSubscribe") 13 | q := c.GetQueue(ch, "order") 14 | 15 | go func() { 16 | msgs, err := c.Consume(ch, q) 17 | if err != nil { 18 | logger.Error(err) 19 | } 20 | 21 | forever := make(chan bool) 22 | 23 | go func() { 24 | for d := range msgs { 25 | msg := &Message{} 26 | err := json.Unmarshal(d.Body, msg) 27 | if err != nil { 28 | logger.Error(err) 29 | continue 30 | } 31 | 32 | go fn(msg) 33 | } 34 | }() 35 | 36 | <-forever 37 | }() 38 | return nil 39 | } 40 | 41 | func (c *Connection) SubscribeTrades(fn func(*types.OperatorMessage) error) error { 42 | ch := c.GetChannel("tradeSubscribe") 43 | q := c.GetQueue(ch, "trades") 44 | 45 | go func() { 46 | msgs, err := c.Consume(ch, q) 47 | if err != nil { 48 | logger.Error(err) 49 | } 50 | 51 | forever := make(chan bool) 52 | 53 | go func() { 54 | for d := range msgs { 55 | msg := &types.OperatorMessage{} 56 | err := json.Unmarshal(d.Body, msg) 57 | if err != nil { 58 | logger.Error(err) 59 | continue 60 | } 61 | 62 | go fn(msg) 63 | } 64 | }() 65 | 66 | <-forever 67 | }() 68 | return nil 69 | } 70 | 71 | func (c *Connection) PublishTrade(o *types.Order, t *types.Trade) error { 72 | ch := c.GetChannel("tradePublish") 73 | q := c.GetQueue(ch, "trades") 74 | 75 | msg := &types.OperatorMessage{ 76 | MessageType: "NEW_ORDER", 77 | Order: o, 78 | Trade: t, 79 | } 80 | 81 | bytes, err := json.Marshal(msg) 82 | if err != nil { 83 | logger.Error(err) 84 | return err 85 | } 86 | 87 | err = c.Publish(ch, q, bytes) 88 | if err != nil { 89 | logger.Error(err) 90 | return err 91 | } 92 | 93 | return nil 94 | } 95 | 96 | func (c *Connection) PublishOrder(order *Message) error { 97 | ch := c.GetChannel("orderPublish") 98 | q := c.GetQueue(ch, "order") 99 | 100 | bytes, err := json.Marshal(order) 101 | if err != nil { 102 | log.Fatal("Failed to marshal order: ", err) 103 | return errors.New("Failed to marshal order: " + err.Error()) 104 | } 105 | 106 | err = c.Publish(ch, q, bytes) 107 | if err != nil { 108 | logger.Error(err) 109 | return err 110 | } 111 | 112 | return nil 113 | } 114 | -------------------------------------------------------------------------------- /rabbitmq/rabbitmq.go: -------------------------------------------------------------------------------- 1 | package rabbitmq 2 | 3 | import ( 4 | "log" 5 | 6 | "github.com/Proofsuite/amp-matching-engine/utils" 7 | "github.com/ethereum/go-ethereum/common" 8 | "github.com/streadway/amqp" 9 | ) 10 | 11 | // Conn is singleton rabbitmq connection 12 | var conn *Connection 13 | var channels = make(map[string]*amqp.Channel) 14 | var queues = make(map[string]*amqp.Queue) 15 | 16 | var logger = utils.RabbitLogger 17 | 18 | type Connection struct { 19 | Conn *amqp.Connection 20 | } 21 | type Message struct { 22 | Type string `json:"type"` 23 | Data []byte `json:"data"` 24 | HashID common.Hash `json:"hashID"` 25 | } 26 | 27 | // InitConnection Initializes single rabbitmq connection for whole system 28 | func InitConnection(address string) *Connection { 29 | if conn == nil { 30 | newConn, err := amqp.Dial(address) 31 | if err != nil { 32 | panic(err) 33 | } 34 | conn = &Connection{newConn} 35 | } 36 | return conn 37 | } 38 | 39 | func (c *Connection) NewConnection(address string) *amqp.Connection { 40 | conn, err := amqp.Dial(address) 41 | if err != nil { 42 | panic(err) 43 | } 44 | 45 | return conn 46 | } 47 | 48 | func (c *Connection) GetQueue(ch *amqp.Channel, queue string) *amqp.Queue { 49 | if queues[queue] == nil { 50 | q, err := ch.QueueDeclare(queue, false, false, false, false, nil) 51 | if err != nil { 52 | log.Fatalf("Failed to declare a queue: %s", err) 53 | } 54 | 55 | queues[queue] = &q 56 | } 57 | 58 | return queues[queue] 59 | } 60 | 61 | func (c *Connection) DeclareQueue(ch *amqp.Channel, name string) error { 62 | if queues[name] == nil { 63 | q, err := ch.QueueDeclare(name, false, false, false, false, nil) 64 | if err != nil { 65 | logger.Error(err) 66 | return err 67 | } 68 | 69 | queues[name] = &q 70 | } 71 | 72 | return nil 73 | } 74 | 75 | func (c *Connection) GetChannel(id string) *amqp.Channel { 76 | if channels[id] == nil { 77 | ch, err := c.Conn.Channel() 78 | if err != nil { 79 | log.Fatalf("Failed to open a channel: %s", err) 80 | panic(err) 81 | } 82 | 83 | channels[id] = ch 84 | } 85 | 86 | return channels[id] 87 | } 88 | 89 | // Publish 90 | func (c *Connection) Publish(ch *amqp.Channel, q *amqp.Queue, bytes []byte) error { 91 | err := ch.Publish( 92 | "", 93 | q.Name, 94 | false, 95 | false, 96 | amqp.Publishing{ 97 | ContentType: "text/json", 98 | Body: bytes, 99 | }, 100 | ) 101 | 102 | if err != nil { 103 | logger.Error(err) 104 | return err 105 | } 106 | 107 | return nil 108 | } 109 | 110 | func (c *Connection) Consume(ch *amqp.Channel, q *amqp.Queue) (<-chan amqp.Delivery, error) { 111 | msgs, err := ch.Consume( 112 | q.Name, // queue 113 | "", // consumer 114 | true, // auto-ack 115 | false, // exclusive 116 | false, // no-local 117 | false, // no-wait 118 | nil, // args 119 | ) 120 | 121 | if err != nil { 122 | logger.Error(err) 123 | return nil, err 124 | } 125 | 126 | return msgs, nil 127 | } 128 | 129 | func (c *Connection) Purge(ch *amqp.Channel, name string) error { 130 | _, err := ch.QueueInspect(name) 131 | if err != nil { 132 | return nil 133 | } 134 | 135 | _, err = ch.QueuePurge(name, false) 136 | if err != nil { 137 | logger.Error(err) 138 | return err 139 | } 140 | 141 | return nil 142 | } 143 | -------------------------------------------------------------------------------- /server.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import "github.com/Proofsuite/amp-matching-engine/cmd" 4 | 5 | func main() { 6 | cmd.Execute() 7 | } 8 | -------------------------------------------------------------------------------- /server_test.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "testing" 6 | 7 | "github.com/Proofsuite/amp-matching-engine/app" 8 | "github.com/Proofsuite/amp-matching-engine/e2e" 9 | "github.com/Proofsuite/amp-matching-engine/errors" 10 | "github.com/Proofsuite/amp-matching-engine/rabbitmq" 11 | ) 12 | 13 | func TestApp(t *testing.T) { 14 | // load application configurations 15 | if err := app.LoadConfig("./config", ""); err != nil { 16 | panic(fmt.Errorf("Invalid application configuration: %s", err)) 17 | } 18 | 19 | app.Config.DBName = "proofdextest" 20 | // load error messages 21 | if err := errors.LoadMessages(app.Config.ErrorFile); err != nil { 22 | panic(fmt.Errorf("Failed to read the error message file: %s", err)) 23 | } 24 | 25 | rabbitmq.InitConnection(app.Config.Rabbitmq) 26 | 27 | e2e.Init(t) 28 | } 29 | -------------------------------------------------------------------------------- /services/account.go: -------------------------------------------------------------------------------- 1 | package services 2 | 3 | import ( 4 | "math/big" 5 | 6 | "github.com/Proofsuite/amp-matching-engine/interfaces" 7 | "github.com/Proofsuite/amp-matching-engine/types" 8 | "github.com/ethereum/go-ethereum/common" 9 | "gopkg.in/mgo.v2/bson" 10 | ) 11 | 12 | type AccountService struct { 13 | AccountDao interfaces.AccountDao 14 | TokenDao interfaces.TokenDao 15 | } 16 | 17 | // NewAddressService returns a new instance of accountService 18 | func NewAccountService( 19 | AccountDao interfaces.AccountDao, 20 | TokenDao interfaces.TokenDao, 21 | ) *AccountService { 22 | return &AccountService{AccountDao, TokenDao} 23 | } 24 | 25 | func (s *AccountService) Create(a *types.Account) error { 26 | addr := a.Address 27 | 28 | acc, err := s.AccountDao.GetByAddress(addr) 29 | if err != nil { 30 | logger.Error(err) 31 | return err 32 | } 33 | 34 | if acc != nil { 35 | return ErrAccountExists 36 | } 37 | 38 | tokens, err := s.TokenDao.GetAll() 39 | if err != nil { 40 | logger.Error(err) 41 | return err 42 | } 43 | 44 | a.IsBlocked = false 45 | a.TokenBalances = make(map[common.Address]*types.TokenBalance) 46 | 47 | // currently by default, the tokens balances are set to 0 48 | for _, token := range tokens { 49 | a.TokenBalances[token.ContractAddress] = &types.TokenBalance{ 50 | Address: token.ContractAddress, 51 | Symbol: token.Symbol, 52 | Balance: big.NewInt(0), 53 | Allowance: big.NewInt(0), 54 | LockedBalance: big.NewInt(0), 55 | } 56 | } 57 | if a != nil { 58 | err = s.AccountDao.Create(a) 59 | if err != nil { 60 | logger.Error(err) 61 | return err 62 | } 63 | } 64 | 65 | return nil 66 | } 67 | 68 | func (s *AccountService) GetByID(id bson.ObjectId) (*types.Account, error) { 69 | return s.AccountDao.GetByID(id) 70 | } 71 | 72 | func (s *AccountService) GetAll() ([]types.Account, error) { 73 | return s.AccountDao.GetAll() 74 | } 75 | 76 | func (s *AccountService) GetByAddress(a common.Address) (*types.Account, error) { 77 | return s.AccountDao.GetByAddress(a) 78 | } 79 | 80 | func (s *AccountService) GetTokenBalance(owner common.Address, token common.Address) (*types.TokenBalance, error) { 81 | return s.AccountDao.GetTokenBalance(owner, token) 82 | } 83 | 84 | func (s *AccountService) GetTokenBalances(owner common.Address) (map[common.Address]*types.TokenBalance, error) { 85 | return s.AccountDao.GetTokenBalances(owner) 86 | } 87 | -------------------------------------------------------------------------------- /services/main.go: -------------------------------------------------------------------------------- 1 | package services 2 | 3 | import ( 4 | "errors" 5 | 6 | "github.com/Proofsuite/amp-matching-engine/utils" 7 | ) 8 | 9 | var logger = utils.Logger 10 | 11 | var ErrPairExists = errors.New("Pairs already exists") 12 | var ErrPairNotFound = errors.New("Pair not found") 13 | var ErrBaseTokenNotFound = errors.New("BaseToken not found") 14 | var ErrQuoteTokenNotFound = errors.New("QuoteToken not found") 15 | var ErrQuoteTokenInvalid = errors.New("Quote Token Invalid (not a quote)") 16 | var ErrTokenExists = errors.New("Token already exists") 17 | 18 | var ErrAccountNotFound = errors.New("Account not found") 19 | var ErrAccountExists = errors.New("Account already Exists") 20 | -------------------------------------------------------------------------------- /services/order_test.go: -------------------------------------------------------------------------------- 1 | package services 2 | 3 | import ( 4 | "math/big" 5 | "testing" 6 | 7 | "github.com/Proofsuite/amp-matching-engine/rabbitmq" 8 | 9 | "github.com/Proofsuite/amp-matching-engine/types" 10 | "github.com/Proofsuite/amp-matching-engine/utils/testutils" 11 | "github.com/Proofsuite/amp-matching-engine/utils/testutils/mocks" 12 | "github.com/ethereum/go-ethereum/common" 13 | ) 14 | 15 | func TestCancelTrades(t *testing.T) { 16 | orderDao := new(mocks.OrderDao) 17 | pairDao := new(mocks.PairDao) 18 | accountDao := new(mocks.AccountDao) 19 | tradeDao := new(mocks.TradeDao) 20 | engine := new(mocks.Engine) 21 | ethereum := new(mocks.EthereumProvider) 22 | 23 | amqp := rabbitmq.InitConnection("amqp://guest:guest@localhost:5672/") 24 | orderService := NewOrderService( 25 | orderDao, 26 | pairDao, 27 | accountDao, 28 | tradeDao, 29 | engine, 30 | ethereum, 31 | amqp, 32 | ) 33 | 34 | t1 := testutils.GetTestTrade1() 35 | t2 := testutils.GetTestTrade2() 36 | o1 := testutils.GetTestOrder1() 37 | o2 := testutils.GetTestOrder2() 38 | 39 | trades := []*types.Trade{&t1, &t2} 40 | hashes := []common.Hash{t1.OrderHash, t2.OrderHash} 41 | amounts := []*big.Int{t1.Amount, t2.Amount} 42 | orders := []*types.Order{&o1, &o2} 43 | 44 | orderDao.On("GetByHashes", hashes).Return(orders, nil) 45 | engine.On("CancelTrades", orders, amounts).Return(nil) 46 | 47 | err := orderService.CancelTrades(trades) 48 | if err != nil { 49 | t.Error("Could not cancel trades", err) 50 | } 51 | 52 | orderDao.AssertCalled(t, "GetByHashes", hashes) 53 | engine.AssertCalled(t, "CancelTrades", orders, amounts) 54 | } 55 | -------------------------------------------------------------------------------- /services/orderbook.go: -------------------------------------------------------------------------------- 1 | package services 2 | 3 | import ( 4 | "errors" 5 | 6 | "github.com/Proofsuite/amp-matching-engine/interfaces" 7 | "github.com/Proofsuite/amp-matching-engine/types" 8 | "github.com/Proofsuite/amp-matching-engine/utils" 9 | "github.com/ethereum/go-ethereum/common" 10 | 11 | "github.com/Proofsuite/amp-matching-engine/ws" 12 | ) 13 | 14 | // PairService struct with daos required, responsible for communicating with daos. 15 | // PairService functions are responsible for interacting with daos and implements business logics. 16 | type OrderBookService struct { 17 | pairDao interfaces.PairDao 18 | tokenDao interfaces.TokenDao 19 | orderDao interfaces.OrderDao 20 | eng interfaces.Engine 21 | } 22 | 23 | // NewPairService returns a new instance of balance service 24 | func NewOrderBookService( 25 | pairDao interfaces.PairDao, 26 | tokenDao interfaces.TokenDao, 27 | orderDao interfaces.OrderDao, 28 | eng interfaces.Engine, 29 | ) *OrderBookService { 30 | return &OrderBookService{pairDao, tokenDao, orderDao, eng} 31 | } 32 | 33 | // GetOrderBook fetches orderbook from engine/redis and returns it as an map[string]interface 34 | func (s *OrderBookService) GetOrderBook(bt, qt common.Address) (map[string]interface{}, error) { 35 | pair, err := s.pairDao.GetByTokenAddress(bt, qt) 36 | if err != nil { 37 | logger.Error(err) 38 | return nil, err 39 | } 40 | 41 | if pair == nil { 42 | return nil, errors.New("Pair not found") 43 | } 44 | 45 | bids, asks, err := s.orderDao.GetOrderBook(pair) 46 | if err != nil { 47 | logger.Error(err) 48 | return nil, err 49 | } 50 | 51 | ob := map[string]interface{}{ 52 | "asks": asks, 53 | "bids": bids, 54 | } 55 | 56 | return ob, nil 57 | } 58 | 59 | // SubscribeOrderBook is responsible for handling incoming orderbook subscription messages 60 | // It makes an entry of connection in pairSocket corresponding to pair,unit and duration 61 | func (s *OrderBookService) SubscribeOrderBook(conn *ws.Conn, bt, qt common.Address) { 62 | socket := ws.GetOrderBookSocket() 63 | 64 | ob, err := s.GetOrderBook(bt, qt) 65 | if err != nil { 66 | socket.SendErrorMessage(conn, err.Error()) 67 | return 68 | } 69 | 70 | id := utils.GetOrderBookChannelID(bt, qt) 71 | err = socket.Subscribe(id, conn) 72 | if err != nil { 73 | message := map[string]string{ 74 | "Code": "Internal Server Error", 75 | "Message": err.Error(), 76 | } 77 | 78 | socket.SendErrorMessage(conn, message) 79 | return 80 | } 81 | 82 | ws.RegisterConnectionUnsubscribeHandler(conn, socket.UnsubscribeHandler(id)) 83 | socket.SendInitMessage(conn, ob) 84 | } 85 | 86 | // UnSubscribeOrderBook is responsible for handling incoming orderbook unsubscription messages 87 | func (s *OrderBookService) UnSubscribeOrderBook(conn *ws.Conn, bt, qt common.Address) { 88 | socket := ws.GetOrderBookSocket() 89 | id := utils.GetOrderBookChannelID(bt, qt) 90 | socket.Unsubscribe(id, conn) 91 | } 92 | 93 | // GetRawOrderBook fetches complete orderbook from engine/redis 94 | func (s *OrderBookService) GetRawOrderBook(bt, qt common.Address) ([]*types.Order, error) { 95 | pair, err := s.pairDao.GetByTokenAddress(bt, qt) 96 | if err != nil { 97 | logger.Error(err) 98 | return nil, err 99 | } 100 | 101 | ob, err := s.orderDao.GetRawOrderBook(pair) 102 | if err != nil { 103 | logger.Error(err) 104 | return nil, err 105 | } 106 | 107 | return ob, nil 108 | } 109 | 110 | // SubscribeRawOrderBook is responsible for handling incoming orderbook subscription messages 111 | // It makes an entry of connection in pairSocket corresponding to pair,unit and duration 112 | func (s *OrderBookService) SubscribeRawOrderBook(conn *ws.Conn, bt, qt common.Address) { 113 | socket := ws.GetRawOrderBookSocket() 114 | 115 | ob, err := s.GetRawOrderBook(bt, qt) 116 | if err != nil { 117 | socket.SendErrorMessage(conn, err.Error()) 118 | return 119 | } 120 | 121 | id := utils.GetOrderBookChannelID(bt, qt) 122 | err = socket.Subscribe(id, conn) 123 | if err != nil { 124 | message := map[string]string{ 125 | "Code": "Internal Server Error", 126 | "Message": err.Error(), 127 | } 128 | 129 | socket.SendErrorMessage(conn, message) 130 | return 131 | } 132 | 133 | ws.RegisterConnectionUnsubscribeHandler(conn, socket.UnsubscribeHandler(id)) 134 | socket.SendInitMessage(conn, ob) 135 | } 136 | 137 | // UnSubscribeRawOrderBook is responsible for handling incoming orderbook unsubscription messages 138 | func (s *OrderBookService) UnSubscribeRawOrderBook(conn *ws.Conn, bt, qt common.Address) { 139 | socket := ws.GetRawOrderBookSocket() 140 | 141 | id := utils.GetOrderBookChannelID(bt, qt) 142 | socket.Unsubscribe(id, conn) 143 | } 144 | -------------------------------------------------------------------------------- /services/pair.go: -------------------------------------------------------------------------------- 1 | package services 2 | 3 | import ( 4 | "github.com/Proofsuite/amp-matching-engine/interfaces" 5 | "github.com/ethereum/go-ethereum/common" 6 | 7 | "gopkg.in/mgo.v2/bson" 8 | 9 | "github.com/Proofsuite/amp-matching-engine/types" 10 | ) 11 | 12 | // PairService struct with daos required, responsible for communicating with daos. 13 | // PairService functions are responsible for interacting with daos and implements business logics. 14 | type PairService struct { 15 | pairDao interfaces.PairDao 16 | tokenDao interfaces.TokenDao 17 | eng interfaces.Engine 18 | tradeService *TradeService 19 | } 20 | 21 | // NewPairService returns a new instance of balance service 22 | func NewPairService( 23 | pairDao interfaces.PairDao, 24 | tokenDao interfaces.TokenDao, 25 | eng interfaces.Engine, 26 | tradeService *TradeService, 27 | ) *PairService { 28 | 29 | return &PairService{pairDao, tokenDao, eng, tradeService} 30 | } 31 | 32 | // Create function is responsible for inserting new pair in DB. 33 | // It checks for existence of tokens in DB first 34 | func (s *PairService) Create(pair *types.Pair) error { 35 | p, err := s.pairDao.GetByBuySellTokenAddress(pair.BaseTokenAddress, pair.QuoteTokenAddress) 36 | if err != nil { 37 | return err 38 | } 39 | 40 | if p != nil { 41 | return ErrPairExists 42 | } 43 | 44 | bt, err := s.tokenDao.GetByAddress(pair.BaseTokenAddress) 45 | if err != nil { 46 | return err 47 | } 48 | 49 | if bt == nil { 50 | return ErrBaseTokenNotFound 51 | } 52 | 53 | st, err := s.tokenDao.GetByAddress(pair.QuoteTokenAddress) 54 | if err != nil { 55 | return err 56 | } 57 | 58 | if st == nil { 59 | return ErrQuoteTokenNotFound 60 | } 61 | 62 | if !st.Quote { 63 | return ErrQuoteTokenInvalid 64 | } 65 | 66 | pair.QuoteTokenSymbol = st.Symbol 67 | pair.QuoteTokenAddress = st.ContractAddress 68 | pair.QuoteTokenDecimal = st.Decimal 69 | pair.BaseTokenSymbol = bt.Symbol 70 | pair.BaseTokenAddress = bt.ContractAddress 71 | pair.BaseTokenDecimal = bt.Decimal 72 | err = s.pairDao.Create(pair) 73 | if err != nil { 74 | return err 75 | } 76 | 77 | return nil 78 | } 79 | 80 | // GetByID fetches details of a pair using its mongo ID 81 | func (s *PairService) GetByID(id bson.ObjectId) (*types.Pair, error) { 82 | return s.pairDao.GetByID(id) 83 | } 84 | 85 | // GetByTokenAddress fetches details of a pair using contract address of 86 | // its constituting tokens 87 | func (s *PairService) GetByTokenAddress(bt, qt common.Address) (*types.Pair, error) { 88 | return s.pairDao.GetByTokenAddress(bt, qt) 89 | } 90 | 91 | // GetAll is reponsible for fetching all the pairs in the DB 92 | func (s *PairService) GetAll() ([]types.Pair, error) { 93 | return s.pairDao.GetAll() 94 | } 95 | -------------------------------------------------------------------------------- /services/services_test.go: -------------------------------------------------------------------------------- 1 | package services 2 | 3 | import ( 4 | "github.com/Proofsuite/amp-matching-engine/daos" 5 | "gopkg.in/mgo.v2" 6 | "gopkg.in/mgo.v2/dbtest" 7 | "io/ioutil" 8 | ) 9 | 10 | var server dbtest.DBServer 11 | var db *mgo.Session 12 | 13 | func init() { 14 | temp, _ := ioutil.TempDir("", "test") 15 | server.SetPath(temp) 16 | 17 | session := server.Session() 18 | if _, err := daos.InitSession(session); err != nil { 19 | panic(err) 20 | } 21 | db = session 22 | } 23 | -------------------------------------------------------------------------------- /services/token.go: -------------------------------------------------------------------------------- 1 | package services 2 | 3 | import ( 4 | "github.com/Proofsuite/amp-matching-engine/interfaces" 5 | "github.com/ethereum/go-ethereum/common" 6 | "gopkg.in/mgo.v2/bson" 7 | 8 | "github.com/Proofsuite/amp-matching-engine/types" 9 | ) 10 | 11 | // TokenService struct with daos required, responsible for communicating with daos. 12 | // TokenService functions are responsible for interacting with daos and implements business logics. 13 | type TokenService struct { 14 | tokenDao interfaces.TokenDao 15 | } 16 | 17 | // NewTokenService returns a new instance of TokenService 18 | func NewTokenService(tokenDao interfaces.TokenDao) *TokenService { 19 | return &TokenService{tokenDao} 20 | } 21 | 22 | // Create inserts a new token into the database 23 | func (s *TokenService) Create(token *types.Token) error { 24 | t, err := s.tokenDao.GetByAddress(token.ContractAddress) 25 | if err != nil { 26 | logger.Error(err) 27 | return err 28 | } 29 | 30 | if t != nil { 31 | return ErrTokenExists 32 | } 33 | 34 | err = s.tokenDao.Create(token) 35 | if err != nil { 36 | logger.Error(err) 37 | return err 38 | } 39 | 40 | return nil 41 | } 42 | 43 | // GetByID fetches the detailed document of a token using its mongo ID 44 | func (s *TokenService) GetByID(id bson.ObjectId) (*types.Token, error) { 45 | return s.tokenDao.GetByID(id) 46 | } 47 | 48 | // GetByAddress fetches the detailed document of a token using its contract address 49 | func (s *TokenService) GetByAddress(addr common.Address) (*types.Token, error) { 50 | return s.tokenDao.GetByAddress(addr) 51 | } 52 | 53 | // GetAll fetches all the tokens from db 54 | func (s *TokenService) GetAll() ([]types.Token, error) { 55 | return s.tokenDao.GetAll() 56 | } 57 | 58 | // GetQuote fetches all the quote tokens from db 59 | func (s *TokenService) GetQuoteTokens() ([]types.Token, error) { 60 | return s.tokenDao.GetQuoteTokens() 61 | } 62 | 63 | // GetBase fetches all the quote tokens from db 64 | func (s *TokenService) GetBaseTokens() ([]types.Token, error) { 65 | return s.tokenDao.GetBaseTokens() 66 | } 67 | -------------------------------------------------------------------------------- /services/trade.go: -------------------------------------------------------------------------------- 1 | package services 2 | 3 | import ( 4 | "github.com/Proofsuite/amp-matching-engine/interfaces" 5 | "github.com/Proofsuite/amp-matching-engine/types" 6 | "github.com/Proofsuite/amp-matching-engine/utils" 7 | "github.com/Proofsuite/amp-matching-engine/ws" 8 | 9 | "github.com/ethereum/go-ethereum/common" 10 | ) 11 | 12 | // TradeService struct with daos required, responsible for communicating with daos. 13 | // TradeService functions are responsible for interacting with daos and implements business logics. 14 | type TradeService struct { 15 | tradeDao interfaces.TradeDao 16 | } 17 | 18 | // NewTradeService returns a new instance of TradeService 19 | func NewTradeService(TradeDao interfaces.TradeDao) *TradeService { 20 | return &TradeService{TradeDao} 21 | } 22 | 23 | // Subscribe 24 | func (s *TradeService) Subscribe(conn *ws.Conn, bt, qt common.Address) { 25 | socket := ws.GetTradeSocket() 26 | 27 | trades, err := s.GetTrades(bt, qt) 28 | if err != nil { 29 | socket.SendErrorMessage(conn, err.Error()) 30 | return 31 | } 32 | 33 | id := utils.GetTradeChannelID(bt, qt) 34 | err = socket.Subscribe(id, conn) 35 | if err != nil { 36 | message := map[string]string{ 37 | "Code": "UNABLE_TO_REGISTER", 38 | "Message": "UNABLE_TO_REGISTER " + err.Error(), 39 | } 40 | 41 | socket.SendErrorMessage(conn, message) 42 | return 43 | } 44 | 45 | ws.RegisterConnectionUnsubscribeHandler(conn, socket.UnsubscribeHandler(id)) 46 | socket.SendInitMessage(conn, trades) 47 | } 48 | 49 | // Unsubscribe 50 | func (s *TradeService) Unsubscribe(conn *ws.Conn, bt, qt common.Address) { 51 | socket := ws.GetTradeSocket() 52 | 53 | id := utils.GetTradeChannelID(bt, qt) 54 | socket.Unsubscribe(id, conn) 55 | } 56 | 57 | // GetByPairName fetches all the trades corresponding to a pair using pair's name 58 | func (s *TradeService) GetByPairName(pairName string) ([]*types.Trade, error) { 59 | return s.tradeDao.GetByPairName(pairName) 60 | } 61 | 62 | // GetTrades is currently not implemented correctly 63 | func (s *TradeService) GetTrades(bt, qt common.Address) ([]types.Trade, error) { 64 | return s.tradeDao.GetAll() 65 | } 66 | 67 | // GetByPairAddress fetches all the trades corresponding to a pair using pair's token address 68 | func (s *TradeService) GetByPairAddress(bt, qt common.Address) ([]*types.Trade, error) { 69 | return s.tradeDao.GetByPairAddress(bt, qt) 70 | } 71 | 72 | // GetByUserAddress fetches all the trades corresponding to a user address 73 | func (s *TradeService) GetByUserAddress(addr common.Address) ([]*types.Trade, error) { 74 | return s.tradeDao.GetByUserAddress(addr) 75 | } 76 | 77 | // GetByHash fetches all trades corresponding to a trade hash 78 | func (s *TradeService) GetByHash(hash common.Hash) (*types.Trade, error) { 79 | return s.tradeDao.GetByHash(hash) 80 | } 81 | 82 | // GetByOrderHash fetches all trades corresponding to an order hash 83 | func (s *TradeService) GetByOrderHash(hash common.Hash) ([]*types.Trade, error) { 84 | return s.tradeDao.GetByOrderHash(hash) 85 | } 86 | 87 | func (s *TradeService) UpdateTradeTxHash(tr *types.Trade, txHash common.Hash) error { 88 | tr.TxHash = txHash 89 | 90 | err := s.tradeDao.UpdateByHash(tr.Hash, tr) 91 | if err != nil { 92 | logger.Error(err) 93 | return err 94 | } 95 | 96 | return nil 97 | } 98 | -------------------------------------------------------------------------------- /services/tx.go: -------------------------------------------------------------------------------- 1 | package services 2 | 3 | import ( 4 | "github.com/Proofsuite/amp-matching-engine/interfaces" 5 | "github.com/Proofsuite/amp-matching-engine/types" 6 | "github.com/ethereum/go-ethereum/accounts/abi/bind" 7 | ) 8 | 9 | // WalletService struct with daos required, responsible for communicating with daos 10 | type TxService struct { 11 | WalletDao interfaces.WalletDao 12 | Wallet *types.Wallet 13 | } 14 | 15 | func NewTxService(dao interfaces.WalletDao, w *types.Wallet) *TxService { 16 | return &TxService{dao, w} 17 | } 18 | 19 | func (s *TxService) GetTxCallOptions() *bind.CallOpts { 20 | return &bind.CallOpts{Pending: true} 21 | } 22 | 23 | func (s *TxService) GetTxDefaultSendOptions() (*bind.TransactOpts, error) { 24 | wallet, err := s.WalletDao.GetDefaultAdminWallet() 25 | if err != nil { 26 | return nil, err 27 | } 28 | 29 | return bind.NewKeyedTransactor(wallet.PrivateKey), nil 30 | } 31 | 32 | func (s *TxService) GetTxSendOptions() (*bind.TransactOpts, error) { 33 | return bind.NewKeyedTransactor(s.Wallet.PrivateKey), nil 34 | } 35 | 36 | func (s *TxService) SetTxSender(w *types.Wallet) { 37 | s.Wallet = w 38 | } 39 | 40 | func (s *TxService) GetCustomTxSendOptions(w *types.Wallet) *bind.TransactOpts { 41 | return bind.NewKeyedTransactor(w.PrivateKey) 42 | } 43 | -------------------------------------------------------------------------------- /services/wallet.go: -------------------------------------------------------------------------------- 1 | package services 2 | 3 | import ( 4 | "github.com/Proofsuite/amp-matching-engine/interfaces" 5 | "github.com/Proofsuite/amp-matching-engine/types" 6 | "github.com/ethereum/go-ethereum/common" 7 | ) 8 | 9 | // WalletService struct with daos required, responsible for communicating with daos 10 | type WalletService struct { 11 | WalletDao interfaces.WalletDao 12 | } 13 | 14 | func NewWalletService(walletDao interfaces.WalletDao) *WalletService { 15 | return &WalletService{walletDao} 16 | } 17 | 18 | func (s *WalletService) CreateAdminWallet(a common.Address) (*types.Wallet, error) { 19 | w := &types.Wallet{ 20 | Address: a, 21 | Admin: true, 22 | } 23 | 24 | err := s.WalletDao.Create(w) 25 | if err != nil { 26 | logger.Error(err) 27 | return nil, err 28 | } 29 | 30 | return w, nil 31 | } 32 | 33 | func (s *WalletService) GetDefaultAdminWallet() (*types.Wallet, error) { 34 | return s.WalletDao.GetDefaultAdminWallet() 35 | } 36 | 37 | func (s *WalletService) GetOperatorWallets() ([]*types.Wallet, error) { 38 | return s.WalletDao.GetOperatorWallets() 39 | } 40 | 41 | func (s *WalletService) GetAll() ([]types.Wallet, error) { 42 | return s.WalletDao.GetAll() 43 | } 44 | 45 | func (s *WalletService) GetByAddress(a common.Address) (*types.Wallet, error) { 46 | return s.WalletDao.GetByAddress(a) 47 | } 48 | -------------------------------------------------------------------------------- /testdata/init.go: -------------------------------------------------------------------------------- 1 | package testdata 2 | 3 | import ( 4 | "github.com/Proofsuite/amp-matching-engine/app" 5 | "github.com/Proofsuite/amp-matching-engine/daos" 6 | ) 7 | 8 | func init() { 9 | // the test may be started from the home directory or a subdirectory 10 | err := app.LoadConfig("./config", "../config") 11 | if err != nil { 12 | panic(err) 13 | } 14 | // connect to the database 15 | if err := daos.InitSession(nil); err != nil { 16 | panic(err) 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /types/account_test.go: -------------------------------------------------------------------------------- 1 | package types 2 | 3 | import ( 4 | "math/big" 5 | "testing" 6 | 7 | "github.com/ethereum/go-ethereum/common" 8 | "github.com/stretchr/testify/assert" 9 | "gopkg.in/mgo.v2/bson" 10 | ) 11 | 12 | func TestAccountBSON(t *testing.T) { 13 | assert := assert.New(t) 14 | 15 | address := common.HexToAddress("0xe8e84ee367bc63ddb38d3d01bccef106c194dc47") 16 | tokenAddress1 := common.HexToAddress("0xcf7389dc6c63637598402907d5431160ec8972a5") 17 | tokenAddress2 := common.HexToAddress("0x7a9f3cd060ab180f36c17fe6bdf9974f577d77aa") 18 | 19 | tokenBalance1 := &TokenBalance{ 20 | ID: bson.NewObjectId(), 21 | Address: tokenAddress1, 22 | Symbol: "EOS", 23 | Balance: big.NewInt(10000), 24 | Allowance: big.NewInt(10000), 25 | LockedBalance: big.NewInt(5000), 26 | } 27 | 28 | tokenBalance2 := &TokenBalance{ 29 | ID: bson.NewObjectId(), 30 | Address: tokenAddress2, 31 | Symbol: "ZRX", 32 | Balance: big.NewInt(10000), 33 | Allowance: big.NewInt(10000), 34 | LockedBalance: big.NewInt(5000), 35 | } 36 | 37 | account := &Account{ 38 | ID: bson.NewObjectId(), 39 | Address: address, 40 | TokenBalances: map[common.Address]*TokenBalance{ 41 | tokenAddress1: tokenBalance1, 42 | tokenAddress2: tokenBalance2, 43 | }, 44 | IsBlocked: false, 45 | } 46 | 47 | data, err := bson.Marshal(account) 48 | if err != nil { 49 | t.Error(err) 50 | } 51 | 52 | decoded := &Account{} 53 | if err := bson.Unmarshal(data, decoded); err != nil { 54 | t.Error(err) 55 | } 56 | 57 | assert.Equal(decoded, account) 58 | } 59 | -------------------------------------------------------------------------------- /types/engine.go: -------------------------------------------------------------------------------- 1 | package types 2 | 3 | import "github.com/ethereum/go-ethereum/common" 4 | 5 | type OrderTradePair struct { 6 | Order *Order 7 | Trade *Trade 8 | } 9 | 10 | type EngineResponse struct { 11 | Status string `json:"fillStatus,omitempty"` 12 | HashID common.Hash `json:"hashID, omitempty"` 13 | Order *Order `json:"order,omitempty"` 14 | RemainingOrder *Order `json:"remainingOrder,omitempty"` 15 | Matches []*OrderTradePair `json:"matches,omitempty"` 16 | } 17 | -------------------------------------------------------------------------------- /types/main.go: -------------------------------------------------------------------------------- 1 | package types 2 | 3 | import "github.com/Proofsuite/amp-matching-engine/utils" 4 | 5 | var logger = utils.Logger 6 | -------------------------------------------------------------------------------- /types/operator.go: -------------------------------------------------------------------------------- 1 | package types 2 | 3 | type OperatorMessage struct { 4 | MessageType string 5 | Order *Order 6 | Trade *Trade 7 | ErrID int 8 | } 9 | 10 | type PendingTradeMessage struct { 11 | Order *Order 12 | Trade *Trade 13 | } 14 | -------------------------------------------------------------------------------- /types/order_cancel.go: -------------------------------------------------------------------------------- 1 | package types 2 | 3 | import ( 4 | "encoding/json" 5 | "errors" 6 | "fmt" 7 | 8 | . "github.com/ethereum/go-ethereum/common" 9 | "github.com/ethereum/go-ethereum/crypto" 10 | "github.com/ethereum/go-ethereum/crypto/sha3" 11 | ) 12 | 13 | // OrderCancel is a group of params used for canceling an order previously 14 | // sent to the matching engine. The OrderId and OrderHash must correspond to the 15 | // same order. To be valid and be able to be processed by the matching engine, 16 | // the OrderCancel must include a signature by the Maker of the order corresponding 17 | // to the OrderHash. 18 | type OrderCancel struct { 19 | OrderHash Hash `json:"orderHash"` 20 | Hash Hash `json:"hash"` 21 | Signature *Signature `json:"signature"` 22 | } 23 | 24 | // NewOrderCancel returns a new empty OrderCancel object 25 | func NewOrderCancel() *OrderCancel { 26 | return &OrderCancel{ 27 | Hash: Hash{}, 28 | OrderHash: Hash{}, 29 | Signature: &Signature{}, 30 | } 31 | } 32 | 33 | // MarshalJSON returns the json encoded byte array representing the OrderCancel struct 34 | func (oc *OrderCancel) MarshalJSON() ([]byte, error) { 35 | orderCancel := map[string]interface{}{ 36 | "orderHash": oc.OrderHash, 37 | "hash": oc.Hash, 38 | "signature": map[string]interface{}{ 39 | "V": oc.Signature.V, 40 | "R": oc.Signature.R, 41 | "S": oc.Signature.S, 42 | }, 43 | } 44 | 45 | return json.Marshal(orderCancel) 46 | } 47 | 48 | func (oc *OrderCancel) String() string { 49 | return fmt.Sprintf("\nOrderCancel:\nOrderHash: %x\nHash: %x\nSignature.V: %x\nSignature.R: %x\nSignature.S: %x\n\n", 50 | oc.OrderHash, oc.Hash, oc.Signature.V, oc.Signature.R, oc.Signature.S) 51 | } 52 | 53 | // UnmarshalJSON creates an OrderCancel object from a json byte string 54 | func (oc *OrderCancel) UnmarshalJSON(b []byte) error { 55 | parsed := map[string]interface{}{} 56 | 57 | err := json.Unmarshal(b, &parsed) 58 | if err != nil { 59 | return err 60 | } 61 | 62 | if parsed["orderHash"] == nil { 63 | return errors.New("Order Hash is missing") 64 | } 65 | oc.OrderHash = HexToHash(parsed["orderHash"].(string)) 66 | 67 | if parsed["hash"] == nil { 68 | return errors.New("Hash is missing") 69 | } 70 | oc.Hash = HexToHash(parsed["hash"].(string)) 71 | 72 | sig := parsed["signature"].(map[string]interface{}) 73 | oc.Signature = &Signature{ 74 | V: byte(sig["V"].(float64)), 75 | R: HexToHash(sig["R"].(string)), 76 | S: HexToHash(sig["S"].(string)), 77 | } 78 | 79 | return nil 80 | } 81 | 82 | // VerifySignature returns a true value if the OrderCancel object signature 83 | // corresponds to the Maker of the given order 84 | func (oc *OrderCancel) VerifySignature(o *Order) (bool, error) { 85 | message := crypto.Keccak256( 86 | []byte("\x19Ethereum Signed Message:\n32"), 87 | oc.Hash.Bytes(), 88 | ) 89 | 90 | address, err := oc.Signature.Verify(BytesToHash(message)) 91 | if err != nil { 92 | return false, err 93 | } 94 | 95 | if address != o.UserAddress { 96 | return false, errors.New("Recovered address is incorrect") 97 | } 98 | 99 | return true, nil 100 | } 101 | 102 | // ComputeHash computes the hash of an order cancel message 103 | func (oc *OrderCancel) ComputeHash() Hash { 104 | sha := sha3.NewKeccak256() 105 | sha.Write(oc.OrderHash.Bytes()) 106 | return BytesToHash(sha.Sum(nil)) 107 | } 108 | 109 | // Sign first computes the order cancel hash, then signs and sets the signature 110 | func (oc *OrderCancel) Sign(w *Wallet) error { 111 | h := oc.ComputeHash() 112 | sig, err := w.SignHash(h) 113 | if err != nil { 114 | return err 115 | } 116 | 117 | oc.Hash = h 118 | oc.Signature = sig 119 | return nil 120 | } 121 | -------------------------------------------------------------------------------- /types/pair_test.go: -------------------------------------------------------------------------------- 1 | package types 2 | 3 | import ( 4 | "math/big" 5 | "testing" 6 | 7 | "github.com/ethereum/go-ethereum/common" 8 | "github.com/stretchr/testify/assert" 9 | "gopkg.in/mgo.v2/bson" 10 | ) 11 | 12 | func ComparePair(t *testing.T, a, b *Pair) { 13 | assert.Equal(t, a.ID, b.ID) 14 | assert.Equal(t, a.Name, b.Name) 15 | assert.Equal(t, a.BaseTokenSymbol, b.BaseTokenSymbol) 16 | assert.Equal(t, a.BaseTokenAddress, b.BaseTokenAddress) 17 | assert.Equal(t, a.QuoteTokenSymbol, b.QuoteTokenSymbol) 18 | assert.Equal(t, a.QuoteTokenAddress, b.QuoteTokenAddress) 19 | assert.Equal(t, a.Active, b.Active) 20 | assert.Equal(t, a.MakeFee, b.MakeFee) 21 | assert.Equal(t, a.TakeFee, b.TakeFee) 22 | } 23 | 24 | func TestPairBSON(t *testing.T) { 25 | pair := &Pair{ 26 | ID: bson.NewObjectId(), 27 | Name: "REQ", 28 | BaseTokenSymbol: "REQ", 29 | BaseTokenAddress: common.HexToAddress("0xcf7389dc6c63637598402907d5431160ec8972a5"), 30 | QuoteTokenSymbol: "WETH", 31 | QuoteTokenAddress: common.HexToAddress("0x7a9f3cd060ab180f36c17fe6bdf9974f577d77aa"), 32 | Active: true, 33 | MakeFee: big.NewInt(10000), 34 | TakeFee: big.NewInt(10000), 35 | } 36 | 37 | data, err := bson.Marshal(pair) 38 | if err != nil { 39 | t.Errorf("%+v", err) 40 | } 41 | 42 | decoded := &Pair{} 43 | if err := bson.Unmarshal(data, decoded); err != nil { 44 | t.Error(err) 45 | } 46 | 47 | ComparePair(t, pair, decoded) 48 | } 49 | -------------------------------------------------------------------------------- /types/payloads_test.go: -------------------------------------------------------------------------------- 1 | package types 2 | 3 | import ( 4 | "encoding/json" 5 | "fmt" 6 | "math/big" 7 | "testing" 8 | 9 | "github.com/ethereum/go-ethereum/common" 10 | "github.com/go-test/deep" 11 | ) 12 | 13 | func TestNewOrderPayload(t *testing.T) { 14 | p := &NewOrderPayload{ 15 | PairName: "ZRX/WETH", 16 | UserAddress: common.HexToAddress("0x7a9f3cd060ab180f36c17fe6bdf9974f577d77aa"), 17 | ExchangeAddress: common.HexToAddress("0xae55690d4b079460e6ac28aaa58c9ec7b73a7485"), 18 | BuyToken: common.HexToAddress("0xe41d2489571d322189246dafa5ebde1f4699f498"), 19 | SellToken: common.HexToAddress("0x12459c951127e0c374ff9105dda097662a027093"), 20 | BuyAmount: big.NewInt(1000), 21 | SellAmount: big.NewInt(100), 22 | MakeFee: big.NewInt(50), 23 | TakeFee: big.NewInt(50), 24 | Nonce: big.NewInt(1000), 25 | Expires: big.NewInt(10000), 26 | Signature: &Signature{ 27 | V: 28, 28 | R: common.HexToHash("0x10b30eb0072a4f0a38b6fca0b731cba15eb2e1702845d97c1230b53a839bcb85"), 29 | S: common.HexToHash("0x6d9ad89548c9e3ce4c97825d027291477f2c44a8caef792095f2cabc978493ff"), 30 | }, 31 | Hash: common.HexToHash("0xb9070a2d333403c255ce71ddf6e795053599b2e885321de40353832b96d8880a"), 32 | } 33 | 34 | encoded, err := json.Marshal(p) 35 | if err != nil { 36 | t.Errorf("Error encoding order: %v", err) 37 | } 38 | 39 | decoded := &NewOrderPayload{} 40 | err = json.Unmarshal([]byte(encoded), decoded) 41 | if err != nil { 42 | t.Errorf("Could not unmarshal payload: %v", err) 43 | } 44 | 45 | if diff := deep.Equal(p, decoded); diff != nil { 46 | fmt.Printf("Expected: \n%+v\nGot: \n%+v\n\n", p, decoded) 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /types/signature.go: -------------------------------------------------------------------------------- 1 | package types 2 | 3 | import ( 4 | "crypto/ecdsa" 5 | "errors" 6 | 7 | "github.com/ethereum/go-ethereum/common" 8 | "github.com/ethereum/go-ethereum/crypto" 9 | ) 10 | 11 | // Signature struct 12 | type Signature struct { 13 | V byte 14 | R common.Hash 15 | S common.Hash 16 | } 17 | 18 | type SignatureRecord struct { 19 | V byte `json:"V" bson:"V"` 20 | R string `json:"R" bson:"R"` 21 | S string `json:"S" bson:"S"` 22 | } 23 | 24 | // NewSignature function decodes []byte to Signature type 25 | func NewSignature(b []byte) (*Signature, error) { 26 | if len(b) != 64 { 27 | return nil, errors.New("Signature length should be 64 bytes") 28 | } 29 | 30 | return &Signature{ 31 | R: common.BytesToHash(b[0:32]), 32 | S: common.BytesToHash(b[32:65]), 33 | V: b[64] + 27, 34 | }, nil 35 | } 36 | 37 | // MarshalSignature marshals the signature struct to []byte 38 | func (s *Signature) MarshalSignature() ([]byte, error) { 39 | sigBytes1 := s.R.Bytes() 40 | sigBytes2 := s.S.Bytes() 41 | sigBytes3 := s.V - 27 42 | 43 | sigBytes := append([]byte{}, sigBytes1...) 44 | sigBytes = append(sigBytes, sigBytes2...) 45 | sigBytes = append(sigBytes, sigBytes3) 46 | 47 | return sigBytes, nil 48 | } 49 | 50 | // Verify returns the address that corresponds to the given signature and signed message 51 | func (s *Signature) Verify(hash common.Hash) (common.Address, error) { 52 | 53 | hashBytes := hash.Bytes() 54 | sigBytes, err := s.MarshalSignature() 55 | if err != nil { 56 | return common.Address{}, err 57 | } 58 | 59 | pubKey, err := crypto.SigToPub(hashBytes, sigBytes) 60 | if err != nil { 61 | return common.Address{}, err 62 | } 63 | address := crypto.PubkeyToAddress(*pubKey) 64 | return address, nil 65 | } 66 | 67 | // Sign calculates the EDCSA signature corresponding of a hashed message from a given private key 68 | func Sign(hash common.Hash, privKey *ecdsa.PrivateKey) (*Signature, error) { 69 | sigBytes, err := crypto.Sign(hash.Bytes(), privKey) 70 | if err != nil { 71 | return &Signature{}, err 72 | } 73 | 74 | sig := &Signature{ 75 | R: common.BytesToHash(sigBytes[0:32]), 76 | S: common.BytesToHash(sigBytes[32:64]), 77 | V: sigBytes[64] + 27, 78 | } 79 | 80 | return sig, nil 81 | } 82 | 83 | // SignHash also calculates the EDCSA signature of a message but adds an "Ethereum Signed Message" prefix 84 | // https://github.com/ethereum/EIPs/issues/191 85 | func SignHash(hash common.Hash, privKey *ecdsa.PrivateKey) (*Signature, error) { 86 | message := crypto.Keccak256( 87 | []byte("\x19Ethereum Signed Message:\n32"), 88 | hash.Bytes(), 89 | ) 90 | 91 | sigBytes, err := crypto.Sign(message, privKey) 92 | if err != nil { 93 | return &Signature{}, err 94 | } 95 | 96 | sig := &Signature{ 97 | R: common.BytesToHash(sigBytes[0:32]), 98 | S: common.BytesToHash(sigBytes[32:64]), 99 | V: sigBytes[64] + 27, 100 | } 101 | 102 | return sig, nil 103 | } 104 | -------------------------------------------------------------------------------- /types/token.go: -------------------------------------------------------------------------------- 1 | package types 2 | 3 | import ( 4 | "encoding/json" 5 | "time" 6 | 7 | "github.com/ethereum/go-ethereum/common" 8 | "github.com/go-ozzo/ozzo-validation" 9 | "gopkg.in/mgo.v2/bson" 10 | ) 11 | 12 | // Token struct is used to model the token data in the system and DB 13 | type Token struct { 14 | ID bson.ObjectId `json:"-" bson:"_id"` 15 | Name string `json:"name" bson:"name"` 16 | Symbol string `json:"symbol" bson:"symbol"` 17 | Image Image `json:"image" bson:"image"` 18 | ContractAddress common.Address `json:"contractAddress" bson:"contractAddress"` 19 | Decimal int `json:"decimal" bson:"decimal"` 20 | Active bool `json:"active" bson:"active"` 21 | Quote bool `json:"quote" bson:"quote"` 22 | 23 | CreatedAt time.Time `json:"createdAt" bson:"createdAt"` 24 | UpdatedAt time.Time `json:"updatedAt" bson:"updatedAt"` 25 | } 26 | 27 | // TokenRecord is the struct which is stored in db 28 | type TokenRecord struct { 29 | ID bson.ObjectId `json:"-" bson:"_id"` 30 | Name string `json:"name" bson:"name"` 31 | Symbol string `json:"symbol" bson:"symbol"` 32 | Image Image `json:"image" bson:"image"` 33 | ContractAddress string `json:"contractAddress" bson:"contractAddress"` 34 | Decimal int `json:"decimal" bson:"decimal"` 35 | Active bool `json:"active" bson:"active"` 36 | Quote bool `json:"quote" bson:"quote"` 37 | 38 | CreatedAt time.Time `json:"createdAt" bson:"createdAt"` 39 | UpdatedAt time.Time `json:"updatedAt" bson:"updatedAt"` 40 | } 41 | 42 | // Image is a sub document used to store data related to images 43 | type Image struct { 44 | URL string `json:"url" bson:"url"` 45 | Meta map[string]interface{} `json:"meta" bson:"meta"` 46 | } 47 | 48 | // Validate function is used to verify if an instance of 49 | // struct satisfies all the conditions for a valid instance 50 | func (t Token) Validate() error { 51 | return validation.ValidateStruct(&t, 52 | validation.Field(&t.Symbol, validation.Required), 53 | validation.Field(&t.ContractAddress, validation.Required), 54 | validation.Field(&t.Decimal, validation.Required), 55 | ) 56 | } 57 | 58 | // GetBSON implements bson.Getter 59 | func (t *Token) GetBSON() (interface{}, error) { 60 | 61 | return TokenRecord{ 62 | ID: t.ID, 63 | Name: t.Name, 64 | Symbol: t.Symbol, 65 | Image: t.Image, 66 | ContractAddress: t.ContractAddress.Hex(), 67 | Decimal: t.Decimal, 68 | Active: t.Active, 69 | Quote: t.Quote, 70 | CreatedAt: t.CreatedAt, 71 | UpdatedAt: t.UpdatedAt, 72 | }, nil 73 | } 74 | 75 | // SetBSON implemenets bson.Setter 76 | func (t *Token) SetBSON(raw bson.Raw) error { 77 | decoded := &TokenRecord{} 78 | 79 | err := raw.Unmarshal(decoded) 80 | if err != nil { 81 | return err 82 | } 83 | t.ID = decoded.ID 84 | t.Name = decoded.Name 85 | t.Symbol = decoded.Symbol 86 | t.Image = decoded.Image 87 | if common.IsHexAddress(decoded.ContractAddress) { 88 | t.ContractAddress = common.HexToAddress(decoded.ContractAddress) 89 | } 90 | t.Decimal = decoded.Decimal 91 | t.Active = decoded.Active 92 | t.Quote = decoded.Quote 93 | t.CreatedAt = decoded.CreatedAt 94 | t.UpdatedAt = decoded.UpdatedAt 95 | return nil 96 | } 97 | 98 | func (t *Token) Print() { 99 | b, err := json.MarshalIndent(t, "", " ") 100 | if err != nil { 101 | logger.Error(err) 102 | } 103 | 104 | logger.Info(string(b)) 105 | } 106 | 107 | func (t *TokenRecord) Print() { 108 | b, err := json.MarshalIndent(t, "", " ") 109 | if err != nil { 110 | logger.Error(err) 111 | } 112 | 113 | logger.Info(string(b)) 114 | } 115 | -------------------------------------------------------------------------------- /types/trade_test.go: -------------------------------------------------------------------------------- 1 | package types 2 | 3 | import ( 4 | "encoding/json" 5 | "math/big" 6 | "testing" 7 | "time" 8 | 9 | "github.com/ethereum/go-ethereum/common" 10 | "github.com/go-test/deep" 11 | "github.com/stretchr/testify/assert" 12 | "gopkg.in/mgo.v2/bson" 13 | ) 14 | 15 | func TestTradeJSON(t *testing.T) { 16 | expected := &Trade{ 17 | ID: bson.ObjectIdHex("537f700b537461b70c5f0000"), 18 | Maker: common.HexToAddress("0x7a9f3cd060ab180f36c17fe6bdf9974f577d77aa"), 19 | Taker: common.HexToAddress("0xae55690d4b079460e6ac28aaa58c9ec7b73a7485"), 20 | BaseToken: common.HexToAddress("0xe41d2489571d322189246dafa5ebde1f4699f498"), 21 | QuoteToken: common.HexToAddress("0x12459c951127e0c374ff9105dda097662a027093"), 22 | Hash: common.HexToHash("0xb9070a2d333403c255ce71ddf6e795053599b2e885321de40353832b96d8880a"), 23 | OrderHash: common.HexToHash("0x6d9ad89548c9e3ce4c97825d027291477f2c44a8caef792095f2cabc978493ff"), 24 | PairName: "ZRX/WETH", 25 | TradeNonce: big.NewInt(100), 26 | Signature: &Signature{ 27 | V: 28, 28 | R: common.HexToHash("0x10b30eb0072a4f0a38b6fca0b731cba15eb2e1702845d97c1230b53a839bcb85"), 29 | S: common.HexToHash("0x6d9ad89548c9e3ce4c97825d027291477f2c44a8caef792095f2cabc978493ff"), 30 | }, 31 | PricePoint: big.NewInt(10000), 32 | Side: "BUY", 33 | Amount: big.NewInt(100), 34 | } 35 | 36 | encoded, err := json.Marshal(expected) 37 | if err != nil { 38 | t.Errorf("Error encoding order: %v", err) 39 | } 40 | 41 | trade := &Trade{} 42 | err = json.Unmarshal([]byte(encoded), &trade) 43 | if err != nil { 44 | t.Errorf("Could not unmarshal payload: %v", err) 45 | } 46 | 47 | if diff := deep.Equal(expected, trade); diff != nil { 48 | t.Errorf("Expected: \n%+v\nGot: \n%+v\n\n", expected, trade) 49 | } 50 | } 51 | 52 | func TestTradeBSON(t *testing.T) { 53 | expected := &Trade{ 54 | ID: bson.ObjectIdHex("537f700b537461b70c5f0000"), 55 | Maker: common.HexToAddress("0x7a9f3cd060ab180f36c17fe6bdf9974f577d77aa"), 56 | Taker: common.HexToAddress("0xae55690d4b079460e6ac28aaa58c9ec7b73a7485"), 57 | BaseToken: common.HexToAddress("0xe41d2489571d322189246dafa5ebde1f4699f498"), 58 | QuoteToken: common.HexToAddress("0x12459c951127e0c374ff9105dda097662a027093"), 59 | Hash: common.HexToHash("0xb9070a2d333403c255ce71ddf6e795053599b2e885321de40353832b96d8880a"), 60 | OrderHash: common.HexToHash("0x6d9ad89548c9e3ce4c97825d027291477f2c44a8caef792095f2cabc978493ff"), 61 | PairName: "ZRX/WETH", 62 | TradeNonce: big.NewInt(100), 63 | Signature: &Signature{ 64 | V: 28, 65 | R: common.HexToHash("0x10b30eb0072a4f0a38b6fca0b731cba15eb2e1702845d97c1230b53a839bcb85"), 66 | S: common.HexToHash("0x6d9ad89548c9e3ce4c97825d027291477f2c44a8caef792095f2cabc978493ff"), 67 | }, 68 | PricePoint: big.NewInt(10000), 69 | Side: "BUY", 70 | Amount: big.NewInt(100), 71 | CreatedAt: time.Unix(1405544146, 0), 72 | UpdatedAt: time.Unix(1405544146, 0), 73 | } 74 | 75 | data, err := bson.Marshal(expected) 76 | if err != nil { 77 | t.Error(err) 78 | } 79 | 80 | decoded := &Trade{} 81 | if err := bson.Unmarshal(data, decoded); err != nil { 82 | t.Error(err) 83 | } 84 | 85 | assert.Equal(t, decoded, expected) 86 | } 87 | -------------------------------------------------------------------------------- /types/wallet.go: -------------------------------------------------------------------------------- 1 | package types 2 | 3 | import ( 4 | "crypto/ecdsa" 5 | "encoding/hex" 6 | "encoding/json" 7 | "fmt" 8 | 9 | "github.com/ethereum/go-ethereum/common" 10 | "github.com/ethereum/go-ethereum/crypto" 11 | "gopkg.in/mgo.v2/bson" 12 | ) 13 | 14 | // Wallet holds both the address and the private key of an ethereum account 15 | type Wallet struct { 16 | ID bson.ObjectId 17 | Address common.Address 18 | PrivateKey *ecdsa.PrivateKey 19 | Admin bool 20 | Operator bool 21 | } 22 | 23 | // NewWallet returns a new wallet object corresponding to a random private key 24 | func NewWallet() *Wallet { 25 | privateKey, _ := crypto.GenerateKey() 26 | address := crypto.PubkeyToAddress(privateKey.PublicKey) 27 | 28 | return &Wallet{ 29 | Address: address, 30 | PrivateKey: privateKey, 31 | } 32 | } 33 | 34 | // NewWalletFromPrivateKey returns a new wallet object corresponding 35 | // to a given private key 36 | func NewWalletFromPrivateKey(key string) *Wallet { 37 | privateKey, err := crypto.HexToECDSA(key) 38 | if err != nil { 39 | logger.Error(err) 40 | } 41 | 42 | return &Wallet{ 43 | Address: crypto.PubkeyToAddress(privateKey.PublicKey), 44 | PrivateKey: privateKey, 45 | } 46 | } 47 | 48 | // GetAddress returns the wallet address 49 | func (w *Wallet) GetAddress() string { 50 | return w.Address.Hex() 51 | } 52 | 53 | // GetPrivateKey returns the wallet private key 54 | func (w *Wallet) GetPrivateKey() string { 55 | return hex.EncodeToString(w.PrivateKey.D.Bytes()) 56 | } 57 | 58 | func (w *Wallet) Validate() error { 59 | return nil 60 | } 61 | 62 | type WalletRecord struct { 63 | ID bson.ObjectId `json:"id,omitempty" bson:"_id"` 64 | Address string `json:"address" bson:"address"` 65 | PrivateKey string `json:"privateKey" bson:"privateKey"` 66 | Admin bool `json:"admin" bson:"admin"` 67 | Operator bool `json:"operator" bson:"operator"` 68 | } 69 | 70 | func (w *Wallet) GetBSON() (interface{}, error) { 71 | return WalletRecord{ 72 | ID: w.ID, 73 | Address: w.Address.Hex(), 74 | PrivateKey: hex.EncodeToString(w.PrivateKey.D.Bytes()), 75 | Admin: w.Admin, 76 | }, nil 77 | } 78 | 79 | func (w *Wallet) SetBSON(raw bson.Raw) error { 80 | decoded := &WalletRecord{} 81 | err := raw.Unmarshal(decoded) 82 | if err != nil { 83 | logger.Error(err) 84 | return err 85 | } 86 | 87 | w.ID = decoded.ID 88 | w.Address = common.HexToAddress(decoded.Address) 89 | w.PrivateKey, err = crypto.HexToECDSA(decoded.PrivateKey) 90 | if err != nil { 91 | logger.Error(err) 92 | return err 93 | } 94 | 95 | w.Admin = decoded.Admin 96 | w.Operator = decoded.Operator 97 | return nil 98 | } 99 | 100 | // SignHash signs a hashed message with a wallet private key 101 | // and returns it as a Signature object 102 | func (w *Wallet) SignHash(h common.Hash) (*Signature, error) { 103 | message := crypto.Keccak256( 104 | []byte("\x19Ethereum Signed Message:\n32"), 105 | h.Bytes(), 106 | ) 107 | 108 | sigBytes, err := crypto.Sign(message, w.PrivateKey) 109 | if err != nil { 110 | return &Signature{}, err 111 | } 112 | 113 | sig := &Signature{ 114 | R: common.BytesToHash(sigBytes[0:32]), 115 | S: common.BytesToHash(sigBytes[32:64]), 116 | V: sigBytes[64] + 27, 117 | } 118 | 119 | return sig, nil 120 | } 121 | 122 | // SignTrade signs and sets the signature of a trade with a wallet private key 123 | func (w *Wallet) SignTrade(t *Trade) error { 124 | hash := t.ComputeHash() 125 | 126 | sig, err := w.SignHash(hash) 127 | if err != nil { 128 | return err 129 | } 130 | 131 | t.Hash = hash 132 | t.Signature = sig 133 | return nil 134 | } 135 | 136 | func (w *Wallet) SignOrder(o *Order) error { 137 | hash := o.ComputeHash() 138 | sig, err := w.SignHash(hash) 139 | if err != nil { 140 | return err 141 | } 142 | 143 | o.Hash = hash 144 | o.Signature = sig 145 | return nil 146 | } 147 | 148 | func (w *Wallet) Print() { 149 | b, err := json.MarshalIndent(w, "", " ") 150 | if err != nil { 151 | fmt.Println("Error: ", err) 152 | } 153 | 154 | fmt.Print(string(b)) 155 | } 156 | -------------------------------------------------------------------------------- /types/wallet_test.go: -------------------------------------------------------------------------------- 1 | package types 2 | 3 | import ( 4 | "encoding/hex" 5 | "reflect" 6 | "testing" 7 | 8 | "github.com/stretchr/testify/assert" 9 | "gopkg.in/mgo.v2/bson" 10 | ) 11 | 12 | func TestNewWallet(t *testing.T) { 13 | wallet := NewWallet() 14 | 15 | if reflect.TypeOf(*wallet) != reflect.TypeOf(Wallet{}) { 16 | t.Error("Wallet type is not correct") 17 | } 18 | 19 | address := wallet.GetAddress() 20 | if addressLength := len(address); addressLength != 42 { 21 | t.Error("Expected address length to be 40, but got: ", addressLength) 22 | } 23 | 24 | privateKey := wallet.GetPrivateKey() 25 | if privateKeyLength := len(privateKey); privateKeyLength != 64 { 26 | t.Error("Expected private key length to be 64, but got: ", privateKeyLength) 27 | } 28 | } 29 | 30 | func TestNewWalletFromPrivateKey(t *testing.T) { 31 | key := "7c78c6e2f65d0d84c44ac0f7b53d6e4dd7a82c35f51b251d387c2a69df712660" 32 | 33 | wallet := NewWalletFromPrivateKey(key) 34 | if address := wallet.GetAddress(); address != "0xE8E84ee367BC63ddB38d3D01bCCEF106c194dc47" { 35 | t.Error("Expected address to equal 0xE8E84ee367BC63ddB38d3D01bCCEF106c194dc47 but got: ", address) 36 | } 37 | } 38 | 39 | func TestBSON(t *testing.T) { 40 | key := "7c78c6e2f65d0d84c44ac0f7b53d6e4dd7a82c35f51b251d387c2a69df712660" 41 | w := NewWalletFromPrivateKey(key) 42 | w.ID = bson.NewObjectId() 43 | 44 | data, err := bson.Marshal(w) 45 | if err != nil { 46 | t.Error("some error:", err) 47 | } 48 | 49 | decoded := &Wallet{} 50 | bson.Unmarshal(data, decoded) 51 | 52 | assert.Equal( 53 | t, 54 | w.ID, 55 | decoded.ID, 56 | "ID should be encoded and decoded correctly", 57 | ) 58 | 59 | assert.Equal( 60 | t, 61 | decoded.Address.Hex(), 62 | "0xE8E84ee367BC63ddB38d3D01bCCEF106c194dc47", 63 | "Address should be encoded and decoded correctly", 64 | ) 65 | 66 | assert.Equal( 67 | t, 68 | hex.EncodeToString(decoded.PrivateKey.D.Bytes()), 69 | "7c78c6e2f65d0d84c44ac0f7b53d6e4dd7a82c35f51b251d387c2a69df712660", 70 | "Private key should be encoded and decoded correctly", 71 | ) 72 | } 73 | -------------------------------------------------------------------------------- /types/wsmsg.go: -------------------------------------------------------------------------------- 1 | package types 2 | 3 | import ( 4 | "encoding/json" 5 | "math/big" 6 | 7 | "github.com/ethereum/go-ethereum/common" 8 | ) 9 | 10 | // SubscriptionEvent is an enum signifies whether the incoming message is of type Subscribe or unsubscribe 11 | type SubscriptionEvent string 12 | 13 | // Enum members for SubscriptionEvent 14 | const ( 15 | SUBSCRIBE SubscriptionEvent = "subscribe" 16 | UNSUBSCRIBE SubscriptionEvent = "unsubscribe" 17 | Fetch SubscriptionEvent = "fetch" 18 | ) 19 | 20 | const TradeChannel = "trades" 21 | const OrderbookChannel = "order_book" 22 | const OrderChannel = "orders" 23 | const OHLCVChannel = "ohlcv" 24 | 25 | type WebSocketMessage struct { 26 | Channel string `json:"channel"` 27 | Payload WebSocketPayload `json:"payload"` 28 | } 29 | 30 | type WebSocketPayload struct { 31 | Type string `json:"type"` 32 | Hash string `json:"hash,omitempty"` 33 | Data interface{} `json:"data"` 34 | } 35 | 36 | type WebSocketSubscription struct { 37 | Event SubscriptionEvent `json:"event"` 38 | Pair PairSubDoc `json:"pair"` 39 | Params `json:"params"` 40 | } 41 | 42 | // Params is a sub document used to pass parameters in Subscription messages 43 | type Params struct { 44 | From int64 `json:"from"` 45 | To int64 `json:"to"` 46 | Duration int64 `json:"duration"` 47 | Units string `json:"units"` 48 | TickID string `json:"tickID"` 49 | } 50 | 51 | type SignaturePayload struct { 52 | Order *Order `json:"order"` 53 | Matches []*OrderTradePair `json:"matches"` 54 | } 55 | 56 | func NewOrderWebsocketMessage(o *Order) *WebSocketMessage { 57 | return &WebSocketMessage{ 58 | Channel: "orders", 59 | Payload: WebSocketPayload{ 60 | Type: "NEW_ORDER", 61 | Hash: o.Hash.Hex(), 62 | Data: o, 63 | }, 64 | } 65 | } 66 | 67 | func NewOrderAddedWebsocketMessage(o *Order, p *Pair, filled int64) *WebSocketMessage { 68 | o.Process(p) 69 | o.FilledAmount = big.NewInt(filled) 70 | o.Status = "OPEN" 71 | return &WebSocketMessage{ 72 | Channel: "orders", 73 | Payload: WebSocketPayload{ 74 | Type: "ORDER_ADDED", 75 | Hash: o.Hash.Hex(), 76 | Data: o, 77 | }, 78 | } 79 | } 80 | 81 | func NewOrderCancelWebsocketMessage(oc *OrderCancel) *WebSocketMessage { 82 | return &WebSocketMessage{ 83 | Channel: "orders", 84 | Payload: WebSocketPayload{ 85 | Type: "CANCEL_ORDER", 86 | Hash: oc.Hash.Hex(), 87 | Data: oc, 88 | }, 89 | } 90 | } 91 | 92 | func NewRequestSignaturesWebsocketMessage(hash common.Hash, m []*OrderTradePair, o *Order) *WebSocketMessage { 93 | return &WebSocketMessage{ 94 | Channel: "orders", 95 | Payload: WebSocketPayload{ 96 | Type: "REQUEST_SIGNATURE", 97 | Hash: hash.Hex(), 98 | Data: SignaturePayload{o, m}, 99 | }, 100 | } 101 | } 102 | 103 | func NewSubmitSignatureWebsocketMessage(hash string, m []*OrderTradePair, o *Order) *WebSocketMessage { 104 | return &WebSocketMessage{ 105 | Channel: "orders", 106 | Payload: WebSocketPayload{ 107 | Type: "SUBMIT_SIGNATURE", 108 | Hash: hash, 109 | Data: SignaturePayload{o, m}, 110 | }, 111 | } 112 | } 113 | 114 | func (w *WebSocketMessage) Print() { 115 | b, err := json.MarshalIndent(w, "", " ") 116 | if err != nil { 117 | logger.Error(err) 118 | } 119 | 120 | logger.Info(string(b)) 121 | } 122 | 123 | func (w *WebSocketPayload) Print() { 124 | b, err := json.MarshalIndent(w, "", " ") 125 | if err != nil { 126 | logger.Error(err) 127 | } 128 | 129 | logger.Info(string(b)) 130 | } 131 | -------------------------------------------------------------------------------- /utils/common.go: -------------------------------------------------------------------------------- 1 | package utils 2 | 3 | import ( 4 | "encoding/json" 5 | "fmt" 6 | "log" 7 | "strings" 8 | 9 | "github.com/ethereum/go-ethereum/common" 10 | ) 11 | 12 | // MockServices is a that tolds different mock services to be passed 13 | // around easily for testing setup 14 | 15 | // UintToPaddedString converts an int to string of length 19 by padding with 0 16 | func UintToPaddedString(num int64) string { 17 | return fmt.Sprintf("%019d", num) 18 | } 19 | 20 | // GetTickChannelID is used to get the channel id for OHLCV data streaming 21 | // it takes pairname, duration and units of data streaming 22 | func GetTickChannelID(bt, qt common.Address, unit string, duration int64) string { 23 | pair := GetPairKey(bt, qt) 24 | return fmt.Sprintf("%s::%d::%s", pair, duration, unit) 25 | } 26 | 27 | // GetPairKey return the pair key identifier corresponding to two 28 | func GetPairKey(bt, qt common.Address) string { 29 | return strings.ToLower(fmt.Sprintf("%s::%s", bt.Hex(), qt.Hex())) 30 | } 31 | 32 | func GetTradeChannelID(bt, qt common.Address) string { 33 | return strings.ToLower(fmt.Sprintf("%s::%s", bt.Hex(), qt.Hex())) 34 | } 35 | 36 | func GetOHLCVChannelID(bt, qt common.Address, unit string, duration int64) string { 37 | pair := GetPairKey(bt, qt) 38 | return fmt.Sprintf("%s::%d::%s", pair, duration, unit) 39 | } 40 | 41 | func GetOrderBookChannelID(bt, qt common.Address) string { 42 | return strings.ToLower(fmt.Sprintf("%s::%s", bt.Hex(), qt.Hex())) 43 | } 44 | 45 | func PrintJSON(x interface{}) { 46 | b, err := json.MarshalIndent(x, "", " ") 47 | if err != nil { 48 | fmt.Println("Error: ", err) 49 | } 50 | 51 | fmt.Print(string(b), "\n") 52 | } 53 | 54 | func PrintError(msg string, err error) { 55 | log.Printf("\n%v: %v\n", msg, err) 56 | } 57 | 58 | // Util function to handle unused variables while testing 59 | func Use(...interface{}) { 60 | 61 | } 62 | -------------------------------------------------------------------------------- /utils/httputils/httputils.go: -------------------------------------------------------------------------------- 1 | package httputils 2 | 3 | import ( 4 | "encoding/json" 5 | "net/http" 6 | ) 7 | 8 | func WriteError(w http.ResponseWriter, code int, message string) { 9 | WriteJSON(w, code, map[string]string{"error": message}) 10 | } 11 | 12 | func WriteJSON(w http.ResponseWriter, code int, payload interface{}) { 13 | response, _ := json.Marshal(payload) 14 | 15 | w.Header().Set("Content-Type", "application/json") 16 | w.WriteHeader(code) 17 | w.Write(response) 18 | } 19 | -------------------------------------------------------------------------------- /utils/log.go: -------------------------------------------------------------------------------- 1 | package utils 2 | 3 | import ( 4 | "io" 5 | "os" 6 | "path" 7 | "runtime" 8 | 9 | logging "github.com/op/go-logging" 10 | ) 11 | 12 | var Logger = NewLogger("main", "./logs/main.log") 13 | var OperatorLogger = NewLogger("operator", "./logs/operator.log") 14 | var EngineLogger = NewLogger("engine", "./logs/engine.log") 15 | var APILogger = NewLogger("api", "./logs/api.log") 16 | var RabbitLogger = NewLogger("rabbitmq", "./logs/rabbit.log") 17 | var TerminalLogger = NewColoredLogger() 18 | 19 | func NewLogger(module string, logFile string) *logging.Logger { 20 | _, fileName, _, _ := runtime.Caller(1) 21 | mainLogFile := path.Join(path.Dir(fileName), "../logs/main.log") 22 | logFile = path.Join(path.Dir(fileName), "../", logFile) 23 | 24 | logger, err := logging.GetLogger("api") 25 | if err != nil { 26 | panic(err) 27 | } 28 | 29 | var format = logging.MustStringFormatter( 30 | `%{level:.4s} %{time:15:04:05} at %{shortpkg}/%{shortfile} in %{shortfunc}():%{message}`, 31 | ) 32 | 33 | mainLog, err := os.OpenFile(mainLogFile, os.O_RDWR|os.O_CREATE|os.O_APPEND, 0666) 34 | if err != nil { 35 | panic(err) 36 | } 37 | 38 | log, err := os.OpenFile(logFile, os.O_RDWR|os.O_CREATE|os.O_APPEND, 0666) 39 | if err != nil { 40 | panic(err) 41 | } 42 | 43 | writer := io.MultiWriter(os.Stdout, mainLog, log) 44 | backend := logging.NewLogBackend(writer, "", 0) 45 | 46 | formattedBackend := logging.NewBackendFormatter(backend, format) 47 | leveledBackend := logging.AddModuleLevel(formattedBackend) 48 | 49 | logger.SetBackend(leveledBackend) 50 | return logger 51 | } 52 | 53 | func NewColoredLogger() *logging.Logger { 54 | logger, err := logging.GetLogger("colored") 55 | if err != nil { 56 | panic(err) 57 | } 58 | 59 | var format = logging.MustStringFormatter( 60 | `%{color}%{level:.4s} %{time:15:04:05} at %{shortpkg}/%{shortfile} in %{shortfunc}():%{color:reset} %{message}`, 61 | ) 62 | 63 | writer := io.MultiWriter(os.Stdout) 64 | backend := logging.NewLogBackend(writer, "", 0) 65 | 66 | formattedBackend := logging.NewBackendFormatter(backend, format) 67 | leveledBackend := logging.AddModuleLevel(formattedBackend) 68 | 69 | logger.SetBackend(leveledBackend) 70 | return logger 71 | } 72 | -------------------------------------------------------------------------------- /utils/math/big.go: -------------------------------------------------------------------------------- 1 | package math 2 | 3 | import "math/big" 4 | 5 | func Mul(x, y *big.Int) *big.Int { 6 | return big.NewInt(0).Mul(x, y) 7 | } 8 | 9 | func Div(x, y *big.Int) *big.Int { 10 | return big.NewInt(0).Div(x, y) 11 | } 12 | 13 | func Add(x, y *big.Int) *big.Int { 14 | return big.NewInt(0).Add(x, y) 15 | } 16 | 17 | func Sub(x, y *big.Int) *big.Int { 18 | return big.NewInt(0).Sub(x, y) 19 | } 20 | 21 | func Neg(x *big.Int) *big.Int { 22 | return big.NewInt(0).Neg(x) 23 | } 24 | 25 | func ToBigInt(s string) *big.Int { 26 | res := big.NewInt(0) 27 | res.SetString(s, 10) 28 | return res 29 | } 30 | 31 | func Max(a, b *big.Int) *big.Int { 32 | if a.Cmp(b) == 1 { 33 | return a 34 | } else { 35 | return b 36 | } 37 | } 38 | 39 | func IsZero(x *big.Int) bool { 40 | if x.Cmp(big.NewInt(0)) == 0 { 41 | return true 42 | } else { 43 | return false 44 | } 45 | } 46 | 47 | func IsEqual(x, y *big.Int) bool { 48 | if x.Cmp(y) == 0 { 49 | return true 50 | } else { 51 | return false 52 | } 53 | } 54 | 55 | func IsGreaterThan(x, y *big.Int) bool { 56 | if x.Cmp(y) == 1 { 57 | return true 58 | } else { 59 | return false 60 | } 61 | } 62 | 63 | func IsSmallerThan(x, y *big.Int) bool { 64 | if x.Cmp(y) == -1 { 65 | return true 66 | } else { 67 | return false 68 | } 69 | } 70 | 71 | func IsEqualOrGreaterThan(x, y *big.Int) bool { 72 | return (IsEqual(x, y) || IsGreaterThan(x, y)) 73 | } 74 | 75 | func IsEqualOrSmallerThan(x, y *big.Int) bool { 76 | return (IsEqual(x, y) || IsSmallerThan(x, y)) 77 | } 78 | -------------------------------------------------------------------------------- /utils/testutils/address.go: -------------------------------------------------------------------------------- 1 | package testutils 2 | 3 | import "github.com/ethereum/go-ethereum/common" 4 | 5 | func GetTestAddress1() common.Address { 6 | return common.HexToAddress("0x1") 7 | } 8 | 9 | func GetTestAddress2() common.Address { 10 | return common.HexToAddress("0x2") 11 | } 12 | 13 | func GetTestAddress3() common.Address { 14 | return common.HexToAddress("0x3") 15 | } 16 | -------------------------------------------------------------------------------- /utils/testutils/compare.go: -------------------------------------------------------------------------------- 1 | package testutils 2 | 3 | import ( 4 | "encoding/json" 5 | "testing" 6 | 7 | "github.com/Proofsuite/amp-matching-engine/types" 8 | "github.com/go-test/deep" 9 | "github.com/stretchr/testify/assert" 10 | "gopkg.in/mgo.v2/dbtest" 11 | ) 12 | 13 | func CompareOrder(t *testing.T, a, b *types.Order) { 14 | assert.Equal(t, a.ID, b.ID) 15 | assert.Equal(t, a.UserAddress, b.UserAddress) 16 | assert.Equal(t, a.ExchangeAddress, b.ExchangeAddress) 17 | assert.Equal(t, a.BuyToken, b.BuyToken) 18 | assert.Equal(t, a.SellToken, b.SellToken) 19 | assert.Equal(t, a.BaseToken, b.BaseToken) 20 | assert.Equal(t, a.BuyAmount, b.BuyAmount) 21 | assert.Equal(t, a.SellAmount, b.SellAmount) 22 | assert.Equal(t, a.PricePoint, b.PricePoint) 23 | assert.Equal(t, a.Amount, b.Amount) 24 | assert.Equal(t, a.FilledAmount, b.FilledAmount) 25 | assert.Equal(t, a.Status, b.Status) 26 | assert.Equal(t, a.Side, b.Side) 27 | assert.Equal(t, a.PairName, b.PairName) 28 | assert.Equal(t, a.Expires, b.Expires) 29 | assert.Equal(t, a.MakeFee, b.MakeFee) 30 | assert.Equal(t, a.Nonce, b.Nonce) 31 | assert.Equal(t, a.TakeFee, b.TakeFee) 32 | assert.Equal(t, a.Signature, b.Signature) 33 | assert.Equal(t, a.Hash, b.Hash) 34 | } 35 | 36 | func ComparePair(t *testing.T, a, b *types.Pair) { 37 | assert.Equal(t, a.ID, b.ID) 38 | assert.Equal(t, a.BaseTokenSymbol, b.BaseTokenSymbol) 39 | assert.Equal(t, a.BaseTokenAddress, b.BaseTokenAddress) 40 | assert.Equal(t, a.QuoteTokenSymbol, b.QuoteTokenSymbol) 41 | assert.Equal(t, a.QuoteTokenAddress, b.QuoteTokenAddress) 42 | assert.Equal(t, a.Active, b.Active) 43 | assert.Equal(t, a.MakeFee, b.MakeFee) 44 | assert.Equal(t, a.TakeFee, b.TakeFee) 45 | } 46 | 47 | func CompareToken(t *testing.T, a, b *types.Token) { 48 | assert.Equal(t, a.Name, b.Name) 49 | assert.Equal(t, a.Symbol, b.Symbol) 50 | assert.Equal(t, a.ContractAddress, b.ContractAddress) 51 | assert.Equal(t, a.Decimal, b.Decimal) 52 | assert.Equal(t, a.Active, b.Active) 53 | assert.Equal(t, a.Quote, b.Quote) 54 | assert.Equal(t, a.ID, b.ID) 55 | } 56 | 57 | func CompareTrade(t *testing.T, a, b *types.Trade) { 58 | assert.Equal(t, a.ID, b.ID) 59 | assert.Equal(t, a.Maker, b.Maker) 60 | assert.Equal(t, a.Taker, b.Taker) 61 | assert.Equal(t, a.BaseToken, b.BaseToken) 62 | assert.Equal(t, a.QuoteToken, b.QuoteToken) 63 | assert.Equal(t, a.OrderHash, b.OrderHash) 64 | assert.Equal(t, a.Hash, b.Hash) 65 | assert.Equal(t, a.PairName, b.PairName) 66 | assert.Equal(t, a.TradeNonce, b.TradeNonce) 67 | assert.Equal(t, a.Signature, b.Signature) 68 | assert.Equal(t, a.TxHash, b.TxHash) 69 | 70 | assert.Equal(t, a.Side, b.Side) 71 | assert.Equal(t, a.Amount, b.Amount) 72 | } 73 | 74 | func CompareAccount(t *testing.T, a, b *types.Account) { 75 | assert.Equal(t, a.Address, b.Address) 76 | assert.Equal(t, a.TokenBalances, b.TokenBalances) 77 | assert.Equal(t, a.IsBlocked, b.IsBlocked) 78 | } 79 | 80 | func CompareAccountStrict(t *testing.T, a, b *types.Account) { 81 | assert.Equal(t, a.ID, b.ID) 82 | assert.Equal(t, a.Address, b.Address) 83 | assert.Equal(t, a.TokenBalances, b.TokenBalances) 84 | assert.Equal(t, a.IsBlocked, b.IsBlocked) 85 | assert.Equal(t, a.UpdatedAt, b.UpdatedAt) 86 | assert.Equal(t, a.CreatedAt, b.CreatedAt) 87 | } 88 | 89 | func Compare(t *testing.T, expected interface{}, value interface{}) { 90 | expectedBytes, _ := json.Marshal(expected) 91 | bytes, _ := json.Marshal(value) 92 | 93 | assert.JSONEqf(t, string(expectedBytes), string(bytes), "") 94 | } 95 | 96 | func CompareStructs(t *testing.T, expected interface{}, order interface{}) { 97 | diff := deep.Equal(expected, order) 98 | if diff != nil { 99 | t.Errorf("\n%+v\nGot: \n%+v\n\n", expected, order) 100 | } 101 | } 102 | 103 | func NewDBTestServer() *dbtest.DBServer { 104 | return &dbtest.DBServer{} 105 | } 106 | -------------------------------------------------------------------------------- /utils/testutils/mocks/account_service.go: -------------------------------------------------------------------------------- 1 | // Code generated by mockery v1.0.0. DO NOT EDIT. 2 | 3 | package mocks 4 | 5 | import bson "gopkg.in/mgo.v2/bson" 6 | import common "github.com/ethereum/go-ethereum/common" 7 | import mock "github.com/stretchr/testify/mock" 8 | 9 | import types "github.com/Proofsuite/amp-matching-engine/types" 10 | 11 | // AccountService is an autogenerated mock type for the AccountService type 12 | type AccountService struct { 13 | mock.Mock 14 | } 15 | 16 | // Create provides a mock function with given fields: account 17 | func (_m *AccountService) Create(account *types.Account) error { 18 | ret := _m.Called(account) 19 | 20 | var r0 error 21 | if rf, ok := ret.Get(0).(func(*types.Account) error); ok { 22 | r0 = rf(account) 23 | } else { 24 | r0 = ret.Error(0) 25 | } 26 | 27 | return r0 28 | } 29 | 30 | // GetAll provides a mock function with given fields: 31 | func (_m *AccountService) GetAll() ([]types.Account, error) { 32 | ret := _m.Called() 33 | 34 | var r0 []types.Account 35 | if rf, ok := ret.Get(0).(func() []types.Account); ok { 36 | r0 = rf() 37 | } else { 38 | if ret.Get(0) != nil { 39 | r0 = ret.Get(0).([]types.Account) 40 | } 41 | } 42 | 43 | var r1 error 44 | if rf, ok := ret.Get(1).(func() error); ok { 45 | r1 = rf() 46 | } else { 47 | r1 = ret.Error(1) 48 | } 49 | 50 | return r0, r1 51 | } 52 | 53 | // GetByAddress provides a mock function with given fields: a 54 | func (_m *AccountService) GetByAddress(a common.Address) (*types.Account, error) { 55 | ret := _m.Called(a) 56 | 57 | var r0 *types.Account 58 | if rf, ok := ret.Get(0).(func(common.Address) *types.Account); ok { 59 | r0 = rf(a) 60 | } else { 61 | if ret.Get(0) != nil { 62 | r0 = ret.Get(0).(*types.Account) 63 | } 64 | } 65 | 66 | var r1 error 67 | if rf, ok := ret.Get(1).(func(common.Address) error); ok { 68 | r1 = rf(a) 69 | } else { 70 | r1 = ret.Error(1) 71 | } 72 | 73 | return r0, r1 74 | } 75 | 76 | // GetByID provides a mock function with given fields: id 77 | func (_m *AccountService) GetByID(id bson.ObjectId) (*types.Account, error) { 78 | ret := _m.Called(id) 79 | 80 | var r0 *types.Account 81 | if rf, ok := ret.Get(0).(func(bson.ObjectId) *types.Account); ok { 82 | r0 = rf(id) 83 | } else { 84 | if ret.Get(0) != nil { 85 | r0 = ret.Get(0).(*types.Account) 86 | } 87 | } 88 | 89 | var r1 error 90 | if rf, ok := ret.Get(1).(func(bson.ObjectId) error); ok { 91 | r1 = rf(id) 92 | } else { 93 | r1 = ret.Error(1) 94 | } 95 | 96 | return r0, r1 97 | } 98 | 99 | // GetTokenBalance provides a mock function with given fields: owner, token 100 | func (_m *AccountService) GetTokenBalance(owner common.Address, token common.Address) (*types.TokenBalance, error) { 101 | ret := _m.Called(owner, token) 102 | 103 | var r0 *types.TokenBalance 104 | if rf, ok := ret.Get(0).(func(common.Address, common.Address) *types.TokenBalance); ok { 105 | r0 = rf(owner, token) 106 | } else { 107 | if ret.Get(0) != nil { 108 | r0 = ret.Get(0).(*types.TokenBalance) 109 | } 110 | } 111 | 112 | var r1 error 113 | if rf, ok := ret.Get(1).(func(common.Address, common.Address) error); ok { 114 | r1 = rf(owner, token) 115 | } else { 116 | r1 = ret.Error(1) 117 | } 118 | 119 | return r0, r1 120 | } 121 | 122 | // GetTokenBalances provides a mock function with given fields: owner 123 | func (_m *AccountService) GetTokenBalances(owner common.Address) (map[common.Address]*types.TokenBalance, error) { 124 | ret := _m.Called(owner) 125 | 126 | var r0 map[common.Address]*types.TokenBalance 127 | if rf, ok := ret.Get(0).(func(common.Address) map[common.Address]*types.TokenBalance); ok { 128 | r0 = rf(owner) 129 | } else { 130 | if ret.Get(0) != nil { 131 | r0 = ret.Get(0).(map[common.Address]*types.TokenBalance) 132 | } 133 | } 134 | 135 | var r1 error 136 | if rf, ok := ret.Get(1).(func(common.Address) error); ok { 137 | r1 = rf(owner) 138 | } else { 139 | r1 = ret.Error(1) 140 | } 141 | 142 | return r0, r1 143 | } 144 | -------------------------------------------------------------------------------- /utils/testutils/mocks/engine.go: -------------------------------------------------------------------------------- 1 | // Code generated by mockery v1.0.0. DO NOT EDIT. 2 | 3 | package mocks 4 | 5 | import big "math/big" 6 | 7 | import mock "github.com/stretchr/testify/mock" 8 | import rabbitmq "github.com/Proofsuite/amp-matching-engine/rabbitmq" 9 | import types "github.com/Proofsuite/amp-matching-engine/types" 10 | 11 | // Engine is an autogenerated mock type for the Engine type 12 | type Engine struct { 13 | mock.Mock 14 | } 15 | 16 | // CancelOrder provides a mock function with given fields: order 17 | func (_m *Engine) CancelOrder(order *types.Order) (*types.EngineResponse, error) { 18 | ret := _m.Called(order) 19 | 20 | var r0 *types.EngineResponse 21 | if rf, ok := ret.Get(0).(func(*types.Order) *types.EngineResponse); ok { 22 | r0 = rf(order) 23 | } else { 24 | if ret.Get(0) != nil { 25 | r0 = ret.Get(0).(*types.EngineResponse) 26 | } 27 | } 28 | 29 | var r1 error 30 | if rf, ok := ret.Get(1).(func(*types.Order) error); ok { 31 | r1 = rf(order) 32 | } else { 33 | r1 = ret.Error(1) 34 | } 35 | 36 | return r0, r1 37 | } 38 | 39 | // CancelTrades provides a mock function with given fields: orders, amount 40 | func (_m *Engine) CancelTrades(orders []*types.Order, amount []*big.Int) error { 41 | ret := _m.Called(orders, amount) 42 | 43 | var r0 error 44 | if rf, ok := ret.Get(0).(func([]*types.Order, []*big.Int) error); ok { 45 | r0 = rf(orders, amount) 46 | } else { 47 | r0 = ret.Error(0) 48 | } 49 | 50 | return r0 51 | } 52 | 53 | // DeleteOrder provides a mock function with given fields: o 54 | func (_m *Engine) DeleteOrder(o *types.Order) error { 55 | ret := _m.Called(o) 56 | 57 | var r0 error 58 | if rf, ok := ret.Get(0).(func(*types.Order) error); ok { 59 | r0 = rf(o) 60 | } else { 61 | r0 = ret.Error(0) 62 | } 63 | 64 | return r0 65 | } 66 | 67 | // DeleteOrders provides a mock function with given fields: orders 68 | func (_m *Engine) DeleteOrders(orders ...types.Order) error { 69 | _va := make([]interface{}, len(orders)) 70 | for _i := range orders { 71 | _va[_i] = orders[_i] 72 | } 73 | var _ca []interface{} 74 | _ca = append(_ca, _va...) 75 | ret := _m.Called(_ca...) 76 | 77 | var r0 error 78 | if rf, ok := ret.Get(0).(func(...types.Order) error); ok { 79 | r0 = rf(orders...) 80 | } else { 81 | r0 = ret.Error(0) 82 | } 83 | 84 | return r0 85 | } 86 | 87 | // GetOrderBook provides a mock function with given fields: pair 88 | func (_m *Engine) GetOrderBook(pair *types.Pair) ([]*map[string]float64, []*map[string]float64, error) { 89 | ret := _m.Called(pair) 90 | 91 | var r0 []*map[string]float64 92 | if rf, ok := ret.Get(0).(func(*types.Pair) []*map[string]float64); ok { 93 | r0 = rf(pair) 94 | } else { 95 | if ret.Get(0) != nil { 96 | r0 = ret.Get(0).([]*map[string]float64) 97 | } 98 | } 99 | 100 | var r1 []*map[string]float64 101 | if rf, ok := ret.Get(1).(func(*types.Pair) []*map[string]float64); ok { 102 | r1 = rf(pair) 103 | } else { 104 | if ret.Get(1) != nil { 105 | r1 = ret.Get(1).([]*map[string]float64) 106 | } 107 | } 108 | 109 | var r2 error 110 | if rf, ok := ret.Get(2).(func(*types.Pair) error); ok { 111 | r2 = rf(pair) 112 | } else { 113 | r2 = ret.Error(2) 114 | } 115 | 116 | return r0, r1, r2 117 | } 118 | 119 | // GetRawOrderBook provides a mock function with given fields: pair 120 | func (_m *Engine) GetRawOrderBook(pair *types.Pair) ([][]types.Order, error) { 121 | ret := _m.Called(pair) 122 | 123 | var r0 [][]types.Order 124 | if rf, ok := ret.Get(0).(func(*types.Pair) [][]types.Order); ok { 125 | r0 = rf(pair) 126 | } else { 127 | if ret.Get(0) != nil { 128 | r0 = ret.Get(0).([][]types.Order) 129 | } 130 | } 131 | 132 | var r1 error 133 | if rf, ok := ret.Get(1).(func(*types.Pair) error); ok { 134 | r1 = rf(pair) 135 | } else { 136 | r1 = ret.Error(1) 137 | } 138 | 139 | return r0, r1 140 | } 141 | 142 | // HandleOrders provides a mock function with given fields: msg 143 | func (_m *Engine) HandleOrders(msg *rabbitmq.Message) error { 144 | ret := _m.Called(msg) 145 | 146 | var r0 error 147 | if rf, ok := ret.Get(0).(func(*rabbitmq.Message) error); ok { 148 | r0 = rf(msg) 149 | } else { 150 | r0 = ret.Error(0) 151 | } 152 | 153 | return r0 154 | } 155 | 156 | // RecoverOrders provides a mock function with given fields: orders 157 | func (_m *Engine) RecoverOrders(orders []*types.OrderTradePair) error { 158 | ret := _m.Called(orders) 159 | 160 | var r0 error 161 | if rf, ok := ret.Get(0).(func([]*types.OrderTradePair) error); ok { 162 | r0 = rf(orders) 163 | } else { 164 | r0 = ret.Error(0) 165 | } 166 | 167 | return r0 168 | } 169 | -------------------------------------------------------------------------------- /utils/testutils/mocks/ethereum.go: -------------------------------------------------------------------------------- 1 | // Code generated by mockery v1.0.0. DO NOT EDIT. 2 | 3 | package mocks 4 | 5 | import big "math/big" 6 | import common "github.com/ethereum/go-ethereum/common" 7 | 8 | import mock "github.com/stretchr/testify/mock" 9 | import types "github.com/ethereum/go-ethereum/core/types" 10 | 11 | // Ethereum is an autogenerated mock type for the Ethereum type 12 | type Ethereum struct { 13 | mock.Mock 14 | } 15 | 16 | // Allowance provides a mock function with given fields: owner, spender, token 17 | func (_m *Ethereum) Allowance(owner common.Address, spender common.Address, token common.Address) (*big.Int, error) { 18 | ret := _m.Called(owner, spender, token) 19 | 20 | var r0 *big.Int 21 | if rf, ok := ret.Get(0).(func(common.Address, common.Address, common.Address) *big.Int); ok { 22 | r0 = rf(owner, spender, token) 23 | } else { 24 | if ret.Get(0) != nil { 25 | r0 = ret.Get(0).(*big.Int) 26 | } 27 | } 28 | 29 | var r1 error 30 | if rf, ok := ret.Get(1).(func(common.Address, common.Address, common.Address) error); ok { 31 | r1 = rf(owner, spender, token) 32 | } else { 33 | r1 = ret.Error(1) 34 | } 35 | 36 | return r0, r1 37 | } 38 | 39 | // BalanceOf provides a mock function with given fields: owner, token 40 | func (_m *Ethereum) BalanceOf(owner common.Address, token common.Address) (*big.Int, error) { 41 | ret := _m.Called(owner, token) 42 | 43 | var r0 *big.Int 44 | if rf, ok := ret.Get(0).(func(common.Address, common.Address) *big.Int); ok { 45 | r0 = rf(owner, token) 46 | } else { 47 | if ret.Get(0) != nil { 48 | r0 = ret.Get(0).(*big.Int) 49 | } 50 | } 51 | 52 | var r1 error 53 | if rf, ok := ret.Get(1).(func(common.Address, common.Address) error); ok { 54 | r1 = rf(owner, token) 55 | } else { 56 | r1 = ret.Error(1) 57 | } 58 | 59 | return r0, r1 60 | } 61 | 62 | // ExchangeAllowance provides a mock function with given fields: owner, token 63 | func (_m *Ethereum) ExchangeAllowance(owner common.Address, token common.Address) (*big.Int, error) { 64 | ret := _m.Called(owner, token) 65 | 66 | var r0 *big.Int 67 | if rf, ok := ret.Get(0).(func(common.Address, common.Address) *big.Int); ok { 68 | r0 = rf(owner, token) 69 | } else { 70 | if ret.Get(0) != nil { 71 | r0 = ret.Get(0).(*big.Int) 72 | } 73 | } 74 | 75 | var r1 error 76 | if rf, ok := ret.Get(1).(func(common.Address, common.Address) error); ok { 77 | r1 = rf(owner, token) 78 | } else { 79 | r1 = ret.Error(1) 80 | } 81 | 82 | return r0, r1 83 | } 84 | 85 | // GetBalanceAt provides a mock function with given fields: a 86 | func (_m *Ethereum) GetBalanceAt(a common.Address) (*big.Int, error) { 87 | ret := _m.Called(a) 88 | 89 | var r0 *big.Int 90 | if rf, ok := ret.Get(0).(func(common.Address) *big.Int); ok { 91 | r0 = rf(a) 92 | } else { 93 | if ret.Get(0) != nil { 94 | r0 = ret.Get(0).(*big.Int) 95 | } 96 | } 97 | 98 | var r1 error 99 | if rf, ok := ret.Get(1).(func(common.Address) error); ok { 100 | r1 = rf(a) 101 | } else { 102 | r1 = ret.Error(1) 103 | } 104 | 105 | return r0, r1 106 | } 107 | 108 | // GetPendingNonceAt provides a mock function with given fields: a 109 | func (_m *Ethereum) GetPendingNonceAt(a common.Address) (uint64, error) { 110 | ret := _m.Called(a) 111 | 112 | var r0 uint64 113 | if rf, ok := ret.Get(0).(func(common.Address) uint64); ok { 114 | r0 = rf(a) 115 | } else { 116 | r0 = ret.Get(0).(uint64) 117 | } 118 | 119 | var r1 error 120 | if rf, ok := ret.Get(1).(func(common.Address) error); ok { 121 | r1 = rf(a) 122 | } else { 123 | r1 = ret.Error(1) 124 | } 125 | 126 | return r0, r1 127 | } 128 | 129 | // WaitMined provides a mock function with given fields: tx 130 | func (_m *Ethereum) WaitMined(tx *types.Transaction) (*types.Receipt, error) { 131 | ret := _m.Called(tx) 132 | 133 | var r0 *types.Receipt 134 | if rf, ok := ret.Get(0).(func(*types.Transaction) *types.Receipt); ok { 135 | r0 = rf(tx) 136 | } else { 137 | if ret.Get(0) != nil { 138 | r0 = ret.Get(0).(*types.Receipt) 139 | } 140 | } 141 | 142 | var r1 error 143 | if rf, ok := ret.Get(1).(func(*types.Transaction) error); ok { 144 | r1 = rf(tx) 145 | } else { 146 | r1 = ret.Error(1) 147 | } 148 | 149 | return r0, r1 150 | } 151 | -------------------------------------------------------------------------------- /utils/testutils/mocks/ethereum_service.go: -------------------------------------------------------------------------------- 1 | // Code generated by mockery v1.0.0. DO NOT EDIT. 2 | 3 | package mocks 4 | 5 | import big "math/big" 6 | import common "github.com/ethereum/go-ethereum/common" 7 | 8 | import mock "github.com/stretchr/testify/mock" 9 | import types "github.com/ethereum/go-ethereum/core/types" 10 | 11 | // EthereumService is an autogenerated mock type for the EthereumService type 12 | type EthereumService struct { 13 | mock.Mock 14 | } 15 | 16 | // GetBalanceAt provides a mock function with given fields: a 17 | func (_m *EthereumService) GetBalanceAt(a common.Address) (*big.Int, error) { 18 | ret := _m.Called(a) 19 | 20 | var r0 *big.Int 21 | if rf, ok := ret.Get(0).(func(common.Address) *big.Int); ok { 22 | r0 = rf(a) 23 | } else { 24 | if ret.Get(0) != nil { 25 | r0 = ret.Get(0).(*big.Int) 26 | } 27 | } 28 | 29 | var r1 error 30 | if rf, ok := ret.Get(1).(func(common.Address) error); ok { 31 | r1 = rf(a) 32 | } else { 33 | r1 = ret.Error(1) 34 | } 35 | 36 | return r0, r1 37 | } 38 | 39 | // GetPendingNonceAt provides a mock function with given fields: a 40 | func (_m *EthereumService) GetPendingNonceAt(a common.Address) (uint64, error) { 41 | ret := _m.Called(a) 42 | 43 | var r0 uint64 44 | if rf, ok := ret.Get(0).(func(common.Address) uint64); ok { 45 | r0 = rf(a) 46 | } else { 47 | r0 = ret.Get(0).(uint64) 48 | } 49 | 50 | var r1 error 51 | if rf, ok := ret.Get(1).(func(common.Address) error); ok { 52 | r1 = rf(a) 53 | } else { 54 | r1 = ret.Error(1) 55 | } 56 | 57 | return r0, r1 58 | } 59 | 60 | // WaitMined provides a mock function with given fields: tx 61 | func (_m *EthereumService) WaitMined(tx *types.Transaction) (*types.Receipt, error) { 62 | ret := _m.Called(tx) 63 | 64 | var r0 *types.Receipt 65 | if rf, ok := ret.Get(0).(func(*types.Transaction) *types.Receipt); ok { 66 | r0 = rf(tx) 67 | } else { 68 | if ret.Get(0) != nil { 69 | r0 = ret.Get(0).(*types.Receipt) 70 | } 71 | } 72 | 73 | var r1 error 74 | if rf, ok := ret.Get(1).(func(*types.Transaction) error); ok { 75 | r1 = rf(tx) 76 | } else { 77 | r1 = ret.Error(1) 78 | } 79 | 80 | return r0, r1 81 | } 82 | -------------------------------------------------------------------------------- /utils/testutils/mocks/ohlcv_service.go: -------------------------------------------------------------------------------- 1 | // Code generated by mockery v1.0.0. DO NOT EDIT. 2 | 3 | package mocks 4 | 5 | import ( 6 | common "github.com/ethereum/go-ethereum/common" 7 | mock "github.com/stretchr/testify/mock" 8 | 9 | types "github.com/Proofsuite/amp-matching-engine/types" 10 | "github.com/Proofsuite/amp-matching-engine/ws" 11 | ) 12 | 13 | // OHLCVService is an autogenerated mock type for the OHLCVService type 14 | type OHLCVService struct { 15 | mock.Mock 16 | } 17 | 18 | // GetOHLCV provides a mock function with given fields: p, duration, unit, timeInterval 19 | func (_m *OHLCVService) GetOHLCV(p []types.PairSubDoc, duration int64, unit string, timeInterval ...int64) ([]*types.Tick, error) { 20 | _va := make([]interface{}, len(timeInterval)) 21 | for _i := range timeInterval { 22 | _va[_i] = timeInterval[_i] 23 | } 24 | var _ca []interface{} 25 | _ca = append(_ca, p, duration, unit) 26 | _ca = append(_ca, _va...) 27 | ret := _m.Called(_ca...) 28 | 29 | var r0 []*types.Tick 30 | if rf, ok := ret.Get(0).(func([]types.PairSubDoc, int64, string, ...int64) []*types.Tick); ok { 31 | r0 = rf(p, duration, unit, timeInterval...) 32 | } else { 33 | if ret.Get(0) != nil { 34 | r0 = ret.Get(0).([]*types.Tick) 35 | } 36 | } 37 | 38 | var r1 error 39 | if rf, ok := ret.Get(1).(func([]types.PairSubDoc, int64, string, ...int64) error); ok { 40 | r1 = rf(p, duration, unit, timeInterval...) 41 | } else { 42 | r1 = ret.Error(1) 43 | } 44 | 45 | return r0, r1 46 | } 47 | 48 | // Subscribe provides a mock function with given fields: conn, bt, qt, p 49 | func (_m *OHLCVService) Subscribe(conn *ws.Conn, bt common.Address, qt common.Address, p *types.Params) { 50 | _m.Called(conn, bt, qt, p) 51 | } 52 | 53 | // Unsubscribe provides a mock function with given fields: conn, bt, qt, p 54 | func (_m *OHLCVService) Unsubscribe(conn *ws.Conn, bt common.Address, qt common.Address, p *types.Params) { 55 | _m.Called(conn, bt, qt, p) 56 | } 57 | -------------------------------------------------------------------------------- /utils/testutils/mocks/orderbook_service.go: -------------------------------------------------------------------------------- 1 | // Code generated by mockery v1.0.0. DO NOT EDIT. 2 | 3 | package mocks 4 | 5 | import ( 6 | "github.com/Proofsuite/amp-matching-engine/ws" 7 | common "github.com/ethereum/go-ethereum/common" 8 | mock "github.com/stretchr/testify/mock" 9 | ) 10 | 11 | // OrderBookService is an autogenerated mock type for the OrderBookService type 12 | type OrderBookService struct { 13 | mock.Mock 14 | } 15 | 16 | // GetOrderBook provides a mock function with given fields: bt, qt 17 | func (_m *OrderBookService) GetOrderBook(bt common.Address, qt common.Address) (map[string]interface{}, error) { 18 | ret := _m.Called(bt, qt) 19 | 20 | var r0 map[string]interface{} 21 | if rf, ok := ret.Get(0).(func(common.Address, common.Address) map[string]interface{}); ok { 22 | r0 = rf(bt, qt) 23 | } else { 24 | if ret.Get(0) != nil { 25 | r0 = ret.Get(0).(map[string]interface{}) 26 | } 27 | } 28 | 29 | var r1 error 30 | if rf, ok := ret.Get(1).(func(common.Address, common.Address) error); ok { 31 | r1 = rf(bt, qt) 32 | } else { 33 | r1 = ret.Error(1) 34 | } 35 | 36 | return r0, r1 37 | } 38 | 39 | // Subscribe provides a mock function with given fields: conn, bt, qt 40 | func (_m *OrderBookService) Subscribe(conn *ws.Conn, bt common.Address, qt common.Address) { 41 | _m.Called(conn, bt, qt) 42 | } 43 | 44 | // Unsubscribe provides a mock function with given fields: conn, bt, qt 45 | func (_m *OrderBookService) Unsubscribe(conn *ws.Conn, bt common.Address, qt common.Address) { 46 | _m.Called(conn, bt, qt) 47 | } 48 | -------------------------------------------------------------------------------- /utils/testutils/mocks/pair_dao.go: -------------------------------------------------------------------------------- 1 | // Code generated by mockery v1.0.0. DO NOT EDIT. 2 | 3 | package mocks 4 | 5 | import bson "gopkg.in/mgo.v2/bson" 6 | import common "github.com/ethereum/go-ethereum/common" 7 | 8 | import mock "github.com/stretchr/testify/mock" 9 | import types "github.com/Proofsuite/amp-matching-engine/types" 10 | 11 | // PairDao is an autogenerated mock type for the PairDao type 12 | type PairDao struct { 13 | mock.Mock 14 | } 15 | 16 | // Create provides a mock function with given fields: o 17 | func (_m *PairDao) Create(o *types.Pair) error { 18 | ret := _m.Called(o) 19 | 20 | var r0 error 21 | if rf, ok := ret.Get(0).(func(*types.Pair) error); ok { 22 | r0 = rf(o) 23 | } else { 24 | r0 = ret.Error(0) 25 | } 26 | 27 | return r0 28 | } 29 | 30 | // GetAll provides a mock function with given fields: 31 | func (_m *PairDao) GetAll() ([]types.Pair, error) { 32 | ret := _m.Called() 33 | 34 | var r0 []types.Pair 35 | if rf, ok := ret.Get(0).(func() []types.Pair); ok { 36 | r0 = rf() 37 | } else { 38 | if ret.Get(0) != nil { 39 | r0 = ret.Get(0).([]types.Pair) 40 | } 41 | } 42 | 43 | var r1 error 44 | if rf, ok := ret.Get(1).(func() error); ok { 45 | r1 = rf() 46 | } else { 47 | r1 = ret.Error(1) 48 | } 49 | 50 | return r0, r1 51 | } 52 | 53 | // GetByBuySellTokenAddress provides a mock function with given fields: buyToken, sellToken 54 | func (_m *PairDao) GetByBuySellTokenAddress(buyToken common.Address, sellToken common.Address) (*types.Pair, error) { 55 | ret := _m.Called(buyToken, sellToken) 56 | 57 | var r0 *types.Pair 58 | if rf, ok := ret.Get(0).(func(common.Address, common.Address) *types.Pair); ok { 59 | r0 = rf(buyToken, sellToken) 60 | } else { 61 | if ret.Get(0) != nil { 62 | r0 = ret.Get(0).(*types.Pair) 63 | } 64 | } 65 | 66 | var r1 error 67 | if rf, ok := ret.Get(1).(func(common.Address, common.Address) error); ok { 68 | r1 = rf(buyToken, sellToken) 69 | } else { 70 | r1 = ret.Error(1) 71 | } 72 | 73 | return r0, r1 74 | } 75 | 76 | // GetByID provides a mock function with given fields: id 77 | func (_m *PairDao) GetByID(id bson.ObjectId) (*types.Pair, error) { 78 | ret := _m.Called(id) 79 | 80 | var r0 *types.Pair 81 | if rf, ok := ret.Get(0).(func(bson.ObjectId) *types.Pair); ok { 82 | r0 = rf(id) 83 | } else { 84 | if ret.Get(0) != nil { 85 | r0 = ret.Get(0).(*types.Pair) 86 | } 87 | } 88 | 89 | var r1 error 90 | if rf, ok := ret.Get(1).(func(bson.ObjectId) error); ok { 91 | r1 = rf(id) 92 | } else { 93 | r1 = ret.Error(1) 94 | } 95 | 96 | return r0, r1 97 | } 98 | 99 | // GetByName provides a mock function with given fields: name 100 | func (_m *PairDao) GetByName(name string) (*types.Pair, error) { 101 | ret := _m.Called(name) 102 | 103 | var r0 *types.Pair 104 | if rf, ok := ret.Get(0).(func(string) *types.Pair); ok { 105 | r0 = rf(name) 106 | } else { 107 | if ret.Get(0) != nil { 108 | r0 = ret.Get(0).(*types.Pair) 109 | } 110 | } 111 | 112 | var r1 error 113 | if rf, ok := ret.Get(1).(func(string) error); ok { 114 | r1 = rf(name) 115 | } else { 116 | r1 = ret.Error(1) 117 | } 118 | 119 | return r0, r1 120 | } 121 | 122 | // GetByTokenAddress provides a mock function with given fields: baseToken, quoteToken 123 | func (_m *PairDao) GetByTokenAddress(baseToken common.Address, quoteToken common.Address) (*types.Pair, error) { 124 | ret := _m.Called(baseToken, quoteToken) 125 | 126 | var r0 *types.Pair 127 | if rf, ok := ret.Get(0).(func(common.Address, common.Address) *types.Pair); ok { 128 | r0 = rf(baseToken, quoteToken) 129 | } else { 130 | if ret.Get(0) != nil { 131 | r0 = ret.Get(0).(*types.Pair) 132 | } 133 | } 134 | 135 | var r1 error 136 | if rf, ok := ret.Get(1).(func(common.Address, common.Address) error); ok { 137 | r1 = rf(baseToken, quoteToken) 138 | } else { 139 | r1 = ret.Error(1) 140 | } 141 | 142 | return r0, r1 143 | } 144 | 145 | // GetByTokenSymbols provides a mock function with given fields: baseTokenSymbol, quoteTokenSymbol 146 | func (_m *PairDao) GetByTokenSymbols(baseTokenSymbol string, quoteTokenSymbol string) (*types.Pair, error) { 147 | ret := _m.Called(baseTokenSymbol, quoteTokenSymbol) 148 | 149 | var r0 *types.Pair 150 | if rf, ok := ret.Get(0).(func(string, string) *types.Pair); ok { 151 | r0 = rf(baseTokenSymbol, quoteTokenSymbol) 152 | } else { 153 | if ret.Get(0) != nil { 154 | r0 = ret.Get(0).(*types.Pair) 155 | } 156 | } 157 | 158 | var r1 error 159 | if rf, ok := ret.Get(1).(func(string, string) error); ok { 160 | r1 = rf(baseTokenSymbol, quoteTokenSymbol) 161 | } else { 162 | r1 = ret.Error(1) 163 | } 164 | 165 | return r0, r1 166 | } 167 | -------------------------------------------------------------------------------- /utils/testutils/mocks/pair_service.go: -------------------------------------------------------------------------------- 1 | // Code generated by mockery v1.0.0. DO NOT EDIT. 2 | 3 | package mocks 4 | 5 | import bson "gopkg.in/mgo.v2/bson" 6 | import common "github.com/ethereum/go-ethereum/common" 7 | import mock "github.com/stretchr/testify/mock" 8 | 9 | import types "github.com/Proofsuite/amp-matching-engine/types" 10 | 11 | // PairService is an autogenerated mock type for the PairService type 12 | type PairService struct { 13 | mock.Mock 14 | } 15 | 16 | // Create provides a mock function with given fields: pair 17 | func (_m *PairService) Create(pair *types.Pair) error { 18 | ret := _m.Called(pair) 19 | 20 | var r0 error 21 | if rf, ok := ret.Get(0).(func(*types.Pair) error); ok { 22 | r0 = rf(pair) 23 | } else { 24 | r0 = ret.Error(0) 25 | } 26 | 27 | return r0 28 | } 29 | 30 | // GetAll provides a mock function with given fields: 31 | func (_m *PairService) GetAll() ([]types.Pair, error) { 32 | ret := _m.Called() 33 | 34 | var r0 []types.Pair 35 | if rf, ok := ret.Get(0).(func() []types.Pair); ok { 36 | r0 = rf() 37 | } else { 38 | if ret.Get(0) != nil { 39 | r0 = ret.Get(0).([]types.Pair) 40 | } 41 | } 42 | 43 | var r1 error 44 | if rf, ok := ret.Get(1).(func() error); ok { 45 | r1 = rf() 46 | } else { 47 | r1 = ret.Error(1) 48 | } 49 | 50 | return r0, r1 51 | } 52 | 53 | // GetByID provides a mock function with given fields: id 54 | func (_m *PairService) GetByID(id bson.ObjectId) (*types.Pair, error) { 55 | ret := _m.Called(id) 56 | 57 | var r0 *types.Pair 58 | if rf, ok := ret.Get(0).(func(bson.ObjectId) *types.Pair); ok { 59 | r0 = rf(id) 60 | } else { 61 | if ret.Get(0) != nil { 62 | r0 = ret.Get(0).(*types.Pair) 63 | } 64 | } 65 | 66 | var r1 error 67 | if rf, ok := ret.Get(1).(func(bson.ObjectId) error); ok { 68 | r1 = rf(id) 69 | } else { 70 | r1 = ret.Error(1) 71 | } 72 | 73 | return r0, r1 74 | } 75 | 76 | // GetByTokenAddress provides a mock function with given fields: bt, qt 77 | func (_m *PairService) GetByTokenAddress(bt common.Address, qt common.Address) (*types.Pair, error) { 78 | ret := _m.Called(bt, qt) 79 | 80 | var r0 *types.Pair 81 | if rf, ok := ret.Get(0).(func(common.Address, common.Address) *types.Pair); ok { 82 | r0 = rf(bt, qt) 83 | } else { 84 | if ret.Get(0) != nil { 85 | r0 = ret.Get(0).(*types.Pair) 86 | } 87 | } 88 | 89 | var r1 error 90 | if rf, ok := ret.Get(1).(func(common.Address, common.Address) error); ok { 91 | r1 = rf(bt, qt) 92 | } else { 93 | r1 = ret.Error(1) 94 | } 95 | 96 | return r0, r1 97 | } 98 | -------------------------------------------------------------------------------- /utils/testutils/mocks/provider.go: -------------------------------------------------------------------------------- 1 | // Code generated by mockery v1.0.0. DO NOT EDIT. 2 | 3 | package mocks 4 | 5 | import big "math/big" 6 | import common "github.com/ethereum/go-ethereum/common" 7 | 8 | import mock "github.com/stretchr/testify/mock" 9 | import types "github.com/ethereum/go-ethereum/core/types" 10 | 11 | // EthereumProvider is an autogenerated mock type for the EthereumProvider type 12 | type EthereumProvider struct { 13 | mock.Mock 14 | } 15 | 16 | // Allowance provides a mock function with given fields: owner, spender, token 17 | func (_m *EthereumProvider) Allowance(owner common.Address, spender common.Address, token common.Address) (*big.Int, error) { 18 | ret := _m.Called(owner, spender, token) 19 | 20 | var r0 *big.Int 21 | if rf, ok := ret.Get(0).(func(common.Address, common.Address, common.Address) *big.Int); ok { 22 | r0 = rf(owner, spender, token) 23 | } else { 24 | if ret.Get(0) != nil { 25 | r0 = ret.Get(0).(*big.Int) 26 | } 27 | } 28 | 29 | var r1 error 30 | if rf, ok := ret.Get(1).(func(common.Address, common.Address, common.Address) error); ok { 31 | r1 = rf(owner, spender, token) 32 | } else { 33 | r1 = ret.Error(1) 34 | } 35 | 36 | return r0, r1 37 | } 38 | 39 | // BalanceOf provides a mock function with given fields: owner, token 40 | func (_m *EthereumProvider) BalanceOf(owner common.Address, token common.Address) (*big.Int, error) { 41 | ret := _m.Called(owner, token) 42 | 43 | var r0 *big.Int 44 | if rf, ok := ret.Get(0).(func(common.Address, common.Address) *big.Int); ok { 45 | r0 = rf(owner, token) 46 | } else { 47 | if ret.Get(0) != nil { 48 | r0 = ret.Get(0).(*big.Int) 49 | } 50 | } 51 | 52 | var r1 error 53 | if rf, ok := ret.Get(1).(func(common.Address, common.Address) error); ok { 54 | r1 = rf(owner, token) 55 | } else { 56 | r1 = ret.Error(1) 57 | } 58 | 59 | return r0, r1 60 | } 61 | 62 | // ExchangeAllowance provides a mock function with given fields: owner, token 63 | func (_m *EthereumProvider) ExchangeAllowance(owner common.Address, token common.Address) (*big.Int, error) { 64 | ret := _m.Called(owner, token) 65 | 66 | var r0 *big.Int 67 | if rf, ok := ret.Get(0).(func(common.Address, common.Address) *big.Int); ok { 68 | r0 = rf(owner, token) 69 | } else { 70 | if ret.Get(0) != nil { 71 | r0 = ret.Get(0).(*big.Int) 72 | } 73 | } 74 | 75 | var r1 error 76 | if rf, ok := ret.Get(1).(func(common.Address, common.Address) error); ok { 77 | r1 = rf(owner, token) 78 | } else { 79 | r1 = ret.Error(1) 80 | } 81 | 82 | return r0, r1 83 | } 84 | 85 | // GetBalanceAt provides a mock function with given fields: a 86 | func (_m *EthereumProvider) GetBalanceAt(a common.Address) (*big.Int, error) { 87 | ret := _m.Called(a) 88 | 89 | var r0 *big.Int 90 | if rf, ok := ret.Get(0).(func(common.Address) *big.Int); ok { 91 | r0 = rf(a) 92 | } else { 93 | if ret.Get(0) != nil { 94 | r0 = ret.Get(0).(*big.Int) 95 | } 96 | } 97 | 98 | var r1 error 99 | if rf, ok := ret.Get(1).(func(common.Address) error); ok { 100 | r1 = rf(a) 101 | } else { 102 | r1 = ret.Error(1) 103 | } 104 | 105 | return r0, r1 106 | } 107 | 108 | // GetPendingNonceAt provides a mock function with given fields: a 109 | func (_m *EthereumProvider) GetPendingNonceAt(a common.Address) (uint64, error) { 110 | ret := _m.Called(a) 111 | 112 | var r0 uint64 113 | if rf, ok := ret.Get(0).(func(common.Address) uint64); ok { 114 | r0 = rf(a) 115 | } else { 116 | r0 = ret.Get(0).(uint64) 117 | } 118 | 119 | var r1 error 120 | if rf, ok := ret.Get(1).(func(common.Address) error); ok { 121 | r1 = rf(a) 122 | } else { 123 | r1 = ret.Error(1) 124 | } 125 | 126 | return r0, r1 127 | } 128 | 129 | // WaitMined provides a mock function with given fields: hash 130 | func (_m *EthereumProvider) WaitMined(hash common.Hash) (*types.Receipt, error) { 131 | ret := _m.Called(hash) 132 | 133 | var r0 *types.Receipt 134 | if rf, ok := ret.Get(0).(func(common.Hash) *types.Receipt); ok { 135 | r0 = rf(hash) 136 | } else { 137 | if ret.Get(0) != nil { 138 | r0 = ret.Get(0).(*types.Receipt) 139 | } 140 | } 141 | 142 | var r1 error 143 | if rf, ok := ret.Get(1).(func(common.Hash) error); ok { 144 | r1 = rf(hash) 145 | } else { 146 | r1 = ret.Error(1) 147 | } 148 | 149 | return r0, r1 150 | } 151 | -------------------------------------------------------------------------------- /utils/testutils/mocks/token_dao.go: -------------------------------------------------------------------------------- 1 | // Code generated by mockery v1.0.0. DO NOT EDIT. 2 | 3 | package mocks 4 | 5 | import bson "gopkg.in/mgo.v2/bson" 6 | import common "github.com/ethereum/go-ethereum/common" 7 | 8 | import mock "github.com/stretchr/testify/mock" 9 | import types "github.com/Proofsuite/amp-matching-engine/types" 10 | 11 | // TokenDao is an autogenerated mock type for the TokenDao type 12 | type TokenDao struct { 13 | mock.Mock 14 | } 15 | 16 | // Create provides a mock function with given fields: token 17 | func (_m *TokenDao) Create(token *types.Token) error { 18 | ret := _m.Called(token) 19 | 20 | var r0 error 21 | if rf, ok := ret.Get(0).(func(*types.Token) error); ok { 22 | r0 = rf(token) 23 | } else { 24 | r0 = ret.Error(0) 25 | } 26 | 27 | return r0 28 | } 29 | 30 | // Drop provides a mock function with given fields: 31 | func (_m *TokenDao) Drop() error { 32 | ret := _m.Called() 33 | 34 | var r0 error 35 | if rf, ok := ret.Get(0).(func() error); ok { 36 | r0 = rf() 37 | } else { 38 | r0 = ret.Error(0) 39 | } 40 | 41 | return r0 42 | } 43 | 44 | // GetAll provides a mock function with given fields: 45 | func (_m *TokenDao) GetAll() ([]types.Token, error) { 46 | ret := _m.Called() 47 | 48 | var r0 []types.Token 49 | if rf, ok := ret.Get(0).(func() []types.Token); ok { 50 | r0 = rf() 51 | } else { 52 | if ret.Get(0) != nil { 53 | r0 = ret.Get(0).([]types.Token) 54 | } 55 | } 56 | 57 | var r1 error 58 | if rf, ok := ret.Get(1).(func() error); ok { 59 | r1 = rf() 60 | } else { 61 | r1 = ret.Error(1) 62 | } 63 | 64 | return r0, r1 65 | } 66 | 67 | // GetBaseTokens provides a mock function with given fields: 68 | func (_m *TokenDao) GetBaseTokens() ([]types.Token, error) { 69 | ret := _m.Called() 70 | 71 | var r0 []types.Token 72 | if rf, ok := ret.Get(0).(func() []types.Token); ok { 73 | r0 = rf() 74 | } else { 75 | if ret.Get(0) != nil { 76 | r0 = ret.Get(0).([]types.Token) 77 | } 78 | } 79 | 80 | var r1 error 81 | if rf, ok := ret.Get(1).(func() error); ok { 82 | r1 = rf() 83 | } else { 84 | r1 = ret.Error(1) 85 | } 86 | 87 | return r0, r1 88 | } 89 | 90 | // GetByAddress provides a mock function with given fields: owner 91 | func (_m *TokenDao) GetByAddress(owner common.Address) (*types.Token, error) { 92 | ret := _m.Called(owner) 93 | 94 | var r0 *types.Token 95 | if rf, ok := ret.Get(0).(func(common.Address) *types.Token); ok { 96 | r0 = rf(owner) 97 | } else { 98 | if ret.Get(0) != nil { 99 | r0 = ret.Get(0).(*types.Token) 100 | } 101 | } 102 | 103 | var r1 error 104 | if rf, ok := ret.Get(1).(func(common.Address) error); ok { 105 | r1 = rf(owner) 106 | } else { 107 | r1 = ret.Error(1) 108 | } 109 | 110 | return r0, r1 111 | } 112 | 113 | // GetByID provides a mock function with given fields: id 114 | func (_m *TokenDao) GetByID(id bson.ObjectId) (*types.Token, error) { 115 | ret := _m.Called(id) 116 | 117 | var r0 *types.Token 118 | if rf, ok := ret.Get(0).(func(bson.ObjectId) *types.Token); ok { 119 | r0 = rf(id) 120 | } else { 121 | if ret.Get(0) != nil { 122 | r0 = ret.Get(0).(*types.Token) 123 | } 124 | } 125 | 126 | var r1 error 127 | if rf, ok := ret.Get(1).(func(bson.ObjectId) error); ok { 128 | r1 = rf(id) 129 | } else { 130 | r1 = ret.Error(1) 131 | } 132 | 133 | return r0, r1 134 | } 135 | 136 | // GetQuoteTokens provides a mock function with given fields: 137 | func (_m *TokenDao) GetQuoteTokens() ([]types.Token, error) { 138 | ret := _m.Called() 139 | 140 | var r0 []types.Token 141 | if rf, ok := ret.Get(0).(func() []types.Token); ok { 142 | r0 = rf() 143 | } else { 144 | if ret.Get(0) != nil { 145 | r0 = ret.Get(0).([]types.Token) 146 | } 147 | } 148 | 149 | var r1 error 150 | if rf, ok := ret.Get(1).(func() error); ok { 151 | r1 = rf() 152 | } else { 153 | r1 = ret.Error(1) 154 | } 155 | 156 | return r0, r1 157 | } 158 | -------------------------------------------------------------------------------- /utils/testutils/mocks/token_service.go: -------------------------------------------------------------------------------- 1 | // Code generated by mockery v1.0.0. DO NOT EDIT. 2 | 3 | package mocks 4 | 5 | import bson "gopkg.in/mgo.v2/bson" 6 | import common "github.com/ethereum/go-ethereum/common" 7 | 8 | import mock "github.com/stretchr/testify/mock" 9 | import types "github.com/Proofsuite/amp-matching-engine/types" 10 | 11 | // TokenService is an autogenerated mock type for the TokenService type 12 | type TokenService struct { 13 | mock.Mock 14 | } 15 | 16 | // Create provides a mock function with given fields: token 17 | func (_m *TokenService) Create(token *types.Token) error { 18 | ret := _m.Called(token) 19 | 20 | var r0 error 21 | if rf, ok := ret.Get(0).(func(*types.Token) error); ok { 22 | r0 = rf(token) 23 | } else { 24 | r0 = ret.Error(0) 25 | } 26 | 27 | return r0 28 | } 29 | 30 | // GetAll provides a mock function with given fields: 31 | func (_m *TokenService) GetAll() ([]types.Token, error) { 32 | ret := _m.Called() 33 | 34 | var r0 []types.Token 35 | if rf, ok := ret.Get(0).(func() []types.Token); ok { 36 | r0 = rf() 37 | } else { 38 | if ret.Get(0) != nil { 39 | r0 = ret.Get(0).([]types.Token) 40 | } 41 | } 42 | 43 | var r1 error 44 | if rf, ok := ret.Get(1).(func() error); ok { 45 | r1 = rf() 46 | } else { 47 | r1 = ret.Error(1) 48 | } 49 | 50 | return r0, r1 51 | } 52 | 53 | // GetBaseTokens provides a mock function with given fields: 54 | func (_m *TokenService) GetBaseTokens() ([]types.Token, error) { 55 | ret := _m.Called() 56 | 57 | var r0 []types.Token 58 | if rf, ok := ret.Get(0).(func() []types.Token); ok { 59 | r0 = rf() 60 | } else { 61 | if ret.Get(0) != nil { 62 | r0 = ret.Get(0).([]types.Token) 63 | } 64 | } 65 | 66 | var r1 error 67 | if rf, ok := ret.Get(1).(func() error); ok { 68 | r1 = rf() 69 | } else { 70 | r1 = ret.Error(1) 71 | } 72 | 73 | return r0, r1 74 | } 75 | 76 | // GetByAddress provides a mock function with given fields: addr 77 | func (_m *TokenService) GetByAddress(addr common.Address) (*types.Token, error) { 78 | ret := _m.Called(addr) 79 | 80 | var r0 *types.Token 81 | if rf, ok := ret.Get(0).(func(common.Address) *types.Token); ok { 82 | r0 = rf(addr) 83 | } else { 84 | if ret.Get(0) != nil { 85 | r0 = ret.Get(0).(*types.Token) 86 | } 87 | } 88 | 89 | var r1 error 90 | if rf, ok := ret.Get(1).(func(common.Address) error); ok { 91 | r1 = rf(addr) 92 | } else { 93 | r1 = ret.Error(1) 94 | } 95 | 96 | return r0, r1 97 | } 98 | 99 | // GetByID provides a mock function with given fields: id 100 | func (_m *TokenService) GetByID(id bson.ObjectId) (*types.Token, error) { 101 | ret := _m.Called(id) 102 | 103 | var r0 *types.Token 104 | if rf, ok := ret.Get(0).(func(bson.ObjectId) *types.Token); ok { 105 | r0 = rf(id) 106 | } else { 107 | if ret.Get(0) != nil { 108 | r0 = ret.Get(0).(*types.Token) 109 | } 110 | } 111 | 112 | var r1 error 113 | if rf, ok := ret.Get(1).(func(bson.ObjectId) error); ok { 114 | r1 = rf(id) 115 | } else { 116 | r1 = ret.Error(1) 117 | } 118 | 119 | return r0, r1 120 | } 121 | 122 | // GetQuoteTokens provides a mock function with given fields: 123 | func (_m *TokenService) GetQuoteTokens() ([]types.Token, error) { 124 | ret := _m.Called() 125 | 126 | var r0 []types.Token 127 | if rf, ok := ret.Get(0).(func() []types.Token); ok { 128 | r0 = rf() 129 | } else { 130 | if ret.Get(0) != nil { 131 | r0 = ret.Get(0).([]types.Token) 132 | } 133 | } 134 | 135 | var r1 error 136 | if rf, ok := ret.Get(1).(func() error); ok { 137 | r1 = rf() 138 | } else { 139 | r1 = ret.Error(1) 140 | } 141 | 142 | return r0, r1 143 | } 144 | -------------------------------------------------------------------------------- /utils/testutils/mocks/tx_service.go: -------------------------------------------------------------------------------- 1 | // Code generated by mockery v1.0.0. DO NOT EDIT. 2 | 3 | package mocks 4 | 5 | import bind "github.com/ethereum/go-ethereum/accounts/abi/bind" 6 | import mock "github.com/stretchr/testify/mock" 7 | 8 | import types "github.com/Proofsuite/amp-matching-engine/types" 9 | 10 | // TxService is an autogenerated mock type for the TxService type 11 | type TxService struct { 12 | mock.Mock 13 | } 14 | 15 | // GetCustomTxSendOptions provides a mock function with given fields: w 16 | func (_m *TxService) GetCustomTxSendOptions(w *types.Wallet) *bind.TransactOpts { 17 | ret := _m.Called(w) 18 | 19 | var r0 *bind.TransactOpts 20 | if rf, ok := ret.Get(0).(func(*types.Wallet) *bind.TransactOpts); ok { 21 | r0 = rf(w) 22 | } else { 23 | if ret.Get(0) != nil { 24 | r0 = ret.Get(0).(*bind.TransactOpts) 25 | } 26 | } 27 | 28 | return r0 29 | } 30 | 31 | // GetTxCallOptions provides a mock function with given fields: 32 | func (_m *TxService) GetTxCallOptions() *bind.CallOpts { 33 | ret := _m.Called() 34 | 35 | var r0 *bind.CallOpts 36 | if rf, ok := ret.Get(0).(func() *bind.CallOpts); ok { 37 | r0 = rf() 38 | } else { 39 | if ret.Get(0) != nil { 40 | r0 = ret.Get(0).(*bind.CallOpts) 41 | } 42 | } 43 | 44 | return r0 45 | } 46 | 47 | // GetTxDefaultSendOptions provides a mock function with given fields: 48 | func (_m *TxService) GetTxDefaultSendOptions() (*bind.TransactOpts, error) { 49 | ret := _m.Called() 50 | 51 | var r0 *bind.TransactOpts 52 | if rf, ok := ret.Get(0).(func() *bind.TransactOpts); ok { 53 | r0 = rf() 54 | } else { 55 | if ret.Get(0) != nil { 56 | r0 = ret.Get(0).(*bind.TransactOpts) 57 | } 58 | } 59 | 60 | var r1 error 61 | if rf, ok := ret.Get(1).(func() error); ok { 62 | r1 = rf() 63 | } else { 64 | r1 = ret.Error(1) 65 | } 66 | 67 | return r0, r1 68 | } 69 | 70 | // GetTxSendOptions provides a mock function with given fields: 71 | func (_m *TxService) GetTxSendOptions() (*bind.TransactOpts, error) { 72 | ret := _m.Called() 73 | 74 | var r0 *bind.TransactOpts 75 | if rf, ok := ret.Get(0).(func() *bind.TransactOpts); ok { 76 | r0 = rf() 77 | } else { 78 | if ret.Get(0) != nil { 79 | r0 = ret.Get(0).(*bind.TransactOpts) 80 | } 81 | } 82 | 83 | var r1 error 84 | if rf, ok := ret.Get(1).(func() error); ok { 85 | r1 = rf() 86 | } else { 87 | r1 = ret.Error(1) 88 | } 89 | 90 | return r0, r1 91 | } 92 | 93 | // SetTxSender provides a mock function with given fields: w 94 | func (_m *TxService) SetTxSender(w *types.Wallet) { 95 | _m.Called(w) 96 | } 97 | -------------------------------------------------------------------------------- /utils/testutils/mocks/wallet_dao.go: -------------------------------------------------------------------------------- 1 | // Code generated by mockery v1.0.0. DO NOT EDIT. 2 | 3 | package mocks 4 | 5 | import bson "gopkg.in/mgo.v2/bson" 6 | import common "github.com/ethereum/go-ethereum/common" 7 | 8 | import mock "github.com/stretchr/testify/mock" 9 | import types "github.com/Proofsuite/amp-matching-engine/types" 10 | 11 | // WalletDao is an autogenerated mock type for the WalletDao type 12 | type WalletDao struct { 13 | mock.Mock 14 | } 15 | 16 | // Create provides a mock function with given fields: wallet 17 | func (_m *WalletDao) Create(wallet *types.Wallet) error { 18 | ret := _m.Called(wallet) 19 | 20 | var r0 error 21 | if rf, ok := ret.Get(0).(func(*types.Wallet) error); ok { 22 | r0 = rf(wallet) 23 | } else { 24 | r0 = ret.Error(0) 25 | } 26 | 27 | return r0 28 | } 29 | 30 | // GetAll provides a mock function with given fields: 31 | func (_m *WalletDao) GetAll() ([]types.Wallet, error) { 32 | ret := _m.Called() 33 | 34 | var r0 []types.Wallet 35 | if rf, ok := ret.Get(0).(func() []types.Wallet); ok { 36 | r0 = rf() 37 | } else { 38 | if ret.Get(0) != nil { 39 | r0 = ret.Get(0).([]types.Wallet) 40 | } 41 | } 42 | 43 | var r1 error 44 | if rf, ok := ret.Get(1).(func() error); ok { 45 | r1 = rf() 46 | } else { 47 | r1 = ret.Error(1) 48 | } 49 | 50 | return r0, r1 51 | } 52 | 53 | // GetByAddress provides a mock function with given fields: addr 54 | func (_m *WalletDao) GetByAddress(addr common.Address) (*types.Wallet, error) { 55 | ret := _m.Called(addr) 56 | 57 | var r0 *types.Wallet 58 | if rf, ok := ret.Get(0).(func(common.Address) *types.Wallet); ok { 59 | r0 = rf(addr) 60 | } else { 61 | if ret.Get(0) != nil { 62 | r0 = ret.Get(0).(*types.Wallet) 63 | } 64 | } 65 | 66 | var r1 error 67 | if rf, ok := ret.Get(1).(func(common.Address) error); ok { 68 | r1 = rf(addr) 69 | } else { 70 | r1 = ret.Error(1) 71 | } 72 | 73 | return r0, r1 74 | } 75 | 76 | // GetByID provides a mock function with given fields: id 77 | func (_m *WalletDao) GetByID(id bson.ObjectId) (*types.Wallet, error) { 78 | ret := _m.Called(id) 79 | 80 | var r0 *types.Wallet 81 | if rf, ok := ret.Get(0).(func(bson.ObjectId) *types.Wallet); ok { 82 | r0 = rf(id) 83 | } else { 84 | if ret.Get(0) != nil { 85 | r0 = ret.Get(0).(*types.Wallet) 86 | } 87 | } 88 | 89 | var r1 error 90 | if rf, ok := ret.Get(1).(func(bson.ObjectId) error); ok { 91 | r1 = rf(id) 92 | } else { 93 | r1 = ret.Error(1) 94 | } 95 | 96 | return r0, r1 97 | } 98 | 99 | // GetDefaultAdminWallet provides a mock function with given fields: 100 | func (_m *WalletDao) GetDefaultAdminWallet() (*types.Wallet, error) { 101 | ret := _m.Called() 102 | 103 | var r0 *types.Wallet 104 | if rf, ok := ret.Get(0).(func() *types.Wallet); ok { 105 | r0 = rf() 106 | } else { 107 | if ret.Get(0) != nil { 108 | r0 = ret.Get(0).(*types.Wallet) 109 | } 110 | } 111 | 112 | var r1 error 113 | if rf, ok := ret.Get(1).(func() error); ok { 114 | r1 = rf() 115 | } else { 116 | r1 = ret.Error(1) 117 | } 118 | 119 | return r0, r1 120 | } 121 | 122 | // GetOperatorWallets provides a mock function with given fields: 123 | func (_m *WalletDao) GetOperatorWallets() ([]*types.Wallet, error) { 124 | ret := _m.Called() 125 | 126 | var r0 []*types.Wallet 127 | if rf, ok := ret.Get(0).(func() []*types.Wallet); ok { 128 | r0 = rf() 129 | } else { 130 | if ret.Get(0) != nil { 131 | r0 = ret.Get(0).([]*types.Wallet) 132 | } 133 | } 134 | 135 | var r1 error 136 | if rf, ok := ret.Get(1).(func() error); ok { 137 | r1 = rf() 138 | } else { 139 | r1 = ret.Error(1) 140 | } 141 | 142 | return r0, r1 143 | } 144 | -------------------------------------------------------------------------------- /utils/testutils/mocks/wallet_service.go: -------------------------------------------------------------------------------- 1 | // Code generated by mockery v1.0.0. DO NOT EDIT. 2 | 3 | package mocks 4 | 5 | import common "github.com/ethereum/go-ethereum/common" 6 | import mock "github.com/stretchr/testify/mock" 7 | 8 | import types "github.com/Proofsuite/amp-matching-engine/types" 9 | 10 | // WalletService is an autogenerated mock type for the WalletService type 11 | type WalletService struct { 12 | mock.Mock 13 | } 14 | 15 | // CreateAdminWallet provides a mock function with given fields: a 16 | func (_m *WalletService) CreateAdminWallet(a common.Address) (*types.Wallet, error) { 17 | ret := _m.Called(a) 18 | 19 | var r0 *types.Wallet 20 | if rf, ok := ret.Get(0).(func(common.Address) *types.Wallet); ok { 21 | r0 = rf(a) 22 | } else { 23 | if ret.Get(0) != nil { 24 | r0 = ret.Get(0).(*types.Wallet) 25 | } 26 | } 27 | 28 | var r1 error 29 | if rf, ok := ret.Get(1).(func(common.Address) error); ok { 30 | r1 = rf(a) 31 | } else { 32 | r1 = ret.Error(1) 33 | } 34 | 35 | return r0, r1 36 | } 37 | 38 | // GetAll provides a mock function with given fields: 39 | func (_m *WalletService) GetAll() ([]types.Wallet, error) { 40 | ret := _m.Called() 41 | 42 | var r0 []types.Wallet 43 | if rf, ok := ret.Get(0).(func() []types.Wallet); ok { 44 | r0 = rf() 45 | } else { 46 | if ret.Get(0) != nil { 47 | r0 = ret.Get(0).([]types.Wallet) 48 | } 49 | } 50 | 51 | var r1 error 52 | if rf, ok := ret.Get(1).(func() error); ok { 53 | r1 = rf() 54 | } else { 55 | r1 = ret.Error(1) 56 | } 57 | 58 | return r0, r1 59 | } 60 | 61 | // GetByAddress provides a mock function with given fields: a 62 | func (_m *WalletService) GetByAddress(a common.Address) (*types.Wallet, error) { 63 | ret := _m.Called(a) 64 | 65 | var r0 *types.Wallet 66 | if rf, ok := ret.Get(0).(func(common.Address) *types.Wallet); ok { 67 | r0 = rf(a) 68 | } else { 69 | if ret.Get(0) != nil { 70 | r0 = ret.Get(0).(*types.Wallet) 71 | } 72 | } 73 | 74 | var r1 error 75 | if rf, ok := ret.Get(1).(func(common.Address) error); ok { 76 | r1 = rf(a) 77 | } else { 78 | r1 = ret.Error(1) 79 | } 80 | 81 | return r0, r1 82 | } 83 | 84 | // GetDefaultAdminWallet provides a mock function with given fields: 85 | func (_m *WalletService) GetDefaultAdminWallet() (*types.Wallet, error) { 86 | ret := _m.Called() 87 | 88 | var r0 *types.Wallet 89 | if rf, ok := ret.Get(0).(func() *types.Wallet); ok { 90 | r0 = rf() 91 | } else { 92 | if ret.Get(0) != nil { 93 | r0 = ret.Get(0).(*types.Wallet) 94 | } 95 | } 96 | 97 | var r1 error 98 | if rf, ok := ret.Get(1).(func() error); ok { 99 | r1 = rf() 100 | } else { 101 | r1 = ret.Error(1) 102 | } 103 | 104 | return r0, r1 105 | } 106 | 107 | // GetOperatorWallets provides a mock function with given fields: 108 | func (_m *WalletService) GetOperatorWallets() ([]*types.Wallet, error) { 109 | ret := _m.Called() 110 | 111 | var r0 []*types.Wallet 112 | if rf, ok := ret.Get(0).(func() []*types.Wallet); ok { 113 | r0 = rf() 114 | } else { 115 | if ret.Get(0) != nil { 116 | r0 = ret.Get(0).([]*types.Wallet) 117 | } 118 | } 119 | 120 | var r1 error 121 | if rf, ok := ret.Get(1).(func() error); ok { 122 | r1 = rf() 123 | } else { 124 | r1 = ret.Error(1) 125 | } 126 | 127 | return r0, r1 128 | } 129 | -------------------------------------------------------------------------------- /utils/testutils/pair.go: -------------------------------------------------------------------------------- 1 | package testutils 2 | 3 | import ( 4 | "math/big" 5 | 6 | "github.com/Proofsuite/amp-matching-engine/types" 7 | "github.com/ethereum/go-ethereum/common" 8 | ) 9 | 10 | func GetZRXWETHTestPair() *types.Pair { 11 | return &types.Pair{ 12 | BaseTokenSymbol: "ZRX", 13 | BaseTokenAddress: common.HexToAddress("0x2034842261b82651885751fc293bba7ba5398156"), 14 | BaseTokenDecimal: 18, 15 | QuoteTokenSymbol: "WETH", 16 | PriceMultiplier: big.NewInt(1e6), 17 | QuoteTokenAddress: common.HexToAddress("0x276e16ada4b107332afd776691a7fbbaede168ef"), 18 | QuoteTokenDecimal: 18, 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /utils/testutils/testutils.go: -------------------------------------------------------------------------------- 1 | package testutils 2 | 3 | import ( 4 | "time" 5 | 6 | "github.com/Proofsuite/amp-matching-engine/ethereum" 7 | "github.com/Proofsuite/amp-matching-engine/utils/testutils/mocks" 8 | ) 9 | 10 | func Mine(client *ethereum.SimulatedClient) { 11 | nextTime := time.Now() 12 | nextTime = nextTime.Add(500 * time.Millisecond) 13 | time.Sleep(time.Until(nextTime)) 14 | 15 | client.Commit() 16 | go Mine(client) 17 | } 18 | 19 | type MockServices struct { 20 | WalletService *mocks.WalletService 21 | AccountService *mocks.AccountService 22 | EthereumService *mocks.EthereumService 23 | OrderService *mocks.OrderService 24 | OrderBookService *mocks.OrderBookService 25 | TokenService *mocks.TokenService 26 | TxService *mocks.TxService 27 | PairService *mocks.PairService 28 | TradeService *mocks.TradeService 29 | } 30 | 31 | type MockDaos struct { 32 | WalletDao *mocks.WalletDao 33 | AccountDao *mocks.AccountDao 34 | OrderDao *mocks.OrderDao 35 | TokenDao *mocks.TokenDao 36 | TradeDao *mocks.TradeDao 37 | PairDao *mocks.PairDao 38 | } 39 | 40 | func NewMockServices() *MockServices { 41 | return &MockServices{ 42 | WalletService: new(mocks.WalletService), 43 | AccountService: new(mocks.AccountService), 44 | EthereumService: new(mocks.EthereumService), 45 | OrderService: new(mocks.OrderService), 46 | OrderBookService: new(mocks.OrderBookService), 47 | TokenService: new(mocks.TokenService), 48 | TxService: new(mocks.TxService), 49 | PairService: new(mocks.PairService), 50 | } 51 | } 52 | 53 | func NewMockDaos() *MockDaos { 54 | return &MockDaos{ 55 | WalletDao: new(mocks.WalletDao), 56 | AccountDao: new(mocks.AccountDao), 57 | OrderDao: new(mocks.OrderDao), 58 | TokenDao: new(mocks.TokenDao), 59 | TradeDao: new(mocks.TradeDao), 60 | PairDao: new(mocks.PairDao), 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /utils/testutils/tokens.go: -------------------------------------------------------------------------------- 1 | package testutils 2 | 3 | import ( 4 | "github.com/Proofsuite/amp-matching-engine/types" 5 | "github.com/ethereum/go-ethereum/common" 6 | ) 7 | 8 | func GetTestZRXToken() types.Token { 9 | return types.Token{ 10 | Name: "ZRX", 11 | Symbol: "ZRX", 12 | Decimal: 18, 13 | ContractAddress: common.HexToAddress("0x2034842261b82651885751fc293bba7ba5398156"), 14 | } 15 | } 16 | 17 | func GetTestWETHToken() types.Token { 18 | return types.Token{ 19 | Name: "WETH", 20 | Symbol: "WETH", 21 | Decimal: 18, 22 | ContractAddress: common.HexToAddress("0x276e16ada4b107332afd776691a7fbbaede168ef"), 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /utils/testutils/trade.go: -------------------------------------------------------------------------------- 1 | package testutils 2 | 3 | import ( 4 | "math/big" 5 | 6 | "github.com/Proofsuite/amp-matching-engine/types" 7 | "github.com/ethereum/go-ethereum/common" 8 | "gopkg.in/mgo.v2/bson" 9 | ) 10 | 11 | func GetTestTrade1() types.Trade { 12 | return types.Trade{ 13 | ID: bson.ObjectIdHex("537f700b537461b70c5f0001"), 14 | Maker: common.HexToAddress("0x7a9f3cd060ab180f36c17fe6bdf9974f577d77aa"), 15 | Taker: common.HexToAddress("0xae55690d4b079460e6ac28aaa58c9ec7b73a7485"), 16 | BaseToken: common.HexToAddress("0xa114dd77c888aa2edb699de4faa2afbe4575ffd3"), 17 | QuoteToken: common.HexToAddress("0x4bc89ac6f1c55ea645294f3fed949813a768ac6d"), 18 | Hash: common.HexToHash("0xb9070a2d333403c255ce71ddf6e795053599b2e885321de40353832b96d8880a"), 19 | OrderHash: common.HexToHash("0x6d9ad89548c9e3ce4c97825d027291477f2c44a8caef792095f2cabc978493ff"), 20 | PairName: "ZRX/WETH", 21 | TradeNonce: big.NewInt(1), 22 | Signature: &types.Signature{ 23 | V: 28, 24 | R: common.HexToHash("0x10b30eb0072a4f0a38b6fca0b731cba15eb2e1702845d97c1230b53a839bcb85"), 25 | S: common.HexToHash("0x6d9ad89548c9e3ce4c97825d027291477f2c44a8caef792095f2cabc978493ff"), 26 | }, 27 | PricePoint: big.NewInt(10000000), 28 | Side: "BUY", 29 | Amount: big.NewInt(100), 30 | } 31 | } 32 | 33 | func GetTestTrade2() types.Trade { 34 | return types.Trade{ 35 | ID: bson.ObjectIdHex("537f700b537461b70c5f0007"), 36 | Maker: common.HexToAddress("0x7a9f3cd060ab180f36c17fe6bdf9974f577d77aa"), 37 | Taker: common.HexToAddress("0xae55690d4b079460e6ac28aaa58c9ec7b73a7485"), 38 | BaseToken: common.HexToAddress("0xa114dd77c888aa2edb699de4faa2afbe4575ffd3"), 39 | QuoteToken: common.HexToAddress("0x9aef1ccfe2171300465bb5f752477eb52cb0c59d"), 40 | Hash: common.HexToHash("0xecf27444c5ce65a88f73db628687fb9b4ac2686b5577df405958d47bee8eaa53"), 41 | OrderHash: common.HexToHash("0x400558b2f5a7b20dd06241c2313c08f652b297e819926b5a51a5abbc60f451e6"), 42 | PairName: "ZRX/DAI", 43 | TradeNonce: big.NewInt(3), 44 | Signature: &types.Signature{ 45 | V: 28, 46 | R: common.HexToHash("0x10b30eb0072a4f0a38b6fca0b731cba15eb2e1702845d97c1230b53a839bcb85"), 47 | S: common.HexToHash("0x6d9ad89548c9e3ce4c97825d027291477f2c44a8caef792095f2cabc978493ff"), 48 | }, 49 | PricePoint: big.NewInt(10000000), 50 | Side: "SELL", 51 | Amount: big.NewInt(100), 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /utils/testutils/wallet.go: -------------------------------------------------------------------------------- 1 | package testutils 2 | 3 | import "github.com/Proofsuite/amp-matching-engine/types" 4 | 5 | func GetTestWallet() *types.Wallet { 6 | return types.NewWalletFromPrivateKey("7c78c6e2f65d0d84c44ac0f7b53d6e4dd7a82c35f51b251d387c2a69df712660") 7 | } 8 | 9 | func GetTestWallet1() *types.Wallet { 10 | return types.NewWalletFromPrivateKey("7c78c6e2f65d0d84c44ac0f7b53d6e4dd7a82c35f51b251d387c2a69df712660") 11 | } 12 | 13 | func GetTestWallet2() *types.Wallet { 14 | return types.NewWalletFromPrivateKey("7c78c6e2f65d0d84c44ac0f7b53d6e4dd7a82c35f51b251d387c2a69df712661") 15 | } 16 | 17 | func GetTestWallet3() *types.Wallet { 18 | return types.NewWalletFromPrivateKey("7c78c6e2f65d0d84c44ac0f7b53d6e4dd7a82c35f51b251d387c2a69df712662") 19 | } 20 | 21 | func GetTestWallet4() *types.Wallet { 22 | return types.NewWalletFromPrivateKey("7c78c6e2f65d0d84c44ac0f7b53d6e4dd7a82c35f51b251d387c2a69df712663") 23 | } 24 | 25 | func GetTestWallet5() *types.Wallet { 26 | return types.NewWalletFromPrivateKey("7c78c6e2f65d0d84c44ac0f7b53d6e4dd7a82c35f51b251d387c2a69df712663") 27 | } 28 | -------------------------------------------------------------------------------- /utils/units.go: -------------------------------------------------------------------------------- 1 | package utils 2 | 3 | import ( 4 | "math/big" 5 | 6 | "github.com/Proofsuite/amp-matching-engine/utils/math" 7 | ) 8 | 9 | func Ethers(value int64) *big.Int { 10 | return math.Mul(big.NewInt(1e18), big.NewInt(value)) 11 | } 12 | -------------------------------------------------------------------------------- /utils/units/units.go: -------------------------------------------------------------------------------- 1 | package units 2 | 3 | import ( 4 | "math/big" 5 | 6 | "github.com/Proofsuite/amp-matching-engine/utils/math" 7 | ) 8 | 9 | func Ethers(value int64) *big.Int { 10 | return math.Mul(big.NewInt(1e18), big.NewInt(value)) 11 | } 12 | -------------------------------------------------------------------------------- /ws/ohlcv.go: -------------------------------------------------------------------------------- 1 | package ws 2 | 3 | import ( 4 | "errors" 5 | ) 6 | 7 | var ohlcvSocket *OHLCVSocket 8 | 9 | // OHLCVSocket holds the map of subscribtions subscribed to pair channels 10 | // corresponding to the key/event they have subscribed to. 11 | type OHLCVSocket struct { 12 | subscriptions map[string]map[*Conn]bool 13 | } 14 | 15 | // GetOHLCVSocket return singleton instance of PairSockets type struct 16 | func GetOHLCVSocket() *OHLCVSocket { 17 | if ohlcvSocket == nil { 18 | ohlcvSocket = &OHLCVSocket{make(map[string]map[*Conn]bool)} 19 | } 20 | 21 | return ohlcvSocket 22 | } 23 | 24 | // Subscribe handles the registration of connection to get 25 | // streaming data over the socker for any pair. 26 | func (s *OHLCVSocket) Subscribe(channelID string, conn *Conn) error { 27 | if conn == nil { 28 | return errors.New("Empty connection object") 29 | } 30 | 31 | if s.subscriptions[channelID] == nil { 32 | s.subscriptions[channelID] = make(map[*Conn]bool) 33 | } 34 | 35 | s.subscriptions[channelID][conn] = true 36 | return nil 37 | } 38 | 39 | // UnsubscribeHandler returns function of type unsubscribe handler, 40 | // it handles the unsubscription of pair in case of connection closing. 41 | func (s *OHLCVSocket) UnsubscribeHandler(channelID string) func(conn *Conn) { 42 | return func(conn *Conn) { 43 | s.Unsubscribe(channelID, conn) 44 | } 45 | } 46 | 47 | // Unsubscribe is used to unsubscribe the connection from listening to the key 48 | // subscribed to. It can be called on unsubscription message from user or due to some other reason by 49 | // system 50 | func (s *OHLCVSocket) Unsubscribe(channelID string, conn *Conn) { 51 | if s.subscriptions[channelID][conn] { 52 | s.subscriptions[channelID][conn] = false 53 | delete(s.subscriptions[channelID], conn) 54 | } 55 | } 56 | 57 | // BroadcastOHLCV Message streams message to all the subscribtions subscribed to the pair 58 | func (s *OHLCVSocket) BroadcastOHLCV(channelID string, p interface{}) error { 59 | for conn, status := range s.subscriptions[channelID] { 60 | if status { 61 | s.SendUpdateMessage(conn, p) 62 | } 63 | } 64 | 65 | return nil 66 | } 67 | 68 | // SendMessage sends a websocket message on the trade channel 69 | func (s *OHLCVSocket) SendMessage(conn *Conn, msgType string, p interface{}) { 70 | SendMessage(conn, OHLCVChannel, msgType, p) 71 | } 72 | 73 | // SendErrorMessage sends an error message on the trade channel 74 | func (s *OHLCVSocket) SendErrorMessage(conn *Conn, p interface{}) { 75 | s.SendMessage(conn, "ERROR", p) 76 | } 77 | 78 | // SendInitMessage is responsible for sending message on trade ohlcv channel at subscription 79 | func (s *OHLCVSocket) SendInitMessage(conn *Conn, p interface{}) { 80 | s.SendMessage(conn, "INIT", p) 81 | } 82 | 83 | // SendUpdateMessage is responsible for sending message on trade ohlcv channel at subscription 84 | func (s *OHLCVSocket) SendUpdateMessage(conn *Conn, p interface{}) { 85 | s.SendMessage(conn, "UPDATE", p) 86 | } 87 | -------------------------------------------------------------------------------- /ws/orderbook.go: -------------------------------------------------------------------------------- 1 | package ws 2 | 3 | import ( 4 | "errors" 5 | ) 6 | 7 | var orderbook *OrderBookSocket 8 | 9 | // OrderBookSocket holds the map of subscribtions subscribed to pair channels 10 | // corresponding to the key/event they have subscribed to. 11 | type OrderBookSocket struct { 12 | subscriptions map[string]map[*Conn]bool 13 | } 14 | 15 | // GetOrderBookSocket return singleton instance of PairSockets type struct 16 | func GetOrderBookSocket() *OrderBookSocket { 17 | if orderbook == nil { 18 | orderbook = &OrderBookSocket{make(map[string]map[*Conn]bool)} 19 | } 20 | 21 | return orderbook 22 | } 23 | 24 | // Subscribe handles the subscription of connection to get 25 | // streaming data over the socker for any pair. 26 | // pair := utils.GetPairKey(bt, qt) 27 | func (s *OrderBookSocket) Subscribe(channelID string, conn *Conn) error { 28 | if conn == nil { 29 | return errors.New("Empty connection object") 30 | } 31 | 32 | if s.subscriptions[channelID] == nil { 33 | s.subscriptions[channelID] = make(map[*Conn]bool) 34 | } 35 | 36 | s.subscriptions[channelID][conn] = true 37 | return nil 38 | } 39 | 40 | // UnsubscribeHandler returns function of type unsubscribe handler, 41 | // it handles the unsubscription of pair in case of connection closing. 42 | func (s *OrderBookSocket) UnsubscribeHandler(channelID string) func(conn *Conn) { 43 | return func(conn *Conn) { 44 | s.Unsubscribe(channelID, conn) 45 | } 46 | } 47 | 48 | // Unsubscribe is used to unsubscribe the connection from listening to the key 49 | // subscribed to. It can be called on unsubscription message from user or due to some other reason by 50 | // system 51 | func (s *OrderBookSocket) Unsubscribe(channelID string, conn *Conn) { 52 | if s.subscriptions[channelID][conn] { 53 | s.subscriptions[channelID][conn] = false 54 | delete(s.subscriptions[channelID], conn) 55 | } 56 | } 57 | 58 | // BroadcastMessage streams message to all the subscribtions subscribed to the pair 59 | func (s *OrderBookSocket) BroadcastMessage(channelID string, p interface{}) error { 60 | for conn, status := range s.subscriptions[channelID] { 61 | if status { 62 | s.SendUpdateMessage(conn, p) 63 | } 64 | } 65 | 66 | return nil 67 | } 68 | 69 | // SendMessage sends a message on the orderbook channel 70 | func (s *OrderBookSocket) SendMessage(conn *Conn, msgType string, data interface{}) { 71 | SendMessage(conn, LiteOrderBookChannel, msgType, data) 72 | } 73 | 74 | // SendErrorMessage sends error message on orderbookchannel 75 | func (s *OrderBookSocket) SendErrorMessage(conn *Conn, data interface{}) { 76 | s.SendMessage(conn, "ERROR", data) 77 | } 78 | 79 | // SendInitMessage sends INIT message on orderbookchannel on subscription event 80 | func (s *OrderBookSocket) SendInitMessage(conn *Conn, data interface{}) { 81 | s.SendMessage(conn, "INIT", data) 82 | } 83 | 84 | // SendUpdateMessage sends UPDATE message on orderbookchannel as new data is created 85 | func (s *OrderBookSocket) SendUpdateMessage(conn *Conn, data interface{}) { 86 | s.SendMessage(conn, "UPDATE", data) 87 | } 88 | -------------------------------------------------------------------------------- /ws/orders.go: -------------------------------------------------------------------------------- 1 | package ws 2 | 3 | import ( 4 | "sync" 5 | 6 | "github.com/Proofsuite/amp-matching-engine/types" 7 | "github.com/ethereum/go-ethereum/common" 8 | ) 9 | 10 | // OrderConn is websocket order connection struct 11 | // It holds the reference to connection and the channel of type OrderMessage 12 | type OrderConnection struct { 13 | Conn *Conn 14 | ReadChannel chan *types.WebSocketPayload 15 | Active bool 16 | Once sync.Once 17 | } 18 | 19 | var orderConnections map[string]*OrderConnection 20 | 21 | // GetOrderConn returns the connection associated with an order ID 22 | func GetOrderConnection(hash common.Hash) (conn *Conn) { 23 | c := orderConnections[hash.Hex()] 24 | if c == nil { 25 | logger.Warning("No connection found") 26 | return nil 27 | } 28 | 29 | return orderConnections[hash.Hex()].Conn 30 | } 31 | 32 | // GetOrderChannel returns the channel associated with an order ID 33 | func GetOrderChannel(h common.Hash) chan *types.WebSocketPayload { 34 | hash := h.Hex() 35 | 36 | if orderConnections[hash] == nil { 37 | return nil 38 | } 39 | 40 | if orderConnections[hash] == nil { 41 | return nil 42 | } else if !orderConnections[hash].Active { 43 | return nil 44 | } 45 | 46 | return orderConnections[hash].ReadChannel 47 | } 48 | 49 | // OrderSocketUnsubscribeHandler returns a function of type unsubscribe handler. 50 | func OrderSocketUnsubscribeHandler(h common.Hash) func(conn *Conn) { 51 | hash := h.Hex() 52 | 53 | return func(conn *Conn) { 54 | if orderConnections[hash] != nil { 55 | orderConnections[hash] = nil 56 | delete(orderConnections, hash) 57 | } 58 | } 59 | } 60 | 61 | // RegisterOrderConnection registers a connection with and orderID. 62 | // It is called whenever a message is recieved over order channel 63 | func RegisterOrderConnection(h common.Hash, conn *OrderConnection) { 64 | hash := h.Hex() 65 | 66 | if orderConnections == nil { 67 | orderConnections = make(map[string]*OrderConnection) 68 | } 69 | 70 | if orderConnections[hash] == nil { 71 | conn.Active = true 72 | orderConnections[hash] = conn 73 | } 74 | } 75 | 76 | // CloseOrderReadChannel is called whenever an order processing is done 77 | // and no further messages are to be accepted for an hash 78 | func CloseOrderReadChannel(h common.Hash) error { 79 | hash := h.Hex() 80 | 81 | orderConnections[hash].Once.Do(func() { 82 | close(orderConnections[hash].ReadChannel) 83 | orderConnections[hash].Active = false 84 | }) 85 | 86 | return nil 87 | } 88 | 89 | func SendOrderMessage(msgType string, hash common.Hash, data interface{}) { 90 | conn := GetOrderConnection(hash) 91 | SendMessage(conn, OrderChannel, msgType, data, hash) 92 | } 93 | -------------------------------------------------------------------------------- /ws/raw_orderbook.go: -------------------------------------------------------------------------------- 1 | package ws 2 | 3 | import ( 4 | "errors" 5 | ) 6 | 7 | var fullOrderBookSocket *RawOrderBookSocket 8 | 9 | // RawOrderBookSocket holds the map of subscribtions subscribed to pair channels 10 | // corresponding to the key/event they have subscribed to. 11 | type RawOrderBookSocket struct { 12 | subscriptions map[string]map[*Conn]bool 13 | } 14 | 15 | // GetRawOrderBookSocket return singleton instance of PairSockets type struct 16 | func GetRawOrderBookSocket() *RawOrderBookSocket { 17 | if fullOrderBookSocket == nil { 18 | fullOrderBookSocket = &RawOrderBookSocket{make(map[string]map[*Conn]bool)} 19 | } 20 | 21 | return fullOrderBookSocket 22 | } 23 | 24 | // Subscribe handles the subscription of connection to get 25 | // streaming data over the socker for any pair. 26 | // pair := utils.GetPairKey(bt, qt) 27 | func (s *RawOrderBookSocket) Subscribe(channelID string, conn *Conn) error { 28 | if conn == nil { 29 | return errors.New("Empty connection object") 30 | } 31 | 32 | if s.subscriptions[channelID] == nil { 33 | s.subscriptions[channelID] = make(map[*Conn]bool) 34 | } 35 | 36 | s.subscriptions[channelID][conn] = true 37 | return nil 38 | } 39 | 40 | // UnsubscribeHandler returns function of type unsubscribe handler, 41 | // it handles the unsubscription of pair in case of connection closing. 42 | func (s *RawOrderBookSocket) UnsubscribeHandler(channelID string) func(conn *Conn) { 43 | return func(conn *Conn) { 44 | s.Unsubscribe(channelID, conn) 45 | } 46 | } 47 | 48 | // Unsubscribe is used to unsubscribe the connection from listening to the key 49 | // subscribed to. It can be called on unsubscription message from user or due to some other reason by 50 | // system 51 | func (s *RawOrderBookSocket) Unsubscribe(channelID string, conn *Conn) { 52 | if s.subscriptions[channelID][conn] { 53 | s.subscriptions[channelID][conn] = false 54 | delete(s.subscriptions[channelID], conn) 55 | } 56 | } 57 | 58 | // BroadcastMessage streams message to all the subscribtions subscribed to the pair 59 | func (s *RawOrderBookSocket) BroadcastMessage(channelID string, p interface{}) error { 60 | for conn, status := range s.subscriptions[channelID] { 61 | if status { 62 | s.SendUpdateMessage(conn, p) 63 | } 64 | } 65 | 66 | return nil 67 | } 68 | 69 | // SendMessage sends a message on the orderbook channel 70 | func (s *RawOrderBookSocket) SendMessage(conn *Conn, msgType string, data interface{}) { 71 | SendMessage(conn, RawOrderBookChannel, msgType, data) 72 | } 73 | 74 | // SendErrorMessage sends error message on orderbookchannel 75 | func (s *RawOrderBookSocket) SendOrderMessage(conn *Conn, msg string) { 76 | s.SendMessage(conn, "ERROR", map[string]string{"Message": msg}) 77 | } 78 | 79 | // SendInitMessage sends INIT message on orderbookchannel on subscription event 80 | func (s *RawOrderBookSocket) SendInitMessage(conn *Conn, data interface{}) { 81 | s.SendMessage(conn, "INIT", data) 82 | } 83 | 84 | // SendUpdateMessage sends UPDATE message on orderbookchannel as new data is created 85 | func (s *RawOrderBookSocket) SendUpdateMessage(conn *Conn, data interface{}) { 86 | s.SendMessage(conn, "UPDATE", data) 87 | } 88 | 89 | func (s *RawOrderBookSocket) SendErrorMessage(conn *Conn, data interface{}) { 90 | s.SendMessage(conn, "ERROR", data) 91 | } 92 | -------------------------------------------------------------------------------- /ws/trades.go: -------------------------------------------------------------------------------- 1 | package ws 2 | 3 | var tradeSocket *TradeSocket 4 | 5 | // TradeSocket holds the map of connections subscribed to pair channels 6 | // corresponding to the key/event they have subscribed to. 7 | type TradeSocket struct { 8 | subscriptions map[string]map[*Conn]bool 9 | } 10 | 11 | func GetTradeSocket() *TradeSocket { 12 | if tradeSocket == nil { 13 | tradeSocket = &TradeSocket{make(map[string]map[*Conn]bool)} 14 | } 15 | 16 | return tradeSocket 17 | } 18 | 19 | // Subscribe registers a new websocket connections to the trade channel updates 20 | func (s *TradeSocket) Subscribe(channelID string, conn *Conn) error { 21 | if s.subscriptions[channelID] == nil { 22 | s.subscriptions[channelID] = make(map[*Conn]bool) 23 | } 24 | 25 | s.subscriptions[channelID][conn] = true 26 | return nil 27 | } 28 | 29 | // Unsubscribe removes a websocket connection from the trade channel updates 30 | func (s *TradeSocket) Unsubscribe(channelID string, conn *Conn) { 31 | if s.subscriptions[channelID][conn] { 32 | s.subscriptions[channelID][conn] = false 33 | delete(s.subscriptions[channelID], conn) 34 | } 35 | } 36 | 37 | // UnsubscribeHandler unsubscribes a connection from a certain trade channel id 38 | func (s *TradeSocket) UnsubscribeHandler(channelID string) func(conn *Conn) { 39 | return func(conn *Conn) { 40 | s.Unsubscribe(channelID, conn) 41 | } 42 | } 43 | 44 | // BroadcastMessage broadcasts trade message to all subscribed sockets 45 | func (s *TradeSocket) BroadcastMessage(channelID string, p interface{}) { 46 | go func() { 47 | for conn, active := range tradeSocket.subscriptions[channelID] { 48 | if active { 49 | s.SendUpdateMessage(conn, p) 50 | } 51 | } 52 | }() 53 | } 54 | 55 | // SendMessage sends a websocket message on the trade channel 56 | func (s *TradeSocket) SendMessage(conn *Conn, msgType string, p interface{}) { 57 | SendMessage(conn, TradeChannel, msgType, p) 58 | } 59 | 60 | // SendErrorMessage sends an error message on the trade channel 61 | func (s *TradeSocket) SendErrorMessage(conn *Conn, p interface{}) { 62 | s.SendMessage(conn, "ERROR", p) 63 | } 64 | 65 | // SendInitMessage is responsible for sending message on trade ohlcv channel at subscription 66 | func (s *TradeSocket) SendInitMessage(conn *Conn, p interface{}) { 67 | s.SendMessage(conn, "INIT", p) 68 | } 69 | 70 | // SendUpdateMessage is responsible for sending message on trade ohlcv channel at subscription 71 | func (s *TradeSocket) SendUpdateMessage(conn *Conn, p interface{}) { 72 | s.SendMessage(conn, "UPDATE", p) 73 | } 74 | --------------------------------------------------------------------------------