Skip to content

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
Copy link
Collaborator

@samuael samuael left a comment

Choose a reason for hiding this comment

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

I like this PR 👍 . I just left some reviewws and
will drop another round of reviews when this are resolved.

errDepositIDEmpty = errors.New("deposit id cannot be empty")
errInvalidPriceType = errors.New("price type must be spot, buy, or sell")
errInvalidOrderType = errors.New("order type must be market, limit, or stop")
errEndTimeInPast = errors.New("end time cannot be in the past")
Copy link
Collaborator

Choose a reason for hiding this comment

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

could common.ErrStartAfterEnd replace this?

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Technically, start time could be even further in the past, making this error more apt.

errAccountIDEmpty = errors.New("account id cannot be empty")
errClientOrderIDEmpty = errors.New("client order id cannot be empty")
errProductIDEmpty = errors.New("product id cannot be empty")
errOrderIDEmpty = errors.New("order ids cannot be empty")
Copy link
Collaborator

Choose a reason for hiding this comment

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

couldn't you use order.ErrOrderIDNotSet instead for consistency.


var (
errAccountIDEmpty = errors.New("account id cannot be empty")
errClientOrderIDEmpty = errors.New("client order id cannot be empty")
Copy link
Collaborator

Choose a reason for hiding this comment

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

can replace this with order.ErrClientOrderIDMustBeSet

// CancelOrders cancels orders by orderID. Can only cancel 100 orders per request
func (e *Exchange) CancelOrders(ctx context.Context, orderIDs []string) ([]OrderCancelDetail, error) {
if len(orderIDs) == 0 {
return nil, errOrderIDEmpty
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
return nil, errOrderIDEmpty
return nil, order.ErrOrderIDNotSet

// ClosePosition closes a position by client order ID, product ID, and size
func (e *Exchange) ClosePosition(ctx context.Context, clientOrderID string, productID currency.Pair, size float64) (*SuccessFailureConfig, error) {
if clientOrderID == "" {
return nil, errClientOrderIDEmpty
Copy link
Collaborator

Choose a reason for hiding this comment

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

can change all occurrences of such type with order.ErrClientOrderIDMustBeSet

params.Values.Set("cursor", strconv.FormatInt(req.Cursor, 10))
params.Values.Set("limit", strconv.FormatInt(int64(req.Limit), 10))
params.Values.Set("user_native_currency", req.UserNativeCurrency.String())
params.Values.Set("retail_portfolio_id", req.RetailPortfolioID) // deprecated and only works for legacy API keys
Copy link
Collaborator

Choose a reason for hiding this comment

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

I don't see a section where you check the value of these variables if they were required

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 of the time I tested these functions, the parameters are....

  • Checked at the start, with an error thrown if empty, if they are required.
  • Only set if they're not empty, if assigning them while empty causes an error.
  • Set regardless, otherwise.
    These values landed in the last camp.

AttachedOrderConfiguration: &inf.AttachedOrderConfiguration,
MarginType: FormatMarginType(inf.MarginType),
}
var resp *PreviewOrderResp
Copy link
Collaborator

Choose a reason for hiding this comment

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

You can use this approach everywhere for consistency. I see you used var resp TheStruct --> return &resp, e.Sen... at some endpoint calls.

return nil, err
}
var resp *ManyDeposWithdrResp
if err := e.SendAuthenticatedHTTPRequest(ctx, exchange.RestSpot, http.MethodGet, path, params.Values, nil, false, &resp); 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.

Why the error check here? couldnt it be handled the calling code

if walletID == "" {
return nil, errWalletIDEmpty
}
path := v2Path + accountsPath + "/" + walletID + "/" + transactionsPath
Copy link
Collaborator

Choose a reason for hiding this comment

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

move this to below the error check

longResp, err := e.ListAccounts(t.Context(), 49, 0)
assert.NoError(t, err)
if longResp == nil || len(longResp.Accounts) == 0 {
t.Fatal(errExpectedNonEmpty)
Copy link
Collaborator

Choose a reason for hiding this comment

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

replace this with require.True

Copy link
Collaborator

@gbjk gbjk left a comment

Choose a reason for hiding this comment

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

Great work

🐒 Optional nitpick
🌴 This issue lives in multiple other places
🦍 Optional and pedantic nitpick
🚧 Change request
🥃 Just conversational observation

}

type pairAliases struct {
associatedAliases map[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.

🦍 Can be simpler

Suggested change
associatedAliases map[currency.Pair]currency.Pairs
pairs map[currency.Pair]currency.Pairs

@@ -1916,25 +1905,13 @@ func TestChannelName(t *testing.T) {
}

func exchangeBaseHelper(e *Exchange) error {
Copy link
Collaborator

Choose a reason for hiding this comment

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

🐒 Precedence for this is:

Suggested change
func exchangeBaseHelper(e *Exchange) error {
// testInstance returns a local instance for isolated testing
func testInstance(e *Exchange) 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.

Is that right? "testInstance" and "local instance" give, to me, the impression that it's mocking functionality. Heck, from the function signature itself it's not "returning" an instance, just executing some common initialisation.

Copy link
Collaborator

Choose a reason for hiding this comment

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

Sorry, I was lazy and didn't check the rest of the function.

Suggested change
func exchangeBaseHelper(e *Exchange) error {
func testInstance(tb testing.TB) *Exchange {

See kucoin_test.go for example

I'd try to use something like that in the places you've got all the bootstrapping for the exchange in this test.

Note: This nit is optional.

Comment on lines 43 to 44
// ValueWithCurrency is a sub-struct used in the types Account, NativeAndRaw, DetailedPortfolioResponse, ErrorMetadata, SubscriptionInfo, FuturesBalanceSummary, ListFuturesSweepsResponse, PerpetualsPortfolioSummary, PerpPositionDetail, FeeStruct, AmScale, and ConvertResponse
type ValueWithCurrency struct {
Copy link
Collaborator

Choose a reason for hiding this comment

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

🚧

  • Definitely don't want to set precedent of listing all the places a symbol is used.
    🌴 There's a few places we've mentioned where something is used/returned below which are also unnecessary. It just clouds search results and doesn't add information the IDE wouldn't give you.
  • Value feels like it's Amount. But then this entire concept has other names. We could go for CurrAmount, or just Monetary, or Money. Happy with anything, but Value feels meaningless (in the context of code, because it's an overloaded term here, and we can't assume financial value)
  • 🥃 Aside: The term sub-struct is misleading unless we mean embedded anonymous structs. Which we don't like anyway. And this isn't one.
Suggested change
// ValueWithCurrency is a sub-struct used in the types Account, NativeAndRaw, DetailedPortfolioResponse, ErrorMetadata, SubscriptionInfo, FuturesBalanceSummary, ListFuturesSweepsResponse, PerpetualsPortfolioSummary, PerpPositionDetail, FeeStruct, AmScale, and ConvertResponse
type ValueWithCurrency struct {
// CurrAmount is holds a currencycode and amount
type CurrAmount struct {

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Yeah, I made a very bad decision when I first started writing comments on this types file, and I have been paying the price ever since. I can easily chuck it out from this struct, but it would be a bit annoying to do that over the whole file.

I'll move to CurrAmount.

Copy link
Collaborator

Choose a reason for hiding this comment

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

Yeah - sure. Chuck it out of this comment and any others which we spot that are crazy long.
The rest of them can be hit as driveby fixes later, it's fine.

// ValueWithCurrency is a sub-struct used in the types Account, NativeAndRaw, DetailedPortfolioResponse, ErrorMetadata, SubscriptionInfo, FuturesBalanceSummary, ListFuturesSweepsResponse, PerpetualsPortfolioSummary, PerpPositionDetail, FeeStruct, AmScale, and ConvertResponse
type ValueWithCurrency struct {
Value types.Number `json:"value"`
Currency string `json:"currency"`
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
Currency string `json:"currency"`
Currency currency.Code `json:"currency"`

Comment on lines 251 to 253
var params Params
params.Values = url.Values{}
if err := params.encodeDateRange(startDate, endDate, startDateString, endDateString); 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.

🚧 I quite liked this pattern at first.
But then I realised we are always declaring it, and then calling a method on it, right?
So ... cut out the type and just have a func that returns a straight url.Values
You can either make the 2 funcs that we call just return a fresh url.Values, or you could pass one in.
The former approach seems like less code everywhere.
All the lines below this just become params.Set instead, and everything is clearer.

Suggested change
var params Params
params.Values = url.Values{}
if err := params.encodeDateRange(startDate, endDate, startDateString, endDateString); err != nil {
params := encodeDateRange(startDate, endDate, startDateString, endDateString)
if err != nil {

Copy link
Collaborator Author

@cranktakular cranktakular Sep 9, 2025

Choose a reason for hiding this comment

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

Yeah, it was set up this way from the start due to the potential of calling both methods (or any other ones I might add) within one function, but that hasn't made its way to the current version. I'll make that swap.

var ErrSymbolCannotBeMatched = errors.New("symbol cannot be matched")
// Public Errors
var (
ErrSettingProxyAddress = errors.New("setting proxy address error")
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
ErrSettingProxyAddress = errors.New("setting proxy address error")
ErrSettingProxyAddress = errors.New("error setting proxy address")

var (
ErrSettingProxyAddress = errors.New("setting proxy address error")
ErrEndpointPathNotFound = errors.New("no endpoint path found for the given key")
ErrSymbolCannotBeMatched = errors.New("symbol cannot be matched")
Copy link
Collaborator

Choose a reason for hiding this comment

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

🐒 Optional change

Suggested change
ErrSymbolCannotBeMatched = errors.New("symbol cannot be matched")
ErrSymbolNotMatched = errors.New("symbol cannot be matched")

diff.Remove)
}
}
// TODO: Add check for nil config etc.
Copy link
Collaborator

Choose a reason for hiding this comment

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

🚧 Please fulfil this TODO 🙂
Note you can use common.NilGuard

@thrasher-
Copy link
Collaborator

@codex please review

Copy link

@chatgpt-codex-connector chatgpt-codex-connector bot left a comment

Choose a reason for hiding this comment

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

Codex Review: Here are some suggestions.

About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you open a pull request for review, mark a draft as ready, or comment "@codex review". If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex fix this CI failure" or "@codex address that feedback".

Comment on lines 746 to 763
// GetActiveOrders retrieves any orders that are active/open
func (e *Exchange) GetActiveOrders(ctx context.Context, req *order.MultiOrderRequest) (order.FilteredOrders, error) {
if req == nil {
return nil, common.ErrNilPointer
}
err := req.Validate()
if err != nil {
return nil, err
}
var respOrders []GetOrderResponse
ordStatus := []string{"OPEN"}
if respOrders, err = e.iterativeGetAllOrders(ctx, req.Pairs, req.Type.String(), req.Side.String(), req.AssetType.Upper(), ordStatus, 1000, req.StartTime, req.EndTime); err != nil {
return nil, err
}
orders := make([]order.Detail, len(respOrders))
for i := range respOrders {
orderRec := e.getOrderRespToOrderDetail(&respOrders[i], respOrders[i].ProductID, asset.Spot)
orders[i] = *orderRec

Choose a reason for hiding this comment

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

[P1] Preserve requested asset type when reporting active orders

In GetActiveOrders the converted order.Detail is always created with asset.Spot, ignoring the caller’s requested req.AssetType. This means a futures request will return orders whose AssetType is mislabelled as spot, so downstream filters and order management that depend on the asset type will silently skip or mis-handle those orders. The conversion in GetOrderHistory correctly forwards req.AssetType; the same should happen here.

Useful? React with 👍 / 👎.

Comment on lines 1500 to 1501
return sign, regTime.Add(time.Minute * 2), err
// The code below mostly works, but seems to lead to bad results on the signature step. Deferring until later
Copy link
Collaborator

Choose a reason for hiding this comment

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

When I was reviewing this for some fun I asked GPT5 to resolve this and looks like it's working:

// GetJWT generates a new JWT
func (e *Exchange) GetJWT(ctx context.Context, uri string) (string, time.Time, error) {
	creds, err := e.GetCredentials(ctx)
	if err != nil {
		return "", time.Time{}, err
	}

	block, _ := pem.Decode([]byte(creds.Secret))
	if block == nil {
		return "", time.Time{}, errCantDecodePrivKey
	}

	privateKey, 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
	}

	// Manual JWS (ES256) implementation
	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
	}
	headEnc := base64.RawURLEncoding.EncodeToString(headJSON)

	regTime := time.Now()
	body := map[string]any{
		"iss": "cdp",
		"nbf": regTime.Unix(),
		"exp": regTime.Add(2 * time.Minute).Unix(),
		"sub": creds.Key,
	}
	if uri != "" {
		body["uri"] = uri
	}
	bodyJSON, err := json.Marshal(body)
	if err != nil {
		return "", time.Time{}, err
	}
	bodyEnc := base64.RawURLEncoding.EncodeToString(bodyJSON)

	// Signing input per JWS
	signingInput := headEnc + "." + bodyEnc
	hash := sha256.Sum256([]byte(signingInput))

	// IMPORTANT: JWS needs raw R||S, not ASN.1 DER
	r, s, err := ecdsa.Sign(rand.Reader, privateKey, hash[:])
	if err != nil {
		return "", time.Time{}, err
	}

	// Optional: enforce low-S to avoid malleability
	n := privateKey.Params().N
	halfN := new(big.Int).Rsh(n, 1)
	if s.Cmp(halfN) == 1 {
		s.Sub(n, s)
	}

	// Serialise R||S, each left-padded to 32 bytes for P-256
	rb := r.Bytes()
	sb := s.Bytes()

	sig := make([]byte, 64)
	copy(sig[32-len(rb):32], rb)
	copy(sig[64-len(sb):], sb)

	sigEnc := base64.RawURLEncoding.EncodeToString(sig)

	return signingInput + "." + sigEnc, regTime.Add(2 * time.Minute), nil
}

Though I'd say we can just leave the current JWT implementation and can rid base64URLEncode since it isn't being used currently

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 per newer information we've discussed (the NPM attack, the fact that we only use the external package for this single exchange), I have tested and will be implementing this GPT5-written code.

We can reconsider the utility of the dependency if/when other exchanges need JWT.

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.

I didnt come across much from checking changes since my last review. I'll do some more testing but should still be happy with. it

Copy link
Collaborator

Choose a reason for hiding this comment

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

rm 🌈

return resp, e.SendHTTPRequest(ctx, exchange.RestSpot, v3Path+timePath, nil, &resp)
}

type sendMoneyReqBase struct {
Copy link
Collaborator

Choose a reason for hiding this comment

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

move to types

return resp, e.SendAuthenticatedHTTPRequest(ctx, exchange.RestSpot, http.MethodGet, path, params.Values, nil, false, &resp)
}

type fiatTransferReqBase struct {
Copy link
Collaborator

Choose a reason for hiding this comment

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

move to types

var resp *ManyDeposWithdrResp
err := e.SendAuthenticatedHTTPRequest(ctx, exchange.RestSpot, http.MethodGet, path, params.Values, nil, false, &resp)
if err != nil {
if err := e.SendAuthenticatedHTTPRequest(ctx, exchange.RestSpot, http.MethodGet, path, params.Values, nil, false, &resp); 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.

Seems like a hot area. This could have worked but this isn't holding up anything:

	return resp, e.SendAuthenticatedHTTPRequest(ctx, exchange.RestSpot, http.MethodGet, path, params.Values, nil, false, &resp)

Copy link
Collaborator

@thrasher- thrasher- 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 making those changes! Almost there

)

func TestMain(m *testing.M) {
if testingInSandbox {
Copy link
Collaborator

Choose a reason for hiding this comment

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

Needs to be placed after the exchangeBaseHelper otherwise would be overridden

assert.ErrorIs(t, err, errUnrecognisedStrategyType)
}

func TestBase64URLEncode(t *testing.T) {
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 removed now since you're using base64 rawdog encoding which does the same thing

e.API.Endpoints = e.NewEndpoints()
if err = e.API.Endpoints.SetDefaultEndpoints(map[exchange.URL]string{
exchange.RestSpot: apiURL,
exchange.RestSandbox: sandboxAPIURL,
Copy link
Collaborator

Choose a reason for hiding this comment

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

Noticed that this sandbox URL differs to the one in config_example.json

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Good catch! Fixed.

return nil, err
}
var respOrders []GetOrderResponse
ordStatus := []string{"OPEN"}
Copy link
Collaborator

Choose a reason for hiding this comment

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

Can remove this and use openStatus since they're the same

e.Enabled = true
e.API.CredentialsValidator.RequiresKey = true
e.API.CredentialsValidator.RequiresSecret = true
e.API.CredentialsValidator.RequiresBase64DecodeSecret = false
Copy link
Collaborator

Choose a reason for hiding this comment

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

Can rm this since the default value is false

for i := range aliases {
isEnabled, err := e.CurrencyPairs.IsPairEnabled(aliases[i], assetType)
if err != nil {
errs = fmt.Errorf("%v %v", errs, err)
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
errs = fmt.Errorf("%v %v", errs, err)
errs = common.AppendErrors(errs, err)

if isEnabled {
book.Pair = aliases[i]
if err := book.Process(); err != nil {
errs = fmt.Errorf("%v %v", errs, err)
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
errs = fmt.Errorf("%v %v", errs, err)
errs = common.AppendErrors(errs, err)

Copy link
Collaborator

@gbjk gbjk 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 those changes. Good work

None of my requested changes are mandatory.

var params Params
params.Values = url.Values{}
if err := params.encodeDateRange(startDate, endDate, startDateString, endDateString); err != nil {
vals, err := encodeDateRange(startDate, endDate, startDateString, endDateString)
Copy link
Collaborator

Choose a reason for hiding this comment

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

🦍 vals isn't a great name. Values is only valid in the context of url.
These are url params, or params.

Suggested change
vals, err := encodeDateRange(startDate, endDate, startDateString, endDateString)
params, err := encodeDateRange(startDate, endDate, startDateString, endDateString)

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 variable returned by that function is of the type url.Values, so wouldn't that be fine?

Copy link
Collaborator

@gbjk gbjk Sep 16, 2025

Choose a reason for hiding this comment

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

Because Values only has meaning with the context of url.Values.
As soon as you drop url, you're just calling this things.

I've just seen the func is renamed. That actually helps a lot.
Happy for you to ignore or action.

var params Params
params.Values = url.Values{}
if err := params.encodeDateRange(startDate, endDate, "start_sequence_timestamp", "end_sequence_timestamp"); err != nil {
vals, err := encodeDateRange(startDate, endDate, "start_sequence_timestamp", "end_sequence_timestamp")
Copy link
Collaborator

Choose a reason for hiding this comment

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

🦍 🌴 Same as above

Suggested change
vals, err := encodeDateRange(startDate, endDate, "start_sequence_timestamp", "end_sequence_timestamp")
params, err := encodeDateRange(startDate, endDate, "start_sequence_timestamp", "end_sequence_timestamp")

var params Params
params.Values = make(url.Values)
if err := params.encodeDateRange(req.StartDate, req.EndDate, startDateString, endDateString); err != nil {
vals, err := encodeDateRange(req.StartDate, req.EndDate, startDateString, endDateString)
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
vals, err := encodeDateRange(req.StartDate, req.EndDate, startDateString, endDateString)
params, err := encodeDateRange(req.StartDate, req.EndDate, startDateString, endDateString)


// GetAllWallets lists all accounts associated with the API key
func (e *Exchange) GetAllWallets(ctx context.Context, pag PaginationInp) (*GetAllWalletsResponse, error) {
vals := encodePagination(pag)
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
vals := encodePagination(pag)
params := encodePagination(pag)

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.

Changes look great just some minor nits if you so choose.

if err != nil {
return book, err
}
if orderbookNew, err = e.GetProductBookV3(ctx, p, 1000, 0, false); 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.

suggestion: doesn't look like you need to ifshort can just short hand this but up to you if you want to change

Copy link
Collaborator

@gbjk gbjk left a comment

Choose a reason for hiding this comment

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

Good work.

I've left the vals threads open if you want to change them, but happy for you to ignore too, especially now that the func is renamed.

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.

Nothing else from me. Nice work! 🚀🚀 🚀 🚀

Copy link
Collaborator

@thrasher- thrasher- 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 making those changes, double checked sandbox endpoints and all working well! Great work

})
if err != nil {
log.Fatal("failed to set sandbox endpoint", err)
log.Fatalf("Coinbase SetDefaultEndpoints sandbox error: %s", err)
Copy link
Collaborator

Choose a reason for hiding this comment

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

Erm. Yeah. 💯 you don't have to do this, but just ... yeah. Sorry.
More just for future thinking.
Want to move Error to early in the message. Here we probably allow the exchange context to go first, but that's it.
Using func names is fine, but most of the time just using human readable is better.

Suggested change
log.Fatalf("Coinbase SetDefaultEndpoints sandbox error: %s", err)
log.Fatalf("Coinbase: Error setting sandbox defaults endpoints: %s", err)

or if we want to keep the func name (which I don't):

Suggested change
log.Fatalf("Coinbase SetDefaultEndpoints sandbox error: %s", err)
log.Fatalf("Coinbase: Error in SetDefaultEndpoints for sandbox: %s", err)

@thrasher- thrasher- merged commit fd9aaf0 into thrasher-corp:master Sep 16, 2025
11 of 15 checks passed
@github-project-automation github-project-automation bot moved this from In review to Done in GoCryptoTrader Kanban Sep 16, 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