Thanks to visit codestin.com
Credit goes to github.com

Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
4 changes: 2 additions & 2 deletions AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -58,13 +58,13 @@

## Storage & Data Modeling
- SQLite schema (`storage/sqlc/schema.sql`) covers 3Commas bots/deals/events, Hyperliquid submissions/status history, vault payloads, and WebAuthn credentials.
- Sqlc queries (`storage/sqlc/queries.sql`) power both API pagination and internal helpers (e.g., metadata lookups, take-profit queries).
- Sqlc queries (`storage/sqlc/queries.sql`) power both API pagination and internal helpers (e.g., OrderId lookups, take-profit queries).
- Storage methods publish `StreamEvent`s so the API can fan-out live updates without polling.

## API & Eventing
- OpenAPI spec lives in `openapi.yaml`; generation config is `oapi.yaml`.
- `internal/api/handler.go` implements the strict server interface, wraps vault + WebAuthn services, and exposes Hyperliquid price subscriptions.
- SSE is delivered through `internal/api/stream_controller.go`; filters support metadata prefix, bot/deal IDs, timestamps.
- SSE is delivered through `internal/api/stream_controller.go`; filters support OrderID prefix, bot/deal IDs, timestamps.
- Manual order actions go through the API to the queue emitter to reuse worker pacing.

## Testing & Fixtures
Expand Down
26 changes: 13 additions & 13 deletions adapter/adapter.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,20 +4,20 @@ import (
"math"

tc "github.com/recomma/3commas-sdk-go/threecommas"
"github.com/recomma/recomma/metadata"
"github.com/recomma/recomma/orderid"
"github.com/recomma/recomma/recomma"
"github.com/sonirico/go-hyperliquid"
)

