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

Coinbase: Update exchange implementation #1480

Open
wants to merge 104 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 2 commits
Commits
Show all changes
104 commits
Select commit Hold shift + click to select a range
a006163
Slight enhance of Coinbase tests
cranktakular Sep 12, 2023
074f367
Merge branch 'master' into coinbase_api_revamp
cranktakular Oct 8, 2023
bd9f2dd
Coinbase revamp; CreateReport is still WIP
cranktakular Oct 9, 2023
25fcd2e
More coinbase improvements; onto sandbox testing
cranktakular Oct 16, 2023
5d3fd99
Coinbase revamp continues
cranktakular Oct 23, 2023
3aca36b
Coinbase revamp continues
cranktakular Oct 23, 2023
f58b76d
Merge branch 'coinbase_api_revamp' of github.com:cranktakular/gocrypt…
cranktakular Oct 23, 2023
eded160
Coinbasepro revamp is ceaseless
cranktakular Oct 24, 2023
5a0ba23
Coinbase revamp, starting on advanced trade API
cranktakular Nov 1, 2023
88e6713
Coinbase Advanced Trade Starts in Ernest
cranktakular Nov 7, 2023
aa2efef
Coinbase revamp stage wrapper
cranktakular Nov 28, 2023
3560599
Merge branch 'master' into coinbase_api_revamp
cranktakular Nov 28, 2023
4593828
Coinbase wrapper coherence continues
cranktakular Nov 28, 2023
a8cae40
Coinbase wrapper continues writhing
cranktakular Dec 5, 2023
0564e45
Coinbase wrapper & codebase cleanup
cranktakular Dec 11, 2023
4f5ccb5
Coinbase updates & wrap progress
cranktakular Dec 19, 2023
9a6c7e6
More Coinbase wrapper progress
cranktakular Dec 27, 2023
2896b3e
Wrapper is wrapped, kinda
cranktakular Jan 2, 2024
ffb43d8
Test & type checking
cranktakular Jan 9, 2024
7dd4589
Coinbase REST revamp finished
cranktakular Jan 16, 2024
6d7ac5f
Merge branch 'master' into coinbase_api_revamp
cranktakular Jan 16, 2024
2881841
Post-merge fix
cranktakular Jan 16, 2024
2166886
WS revamp begins
cranktakular Jan 16, 2024
35eda67
WS Main Revamp Done?
cranktakular Jan 22, 2024
6ac86cd
CB websocket tidying up
cranktakular Jan 31, 2024
cfe6e11
Coinbase WS wrapperupperer
cranktakular Feb 6, 2024
2f770d5
Coinbase revamp done??
cranktakular Feb 14, 2024
3e54528
Merge branch 'master' into coinbase_api_revamp
cranktakular Feb 14, 2024
2f05c05
Linter progress
cranktakular Feb 20, 2024
005af4a
Continued lint cleanup
cranktakular Feb 20, 2024
2b5df7c
Further lint cleanup
cranktakular Feb 20, 2024
93d6722
Increased lint coverage
cranktakular Feb 20, 2024
529f7f3
Does this fix all sloppy reassigns & shadowing?
cranktakular Feb 20, 2024
8bb2561
Undoing retry policy change
cranktakular Feb 20, 2024
5f813e3
Documentation regeneration
cranktakular Feb 20, 2024
c7d7ac2
Coinbase code improvements
cranktakular Feb 27, 2024
8cdc78a
Providing warning about known issue
cranktakular Feb 27, 2024
19245e3
Merge branch 'master' into coinbase_api_revamp
cranktakular Feb 27, 2024
79ac205
Updating an error to new format
cranktakular Feb 27, 2024
e073757
Making gocritic happy
cranktakular Feb 27, 2024
a65fc38
Review adherence
cranktakular Mar 7, 2024
254efe7
Merge branch 'master' into coinbase_api_revamp
cranktakular Mar 7, 2024
14ab490
Endpoints moved to V3 & nil pointer fixes
cranktakular Mar 7, 2024
bee2878
Removing seemingly superfluous constant
cranktakular Mar 8, 2024
1d51a2e
Glorious improvements
cranktakular Apr 9, 2024
90e20e1
Merge master, resolving conflict
cranktakular Apr 14, 2024
4d1afc2
Removing unused error
cranktakular Apr 14, 2024
55596d8
Partial public endpoint addition
cranktakular Apr 15, 2024
343052b
Slight improvements
cranktakular Apr 22, 2024
ea43046
Wrapper improvements; still a few errors left in other packages
cranktakular Apr 30, 2024
6dbb243
A lil Coinbase progress
cranktakular May 7, 2024
f3fc162
Merge with master + improvements
cranktakular May 7, 2024
a5a3bad
Json cleaning
cranktakular May 7, 2024
742b21e
Lint appeasement
cranktakular May 7, 2024
71e0fa5
Config repair
cranktakular May 7, 2024
01ccd01
Config fix (real)
cranktakular May 7, 2024
228c3ba
Merge master maybe?
cranktakular May 30, 2024
91d4b24
Little fix
cranktakular May 30, 2024
75425fd
New public endpoint incorporation
cranktakular Jun 11, 2024
44ff400
Merge branch 'master' into coinbase_api_revamp
cranktakular Jun 11, 2024
eec8a5d
Additional fixes
cranktakular Jun 11, 2024
bbcc27b
Improvements & Appeasements
cranktakular Jun 11, 2024
e8a06e8
LineSaver
cranktakular Jun 11, 2024
d70d599
Merge branch 'master' into coinbase_api_revamp
cranktakular Jun 18, 2024
4b79d3f
Merge branch 'master' into coinbase_api_revamp
cranktakular Jul 30, 2024
d699631
Additional fixes
cranktakular Jul 30, 2024
0f18f8c
Another fix
cranktakular Jul 30, 2024
c0aa9a4
Fixing picked nits
cranktakular Aug 7, 2024
ac98019
Quick fixies
cranktakular Aug 7, 2024
8e1b87b
Merge branch 'master' into coinbase_api_revamp
cranktakular Aug 13, 2024
e372aea
Lil fixes
cranktakular Aug 13, 2024
d384ec2
Subscriptions: Add List.Enabled
gbjk Aug 23, 2024
9205394
CoinbasePro: Add subscription templating
gbjk Aug 21, 2024
13a3faf
fixup! CoinbasePro: Add subscription templating
gbjk Aug 27, 2024
62c48af
fixup! CoinbasePro: Add subscription templating
gbjk Aug 27, 2024
1dac1c1
Merge pull request #3 from gbjk/feature/coinbase_sub_conf_v2
cranktakular Aug 27, 2024
2e814d5
Comment fix
cranktakular Aug 27, 2024
57567f8
Merge branch 'coinbase_api_revamp' of github.com:cranktakular/gocrypt…
cranktakular Aug 27, 2024
02b88c4
Merge branch 'master' into coinbase_api_revamp
cranktakular Aug 27, 2024
06f8a6a
Subsequent fixes
cranktakular Aug 27, 2024
0e0c09e
Merge branch 'master' into coinbase_api_revamp
cranktakular Sep 2, 2024
9edf0c9
Issues hopefully fixed
cranktakular Sep 3, 2024
7e30794
Lint fix
cranktakular Sep 3, 2024
242efa3
Merge branch 'master' into coinbase_api_revamp
cranktakular Sep 17, 2024
329e039
Glorious fixes
cranktakular Sep 17, 2024
d797e69
Json formatting
cranktakular Sep 17, 2024
9925c88
ShazNits
cranktakular Sep 24, 2024
c88b057
(L/N)i(n/)t
cranktakular Sep 29, 2024
0eeb73c
Merge master and minor fixes
cranktakular Oct 8, 2024
4acb64d
Merge master and minor fixes
cranktakular Oct 16, 2024
5974421
Adding a test
cranktakular Oct 16, 2024
2a90b5d
Tiny test improvement
cranktakular Oct 16, 2024
3f6976c
Merge branch 'master' into coinbase_api_revamp
cranktakular Oct 29, 2024
3eb15db
Template patch testing
cranktakular Nov 5, 2024
38f2765
Fixes
cranktakular Nov 5, 2024
2cc9d0c
Merge branch 'master' into coinbase_api_revamp
cranktakular Nov 5, 2024
2fd8bb1
Further shaznits
cranktakular Nov 12, 2024
1229376
Merge branch 'master' into coinbase_api_revamp
cranktakular Nov 12, 2024
a4252ec
Lint nit
cranktakular Nov 12, 2024
ff6ce5f
JWT move and other fixes
cranktakular Nov 27, 2024
19fbf09
Small nits
cranktakular Dec 3, 2024
e1ba339
Shaznit, singular
cranktakular Dec 11, 2024
59a9d7b
Merge branch 'master' into coinbase_api_revamp
cranktakular Dec 12, 2024
80cb272
Post-merge fix
cranktakular Dec 12, 2024
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
102 changes: 72 additions & 30 deletions exchanges/coinbasepro/coinbasepro.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,9 @@ package coinbasepro
import (
gloriousCode marked this conversation as resolved.
Show resolved Hide resolved
"bytes"
"context"
"encoding/hex"
"crypto/x509"
"encoding/json"
"encoding/pem"
"errors"
"fmt"
"net/http"
Expand All @@ -14,8 +15,8 @@ import (
"time"

"github.com/gofrs/uuid"
"github.com/golang-jwt/jwt"
"github.com/thrasher-corp/gocryptotrader/common"
"github.com/thrasher-corp/gocryptotrader/common/crypto"
"github.com/thrasher-corp/gocryptotrader/common/key"
"github.com/thrasher-corp/gocryptotrader/currency"
exchange "github.com/thrasher-corp/gocryptotrader/exchanges"
Expand Down Expand Up @@ -155,6 +156,7 @@ var (
errUnrecognisedAssetType = errors.New("unrecognised asset type")
errUnrecognisedStrategyType = errors.New("unrecognised strategy type")
errIntervalNotSupported = errors.New("interval not supported")
errEndpointPathInvalid = errors.New("endpoint path invalid, should start with https://")

allowedGranularities = []string{granOneMin, granFiveMin, granFifteenMin, granThirtyMin, granOneHour, granTwoHour, granSixHour, granOneDay}
closedStatuses = []string{"FILLED", "CANCELLED", "EXPIRED", "FAILED"}
Expand Down Expand Up @@ -428,6 +430,7 @@ func (c *CoinbasePro) GetAllOrders(ctx context.Context, productID, userNativeCur
if contractExpiryType != "" {
params.Values.Set("contract_expiry_type", contractExpiryType)
}
// This functionality has been deprecated, and only works for legacy API keys
if retailPortfolioID != "" {
params.Values.Set("retail_portfolio_id", retailPortfolioID)
}
Expand Down Expand Up @@ -1396,19 +1399,14 @@ func (c *CoinbasePro) SendHTTPRequest(ctx context.Context, ep exchange.URL, path

// SendAuthenticatedHTTPRequest sends an authenticated HTTP request
func (c *CoinbasePro) SendAuthenticatedHTTPRequest(ctx context.Context, ep exchange.URL, method, path string, queryParams url.Values, bodyParams map[string]any, isVersion3 bool, result any, returnHead *http.Header) (err error) {
creds, err := c.GetCredentials(ctx)
if err != nil {
return err
}
endpoint, err := c.API.Endpoints.GetURL(ep)
if err != nil {
return err
}
queryString := common.EncodeURLValues("", queryParams)
// Version 2 wants query params in the path during signing
if !isVersion3 {
path += queryString
if len(endpoint) < 8 {
return errEndpointPathInvalid
}
queryString := common.EncodeURLValues("", queryParams)
interim := json.RawMessage{}
newRequest := func() (*request.Item, error) {
payload := []byte("")
Expand All @@ -1418,31 +1416,16 @@ func (c *CoinbasePro) SendAuthenticatedHTTPRequest(ctx context.Context, ep excha
return nil, err
}
}
n := strconv.FormatInt(time.Now().Unix(), 10)
message := n + method + path + string(payload)
var hmac []byte
hmac, err = crypto.GetHMAC(crypto.HashSHA256,
[]byte(message),
[]byte(creds.Secret))
var jwt string
jwt, _, err = c.GetJWT(ctx, method+" "+endpoint[8:]+path)
if err != nil {
return nil, err
}
// TODO: Implement JWT authentication once it's supported by all endpoints we care about
// jwt, err := c.GetJWT(ctx, method+" "+path)
// if err != nil {
// return nil, err
// }
headers := make(map[string]string)
headers["CB-ACCESS-KEY"] = creds.Key
headers["CB-ACCESS-SIGN"] = hex.EncodeToString(hmac)
headers["CB-ACCESS-TIMESTAMP"] = n
headers["Content-Type"] = "application/json"
headers["CB-VERSION"] = "2024-09-24"
// headers["Authorization"] = "Bearer " + jwt
// Version 3 only wants query params in the path when the request is sent
if isVersion3 {
path += queryString
}
headers["CB-VERSION"] = "2024-11-27"
headers["Authorization"] = "Bearer " + jwt
path += queryString
return &request.Item{
Method: method,
Path: endpoint + path,
Expand Down Expand Up @@ -1505,6 +1488,65 @@ func (c *CoinbasePro) SendAuthenticatedHTTPRequest(ctx context.Context, ep excha
return json.Unmarshal(interim, result)
}

// GetJWT generates a new JWT
func (c *CoinbasePro) GetJWT(ctx context.Context, uri string) (string, time.Time, error) {
creds, err := c.GetCredentials(ctx)
if err != nil {
return "", time.Time{}, err
}
block, _ := pem.Decode([]byte(creds.Secret))
if block == nil {
return "", time.Time{}, errCantDecodePrivKey
}
key, err := x509.ParseECPrivateKey(block.Bytes)
if err != nil {
return "", time.Time{}, err
}
nonce, err := common.GenerateRandomString(16, "1234567890ABCDEF")
if err != nil {
return "", time.Time{}, err
}
regTime := time.Now()
mapClaims := jwt.MapClaims{
"iss": "cdp",
"nbf": regTime.Unix(),
"exp": regTime.Add(time.Minute * 2).Unix(),
"sub": creds.Key,
}
if uri != "" {
mapClaims["uri"] = uri
}
tok := jwt.NewWithClaims(jwt.SigningMethodES256, mapClaims)
tok.Header["kid"] = creds.Key
tok.Header["nonce"] = nonce
sign, err := tok.SignedString(key)
return sign, regTime, err
// The code below mostly works, but seems to lead to bad results on the signature step. Deferring until later
// head := map[string]any{"kid": creds.Key, "typ": "JWT", "alg": "ES256", "nonce": nonce}
// headJSON, err := json.Marshal(head)
// if err != nil {
// return "", time.Time{}, err
// }
// headEncode := base64URLEncode(headJSON)
// regTime := time.Now()
// body := map[string]any{"iss": "cdp", "nbf": regTime.Unix(), "exp": regTime.Add(time.Minute * 2).Unix(), "sub": creds.Key /*, "aud": "retail_rest_api_proxy"*/}
// if uri != "" {
// body["uri"] = uri
// }
// bodyJSON, err := json.Marshal(body)
// if err != nil {
// return "", time.Time{}, err
// }
// bodyEncode := base64URLEncode(bodyJSON)
// hash := sha256.Sum256([]byte(headEncode + "." + bodyEncode))
// sig, err := ecdsa.SignASN1(rand.Reader, key, hash[:])
// if err != nil {
// return "", time.Time{}, err
// }
// sigEncode := base64URLEncode(sig)
// return headEncode + "." + bodyEncode + "." + sigEncode, regTime, nil
Copy link
Collaborator

Choose a reason for hiding this comment

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

What is this for?

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

As comment at the top of the block says, it's my implementation of the JWT protocol, so we won't have to import a separate package to handle that. Alas, there's some issue I can't identify with generating the signature, which has it create an invalid one, so this can't be used right now.

}

// GetFee returns an estimate of fee based on type of transaction
func (c *CoinbasePro) GetFee(ctx context.Context, feeBuilder *exchange.FeeBuilder) (float64, error) {
if feeBuilder == nil {
Expand Down
22 changes: 11 additions & 11 deletions exchanges/coinbasepro/coinbasepro_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -326,7 +326,7 @@ func TestGetAllOrders(t *testing.T) {
status = make([]string, 0)
assets = make([]string, 1)
assets[0] = testCrypto.String()
_, err = c.GetAllOrders(context.Background(), "", testFiat.String(), "LIMIT", "SELL", "", "SPOT", "RETAIL_ADVANCED", "UNKNOWN_CONTRACT_EXPIRY_TYPE", "2", status, assets, 10, time.Time{}, time.Time{})
_, err = c.GetAllOrders(context.Background(), "", testFiat.String(), "LIMIT", "SELL", "", "SPOT", "RETAIL_ADVANCED", "UNKNOWN_CONTRACT_EXPIRY_TYPE", "", status, assets, 10, time.Time{}, time.Time{})
assert.NoError(t, err)
}

Expand Down Expand Up @@ -435,6 +435,7 @@ func TestDeletePortfolio(t *testing.T) {
assert.ErrorIs(t, err, errPortfolioIDEmpty)
pID := portfolioIDFromName(t, "GCT Test Portfolio To-Delete")
err = c.DeletePortfolio(context.Background(), pID)
// The new JWT-based keys don't have permissions to delete portfolios they aren't assigned to, causing this to fail
assert.NoError(t, err)
}

Expand All @@ -446,6 +447,7 @@ func TestEditPortfolio(t *testing.T) {
assert.ErrorIs(t, err, errNameEmpty)
pID := portfolioIDFromName(t, "GCT Test Portfolio To-Edit")
_, err = c.EditPortfolio(context.Background(), pID, "GCT Test Portfolio Edited")
// The new JWT-based keys don't have permissions to edit portfolios they aren't assigned to, causing this to fail
if err != nil && err.Error() != errPortfolioNameDuplicate {
t.Error(err)
}
Expand Down Expand Up @@ -586,6 +588,7 @@ func TestGetPaymentMethodByID(t *testing.T) {
func TestGetCurrentUser(t *testing.T) {
t.Parallel()
sharedtestvalues.SkipTestIfCredentialsUnset(t, c)
// This intermittently fails with the message "Unauthorized", for no clear reason
testGetNoArgs(t, c.GetCurrentUser)
}

Expand All @@ -595,7 +598,7 @@ func TestGetAllWallets(t *testing.T) {
pagIn := PaginationInp{Limit: 2}
resp, err := c.GetAllWallets(context.Background(), pagIn)
assert.NoError(t, err)
assert.NotEmpty(t, resp, errExpectedNonEmpty)
require.NotEmpty(t, resp, errExpectedNonEmpty)
if resp.Pagination.NextStartingAfter == "" {
t.Skip(skipInsufficientWallets)
}
Expand Down Expand Up @@ -963,11 +966,7 @@ func TestSendHTTPRequest(t *testing.T) {

func TestSendAuthenticatedHTTPRequest(t *testing.T) {
t.Parallel()
fc := &CoinbasePro{}
err := fc.SendAuthenticatedHTTPRequest(context.Background(), exchange.EdgeCase3, "", "", nil, nil, false, nil, nil)
assert.ErrorIs(t, err, exchange.ErrCredentialsAreEmpty)
sharedtestvalues.SkipTestIfCredentialsUnset(t, c)
err = c.SendAuthenticatedHTTPRequest(context.Background(), exchange.EdgeCase3, "", "", nil, nil, false, nil, nil)
err := c.SendAuthenticatedHTTPRequest(context.Background(), exchange.EdgeCase3, "", "", nil, nil, false, nil, nil)
assert.ErrorIs(t, err, exchange.ErrEndpointPathNotFound)
ch := make(chan struct{})
body := map[string]any{"Unmarshalable": ch}
Expand Down Expand Up @@ -1518,9 +1517,10 @@ func TestWsAuth(t *testing.T) {
go c.wsReadData()
err = c.Subscribe(subscription.List{
{
Channel: "myAccount",
Asset: asset.All,
Pairs: p,
Channel: "myAccount",
Asset: asset.All,
Pairs: p,
Authenticated: true,
},
})
assert.NoError(t, err)
Expand Down Expand Up @@ -1705,7 +1705,7 @@ func TestCheckSubscriptions(t *testing.T) {
func TestGetJWT(t *testing.T) {
t.Parallel()
sharedtestvalues.SkipTestIfCredentialsUnset(t, c)
_, err := c.GetJWT(context.Background(), "")
_, _, err := c.GetJWT(context.Background(), "")
assert.NoError(t, err)
}

Expand Down
Loading
Loading