diff --git a/.idea/.gitignore b/.idea/.gitignore
new file mode 100644
index 0000000..1c2fda5
--- /dev/null
+++ b/.idea/.gitignore
@@ -0,0 +1,8 @@
+# Default ignored files
+/shelf/
+/workspace.xml
+# Editor-based HTTP Client requests
+/httpRequests/
+# Datasource local storage ignored files
+/dataSources/
+/dataSources.local.xml
diff --git a/.idea/bybit.go.api.iml b/.idea/bybit.go.api.iml
new file mode 100644
index 0000000..338a266
--- /dev/null
+++ b/.idea/bybit.go.api.iml
@@ -0,0 +1,9 @@
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/.idea/modules.xml b/.idea/modules.xml
new file mode 100644
index 0000000..7288a09
--- /dev/null
+++ b/.idea/modules.xml
@@ -0,0 +1,8 @@
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/.idea/vcs.xml b/.idea/vcs.xml
new file mode 100644
index 0000000..f245aa7
--- /dev/null
+++ b/.idea/vcs.xml
@@ -0,0 +1,7 @@
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/bybit_api_client.go b/bybit_api_client.go
new file mode 100644
index 0000000..15e0d8d
--- /dev/null
+++ b/bybit_api_client.go
@@ -0,0 +1,186 @@
+package bybit
+
+import (
+ "bybit.go.api/handlers"
+ "bytes"
+ "context"
+ "crypto/hmac"
+ "crypto/sha256"
+ "encoding/json"
+ "fmt"
+ "io"
+ "log"
+ "net/http"
+ "os"
+ "time"
+)
+
+// TimeInForceType define time in force type of order
+type TimeInForceType string
+
+// Client define API client
+type Client struct {
+ APIKey string
+ SecretKey string
+ BaseURL string
+ HTTPClient *http.Client
+ Debug bool
+ Logger *log.Logger
+ do doFunc
+}
+
+type doFunc func(req *http.Request) (*http.Response, error)
+
+// Globals
+const (
+ timestampKey = "X-BAPI-TIMESTAMP"
+ signatureKey = "X-BAPI-SIGN"
+ apiRequestKey = "X-BAPI-API-KEY"
+ recvWindowKey = "X-BAPI-RECV-WINDOW"
+ signTypeKey = "2"
+)
+
+func currentTimestamp() int64 {
+ return FormatTimestamp(time.Now())
+}
+
+// FormatTimestamp formats a time into Unix timestamp in milliseconds, as requested by Bybit.
+func FormatTimestamp(t time.Time) int64 {
+ return t.UnixNano() / int64(time.Millisecond)
+}
+
+func PrettyPrint(i interface{}) string {
+ s, _ := json.MarshalIndent(i, "", "\t")
+ return string(s)
+}
+
+func (c *Client) debug(format string, v ...interface{}) {
+ if c.Debug {
+ c.Logger.Printf(format, v...)
+ }
+}
+
+// NewClient Create client function for initialising new Bybit client
+func NewClient(apiKey string, secretKey string, baseURL ...string) *Client {
+ url := "https://api.bybit.com"
+
+ if len(baseURL) > 0 {
+ url = baseURL[0]
+ }
+
+ return &Client{
+ APIKey: apiKey,
+ SecretKey: secretKey,
+ BaseURL: url,
+ HTTPClient: http.DefaultClient,
+ Logger: log.New(os.Stderr, Name, log.LstdFlags),
+ }
+}
+
+func (c *Client) parseRequest(r *request, opts ...RequestOption) (err error) {
+ // set request options from user
+ for _, opt := range opts {
+ opt(r)
+ }
+ err = r.validate()
+ if err != nil {
+ return err
+ }
+
+ fullURL := fmt.Sprintf("%s%s", c.BaseURL, r.endpoint)
+
+ queryString := r.query.Encode()
+ body := &bytes.Buffer{}
+ bodyString := r.form.Encode()
+ header := http.Header{}
+ if r.header != nil {
+ header = r.header.Clone()
+ }
+ header.Set("User-Agent", fmt.Sprintf("%s/%s", Name, Version))
+ if bodyString != "" {
+ header.Set("Content-Type", "application/json")
+ body = bytes.NewBufferString(bodyString)
+ }
+ if r.secType == secTypeSigned {
+ header.Set(signTypeKey, "2")
+ header.Set(apiRequestKey, c.APIKey)
+ header.Set(timestampKey, fmt.Sprintf("%d", currentTimestamp()))
+ if r.recvWindow == "" {
+ header.Set(recvWindowKey, "5000")
+ } else {
+ header.Set(recvWindowKey, r.recvWindow)
+ }
+
+ var signatureBase string
+ if r.method == "GET" {
+ signatureBase = fmt.Sprintf("%d%s%s%s", FormatTimestamp(time.Now()), c.APIKey, r.recvWindow, queryString)
+ } else if r.method == "POST" {
+ signatureBase = fmt.Sprintf("%d%s%s%s", FormatTimestamp(time.Now()), c.APIKey, r.recvWindow, bodyString)
+ }
+
+ mac := hmac.New(sha256.New, []byte(c.SecretKey))
+ _, err = mac.Write([]byte(signatureBase))
+ if err != nil {
+ return err
+ }
+ signatureValue := fmt.Sprintf("%x", mac.Sum(nil))
+ header.Set(signatureKey, signatureValue)
+ }
+ if queryString != "" {
+ fullURL = fmt.Sprintf("%s?%s", fullURL, queryString)
+ }
+ // c.debug("full url: %s, body: %s", fullURL, bodyString)
+ r.fullURL = fullURL
+ r.header = header
+ r.body = body
+ return nil
+}
+
+func (c *Client) callAPI(ctx context.Context, r *request, opts ...RequestOption) (data []byte, err error) {
+ err = c.parseRequest(r, opts...)
+ req, err := http.NewRequest(r.method, r.fullURL, r.body)
+ if err != nil {
+ return []byte{}, err
+ }
+ req = req.WithContext(ctx)
+ req.Header = r.header
+ // c.debug("request: %#v", req)
+ f := c.do
+ if f == nil {
+ f = c.HTTPClient.Do
+ }
+ res, err := f(req)
+ if err != nil {
+ return []byte{}, err
+ }
+ data, err = io.ReadAll(res.Body)
+ if err != nil {
+ return []byte{}, err
+ }
+ defer func() {
+ cerr := res.Body.Close()
+ // Only overwrite the returned error if the original error was nil and an
+ // error occurred while closing the body.
+ if err == nil && cerr != nil {
+ err = cerr
+ }
+ }()
+ // c.debug("response: %#v", res)
+ c.debug("response body: %s", string(data))
+ // c.debug("response status code: %d", res.StatusCode)
+
+ if res.StatusCode >= http.StatusBadRequest {
+ apiErr := new(handlers.APIError)
+ e := json.Unmarshal(data, apiErr)
+ if e != nil {
+ // c.debug("failed to unmarshal json: %s", e)
+ }
+ return nil, apiErr
+ }
+ return data, nil
+}
+
+// NewServerTimeService Market Endpoints
+func (c *Client) NewServerTimeService() *ServerTime {
+ return &ServerTime{c: c}
+}
diff --git a/consts.go b/consts.go
new file mode 100644
index 0000000..cdbd73c
--- /dev/null
+++ b/consts.go
@@ -0,0 +1,5 @@
+package bybit
+
+const Name = "bybit.api.go"
+
+const Version = "1.0.0"
diff --git a/examples/app.go b/examples/app.go
new file mode 100644
index 0000000..c048119
--- /dev/null
+++ b/examples/app.go
@@ -0,0 +1,7 @@
+package main
+
+import "fmt"
+
+func main() {
+ fmt.Println("hello world")
+}
diff --git a/examples/market/server_time.go b/examples/market/server_time.go
new file mode 100644
index 0000000..77d1d5a
--- /dev/null
+++ b/examples/market/server_time.go
@@ -0,0 +1,23 @@
+package main
+
+import (
+ bybit "bybit.go.api"
+ "context"
+ "fmt"
+)
+
+func main() {
+ ServerTime()
+}
+
+func ServerTime() {
+
+ client := bybit.NewClient("", "")
+
+ // set to debug mode
+ client.Debug = true
+
+ // NewServerTimeService
+ serverTime := client.NewServerTimeService().Do(context.Background())
+ fmt.Println(bybit.PrettyPrint(serverTime))
+}
diff --git a/go.mod b/go.mod
new file mode 100644
index 0000000..914a29e
--- /dev/null
+++ b/go.mod
@@ -0,0 +1,8 @@
+module bybit.go.api
+
+go 1.21
+
+require (
+ github.com/bitly/go-simplejson v0.5.1
+ github.com/gorilla/websocket v1.5.0
+ )
\ No newline at end of file
diff --git a/go.sum b/go.sum
new file mode 100644
index 0000000..a746f7a
--- /dev/null
+++ b/go.sum
@@ -0,0 +1,3 @@
+github.com/bitly/go-simplejson v0.5.0/go.mod h1:cXHtHw4XUPsvGaxgjIAn8PhEWG9NfngEKAMDJEczWVA=
+github.com/bitly/go-simplejson v0.5.1/go.mod h1:YOPVLzCfwK14b4Sff3oP1AmGhI9T9Vsg84etUnlyp+Q=
+github.com/gorilla/websocket v1.5.0/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
diff --git a/handlers/errors.go b/handlers/errors.go
new file mode 100644
index 0000000..a2ab602
--- /dev/null
+++ b/handlers/errors.go
@@ -0,0 +1,20 @@
+package handlers
+
+import "fmt"
+
+// APIError define API error when response status is 4xx or 5xx
+type APIError struct {
+ Code int64 `json:"retCode"`
+ Message string `json:"retMsg"`
+}
+
+// Error return error code and message
+func (e APIError) Error() string {
+ return fmt.Sprintf(" code=%d, msg=%s", e.Code, e.Message)
+}
+
+// IsAPIError check if e is an API error
+func IsAPIError(e error) bool {
+ _, ok := e.(*APIError)
+ return ok
+}
diff --git a/market.go b/market.go
new file mode 100644
index 0000000..448335b
--- /dev/null
+++ b/market.go
@@ -0,0 +1,23 @@
+package bybit
+
+import (
+ "context"
+ "net/http"
+)
+
+// Binance Check Server Time endpoint (GET /v5/market/time)
+type ServerTime struct {
+ c *Client
+}
+
+// Send the request
+func (s *ServerTime) Do(ctx context.Context, opts ...RequestOption) (res []byte) {
+ r := &request{
+ method: http.MethodGet,
+ endpoint: "/v5/market/time",
+ secType: secTypeNone,
+ }
+ data, _ := s.c.callAPI(ctx, r, opts...)
+ res = data
+ return res
+}
diff --git a/request.go b/request.go
new file mode 100644
index 0000000..015bf7d
--- /dev/null
+++ b/request.go
@@ -0,0 +1,76 @@
+package bybit
+
+import (
+ "fmt"
+ "io"
+ "net/http"
+ "net/url"
+)
+
+type secType int
+
+const (
+ secTypeNone secType = iota
+ secTypeSigned // private request
+)
+
+type params map[string]interface{}
+
+// request define an API request
+type request struct {
+ method string
+ endpoint string
+ query url.Values
+ form url.Values
+ recvWindow string
+ secType secType
+ header http.Header
+ body io.Reader
+ fullURL string
+}
+
+// addParam add param with key/value to query string
+func (r *request) addParam(key string, value interface{}) *request {
+ if r.query == nil {
+ r.query = url.Values{}
+ }
+ r.query.Add(key, fmt.Sprintf("%v", value))
+ return r
+}
+
+// setParam set param with key/value to query string
+func (r *request) setParam(key string, value interface{}) *request {
+ if r.query == nil {
+ r.query = url.Values{}
+ }
+ r.query.Set(key, fmt.Sprintf("%v", value))
+ return r
+}
+
+// setParams set params with key/values to query string
+func (r *request) setParams(m params) *request {
+ for k, v := range m {
+ r.setParam(k, v)
+ }
+ return r
+}
+
+func (r *request) validate() (err error) {
+ if r.query == nil {
+ r.query = url.Values{}
+ }
+ if r.form == nil {
+ r.form = url.Values{}
+ }
+ return nil
+}
+
+// Append `WithRecvWindow(insert_recvwindow)` to request to modify the default recvWindow value
+func WithRecvWindow(recvWindow string) RequestOption {
+ return func(r *request) {
+ r.recvWindow = recvWindow
+ }
+}
+
+// RequestOption define option type for request
+type RequestOption func(*request)