// ToCreateOrderRequest converts one 3Commas BotEvent + its Deal metadata
// ToCreateOrderRequest converts one 3Commas BotEvent + its Deal OrderId
// into your venue's CreateOrderRequest.
//
// Mapping notes:
// - Coin: Deal.ToCurrency (base coin).
// - IsBuy: BotEvent.Type == "BUY".
// - Price / Size: BotEvent already exposes float64 fields.
// - ReduceOnly: true for SELL + take-profit BotEvent.
func ToCreateOrderRequest(currency string, event recomma.BotEvent, md metadata.Metadata) hyperliquid.CreateOrderRequest {
func ToCreateOrderRequest(currency string, event recomma.BotEvent, oid orderid.OrderId) hyperliquid.CreateOrderRequest {
isBuy := event.Type == tc.BUY
reduceOnly := !isBuy && event.OrderType == tc.MarketOrderDealOrderTypeTakeProfit

Expand All @@ -28,7 +28,7 @@ func ToCreateOrderRequest(currency string, event recomma.BotEvent, md metadata.M
Size: event.Size,
ReduceOnly: reduceOnly,
OrderType: orderTypeFrom3C(event),
ClientOrderID: md.HexAsPointer(),
ClientOrderID: oid.HexAsPointer(),
}

return req
Expand Down Expand Up @@ -98,7 +98,7 @@ func BuildAction(
currency string,
prev *recomma.BotEvent,
next recomma.BotEvent,
md metadata.Metadata,
oid orderid.OrderId,
) recomma.Action {
// 1. classify transition
prevState := classify(prev)
Expand All @@ -107,20 +107,20 @@ func BuildAction(
switch {
case prev == nil && nextState == stateActive:
// New order we have never submitted → create
req := ToCreateOrderRequest(currency, next, md)
req := ToCreateOrderRequest(currency, next, oid)
return recomma.Action{Type: recomma.ActionCreate, Create: &req}
case prev == nil:
// Never saw it active; HL won’t have it either
return recomma.Action{Type: recomma.ActionNone, Reason: "skipped: no prior order submitted"}
case prevState == stateActive && nextState == stateCancelled:
cancel := ToCancelByCloidRequest(currency, md)
cancel := ToCancelByCloidRequest(currency, oid)
return recomma.Action{Type: recomma.ActionCancel, Cancel: &cancel}
case prevState == stateActive && nextState == stateFilled:
// Nothing to do on Hyperliquid, but tell caller to persist updated snapshot
return recomma.Action{Type: recomma.ActionNone, Reason: "filled locally"}
case prevState == stateActive && nextState == stateActive:
if needsModify(prev, &next) {
modify := ToModifyOrderRequest(currency, next, md)
modify := ToModifyOrderRequest(currency, next, oid)
return recomma.Action{Type: recomma.ActionModify, Modify: &modify}
}
return recomma.Action{Type: recomma.ActionNone, Reason: "no material change"}
Expand All @@ -129,17 +129,17 @@ func BuildAction(
}
}

func ToCancelByCloidRequest(currency string, md metadata.Metadata) hyperliquid.CancelOrderRequestByCloid {
func ToCancelByCloidRequest(currency string, oid orderid.OrderId) hyperliquid.CancelOrderRequestByCloid {
return hyperliquid.CancelOrderRequestByCloid{
Coin: currency,
Cloid: md.Hex(),
Cloid: oid.Hex(),
}
}

func ToModifyOrderRequest(currency string, next recomma.BotEvent, md metadata.Metadata) hyperliquid.ModifyOrderRequest {
req := ToCreateOrderRequest(currency, next, md)
func ToModifyOrderRequest(currency string, next recomma.BotEvent, oid orderid.OrderId) hyperliquid.ModifyOrderRequest {
req := ToCreateOrderRequest(currency, next, oid)
return hyperliquid.ModifyOrderRequest{
Oid: md.Hex(),
Oid: oid.Hex(),
Order: req,
}
}
Expand Down
2 changes: 1 addition & 1 deletion cmd/recomma/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -228,7 +228,7 @@ func main() {
Wallet: secrets.Secrets.HYPERLIQUIDWALLET,
})

constraints := hl.NewMetadataCache(info)
constraints := hl.NewOrderIdCache(info)
scaler := orderscaler.New(store, constraints, logger, orderscaler.WithMaxMultiplier(cfg.OrderScalerMaxMultiplier))

fillTracker := filltracker.New(store, logger)
Expand Down
2 changes: 1 addition & 1 deletion docs/modules/ROOT/pages/operations/order-scaler.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -127,7 +127,7 @@ confirm the bot now inherits the default multiplier.

* Every `PUT` or `DELETE` produces an `order_scaler_config` event on the `/sse/orders`
stream. The event body carries the actor, default configuration, any override, and the
metadata hex that downstream services can use to correlate changes.
OrderId hex that downstream services can use to correlate changes.
* Whenever the engine scales a mirrored order it writes a `scaled_order_audit` record.
The audit entry captures the original and scaled sizes, the multiplier that was used,
any rounding delta, and the operator who configured the multiplier at the time. These
Expand Down
24 changes: 12 additions & 12 deletions emitter/emitter.go
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,7 @@ var defaultHyperLiquidEmitterConfig = HyperLiquidEmitterConfig{

const iocRetryBumpRatio = 0.0005

type iocRetryMetadata struct {
type iocRetryOrderId struct {
count int
lastError string
}
Expand Down Expand Up @@ -208,7 +208,7 @@ func (e *HyperLiquidEmitter) applyIOCOffset(order hyperliquid.CreateOrderRequest
}

func (e *HyperLiquidEmitter) Emit(ctx context.Context, w recomma.OrderWork) error {
logger := e.logger.With("md", w.MD.Hex()).With("bot-event", w.BotEvent)
logger := e.logger.With("orderid", w.OrderId.Hex()).With("bot-event", w.BotEvent)
logger.Debug("emit", slog.Any("orderwork", w))

if w.Action.Type == recomma.ActionNone {
Expand All @@ -222,7 +222,7 @@ func (e *HyperLiquidEmitter) Emit(ctx context.Context, w recomma.OrderWork) erro

didSubmit := false
var executedAction recomma.Action
var retryMeta *iocRetryMetadata
var retryMeta *iocRetryOrderId
var lastStatus *hyperliquid.OrderStatus

// TODO: decide if we want to persist the result we get back here, it's not interesting ususally as it just states `resting`
Expand All @@ -232,9 +232,9 @@ func (e *HyperLiquidEmitter) Emit(ctx context.Context, w recomma.OrderWork) erro
w.Action.Create = &order

if e.ws != nil {
if status, ok := e.ws.Get(ctx, w.MD); ok && isLiveStatus(status) {
if status, ok := e.ws.Get(ctx, w.OrderId); ok && isLiveStatus(status) {
var latestSubmission *hyperliquid.CreateOrderRequest
if submission, found, err := e.store.LoadHyperliquidSubmission(ctx, w.MD); err != nil {
if submission, found, err := e.store.LoadHyperliquidSubmission(ctx, w.OrderId); err != nil {
logger.Warn("could not load latest submission", slog.String("error", err.Error()))
} else if found {
switch {
Expand All @@ -251,7 +251,7 @@ func (e *HyperLiquidEmitter) Emit(ctx context.Context, w recomma.OrderWork) erro
return recomma.ErrOrderAlreadySatisfied
}

modifyReq := hyperliquid.ModifyOrderRequest{Oid: w.MD.Hex(), Order: order}
modifyReq := hyperliquid.ModifyOrderRequest{Oid: w.OrderId.Hex(), Order: order}
status, err := e.submitModify(ctx, logger, w, modifyReq)
if err != nil {
return err
Expand Down Expand Up @@ -292,15 +292,15 @@ func (e *HyperLiquidEmitter) Emit(ctx context.Context, w recomma.OrderWork) erro
status, err := e.exchange.Order(ctx, *w.Action.Create, nil)
if err != nil {
if strings.Contains(err.Error(), "Order must have minimum value") {
if err := e.store.RecordHyperliquidOrderRequest(ctx, w.MD, *w.Action.Create, w.BotEvent.RowID); err != nil {
if err := e.store.RecordHyperliquidOrderRequest(ctx, w.OrderId, *w.Action.Create, w.BotEvent.RowID); err != nil {
logger.Warn("could not add to store", slog.String("error", err.Error()))
}
logger.Warn("could not submit (order value), ignoring", slog.String("error", err.Error()))
return nil
}

if strings.Contains(err.Error(), "Reduce only order would increase position") {
if err := e.store.RecordHyperliquidOrderRequest(ctx, w.MD, *w.Action.Create, w.BotEvent.RowID); err != nil {
if err := e.store.RecordHyperliquidOrderRequest(ctx, w.OrderId, *w.Action.Create, w.BotEvent.RowID); err != nil {
logger.Warn("could not add to store", slog.String("error", err.Error()))
}
logger.Warn("could not submit (reduce only order, increase position), ignoring", slog.String("error", err.Error()))
Expand Down Expand Up @@ -337,13 +337,13 @@ func (e *HyperLiquidEmitter) Emit(ctx context.Context, w recomma.OrderWork) erro
return err
}

if err := e.store.RecordHyperliquidOrderRequest(ctx, w.MD, *w.Action.Create, w.BotEvent.RowID); err != nil {
if err := e.store.RecordHyperliquidOrderRequest(ctx, w.OrderId, *w.Action.Create, w.BotEvent.RowID); err != nil {
logger.Warn("could not add to store", slog.String("error", err.Error()))
}
executedAction = w.Action
didSubmit = true
if retryInfo.count > 0 {
retryMeta = &iocRetryMetadata{count: retryInfo.count, lastError: retryInfo.lastErr}
retryMeta = &iocRetryOrderId{count: retryInfo.count, lastError: retryInfo.lastErr}
}

case recomma.ActionCancel:
Expand All @@ -359,7 +359,7 @@ func (e *HyperLiquidEmitter) Emit(ctx context.Context, w recomma.OrderWork) erro
logger.Warn("could not cancel order", slog.String("error", err.Error()), slog.Any("action", w.Action.Cancel))
return fmt.Errorf("could not cancel order: %w", err)
}
if err := e.store.RecordHyperliquidCancel(ctx, w.MD, *w.Action.Cancel, w.BotEvent.RowID); err != nil {
if err := e.store.RecordHyperliquidCancel(ctx, w.OrderId, *w.Action.Cancel, w.BotEvent.RowID); err != nil {
logger.Warn("could not add to store", slog.String("error", err.Error()))
}
executedAction = w.Action
Expand Down Expand Up @@ -420,7 +420,7 @@ func (e *HyperLiquidEmitter) submitModify(
logger.Warn("could not modify order", slog.String("error", err.Error()), slog.Any("action", req))
return nil, fmt.Errorf("could not modify order: %w", err)
}
if err := e.store.AppendHyperliquidModify(ctx, w.MD, req, w.BotEvent.RowID); err != nil {
if err := e.store.AppendHyperliquidModify(ctx, w.OrderId, req, w.BotEvent.RowID); err != nil {
logger.Warn("could not add to store", slog.String("error", err.Error()))
}
return &status, nil
Expand Down
14 changes: 7 additions & 7 deletions emitter/emitter_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ import (
"sync"
"testing"

"github.com/recomma/recomma/metadata"
"github.com/recomma/recomma/orderid"
"github.com/recomma/recomma/recomma"
"github.com/recomma/recomma/storage"
"github.com/sonirico/go-hyperliquid"
Expand Down Expand Up @@ -62,8 +62,8 @@ func TestHyperLiquidEmitterIOCRetriesLogSuccess(t *testing.T) {
WithHyperLiquidEmitterConfig(HyperLiquidEmitterConfig{MaxIOCRetries: 3}),
)

md := metadata.Metadata{BotID: 1, DealID: 2, BotEventID: 3}
cloid := md.Hex()
oid := orderid.OrderId{BotID: 1, DealID: 2, BotEventID: 3}
cloid := oid.Hex()
order := hyperliquid.CreateOrderRequest{
Coin: "BTC",
IsBuy: true,
Expand All @@ -75,7 +75,7 @@ func TestHyperLiquidEmitterIOCRetriesLogSuccess(t *testing.T) {
},
}
work := recomma.OrderWork{
MD: md,
OrderId: oid,
Action: recomma.Action{Type: recomma.ActionCreate, Create: &order},
BotEvent: recomma.BotEvent{RowID: 1},
}
Expand Down Expand Up @@ -134,8 +134,8 @@ func TestHyperLiquidEmitterIOCRetriesWarnOnFailure(t *testing.T) {
WithHyperLiquidEmitterConfig(HyperLiquidEmitterConfig{MaxIOCRetries: 3}),
)

md := metadata.Metadata{BotID: 4, DealID: 5, BotEventID: 6}
cloid := md.Hex()
oid := orderid.OrderId{BotID: 4, DealID: 5, BotEventID: 6}
cloid := oid.Hex()
order := hyperliquid.CreateOrderRequest{
Coin: "ETH",
IsBuy: true,
Expand All @@ -147,7 +147,7 @@ func TestHyperLiquidEmitterIOCRetriesWarnOnFailure(t *testing.T) {
},
}
work := recomma.OrderWork{
MD: md,
OrderId: oid,
Action: recomma.Action{Type: recomma.ActionCreate, Create: &order},
BotEvent: recomma.BotEvent{RowID: 2},
}
Expand Down
Loading