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 127 commits into
base: master
Choose a base branch
from

Conversation

cranktakular
Copy link
Collaborator

@cranktakular cranktakular commented Feb 14, 2024

PR Description

Updating Coinbase to the Advanced Trade and Sign In With Coinbase APIs, as well as a few tiny fixes of other parts of the codebase found along the way.

Type of change

Please delete options that are not relevant and add an x in [] as item is complete.

  • Bug fix (non-breaking change which fixes an issue)
  • New feature (non-breaking change which adds functionality)
  • This change requires a documentation update

How has this been tested

Please describe the tests that you ran to verify your changes. Provide instructions so we can reproduce. Please also list any relevant details for your test configuration and
also consider improving test coverage whilst working on a certain feature or package.

  • go test ./... -race
  • golangci-lint run

Checklist

  • My code follows the style guidelines of this project
  • I have performed a self-review of my own code
  • I have commented my code, particularly in hard-to-understand areas
  • I have made corresponding changes to the documentation and regenerated documentation via the documentation tool
  • My changes generate no new warnings
  • I have added tests that prove my fix is effective or that my feature works
  • New and existing unit tests pass locally and on Github Actions with my changes

The only tests which seem to be failing (exchanges/kucoin, database/models/postgres, config, and common/file) are parts that I haven't substantively changed, and which seem to be failing on master too.

Continual enhance of Coinbase tests

The revamp continues

Oh jeez the Orderbook part's unfinished don't look

Coinbase revamp, Orderbook still unfinished
V3 done, onto V2

Coinbase revamp nears completion

Coinbase revamp nears completion

Test commit should fail

Coinbase revamp nears completion
@thrasher- thrasher- changed the title Coinbase api revamp Coinbase: Update exchange implementation Feb 14, 2024
@thrasher- thrasher- requested a review from a team February 14, 2024 23:40
sharedtestvalues.SkipTestIfCredentialsUnset(t, c)
resp, err = c.GetProductBookV3(context.Background(), testPair, 4, -1, true)
assert.NoError(t, err)
require.NoError(t, err)
Copy link
Collaborator

@gloriousCode gloriousCode Jan 21, 2025

Choose a reason for hiding this comment

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

Just as an FYI, not a required change, asserts' have bool responses, so if you don't like the require, you can do

if assert.NoError(errawr) {
    assert.NotEmpty(kasjhdfaksdjfhasldkfh)
}

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Ooh, that's nice.

@gloriousCode gloriousCode removed the review me This pull request is ready for review label Jan 28, 2025
Copy link
Collaborator

@gloriousCode gloriousCode left a comment

Choose a reason for hiding this comment

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

Thank you for resolving the orderbook+ticker sync issue!

