Skip to content

Conversation

@jdupas22
Copy link

@jdupas22 jdupas22 commented Nov 28, 2025

Summary by cubic

Add first-class Trades support across the API and engine, and introduce a Bitstamp connector with trade ingestion. Also adds an initial Coinbase Prime connector with accounts, balances, payments, and wallet-to-wallet transfers.

  • New Features

    • Trade domain models and validation (status, side, order type, fees, liquidity).
    • New v3 endpoints: POST /trades, GET /trades, GET /trades/{tradeID}, PATCH /trades/{tradeID}/metadata.
    • Engine workflows/activities to fetch trades from connectors, upsert to storage, and publish trade events.
    • Bitstamp connector: accounts, balances, payments, and trades ingestion with capabilities registered.
    • Coinbase Prime connector: accounts, balances, payments, and wallet-to-wallet transfers using the Prime SDK.
    • Dummypay: trades fetch added; capabilities updated to allow Formance trade creation.
  • Dependencies

    • Added github.com/coinbase-samples/prime-sdk-go v0.5.3 and github.com/shopspring/decimal v1.4.0.
    • Updated go.uber.org/mock to v0.4.0 and regenerated mocks.
    • docker-compose: set GOCACHE and GOMODCACHE for gateway and workers to speed local builds.

Written for commit a987f9b. Summary will update automatically on new commits.

jdupas22 and others added 25 commits October 1, 2025 18:25
feat: adding a connector development server with direct access to connectors functions

Signed-off-by: Clément Salaün <[email protected]>
Signed-off-by: Clément Salaün <[email protected]>
* Handle SQLSTATE 22P02 in storage as HTTP 400

* Disable profiler in e2e tests to remove unneeded overhead
…ling

- Added detailed logging for account and balance fetching processes.
- Refactored payment transaction handling to support multiple payment types from a single transaction.
- Updated transaction struct to dynamically capture exchange rates.
- Cleaned up unused comments and improved code readability across various files.
…ndling

- Introduced FetchNextTrades method in the Bitstamp plugin to retrieve trades.
- Updated fetchNextTrades to handle unique trades and filter out duplicates.
- Modified transactionToTrade to include account reference and adjusted logging.
- Removed legacy payment handling for exchange transactions, now managed by FetchTrades.
- Added new capability for fetching trades in the Bitstamp capabilities list.
@coderabbitai
Copy link
Contributor

coderabbitai bot commented Nov 28, 2025

Important

Review skipped

Draft detected.

Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

✨ Finishing touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch feat/trades-and-bitstamp

Tip

📝 Customizable high-level summaries are now available in beta!

You can now customize how CodeRabbit generates the high-level summary in your pull requests — including its content, structure, tone, and formatting.

  • Provide your own instructions using the high_level_summary_instructions setting.
  • Format the summary however you like (bullet lists, tables, multi-section layouts, contributor stats, etc.).
  • Use high_level_summary_in_walkthrough to move the summary from the description to the walkthrough section.

Example instruction:

"Divide the high-level summary into five sections:

  1. 📝 Description — Summarize the main change in 50–60 words, explaining what was done.
  2. 📓 References — List relevant issues, discussions, documentation, or related PRs.
  3. 📦 Dependencies & Requirements — Mention any new/updated dependencies, environment variable changes, or configuration updates.
  4. 📊 Contributor Summary — Include a Markdown table showing contributions:
    | Contributor | Lines Added | Lines Removed | Files Changed |
  5. ✔️ Additional Notes — Add any extra reviewer context.
    Keep each section concise (under 200 words) and use bullet or numbered lists for clarity."

Note: This feature is currently in beta for Pro-tier users, and pricing will be announced later.


Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@@ -0,0 +1,185 @@
package main
Copy link
Contributor

Choose a reason for hiding this comment

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

Do we still need this file for something?

return e("failed to convert trade to model", err)
}

