Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

FEATURE: Implement Coinbase API client and order management features #1903

Draft
wants to merge 21 commits into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
21 commits
Select commit Hold shift + click to select a range
ec63926
✨ feat(coinbase): symbol mapping and API client
Feb 12, 2025
e2e5e32
✨ feat(coinbase): add balance request handling
Feb 12, 2025
4525320
✨ feat(coinbase): implement order management API with create, get, an…
Feb 12, 2025
6375357
✨ feat(coinbase): add order trades associated methods for the api client
Feb 12, 2025
2bb9519
✨ feat(coinbase): implement market information retrieval
Feb 12, 2025
10e27b3
✨ feat(coinbase): add conversion functions and exchange service imple…
Feb 12, 2025
43a6476
✨ feat(coinbase): add Makefile
Feb 12, 2025
6c8e4a2
✨ feat(coinbase): add GetCandlesRequest and response handling for mar…
Feb 13, 2025
98f71f7
✨ feat(coinbase): implement custom interval support and enhance ticke…
Feb 13, 2025
8a97e11
✨ feat(coinbase): add GetTickerRequest and implement timeout configur…
Feb 13, 2025
8cf8461
WIP: coinbase websocket stream feeds
Feb 14, 2025
00a9918
🔧 fix(coinbase): resolve review comment, moving constants to single file
Feb 14, 2025
1a19550
🔧 fix(coinbase): remove unnecessary db tags.
Feb 14, 2025
e7ba736
🔧 fix(coinbase): resolve review comments, fixed-point value type for …
Feb 14, 2025
aeebe4a
🔧 refactor(coinbase): remove unused API methods and simplify request …
Feb 14, 2025
1d47e0e
🔧 fix(coinbase): update liquidity checks to use constants for improve…
Feb 17, 2025
674a0d6
🔧 fix(coinbase): add toGlobalBalance function for improved balance co…
Feb 17, 2025
8770af2
🔧 fix(coinbase): Add `Locked` and `NetAsset`
Feb 17, 2025
f442836
🔧 fix(coinbase): client constructor should return a reference.
Feb 17, 2025
c3d5d9c
🔧 fix(coinbase): replace time.Time with types.Time for improved type …
Feb 17, 2025
50caaeb
🔧 fix(coinbase): bug fix
Feb 17, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions pkg/exchange/coinbase/Makefile
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
generate: symbols go-generate

symbols:
@echo "Generate symbols.go" && go run ./generate_symbol_map.go

go-generate:
@echo "Running \`go generate\`" && go generate ./...
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

you don't need the makefile actually? you can embed them in the go:generate comment

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I just want a way to document how I generate the symbols.go

108 changes: 108 additions & 0 deletions pkg/exchange/coinbase/api/v1/api_client.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,108 @@
package coinbase

import (
"context"
"crypto/hmac"
"crypto/sha256"
"encoding/base64"
"encoding/json"
"net/http"
"net/url"
"strconv"
"time"

"github.com/c9s/requestgen"
"github.com/pkg/errors"
)

const (
DefaultHTTPTimeout = 15 * time.Second
ProductionAPIURL = "https://api.exchange.coinbase.com"
)

var parsedBaseURL *url.URL

func init() {
url, err := url.Parse(ProductionAPIURL)
if err != nil {
panic(err)
}
parsedBaseURL = url
}

type RestAPIClient struct {
requestgen.BaseAPIClient

key, secret, passphrase string
}

func NewClient(
key, secret, passphrase string, timeout time.Duration,
) *RestAPIClient {
if timeout == 0 {
timeout = DefaultHTTPTimeout
}
return &RestAPIClient{
BaseAPIClient: requestgen.BaseAPIClient{
BaseURL: parsedBaseURL,
HttpClient: &http.Client{Timeout: timeout},
},
key: key,
secret: secret,
passphrase: passphrase,
}
}

// Implements AuthenticatedAPIClient
func (client *RestAPIClient) NewAuthenticatedRequest(ctx context.Context, method, refURL string, params url.Values, payload interface{}) (*http.Request, error) {
return client.newAuthenticatedRequest(ctx, method, refURL, time.Now(), params, payload)
}

func (client *RestAPIClient) newAuthenticatedRequest(ctx context.Context, method, refURL string, timestamp time.Time, params url.Values, payload interface{}) (*http.Request, error) {
req, err := client.NewRequest(ctx, method, refURL, params, payload)
if err != nil {
return nil, err
}

tsStr := strconv.FormatInt(timestamp.Unix(), 10)
signature, err := sign(client.secret, tsStr, method, req.URL.RequestURI(), payload)
if err != nil {
return nil, err
}

req.Header.Add("Content-Type", "application/json")
req.Header.Add("CB-ACCESS-KEY", client.key)
req.Header.Add("CB-ACCESS-SIGN", signature)
req.Header.Add("CB-ACCESS-TIMESTAMP", tsStr)
req.Header.Add("CB-ACCESS-PASSPHRASE", client.passphrase)

return req, nil
}

// https://docs.cdp.coinbase.com/exchange/docs/rest-auth#signing-a-message
func sign(secret, timestamp, method, requestPath string, body interface{}) (string, error) {
var bodyStr string
if body != nil {
bodyByte, err := json.Marshal(body)
if err != nil {
return "", errors.Wrap(err, "failed to marshal body to string")
}

bodyStr = string(bodyByte)
}

message := timestamp + method + requestPath + bodyStr

secretByte, err := base64.StdEncoding.DecodeString(secret)
if err != nil {
return "", errors.Wrap(err, "failed to decode secret string to byte")
}
mac := hmac.New(sha256.New, secretByte)
_, err = mac.Write([]byte(message))
if err != nil {
return "", errors.Wrap(err, "failed to encode message")
}
signature := base64.StdEncoding.EncodeToString(mac.Sum(nil))

return signature, nil
}
19 changes: 19 additions & 0 deletions pkg/exchange/coinbase/api/v1/cancel_order_request.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
package coinbase

import "github.com/c9s/requestgen"

type CancelOrderResponse string

//go:generate requestgen -method DELETE -url "/orders/:order_id" -type CancelOrderRequest -responseType .CancelOrderResponse
type CancelOrderRequest struct {
client requestgen.AuthenticatedAPIClient

orderID string `param:"order_id,slug,required"`

profileID *string `param:"profile_id"`
productID *string `param:"product_id"`
}

func (c *RestAPIClient) NewCancelOrderRequest() *CancelOrderRequest {
return &CancelOrderRequest{client: c}
}
217 changes: 217 additions & 0 deletions pkg/exchange/coinbase/api/v1/cancel_order_request_requestgen.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading
Loading