@@ -3,30 +3,63 @@ package coinbasepro
import (
Copy link
Collaborator

@gloriousCode gloriousCode Feb 3, 2025

Choose a reason for hiding this comment

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

I am getting this when running CBP:

[DEBUG] | REQUESTER | 03/02/2025 11:45:07 | CoinbasePro attempt 1 request path: https://api.coinbase.com/v2/accounts/2b6b9d41-33bd-1337-83ac-dbe5b4221337/addresses
[DEBUG] | REQUESTER | 03/02/2025 11:45:07 | CoinbasePro request header [Authorization]: [Bearer: roar]
[DEBUG] | REQUESTER | 03/02/2025 11:45:07 | CoinbasePro request header [Content-Type]: [application/json]
[DEBUG] | REQUESTER | 03/02/2025 11:45:07 | CoinbasePro request header [Cb-Version]: [2024-11-27]
[DEBUG] | REQUESTER | 03/02/2025 11:45:07 | CoinbasePro request type: POST
[DEBUG] | REQUESTER | 03/02/2025 11:45:07 | CoinbasePro request body: {"name":""}
[ERROR] | LOG | 03/02/2025 11:45:08 | CoinbasePro failed to get cryptocurrency deposit address for ETH. Err: CoinbasePro unsuccessful HTTP status code: 401 raw response: {"errors":[{"id":"internal_server_error","message":"An error has occurred. If this problem persists, please visit https://support.coinbase.com for assistance."}]}, authenticated request failed

However, I receive no error running TestGetAllAddresses:

CoinbasePro attempt 1 request path: https://api.coinbase.com/v2/accounts/2b6b9d41-33bd-1337-83ac-dbe5b4221337/addresses
CoinbasePro request header [Content-Type]: [application/json]
CoinbasePro request header [Cb-Version]: [2024-11-27]
CoinbasePro request header [Authorization]: [Bearer: roar]
CoinbasePro request type: GET
CoinbasePro request body: 
HTTP status: 200 OK, Code: 200

Given the success in tests using a get versus an error in POST can you look into what func you're using in normal operations?

Though if nothing is wrong when you test, please le me know since it is saying internal_server_error, it may just go away

Copy link
Collaborator

@gloriousCode gloriousCode Feb 3, 2025

Choose a reason for hiding this comment

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

For extra context the credentials are the same between my config and tests.

[ERROR] | WEBSOCKET | 03/02/2025 11:52:51 | exchange CoinbasePro websocket error - {"type":"error","message":"authentication failure"}

Copy link
Collaborator Author

@cranktakular cranktakular Feb 5, 2025

Choose a reason for hiding this comment

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

The function which calls that endpoint with the POST method is CreateAddress. Running that myself, I get the error you mention only intermittently.

So intermittently, in fact, that I've been unable to reproduce it and debug it; I wasn't able to catch verbose output of that error.

If it's still coming up for you, a verbose paste of TestCreateAddress' output would be appreciated!

EDIT: However, thinking about this, it would probably be a functionality improvement if I check for existing addresses before generating a new one; I'll update GetDepositAddress' functionality to do that.

EDIT 2: Done.

Copy link
Collaborator

@gloriousCode gloriousCode Feb 5, 2025

Choose a reason for hiding this comment

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

The test is intermittent, in GCT I receive it as above 100% of the time. You shouldn't be calling CreateAddress on GetDepositAddress anyway, it's a retrieval endpoint. There are two exchanges where that isn't the case though (I would still argue the same thing), so I understand how you could have looked at those, but just retrieve the deposit address and I imagine the error will cease

edit: no fair on editing and committing while I reply 😄 that is a good improvement, thank you

@gloriousCode gloriousCode added the review me This pull request is ready for review label Feb 3, 2025
Copy link
Collaborator

@shazbert shazbert left a comment

Choose a reason for hiding this comment

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

Thanks for the changes! Some more nits getting my head around everything again before deep dive into testing. 🚀

errPayMethodNotFound = errors.New("payment method not found")
errUnknownL2DataType = errors.New("unknown l2update data type")
errOrderFailedToCancel = errors.New("failed to cancel order")
errUnrecognisedStatusType = errors.New("unrecognised status type")
Copy link
Collaborator

Choose a reason for hiding this comment

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

seems like this could be an order package level error

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 can't find it there.

Copy link
Collaborator

Choose a reason for hiding this comment

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

Can create a new status error if one is not found

Comment on lines +268 to +271
Total: accountBalance[i].AvailableBalance.Value,
Hold: accountBalance[i].Hold.Value,
Free: accountBalance[i].AvailableBalance.Value - accountBalance[i].Hold.Value,
AvailableWithoutBorrow: accountBalance[i].AvailableBalance.Value,
Copy link
Collaborator

Choose a reason for hiding this comment

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

NOTE 4 me test this.

@gloriousCode gloriousCode removed the review me This pull request is ready for review label Feb 19, 2025
Copy link
Collaborator

@shazbert shazbert left a comment

Choose a reason for hiding this comment

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

Just some nits on new changes thanks heaps for going over them! Nice stuff.

// UnmarshalJSON unmarshals the JSON data
func (o *Orders) UnmarshalJSON(data []byte) error {
var alias any
var str1, str2 string
Copy link
Collaborator

Choose a reason for hiding this comment

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

these don't need to be allocated if you change Orders Field value to types.Number and the below ParseFloat can be removed and below

// UnmarshalJSON unmarshals the JSON data
func (c *Candle) UnmarshalJSON(data []byte) error {
var f1, f2, f3, f4, f5, f6 float64
temp := [6]any{&f1, &f2, &f3, &f4, &f5, &f6}
Copy link
Collaborator

Choose a reason for hiding this comment

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

point directly to field of c and change Time feild to types.Time

Comment on lines 155 to 157
c.aliasStruct.m.RLock()
aliases := make(map[currency.Pair]currency.Pairs)
maps.Copy(aliases, c.aliasStruct.associatedAliases)
aliases := maps.Clone(c.aliasStruct.associatedAliases)
c.aliasStruct.m.RUnlock()
Copy link
Collaborator

Choose a reason for hiding this comment

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

can be GetAliases() method with mutex calls in scope.

Comment on lines 213 to 220
c.aliasStruct.m.Lock()
defer c.aliasStruct.m.Unlock()
if c.aliasStruct.associatedAliases == nil {
c.aliasStruct.associatedAliases = make(map[currency.Pair]currency.Pairs)
}
for k, v := range aliases {
c.aliasStruct.associatedAliases[k] = c.aliasStruct.associatedAliases[k].Add(v...)
}
Copy link
Collaborator

Choose a reason for hiding this comment

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

can be method Load(...)

Comment on lines 1327 to 1329
urlMap := maps.Clone(e.defaults)
e.mu.RUnlock()
return urlMap
Copy link
Collaborator

Choose a reason for hiding this comment

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

Suggested change
urlMap := maps.Clone(e.defaults)
e.mu.RUnlock()
return urlMap
defer e.mu.RUnlock()
return maps.Clone(e.defaults)

@@ -1338,3 +1332,10 @@ func FormatAssetOutbound(a asset.Item) string {
}
return a.Upper()
}

// LoadAlias returns the aliases for a currency pair, with the original pair included
func (a *aliasStruct) LoadAlias(p currency.Pair) currency.Pairs {
Copy link
Collaborator

Choose a reason for hiding this comment

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

Good start, all the other references to Alias struct can be methods as suggested above. Can change name of aliasstruct to productAlias or pairAlias and have specific tests associated for the methods.

errEndpointPathInvalid = errors.New("endpoint path invalid, should start with https://")
errPairsDisabledOrErrored = errors.New("pairs are either disabled or errored")
errTypeAssert = errors.New("type assertion failed")
Copy link
Collaborator

Choose a reason for hiding this comment

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

When common.GetTypeAssertError is called it returns common.ErrTypeAssertFailure so this can be removed

errPayMethodNotFound = errors.New("payment method not found")
errUnknownL2DataType = errors.New("unknown l2update data type")
errOrderFailedToCancel = errors.New("failed to cancel order")
errUnrecognisedStatusType = errors.New("unrecognised status type")
Copy link
Collaborator

Choose a reason for hiding this comment

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

Can create a new status error if one is not found

Copy link
Collaborator

@shazbert shazbert left a comment

Choose a reason for hiding this comment

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

Another round of nits and suggestions for you, really nice work on what you have done so far.

Volume: resp[x][5],
// GetAllOrders lists orders, filtered by their status
func (c *CoinbasePro) GetAllOrders(ctx context.Context, productID, userNativeCurrency, orderType, orderSide, cursor, productType, orderPlacementSource, contractExpiryType, retailPortfolioID string, orderStatus, assetFilters []string, limit int32, startDate, endDate time.Time) (*GetAllOrdersResp, error) {
var params Params
Copy link
Collaborator

Choose a reason for hiding this comment

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

This embeded struct type can just be params := make(url.Values) unless needed for methods across this PR.

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

It's needed for methods

}
if clientRef != "" {
req["client_oid"] = clientRef
if amount == 0 {
Copy link
Collaborator

Choose a reason for hiding this comment

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

missing test coverage

}

// GetV3Time returns the current server time, calling V3 of the API
func (c *CoinbasePro) GetV3Time(ctx context.Context) (*ServerTimeV3, error) {
Copy link
Collaborator

Choose a reason for hiding this comment

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

missing test coverage

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

The test was skipping since the endpoint was authenticated a year ago, and I forgot to update the test when it was made unauth ~9 months ago.

Comment on lines 1493 to 1494
err := common.StartEndTimeCheck(startDate, endDate)
if err != nil {
Copy link
Collaborator

Choose a reason for hiding this comment

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

Suggested change
err := common.StartEndTimeCheck(startDate, endDate)
if err != nil {
if err := common.StartEndTimeCheck(startDate, endDate); err != nil {

}
break
// PrepareDateString encodes a set of parameters indicating start & end dates
func (p *Params) prepareDateString(startDate, endDate time.Time, labelStart, labelEnd string) error {
Copy link
Collaborator

Choose a reason for hiding this comment

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

maybe change name: encodeDateRange or setDateRange also zero strings are not being checked and the method needs test coverage.


err = ticker.ProcessTicker(tickerPrice)
// FetchTicker returns the ticker for a currency pair
func (c *CoinbasePro) FetchTicker(ctx context.Context, p currency.Pair, assetType asset.Item) (*ticker.Price, error) {
Copy link
Collaborator

Choose a reason for hiding this comment

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

RM FetchTicker, Orderbook, AccountInfo aas well as tests as they are now base level methods from

func (c *CoinbasePro) ValidateAPICredentials(ctx context.Context, assetType asset.Item) error {
_, err := c.UpdateAccountInfo(ctx, assetType)
return c.CheckTransientError(err)
}

// GetServerTime returns the current exchange server time.
func (c *CoinbasePro) GetServerTime(ctx context.Context, _ asset.Item) (time.Time, error) {
st, err := c.GetCurrentServerTime(ctx)
st, err := c.GetV2Time(ctx)
Copy link
Collaborator

Choose a reason for hiding this comment

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

GetV3Time?

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Used V2Time since V3Time used to be authenticated, but I guess I can switch it now.

}

// processFundingData is a helper function for GetAccountFundingHistory and GetWithdrawalsHistory, transforming the data returned by the Coinbase API into a format suitable for the exchange package
func (c *CoinbasePro) processFundingData(accHistory []DeposWithdrData, cryptoHistory []TransactionData) []exchange.FundingHistory {
Copy link
Collaborator

Choose a reason for hiding this comment

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

missing test coverage

return granFifteenMin, nil
case kline.ThirtyMin:
return granThirtyMin, nil
case kline.OneHour:
Copy link
Collaborator

Choose a reason for hiding this comment

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

Missing test coverage

}

// XOR's the current time with the amount to cheaply generate an idempotency token where unwanted collisions should be rare
func generateIdempotency(am float64) string {
Copy link
Collaborator

Choose a reason for hiding this comment

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

If this is required I would update the withdraw.Request type so that this can be defined outside of this package. I wouldn't recommend, I would prefer a uuid instead I think V7 is a performant option. I don't know enough yet about what is going on to guarantee uniqueness for a edge case we can parallel this request and do two independant withdraws of 1337.1337 and 1337.1336 both will have the same unix nano timestamp and might be the same value u.

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

It's not required, and is only used for this function of withdrawing money.

Could remove it if you want.

@gloriousCode gloriousCode added the review me This pull request is ready for review label Mar 11, 2025
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
high priority review me This pull request is ready for review test(s) fix This is to denote the PR is fixing a build test issue
Projects
Status: In review
Development

Successfully merging this pull request may close these issues.

5 participants