-
Notifications
You must be signed in to change notification settings - Fork 837
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
base: master
Are you sure you want to change the base?
Coinbase: Update exchange implementation #1480
Conversation
Continual enhance of Coinbase tests The revamp continues Oh jeez the Orderbook part's unfinished don't look Coinbase revamp, Orderbook still unfinished
…otrader into coinbase_api_revamp
V3 done, onto V2 Coinbase revamp nears completion Coinbase revamp nears completion Test commit should fail Coinbase revamp nears completion
sharedtestvalues.SkipTestIfCredentialsUnset(t, c) | ||
resp, err = c.GetProductBookV3(context.Background(), testPair, 4, -1, true) | ||
assert.NoError(t, err) | ||
require.NoError(t, err) |
There was a problem hiding this comment.
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)
}
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ooh, that's nice.
There was a problem hiding this 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 ( |
There was a problem hiding this comment.
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
There was a problem hiding this comment.
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"}
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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
There was a problem hiding this 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. 🚀
exchanges/coinbasepro/coinbasepro.go
Outdated
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") |
There was a problem hiding this comment.
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
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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
Total: accountBalance[i].AvailableBalance.Value, | ||
Hold: accountBalance[i].Hold.Value, | ||
Free: accountBalance[i].AvailableBalance.Value - accountBalance[i].Hold.Value, | ||
AvailableWithoutBorrow: accountBalance[i].AvailableBalance.Value, |
There was a problem hiding this comment.
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.
There was a problem hiding this 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.
exchanges/coinbasepro/coinbasepro.go
Outdated
// UnmarshalJSON unmarshals the JSON data | ||
func (o *Orders) UnmarshalJSON(data []byte) error { | ||
var alias any | ||
var str1, str2 string |
There was a problem hiding this comment.
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
exchanges/coinbasepro/coinbasepro.go
Outdated
// 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} |
There was a problem hiding this comment.
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
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() |
There was a problem hiding this comment.
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.
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...) | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
can be method Load(...)
exchanges/exchange.go
Outdated
urlMap := maps.Clone(e.defaults) | ||
e.mu.RUnlock() | ||
return urlMap |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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 { |
There was a problem hiding this comment.
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.
exchanges/coinbasepro/coinbasepro.go
Outdated
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") |
There was a problem hiding this comment.
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
exchanges/coinbasepro/coinbasepro.go
Outdated
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") |
There was a problem hiding this comment.
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
There was a problem hiding this 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 |
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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 { |
There was a problem hiding this comment.
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) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
missing test coverage
There was a problem hiding this comment.
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.
exchanges/coinbasepro/coinbasepro.go
Outdated
err := common.StartEndTimeCheck(startDate, endDate) | ||
if err != nil { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
err := common.StartEndTimeCheck(startDate, endDate) | |
if err != nil { | |
if err := common.StartEndTimeCheck(startDate, endDate); err != nil { |
exchanges/coinbasepro/coinbasepro.go
Outdated
} | ||
break | ||
// PrepareDateString encodes a set of parameters indicating start & end dates | ||
func (p *Params) prepareDateString(startDate, endDate time.Time, labelStart, labelEnd string) error { |
There was a problem hiding this comment.
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) { |
There was a problem hiding this comment.
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) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
GetV3Time?
There was a problem hiding this comment.
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 { |
There was a problem hiding this comment.
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: |
There was a problem hiding this comment.
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 { |
There was a problem hiding this comment.
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
.
There was a problem hiding this comment.
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.
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.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.
Checklist
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.