From 7ac98ac03a042a1659e53816d001bd58fa4073cd Mon Sep 17 00:00:00 2001 From: jeffyanta Date: Sun, 25 Jan 2026 23:38:20 -0500 Subject: [PATCH] Add swap metadata to intent record --- ocp/balance/calculator_test.go | 9 ++--- ocp/data/deposit/memory/store.go | 22 ---------- ocp/data/deposit/postgres/model.go | 54 +++++++------------------ ocp/data/deposit/postgres/store.go | 5 --- ocp/data/deposit/postgres/store_test.go | 1 - ocp/data/deposit/store.go | 23 +++-------- ocp/data/deposit/tests/tests.go | 24 +++-------- ocp/data/intent/intent.go | 46 ++++++++++++++++++--- ocp/data/intent/postgres/model.go | 41 ++++++++++++++----- ocp/data/intent/postgres/store_test.go | 2 +- ocp/data/intent/tests/tests.go | 20 +++++++-- ocp/data/internal.go | 4 -- ocp/rpc/account/server_test.go | 7 ++-- ocp/rpc/transaction/intent_handler.go | 7 ++++ ocp/worker/geyser/external_deposit.go | 11 +++-- ocp/worker/swap/util.go | 27 ++++++++++--- 16 files changed, 156 insertions(+), 147 deletions(-) diff --git a/ocp/balance/calculator_test.go b/ocp/balance/calculator_test.go index aeb3c86..93fba32 100644 --- a/ocp/balance/calculator_test.go +++ b/ocp/balance/calculator_test.go @@ -11,6 +11,7 @@ import ( commonpb "github.com/code-payments/ocp-protobuf-api/generated/go/common/v1" + currency_lib "github.com/code-payments/ocp-server/currency" "github.com/code-payments/ocp-server/ocp/common" ocp_data "github.com/code-payments/ocp-server/ocp/data" "github.com/code-payments/ocp-server/ocp/data/account" @@ -18,7 +19,6 @@ import ( "github.com/code-payments/ocp-server/ocp/data/deposit" "github.com/code-payments/ocp-server/ocp/data/intent" "github.com/code-payments/ocp-server/ocp/data/transaction" - currency_lib "github.com/code-payments/ocp-server/currency" timelock_token_v1 "github.com/code-payments/ocp-server/solana/timelock/v1" "github.com/code-payments/ocp-server/testutil" ) @@ -436,10 +436,9 @@ func setupBalanceTestData(t *testing.T, env balanceTestEnv, data *balanceTestDat // There's no intent, so we have an external deposit if len(txn.intentID) == 0 && txn.transactionState != transaction.ConfirmationUnknown { depositRecord := &deposit.Record{ - Signature: fmt.Sprintf("txn%d", i), - Destination: txn.destination.PublicKey().ToBase58(), - Amount: txn.quantity, - UsdMarketValue: 1.0, + Signature: fmt.Sprintf("txn%d", i), + Destination: txn.destination.PublicKey().ToBase58(), + Amount: txn.quantity, Slot: 12345, ConfirmationState: txn.transactionState, diff --git a/ocp/data/deposit/memory/store.go b/ocp/data/deposit/memory/store.go index 7bc4a78..96da534 100644 --- a/ocp/data/deposit/memory/store.go +++ b/ocp/data/deposit/memory/store.go @@ -85,26 +85,12 @@ func (s *store) GetQuarkAmountBatch(_ context.Context, accounts ...string) (map[ return res, nil } -// GetUsdAmount implements deposit.Store.GetUsdAmount -func (s *store) GetUsdAmount(ctx context.Context, account string) (float64, error) { - s.mu.Lock() - defer s.mu.Unlock() - - return s.getUsdAmount(account), nil -} - func (s *store) getQuarkAmount(account string) uint64 { items := s.findByDestination(account) items = s.filterFinalized(items) return s.sumAmounts(items) } -func (s *store) getUsdAmount(account string) float64 { - items := s.findByDestination(account) - items = s.filterFinalized(items) - return s.sumUsd(items) -} - func (s *store) find(data *deposit.Record) *deposit.Record { for _, item := range s.records { if item.Id == data.Id { @@ -155,14 +141,6 @@ func (s *store) sumAmounts(items []*deposit.Record) uint64 { return res } -func (s *store) sumUsd(items []*deposit.Record) float64 { - var res float64 - for _, item := range items { - res += item.UsdMarketValue - } - return res -} - func (s *store) reset() { s.mu.Lock() defer s.mu.Unlock() diff --git a/ocp/data/deposit/postgres/model.go b/ocp/data/deposit/postgres/model.go index 0d7da65..1a8dadb 100644 --- a/ocp/data/deposit/postgres/model.go +++ b/ocp/data/deposit/postgres/model.go @@ -9,9 +9,9 @@ import ( "github.com/jmoiron/sqlx" + pgutil "github.com/code-payments/ocp-server/database/postgres" "github.com/code-payments/ocp-server/ocp/data/deposit" "github.com/code-payments/ocp-server/ocp/data/transaction" - pgutil "github.com/code-payments/ocp-server/database/postgres" ) const ( @@ -21,10 +21,9 @@ const ( type model struct { Id sql.NullInt64 `db:"id"` - Signature string `db:"signature"` - Destination string `db:"destination"` - Amount uint64 `db:"amount"` - UsdMarketValue float64 `db:"usd_market_value"` + Signature string `db:"signature"` + Destination string `db:"destination"` + Amount uint64 `db:"amount"` Slot uint64 `db:"slot"` ConfirmationState int `db:"confirmation_state"` @@ -38,10 +37,9 @@ func toModel(obj *deposit.Record) (*model, error) { } return &model{ - Signature: obj.Signature, - Destination: obj.Destination, - Amount: obj.Amount, - UsdMarketValue: obj.UsdMarketValue, + Signature: obj.Signature, + Destination: obj.Destination, + Amount: obj.Amount, Slot: obj.Slot, ConfirmationState: int(obj.ConfirmationState), @@ -54,10 +52,9 @@ func fromModel(obj *model) *deposit.Record { return &deposit.Record{ Id: uint64(obj.Id.Int64), - Signature: obj.Signature, - Destination: obj.Destination, - Amount: obj.Amount, - UsdMarketValue: obj.UsdMarketValue, + Signature: obj.Signature, + Destination: obj.Destination, + Amount: obj.Amount, Slot: obj.Slot, ConfirmationState: transaction.Confirmation(obj.ConfirmationState), @@ -68,15 +65,15 @@ func fromModel(obj *model) *deposit.Record { func (m *model) dbSave(ctx context.Context, db *sqlx.DB) error { query := `INSERT INTO ` + tableName + ` - (signature, destination, amount, usd_market_value, slot, confirmation_state, created_at) - VALUES ($1, $2, $3, $4, $5, $6, $7) + (signature, destination, amount, slot, confirmation_state, created_at) + VALUES ($1, $2, $3, $4, $5, $6) ON CONFLICT(signature, destination) DO UPDATE - SET slot = $5, confirmation_state = $6 + SET slot = $4, confirmation_state = $5 WHERE ` + tableName + `.signature = $1 AND ` + tableName + `.destination = $2 - RETURNING id, signature, destination, amount, usd_market_value, slot, confirmation_state, created_at` + RETURNING id, signature, destination, amount, slot, confirmation_state, created_at` if m.CreatedAt.IsZero() { m.CreatedAt = time.Now() @@ -88,7 +85,6 @@ func (m *model) dbSave(ctx context.Context, db *sqlx.DB) error { m.Signature, m.Destination, m.Amount, - m.UsdMarketValue, m.Slot, m.ConfirmationState, m.CreatedAt, @@ -98,7 +94,7 @@ func (m *model) dbSave(ctx context.Context, db *sqlx.DB) error { func dbGet(ctx context.Context, db *sqlx.DB, signature, account string) (*model, error) { var res model - query := `SELECT id, signature, destination, amount, usd_market_value, slot, confirmation_state, created_at FROM ` + tableName + ` + query := `SELECT id, signature, destination, amount, slot, confirmation_state, created_at FROM ` + tableName + ` WHERE signature = $1 AND destination = $2 ` @@ -167,23 +163,3 @@ func dbGetQuarkAmountBatch(ctx context.Context, db *sqlx.DB, accounts ...string) } return res, nil } - -func dbGetUsdAmount(ctx context.Context, db *sqlx.DB, account string) (float64, error) { - var res sql.NullFloat64 - - query := `SELECT SUM(usd_market_value) FROM ` + tableName + ` - WHERE destination = $1 AND confirmation_state = $2 - ` - - err := pgutil.ExecuteInTx(ctx, db, sql.LevelDefault, func(tx *sqlx.Tx) error { - return db.GetContext(ctx, &res, query, account, transaction.ConfirmationFinalized) - }) - if err != nil { - return 0, err - } - - if !res.Valid { - return 0, nil - } - return res.Float64, nil -} diff --git a/ocp/data/deposit/postgres/store.go b/ocp/data/deposit/postgres/store.go index 320b330..b0a9b8d 100644 --- a/ocp/data/deposit/postgres/store.go +++ b/ocp/data/deposit/postgres/store.go @@ -56,8 +56,3 @@ func (s *store) GetQuarkAmount(ctx context.Context, account string) (uint64, err func (s *store) GetQuarkAmountBatch(ctx context.Context, accounts ...string) (map[string]uint64, error) { return dbGetQuarkAmountBatch(ctx, s.db, accounts...) } - -// GetUsdAmount implements deposit.Store.GetUsdAmount -func (s *store) GetUsdAmount(ctx context.Context, account string) (float64, error) { - return dbGetUsdAmount(ctx, s.db, account) -} diff --git a/ocp/data/deposit/postgres/store_test.go b/ocp/data/deposit/postgres/store_test.go index b60f6f2..8212450 100644 --- a/ocp/data/deposit/postgres/store_test.go +++ b/ocp/data/deposit/postgres/store_test.go @@ -25,7 +25,6 @@ const ( signature TEXT NOT NULL, destination TEXT NOT NULL, amount BIGINT NOT NULL CHECK (amount > 0), - usd_market_value NUMERIC(18, 9) NOT NULL, slot BIGINT NOT NULL, confirmation_state INTEGER NOT NULL, diff --git a/ocp/data/deposit/store.go b/ocp/data/deposit/store.go index f72995e..37a8d75 100644 --- a/ocp/data/deposit/store.go +++ b/ocp/data/deposit/store.go @@ -16,10 +16,9 @@ var ( type Record struct { Id uint64 - Signature string - Destination string - Amount uint64 - UsdMarketValue float64 + Signature string + Destination string + Amount uint64 Slot uint64 ConfirmationState transaction.Confirmation @@ -40,10 +39,6 @@ type Store interface { // GetQuarkAmountBatch is like GetQuarkAmount but for a batch of accounts GetQuarkAmountBatch(ctx context.Context, accounts ...string) (map[string]uint64, error) - - // GetUsdAmount gets the total deposited USD amount to an account for finalized - // transactions - GetUsdAmount(ctx context.Context, account string) (float64, error) } func (r *Record) Validate() error { @@ -59,10 +54,6 @@ func (r *Record) Validate() error { return errors.New("amount is required") } - if r.UsdMarketValue <= 0 { - return errors.New("usd market value must be positive") - } - if r.ConfirmationState == transaction.ConfirmationUnknown { return errors.New("confirmation state is required") } @@ -78,10 +69,9 @@ func (r *Record) Clone() Record { return Record{ Id: r.Id, - Signature: r.Signature, - Destination: r.Destination, - Amount: r.Amount, - UsdMarketValue: r.UsdMarketValue, + Signature: r.Signature, + Destination: r.Destination, + Amount: r.Amount, Slot: r.Slot, ConfirmationState: r.ConfirmationState, @@ -96,7 +86,6 @@ func (r *Record) CopyTo(dst *Record) { dst.Signature = r.Signature dst.Destination = r.Destination dst.Amount = r.Amount - dst.UsdMarketValue = r.UsdMarketValue dst.Slot = r.Slot dst.ConfirmationState = r.ConfirmationState diff --git a/ocp/data/deposit/tests/tests.go b/ocp/data/deposit/tests/tests.go index f21baf8..3293bcf 100644 --- a/ocp/data/deposit/tests/tests.go +++ b/ocp/data/deposit/tests/tests.go @@ -31,7 +31,6 @@ func testRoundTrip(t *testing.T, s deposit.Store) { Signature: "txn", Destination: "destination", Amount: 1, - UsdMarketValue: 1.23, Slot: 0, ConfirmationState: transaction.ConfirmationConfirmed, } @@ -78,16 +77,12 @@ func testGetAmounts(t *testing.T, s deposit.Store) { assert.EqualValues(t, 0, quarksByAccount[destination1]) assert.EqualValues(t, 0, quarksByAccount[destination2]) - usd, err := s.GetUsdAmount(ctx, destination1) - require.NoError(t, err) - assert.EqualValues(t, 0, usd) - records := []*deposit.Record{ - {Signature: "txn1", Destination: destination1, Amount: 1, UsdMarketValue: 2, Slot: 12345, ConfirmationState: transaction.ConfirmationConfirmed}, - {Signature: "txn2", Destination: destination1, Amount: 10, UsdMarketValue: 20, Slot: 12345, ConfirmationState: transaction.ConfirmationFailed}, - {Signature: "txn3", Destination: destination1, Amount: 100, UsdMarketValue: 200, Slot: 12345, ConfirmationState: transaction.ConfirmationFinalized}, - {Signature: "txn4", Destination: destination1, Amount: 1000, UsdMarketValue: 2000, Slot: 12345, ConfirmationState: transaction.ConfirmationFinalized}, - {Signature: "txn1", Destination: destination2, Amount: 10000, UsdMarketValue: 20000, Slot: 12345, ConfirmationState: transaction.ConfirmationFinalized}, + {Signature: "txn1", Destination: destination1, Amount: 1, Slot: 12345, ConfirmationState: transaction.ConfirmationConfirmed}, + {Signature: "txn2", Destination: destination1, Amount: 10, Slot: 12345, ConfirmationState: transaction.ConfirmationFailed}, + {Signature: "txn3", Destination: destination1, Amount: 100, Slot: 12345, ConfirmationState: transaction.ConfirmationFinalized}, + {Signature: "txn4", Destination: destination1, Amount: 1000, Slot: 12345, ConfirmationState: transaction.ConfirmationFinalized}, + {Signature: "txn1", Destination: destination2, Amount: 10000, Slot: 12345, ConfirmationState: transaction.ConfirmationFinalized}, } for _, record := range records { require.NoError(t, s.Save(ctx, record)) @@ -109,14 +104,6 @@ func testGetAmounts(t *testing.T, s deposit.Store) { require.Len(t, quarksByAccount, 2) assert.EqualValues(t, 1100, quarksByAccount[destination1]) assert.EqualValues(t, 10000, quarksByAccount[destination2]) - - usd, err = s.GetUsdAmount(ctx, destination1) - require.NoError(t, err) - assert.EqualValues(t, 2200, usd) - - usd, err = s.GetUsdAmount(ctx, destination2) - require.NoError(t, err) - assert.EqualValues(t, 20000, usd) }) } @@ -124,7 +111,6 @@ func assertEquivalentRecords(t *testing.T, obj1, obj2 *deposit.Record) { assert.Equal(t, obj1.Signature, obj2.Signature) assert.Equal(t, obj1.Destination, obj2.Destination) assert.Equal(t, obj1.Amount, obj2.Amount) - assert.Equal(t, obj1.UsdMarketValue, obj2.UsdMarketValue) assert.Equal(t, obj1.Slot, obj2.Slot) assert.Equal(t, obj1.ConfirmationState, obj2.ConfirmationState) } diff --git a/ocp/data/intent/intent.go b/ocp/data/intent/intent.go index 16a7a55..c418394 100644 --- a/ocp/data/intent/intent.go +++ b/ocp/data/intent/intent.go @@ -58,7 +58,13 @@ type OpenAccountsMetadata struct { type ExternalDepositMetadata struct { DestinationTokenAccount string Quantity uint64 - UsdMarketValue float64 + + ExchangeCurrency currency.Code + ExchangeRate float64 + NativeAmount float64 + UsdMarketValue float64 + + IsSwapBuy bool } type SendPublicPaymentMetadata struct { @@ -73,6 +79,7 @@ type SendPublicPaymentMetadata struct { IsWithdrawal bool IsRemoteSend bool + IsSwapSell bool } type ReceivePaymentsPubliclyMetadata struct { @@ -275,14 +282,26 @@ func (m *ExternalDepositMetadata) Clone() ExternalDepositMetadata { return ExternalDepositMetadata{ DestinationTokenAccount: m.DestinationTokenAccount, Quantity: m.Quantity, - UsdMarketValue: m.UsdMarketValue, + + ExchangeCurrency: m.ExchangeCurrency, + ExchangeRate: m.ExchangeRate, + NativeAmount: m.NativeAmount, + UsdMarketValue: m.UsdMarketValue, + + IsSwapBuy: m.IsSwapBuy, } } func (m *ExternalDepositMetadata) CopyTo(dst *ExternalDepositMetadata) { dst.DestinationTokenAccount = m.DestinationTokenAccount dst.Quantity = m.Quantity + + dst.ExchangeCurrency = m.ExchangeCurrency + dst.ExchangeRate = m.ExchangeRate + dst.NativeAmount = m.NativeAmount dst.UsdMarketValue = m.UsdMarketValue + + dst.IsSwapBuy = m.IsSwapBuy } func (m *ExternalDepositMetadata) Validate() error { @@ -294,8 +313,20 @@ func (m *ExternalDepositMetadata) Validate() error { return errors.New("quantity is required") } - if m.UsdMarketValue <= 0 { - return errors.New("usd market value is required") + if len(m.ExchangeCurrency) == 0 { + return errors.New("exchange currency is required") + } + + if m.ExchangeRate == 0 { + return errors.New("exchange rate cannot be zero") + } + + if m.NativeAmount == 0 { + return errors.New("native amount cannot be zero") + } + + if m.UsdMarketValue == 0 { + return errors.New("usd market value cannot be zero") } return nil @@ -311,8 +342,10 @@ func (m *SendPublicPaymentMetadata) Clone() SendPublicPaymentMetadata { ExchangeRate: m.ExchangeRate, NativeAmount: m.NativeAmount, UsdMarketValue: m.UsdMarketValue, - IsWithdrawal: m.IsWithdrawal, - IsRemoteSend: m.IsRemoteSend, + + IsWithdrawal: m.IsWithdrawal, + IsRemoteSend: m.IsRemoteSend, + IsSwapSell: m.IsSwapSell, } } @@ -328,6 +361,7 @@ func (m *SendPublicPaymentMetadata) CopyTo(dst *SendPublicPaymentMetadata) { dst.IsWithdrawal = m.IsWithdrawal dst.IsRemoteSend = m.IsRemoteSend + dst.IsSwapSell = m.IsSwapSell } func (m *SendPublicPaymentMetadata) Validate() error { diff --git a/ocp/data/intent/postgres/model.go b/ocp/data/intent/postgres/model.go index bd95ec2..43b0f38 100644 --- a/ocp/data/intent/postgres/model.go +++ b/ocp/data/intent/postgres/model.go @@ -11,9 +11,9 @@ import ( "github.com/jmoiron/sqlx" + "github.com/code-payments/ocp-server/currency" "github.com/code-payments/ocp-server/ocp/config" "github.com/code-payments/ocp-server/ocp/data/intent" - "github.com/code-payments/ocp-server/currency" pgutil "github.com/code-payments/ocp-server/database/postgres" q "github.com/code-payments/ocp-server/database/query" @@ -39,10 +39,10 @@ type intentModel struct { NativeAmount float64 `db:"native_amount"` UsdMarketValue float64 `db:"usd_market_value"` IsWithdrawal bool `db:"is_withdraw"` - IsDeposit bool `db:"is_deposit"` IsRemoteSend bool `db:"is_remote_send"` IsReturned bool `db:"is_returned"` IsIssuerVoidingGiftCard bool `db:"is_issuer_voiding_gift_card"` + IsSwap bool `db:"is_swap"` State uint `db:"state"` Version int64 `db:"version"` CreatedAt time.Time `db:"created_at"` @@ -82,7 +82,13 @@ func toIntentModel(obj *intent.Record) (*intentModel, error) { case intent.ExternalDeposit: m.DestinationTokenAccount = obj.ExternalDepositMetadata.DestinationTokenAccount m.Quantity = obj.ExternalDepositMetadata.Quantity + + m.ExchangeCurrency = string(obj.ExternalDepositMetadata.ExchangeCurrency) + m.ExchangeRate = obj.ExternalDepositMetadata.ExchangeRate + m.NativeAmount = obj.ExternalDepositMetadata.NativeAmount m.UsdMarketValue = obj.ExternalDepositMetadata.UsdMarketValue + + m.IsSwap = obj.ExternalDepositMetadata.IsSwapBuy case intent.SendPublicPayment: m.DestinationOwnerAccount = obj.SendPublicPaymentMetadata.DestinationOwnerAccount m.DestinationTokenAccount = obj.SendPublicPaymentMetadata.DestinationTokenAccount @@ -95,6 +101,7 @@ func toIntentModel(obj *intent.Record) (*intentModel, error) { m.IsWithdrawal = obj.SendPublicPaymentMetadata.IsWithdrawal m.IsRemoteSend = obj.SendPublicPaymentMetadata.IsRemoteSend + m.IsSwap = obj.SendPublicPaymentMetadata.IsSwapSell case intent.ReceivePaymentsPublicly: m.Source = obj.ReceivePaymentsPubliclyMetadata.Source m.Quantity = obj.ReceivePaymentsPubliclyMetadata.Quantity @@ -148,7 +155,18 @@ func fromIntentModel(obj *intentModel) *intent.Record { record.ExternalDepositMetadata = &intent.ExternalDepositMetadata{ DestinationTokenAccount: obj.DestinationTokenAccount, Quantity: obj.Quantity, - UsdMarketValue: obj.UsdMarketValue, + + ExchangeCurrency: currency.Code(obj.ExchangeCurrency), + ExchangeRate: obj.ExchangeRate, + NativeAmount: obj.NativeAmount, + UsdMarketValue: obj.UsdMarketValue, + + IsSwapBuy: obj.IsSwap, + } + + if len(record.ExternalDepositMetadata.ExchangeCurrency) == 0 { + record.ExternalDepositMetadata.ExchangeCurrency = currency.USD + record.ExternalDepositMetadata.NativeAmount = record.ExternalDepositMetadata.UsdMarketValue } case intent.SendPublicPayment: record.SendPublicPaymentMetadata = &intent.SendPublicPaymentMetadata{ @@ -163,6 +181,7 @@ func fromIntentModel(obj *intentModel) *intent.Record { IsWithdrawal: obj.IsWithdrawal, IsRemoteSend: obj.IsRemoteSend, + IsSwapSell: obj.IsSwap, } case intent.ReceivePaymentsPublicly: record.ReceivePaymentsPubliclyMetadata = &intent.ReceivePaymentsPubliclyMetadata{ @@ -199,7 +218,7 @@ func (m *intentModel) dbSave(ctx context.Context, db *sqlx.DB) error { return pgutil.ExecuteInTx(ctx, db, sql.LevelDefault, func(tx *sqlx.Tx) error { query := `INSERT INTO ` + intentTableName + ` - (intent_id, intent_type, mint, owner, source, destination_owner, destination, quantity, exchange_currency, exchange_rate, native_amount, usd_market_value, is_withdraw, is_deposit, is_remote_send, is_returned, is_issuer_voiding_gift_card, state, version, created_at) + (intent_id, intent_type, mint, owner, source, destination_owner, destination, quantity, exchange_currency, exchange_rate, native_amount, usd_market_value, is_withdraw, is_remote_send, is_returned, is_issuer_voiding_gift_card, is_swap, state, version, created_at) VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13, $14, $15, $16, $17, $18, $19 + 1, $20) ON CONFLICT (intent_id) @@ -208,7 +227,7 @@ func (m *intentModel) dbSave(ctx context.Context, db *sqlx.DB) error { WHERE ` + intentTableName + `.intent_id = $1 AND ` + intentTableName + `.version = $19 RETURNING - id, intent_id, intent_type, mint, owner, source, destination_owner, destination, quantity, exchange_currency, exchange_rate, native_amount, usd_market_value, is_withdraw, is_deposit, is_remote_send, is_returned, is_issuer_voiding_gift_card, state, version, created_at` + id, intent_id, intent_type, mint, owner, source, destination_owner, destination, quantity, exchange_currency, exchange_rate, native_amount, usd_market_value, is_withdraw, is_remote_send, is_returned, is_issuer_voiding_gift_card, is_swap, state, version, created_at` err := tx.QueryRowxContext( ctx, @@ -226,10 +245,10 @@ func (m *intentModel) dbSave(ctx context.Context, db *sqlx.DB) error { m.NativeAmount, m.UsdMarketValue, m.IsWithdrawal, - m.IsDeposit, m.IsRemoteSend, m.IsReturned, m.IsIssuerVoidingGiftCard, + m.IsSwap, m.State, m.Version, m.CreatedAt, @@ -355,7 +374,7 @@ func dbGetAccounts(ctx context.Context, db *sqlx.DB, intentType intent.Type, pag func dbGetIntentByIntentID(ctx context.Context, db *sqlx.DB, intentID string) (*intentModel, error) { res := &intentModel{} - query := `SELECT id, intent_id, intent_type, mint, owner, source, destination_owner, destination, quantity, exchange_currency, exchange_rate, native_amount, usd_market_value, is_withdraw, is_deposit, is_remote_send, is_returned, is_issuer_voiding_gift_card, state, version, created_at + query := `SELECT id, intent_id, intent_type, mint, owner, source, destination_owner, destination, quantity, exchange_currency, exchange_rate, native_amount, usd_market_value, is_withdraw, is_remote_send, is_returned, is_issuer_voiding_gift_card, is_swap, state, version, created_at FROM ` + intentTableName + ` WHERE intent_id = $1 LIMIT 1` @@ -375,7 +394,7 @@ func dbGetIntentByIntentID(ctx context.Context, db *sqlx.DB, intentID string) (* func dbGetIntentByID(ctx context.Context, db *sqlx.DB, id int64) (*intentModel, error) { res := &intentModel{} - query := `SELECT id, intent_id, intent_type, mint, owner, source, destination_owner, destination, quantity, exchange_currency, exchange_rate, native_amount, usd_market_value, is_withdraw, is_deposit, is_remote_send, is_returned, is_issuer_voiding_gift_card, state, version, created_at + query := `SELECT id, intent_id, intent_type, mint, owner, source, destination_owner, destination, quantity, exchange_currency, exchange_rate, native_amount, usd_market_value, is_withdraw, is_remote_send, is_returned, is_issuer_voiding_gift_card, is_swap, state, version, created_at FROM ` + intentTableName + ` WHERE id = $1 LIMIT 1` @@ -398,7 +417,7 @@ func dbGetAllByOwner(ctx context.Context, db *sqlx.DB, owner string, cursor q.Cu models := []*intentModel{} opts := []any{owner} - query1 := `SELECT id, intent_id, intent_type, mint, owner, source, destination_owner, destination, quantity, exchange_currency, exchange_rate, native_amount, usd_market_value, is_withdraw, is_deposit, is_remote_send, is_returned, is_issuer_voiding_gift_card, state, version, created_at + query1 := `SELECT id, intent_id, intent_type, mint, owner, source, destination_owner, destination, quantity, exchange_currency, exchange_rate, native_amount, usd_market_value, is_withdraw, is_remote_send, is_returned, is_issuer_voiding_gift_card, is_swap, state, version, created_at FROM ` + intentTableName + ` WHERE (owner = $1 OR destination_owner = $1) ` @@ -467,7 +486,7 @@ func dbGetAllByOwner(ctx context.Context, db *sqlx.DB, owner string, cursor q.Cu func dbGetOriginalGiftCardIssuedIntent(ctx context.Context, db *sqlx.DB, giftCardVault string) (*intentModel, error) { res := []*intentModel{} - query := `SELECT id, intent_id, intent_type, mint, owner, source, destination_owner, destination, quantity, exchange_currency, exchange_rate, native_amount, usd_market_value, is_withdraw, is_deposit, is_remote_send, is_returned, is_issuer_voiding_gift_card, state, version, created_at + query := `SELECT id, intent_id, intent_type, mint, owner, source, destination_owner, destination, quantity, exchange_currency, exchange_rate, native_amount, usd_market_value, is_withdraw, is_remote_send, is_returned, is_issuer_voiding_gift_card, is_swap, state, version, created_at FROM ` + intentTableName + ` WHERE destination = $1 and intent_type = $2 AND state != $3 AND is_remote_send IS TRUE LIMIT 2 @@ -499,7 +518,7 @@ func dbGetOriginalGiftCardIssuedIntent(ctx context.Context, db *sqlx.DB, giftCar func dbGetGiftCardClaimedIntent(ctx context.Context, db *sqlx.DB, giftCardVault string) (*intentModel, error) { res := []*intentModel{} - query := `SELECT id, intent_id, intent_type, mint, owner, source, destination_owner, destination, quantity, exchange_currency, exchange_rate, native_amount, usd_market_value, is_withdraw, is_deposit, is_remote_send, is_returned, is_issuer_voiding_gift_card, state, version, created_at + query := `SELECT id, intent_id, intent_type, mint, owner, source, destination_owner, destination, quantity, exchange_currency, exchange_rate, native_amount, usd_market_value, is_withdraw, is_remote_send, is_returned, is_issuer_voiding_gift_card, is_swap, state, version, created_at FROM ` + intentTableName + ` WHERE source = $1 and intent_type = $2 AND state != $3 AND is_remote_send IS TRUE LIMIT 2 diff --git a/ocp/data/intent/postgres/store_test.go b/ocp/data/intent/postgres/store_test.go index 3189378..4f88756 100644 --- a/ocp/data/intent/postgres/store_test.go +++ b/ocp/data/intent/postgres/store_test.go @@ -40,10 +40,10 @@ const ( usd_market_value NUMERIC(18, 9) NULL, is_withdraw BOOL NOT NULL, - is_deposit BOOL NOT NULL, is_remote_send BOOL NOT NULL, is_returned BOOL NOT NULL, is_issuer_voiding_gift_card BOOL NOT NULL, + is_swap BOOL NOT NULL, state INTEGER NOT NULL, diff --git a/ocp/data/intent/tests/tests.go b/ocp/data/intent/tests/tests.go index b4a6e67..d0c6701 100644 --- a/ocp/data/intent/tests/tests.go +++ b/ocp/data/intent/tests/tests.go @@ -8,9 +8,9 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" - "github.com/code-payments/ocp-server/ocp/data/intent" "github.com/code-payments/ocp-server/currency" "github.com/code-payments/ocp-server/database/query" + "github.com/code-payments/ocp-server/ocp/data/intent" ) func RunTests(t *testing.T, s intent.Store, teardown func()) { @@ -87,7 +87,13 @@ func testExternalDepositRoundTrip(t *testing.T, s intent.Store) { ExternalDepositMetadata: &intent.ExternalDepositMetadata{ DestinationTokenAccount: "test_destination_token", Quantity: 12345, - UsdMarketValue: 1.2345, + + ExchangeCurrency: currency.CAD, + ExchangeRate: 0.00073, + NativeAmount: 0.00073 * 12345, + UsdMarketValue: 0.00042, + + IsSwapBuy: true, }, State: intent.StateUnknown, CreatedAt: time.Now(), @@ -107,7 +113,11 @@ func testExternalDepositRoundTrip(t *testing.T, s intent.Store) { require.NotNil(t, actual.ExternalDepositMetadata) assert.Equal(t, cloned.ExternalDepositMetadata.DestinationTokenAccount, actual.ExternalDepositMetadata.DestinationTokenAccount) assert.Equal(t, cloned.ExternalDepositMetadata.Quantity, actual.ExternalDepositMetadata.Quantity) + assert.Equal(t, cloned.ExternalDepositMetadata.ExchangeCurrency, actual.ExternalDepositMetadata.ExchangeCurrency) + assert.Equal(t, cloned.ExternalDepositMetadata.ExchangeRate, actual.ExternalDepositMetadata.ExchangeRate) + assert.Equal(t, cloned.ExternalDepositMetadata.NativeAmount, actual.ExternalDepositMetadata.NativeAmount) assert.Equal(t, cloned.ExternalDepositMetadata.UsdMarketValue, actual.ExternalDepositMetadata.UsdMarketValue) + assert.Equal(t, cloned.ExternalDepositMetadata.IsSwapBuy, actual.ExternalDepositMetadata.IsSwapBuy) assert.Equal(t, cloned.State, actual.State) assert.Equal(t, cloned.CreatedAt.Unix(), actual.CreatedAt.Unix()) assert.EqualValues(t, 1, actual.Id) @@ -141,6 +151,7 @@ func testSendPublicPaymentRoundTrip(t *testing.T, s intent.Store) { IsWithdrawal: true, IsRemoteSend: true, + IsSwapSell: true, }, State: intent.StateUnknown, CreatedAt: time.Now(), @@ -167,6 +178,7 @@ func testSendPublicPaymentRoundTrip(t *testing.T, s intent.Store) { assert.Equal(t, cloned.SendPublicPaymentMetadata.UsdMarketValue, actual.SendPublicPaymentMetadata.UsdMarketValue) assert.Equal(t, cloned.SendPublicPaymentMetadata.IsWithdrawal, actual.SendPublicPaymentMetadata.IsWithdrawal) assert.Equal(t, cloned.SendPublicPaymentMetadata.IsRemoteSend, actual.SendPublicPaymentMetadata.IsRemoteSend) + assert.Equal(t, cloned.SendPublicPaymentMetadata.IsSwapSell, actual.SendPublicPaymentMetadata.IsSwapSell) assert.Equal(t, cloned.State, actual.State) assert.Equal(t, cloned.CreatedAt.Unix(), actual.CreatedAt.Unix()) assert.EqualValues(t, 1, actual.Id) @@ -395,7 +407,7 @@ func testGetOriginalGiftCardIssuedIntent(t *testing.T, s intent.Store) { {IntentId: "i2", IntentType: intent.SendPublicPayment, SendPublicPaymentMetadata: &intent.SendPublicPaymentMetadata{IsRemoteSend: true, DestinationTokenAccount: "a2", DestinationOwnerAccount: "o2", Quantity: 1, ExchangeCurrency: currency.USD, ExchangeRate: 1, NativeAmount: 1, UsdMarketValue: 1}, MintAccount: "mint", InitiatorOwnerAccount: "user", State: intent.StateConfirmed}, {IntentId: "i3", IntentType: intent.SendPublicPayment, SendPublicPaymentMetadata: &intent.SendPublicPaymentMetadata{IsRemoteSend: false, DestinationTokenAccount: "a2", DestinationOwnerAccount: "o2", Quantity: 1, ExchangeCurrency: currency.USD, ExchangeRate: 1, NativeAmount: 1, UsdMarketValue: 1}, MintAccount: "mint", InitiatorOwnerAccount: "user", State: intent.StateConfirmed}, - {IntentId: "i4", IntentType: intent.ExternalDeposit, ExternalDepositMetadata: &intent.ExternalDepositMetadata{DestinationTokenAccount: "a2", Quantity: 1, UsdMarketValue: 1}, MintAccount: "mint", InitiatorOwnerAccount: "user", State: intent.StateConfirmed}, + {IntentId: "i4", IntentType: intent.ExternalDeposit, ExternalDepositMetadata: &intent.ExternalDepositMetadata{DestinationTokenAccount: "a2", ExchangeCurrency: currency.USD, ExchangeRate: 1, NativeAmount: 1, Quantity: 1, UsdMarketValue: 1}, MintAccount: "mint", InitiatorOwnerAccount: "user", State: intent.StateConfirmed}, {IntentId: "i5", IntentType: intent.SendPublicPayment, SendPublicPaymentMetadata: &intent.SendPublicPaymentMetadata{IsRemoteSend: true, DestinationTokenAccount: "a3", DestinationOwnerAccount: "o3", Quantity: 1, ExchangeCurrency: currency.USD, ExchangeRate: 1, NativeAmount: 1, UsdMarketValue: 1}, MintAccount: "mint", InitiatorOwnerAccount: "user", State: intent.StateConfirmed}, {IntentId: "i6", IntentType: intent.SendPublicPayment, SendPublicPaymentMetadata: &intent.SendPublicPaymentMetadata{IsRemoteSend: true, DestinationTokenAccount: "a3", DestinationOwnerAccount: "o3", Quantity: 1, ExchangeCurrency: currency.USD, ExchangeRate: 1, NativeAmount: 1, UsdMarketValue: 1}, MintAccount: "mint", InitiatorOwnerAccount: "user", State: intent.StateConfirmed}, @@ -484,7 +496,7 @@ func testGetTransactedAmountForAntiMoneyLaundering(t *testing.T, s intent.Store) {IntentId: "t4", IntentType: intent.SendPublicPayment, InitiatorOwnerAccount: "o1", SendPublicPaymentMetadata: &intent.SendPublicPaymentMetadata{DestinationOwnerAccount: "o4", DestinationTokenAccount: "a4", Quantity: 1000, ExchangeCurrency: currency.USD, ExchangeRate: 2, NativeAmount: 2000, UsdMarketValue: 2000}, State: intent.StateFailed, MintAccount: "mint", CreatedAt: time.Now().Add(-4 * time.Minute)}, {IntentId: "t5", IntentType: intent.SendPublicPayment, InitiatorOwnerAccount: "o1", SendPublicPaymentMetadata: &intent.SendPublicPaymentMetadata{DestinationOwnerAccount: "o5", DestinationTokenAccount: "a5", Quantity: 10000, ExchangeCurrency: currency.USD, ExchangeRate: 2, NativeAmount: 20000, UsdMarketValue: 20000}, State: intent.StateRevoked, MintAccount: "mint", CreatedAt: time.Now().Add(-5 * time.Minute)}, {IntentId: "t6", IntentType: intent.ReceivePaymentsPublicly, InitiatorOwnerAccount: "o1", ReceivePaymentsPubliclyMetadata: &intent.ReceivePaymentsPubliclyMetadata{Source: "a6", Quantity: 100000, UsdMarketValue: 200000, OriginalExchangeCurrency: currency.USD, OriginalExchangeRate: 2, OriginalNativeAmount: 200000}, State: intent.StateConfirmed, MintAccount: "mint", CreatedAt: time.Now()}, - {IntentId: "t7", IntentType: intent.ExternalDeposit, InitiatorOwnerAccount: "o1", ExternalDepositMetadata: &intent.ExternalDepositMetadata{DestinationTokenAccount: "a7", Quantity: 1000000, UsdMarketValue: 20000}, MintAccount: "mint", State: intent.StateConfirmed, CreatedAt: time.Now()}, + {IntentId: "t7", IntentType: intent.ExternalDeposit, InitiatorOwnerAccount: "o1", ExternalDepositMetadata: &intent.ExternalDepositMetadata{DestinationTokenAccount: "a7", ExchangeCurrency: currency.USD, ExchangeRate: 1000000, NativeAmount: 1000000, Quantity: 1000000, UsdMarketValue: 20000}, MintAccount: "mint", State: intent.StateConfirmed, CreatedAt: time.Now()}, {IntentId: "t8", IntentType: intent.SendPublicPayment, InitiatorOwnerAccount: "o1", SendPublicPaymentMetadata: &intent.SendPublicPaymentMetadata{DestinationOwnerAccount: "o8", DestinationTokenAccount: "a8", Quantity: 10000000, ExchangeCurrency: currency.USD, ExchangeRate: 2, NativeAmount: 2000000, UsdMarketValue: 2000000, IsWithdrawal: true}, State: intent.StateConfirmed, MintAccount: "mint", CreatedAt: time.Now()}, } diff --git a/ocp/data/internal.go b/ocp/data/internal.go index 23f88e8..bedd118 100644 --- a/ocp/data/internal.go +++ b/ocp/data/internal.go @@ -144,7 +144,6 @@ type DatabaseData interface { GetExternalDeposit(ctx context.Context, signature, destination string) (*deposit.Record, error) GetTotalExternalDepositedAmountInQuarks(ctx context.Context, account string) (uint64, error) GetTotalExternalDepositedAmountInQuarksBatch(ctx context.Context, accounts ...string) (map[string]uint64, error) - GetTotalExternalDepositedAmountInUsd(ctx context.Context, account string) (float64, error) // Fulfillments // -------------------------------------------------------------------------------- @@ -550,9 +549,6 @@ func (dp *DatabaseProvider) GetTotalExternalDepositedAmountInQuarks(ctx context. func (dp *DatabaseProvider) GetTotalExternalDepositedAmountInQuarksBatch(ctx context.Context, accounts ...string) (map[string]uint64, error) { return dp.deposits.GetQuarkAmountBatch(ctx, accounts...) } -func (dp *DatabaseProvider) GetTotalExternalDepositedAmountInUsd(ctx context.Context, account string) (float64, error) { - return dp.deposits.GetUsdAmount(ctx, account) -} // Fulfillments // -------------------------------------------------------------------------------- diff --git a/ocp/rpc/account/server_test.go b/ocp/rpc/account/server_test.go index 065d037..fc5b4bf 100644 --- a/ocp/rpc/account/server_test.go +++ b/ocp/rpc/account/server_test.go @@ -774,10 +774,9 @@ func getDefaultTestAccountRecords(t *testing.T, ownerAccount, authorityAccount * func setupCachedBalance(t *testing.T, env testEnv, accountRecords *common.AccountRecords, balance uint64) { depositRecord := &deposit.Record{ - Signature: fmt.Sprintf("txn%d", rand.Uint64()), - Destination: accountRecords.General.TokenAccount, - Amount: balance, - UsdMarketValue: 1, + Signature: fmt.Sprintf("txn%d", rand.Uint64()), + Destination: accountRecords.General.TokenAccount, + Amount: balance, ConfirmationState: transaction.ConfirmationFinalized, Slot: 12345, diff --git a/ocp/rpc/transaction/intent_handler.go b/ocp/rpc/transaction/intent_handler.go index 80e7a67..7fab4f2 100644 --- a/ocp/rpc/transaction/intent_handler.go +++ b/ocp/rpc/transaction/intent_handler.go @@ -462,6 +462,13 @@ func (h *SendPublicPaymentIntentHandler) PopulateMetadata(ctx context.Context, i return err } intentRecord.SendPublicPaymentMetadata.DestinationOwnerAccount = destinationOwner.PublicKey().ToBase58() + + _, err = h.data.GetTimelockBySwapPda(ctx, destinationOwner.PublicKey().ToBase58()) + if err == nil { + intentRecord.SendPublicPaymentMetadata.IsSwapSell = true + } else if err != timelock.ErrTimelockNotFound { + return err + } } return nil diff --git a/ocp/worker/geyser/external_deposit.go b/ocp/worker/geyser/external_deposit.go index 8a4060c..25a61ed 100644 --- a/ocp/worker/geyser/external_deposit.go +++ b/ocp/worker/geyser/external_deposit.go @@ -14,6 +14,7 @@ import ( commonpb "github.com/code-payments/ocp-protobuf-api/generated/go/common/v1" "github.com/code-payments/ocp-server/cache" + currency_lib "github.com/code-payments/ocp-server/currency" "github.com/code-payments/ocp-server/database/query" "github.com/code-payments/ocp-server/ocp/common" currency_util "github.com/code-payments/ocp-server/ocp/currency" @@ -331,6 +332,9 @@ func processPotentialExternalDepositIntoVm(ctx context.Context, data ocp_data.Pr ExternalDepositMetadata: &intent.ExternalDepositMetadata{ DestinationTokenAccount: userVirtualTimelockVaultAccount.PublicKey().ToBase58(), Quantity: uint64(deltaQuarksIntoOmnibus), + ExchangeCurrency: currency_lib.USD, + NativeAmount: usdMarketValue, + ExchangeRate: currency_util.CalculateExchangeRate(mint, uint64(deltaQuarksIntoOmnibus), usdMarketValue), UsdMarketValue: usdMarketValue, }, @@ -344,10 +348,9 @@ func processPotentialExternalDepositIntoVm(ctx context.Context, data ocp_data.Pr // For tracking in cached balances externalDepositRecord := &deposit.Record{ - Signature: signature, - Destination: userVirtualTimelockVaultAccount.PublicKey().ToBase58(), - Amount: uint64(deltaQuarksIntoOmnibus), - UsdMarketValue: usdMarketValue, + Signature: signature, + Destination: userVirtualTimelockVaultAccount.PublicKey().ToBase58(), + Amount: uint64(deltaQuarksIntoOmnibus), Slot: tokenBalances.Slot, ConfirmationState: transaction.ConfirmationFinalized, diff --git a/ocp/worker/swap/util.go b/ocp/worker/swap/util.go index 8d1faf9..1bb0789 100644 --- a/ocp/worker/swap/util.go +++ b/ocp/worker/swap/util.go @@ -171,6 +171,9 @@ func (p *runtime) updateBalancesForFinalizedSwap(ctx context.Context, swapRecord return 0, errors.New("delta quarks into destination vm omnibus is not positive") } + var exchangeCurrency currency_lib.Code + var exchangeRate float64 + var nativeAmountWithoutFees float64 var usdMarketValueWithoutFees float64 switch swapRecord.FundingSource { case swap.FundingSourceSubmitIntent: @@ -183,6 +186,9 @@ func (p *runtime) updateBalancesForFinalizedSwap(ctx context.Context, swapRecord return 0, errors.New("unexpected intent type") } + exchangeCurrency = fundingIntentRecord.SendPublicPaymentMetadata.ExchangeCurrency + exchangeRate = fundingIntentRecord.SendPublicPaymentMetadata.ExchangeRate + nativeAmountWithoutFees = fundingIntentRecord.SendPublicPaymentMetadata.NativeAmount usdMarketValueWithoutFees = fundingIntentRecord.SendPublicPaymentMetadata.UsdMarketValue case swap.FundingSourceExternalWallet: if !common.IsCoreMint(fromMint) { @@ -193,19 +199,27 @@ func (p *runtime) updateBalancesForFinalizedSwap(ctx context.Context, swapRecord return 0, errors.New("core mint is not a usd stable coin") } + exchangeCurrency = currency_lib.USD usdMarketValueWithoutFees, err = currency_util.CalculateUsdMarketValueFromTokenAmount(ctx, p.data, common.CoreMintAccount, swapRecord.Amount, time.Now()) if err != nil { return 0, err } + nativeAmountWithoutFees = usdMarketValueWithoutFees + exchangeRate = currency_util.CalculateExchangeRate(common.CoreMintAccount, swapRecord.Amount, usdMarketValueWithoutFees) default: return 0, errors.New("unsupported funding source") } + nativeAmount := nativeAmountWithoutFees usdMarketValue := usdMarketValueWithoutFees if !common.IsCoreMint(fromMint) { + nativeAmount, _ = new(big.Float).Mul( + big.NewFloat(0.99).SetPrec(128), + big.NewFloat(nativeAmountWithoutFees).SetPrec(128), + ).Float64() usdMarketValue, _ = new(big.Float).Mul( big.NewFloat(0.99).SetPrec(128), - big.NewFloat(usdMarketValue).SetPrec(128), + big.NewFloat(usdMarketValueWithoutFees).SetPrec(128), ).Float64() } @@ -222,7 +236,11 @@ func (p *runtime) updateBalancesForFinalizedSwap(ctx context.Context, swapRecord ExternalDepositMetadata: &intent.ExternalDepositMetadata{ DestinationTokenAccount: ownerDestinationTimelockVault.PublicKey().ToBase58(), Quantity: uint64(deltaQuarksIntoOmnibus), + ExchangeCurrency: exchangeCurrency, + ExchangeRate: exchangeRate, + NativeAmount: nativeAmount, UsdMarketValue: usdMarketValue, + IsSwapBuy: true, }, State: intent.StateConfirmed, @@ -235,10 +253,9 @@ func (p *runtime) updateBalancesForFinalizedSwap(ctx context.Context, swapRecord // For tracking in cached balances externalDepositRecord := &deposit.Record{ - Signature: swapRecord.TransactionSignature, - Destination: ownerDestinationTimelockVault.PublicKey().ToBase58(), - Amount: uint64(deltaQuarksIntoOmnibus), - UsdMarketValue: usdMarketValue, + Signature: swapRecord.TransactionSignature, + Destination: ownerDestinationTimelockVault.PublicKey().ToBase58(), + Amount: uint64(deltaQuarksIntoOmnibus), Slot: tokenBalances.Slot, ConfirmationState: transaction.ConfirmationFinalized,