payload := internalEvents.TradeMessagePayload{
Copy link
Contributor

Choose a reason for hiding this comment

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

Does the consumer of the event need the entire payload? It might be an option to send fewer fields and allow the consumer to query payments if they want additional information.

Copy link
Author

Choose a reason for hiding this comment

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

Yes we could indeed! What would be the gain of doing that though?

Copy link
Contributor

Choose a reason for hiding this comment

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

Some queue systems have somewhat limited payload size so if you have big nested structures you might run into the upper limits and not be able to send the event at all.

But also, just in general I'd be interested to know what the business use-case of these notifications would be.
What value does it bring to clients to know about new trades? What will they typically do with that info?

Fabrice recently did a big refactor of the notifications to reduce the operational costs, so it should be cheaper for us to send notifications now, but I also wanted to check if you added this function to send notifications because there was a particular business use-case in mind, or if you just copied the pattern we had for payments?

CreateFormancePayment(ctx context.Context, payment models.Payment) error
// Create a Formance trade, no call to the plugin, just a creation
// of a trade in the database related to the provided connector id.
CreateFormanceTrade(ctx context.Context, trade models.Trade) error
Copy link
Contributor

Choose a reason for hiding this comment

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

What is the purpose of creating a trade on Formance only? If it doesn't connect to the PSP then what is it used for?

For payments we have a create payment endpoint which is used primarily by people using the generic connector that need a way to backfill data that for one reason or another their endpoint won't return via the connector.

But we have two separate entities:

  • payment_initiation (created via formance API)
  • payment (imported via the connector)

the former is the intent to create a payment, whereas the latter is a representation of something that exists on the PSP. The payment_initiation can be forwarded to the PSP - this will result in a corresponding payment being linked.

Here you have a mixed trade object that may or may not represent a real trade known by the PSP. You also allow (local) updates of the trade metadata which will never be reflected on the PSP.
Would it not be better to keep two different entities that can be linked, like we do for payments?

}

if len(nextTasks) > 0 {
// Logic for next tasks if needed
Copy link
Contributor

Choose a reason for hiding this comment

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

You're probably not seeing any issues with this remaining unimplemented because your connector put fetch trades as the last element in the plugin workflow. Other connectors might not have trades being the last element, which will result in the rest of the workflow never executing. Let's handle this properly.

Comment on lines +46 to +64
// Safely get logger - it may be nil in unit tests
var logger interface{ Info(string, ...interface{}); Error(string, ...interface{}) }
// Try to get activity logger, but don't panic if not in activity context
func() {
defer func() {
if r := recover(); r != nil {
// Not an activity context, logger stays nil
logger = nil
}
}()
logger = activity.GetLogger(ctx)
}()

if logger != nil {
logger.Info("SendEvents activity started",
"idempotency_key", req.IdempotencyKey,
"has_payment", req.Payment != nil)
}

Copy link
Contributor

Choose a reason for hiding this comment

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

You can actually just use a.logger which can be set in unit tests as well. Then we don't need all these if statements checking if the logger is nil

Comment on lines +15 to +21
// TODO: accountsState will be used to know at what point we're at when
// fetching the PSP accounts.
// This struct will be stored as a raw json, you're free to put whatever
// you want.
// Example:
// LastPage int `json:"lastPage"`
// LastIDCreated int64 `json:"lastIDCreated"`
Copy link
Contributor

Choose a reason for hiding this comment

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

Pagination is unimplemented?


if resp.StatusCode != http.StatusOK {
// Try to unmarshal error response
body, _ := io.ReadAll(resp.Body)
Copy link
Contributor

Choose a reason for hiding this comment

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

Please handle the error.

Comment on lines +244 to +252
func newNonce() string {
const charset = "abcdefghijklmnopqrstuvwxyz0123456789"
b := make([]byte, 36)
rand.Read(b)
for i := range b {
b[i] = charset[b[i]%byte(len(charset))]
}
return string(b)
}
Copy link
Contributor

Choose a reason for hiding this comment

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

Since this is only pseudorandom, there might be collisions if a client has multiple payments pods running. Probably not a critical issue right now, but probably using the current time as part of the random generation would decrease chances of collision.

}

// ExpectedLegAmounts calculates expected BASE and QUOTE payment amounts
func ExpectedLegAmounts(trade Trade) (base decimal.Decimal, quote decimal.Decimal) {
Copy link
Contributor

Choose a reason for hiding this comment

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

Any reason you're using this particular decimal library instead of handling decimals the way we do for amounts in payments / balance?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants