From eb6c1ca53679077659074557a4a6871f0aefac67 Mon Sep 17 00:00:00 2001 From: jeffyanta Date: Tue, 7 Jul 2026 08:45:57 -0400 Subject: [PATCH] Support cross-currency swaps --- ocp/rpc/transaction/stateful_swap.go | 78 +++++++++++++++------------- ocp/worker/swap/metrics.go | 23 ++------ ocp/worker/swap/util.go | 42 +++++++++++---- 3 files changed, 80 insertions(+), 63 deletions(-) diff --git a/ocp/rpc/transaction/stateful_swap.go b/ocp/rpc/transaction/stateful_swap.go index 6f107f2..134e72c 100644 --- a/ocp/rpc/transaction/stateful_swap.go +++ b/ocp/rpc/transaction/stateful_swap.go @@ -168,10 +168,6 @@ func (s *transactionServer) handleReserveStatefulSwap( return handleStatefulSwapError(streamer, err) } - if !common.IsCoreMint(fromMint) && !common.IsCoreMint(toMint) { - return handleStatefulSwapError(streamer, NewSwapDeniedError("swap must involve core mint")) - } - if bytes.Equal(fromMint.PublicKey().ToBytes(), toMint.PublicKey().ToBytes()) { return handleStatefulSwapError(streamer, NewSwapValidationError("must swap between two different mints")) } @@ -284,29 +280,41 @@ func (s *transactionServer) handleReserveStatefulSwap( return handleStatefulSwapError(streamer, NewSwapDeniedErrorf("funding source %s is not supported", initiateReserveSwapReq.FundingSource)) } - otherMint := fromMint - if common.IsCoreMint(otherMint) { - otherMint = toMint - } + isSell := !common.IsCoreMint(fromMint) + isBuy := !common.IsCoreMint(toMint) + var destinationCurrencyMetadataRecord *currency.MetadataRecord var initializesMint bool - currencyMetadataRecord, err := s.data.GetCurrencyMetadata(ctx, otherMint.PublicKey().ToBase58()) - if err == currency.ErrNotFound { - return handleStatefulSwapError(streamer, NewSwapValidationError("mint not found")) - } else if err != nil { - log.With(zap.Error(err)).Warn("failure getting destination timelock record") - return handleStatefulSwapError(streamer, err) - } - switch currencyMetadataRecord.State { - case currency.MetadataStateAvailable: - initializesMint = false - case currency.MetadataStateWaitingForInitialPurchase: - initializesMint = true - default: - return handleStatefulSwapError(streamer, NewSwapDeniedError("mint is being initialized")) + if isBuy { + destinationCurrencyMetadataRecord, err = s.data.GetCurrencyMetadata(ctx, toMint.PublicKey().ToBase58()) + if err == currency.ErrNotFound { + return handleStatefulSwapError(streamer, NewSwapValidationError("mint not found")) + } else if err != nil { + log.With(zap.Error(err)).Warn("failure getting destination currency metadata record") + return handleStatefulSwapError(streamer, err) + } + switch destinationCurrencyMetadataRecord.State { + case currency.MetadataStateAvailable: + initializesMint = false + case currency.MetadataStateWaitingForInitialPurchase: + if !common.IsCoreMint(fromMint) { + return handleStatefulSwapError(streamer, NewSwapDeniedError("new currency can only be created from the core mint")) + } + initializesMint = true + default: + return handleStatefulSwapError(streamer, NewSwapDeniedError("mint is being initialized")) + } } - if !initializesMint && !common.IsCoreMint(fromMint) { + if isSell { + sourceCurrencyMetadataRecord, err := s.data.GetCurrencyMetadata(ctx, fromMint.PublicKey().ToBase58()) + if err == currency.ErrNotFound { + return handleStatefulSwapError(streamer, NewSwapValidationError("mint not found")) + } else if err != nil { + log.With(zap.Error(err)).Warn("failure getting source currency metadata record") + return handleStatefulSwapError(streamer, err) + } + liveReserveState, err := s.mintDataProvider.GetLiveReserveState(ctx, fromMint) if err != nil { log.With(zap.Error(err)).Warn("failure getting live reserve state") @@ -317,7 +325,7 @@ func (s *transactionServer) handleReserveStatefulSwap( CurrentSupplyInQuarks: liveReserveState.SupplyFromBonding, SellAmountInQuarks: initiateReserveSwapReq.SwapAmount, ValueMintDecimals: uint8(common.CoreMintDecimals), - SellFeeBps: currencyMetadataRecord.SellFeeBps, + SellFeeBps: sourceCurrencyMetadataRecord.SellFeeBps, }) if estimatedFees == 0 { return handleStatefulSwapError(streamer, NewSwapDeniedError("swap would not generate a sell fee")) @@ -370,7 +378,7 @@ func (s *transactionServer) handleReserveStatefulSwap( return handleStatefulSwapError(streamer, NewSwapValidationError("owner must be swap authority")) } - if owner.PublicKey().ToBase58() != currencyMetadataRecord.CreatedBy { + if owner.PublicKey().ToBase58() != destinationCurrencyMetadataRecord.CreatedBy { return handleStatefulSwapError(streamer, NewSwapDeniedError("only the currency creator can buy initial tokens")) } @@ -379,7 +387,7 @@ func (s *transactionServer) handleReserveStatefulSwap( } // The VM is not supported yet, so we need to work around GetVmConfigForMint - destinationVaultRecord, err := s.data.GetKey(ctx, currencyMetadataRecord.Authority) + destinationVaultRecord, err := s.data.GetKey(ctx, destinationCurrencyMetadataRecord.Authority) if err != nil { log.With(zap.Error(err)).Warn("failure getting destination vm authority vault record") return handleStatefulSwapError(streamer, err) @@ -450,31 +458,31 @@ func (s *transactionServer) handleReserveStatefulSwap( initiateReserveSwapReq.FeeAmount, selectedNonce, ) - } else if common.IsCoreMint(fromMint) { - swapHandler = NewReserveBuySwapHandler( + } else if isBuy && isSell { + swapHandler = NewReserveBuySellSwapHandler( s.data, owner, swapAuthority, + fromMint, toMint, initiateReserveSwapReq.SwapAmount, selectedNonce, ) - } else if common.IsCoreMint(toMint) { - swapHandler = NewReserveSellSwapHandler( + } else if isBuy { + swapHandler = NewReserveBuySwapHandler( s.data, owner, swapAuthority, - fromMint, + toMint, initiateReserveSwapReq.SwapAmount, selectedNonce, ) } else { - swapHandler = NewReserveBuySellSwapHandler( + swapHandler = NewReserveSellSwapHandler( s.data, owner, swapAuthority, fromMint, - toMint, initiateReserveSwapReq.SwapAmount, selectedNonce, ) @@ -639,8 +647,8 @@ func (s *transactionServer) handleReserveStatefulSwap( } if initializesMint { - currencyMetadataRecord.State = currency.MetadataStateFundingAuthority - err = s.data.SaveCurrencyMetadata(ctx, currencyMetadataRecord) + destinationCurrencyMetadataRecord.State = currency.MetadataStateFundingAuthority + err = s.data.SaveCurrencyMetadata(ctx, destinationCurrencyMetadataRecord) if err != nil { log.With(zap.Error(err)).Warn("failure saving currency metadata record") return err diff --git a/ocp/worker/swap/metrics.go b/ocp/worker/swap/metrics.go index e49aec3..dec0dbd 100644 --- a/ocp/worker/swap/metrics.go +++ b/ocp/worker/swap/metrics.go @@ -5,7 +5,6 @@ import ( "time" "github.com/code-payments/ocp-server/metrics" - "github.com/code-payments/ocp-server/ocp/common" "github.com/code-payments/ocp-server/ocp/data/swap" ) @@ -52,23 +51,11 @@ func recordSwapCountEvent(ctx context.Context, state swap.State, count uint64) { } func recordSwapFinalizedEvent(ctx context.Context, swapRecord *swap.Record, quarksBought uint64) { - if !common.IsCoreMintUsdStableCoin() { - return - } - - var usdMarketValue float64 - if common.CoreMintAccount.PublicKey().ToBase58() == swapRecord.FromMint { - usdMarketValue = float64(swapRecord.SwapAmount) / float64(common.CoreMintQuarksPerUnit) - } else { - usdMarketValue = float64(quarksBought) / float64(common.CoreMintQuarksPerUnit) - } - metrics.RecordEvent(ctx, swapFinalizedEventName, map[string]interface{}{ - "id": swapRecord.Id, - "from_mint": swapRecord.FromMint, - "to_mint": swapRecord.ToMint, - "quarks_sold": swapRecord.SwapAmount, - "quarks_bought": quarksBought, - "usd_market_value": usdMarketValue, + "id": swapRecord.Id, + "from_mint": swapRecord.FromMint, + "to_mint": swapRecord.ToMint, + "quarks_sold": swapRecord.SwapAmount, + "quarks_bought": quarksBought, }) } diff --git a/ocp/worker/swap/util.go b/ocp/worker/swap/util.go index 0589548..31900ba 100644 --- a/ocp/worker/swap/util.go +++ b/ocp/worker/swap/util.go @@ -532,12 +532,10 @@ func (p *runtime) maybeUpdateBalancesForFinalizedReserveSwap(ctx context.Context if !common.IsCoreMintUsdStableCoin() { return 0, false, errors.New("core mint is not a usd stable coin") } - if !common.IsCoreMint(fromMint) && !common.IsCoreMint(toMint) { - return 0, false, errors.New("core mint must be involved in swap") - } + var destinationCurrencyMetadataRecord *currency.MetadataRecord if !common.IsCoreMint(toMint) { - destinationCurrencyMetadataRecord, err := p.data.GetCurrencyMetadata(ctx, swapRecord.ToMint) + destinationCurrencyMetadataRecord, err = p.data.GetCurrencyMetadata(ctx, swapRecord.ToMint) if err != nil { return 0, false, err } @@ -599,8 +597,26 @@ func (p *runtime) maybeUpdateBalancesForFinalizedReserveSwap(ctx context.Context nativeAmountWithoutFees = fundingIntentRecord.SendPublicPaymentMetadata.NativeAmount usdMarketValueWithoutFees = fundingIntentRecord.SendPublicPaymentMetadata.UsdMarketValue - if common.IsCoreMint(toMint) { - usdMarketValue, err := currency_util.CalculateUsdMarketValueFromTokenAmount(ctx, p.data, p.exchangeRateStore, p.reserveStore, common.CoreMintAccount, uint64(deltaQuarksIntoOmnibus), time.Now()) + if !common.IsCoreMint(fromMint) { + coreMintQuarksFromSell := uint64(deltaQuarksIntoOmnibus) + if !common.IsCoreMint(toMint) { + destinationCurrencyAccounts, err := common.GetLaunchpadCurrencyAccounts(destinationCurrencyMetadataRecord) + if err != nil { + return 0, false, err + } + + coreMintQuarksIntoDestinationPool, err := transaction_util.GetDeltaQuarksFromTokenBalances(destinationCurrencyAccounts.VaultBase, tokenBalances) + if err != nil { + return 0, false, err + } + if coreMintQuarksIntoDestinationPool <= 0 { + return 0, false, errors.New("delta quarks into destination pool base vault is not positive") + } + + coreMintQuarksFromSell = uint64(coreMintQuarksIntoDestinationPool) + } + + usdMarketValue, err := currency_util.CalculateUsdMarketValueFromTokenAmount(ctx, p.data, p.exchangeRateStore, p.reserveStore, common.CoreMintAccount, coreMintQuarksFromSell, time.Now()) if err != nil { return 0, false, err } @@ -610,11 +626,17 @@ func (p *runtime) maybeUpdateBalancesForFinalizedReserveSwap(ctx context.Context big.NewFloat(0.99).SetPrec(128), ).Float64() - exchangeCurrency = currency_lib.USD - nativeAmountWithoutFees = usdMarketValueWithoutFees + // A sell settles into the core mint, so its native value is reported in USD. A + // cross-currency swap keeps the client's original exchange currency and + // native amount (discounted by the sell fee below); only its USD market + // value is reconciled to the realized core mint. + if common.IsCoreMint(toMint) { + exchangeCurrency = currency_lib.USD + nativeAmountWithoutFees = usdMarketValueWithoutFees + } - // Update funding intent record with actual USD market value for - // consistent USD cost basis + // Reconcile the source funding payment's cost basis to the core mint actually + // realized by the sell. fundingIntentRecord.SendPublicPaymentMetadata.UsdMarketValue = usdMarketValueWithoutFees err = p.data.SaveIntent(ctx, fundingIntentRecord) if err != nil {