diff --git a/.gitignore b/.gitignore index 151a33e4d..c66fcee4e 100644 --- a/.gitignore +++ b/.gitignore @@ -15,6 +15,7 @@ # Dependency directories (remove the comment below to include it) # vendor/ .idea/ +.claude/ vendor/ .vscode/ tmp/ diff --git a/controllers/accounts/profile.go b/controllers/accounts/profile.go index 6b225e95f..61adc2588 100644 --- a/controllers/accounts/profile.go +++ b/controllers/accounts/profile.go @@ -64,6 +64,14 @@ func (ctrl *ProfileController) UpdateSenderProfile(ctx *gin.Context) { return } + if payload.WebhookVersion != "" && payload.WebhookVersion != "1" && payload.WebhookVersion != "2" { + u.APIResponse(ctx, http.StatusBadRequest, "error", "Failed to validate payload", []types.ErrorData{{ + Field: "WebhookVersion", + Message: "Must be \"1\" or \"2\"", + }}) + return + } + // Get sender profile from the context senderCtx, ok := ctx.Get("sender") if !ok { @@ -91,6 +99,10 @@ func (ctrl *ProfileController) UpdateSenderProfile(ctx *gin.Context) { update.SetWebhookURL(payload.WebhookURL) } + if payload.WebhookVersion != "" && payload.WebhookVersion != sender.WebhookVersion { + update.SetWebhookVersion(payload.WebhookVersion) + } + if payload.DomainWhitelist != nil { update.SetDomainWhitelist(payload.DomainWhitelist) } @@ -355,21 +367,68 @@ func (ctrl *ProfileController) UpdateProviderProfile(ctx *gin.Context) { continue } - // Calculate rate from tokenPayload based on conversion type - var rate decimal.Decimal - if tokenPayload.ConversionRateType == providerordertoken.ConversionRateTypeFixed { - rate = tokenPayload.FixedConversionRate - } else { - rate = currency.MarketRate.Add(tokenPayload.FloatingConversionRate) + // Validate buy/sell rates - at least one direction must be configured + hasBuyRate := !tokenPayload.FixedBuyRate.IsZero() || !tokenPayload.FloatingBuyDelta.IsZero() + hasSellRate := !tokenPayload.FixedSellRate.IsZero() || !tokenPayload.FloatingSellDelta.IsZero() + if !hasBuyRate && !hasSellRate { + validationErrors = append(validationErrors, types.ErrorData{ + Field: "Tokens", + Message: fmt.Sprintf("At least one rate (buy or sell) must be configured for %s", tokenPayload.Symbol), + }) + continue + } + + // Validate buy_rate >= sell_rate when both are configured (provider profitability) + var buyRate, sellRate decimal.Decimal + if !tokenPayload.FixedBuyRate.IsZero() { + buyRate = tokenPayload.FixedBuyRate + } else if !tokenPayload.FloatingBuyDelta.IsZero() && !currency.MarketBuyRate.IsZero() { + buyRate = currency.MarketBuyRate.Add(tokenPayload.FloatingBuyDelta) + } + if !tokenPayload.FixedSellRate.IsZero() { + sellRate = tokenPayload.FixedSellRate + } else if !tokenPayload.FloatingSellDelta.IsZero() && !currency.MarketSellRate.IsZero() { + sellRate = currency.MarketSellRate.Add(tokenPayload.FloatingSellDelta) + } + if !buyRate.IsZero() && buyRate.LessThanOrEqual(decimal.Zero) { + validationErrors = append(validationErrors, types.ErrorData{ + Field: "Tokens", + Message: fmt.Sprintf("Buy rate must be positive for %s", tokenPayload.Symbol), + }) + continue + } + if !sellRate.IsZero() && sellRate.LessThanOrEqual(decimal.Zero) { + validationErrors = append(validationErrors, types.ErrorData{ + Field: "Tokens", + Message: fmt.Sprintf("Sell rate must be positive for %s", tokenPayload.Symbol), + }) + continue + } + if !buyRate.IsZero() && !sellRate.IsZero() && buyRate.LessThan(sellRate) { + validationErrors = append(validationErrors, types.ErrorData{ + Field: "Tokens", + Message: fmt.Sprintf("Buy rate must be >= sell rate for profitability (%s)", tokenPayload.Symbol), + }) + continue } - // Validate rate deviation for floating rates - if tokenPayload.ConversionRateType == providerordertoken.ConversionRateTypeFloating { - percentDeviation := u.AbsPercentageDeviation(currency.MarketRate, rate) + // Validate rate deviation only when rate was derived from floating (market + delta), not when using fixed rate + if tokenPayload.FixedBuyRate.IsZero() && !tokenPayload.FloatingBuyDelta.IsZero() && !currency.MarketBuyRate.IsZero() { + percentDeviation := u.AbsPercentageDeviation(currency.MarketBuyRate, buyRate) + if percentDeviation.GreaterThan(orderConf.PercentDeviationFromMarketRate) { + validationErrors = append(validationErrors, types.ErrorData{ + Field: "Tokens", + Message: fmt.Sprintf("Buy rate is too far from market rate for %s", tokenPayload.Symbol), + }) + continue + } + } + if tokenPayload.FixedSellRate.IsZero() && !tokenPayload.FloatingSellDelta.IsZero() && !currency.MarketSellRate.IsZero() { + percentDeviation := u.AbsPercentageDeviation(currency.MarketSellRate, sellRate) if percentDeviation.GreaterThan(orderConf.PercentDeviationFromMarketRate) { validationErrors = append(validationErrors, types.ErrorData{ Field: "Tokens", - Message: fmt.Sprintf("Rate is too far from market rate for %s", tokenPayload.Symbol), + Message: fmt.Sprintf("Sell rate is too far from market rate for %s", tokenPayload.Symbol), }) continue } @@ -384,12 +443,32 @@ func (ctrl *ProfileController) UpdateProviderProfile(ctx *gin.Context) { Message: fmt.Sprintf("Rate slippage cannot be less than 0.1%% for %s", tokenPayload.Symbol), }) continue - } else if rate.Mul(tokenPayload.RateSlippage.Div(decimal.NewFromFloat(100))).GreaterThan(currency.MarketRate.Mul(decimal.NewFromFloat(0.05))) { - validationErrors = append(validationErrors, types.ErrorData{ - Field: "Tokens", - Message: fmt.Sprintf("Rate slippage is too high for %s", tokenPayload.Symbol), - }) - continue + } else { + // Cap slippage percentage so fixed rates (e.g. rateRef=1) cannot bypass validation + const maxSlippagePercent = 5.0 + if tokenPayload.RateSlippage.GreaterThan(decimal.NewFromFloat(maxSlippagePercent)) { + validationErrors = append(validationErrors, types.ErrorData{ + Field: "Tokens", + Message: fmt.Sprintf("Rate slippage is too high for %s (max %.1f%%)", tokenPayload.Symbol, maxSlippagePercent), + }) + continue + } + // Check slippage against market rates (use sell rate as reference for offramp) + marketRef := currency.MarketSellRate + if marketRef.IsZero() { + marketRef = currency.MarketBuyRate + } + rateRef := sellRate + if rateRef.IsZero() { + rateRef = buyRate + } + if !marketRef.IsZero() && !rateRef.IsZero() && rateRef.Mul(tokenPayload.RateSlippage.Div(decimal.NewFromFloat(100))).GreaterThan(marketRef.Mul(decimal.NewFromFloat(0.05))) { + validationErrors = append(validationErrors, types.ErrorData{ + Field: "Tokens", + Message: fmt.Sprintf("Rate slippage is too high for %s", tokenPayload.Symbol), + }) + continue + } } // Check if token already exists for provider @@ -443,11 +522,20 @@ func (ctrl *ProfileController) UpdateProviderProfile(ctx *gin.Context) { tokenPayload.RateSlippage = existingToken.RateSlippage } + // Choose a representative conversion rate for bucket calculations (offramp = sell side) + conversionRate := sellRate + if conversionRate.IsZero() { + conversionRate = buyRate + } + if conversionRate.IsZero() { + conversionRate = decimal.NewFromInt(1) + } + tokenOperations = append(tokenOperations, TokenOperation{ TokenPayload: tokenPayload, ProviderToken: providerToken, Currency: currency, - Rate: rate, + Rate: conversionRate, IsUpdate: isUpdate, ExistingToken: existingToken, }) @@ -586,9 +674,10 @@ func (ctrl *ProfileController) UpdateProviderProfile(ctx *gin.Context) { SetSettlementAddress(op.TokenPayload.SettlementAddress). SetNetwork(op.TokenPayload.Network). SetRateSlippage(op.TokenPayload.RateSlippage). - SetConversionRateType(op.TokenPayload.ConversionRateType). - SetFixedConversionRate(op.TokenPayload.FixedConversionRate). - SetFloatingConversionRate(op.TokenPayload.FloatingConversionRate). + SetFixedBuyRate(op.TokenPayload.FixedBuyRate). + SetFixedSellRate(op.TokenPayload.FixedSellRate). + SetFloatingBuyDelta(op.TokenPayload.FloatingBuyDelta). + SetFloatingSellDelta(op.TokenPayload.FloatingSellDelta). SetMaxOrderAmount(op.TokenPayload.MaxOrderAmount). SetMinOrderAmount(op.TokenPayload.MinOrderAmount). SetMaxOrderAmountOtc(op.TokenPayload.MaxOrderAmountOTC). @@ -610,9 +699,10 @@ func (ctrl *ProfileController) UpdateProviderProfile(ctx *gin.Context) { // Create new token _, err = tx.ProviderOrderToken. Create(). - SetConversionRateType(op.TokenPayload.ConversionRateType). - SetFixedConversionRate(op.TokenPayload.FixedConversionRate). - SetFloatingConversionRate(op.TokenPayload.FloatingConversionRate). + SetFixedBuyRate(op.TokenPayload.FixedBuyRate). + SetFixedSellRate(op.TokenPayload.FixedSellRate). + SetFloatingBuyDelta(op.TokenPayload.FloatingBuyDelta). + SetFloatingSellDelta(op.TokenPayload.FloatingSellDelta). SetMaxOrderAmount(op.TokenPayload.MaxOrderAmount). SetMinOrderAmount(op.TokenPayload.MinOrderAmount). SetMaxOrderAmountOtc(op.TokenPayload.MaxOrderAmountOTC). @@ -829,12 +919,17 @@ func (ctrl *ProfileController) GetSenderProfile(ctx *gin.Context) { kybRejectionComment = kybProfile.KybRejectionComment } + webhookVersion := sender.WebhookVersion + if webhookVersion == "" { + webhookVersion = "1" + } response := &types.SenderProfileResponse{ ID: sender.ID, FirstName: user.FirstName, LastName: user.LastName, Email: user.Email, WebhookURL: sender.WebhookURL, + WebhookVersion: webhookVersion, DomainWhitelist: sender.DomainWhitelist, Tokens: tokensPayload, APIKey: *apiKey, @@ -958,17 +1053,18 @@ func (ctrl *ProfileController) GetProviderProfile(ctx *gin.Context) { tokensPayload := make([]types.ProviderOrderTokenPayload, len(orderTokens)) for i, orderToken := range orderTokens { payload := types.ProviderOrderTokenPayload{ - Symbol: orderToken.Edges.Token.Symbol, - ConversionRateType: orderToken.ConversionRateType, - FixedConversionRate: orderToken.FixedConversionRate, - FloatingConversionRate: orderToken.FloatingConversionRate, - MaxOrderAmount: orderToken.MaxOrderAmount, - MinOrderAmount: orderToken.MinOrderAmount, - MaxOrderAmountOTC: orderToken.MaxOrderAmountOtc, - MinOrderAmountOTC: orderToken.MinOrderAmountOtc, - RateSlippage: orderToken.RateSlippage, - SettlementAddress: orderToken.SettlementAddress, - Network: orderToken.Network, + Symbol: orderToken.Edges.Token.Symbol, + FixedBuyRate: orderToken.FixedBuyRate, + FixedSellRate: orderToken.FixedSellRate, + FloatingBuyDelta: orderToken.FloatingBuyDelta, + FloatingSellDelta: orderToken.FloatingSellDelta, + MaxOrderAmount: orderToken.MaxOrderAmount, + MinOrderAmount: orderToken.MinOrderAmount, + MaxOrderAmountOTC: orderToken.MaxOrderAmountOtc, + MinOrderAmountOTC: orderToken.MinOrderAmountOtc, + RateSlippage: orderToken.RateSlippage, + SettlementAddress: orderToken.SettlementAddress, + Network: orderToken.Network, } tokensPayload[i] = payload } diff --git a/controllers/accounts/profile_test.go b/controllers/accounts/profile_test.go index c12a4d759..10a9873fb 100644 --- a/controllers/accounts/profile_test.go +++ b/controllers/accounts/profile_test.go @@ -576,6 +576,10 @@ func TestProfile(t *testing.T) { Currency: "KES", Tokens: []types.ProviderOrderTokenPayload{{ Symbol: testCtx.token.Symbol, + FixedBuyRate: decimal.NewFromFloat(1.0), + FixedSellRate: decimal.NewFromFloat(1.0), + FloatingBuyDelta: decimal.Zero, + FloatingSellDelta: decimal.Zero, Network: testCtx.orderToken.Network, RateSlippage: decimal.NewFromFloat(25), // 25% slippage MaxOrderAmountOTC: decimal.Zero, @@ -588,7 +592,7 @@ func TestProfile(t *testing.T) { var response types.Response err = json.Unmarshal(res.Body.Bytes(), &response) assert.NoError(t, err) - assert.Equal(t, "Rate slippage is too high for TST", response.Message) + assert.Contains(t, response.Message, "Rate slippage is too high") }) t.Run("fails when rate slippage is less than 0.1", func(t *testing.T) { @@ -598,6 +602,10 @@ func TestProfile(t *testing.T) { Currency: "KES", Tokens: []types.ProviderOrderTokenPayload{{ Symbol: testCtx.token.Symbol, + FixedBuyRate: decimal.NewFromFloat(1.0), + FixedSellRate: decimal.NewFromFloat(1.0), + FloatingBuyDelta: decimal.Zero, + FloatingSellDelta: decimal.Zero, Network: testCtx.orderToken.Network, RateSlippage: decimal.NewFromFloat(0.09), // 0.09% slippage MaxOrderAmountOTC: decimal.Zero, @@ -610,7 +618,7 @@ func TestProfile(t *testing.T) { var response types.Response err = json.Unmarshal(res.Body.Bytes(), &response) assert.NoError(t, err) - assert.Equal(t, "Rate slippage cannot be less than 0.1% for TST", response.Message) + assert.Contains(t, response.Message, "Rate slippage cannot be less than 0.1%") }) t.Run("succeeds with valid rate slippage", func(t *testing.T) { @@ -619,16 +627,17 @@ func TestProfile(t *testing.T) { HostIdentifier: testCtx.providerProfile.HostIdentifier, Currency: "KES", Tokens: []types.ProviderOrderTokenPayload{{ - Symbol: testCtx.token.Symbol, - ConversionRateType: testCtx.orderToken.ConversionRateType, - FixedConversionRate: testCtx.orderToken.FixedConversionRate, - FloatingConversionRate: testCtx.orderToken.FloatingConversionRate, - MaxOrderAmount: testCtx.orderToken.MaxOrderAmount, - MinOrderAmount: testCtx.orderToken.MinOrderAmount, - Network: testCtx.orderToken.Network, - RateSlippage: decimal.NewFromFloat(5), // 5% slippage - MaxOrderAmountOTC: decimal.Zero, - MinOrderAmountOTC: decimal.Zero, + Symbol: testCtx.token.Symbol, + FixedBuyRate: decimal.NewFromFloat(1.0), + FixedSellRate: decimal.NewFromFloat(1.0), + FloatingBuyDelta: decimal.Zero, + FloatingSellDelta: decimal.Zero, + MaxOrderAmount: testCtx.orderToken.MaxOrderAmount, + MinOrderAmount: testCtx.orderToken.MinOrderAmount, + Network: testCtx.orderToken.Network, + RateSlippage: decimal.NewFromFloat(5), // 5% slippage + MaxOrderAmountOTC: decimal.Zero, + MinOrderAmountOTC: decimal.Zero, }}, } res := profileUpdateRequest(payload) @@ -658,14 +667,15 @@ func TestProfile(t *testing.T) { // HostIdentifier: testCtx.providerProfile.HostIdentifier, // Currencies: []string{"KES"}, // Tokens: []types.ProviderOrderTokenPayload{{ - // Currency: testCtx.orderToken.Edges.Currency.Code, - // Symbol: testCtx.orderToken.Edges.Token.Symbol, - // ConversionRateType: testCtx.orderToken.ConversionRateType, - // FixedConversionRate: testCtx.orderToken.FixedConversionRate, - // FloatingConversionRate: testCtx.orderToken.FloatingConversionRate, - // MaxOrderAmount: testCtx.orderToken.MaxOrderAmount, - // MinOrderAmount: testCtx.orderToken.MinOrderAmount, - // Network: testCtx.orderToken.Network, + // Currency: testCtx.orderToken.Edges.Currency.Code, + // Symbol: testCtx.orderToken.Edges.Token.Symbol, + // FixedBuyRate: decimal.NewFromFloat(1.0), + // FixedSellRate: decimal.NewFromFloat(1.0), + // FloatingBuyDelta: decimal.Zero, + // FloatingSellDelta: decimal.Zero, + // MaxOrderAmount: testCtx.orderToken.MaxOrderAmount, + // MinOrderAmount: testCtx.orderToken.MinOrderAmount, + // Network: testCtx.orderToken.Network, // }}, // } // res := profileUpdateRequest(payload) @@ -1284,15 +1294,16 @@ func TestProfile(t *testing.T) { Save(ctx) assert.NoError(t, err) - // Create a provider order token for USD + // Create a provider order token for USD using new two-sided pricing fields _, err = db.Client.ProviderOrderToken. Create(). SetProviderID(testCtx.providerProfile.ID). SetTokenID(testCtx.token.ID). SetCurrencyID(usd.ID). - SetConversionRateType("floating"). - SetFixedConversionRate(decimal.NewFromInt(0)). - SetFloatingConversionRate(decimal.NewFromInt(2)). + SetFixedBuyRate(decimal.NewFromInt(2)). // synthetic buy/sell spread for test + SetFixedSellRate(decimal.NewFromInt(1)). + SetFloatingBuyDelta(decimal.Zero). + SetFloatingSellDelta(decimal.Zero). SetMaxOrderAmount(decimal.NewFromInt(200)). SetMinOrderAmount(decimal.NewFromInt(10)). SetMaxOrderAmountOtc(decimal.Zero). diff --git a/controllers/index.go b/controllers/index.go index 0605b577a..f28be31fa 100644 --- a/controllers/index.go +++ b/controllers/index.go @@ -26,8 +26,6 @@ import ( networkent "github.com/paycrest/aggregator/ent/network" "github.com/paycrest/aggregator/ent/paymentorder" "github.com/paycrest/aggregator/ent/paymentwebhook" - "github.com/paycrest/aggregator/ent/providerordertoken" - "github.com/paycrest/aggregator/ent/providerprofile" "github.com/paycrest/aggregator/ent/senderprofile" tokenEnt "github.com/paycrest/aggregator/ent/token" "github.com/paycrest/aggregator/ent/user" @@ -105,12 +103,13 @@ func (ctrl *Controller) GetFiatCurrencies(ctx *gin.Context) { currencies := make([]types.SupportedCurrencies, 0, len(fiatcurrencies)) for _, currency := range fiatcurrencies { currencies = append(currencies, types.SupportedCurrencies{ - Code: currency.Code, - Name: currency.Name, - ShortName: currency.ShortName, - Decimals: int8(currency.Decimals), - Symbol: currency.Symbol, - MarketRate: currency.MarketRate, + Code: currency.Code, + Name: currency.Name, + ShortName: currency.ShortName, + Decimals: int8(currency.Decimals), + Symbol: currency.Symbol, + MarketBuyRate: currency.MarketBuyRate, + MarketSellRate: currency.MarketSellRate, }) } @@ -218,7 +217,7 @@ func (ctrl *Controller) GetTokenRate(ctx *gin.Context) { } // Validate rate using extracted logic - rateResult, err := u.ValidateRate(ctx, token, currency, tokenAmount, ctx.Query("provider_id"), networkFilter) + rateResult, err := u.ValidateRate(ctx, token, currency, tokenAmount, ctx.Query("provider_id"), networkFilter, u.RateSideSell) if err != nil { // Return 404 if no provider found, else 500 for other errors if strings.Contains(err.Error(), "no provider available") { @@ -1900,8 +1899,10 @@ func (ctrl *Controller) handleNewEvent(ctx *gin.Context, event types.ThirdwebWeb return ctrl.handleTransferEvent(ctx, event) case utils.OrderCreatedEventSignature: return ctrl.handleOrderCreatedEvent(ctx, event) - case utils.OrderSettledEventSignature: - return ctrl.handleOrderSettledEvent(ctx, event) + case utils.SettleOutEventSignature: + return ctrl.handleSettleOutEvent(ctx, event) + case utils.SettleInEventSignature: + return ctrl.handleSettleInEvent(ctx, event) case utils.OrderRefundedEventSignature: return ctrl.handleOrderRefundedEvent(ctx, event) default: @@ -1911,8 +1912,10 @@ func (ctrl *Controller) handleNewEvent(ctx *gin.Context, event types.ThirdwebWeb return ctrl.handleTransferEvent(ctx, event) case "OrderCreated": return ctrl.handleOrderCreatedEvent(ctx, event) - case "OrderSettled": - return ctrl.handleOrderSettledEvent(ctx, event) + case "SettleOut": + return ctrl.handleSettleOutEvent(ctx, event) + case "SettleIn": + return ctrl.handleSettleInEvent(ctx, event) case "OrderRefunded": return ctrl.handleOrderRefundedEvent(ctx, event) default: @@ -2061,8 +2064,8 @@ func (ctrl *Controller) handleOrderCreatedEvent(ctx *gin.Context, event types.Th return nil } -// handleOrderSettledEvent processes OrderSettled events from webhook -func (ctrl *Controller) handleOrderSettledEvent(ctx *gin.Context, event types.ThirdwebWebhookEvent) error { +// handleSettleOutEvent processes SettleOut (offramp) events from webhook +func (ctrl *Controller) handleSettleOutEvent(ctx *gin.Context, event types.ThirdwebWebhookEvent) error { // Convert chain ID from string to int64 chainID, err := strconv.ParseInt(event.Data.ChainID, 10, 64) if err != nil { @@ -2092,8 +2095,8 @@ func (ctrl *Controller) handleOrderSettledEvent(ctx *gin.Context, event types.Th return fmt.Errorf("invalid rebate percent: %w", err) } - // Create order settled event - settledEvent := &types.OrderSettledEvent{ + // Create SettleOut event + settledEvent := &types.SettleOutEvent{ BlockNumber: event.Data.BlockNumber, TxHash: event.Data.TransactionHash, SplitOrderId: nonIndexedParams["splitOrderId"].(string), @@ -2112,7 +2115,7 @@ func (ctrl *Controller) handleOrderSettledEvent(ctx *gin.Context, event types.Th return fmt.Errorf("payment order not found: %w", err) } - err = common.UpdateOrderStatusSettled(ctx, network, settledEvent, lockOrder.MessageHash) + err = common.UpdateOrderStatusSettleOut(ctx, network, settledEvent, lockOrder.MessageHash) if err != nil { return fmt.Errorf("failed to process settled order: %w", err) } @@ -2120,6 +2123,71 @@ func (ctrl *Controller) handleOrderSettledEvent(ctx *gin.Context, event types.Th return nil } +// handleSettleInEvent processes SettleIn (onramp) events from webhook +func (ctrl *Controller) handleSettleInEvent(ctx *gin.Context, event types.ThirdwebWebhookEvent) error { + chainID, err := strconv.ParseInt(event.Data.ChainID, 10, 64) + if err != nil { + return fmt.Errorf("invalid chain ID: %w", err) + } + network, err := storage.Client.Network. + Query(). + Where(networkent.ChainIDEQ(chainID)). + Only(ctx) + if err != nil { + return fmt.Errorf("network not found: %w", err) + } + indexedParams := event.Data.Decoded.IndexedParams + nonIndexedParams := event.Data.Decoded.NonIndexedParams + + orderId, _ := indexedParams["orderId"].(string) + if orderId == "" { + return fmt.Errorf("SettleIn: missing or empty orderId in indexed params") + } + liquidityProvider, _ := indexedParams["liquidityProvider"].(string) + recipient, _ := indexedParams["recipient"].(string) + tokenStr, _ := nonIndexedParams["token"].(string) + + amountStr, ok := nonIndexedParams["amount"].(string) + if !ok || amountStr == "" { + return fmt.Errorf("SettleIn: missing or empty amount in non-indexed params") + } + amount, err := decimal.NewFromString(amountStr) + if err != nil { + return fmt.Errorf("SettleIn: invalid amount %q: %w", amountStr, err) + } + + rateStr, ok := nonIndexedParams["rate"].(string) + if !ok || rateStr == "" { + return fmt.Errorf("SettleIn: missing or empty rate in non-indexed params") + } + rate, err := decimal.NewFromString(rateStr) + if err != nil { + return fmt.Errorf("SettleIn: invalid rate %q: %w", rateStr, err) + } + + aggregatorFee := decimal.Zero + if aggregatorFeeStr, ok := nonIndexedParams["aggregatorFee"].(string); ok && aggregatorFeeStr != "" { + var parseErr error + aggregatorFee, parseErr = decimal.NewFromString(aggregatorFeeStr) + if parseErr != nil { + return fmt.Errorf("SettleIn: invalid aggregatorFee %q: %w", aggregatorFeeStr, parseErr) + } + } + + settleInEvent := &types.SettleInEvent{ + BlockNumber: event.Data.BlockNumber, + TxHash: event.Data.TransactionHash, + OrderId: orderId, + LiquidityProvider: ethcommon.HexToAddress(liquidityProvider).Hex(), + Amount: amount, + Recipient: ethcommon.HexToAddress(recipient).Hex(), + Token: ethcommon.HexToAddress(tokenStr).Hex(), + AggregatorFee: aggregatorFee, + Rate: rate, + } + return common.UpdateOrderStatusSettleIn(ctx, network, settleInEvent) +} + // handleOrderRefundedEvent processes OrderRefunded events from webhook func (ctrl *Controller) handleOrderRefundedEvent(ctx *gin.Context, event types.ThirdwebWebhookEvent) error { // Convert chain ID from string to int64 @@ -2317,18 +2385,13 @@ func (ctrl *Controller) IndexTransaction(ctx *gin.Context) { } // Track event counts - eventCounts := struct { - Transfer int `json:"Transfer"` - OrderCreated int `json:"OrderCreated"` - OrderSettled int `json:"OrderSettled"` - OrderRefunded int `json:"OrderRefunded"` - }{} + eventCounts := &types.EventCounts{} // Run indexing operations based on parameter type var wg sync.WaitGroup var eventCountsMutex sync.Mutex - // If txHash is provided, index Gateway events (OrderCreated, OrderSettled, OrderRefunded) + // If txHash is provided, index Gateway events (OrderCreated, SettleOut, SettleIn, OrderRefunded) if txHash != "" { wg.Add(1) go func() { @@ -2366,7 +2429,8 @@ func (ctrl *Controller) IndexTransaction(ctx *gin.Context) { // Update event counts with actual counts from indexer eventCountsMutex.Lock() eventCounts.OrderCreated += counts.OrderCreated - eventCounts.OrderSettled += counts.OrderSettled + eventCounts.SettleOut += counts.SettleOut + eventCounts.SettleIn += counts.SettleIn eventCounts.OrderRefunded += counts.OrderRefunded eventCountsMutex.Unlock() @@ -2378,7 +2442,7 @@ func (ctrl *Controller) IndexTransaction(ctx *gin.Context) { "ToBlock": toBlock, "EventType": "Gateway", "OrderCreated": counts.OrderCreated, - "OrderSettled": counts.OrderSettled, + "SettleOut": counts.SettleOut, "OrderRefunded": counts.OrderRefunded, }).Infof("Gateway event indexing completed successfully") } @@ -2433,7 +2497,8 @@ func (ctrl *Controller) IndexTransaction(ctx *gin.Context) { // Update event counts with actual counts from indexer eventCountsMutex.Lock() eventCounts.OrderCreated += counts.OrderCreated - eventCounts.OrderSettled += counts.OrderSettled + eventCounts.SettleOut += counts.SettleOut + eventCounts.SettleIn += counts.SettleIn eventCounts.OrderRefunded += counts.OrderRefunded eventCountsMutex.Unlock() @@ -2445,7 +2510,8 @@ func (ctrl *Controller) IndexTransaction(ctx *gin.Context) { "ToBlock": toBlock, "EventType": "Gateway", "OrderCreated": counts.OrderCreated, - "OrderSettled": counts.OrderSettled, + "SettleOut": counts.SettleOut, + "SettleIn": counts.SettleIn, "OrderRefunded": counts.OrderRefunded, }).Infof("Gateway event indexing completed successfully") } @@ -2550,7 +2616,7 @@ func (ctrl *Controller) IndexTransaction(ctx *gin.Context) { wg.Wait() response := types.IndexTransactionResponse{ - Events: eventCounts, + Events: *eventCounts, } // Build response message based on what was indexed @@ -2566,103 +2632,6 @@ func (ctrl *Controller) IndexTransaction(ctx *gin.Context) { u.APIResponse(ctx, http.StatusOK, "success", responseMsg, response) } -// IndexProviderAddress controller indexes provider addresses for OrderSettled events -func (ctrl *Controller) IndexProviderAddress(ctx *gin.Context) { - var request struct { - Network string `json:"network" binding:"required"` - ProviderID string `json:"providerId" binding:"required"` - TokenSymbol string `json:"tokenSymbol" binding:"required"` - CurrencyCode string `json:"currencyCode" binding:"required"` - FromBlock int64 `json:"fromBlock"` - ToBlock int64 `json:"toBlock"` - TxHash string `json:"txHash"` - } - - if err := ctx.ShouldBindJSON(&request); err != nil { - u.APIResponse(ctx, http.StatusBadRequest, "error", "Invalid request payload", nil) - return - } - - // Get network - network, err := storage.Client.Network. - Query(). - Where(networkent.IdentifierEQ(request.Network)). - Only(ctx) - if err != nil { - u.APIResponse(ctx, http.StatusBadRequest, "error", "Network not found", nil) - return - } - - // Get token - token, err := storage.Client.Token. - Query(). - Where( - tokenEnt.SymbolEQ(request.TokenSymbol), - tokenEnt.HasNetworkWith(networkent.IDEQ(network.ID)), - ). - WithNetwork(). - Only(ctx) - if err != nil { - u.APIResponse(ctx, http.StatusBadRequest, "error", "Token not found", nil) - return - } - - // Get provider order token to find the provider address - providerOrderToken, err := storage.Client.ProviderOrderToken. - Query(). - Where( - providerordertoken.HasProviderWith(providerprofile.IDEQ(request.ProviderID)), - providerordertoken.HasTokenWith(tokenEnt.IDEQ(token.ID)), - providerordertoken.HasCurrencyWith(fiatcurrency.CodeEQ(request.CurrencyCode)), - providerordertoken.SettlementAddressNEQ(""), - ). - Only(ctx) - if err != nil { - u.APIResponse(ctx, http.StatusBadRequest, "error", "Provider order token not found", nil) - return - } - - // Create indexer instance - var indexerInstance types.Indexer - if strings.HasPrefix(network.Identifier, "tron") { - indexerInstance = indexer.NewIndexerTron() - } else if strings.HasPrefix(network.Identifier, "starknet") { - indexerInstance, err = indexer.NewIndexerStarknet() - if err != nil { - logger.WithFields(logger.Fields{ - "Error": fmt.Sprintf("%v", err), - "Network": network.Identifier, - }).Errorf("Failed to create Starknet indexer") - u.APIResponse(ctx, http.StatusInternalServerError, "error", "Failed to initialize indexer", nil) - return - } - } else { - indexerInstance, err = indexer.NewIndexerEVM() - if err != nil { - logger.WithFields(logger.Fields{ - "Error": fmt.Sprintf("%v", err), - "Network": network.Identifier, - }).Errorf("Failed to create EVM indexer") - u.APIResponse(ctx, http.StatusInternalServerError, "error", "Failed to initialize indexer", nil) - return - } - } - - // Index provider address - eventCounts, err := indexerInstance.IndexProviderAddress(ctx, network, providerOrderToken.SettlementAddress, request.FromBlock, request.ToBlock, request.TxHash) - if err != nil { - logger.Errorf("Failed to index provider address: %v", err) - u.APIResponse(ctx, http.StatusInternalServerError, "error", "Failed to index provider address", nil) - return - } - - response := types.IndexTransactionResponse{ - Events: *eventCounts, - } - - u.APIResponse(ctx, http.StatusOK, "success", "Provider address indexed successfully", response) -} - // GetEtherscanQueueStats controller returns statistics about the Etherscan queue func (ctrl *Controller) GetEtherscanQueueStats(ctx *gin.Context) { // Create Etherscan service instance diff --git a/controllers/index_test.go b/controllers/index_test.go index 04089363f..79b1cbd86 100644 --- a/controllers/index_test.go +++ b/controllers/index_test.go @@ -1,915 +1,916 @@ -package controllers - -import ( - "context" - "encoding/json" - "fmt" - "net/http" - "testing" - - "github.com/google/uuid" - "github.com/jarcoal/httpmock" - _ "github.com/mattn/go-sqlite3" - "github.com/paycrest/aggregator/config" - "github.com/paycrest/aggregator/ent" - "github.com/paycrest/aggregator/routers/middleware" - db "github.com/paycrest/aggregator/storage" - "github.com/paycrest/aggregator/types" - "github.com/shopspring/decimal" - - "github.com/gin-gonic/gin" - "github.com/paycrest/aggregator/ent/beneficialowner" - "github.com/paycrest/aggregator/ent/enttest" - "github.com/paycrest/aggregator/ent/identityverificationrequest" - "github.com/paycrest/aggregator/ent/kybprofile" - "github.com/paycrest/aggregator/ent/token" - "github.com/paycrest/aggregator/ent/user" - "github.com/paycrest/aggregator/utils/test" - tokenUtils "github.com/paycrest/aggregator/utils/token" - "github.com/stretchr/testify/assert" -) - -var testCtx = struct { - currency *ent.FiatCurrency -}{} - -func setup() error { - // Set up test data - currency, err := test.CreateTestFiatCurrency(nil) - if err != nil { - return err - } - testCtx.currency = currency - - return nil -} - -func TestIndex(t *testing.T) { - // Set up test database client with shared in-memory schema - client := enttest.Open(t, "sqlite3", "file:ent?mode=memory&cache=shared&_fk=1") - defer client.Close() - - db.Client = client - - // Setup test data - err := setup() - assert.NoError(t, err) - - // Set up test routers - // var ctrl Controller - ctrl := NewController() - router := gin.New() - - router.GET("currencies", ctrl.GetFiatCurrencies) - router.GET("pubkey", ctrl.GetAggregatorPublicKey) - router.GET("institutions/:currency_code", ctrl.GetInstitutionsByCurrency) - router.POST("kyc", ctrl.RequestIDVerification) - router.GET("kyc/:wallet_address", ctrl.GetIDVerificationStatus) - router.POST("kyc/webhook", ctrl.KYCWebhook) - router.GET("/v1/tokens", ctrl.GetSupportedTokens) - router.POST("/v1/kyb-submission", middleware.JWTMiddleware, ctrl.HandleKYBSubmission) - router.GET("/v1/kyb-submission", middleware.JWTMiddleware, ctrl.GetKYBDocuments) - - t.Run("GetInstitutions By Currency", func(t *testing.T) { - - res, err := test.PerformRequest(t, "GET", fmt.Sprintf("/institutions/%s", testCtx.currency.Code), nil, nil, router) - assert.NoError(t, err) - - type Response struct { - Status string `json:"status"` - Message string `json:"message"` - Data []types.SupportedInstitutions `json:"data"` - } - - var response Response - // Assert the response body - assert.Equal(t, http.StatusOK, res.Code) - - err = json.Unmarshal(res.Body.Bytes(), &response) - assert.NoError(t, err) - assert.Equal(t, "OK", response.Message) - assert.Equal(t, 2, len(response.Data), "SupportedInstitutions should be two") - }) - - t.Run("Currencies", func(t *testing.T) { - t.Run("fetch supported fiat currencies", func(t *testing.T) { - res, err := test.PerformRequest(t, "GET", "/currencies?scope=sender", nil, nil, router) - assert.NoError(t, err) - - // Assert the response code. - assert.Equal(t, http.StatusOK, res.Code) - - var response struct { - Data []types.SupportedCurrencies - Message string - } - err = json.Unmarshal(res.Body.Bytes(), &response) - assert.NoError(t, err) - assert.Equal(t, "OK", response.Message) - - // Assert /currencies response with the seeded Naira currency. - nairaCurrency := types.SupportedCurrencies{ - Code: "NGN", - Name: "Nigerian Naira", - ShortName: "Naira", - Decimals: 2, - Symbol: "₦", - MarketRate: decimal.NewFromFloat(950.0), - } - - assert.Equal(t, nairaCurrency.Code, response.Data[0].Code) - assert.Equal(t, nairaCurrency.Name, response.Data[0].Name) - assert.Equal(t, nairaCurrency.ShortName, response.Data[0].ShortName) - assert.Equal(t, nairaCurrency.Decimals, response.Data[0].Decimals) - assert.Equal(t, nairaCurrency.Symbol, response.Data[0].Symbol) - assert.True(t, response.Data[0].MarketRate.Equal(nairaCurrency.MarketRate)) - }) - }) - - t.Run("Get Aggregator Public key", func(t *testing.T) { - t.Run("fetch Aggregator Public key", func(t *testing.T) { - res, err := test.PerformRequest(t, "GET", "/pubkey", nil, nil, router) - assert.NoError(t, err) - - // Assert the response code. - assert.Equal(t, http.StatusOK, res.Code) - - var response struct { - Data string - Message string - } - err = json.Unmarshal(res.Body.Bytes(), &response) - assert.NoError(t, err) - assert.Equal(t, "OK", response.Message) - - assert.Equal(t, response.Data, config.CryptoConfig().AggregatorPublicKey) - }) - }) - - t.Run("Request ID Verification", func(t *testing.T) { - // activate httpmock - httpmock.Activate() - defer httpmock.Deactivate() - - // register mock response - httpmock.RegisterResponder("POST", identityConf.SmileIdentityBaseUrl+"/v1/smile_links", - func(r *http.Request) (*http.Response, error) { - resp := httpmock.NewBytesResponse(202, []byte(`{"link": "https://links.usesmileid.com/1111/123456", "ref_id": "123456"}`)) - return resp, nil - }, - ) - t.Run("with valid details", func(t *testing.T) { - payload := types.VerificationRequest{ - WalletAddress: "0xf4c5c4deDde7A86b25E7430796441e209e23eBFB", - Signature: "b1dcfa6beba6c93e5abd38c23890a1ff2e553721c5c379a80b66a2ad74b3755f543cd8e7d8fb064ae4fdeeba93302c156bd012e390c2321a763eddaa12e5ab5d1c", - Nonce: "e08511abb6087c47", - } - - res, err := test.PerformRequest(t, "POST", "/kyc", payload, nil, router) - assert.NoError(t, err) - - // Assert the response code. - assert.Equal(t, http.StatusOK, res.Code) - - var response types.Response - err = json.Unmarshal(res.Body.Bytes(), &response) - assert.NoError(t, err) - assert.Equal(t, "Identity verification requested successfully", response.Message) - data, ok := response.Data.(map[string]interface{}) - assert.True(t, ok, "response.Data is not of type map[string]interface{}") - assert.Equal(t, "https://links.usesmileid.com/1111/123456", data["url"]) - - ivr, err := db.Client.IdentityVerificationRequest. - Query(). - Where( - identityverificationrequest.WalletAddressEQ(payload.WalletAddress), - identityverificationrequest.WalletSignatureEQ(payload.Signature), - ). - Only(context.Background()) - - assert.NoError(t, err) - assert.Equal(t, "success", response.Status) - assert.Equal(t, "https://links.usesmileid.com/1111/123456", ivr.VerificationURL) - assert.Equal(t, "123456", ivr.PlatformRef) - }) - - t.Run("with an already used signature", func(t *testing.T) { - payload := types.VerificationRequest{ - Signature: "b1dcfa6beba6c93e5abd38c23890a1ff2e553721c5c379a80b66a2ad74b3755f543cd8e7d8fb064ae4fdeeba93302c156bd012e390c2321a763eddaa12e5ab5d1c", - WalletAddress: "0xf4c5c4deDde7A86b25E7430796441e209e23eBFB", - Nonce: "e08511abb6087c47", - } - - res, err := test.PerformRequest(t, "POST", "/kyc", payload, nil, router) - assert.NoError(t, err) - - // Assert the response code. - assert.Equal(t, http.StatusBadRequest, res.Code) - - var response types.Response - err = json.Unmarshal(res.Body.Bytes(), &response) - assert.NoError(t, err) - assert.Equal(t, "error", response.Status) - assert.Equal(t, "Signature already used for identity verification", response.Message) - assert.Nil(t, response.Data) - }) - - t.Run("with a different signature for same wallet address with validity duration", func(t *testing.T) { - payload := types.VerificationRequest{ - Signature: "dea3406fa45aa364283e1704b3a8c3b70973a25c262540b71e857efe25e8582b23f98b969cebe320dd2851e5ea36c781253edf7e7d1cd5fe6be704f5709f76df1b", - WalletAddress: "0xf4c5c4deDde7A86b25E7430796441e209e23eBFB", - Nonce: "8c400162fbfe0527", - } - - res, err := test.PerformRequest(t, "POST", "/kyc", payload, nil, router) - assert.NoError(t, err) - - // Assert the response code. - assert.Equal(t, http.StatusOK, res.Code) - - var response types.Response - err = json.Unmarshal(res.Body.Bytes(), &response) - assert.NoError(t, err) - assert.Equal(t, "success", response.Status) - assert.Equal(t, "Identity verification requested successfully", response.Message) - }) - - t.Run("with invalid signature", func(t *testing.T) { - payload := types.VerificationRequest{ - Signature: "invalid_signature", - WalletAddress: "0xf4c5c4deDde7A86b25E7430796441e209e23eBFB", - Nonce: "e08511abb6087c47", - } - - res, err := test.PerformRequest(t, "POST", "/kyc", payload, nil, router) - assert.NoError(t, err) - - // Assert the response code. - assert.Equal(t, http.StatusBadRequest, res.Code) - - var response types.Response - err = json.Unmarshal(res.Body.Bytes(), &response) - assert.NoError(t, err) - assert.Equal(t, "error", response.Status) - assert.Equal(t, "Invalid signature", response.Message) - }) - }) - - t.Run("Get ID Verification Status", func(t *testing.T) { - // Test with a valid wallet address - res, err := test.PerformRequest(t, "GET", fmt.Sprintf("/kyc/%s", "0xf4c5c4deDde7A86b25E7430796441e209e23eBFB"), nil, nil, router) - assert.NoError(t, err) - - // Assert the response code. - assert.Equal(t, http.StatusOK, res.Code) - - var response types.Response - err = json.Unmarshal(res.Body.Bytes(), &response) - assert.NoError(t, err) - assert.Equal(t, "Identity verification status fetched successfully", response.Message) - assert.Equal(t, "success", response.Status) - data, ok := response.Data.(map[string]interface{}) - assert.True(t, ok, "response.Data is not of type map[string]interface{}") - assert.Equal(t, "pending", data["status"]) - }) - - t.Run("GetSupportedTokens", func(t *testing.T) { - // Setup test data for tokens - networks, tokens := test.CreateTestTokenData(t, client) - - // Define response structure - type Response struct { - Status string `json:"status"` - Message string `json:"message"` - Data []types.SupportedTokenResponse `json:"data"` - } - - t.Run("Fetch all enabled tokens", func(t *testing.T) { - res, err := test.PerformRequest(t, "GET", "/v1/tokens", nil, nil, router) - assert.NoError(t, err) - - assert.Equal(t, http.StatusOK, res.Code) - - var response Response - err = json.Unmarshal(res.Body.Bytes(), &response) - assert.NoError(t, err) - assert.Equal(t, "success", response.Status) - assert.Equal(t, "Tokens retrieved successfully", response.Message) - assert.Equal(t, 2, len(response.Data)) // Should only include enabled tokens - - // Verify token details - assert.Equal(t, tokens[0].Symbol, response.Data[0].Symbol) - assert.Equal(t, tokens[0].ContractAddress, response.Data[0].ContractAddress) - assert.Equal(t, tokens[0].Decimals, response.Data[0].Decimals) - assert.Equal(t, tokens[0].BaseCurrency, response.Data[0].BaseCurrency) - assert.Equal(t, networks[0].Identifier, response.Data[0].Network) - }) - - t.Run("Fetch tokens by network", func(t *testing.T) { - res, err := test.PerformRequest(t, "GET", "/v1/tokens?network=arbitrum-one", nil, nil, router) - assert.NoError(t, err) - - assert.Equal(t, http.StatusOK, res.Code) - - var response Response - err = json.Unmarshal(res.Body.Bytes(), &response) - assert.NoError(t, err) - assert.Equal(t, "success", response.Status) - assert.Equal(t, "Tokens retrieved successfully", response.Message) - assert.Equal(t, 1, len(response.Data)) // Should only include tokens for the specified network - - assert.Equal(t, "USDC", response.Data[0].Symbol) - assert.Equal(t, "arbitrum-one", response.Data[0].Network) - }) - - t.Run("Fetch with invalid network", func(t *testing.T) { - res, err := test.PerformRequest(t, "GET", "/v1/tokens?network=invalid-network", nil, nil, router) - assert.NoError(t, err) - - assert.Equal(t, http.StatusOK, res.Code) - - var response Response - err = json.Unmarshal(res.Body.Bytes(), &response) - assert.NoError(t, err) - assert.Equal(t, "success", response.Status) - assert.Equal(t, "Tokens retrieved successfully", response.Message) - assert.Equal(t, 0, len(response.Data)) // No tokens for invalid network - }) - - t.Run("Fetch with no enabled tokens", func(t *testing.T) { - // Disable all tokens - _, err := client.Token.Update(). - Where(token.IsEnabled(true)). - SetIsEnabled(false). - Save(context.Background()) - assert.NoError(t, err) - - res, err := test.PerformRequest(t, "GET", "/v1/tokens", nil, nil, router) - assert.NoError(t, err) - - assert.Equal(t, http.StatusOK, res.Code) - - var response Response - err = json.Unmarshal(res.Body.Bytes(), &response) - assert.NoError(t, err) - assert.Equal(t, "success", response.Status) - assert.Equal(t, "Tokens retrieved successfully", response.Message) - assert.Equal(t, 0, len(response.Data)) // No enabled tokens - }) - }) - - t.Run("HandleKYBSubmission", func(t *testing.T) { - // Create a test user first - testUser, err := test.CreateTestUser(map[string]interface{}{ - "firstName": "Test", - "lastName": "User", - "email": "testuser@example.com", - "scope": "sender", - }) - assert.NoError(t, err) - - // Generate JWT token for the test user - token, err := tokenUtils.GenerateAccessJWT(testUser.ID.String(), "sender") - assert.NoError(t, err) - - // Test data for KYB submission - validKYBSubmission := types.KYBSubmissionInput{ - MobileNumber: "+1234567890", - CompanyName: "Test Company Ltd", - RegisteredBusinessAddress: "123 Business St, Test City, TC 12345", - CertificateOfIncorporationUrl: "https://example.com/cert.pdf", - ArticlesOfIncorporationUrl: "https://example.com/articles.pdf", - BusinessLicenseUrl: nil, // Optional field - ProofOfBusinessAddressUrl: "https://example.com/business-address.pdf", - ProofOfResidentialAddressUrl: "https://example.com/residential-address.pdf", - AmlPolicyUrl: nil, // Optional field - KycPolicyUrl: nil, // Optional field - IAcceptTerms: true, - BeneficialOwners: []types.BeneficialOwnerInput{ - { - FullName: "John Doe", - ResidentialAddress: "456 Residential Ave, Test City, TC 12345", - ProofOfResidentialAddressUrl: "https://example.com/john-residential.pdf", - GovernmentIssuedIdUrl: "https://example.com/john-id.pdf", - DateOfBirth: "1990-01-01", - OwnershipPercentage: 60.0, - GovernmentIssuedIdType: "passport", - }, - { - FullName: "Jane Smith", - ResidentialAddress: "789 Residential Blvd, Test City, TC 12345", - ProofOfResidentialAddressUrl: "https://example.com/jane-residential.pdf", - GovernmentIssuedIdUrl: "https://example.com/jane-id.pdf", - DateOfBirth: "1985-05-15", - OwnershipPercentage: 40.0, - GovernmentIssuedIdType: "drivers_license", - }, - }, - } - - t.Run("successful KYB submission", func(t *testing.T) { - headers := map[string]string{ - "Authorization": "Bearer " + token, - } - - res, err := test.PerformRequest(t, "POST", "/v1/kyb-submission", validKYBSubmission, headers, router) - assert.NoError(t, err) - - assert.Equal(t, http.StatusCreated, res.Code) - - var response types.Response - err = json.Unmarshal(res.Body.Bytes(), &response) - assert.NoError(t, err) - assert.Equal(t, "success", response.Status) - assert.Equal(t, "KYB submission submitted successfully", response.Message) - - // Verify the response data contains submission_id - data, ok := response.Data.(map[string]interface{}) - assert.True(t, ok, "response.Data should be map[string]interface{}") - assert.Contains(t, data, "submission_id") - - // Verify KYB profile was created in database - submissionID, ok := data["submission_id"].(string) - assert.True(t, ok, "submission_id should be a string") - - kybProfileUUID, err := uuid.Parse(submissionID) - assert.NoError(t, err) - - kybProfile, err := db.Client.KYBProfile. - Query(). - Where(kybprofile.IDEQ(kybProfileUUID)). - WithUser(). - WithBeneficialOwners(). - Only(context.Background()) - assert.NoError(t, err) - - // Verify KYB profile details - assert.Equal(t, validKYBSubmission.MobileNumber, kybProfile.MobileNumber) - assert.Equal(t, validKYBSubmission.CompanyName, kybProfile.CompanyName) - assert.Equal(t, validKYBSubmission.RegisteredBusinessAddress, kybProfile.RegisteredBusinessAddress) - assert.Equal(t, validKYBSubmission.CertificateOfIncorporationUrl, kybProfile.CertificateOfIncorporationURL) - assert.Equal(t, validKYBSubmission.ArticlesOfIncorporationUrl, kybProfile.ArticlesOfIncorporationURL) - assert.Equal(t, validKYBSubmission.ProofOfBusinessAddressUrl, kybProfile.ProofOfBusinessAddressURL) - assert.Equal(t, testUser.ID, kybProfile.Edges.User.ID) - - // Verify beneficial owners were created - assert.Equal(t, 2, len(kybProfile.Edges.BeneficialOwners)) - - // Check first beneficial owner - owner1 := kybProfile.Edges.BeneficialOwners[0] - assert.Equal(t, validKYBSubmission.BeneficialOwners[0].FullName, owner1.FullName) - assert.Equal(t, validKYBSubmission.BeneficialOwners[0].ResidentialAddress, owner1.ResidentialAddress) - assert.Equal(t, validKYBSubmission.BeneficialOwners[0].ProofOfResidentialAddressUrl, owner1.ProofOfResidentialAddressURL) - assert.Equal(t, validKYBSubmission.BeneficialOwners[0].GovernmentIssuedIdUrl, owner1.GovernmentIssuedIDURL) - assert.Equal(t, validKYBSubmission.BeneficialOwners[0].DateOfBirth, owner1.DateOfBirth) - assert.Equal(t, validKYBSubmission.BeneficialOwners[0].OwnershipPercentage, owner1.OwnershipPercentage) - assert.Equal(t, beneficialowner.GovernmentIssuedIDType(validKYBSubmission.BeneficialOwners[0].GovernmentIssuedIdType), owner1.GovernmentIssuedIDType) - - // ✅ NEW: Verify user's KYB verification status was updated to "pending" - updatedUser, err := db.Client.User. - Query(). - Where(user.IDEQ(testUser.ID)). - Only(context.Background()) - assert.NoError(t, err) - assert.Equal(t, user.KybVerificationStatusPending, updatedUser.KybVerificationStatus, - "User's KYB verification status should be updated to 'pending' after submission") - }) - - t.Run("duplicate KYB submission", func(t *testing.T) { - headers := map[string]string{ - "Authorization": "Bearer " + token, - } - - res, err := test.PerformRequest(t, "POST", "/v1/kyb-submission", validKYBSubmission, headers, router) - assert.NoError(t, err) - - assert.Equal(t, http.StatusConflict, res.Code) - - var response types.Response - err = json.Unmarshal(res.Body.Bytes(), &response) - assert.NoError(t, err) - assert.Equal(t, "error", response.Status) - assert.Equal(t, "KYB submission already submitted for this user", response.Message) - }) - - t.Run("KYB resubmission after rejection", func(t *testing.T) { - // First, check if a KYB profile already exists for this test user - existingKYBProfile, err := db.Client.KYBProfile. - Query(). - Where(kybprofile.HasUserWith(user.IDEQ(testUser.ID))). - Only(context.Background()) - - var kybProfile *ent.KYBProfile - if err != nil { - if ent.IsNotFound(err) { - // No existing profile found, create a new one - kybProfile, err = db.Client.KYBProfile. - Create(). - SetMobileNumber("+1234567890"). - SetCompanyName("Test Company Ltd"). - SetRegisteredBusinessAddress("123 Business St, Test City, TC 12345"). - SetCertificateOfIncorporationURL("https://example.com/cert.pdf"). - SetArticlesOfIncorporationURL("https://example.com/articles.pdf"). - SetProofOfBusinessAddressURL("https://example.com/business-address.pdf"). - SetUserID(testUser.ID). - Save(context.Background()) - assert.NoError(t, err) - } else { - // Unexpected error during query - assert.NoError(t, err) - } - } else { - // Existing profile found, reuse it - kybProfile = existingKYBProfile - } - - // Simulate a rejected KYB by updating the user's status and adding a rejection comment - _, err = db.Client.User. - Update(). - Where(user.IDEQ(testUser.ID)). - SetKybVerificationStatus(user.KybVerificationStatusRejected). - Save(context.Background()) - assert.NoError(t, err) - - // Update the KYB profile with a rejection comment - _, err = db.Client.KYBProfile. - Update(). - Where(kybprofile.IDEQ(kybProfile.ID)). - SetKybRejectionComment("Incomplete documentation::Please provide clearer business license"). - Save(context.Background()) - assert.NoError(t, err) - - // Create a modified KYB submission for resubmission - businessLicenseUrl := "https://example.com/new-business-license.pdf" - amlPolicyUrl := "https://example.com/new-aml-policy.pdf" - kycPolicyUrl := "https://example.com/new-kyc-policy.pdf" - - modifiedKYBSubmission := types.KYBSubmissionInput{ - MobileNumber: "+9876543210", - CompanyName: "Updated Business Solutions Ltd", - RegisteredBusinessAddress: "456 Corporate Blvd, New City, New Country", - CertificateOfIncorporationUrl: "https://example.com/new-cert-inc.pdf", - ArticlesOfIncorporationUrl: "https://example.com/new-articles-inc.pdf", - BusinessLicenseUrl: &businessLicenseUrl, - ProofOfBusinessAddressUrl: "https://example.com/new-proof-business-address.pdf", - ProofOfResidentialAddressUrl: "https://example.com/new-proof-residential-address.pdf", - AmlPolicyUrl: &amlPolicyUrl, - KycPolicyUrl: &kycPolicyUrl, - IAcceptTerms: true, - BeneficialOwners: []types.BeneficialOwnerInput{ - { - FullName: "Robert Johnson", - ResidentialAddress: "789 Executive Lane, New City, New Country", - ProofOfResidentialAddressUrl: "https://example.com/robert-proof-address.pdf", - GovernmentIssuedIdUrl: "https://example.com/robert-id.pdf", - DateOfBirth: "1975-03-20", - OwnershipPercentage: 70.0, - GovernmentIssuedIdType: "drivers_license", - }, - { - FullName: "Sarah Wilson", - ResidentialAddress: "321 Manager Street, New City, New Country", - ProofOfResidentialAddressUrl: "https://example.com/sarah-proof-address.pdf", - GovernmentIssuedIdUrl: "https://example.com/sarah-id.pdf", - DateOfBirth: "1982-07-10", - OwnershipPercentage: 30.0, - GovernmentIssuedIdType: "national_id", - }, - }, - } - - headers := map[string]string{ - "Authorization": "Bearer " + token, - } - - // Test resubmission - should succeed - res, err := test.PerformRequest(t, "POST", "/v1/kyb-submission", modifiedKYBSubmission, headers, router) - assert.NoError(t, err) - - assert.Equal(t, http.StatusCreated, res.Code) - - var response types.Response - err = json.Unmarshal(res.Body.Bytes(), &response) - assert.NoError(t, err) - assert.Equal(t, "success", response.Status) - assert.Equal(t, "KYB submission updated successfully", response.Message) - - // Verify the KYB profile was updated - updatedKYBProfile, err := db.Client.KYBProfile. - Query(). - Where(kybprofile.HasUserWith(user.IDEQ(testUser.ID))). - WithBeneficialOwners(). - Only(context.Background()) - assert.NoError(t, err) - - // Verify updated fields - assert.Equal(t, modifiedKYBSubmission.MobileNumber, updatedKYBProfile.MobileNumber) - assert.Equal(t, modifiedKYBSubmission.CompanyName, updatedKYBProfile.CompanyName) - assert.Equal(t, modifiedKYBSubmission.RegisteredBusinessAddress, updatedKYBProfile.RegisteredBusinessAddress) - assert.Equal(t, modifiedKYBSubmission.CertificateOfIncorporationUrl, updatedKYBProfile.CertificateOfIncorporationURL) - assert.Equal(t, modifiedKYBSubmission.ArticlesOfIncorporationUrl, updatedKYBProfile.ArticlesOfIncorporationURL) - assert.Equal(t, *modifiedKYBSubmission.BusinessLicenseUrl, *updatedKYBProfile.BusinessLicenseURL) - assert.Equal(t, modifiedKYBSubmission.ProofOfBusinessAddressUrl, updatedKYBProfile.ProofOfBusinessAddressURL) - assert.Equal(t, *modifiedKYBSubmission.AmlPolicyUrl, updatedKYBProfile.AmlPolicyURL) - assert.Equal(t, *modifiedKYBSubmission.KycPolicyUrl, *updatedKYBProfile.KycPolicyURL) - - // Verify beneficial owners were updated - assert.Equal(t, 2, len(updatedKYBProfile.Edges.BeneficialOwners)) - - // Check first beneficial owner - owner1 := updatedKYBProfile.Edges.BeneficialOwners[0] - assert.Equal(t, modifiedKYBSubmission.BeneficialOwners[0].FullName, owner1.FullName) - assert.Equal(t, modifiedKYBSubmission.BeneficialOwners[0].ResidentialAddress, owner1.ResidentialAddress) - assert.Equal(t, modifiedKYBSubmission.BeneficialOwners[0].ProofOfResidentialAddressUrl, owner1.ProofOfResidentialAddressURL) - assert.Equal(t, modifiedKYBSubmission.BeneficialOwners[0].GovernmentIssuedIdUrl, owner1.GovernmentIssuedIDURL) - assert.Equal(t, modifiedKYBSubmission.BeneficialOwners[0].DateOfBirth, owner1.DateOfBirth) - assert.Equal(t, modifiedKYBSubmission.BeneficialOwners[0].OwnershipPercentage, owner1.OwnershipPercentage) - assert.Equal(t, beneficialowner.GovernmentIssuedIDType(modifiedKYBSubmission.BeneficialOwners[0].GovernmentIssuedIdType), owner1.GovernmentIssuedIDType) - - // Verify user's KYB verification status was updated to pending - updatedUser, err := db.Client.User. - Query(). - Where(user.IDEQ(testUser.ID)). - Only(context.Background()) - assert.NoError(t, err) - assert.Equal(t, user.KybVerificationStatusPending, updatedUser.KybVerificationStatus, - "User's KYB verification status should be updated to 'pending' after resubmission") - - // Test that another resubmission is blocked - res, err = test.PerformRequest(t, "POST", "/v1/kyb-submission", modifiedKYBSubmission, headers, router) - assert.NoError(t, err) - - assert.Equal(t, http.StatusConflict, res.Code) - - err = json.Unmarshal(res.Body.Bytes(), &response) - assert.NoError(t, err) - assert.Equal(t, "error", response.Status) - assert.Equal(t, "KYB submission already submitted for this user", response.Message) - }) - - t.Run("missing authorization header", func(t *testing.T) { - res, err := test.PerformRequest(t, "POST", "/v1/kyb-submission", validKYBSubmission, nil, router) - assert.NoError(t, err) - - assert.Equal(t, http.StatusUnauthorized, res.Code) - - var response types.Response - err = json.Unmarshal(res.Body.Bytes(), &response) - assert.NoError(t, err) - assert.Equal(t, "error", response.Status) - assert.Equal(t, "Authorization header is missing", response.Message) - }) - - t.Run("invalid JWT token", func(t *testing.T) { - headers := map[string]string{ - "Authorization": "Bearer invalid-token", - } - - res, err := test.PerformRequest(t, "POST", "/v1/kyb-submission", validKYBSubmission, headers, router) - assert.NoError(t, err) - - assert.Equal(t, http.StatusUnauthorized, res.Code) - - var response types.Response - err = json.Unmarshal(res.Body.Bytes(), &response) - assert.NoError(t, err) - assert.Equal(t, "error", response.Status) - }) - - t.Run("invalid input - missing required fields", func(t *testing.T) { - invalidSubmission := types.KYBSubmissionInput{ - MobileNumber: "+1234567890", - // Missing other required fields - IAcceptTerms: true, - } - - headers := map[string]string{ - "Authorization": "Bearer " + token, - } - - res, err := test.PerformRequest(t, "POST", "/v1/kyb-submission", invalidSubmission, headers, router) - assert.NoError(t, err) - - assert.Equal(t, http.StatusBadRequest, res.Code) - - var response types.Response - err = json.Unmarshal(res.Body.Bytes(), &response) - assert.NoError(t, err) - assert.Equal(t, "error", response.Status) - assert.Equal(t, "Invalid input", response.Message) - }) - - t.Run("invalid input - terms not accepted", func(t *testing.T) { - termsNotAcceptedSubmission := validKYBSubmission - termsNotAcceptedSubmission.IAcceptTerms = false - - headers := map[string]string{ - "Authorization": "Bearer " + token, - } - - res, err := test.PerformRequest(t, "POST", "/v1/kyb-submission", termsNotAcceptedSubmission, headers, router) - assert.NoError(t, err) - - assert.Equal(t, http.StatusBadRequest, res.Code) - - var response types.Response - err = json.Unmarshal(res.Body.Bytes(), &response) - assert.NoError(t, err) - assert.Equal(t, "error", response.Status) - assert.Equal(t, "Kindly accept the terms and conditions to proceed", response.Message) - }) - - t.Run("invalid input - invalid beneficial owner data", func(t *testing.T) { - invalidSubmission := validKYBSubmission - // Create a copy of beneficial owners to avoid modifying the original - invalidSubmission.BeneficialOwners = []types.BeneficialOwnerInput{ - { - FullName: "John Doe", - ResidentialAddress: "456 Residential Ave, Test City, TC 12345", - ProofOfResidentialAddressUrl: "https://example.com/john-residential.pdf", - GovernmentIssuedIdUrl: "https://example.com/john-id.pdf", - DateOfBirth: "1990-01-01", - OwnershipPercentage: 150.0, // Invalid: > 100% - GovernmentIssuedIdType: "passport", - }, - } - - headers := map[string]string{ - "Authorization": "Bearer " + token, - } - - res, err := test.PerformRequest(t, "POST", "/v1/kyb-submission", invalidSubmission, headers, router) - assert.NoError(t, err) - - assert.Equal(t, http.StatusBadRequest, res.Code) - - var response types.Response - err = json.Unmarshal(res.Body.Bytes(), &response) - assert.NoError(t, err) - assert.Equal(t, "error", response.Status) - assert.Equal(t, "Invalid input", response.Message) - }) - }) - - t.Run("GetKYBDocuments", func(t *testing.T) { - // Create a test user first - testUser, err := test.CreateTestUser(map[string]interface{}{ - "firstName": "Rejected", - "lastName": "User", - "email": "rejecteduser@example.com", - "scope": "provider", - }) - assert.NoError(t, err) - - // Generate JWT token for the test user - token, err := tokenUtils.GenerateAccessJWT(testUser.ID.String(), "provider") - assert.NoError(t, err) - - headers := map[string]string{ - "Authorization": "Bearer " + token, - } - - // Test data for KYB submission (we'll create this in the database) - kybData := types.KYBSubmissionInput{ - MobileNumber: "+1234567890", - CompanyName: "Rejected Company Ltd", - RegisteredBusinessAddress: "456 Rejected St, Test City, TC 12345", - CertificateOfIncorporationUrl: "https://example.com/rejected-cert.pdf", - ArticlesOfIncorporationUrl: "https://example.com/rejected-articles.pdf", - BusinessLicenseUrl: nil, - ProofOfBusinessAddressUrl: "https://example.com/rejected-business-address.pdf", - ProofOfResidentialAddressUrl: "https://example.com/rejected-residential-address.pdf", - AmlPolicyUrl: nil, - KycPolicyUrl: nil, - IAcceptTerms: true, - BeneficialOwners: []types.BeneficialOwnerInput{ - { - FullName: "Rejected Owner", - ResidentialAddress: "789 Rejected Ave, Test City, TC 12345", - ProofOfResidentialAddressUrl: "https://example.com/rejected-owner-residential.pdf", - GovernmentIssuedIdUrl: "https://example.com/rejected-owner-id.pdf", - DateOfBirth: "1980-12-25", - OwnershipPercentage: 100.0, - GovernmentIssuedIdType: "national_id", - }, - }, - } - - t.Run("success - rejected user can retrieve documents", func(t *testing.T) { - // Set user status to rejected - _, err := db.Client.User. - UpdateOneID(testUser.ID). - SetKybVerificationStatus(user.KybVerificationStatusRejected). - Save(context.Background()) - assert.NoError(t, err) - - // Create KYB profile in database - rejectionComment := "Certificate of incorporation document is not clear. Please upload a higher quality document." - kybProfile, err := db.Client.KYBProfile. - Create(). - SetMobileNumber(kybData.MobileNumber). - SetCompanyName(kybData.CompanyName). - SetRegisteredBusinessAddress(kybData.RegisteredBusinessAddress). - SetCertificateOfIncorporationURL(kybData.CertificateOfIncorporationUrl). - SetArticlesOfIncorporationURL(kybData.ArticlesOfIncorporationUrl). - SetProofOfBusinessAddressURL(kybData.ProofOfBusinessAddressUrl). - SetKybRejectionComment(rejectionComment). - SetUserID(testUser.ID). - Save(context.Background()) - assert.NoError(t, err) - - // Create beneficial owners - for _, owner := range kybData.BeneficialOwners { - _, err := db.Client.BeneficialOwner. - Create(). - SetFullName(owner.FullName). - SetResidentialAddress(owner.ResidentialAddress). - SetProofOfResidentialAddressURL(owner.ProofOfResidentialAddressUrl). - SetGovernmentIssuedIDURL(owner.GovernmentIssuedIdUrl). - SetDateOfBirth(owner.DateOfBirth). - SetOwnershipPercentage(owner.OwnershipPercentage). - SetGovernmentIssuedIDType(beneficialowner.GovernmentIssuedIDType(owner.GovernmentIssuedIdType)). - SetKybProfileID(kybProfile.ID). - Save(context.Background()) - assert.NoError(t, err) - } - - // Make the request - res, err := test.PerformRequest(t, "GET", "/v1/kyb-submission", nil, headers, router) - assert.NoError(t, err) - - assert.Equal(t, http.StatusOK, res.Code) - - var response types.Response - err = json.Unmarshal(res.Body.Bytes(), &response) - assert.NoError(t, err) - assert.Equal(t, "success", response.Status) - assert.Equal(t, "KYB documents retrieved", response.Message) - - // Verify the response data - data, ok := response.Data.(map[string]interface{}) - assert.True(t, ok, "response.Data should be map[string]interface{}") - - // Check company details - assert.Equal(t, kybData.CompanyName, data["companyName"]) - assert.Equal(t, kybData.MobileNumber, data["mobileNumber"]) - assert.Equal(t, kybData.RegisteredBusinessAddress, data["registeredBusinessAddress"]) - assert.Equal(t, kybData.CertificateOfIncorporationUrl, data["certificateOfIncorporationUrl"]) - assert.Equal(t, kybData.ArticlesOfIncorporationUrl, data["articlesOfIncorporationUrl"]) - assert.Equal(t, kybData.ProofOfBusinessAddressUrl, data["proofOfBusinessAddressUrl"]) - assert.Equal(t, rejectionComment, data["rejectionComment"]) - - // Check beneficial owners - beneficialOwners, ok := data["beneficialOwners"].([]interface{}) - assert.True(t, ok, "beneficialOwners should be []interface{}") - assert.Equal(t, 1, len(beneficialOwners)) - - owner := beneficialOwners[0].(map[string]interface{}) - assert.Equal(t, kybData.BeneficialOwners[0].FullName, owner["fullName"]) - assert.Equal(t, kybData.BeneficialOwners[0].ResidentialAddress, owner["residentialAddress"]) - assert.Equal(t, kybData.BeneficialOwners[0].DateOfBirth, owner["dateOfBirth"]) - assert.Equal(t, kybData.BeneficialOwners[0].OwnershipPercentage, owner["ownershipPercentage"]) - assert.Equal(t, kybData.BeneficialOwners[0].GovernmentIssuedIdType, owner["governmentIssuedIdType"]) - assert.Equal(t, kybData.BeneficialOwners[0].GovernmentIssuedIdUrl, owner["governmentIssuedIdUrl"]) - assert.Equal(t, kybData.BeneficialOwners[0].ProofOfResidentialAddressUrl, owner["proofOfResidentialAddressUrl"]) - }) - - t.Run("forbidden - non-rejected user cannot access documents", func(t *testing.T) { - // Create another test user with approved status - approvedUser, err := test.CreateTestUser(map[string]interface{}{ - "firstName": "Approved", - "lastName": "User", - "email": "approveduser@example.com", - "scope": "provider", - }) - assert.NoError(t, err) - - // Set user status to approved - _, err = db.Client.User. - UpdateOneID(approvedUser.ID). - SetKybVerificationStatus(user.KybVerificationStatusApproved). - Save(context.Background()) - assert.NoError(t, err) - - // Generate JWT token for the approved user - approvedToken, err := tokenUtils.GenerateAccessJWT(approvedUser.ID.String(), "provider") - assert.NoError(t, err) - - approvedHeaders := map[string]string{ - "Authorization": "Bearer " + approvedToken, - } - - // Make the request - res, err := test.PerformRequest(t, "GET", "/v1/kyb-submission", nil, approvedHeaders, router) - assert.NoError(t, err) - - assert.Equal(t, http.StatusForbidden, res.Code) - - var response types.Response - err = json.Unmarshal(res.Body.Bytes(), &response) - assert.NoError(t, err) - assert.Equal(t, "error", response.Status) - assert.Equal(t, "Documents only available for rejected submissions", response.Message) - }) - }) -} +package controllers + +import ( + "context" + "encoding/json" + "fmt" + "net/http" + "testing" + + "github.com/google/uuid" + "github.com/jarcoal/httpmock" + _ "github.com/mattn/go-sqlite3" + "github.com/paycrest/aggregator/config" + "github.com/paycrest/aggregator/ent" + "github.com/paycrest/aggregator/routers/middleware" + db "github.com/paycrest/aggregator/storage" + "github.com/paycrest/aggregator/types" + "github.com/shopspring/decimal" + + "github.com/gin-gonic/gin" + "github.com/paycrest/aggregator/ent/beneficialowner" + "github.com/paycrest/aggregator/ent/enttest" + "github.com/paycrest/aggregator/ent/identityverificationrequest" + "github.com/paycrest/aggregator/ent/kybprofile" + "github.com/paycrest/aggregator/ent/token" + "github.com/paycrest/aggregator/ent/user" + "github.com/paycrest/aggregator/utils/test" + tokenUtils "github.com/paycrest/aggregator/utils/token" + "github.com/stretchr/testify/assert" +) + +var testCtx = struct { + currency *ent.FiatCurrency +}{} + +func setup() error { + // Set up test data + currency, err := test.CreateTestFiatCurrency(nil) + if err != nil { + return err + } + testCtx.currency = currency + + return nil +} + +func TestIndex(t *testing.T) { + // Set up test database client with shared in-memory schema + client := enttest.Open(t, "sqlite3", "file:ent?mode=memory&cache=shared&_fk=1") + defer client.Close() + + db.Client = client + + // Setup test data + err := setup() + assert.NoError(t, err) + + // Set up test routers + // var ctrl Controller + ctrl := NewController() + router := gin.New() + + router.GET("currencies", ctrl.GetFiatCurrencies) + router.GET("pubkey", ctrl.GetAggregatorPublicKey) + router.GET("institutions/:currency_code", ctrl.GetInstitutionsByCurrency) + router.POST("kyc", ctrl.RequestIDVerification) + router.GET("kyc/:wallet_address", ctrl.GetIDVerificationStatus) + router.POST("kyc/webhook", ctrl.KYCWebhook) + router.GET("/v1/tokens", ctrl.GetSupportedTokens) + router.POST("/v1/kyb-submission", middleware.JWTMiddleware, ctrl.HandleKYBSubmission) + router.GET("/v1/kyb-submission", middleware.JWTMiddleware, ctrl.GetKYBDocuments) + + t.Run("GetInstitutions By Currency", func(t *testing.T) { + + res, err := test.PerformRequest(t, "GET", fmt.Sprintf("/institutions/%s", testCtx.currency.Code), nil, nil, router) + assert.NoError(t, err) + + type Response struct { + Status string `json:"status"` + Message string `json:"message"` + Data []types.SupportedInstitutions `json:"data"` + } + + var response Response + // Assert the response body + assert.Equal(t, http.StatusOK, res.Code) + + err = json.Unmarshal(res.Body.Bytes(), &response) + assert.NoError(t, err) + assert.Equal(t, "OK", response.Message) + assert.Equal(t, 2, len(response.Data), "SupportedInstitutions should be two") + }) + + t.Run("Currencies", func(t *testing.T) { + t.Run("fetch supported fiat currencies", func(t *testing.T) { + res, err := test.PerformRequest(t, "GET", "/currencies?scope=sender", nil, nil, router) + assert.NoError(t, err) + + // Assert the response code. + assert.Equal(t, http.StatusOK, res.Code) + + var response struct { + Data []types.SupportedCurrencies + Message string + } + err = json.Unmarshal(res.Body.Bytes(), &response) + assert.NoError(t, err) + assert.Equal(t, "OK", response.Message) + + // Assert /currencies response with the seeded Naira currency. + nairaCurrency := types.SupportedCurrencies{ + Code: "NGN", + Name: "Nigerian Naira", + ShortName: "Naira", + Decimals: 2, + Symbol: "₦", + MarketBuyRate: decimal.NewFromFloat(950.0), + MarketSellRate: decimal.NewFromFloat(950.0), + } + + assert.Equal(t, nairaCurrency.Code, response.Data[0].Code) + assert.Equal(t, nairaCurrency.Name, response.Data[0].Name) + assert.Equal(t, nairaCurrency.ShortName, response.Data[0].ShortName) + assert.Equal(t, nairaCurrency.Decimals, response.Data[0].Decimals) + assert.Equal(t, nairaCurrency.Symbol, response.Data[0].Symbol) + assert.True(t, response.Data[0].MarketSellRate.Equal(nairaCurrency.MarketSellRate)) + }) + }) + + t.Run("Get Aggregator Public key", func(t *testing.T) { + t.Run("fetch Aggregator Public key", func(t *testing.T) { + res, err := test.PerformRequest(t, "GET", "/pubkey", nil, nil, router) + assert.NoError(t, err) + + // Assert the response code. + assert.Equal(t, http.StatusOK, res.Code) + + var response struct { + Data string + Message string + } + err = json.Unmarshal(res.Body.Bytes(), &response) + assert.NoError(t, err) + assert.Equal(t, "OK", response.Message) + + assert.Equal(t, response.Data, config.CryptoConfig().AggregatorPublicKey) + }) + }) + + t.Run("Request ID Verification", func(t *testing.T) { + // activate httpmock + httpmock.Activate() + defer httpmock.Deactivate() + + // register mock response + httpmock.RegisterResponder("POST", identityConf.SmileIdentityBaseUrl+"/v1/smile_links", + func(r *http.Request) (*http.Response, error) { + resp := httpmock.NewBytesResponse(202, []byte(`{"link": "https://links.usesmileid.com/1111/123456", "ref_id": "123456"}`)) + return resp, nil + }, + ) + t.Run("with valid details", func(t *testing.T) { + payload := types.VerificationRequest{ + WalletAddress: "0xf4c5c4deDde7A86b25E7430796441e209e23eBFB", + Signature: "b1dcfa6beba6c93e5abd38c23890a1ff2e553721c5c379a80b66a2ad74b3755f543cd8e7d8fb064ae4fdeeba93302c156bd012e390c2321a763eddaa12e5ab5d1c", + Nonce: "e08511abb6087c47", + } + + res, err := test.PerformRequest(t, "POST", "/kyc", payload, nil, router) + assert.NoError(t, err) + + // Assert the response code. + assert.Equal(t, http.StatusOK, res.Code) + + var response types.Response + err = json.Unmarshal(res.Body.Bytes(), &response) + assert.NoError(t, err) + assert.Equal(t, "Identity verification requested successfully", response.Message) + data, ok := response.Data.(map[string]interface{}) + assert.True(t, ok, "response.Data is not of type map[string]interface{}") + assert.Equal(t, "https://links.usesmileid.com/1111/123456", data["url"]) + + ivr, err := db.Client.IdentityVerificationRequest. + Query(). + Where( + identityverificationrequest.WalletAddressEQ(payload.WalletAddress), + identityverificationrequest.WalletSignatureEQ(payload.Signature), + ). + Only(context.Background()) + + assert.NoError(t, err) + assert.Equal(t, "success", response.Status) + assert.Equal(t, "https://links.usesmileid.com/1111/123456", ivr.VerificationURL) + assert.Equal(t, "123456", ivr.PlatformRef) + }) + + t.Run("with an already used signature", func(t *testing.T) { + payload := types.VerificationRequest{ + Signature: "b1dcfa6beba6c93e5abd38c23890a1ff2e553721c5c379a80b66a2ad74b3755f543cd8e7d8fb064ae4fdeeba93302c156bd012e390c2321a763eddaa12e5ab5d1c", + WalletAddress: "0xf4c5c4deDde7A86b25E7430796441e209e23eBFB", + Nonce: "e08511abb6087c47", + } + + res, err := test.PerformRequest(t, "POST", "/kyc", payload, nil, router) + assert.NoError(t, err) + + // Assert the response code. + assert.Equal(t, http.StatusBadRequest, res.Code) + + var response types.Response + err = json.Unmarshal(res.Body.Bytes(), &response) + assert.NoError(t, err) + assert.Equal(t, "error", response.Status) + assert.Equal(t, "Signature already used for identity verification", response.Message) + assert.Nil(t, response.Data) + }) + + t.Run("with a different signature for same wallet address with validity duration", func(t *testing.T) { + payload := types.VerificationRequest{ + Signature: "dea3406fa45aa364283e1704b3a8c3b70973a25c262540b71e857efe25e8582b23f98b969cebe320dd2851e5ea36c781253edf7e7d1cd5fe6be704f5709f76df1b", + WalletAddress: "0xf4c5c4deDde7A86b25E7430796441e209e23eBFB", + Nonce: "8c400162fbfe0527", + } + + res, err := test.PerformRequest(t, "POST", "/kyc", payload, nil, router) + assert.NoError(t, err) + + // Assert the response code. + assert.Equal(t, http.StatusOK, res.Code) + + var response types.Response + err = json.Unmarshal(res.Body.Bytes(), &response) + assert.NoError(t, err) + assert.Equal(t, "success", response.Status) + assert.Equal(t, "Identity verification requested successfully", response.Message) + }) + + t.Run("with invalid signature", func(t *testing.T) { + payload := types.VerificationRequest{ + Signature: "invalid_signature", + WalletAddress: "0xf4c5c4deDde7A86b25E7430796441e209e23eBFB", + Nonce: "e08511abb6087c47", + } + + res, err := test.PerformRequest(t, "POST", "/kyc", payload, nil, router) + assert.NoError(t, err) + + // Assert the response code. + assert.Equal(t, http.StatusBadRequest, res.Code) + + var response types.Response + err = json.Unmarshal(res.Body.Bytes(), &response) + assert.NoError(t, err) + assert.Equal(t, "error", response.Status) + assert.Equal(t, "Invalid signature", response.Message) + }) + }) + + t.Run("Get ID Verification Status", func(t *testing.T) { + // Test with a valid wallet address + res, err := test.PerformRequest(t, "GET", fmt.Sprintf("/kyc/%s", "0xf4c5c4deDde7A86b25E7430796441e209e23eBFB"), nil, nil, router) + assert.NoError(t, err) + + // Assert the response code. + assert.Equal(t, http.StatusOK, res.Code) + + var response types.Response + err = json.Unmarshal(res.Body.Bytes(), &response) + assert.NoError(t, err) + assert.Equal(t, "Identity verification status fetched successfully", response.Message) + assert.Equal(t, "success", response.Status) + data, ok := response.Data.(map[string]interface{}) + assert.True(t, ok, "response.Data is not of type map[string]interface{}") + assert.Equal(t, "pending", data["status"]) + }) + + t.Run("GetSupportedTokens", func(t *testing.T) { + // Setup test data for tokens + networks, tokens := test.CreateTestTokenData(t, client) + + // Define response structure + type Response struct { + Status string `json:"status"` + Message string `json:"message"` + Data []types.SupportedTokenResponse `json:"data"` + } + + t.Run("Fetch all enabled tokens", func(t *testing.T) { + res, err := test.PerformRequest(t, "GET", "/v1/tokens", nil, nil, router) + assert.NoError(t, err) + + assert.Equal(t, http.StatusOK, res.Code) + + var response Response + err = json.Unmarshal(res.Body.Bytes(), &response) + assert.NoError(t, err) + assert.Equal(t, "success", response.Status) + assert.Equal(t, "Tokens retrieved successfully", response.Message) + assert.Equal(t, 2, len(response.Data)) // Should only include enabled tokens + + // Verify token details + assert.Equal(t, tokens[0].Symbol, response.Data[0].Symbol) + assert.Equal(t, tokens[0].ContractAddress, response.Data[0].ContractAddress) + assert.Equal(t, tokens[0].Decimals, response.Data[0].Decimals) + assert.Equal(t, tokens[0].BaseCurrency, response.Data[0].BaseCurrency) + assert.Equal(t, networks[0].Identifier, response.Data[0].Network) + }) + + t.Run("Fetch tokens by network", func(t *testing.T) { + res, err := test.PerformRequest(t, "GET", "/v1/tokens?network=arbitrum-one", nil, nil, router) + assert.NoError(t, err) + + assert.Equal(t, http.StatusOK, res.Code) + + var response Response + err = json.Unmarshal(res.Body.Bytes(), &response) + assert.NoError(t, err) + assert.Equal(t, "success", response.Status) + assert.Equal(t, "Tokens retrieved successfully", response.Message) + assert.Equal(t, 1, len(response.Data)) // Should only include tokens for the specified network + + assert.Equal(t, "USDC", response.Data[0].Symbol) + assert.Equal(t, "arbitrum-one", response.Data[0].Network) + }) + + t.Run("Fetch with invalid network", func(t *testing.T) { + res, err := test.PerformRequest(t, "GET", "/v1/tokens?network=invalid-network", nil, nil, router) + assert.NoError(t, err) + + assert.Equal(t, http.StatusOK, res.Code) + + var response Response + err = json.Unmarshal(res.Body.Bytes(), &response) + assert.NoError(t, err) + assert.Equal(t, "success", response.Status) + assert.Equal(t, "Tokens retrieved successfully", response.Message) + assert.Equal(t, 0, len(response.Data)) // No tokens for invalid network + }) + + t.Run("Fetch with no enabled tokens", func(t *testing.T) { + // Disable all tokens + _, err := client.Token.Update(). + Where(token.IsEnabled(true)). + SetIsEnabled(false). + Save(context.Background()) + assert.NoError(t, err) + + res, err := test.PerformRequest(t, "GET", "/v1/tokens", nil, nil, router) + assert.NoError(t, err) + + assert.Equal(t, http.StatusOK, res.Code) + + var response Response + err = json.Unmarshal(res.Body.Bytes(), &response) + assert.NoError(t, err) + assert.Equal(t, "success", response.Status) + assert.Equal(t, "Tokens retrieved successfully", response.Message) + assert.Equal(t, 0, len(response.Data)) // No enabled tokens + }) + }) + + t.Run("HandleKYBSubmission", func(t *testing.T) { + // Create a test user first + testUser, err := test.CreateTestUser(map[string]interface{}{ + "firstName": "Test", + "lastName": "User", + "email": "testuser@example.com", + "scope": "sender", + }) + assert.NoError(t, err) + + // Generate JWT token for the test user + token, err := tokenUtils.GenerateAccessJWT(testUser.ID.String(), "sender") + assert.NoError(t, err) + + // Test data for KYB submission + validKYBSubmission := types.KYBSubmissionInput{ + MobileNumber: "+1234567890", + CompanyName: "Test Company Ltd", + RegisteredBusinessAddress: "123 Business St, Test City, TC 12345", + CertificateOfIncorporationUrl: "https://example.com/cert.pdf", + ArticlesOfIncorporationUrl: "https://example.com/articles.pdf", + BusinessLicenseUrl: nil, // Optional field + ProofOfBusinessAddressUrl: "https://example.com/business-address.pdf", + ProofOfResidentialAddressUrl: "https://example.com/residential-address.pdf", + AmlPolicyUrl: nil, // Optional field + KycPolicyUrl: nil, // Optional field + IAcceptTerms: true, + BeneficialOwners: []types.BeneficialOwnerInput{ + { + FullName: "John Doe", + ResidentialAddress: "456 Residential Ave, Test City, TC 12345", + ProofOfResidentialAddressUrl: "https://example.com/john-residential.pdf", + GovernmentIssuedIdUrl: "https://example.com/john-id.pdf", + DateOfBirth: "1990-01-01", + OwnershipPercentage: 60.0, + GovernmentIssuedIdType: "passport", + }, + { + FullName: "Jane Smith", + ResidentialAddress: "789 Residential Blvd, Test City, TC 12345", + ProofOfResidentialAddressUrl: "https://example.com/jane-residential.pdf", + GovernmentIssuedIdUrl: "https://example.com/jane-id.pdf", + DateOfBirth: "1985-05-15", + OwnershipPercentage: 40.0, + GovernmentIssuedIdType: "drivers_license", + }, + }, + } + + t.Run("successful KYB submission", func(t *testing.T) { + headers := map[string]string{ + "Authorization": "Bearer " + token, + } + + res, err := test.PerformRequest(t, "POST", "/v1/kyb-submission", validKYBSubmission, headers, router) + assert.NoError(t, err) + + assert.Equal(t, http.StatusCreated, res.Code) + + var response types.Response + err = json.Unmarshal(res.Body.Bytes(), &response) + assert.NoError(t, err) + assert.Equal(t, "success", response.Status) + assert.Equal(t, "KYB submission submitted successfully", response.Message) + + // Verify the response data contains submission_id + data, ok := response.Data.(map[string]interface{}) + assert.True(t, ok, "response.Data should be map[string]interface{}") + assert.Contains(t, data, "submission_id") + + // Verify KYB profile was created in database + submissionID, ok := data["submission_id"].(string) + assert.True(t, ok, "submission_id should be a string") + + kybProfileUUID, err := uuid.Parse(submissionID) + assert.NoError(t, err) + + kybProfile, err := db.Client.KYBProfile. + Query(). + Where(kybprofile.IDEQ(kybProfileUUID)). + WithUser(). + WithBeneficialOwners(). + Only(context.Background()) + assert.NoError(t, err) + + // Verify KYB profile details + assert.Equal(t, validKYBSubmission.MobileNumber, kybProfile.MobileNumber) + assert.Equal(t, validKYBSubmission.CompanyName, kybProfile.CompanyName) + assert.Equal(t, validKYBSubmission.RegisteredBusinessAddress, kybProfile.RegisteredBusinessAddress) + assert.Equal(t, validKYBSubmission.CertificateOfIncorporationUrl, kybProfile.CertificateOfIncorporationURL) + assert.Equal(t, validKYBSubmission.ArticlesOfIncorporationUrl, kybProfile.ArticlesOfIncorporationURL) + assert.Equal(t, validKYBSubmission.ProofOfBusinessAddressUrl, kybProfile.ProofOfBusinessAddressURL) + assert.Equal(t, testUser.ID, kybProfile.Edges.User.ID) + + // Verify beneficial owners were created + assert.Equal(t, 2, len(kybProfile.Edges.BeneficialOwners)) + + // Check first beneficial owner + owner1 := kybProfile.Edges.BeneficialOwners[0] + assert.Equal(t, validKYBSubmission.BeneficialOwners[0].FullName, owner1.FullName) + assert.Equal(t, validKYBSubmission.BeneficialOwners[0].ResidentialAddress, owner1.ResidentialAddress) + assert.Equal(t, validKYBSubmission.BeneficialOwners[0].ProofOfResidentialAddressUrl, owner1.ProofOfResidentialAddressURL) + assert.Equal(t, validKYBSubmission.BeneficialOwners[0].GovernmentIssuedIdUrl, owner1.GovernmentIssuedIDURL) + assert.Equal(t, validKYBSubmission.BeneficialOwners[0].DateOfBirth, owner1.DateOfBirth) + assert.Equal(t, validKYBSubmission.BeneficialOwners[0].OwnershipPercentage, owner1.OwnershipPercentage) + assert.Equal(t, beneficialowner.GovernmentIssuedIDType(validKYBSubmission.BeneficialOwners[0].GovernmentIssuedIdType), owner1.GovernmentIssuedIDType) + + // ✅ NEW: Verify user's KYB verification status was updated to "pending" + updatedUser, err := db.Client.User. + Query(). + Where(user.IDEQ(testUser.ID)). + Only(context.Background()) + assert.NoError(t, err) + assert.Equal(t, user.KybVerificationStatusPending, updatedUser.KybVerificationStatus, + "User's KYB verification status should be updated to 'pending' after submission") + }) + + t.Run("duplicate KYB submission", func(t *testing.T) { + headers := map[string]string{ + "Authorization": "Bearer " + token, + } + + res, err := test.PerformRequest(t, "POST", "/v1/kyb-submission", validKYBSubmission, headers, router) + assert.NoError(t, err) + + assert.Equal(t, http.StatusConflict, res.Code) + + var response types.Response + err = json.Unmarshal(res.Body.Bytes(), &response) + assert.NoError(t, err) + assert.Equal(t, "error", response.Status) + assert.Equal(t, "KYB submission already submitted for this user", response.Message) + }) + + t.Run("KYB resubmission after rejection", func(t *testing.T) { + // First, check if a KYB profile already exists for this test user + existingKYBProfile, err := db.Client.KYBProfile. + Query(). + Where(kybprofile.HasUserWith(user.IDEQ(testUser.ID))). + Only(context.Background()) + + var kybProfile *ent.KYBProfile + if err != nil { + if ent.IsNotFound(err) { + // No existing profile found, create a new one + kybProfile, err = db.Client.KYBProfile. + Create(). + SetMobileNumber("+1234567890"). + SetCompanyName("Test Company Ltd"). + SetRegisteredBusinessAddress("123 Business St, Test City, TC 12345"). + SetCertificateOfIncorporationURL("https://example.com/cert.pdf"). + SetArticlesOfIncorporationURL("https://example.com/articles.pdf"). + SetProofOfBusinessAddressURL("https://example.com/business-address.pdf"). + SetUserID(testUser.ID). + Save(context.Background()) + assert.NoError(t, err) + } else { + // Unexpected error during query + assert.NoError(t, err) + } + } else { + // Existing profile found, reuse it + kybProfile = existingKYBProfile + } + + // Simulate a rejected KYB by updating the user's status and adding a rejection comment + _, err = db.Client.User. + Update(). + Where(user.IDEQ(testUser.ID)). + SetKybVerificationStatus(user.KybVerificationStatusRejected). + Save(context.Background()) + assert.NoError(t, err) + + // Update the KYB profile with a rejection comment + _, err = db.Client.KYBProfile. + Update(). + Where(kybprofile.IDEQ(kybProfile.ID)). + SetKybRejectionComment("Incomplete documentation::Please provide clearer business license"). + Save(context.Background()) + assert.NoError(t, err) + + // Create a modified KYB submission for resubmission + businessLicenseUrl := "https://example.com/new-business-license.pdf" + amlPolicyUrl := "https://example.com/new-aml-policy.pdf" + kycPolicyUrl := "https://example.com/new-kyc-policy.pdf" + + modifiedKYBSubmission := types.KYBSubmissionInput{ + MobileNumber: "+9876543210", + CompanyName: "Updated Business Solutions Ltd", + RegisteredBusinessAddress: "456 Corporate Blvd, New City, New Country", + CertificateOfIncorporationUrl: "https://example.com/new-cert-inc.pdf", + ArticlesOfIncorporationUrl: "https://example.com/new-articles-inc.pdf", + BusinessLicenseUrl: &businessLicenseUrl, + ProofOfBusinessAddressUrl: "https://example.com/new-proof-business-address.pdf", + ProofOfResidentialAddressUrl: "https://example.com/new-proof-residential-address.pdf", + AmlPolicyUrl: &amlPolicyUrl, + KycPolicyUrl: &kycPolicyUrl, + IAcceptTerms: true, + BeneficialOwners: []types.BeneficialOwnerInput{ + { + FullName: "Robert Johnson", + ResidentialAddress: "789 Executive Lane, New City, New Country", + ProofOfResidentialAddressUrl: "https://example.com/robert-proof-address.pdf", + GovernmentIssuedIdUrl: "https://example.com/robert-id.pdf", + DateOfBirth: "1975-03-20", + OwnershipPercentage: 70.0, + GovernmentIssuedIdType: "drivers_license", + }, + { + FullName: "Sarah Wilson", + ResidentialAddress: "321 Manager Street, New City, New Country", + ProofOfResidentialAddressUrl: "https://example.com/sarah-proof-address.pdf", + GovernmentIssuedIdUrl: "https://example.com/sarah-id.pdf", + DateOfBirth: "1982-07-10", + OwnershipPercentage: 30.0, + GovernmentIssuedIdType: "national_id", + }, + }, + } + + headers := map[string]string{ + "Authorization": "Bearer " + token, + } + + // Test resubmission - should succeed + res, err := test.PerformRequest(t, "POST", "/v1/kyb-submission", modifiedKYBSubmission, headers, router) + assert.NoError(t, err) + + assert.Equal(t, http.StatusCreated, res.Code) + + var response types.Response + err = json.Unmarshal(res.Body.Bytes(), &response) + assert.NoError(t, err) + assert.Equal(t, "success", response.Status) + assert.Equal(t, "KYB submission updated successfully", response.Message) + + // Verify the KYB profile was updated + updatedKYBProfile, err := db.Client.KYBProfile. + Query(). + Where(kybprofile.HasUserWith(user.IDEQ(testUser.ID))). + WithBeneficialOwners(). + Only(context.Background()) + assert.NoError(t, err) + + // Verify updated fields + assert.Equal(t, modifiedKYBSubmission.MobileNumber, updatedKYBProfile.MobileNumber) + assert.Equal(t, modifiedKYBSubmission.CompanyName, updatedKYBProfile.CompanyName) + assert.Equal(t, modifiedKYBSubmission.RegisteredBusinessAddress, updatedKYBProfile.RegisteredBusinessAddress) + assert.Equal(t, modifiedKYBSubmission.CertificateOfIncorporationUrl, updatedKYBProfile.CertificateOfIncorporationURL) + assert.Equal(t, modifiedKYBSubmission.ArticlesOfIncorporationUrl, updatedKYBProfile.ArticlesOfIncorporationURL) + assert.Equal(t, *modifiedKYBSubmission.BusinessLicenseUrl, *updatedKYBProfile.BusinessLicenseURL) + assert.Equal(t, modifiedKYBSubmission.ProofOfBusinessAddressUrl, updatedKYBProfile.ProofOfBusinessAddressURL) + assert.Equal(t, *modifiedKYBSubmission.AmlPolicyUrl, updatedKYBProfile.AmlPolicyURL) + assert.Equal(t, *modifiedKYBSubmission.KycPolicyUrl, *updatedKYBProfile.KycPolicyURL) + + // Verify beneficial owners were updated + assert.Equal(t, 2, len(updatedKYBProfile.Edges.BeneficialOwners)) + + // Check first beneficial owner + owner1 := updatedKYBProfile.Edges.BeneficialOwners[0] + assert.Equal(t, modifiedKYBSubmission.BeneficialOwners[0].FullName, owner1.FullName) + assert.Equal(t, modifiedKYBSubmission.BeneficialOwners[0].ResidentialAddress, owner1.ResidentialAddress) + assert.Equal(t, modifiedKYBSubmission.BeneficialOwners[0].ProofOfResidentialAddressUrl, owner1.ProofOfResidentialAddressURL) + assert.Equal(t, modifiedKYBSubmission.BeneficialOwners[0].GovernmentIssuedIdUrl, owner1.GovernmentIssuedIDURL) + assert.Equal(t, modifiedKYBSubmission.BeneficialOwners[0].DateOfBirth, owner1.DateOfBirth) + assert.Equal(t, modifiedKYBSubmission.BeneficialOwners[0].OwnershipPercentage, owner1.OwnershipPercentage) + assert.Equal(t, beneficialowner.GovernmentIssuedIDType(modifiedKYBSubmission.BeneficialOwners[0].GovernmentIssuedIdType), owner1.GovernmentIssuedIDType) + + // Verify user's KYB verification status was updated to pending + updatedUser, err := db.Client.User. + Query(). + Where(user.IDEQ(testUser.ID)). + Only(context.Background()) + assert.NoError(t, err) + assert.Equal(t, user.KybVerificationStatusPending, updatedUser.KybVerificationStatus, + "User's KYB verification status should be updated to 'pending' after resubmission") + + // Test that another resubmission is blocked + res, err = test.PerformRequest(t, "POST", "/v1/kyb-submission", modifiedKYBSubmission, headers, router) + assert.NoError(t, err) + + assert.Equal(t, http.StatusConflict, res.Code) + + err = json.Unmarshal(res.Body.Bytes(), &response) + assert.NoError(t, err) + assert.Equal(t, "error", response.Status) + assert.Equal(t, "KYB submission already submitted for this user", response.Message) + }) + + t.Run("missing authorization header", func(t *testing.T) { + res, err := test.PerformRequest(t, "POST", "/v1/kyb-submission", validKYBSubmission, nil, router) + assert.NoError(t, err) + + assert.Equal(t, http.StatusUnauthorized, res.Code) + + var response types.Response + err = json.Unmarshal(res.Body.Bytes(), &response) + assert.NoError(t, err) + assert.Equal(t, "error", response.Status) + assert.Equal(t, "Authorization header is missing", response.Message) + }) + + t.Run("invalid JWT token", func(t *testing.T) { + headers := map[string]string{ + "Authorization": "Bearer invalid-token", + } + + res, err := test.PerformRequest(t, "POST", "/v1/kyb-submission", validKYBSubmission, headers, router) + assert.NoError(t, err) + + assert.Equal(t, http.StatusUnauthorized, res.Code) + + var response types.Response + err = json.Unmarshal(res.Body.Bytes(), &response) + assert.NoError(t, err) + assert.Equal(t, "error", response.Status) + }) + + t.Run("invalid input - missing required fields", func(t *testing.T) { + invalidSubmission := types.KYBSubmissionInput{ + MobileNumber: "+1234567890", + // Missing other required fields + IAcceptTerms: true, + } + + headers := map[string]string{ + "Authorization": "Bearer " + token, + } + + res, err := test.PerformRequest(t, "POST", "/v1/kyb-submission", invalidSubmission, headers, router) + assert.NoError(t, err) + + assert.Equal(t, http.StatusBadRequest, res.Code) + + var response types.Response + err = json.Unmarshal(res.Body.Bytes(), &response) + assert.NoError(t, err) + assert.Equal(t, "error", response.Status) + assert.Equal(t, "Invalid input", response.Message) + }) + + t.Run("invalid input - terms not accepted", func(t *testing.T) { + termsNotAcceptedSubmission := validKYBSubmission + termsNotAcceptedSubmission.IAcceptTerms = false + + headers := map[string]string{ + "Authorization": "Bearer " + token, + } + + res, err := test.PerformRequest(t, "POST", "/v1/kyb-submission", termsNotAcceptedSubmission, headers, router) + assert.NoError(t, err) + + assert.Equal(t, http.StatusBadRequest, res.Code) + + var response types.Response + err = json.Unmarshal(res.Body.Bytes(), &response) + assert.NoError(t, err) + assert.Equal(t, "error", response.Status) + assert.Equal(t, "Kindly accept the terms and conditions to proceed", response.Message) + }) + + t.Run("invalid input - invalid beneficial owner data", func(t *testing.T) { + invalidSubmission := validKYBSubmission + // Create a copy of beneficial owners to avoid modifying the original + invalidSubmission.BeneficialOwners = []types.BeneficialOwnerInput{ + { + FullName: "John Doe", + ResidentialAddress: "456 Residential Ave, Test City, TC 12345", + ProofOfResidentialAddressUrl: "https://example.com/john-residential.pdf", + GovernmentIssuedIdUrl: "https://example.com/john-id.pdf", + DateOfBirth: "1990-01-01", + OwnershipPercentage: 150.0, // Invalid: > 100% + GovernmentIssuedIdType: "passport", + }, + } + + headers := map[string]string{ + "Authorization": "Bearer " + token, + } + + res, err := test.PerformRequest(t, "POST", "/v1/kyb-submission", invalidSubmission, headers, router) + assert.NoError(t, err) + + assert.Equal(t, http.StatusBadRequest, res.Code) + + var response types.Response + err = json.Unmarshal(res.Body.Bytes(), &response) + assert.NoError(t, err) + assert.Equal(t, "error", response.Status) + assert.Equal(t, "Invalid input", response.Message) + }) + }) + + t.Run("GetKYBDocuments", func(t *testing.T) { + // Create a test user first + testUser, err := test.CreateTestUser(map[string]interface{}{ + "firstName": "Rejected", + "lastName": "User", + "email": "rejecteduser@example.com", + "scope": "provider", + }) + assert.NoError(t, err) + + // Generate JWT token for the test user + token, err := tokenUtils.GenerateAccessJWT(testUser.ID.String(), "provider") + assert.NoError(t, err) + + headers := map[string]string{ + "Authorization": "Bearer " + token, + } + + // Test data for KYB submission (we'll create this in the database) + kybData := types.KYBSubmissionInput{ + MobileNumber: "+1234567890", + CompanyName: "Rejected Company Ltd", + RegisteredBusinessAddress: "456 Rejected St, Test City, TC 12345", + CertificateOfIncorporationUrl: "https://example.com/rejected-cert.pdf", + ArticlesOfIncorporationUrl: "https://example.com/rejected-articles.pdf", + BusinessLicenseUrl: nil, + ProofOfBusinessAddressUrl: "https://example.com/rejected-business-address.pdf", + ProofOfResidentialAddressUrl: "https://example.com/rejected-residential-address.pdf", + AmlPolicyUrl: nil, + KycPolicyUrl: nil, + IAcceptTerms: true, + BeneficialOwners: []types.BeneficialOwnerInput{ + { + FullName: "Rejected Owner", + ResidentialAddress: "789 Rejected Ave, Test City, TC 12345", + ProofOfResidentialAddressUrl: "https://example.com/rejected-owner-residential.pdf", + GovernmentIssuedIdUrl: "https://example.com/rejected-owner-id.pdf", + DateOfBirth: "1980-12-25", + OwnershipPercentage: 100.0, + GovernmentIssuedIdType: "national_id", + }, + }, + } + + t.Run("success - rejected user can retrieve documents", func(t *testing.T) { + // Set user status to rejected + _, err := db.Client.User. + UpdateOneID(testUser.ID). + SetKybVerificationStatus(user.KybVerificationStatusRejected). + Save(context.Background()) + assert.NoError(t, err) + + // Create KYB profile in database + rejectionComment := "Certificate of incorporation document is not clear. Please upload a higher quality document." + kybProfile, err := db.Client.KYBProfile. + Create(). + SetMobileNumber(kybData.MobileNumber). + SetCompanyName(kybData.CompanyName). + SetRegisteredBusinessAddress(kybData.RegisteredBusinessAddress). + SetCertificateOfIncorporationURL(kybData.CertificateOfIncorporationUrl). + SetArticlesOfIncorporationURL(kybData.ArticlesOfIncorporationUrl). + SetProofOfBusinessAddressURL(kybData.ProofOfBusinessAddressUrl). + SetKybRejectionComment(rejectionComment). + SetUserID(testUser.ID). + Save(context.Background()) + assert.NoError(t, err) + + // Create beneficial owners + for _, owner := range kybData.BeneficialOwners { + _, err := db.Client.BeneficialOwner. + Create(). + SetFullName(owner.FullName). + SetResidentialAddress(owner.ResidentialAddress). + SetProofOfResidentialAddressURL(owner.ProofOfResidentialAddressUrl). + SetGovernmentIssuedIDURL(owner.GovernmentIssuedIdUrl). + SetDateOfBirth(owner.DateOfBirth). + SetOwnershipPercentage(owner.OwnershipPercentage). + SetGovernmentIssuedIDType(beneficialowner.GovernmentIssuedIDType(owner.GovernmentIssuedIdType)). + SetKybProfileID(kybProfile.ID). + Save(context.Background()) + assert.NoError(t, err) + } + + // Make the request + res, err := test.PerformRequest(t, "GET", "/v1/kyb-submission", nil, headers, router) + assert.NoError(t, err) + + assert.Equal(t, http.StatusOK, res.Code) + + var response types.Response + err = json.Unmarshal(res.Body.Bytes(), &response) + assert.NoError(t, err) + assert.Equal(t, "success", response.Status) + assert.Equal(t, "KYB documents retrieved", response.Message) + + // Verify the response data + data, ok := response.Data.(map[string]interface{}) + assert.True(t, ok, "response.Data should be map[string]interface{}") + + // Check company details + assert.Equal(t, kybData.CompanyName, data["companyName"]) + assert.Equal(t, kybData.MobileNumber, data["mobileNumber"]) + assert.Equal(t, kybData.RegisteredBusinessAddress, data["registeredBusinessAddress"]) + assert.Equal(t, kybData.CertificateOfIncorporationUrl, data["certificateOfIncorporationUrl"]) + assert.Equal(t, kybData.ArticlesOfIncorporationUrl, data["articlesOfIncorporationUrl"]) + assert.Equal(t, kybData.ProofOfBusinessAddressUrl, data["proofOfBusinessAddressUrl"]) + assert.Equal(t, rejectionComment, data["rejectionComment"]) + + // Check beneficial owners + beneficialOwners, ok := data["beneficialOwners"].([]interface{}) + assert.True(t, ok, "beneficialOwners should be []interface{}") + assert.Equal(t, 1, len(beneficialOwners)) + + owner := beneficialOwners[0].(map[string]interface{}) + assert.Equal(t, kybData.BeneficialOwners[0].FullName, owner["fullName"]) + assert.Equal(t, kybData.BeneficialOwners[0].ResidentialAddress, owner["residentialAddress"]) + assert.Equal(t, kybData.BeneficialOwners[0].DateOfBirth, owner["dateOfBirth"]) + assert.Equal(t, kybData.BeneficialOwners[0].OwnershipPercentage, owner["ownershipPercentage"]) + assert.Equal(t, kybData.BeneficialOwners[0].GovernmentIssuedIdType, owner["governmentIssuedIdType"]) + assert.Equal(t, kybData.BeneficialOwners[0].GovernmentIssuedIdUrl, owner["governmentIssuedIdUrl"]) + assert.Equal(t, kybData.BeneficialOwners[0].ProofOfResidentialAddressUrl, owner["proofOfResidentialAddressUrl"]) + }) + + t.Run("forbidden - non-rejected user cannot access documents", func(t *testing.T) { + // Create another test user with approved status + approvedUser, err := test.CreateTestUser(map[string]interface{}{ + "firstName": "Approved", + "lastName": "User", + "email": "approveduser@example.com", + "scope": "provider", + }) + assert.NoError(t, err) + + // Set user status to approved + _, err = db.Client.User. + UpdateOneID(approvedUser.ID). + SetKybVerificationStatus(user.KybVerificationStatusApproved). + Save(context.Background()) + assert.NoError(t, err) + + // Generate JWT token for the approved user + approvedToken, err := tokenUtils.GenerateAccessJWT(approvedUser.ID.String(), "provider") + assert.NoError(t, err) + + approvedHeaders := map[string]string{ + "Authorization": "Bearer " + approvedToken, + } + + // Make the request + res, err := test.PerformRequest(t, "GET", "/v1/kyb-submission", nil, approvedHeaders, router) + assert.NoError(t, err) + + assert.Equal(t, http.StatusForbidden, res.Code) + + var response types.Response + err = json.Unmarshal(res.Body.Bytes(), &response) + assert.NoError(t, err) + assert.Equal(t, "error", response.Status) + assert.Equal(t, "Documents only available for rejected submissions", response.Message) + }) + }) +} diff --git a/controllers/provider/provider.go b/controllers/provider/provider.go index e380d49fc..14ebce6ad 100644 --- a/controllers/provider/provider.go +++ b/controllers/provider/provider.go @@ -3,12 +3,19 @@ package provider import ( "context" "encoding/csv" + "encoding/hex" + "errors" "fmt" + "io" + "math/big" "net/http" "strconv" "strings" "time" + "github.com/ethereum/go-ethereum/accounts/abi" + ethcommon "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/crypto" "github.com/google/uuid" fastshot "github.com/opus-domini/fast-shot" "github.com/paycrest/aggregator/config" @@ -19,10 +26,15 @@ import ( "github.com/paycrest/aggregator/ent/paymentorderfulfillment" "github.com/paycrest/aggregator/ent/predicate" "github.com/paycrest/aggregator/ent/providerbalances" + "github.com/paycrest/aggregator/ent/providerordertoken" "github.com/paycrest/aggregator/ent/providerprofile" + "github.com/paycrest/aggregator/ent/senderordertoken" + "github.com/paycrest/aggregator/ent/senderprofile" "github.com/paycrest/aggregator/ent/token" "github.com/paycrest/aggregator/ent/transactionlog" + "github.com/paycrest/aggregator/services" "github.com/paycrest/aggregator/services/balance" + "github.com/paycrest/aggregator/services/contracts" orderService "github.com/paycrest/aggregator/services/order" starknetService "github.com/paycrest/aggregator/services/starknet" "github.com/paycrest/aggregator/storage" @@ -35,6 +47,7 @@ import ( ) var orderConf = config.OrderConfig() +var cryptoConf = config.CryptoConfig() // ProviderController is a controller type for provider endpoints type ProviderController struct { @@ -161,10 +174,14 @@ func (ctrl *ProviderController) handleListPaymentOrders(ctx *gin.Context, provid statusMap := map[string]paymentorder.Status{ "pending": paymentorder.StatusPending, "validated": paymentorder.StatusValidated, + "processing": paymentorder.StatusFulfilling, // for backwards compatibility + "fulfilling": paymentorder.StatusFulfilling, "fulfilled": paymentorder.StatusFulfilled, "cancelled": paymentorder.StatusCancelled, - "processing": paymentorder.StatusFulfilling, // Backward compatibility + "settling": paymentorder.StatusSettling, "settled": paymentorder.StatusSettled, + "refunding": paymentorder.StatusRefunding, + "refunded": paymentorder.StatusRefunded, } statusQueryParam := ctx.Query("status") @@ -552,25 +569,39 @@ func (ctrl *ProviderController) AcceptOrder(ctx *gin.Context) { return } - // Get Order request from Redis - result, err := storage.RedisClient.HGetAll(ctx, fmt.Sprintf("order_request_%s", orderID)).Result() + // Parse request body for direction and amount (for payin orders) + var acceptRequest types.AcceptOrderRequest + if err := ctx.ShouldBindJSON(&acceptRequest); err != nil { + if !errors.Is(err, io.EOF) { + ctx.JSON(http.StatusBadRequest, gin.H{"status": "error", "message": "Invalid request body"}) + return + } + // Empty body: backward compatibility, default to payout + acceptRequest.Direction = "payout" + } + + // Get order request from Redis (offramp: set by assignment; payin: set by sender at creation) + orderRequestKey := fmt.Sprintf("order_request_%s", orderID) + result, err := storage.RedisClient.HGetAll(ctx, orderRequestKey).Result() if err != nil { logger.Errorf("error getting order request from Redis: %v", err) u.APIResponse(ctx, http.StatusInternalServerError, "error", "Failed to accept order request", nil) return } - - if result["providerId"] != provider.ID || len(result) == 0 { + if len(result) == 0 || result["providerId"] != provider.ID { logger.Errorf("order request not found in Redis: %v", orderID) u.APIResponse(ctx, http.StatusNotFound, "error", "Order request not found or is expired", nil) return } + // Payin vs payout: payin has direction=payin in order_request + isPayin := result["direction"] == "payin" + // Best-effort cleanup of metadata key used for timeout recovery. _, _ = storage.RedisClient.Del(ctx, fmt.Sprintf("order_request_meta_%s", orderID)).Result() - // Delete order request from Redis - _, err = storage.RedisClient.Del(ctx, fmt.Sprintf("order_request_%s", orderID)).Result() + // Delete order request from Redis after validation (both payin and offramp) + _, err = storage.RedisClient.Del(ctx, orderRequestKey).Result() if err != nil { logger.Errorf("error deleting order request from Redis: %v", err) u.APIResponse(ctx, http.StatusInternalServerError, "error", "Failed to accept order request", nil) @@ -584,7 +615,20 @@ func (ctrl *ProviderController) AcceptOrder(ctx *gin.Context) { } // Fetch the order to check its current status before accepting - currentOrder, err := tx.PaymentOrder.Get(ctx, orderID) + // For payin, we need token and network info + var currentOrder *ent.PaymentOrder + if isPayin { + currentOrder, err = tx.PaymentOrder. + Query(). + Where(paymentorder.IDEQ(orderID)). + WithProvider(). + WithToken(func(tq *ent.TokenQuery) { + tq.WithNetwork() + }). + Only(ctx) + } else { + currentOrder, err = tx.PaymentOrder.Get(ctx, orderID) + } if err != nil { _ = tx.Rollback() if ent.IsNotFound(err) { @@ -599,6 +643,114 @@ func (ctrl *ProviderController) AcceptOrder(ctx *gin.Context) { return } + // For payin orders, confirm it's an onramp order and check token balance reservation + if isPayin { + // Confirm order is onramp + if currentOrder.Direction != paymentorder.DirectionOnramp { + _ = tx.Rollback() + u.APIResponse(ctx, http.StatusBadRequest, "error", "Order is not an onramp order", nil) + return + } + + // Ensure reserved token liquidity exists / is sufficient + // Total crypto needed: amount + senderFee + totalCryptoNeeded := currentOrder.Amount.Add(currentOrder.SenderFee) + + // Check provider token balance reservation + providerBalance, err := storage.Client.ProviderBalances. + Query(). + Where( + providerbalances.HasProviderWith(providerprofile.IDEQ(provider.ID)), + providerbalances.HasTokenWith(token.IDEQ(currentOrder.Edges.Token.ID)), + ). + Only(ctx) + if err != nil { + _ = tx.Rollback() + logger.Errorf("Failed to get provider token balance: %v", err) + u.APIResponse(ctx, http.StatusInternalServerError, "error", "Failed to check provider balance", nil) + return + } + + // Check if reserved balance is sufficient + if providerBalance.ReservedBalance.LessThan(totalCryptoNeeded) { + // Create order_refunding transaction log and set order to REFUNDING under the same transaction + networkID := "" + if currentOrder.Edges.Token != nil && currentOrder.Edges.Token.Edges.Network != nil { + networkID = currentOrder.Edges.Token.Edges.Network.Identifier + } + transactionLog, err := tx.TransactionLog.Create(). + SetStatus(transactionlog.StatusOrderRefunding). + SetGatewayID(orderID.String()). + SetNetwork(networkID). + Save(ctx) + if err != nil { + _ = tx.Rollback() + logger.Errorf("Failed to create order_refunding transaction log: %v", err) + u.APIResponse(ctx, http.StatusInternalServerError, "error", "Failed to update order status", nil) + return + } + _, err = tx.PaymentOrder. + UpdateOneID(orderID). + SetStatus(paymentorder.StatusRefunding). + AddTransactions(transactionLog). + Save(ctx) + if err != nil { + _ = tx.Rollback() + logger.Errorf("Failed to set order status to refunding: %v", err) + u.APIResponse(ctx, http.StatusInternalServerError, "error", "Failed to update order status", nil) + return + } + if err = tx.Commit(); err != nil { + logger.Errorf("Failed to commit transaction: %v", err) + u.APIResponse(ctx, http.StatusInternalServerError, "error", "Failed to update order status", nil) + return + } + + // Return 4XX with refund details + refundDetails := map[string]interface{}{ + "accountIdentifier": currentOrder.AccountIdentifier, + "accountName": currentOrder.AccountName, + "institution": currentOrder.Institution, + } + if currentOrder.Metadata != nil { + if m, ok := currentOrder.Metadata["refundAccountMetadata"].(map[string]interface{}); ok { + refundDetails["metadata"] = m + } + } + u.APIResponse(ctx, http.StatusBadRequest, "error", "Insufficient reserved token balance", refundDetails) + return + } + } + + // For payin: if order is already Fulfilling and same provider, return 201 + lock payload (idempotent retry after settlement failure) + if isPayin && currentOrder.Direction == paymentorder.DirectionOnramp && currentOrder.Status == paymentorder.StatusFulfilling && currentOrder.Edges.Provider != nil && currentOrder.Edges.Provider.ID == provider.ID { + _ = tx.Rollback() + response := types.AcceptOrderResponse{ + ID: orderID.String(), + Institution: currentOrder.Institution, + AccountIdentifier: currentOrder.AccountIdentifier, + AccountName: currentOrder.AccountName, + Memo: currentOrder.Memo, + Metadata: currentOrder.Metadata, + } + response.Direction = "payin" + response.Amount = currentOrder.Amount.Add(currentOrder.SenderFee).Mul(currentOrder.Rate).RoundBank(0) + if currentOrder.Edges.Token != nil && currentOrder.Edges.Token.Edges.Network != nil { + network := currentOrder.Edges.Token.Edges.Network + response.ChainId = fmt.Sprintf("%d", network.ChainID) + response.RpcUrl = network.RPCEndpoint + response.DelegationAddress = network.GatewayContractAddress + } + if currentOrder.OrderType == paymentorder.OrderTypeOtc { + if response.Metadata == nil { + response.Metadata = make(map[string]interface{}) + } + response.Metadata["otcFulfillmentExpiry"] = time.Now().Add(orderConf.OrderFulfillmentValidityOtc) + } + u.APIResponse(ctx, http.StatusCreated, "success", "Order accepted", response) + return + } + // Check if order is already in a finalized state // Prevent accepting orders that are refunded, fulfilled, validated, settled, cancelled, or processing if currentOrder.Status != paymentorder.StatusPending { @@ -619,23 +771,24 @@ func (ctrl *ProviderController) AcceptOrder(ctx *gin.Context) { Where( paymentorder.IDEQ(orderID), paymentorder.HasTransactionsWith( - transactionlog.StatusEQ(transactionlog.StatusOrderProcessing), + transactionlog.StatusEQ(transactionlog.StatusOrderFulfilling), ), ). Only(ctx) if err != nil { if !ent.IsNotFound(err) { + _ = tx.Rollback() + u.APIResponse(ctx, http.StatusInternalServerError, "error", "Failed to update order status", nil) + return + } + transactionLog, err = tx.TransactionLog. + Create(). + SetStatus(transactionlog.StatusOrderFulfilling). + Save(ctx) + if err != nil { + _ = tx.Rollback() u.APIResponse(ctx, http.StatusInternalServerError, "error", "Failed to update order status", nil) return - } else { - transactionLog, err = tx.TransactionLog. - Create(). - SetStatus(transactionlog.StatusOrderProcessing). - Save(ctx) - if err != nil { - u.APIResponse(ctx, http.StatusInternalServerError, "error", "Failed to update order status", nil) - return - } } } @@ -682,7 +835,18 @@ func (ctrl *ProviderController) AcceptOrder(ctx *gin.Context) { } // Fetch the updated order for response - order, err := tx.PaymentOrder.Get(ctx, orderID) + var order *ent.PaymentOrder + if isPayin { + order, err = tx.PaymentOrder. + Query(). + Where(paymentorder.IDEQ(orderID)). + WithToken(func(tq *ent.TokenQuery) { + tq.WithNetwork() + }). + Only(ctx) + } else { + order, err = tx.PaymentOrder.Get(ctx, orderID) + } if err != nil { logger.Errorf("%s - error.AcceptOrder.Get: %v", orderID, err) _ = tx.Rollback() @@ -696,18 +860,41 @@ func (ctrl *ProviderController) AcceptOrder(ctx *gin.Context) { return } + // Unified AcceptOrder response for both payin and payout response := types.AcceptOrderResponse{ - ID: orderID, - Amount: order.Amount.Mul(order.Rate).RoundBank(0), + ID: orderID.String(), Institution: order.Institution, AccountIdentifier: order.AccountIdentifier, AccountName: order.AccountName, Memo: order.Memo, Metadata: order.Metadata, } + if isPayin { + response.Direction = "payin" + response.Amount = order.Amount.Add(order.SenderFee).Mul(order.Rate).RoundBank(0) + if order.Edges.Token != nil && order.Edges.Token.Edges.Network != nil { + network := order.Edges.Token.Edges.Network + response.ChainId = fmt.Sprintf("%d", network.ChainID) + response.RpcUrl = network.RPCEndpoint + response.DelegationAddress = network.GatewayContractAddress + } + } else { + response.Direction = "payout" + response.Amount = order.Amount.Mul(order.Rate).RoundBank(0) + } - if order.OrderType == paymentorder.OrderTypeOtc && order.Status == paymentorder.StatusFulfilling && order.Edges.Provider == provider { - response.Metadata["otcFulfillmentExpiry"] = time.Now().Add(orderConf.OrderFulfillmentValidityOtc) + if order.OrderType == paymentorder.OrderTypeOtc && order.Status == paymentorder.StatusFulfilling { + orderWithProvider, _ := storage.Client.PaymentOrder. + Query(). + Where(paymentorder.IDEQ(orderID)). + WithProvider(). + Only(ctx) + if orderWithProvider != nil && orderWithProvider.Edges.Provider != nil && orderWithProvider.Edges.Provider.ID == provider.ID { + if response.Metadata == nil { + response.Metadata = make(map[string]interface{}) + } + response.Metadata["otcFulfillmentExpiry"] = time.Now().Add(orderConf.OrderFulfillmentValidityOtc) + } } u.APIResponse(ctx, http.StatusCreated, "success", "Order request accepted successfully", &response) @@ -828,16 +1015,6 @@ func (ctrl *ProviderController) FulfillOrder(ctx *gin.Context) { return } - updateLockOrder := storage.Client.PaymentOrder. - Update(). - Where( - paymentorder.IDEQ(orderID), - paymentorder.Or( - paymentorder.StatusEQ(paymentorder.StatusFulfilling), - paymentorder.StatusEQ(paymentorder.StatusFulfilled), - ), - ) - // Query or create order fulfillment fulfillment, err := storage.Client.PaymentOrderFulfillment. Query(). @@ -908,19 +1085,27 @@ func (ctrl *ProviderController) FulfillOrder(ctx *gin.Context) { }). Only(ctx) if err != nil { + networkID := "" + if fulfillment != nil && fulfillment.Edges.Order != nil && fulfillment.Edges.Order.Edges.Token != nil && fulfillment.Edges.Order.Edges.Token.Edges.Network != nil { + networkID = fulfillment.Edges.Order.Edges.Token.Edges.Network.Identifier + } logger.WithFields(logger.Fields{ "Error": fmt.Sprintf("%v", err), "Trx Id": payload.TxID, - "Network": fulfillment.Edges.Order.Edges.Token.Edges.Network.Identifier, + "Network": networkID, }).Errorf("Failed to fetch order fulfillment: %v", err) u.APIResponse(ctx, http.StatusInternalServerError, "error", "Failed to update order status", nil) return } } else { + networkID := "" + if fulfillment != nil && fulfillment.Edges.Order != nil && fulfillment.Edges.Order.Edges.Token != nil && fulfillment.Edges.Order.Edges.Token.Edges.Network != nil { + networkID = fulfillment.Edges.Order.Edges.Token.Edges.Network.Identifier + } logger.WithFields(logger.Fields{ "Error": fmt.Sprintf("%v", err), "Trx Id": payload.TxID, - "Network": fulfillment.Edges.Order.Edges.Token.Edges.Network.Identifier, + "Network": networkID, }).Errorf("Failed to fetch order fulfillment when order is found: %v", err) u.APIResponse(ctx, http.StatusInternalServerError, "error", "Failed to update order status", nil) return @@ -950,6 +1135,38 @@ func (ctrl *ProviderController) FulfillOrder(ctx *gin.Context) { } } + // Handle refund outcome when order is Refunding (payin insufficient-balance refund path) + if fulfillment.Edges.Order != nil && + fulfillment.Edges.Order.Status == paymentorder.StatusRefunding && + fulfillment.Edges.Order.Direction == paymentorder.DirectionOnramp { + ctrl.handleRefundOutcomeFulfillment(ctx, orderID, payload, fulfillment, provider) + return + } + + // Check if this is a payin order (onramp) + isPayin := fulfillment.Edges.Order != nil && fulfillment.Edges.Order.Direction == paymentorder.DirectionOnramp + + // Handle payin orders + if isPayin { + ctrl.handlePayinFulfillment(ctx, orderID, payload, fulfillment, provider) + return + } + + ctrl.handlePayoutFulfillment(ctx, orderID, payload, fulfillment, provider) +} + +// handlePayoutFulfillment handles payout (offramp) order fulfillment by validation status. +func (ctrl *ProviderController) handlePayoutFulfillment(ctx *gin.Context, orderID uuid.UUID, payload types.FulfillOrderPayload, fulfillment *ent.PaymentOrderFulfillment, provider *ent.ProviderProfile) { + updateLockOrder := storage.Client.PaymentOrder. + Update(). + Where( + paymentorder.IDEQ(orderID), + paymentorder.Or( + paymentorder.StatusEQ(paymentorder.StatusFulfilling), + paymentorder.StatusEQ(paymentorder.StatusFulfilled), + ), + ) + switch payload.ValidationStatus { case paymentorderfulfillment.ValidationStatusSuccess: // Double-check order status (race condition protection) @@ -1124,7 +1341,7 @@ func (ctrl *ProviderController) FulfillOrder(ctx *gin.Context) { }() case paymentorderfulfillment.ValidationStatusFailed: - _, err = fulfillment.Update(). + _, err := fulfillment.Update(). SetValidationStatus(paymentorderfulfillment.ValidationStatusFailed). SetValidationError(payload.ValidationError). Save(ctx) @@ -1201,6 +1418,521 @@ func (ctrl *ProviderController) FulfillOrder(ctx *gin.Context) { u.APIResponse(ctx, http.StatusOK, "success", "Order fulfilled successfully", nil) } +// handleRefundOutcomeFulfillment handles FulfillOrder when order is Refunding (payin insufficient-balance refund path). +// It interprets payload.ValidationStatus as refund outcome: refunded, pending, or failed. +func (ctrl *ProviderController) handleRefundOutcomeFulfillment(ctx *gin.Context, orderID uuid.UUID, payload types.FulfillOrderPayload, fulfillment *ent.PaymentOrderFulfillment, provider *ent.ProviderProfile) { + order := fulfillment.Edges.Order + if order == nil { + u.APIResponse(ctx, http.StatusBadRequest, "error", "Order not found", nil) + return + } + + switch payload.ValidationStatus { + case paymentorderfulfillment.ValidationStatusRefunded: + // Update fulfillment to Refunded (and TxID, PSP if present) + fulfillmentUpdate := fulfillment.Update(). + SetValidationStatus(paymentorderfulfillment.ValidationStatusRefunded). + SetValidationError(payload.ValidationError) + if payload.TxID != "" { + fulfillmentUpdate = fulfillmentUpdate.SetTxID(payload.TxID) + } + if payload.PSP != "" { + fulfillmentUpdate = fulfillmentUpdate.SetPsp(payload.PSP) + } + _, err := fulfillmentUpdate.Save(ctx) + if err != nil { + logger.WithFields(logger.Fields{ + "Error": fmt.Sprintf("%v", err), + "OrderID": orderID.String(), + }).Errorf("Failed to update refund fulfillment to refunded") + u.APIResponse(ctx, http.StatusInternalServerError, "error", "Failed to update order status", nil) + return + } + + // Note: TransactionLog status is immutable in schema; order_refunding log remains. Order status Refunded is the source of truth. + // Set order status to Refunded + _, err = storage.Client.PaymentOrder.UpdateOneID(order.ID).SetStatus(paymentorder.StatusRefunded).Save(ctx) + if err != nil { + logger.WithFields(logger.Fields{ + "Error": fmt.Sprintf("%v", err), + "OrderID": orderID.String(), + }).Errorf("Failed to update order status to refunded") + u.APIResponse(ctx, http.StatusInternalServerError, "error", "Failed to update order status", nil) + return + } + + // Release reserved token balance + if order.Edges.Token != nil && order.Edges.Provider != nil { + totalCryptoReserved := order.Amount.Add(order.SenderFee) + if relErr := ctrl.balanceService.ReleaseTokenBalance(ctx, order.Edges.Provider.ID, order.Edges.Token.ID, totalCryptoReserved, nil); relErr != nil { + logger.Errorf("Failed to release token balance for refunded order (order %s): %v", orderID, relErr) + } + } + u.APIResponse(ctx, http.StatusOK, "success", "Refund completed", nil) + return + case paymentorderfulfillment.ValidationStatusFailed: + _, err := fulfillment.Update(). + SetValidationStatus(paymentorderfulfillment.ValidationStatusFailed). + SetValidationError(payload.ValidationError). + Save(ctx) + if err != nil { + logger.WithFields(logger.Fields{ + "Error": fmt.Sprintf("%v", err), + "OrderID": orderID.String(), + }).Errorf("Failed to update refund fulfillment to failed") + u.APIResponse(ctx, http.StatusInternalServerError, "error", "Failed to update order status", nil) + return + } + // Set order to Fulfilled (terminal state) and release reserved token balance (consistent with handlePayinFulfillment and aggregator refund-failure handling) + _, err = storage.Client.PaymentOrder.UpdateOneID(order.ID).SetStatus(paymentorder.StatusFulfilled).Save(ctx) + if err != nil { + logger.WithFields(logger.Fields{ + "Error": fmt.Sprintf("%v", err), + "OrderID": orderID.String(), + }).Errorf("Failed to update order status for refund failure") + u.APIResponse(ctx, http.StatusInternalServerError, "error", "Failed to update order status", nil) + return + } + if order.Edges.Token != nil && order.Edges.Provider != nil { + totalCryptoReserved := order.Amount.Add(order.SenderFee) + if relErr := ctrl.balanceService.ReleaseTokenBalance(ctx, order.Edges.Provider.ID, order.Edges.Token.ID, totalCryptoReserved, nil); relErr != nil { + logger.Errorf("Failed to release token balance for refund-failed order (order %s): %v", orderID, relErr) + } + } + u.APIResponse(ctx, http.StatusOK, "success", "Refund failed", nil) + return + case paymentorderfulfillment.ValidationStatusPending: + _, err := fulfillment.Update(). + SetValidationStatus(paymentorderfulfillment.ValidationStatusPending). + SetValidationError(payload.ValidationError). + Save(ctx) + if err != nil { + logger.WithFields(logger.Fields{ + "Error": fmt.Sprintf("%v", err), + "OrderID": orderID.String(), + }).Errorf("Failed to update refund fulfillment to pending") + u.APIResponse(ctx, http.StatusInternalServerError, "error", "Failed to update order status", nil) + return + } + u.APIResponse(ctx, http.StatusOK, "success", "Refund pending", nil) + return + default: + u.APIResponse(ctx, http.StatusBadRequest, "error", "Invalid validation status for refund outcome", nil) + return + } +} + +// handlePayinFulfillment handles payin (onramp) order fulfillment +func (ctrl *ProviderController) handlePayinFulfillment(ctx *gin.Context, orderID uuid.UUID, payload types.FulfillOrderPayload, fulfillment *ent.PaymentOrderFulfillment, provider *ent.ProviderProfile) { + // Validate authorization is provided for payin orders + if payload.Authorization == nil { + u.APIResponse(ctx, http.StatusBadRequest, "error", "Authorization is required for payin orders", types.ErrorData{ + Field: "Authorization", + Message: "EIP-7702 SetCodeAuthorization is required for payin fulfillment", + }) + return + } + + // Only proceed to settlement when validation succeeded; reject failed/pending like handlePayoutFulfillment + switch payload.ValidationStatus { + case paymentorderfulfillment.ValidationStatusFailed: + _, err := fulfillment.Update(). + SetValidationStatus(paymentorderfulfillment.ValidationStatusFailed). + SetValidationError(payload.ValidationError). + Save(ctx) + if err != nil { + logger.Errorf("Failed to update payin fulfillment to failed: %v", err) + u.APIResponse(ctx, http.StatusInternalServerError, "error", "Failed to update order status", nil) + return + } + // Set order status to Fulfilled so state is consistent with released balance (match payout flow) + if fulfillment.Edges.Order != nil { + _, err = storage.Client.PaymentOrder.UpdateOneID(fulfillment.Edges.Order.ID).SetStatus(paymentorder.StatusFulfilled).Save(ctx) + if err != nil { + logger.Errorf("Failed to update order status for payin validation failure (order %s): %v", orderID, err) + u.APIResponse(ctx, http.StatusInternalServerError, "error", "Failed to update order status", nil) + return + } + } + // Release reserved token balance (provider reserved when they accepted) + if fulfillment.Edges.Order != nil && fulfillment.Edges.Order.Edges.Token != nil && fulfillment.Edges.Order.Edges.Provider != nil { + totalCryptoReserved := fulfillment.Edges.Order.Amount.Add(fulfillment.Edges.Order.SenderFee) + if relErr := ctrl.balanceService.ReleaseTokenBalance(ctx, fulfillment.Edges.Order.Edges.Provider.ID, fulfillment.Edges.Order.Edges.Token.ID, totalCryptoReserved, nil); relErr != nil { + logger.Errorf("Failed to release token balance for payin validation failure (order %s): %v", orderID, relErr) + } + } + u.APIResponse(ctx, http.StatusOK, "success", "Fulfillment validation failed", nil) + return + case paymentorderfulfillment.ValidationStatusSuccess: + // Proceed to settlement below + default: + // Pending or unknown: do not attempt settlement + u.APIResponse(ctx, http.StatusBadRequest, "error", "Fulfillment must have validation status success to settle payin order", nil) + return + } + + // Fetch order with token, network, provider, and sender profile (prepareSettleInCallData needs SenderProfile.ID) + orderWithDetails, err := storage.Client.PaymentOrder. + Query(). + Where(paymentorder.IDEQ(orderID)). + WithToken(func(tq *ent.TokenQuery) { + tq.WithNetwork() + }). + WithProvider(). + WithSenderProfile(). + Only(ctx) + if err != nil { + logger.Errorf("Failed to fetch order details: %v", err) + u.APIResponse(ctx, http.StatusInternalServerError, "error", "Failed to fetch order details", nil) + return + } + + // Derive currency from order's institution (provider can have same token for multiple fiat currencies) + institution, err := u.GetInstitutionByCode(ctx, orderWithDetails.Institution, true) + if err != nil { + logger.Errorf("Failed to fetch institution for payin settlement: %v", err) + u.APIResponse(ctx, http.StatusInternalServerError, "error", "Failed to fetch order institution", nil) + return + } + if institution == nil || institution.Edges.FiatCurrency == nil { + logger.Errorf("Order institution or fiat currency not found: %s", orderWithDetails.Institution) + u.APIResponse(ctx, http.StatusInternalServerError, "error", "Order institution not configured", nil) + return + } + currencyCode := institution.Edges.FiatCurrency.Code + + // Get provider payout address + providerOrderToken, err := storage.Client.ProviderOrderToken. + Query(). + Where( + providerordertoken.HasProviderWith(providerprofile.IDEQ(provider.ID)), + providerordertoken.HasTokenWith(token.IDEQ(orderWithDetails.Edges.Token.ID)), + providerordertoken.NetworkEQ(orderWithDetails.Edges.Token.Edges.Network.Identifier), + providerordertoken.HasCurrencyWith(fiatcurrency.CodeEQ(currencyCode)), + ). + Only(ctx) + if err != nil { + logger.Errorf("Failed to fetch provider order token: %v", err) + u.APIResponse(ctx, http.StatusInternalServerError, "error", "Failed to fetch provider configuration", nil) + return + } + + if providerOrderToken.PayoutAddress == "" { + u.APIResponse(ctx, http.StatusBadRequest, "error", "Provider payout address not configured", nil) + return + } + + // Generate Gateway order ID for settleIn: keccak256(abi.encode(payoutAddress, aggregatorAddress, paymentOrderID, chainID)) + if cryptoConf.AggregatorAccountEVM == "" { + logger.Errorf("Aggregator EVM address not configured") + u.APIResponse(ctx, http.StatusInternalServerError, "error", "Aggregator not configured", nil) + return + } + gatewayOrderID, err := gatewayOrderIDFromEncode( + providerOrderToken.PayoutAddress, + cryptoConf.AggregatorAccountEVM, + orderID, + orderWithDetails.Edges.Token.Edges.Network.ChainID, + ) + if err != nil { + logger.Errorf("Failed to generate gateway order ID: %v", err) + u.APIResponse(ctx, http.StatusInternalServerError, "error", "Failed to generate order ID", nil) + return + } + + // Prepare settleIn call data + // settleIn(_orderId, _token, _amount, _senderFeeRecipient, _senderFee, _recipient, _rate) + settleInData, err := ctrl.prepareSettleInCallData(ctx, orderWithDetails, gatewayOrderID) + if err != nil { + logger.Errorf("Failed to prepare settleIn call data: %v", err) + u.APIResponse(ctx, http.StatusInternalServerError, "error", "Failed to prepare settlement data", nil) + return + } + + // Atomic settlement submission: create marker and transition to Settling in a single conditional transaction + tx, err := storage.Client.Tx(ctx) + if err != nil { + logger.Errorf("Failed to start transaction: %v", err) + u.APIResponse(ctx, http.StatusInternalServerError, "error", "Failed to update order status", nil) + return + } + transactionLog, err := tx.TransactionLog. + Create(). + SetStatus(transactionlog.StatusOrderSettling). + SetGatewayID(gatewayOrderID). + SetNetwork(orderWithDetails.Edges.Token.Edges.Network.Identifier). + Save(ctx) + if err != nil { + _ = tx.Rollback() + logger.Errorf("Failed to create transaction log: %v", err) + u.APIResponse(ctx, http.StatusInternalServerError, "error", "Failed to update order status", nil) + return + } + // Conditional update: only transition if order is still Fulfilling (atomic guard) + updatedCount, err := tx.PaymentOrder. + Update(). + Where( + paymentorder.IDEQ(orderID), + paymentorder.StatusEQ(paymentorder.StatusFulfilling), + ). + SetStatus(paymentorder.StatusSettling). + SetGatewayID(gatewayOrderID). + AddTransactions(transactionLog). + Save(ctx) + if err != nil { + _ = tx.Rollback() + logger.Errorf("Failed to update order status: %v", err) + u.APIResponse(ctx, http.StatusInternalServerError, "error", "Failed to update order status", nil) + return + } + if updatedCount == 0 { + _ = tx.Rollback() + u.APIResponse(ctx, http.StatusOK, "success", "Settlement already submitted or completed", nil) + return + } + if err := tx.Commit(); err != nil { + logger.Errorf("Failed to commit transaction: %v", err) + u.APIResponse(ctx, http.StatusInternalServerError, "error", "Failed to update order status", nil) + return + } + + // Execute EIP-7702 transaction (after marker is persisted so retries are idempotent) + if err := ctrl.executePayinSettlement(ctx, orderWithDetails, payload.Authorization, settleInData); err != nil { + logger.Errorf("Failed to execute payin settlement: %v", err) + // Compensating update: revert order status and remove settlement log so provider can retry + txCompensate, txErr := storage.Client.Tx(ctx) + if txErr != nil { + logger.Errorf("Failed to start compensating transaction: %v", txErr) + u.APIResponse(ctx, http.StatusInternalServerError, "error", "Failed to execute settlement", nil) + return + } + _, txErr = txCompensate.PaymentOrder. + UpdateOneID(orderID). + SetStatus(paymentorder.StatusFulfilling). + RemoveTransactions(transactionLog). + Save(ctx) + if txErr != nil { + _ = txCompensate.Rollback() + logger.Errorf("Failed to revert order status on settlement error: %v", txErr) + u.APIResponse(ctx, http.StatusInternalServerError, "error", "Failed to execute settlement", nil) + return + } + txErr = txCompensate.TransactionLog.DeleteOneID(transactionLog.ID).Exec(ctx) + if txErr != nil { + _ = txCompensate.Rollback() + logger.Errorf("Failed to remove settlement log on settlement error: %v", txErr) + u.APIResponse(ctx, http.StatusInternalServerError, "error", "Failed to execute settlement", nil) + return + } + if commitErr := txCompensate.Commit(); commitErr != nil { + logger.Errorf("Failed to commit compensating transaction: %v", commitErr) + u.APIResponse(ctx, http.StatusInternalServerError, "error", "Failed to execute settlement", nil) + return + } + u.APIResponse(ctx, http.StatusInternalServerError, "error", "Failed to execute settlement", nil) + return + } + + // Update fulfillment status and release reserved token balance + tx2, err := storage.Client.Tx(ctx) + if err != nil { + logger.Errorf("Failed to start transaction: %v", err) + u.APIResponse(ctx, http.StatusInternalServerError, "error", "Failed to update order status", nil) + return + } + _, err = tx2.PaymentOrderFulfillment. + UpdateOneID(fulfillment.ID). + SetValidationStatus(paymentorderfulfillment.ValidationStatusSuccess). + Save(ctx) + if err != nil { + _ = tx2.Rollback() + logger.Errorf("Failed to update fulfillment status: %v", err) + u.APIResponse(ctx, http.StatusInternalServerError, "error", "Failed to update order status", nil) + return + } + if err := tx2.Commit(); err != nil { + logger.Errorf("Failed to commit transaction: %v", err) + u.APIResponse(ctx, http.StatusInternalServerError, "error", "Failed to update order status", nil) + return + } + totalCryptoReserved := orderWithDetails.Amount.Add(orderWithDetails.SenderFee) + err = ctrl.balanceService.ReleaseTokenBalance(ctx, provider.ID, orderWithDetails.Edges.Token.ID, totalCryptoReserved, nil) + if err != nil { + logger.Errorf("Failed to release token balance: %v", err) + // Don't fail the entire operation if balance release fails - log and continue + } + + u.APIResponse(ctx, http.StatusOK, "success", "Order submitted for settlement", nil) +} + +// gatewayOrderIDFromEncode returns keccak256(abi.encode(payoutAddress, aggregatorAddress, paymentOrderID, chainID)) as hex. +func gatewayOrderIDFromEncode(payoutAddress, aggregatorAddress string, paymentOrderID uuid.UUID, chainID int64) (string, error) { + addressType, err := abi.NewType("address", "", nil) + if err != nil { + return "", err + } + bytes32Type, err := abi.NewType("bytes32", "", nil) + if err != nil { + return "", err + } + uint256Type, err := abi.NewType("uint256", "", nil) + if err != nil { + return "", err + } + arguments := abi.Arguments{ + {Type: addressType}, + {Type: addressType}, + {Type: bytes32Type}, + {Type: uint256Type}, + } + var paymentOrderIDBytes32 [32]byte + copy(paymentOrderIDBytes32[:], paymentOrderID[:]) // 16 UUID bytes, zero-padded to 32 + packed, err := arguments.Pack( + ethcommon.HexToAddress(payoutAddress), + ethcommon.HexToAddress(aggregatorAddress), + paymentOrderIDBytes32, + big.NewInt(chainID), + ) + if err != nil { + return "", err + } + hash := crypto.Keccak256Hash(packed) + return hash.Hex(), nil +} + +// prepareSettleInCallData prepares the call data for Gateway settleIn method +func (ctrl *ProviderController) prepareSettleInCallData(ctx *gin.Context, order *ent.PaymentOrder, gatewayOrderID string) ([]byte, error) { + if order.Edges.SenderProfile == nil { + return nil, fmt.Errorf("order has no sender profile") + } + if order.Edges.Token == nil { + return nil, fmt.Errorf("order has no token") + } + + // Use current Gateway contract bindings; plan expects regenerated bindings if ABI changes + gatewayABI, err := abi.JSON(strings.NewReader(contracts.GatewayMetaData.ABI)) + if err != nil { + return nil, fmt.Errorf("failed to parse Gateway ABI: %w", err) + } + + // Get fee address (sender fee recipient) + senderOrderToken, err := storage.Client.SenderOrderToken. + Query(). + Where( + senderordertoken.HasTokenWith(token.IDEQ(order.Edges.Token.ID)), + senderordertoken.HasSenderWith(senderprofile.IDEQ(order.Edges.SenderProfile.ID)), + ). + Only(ctx) + if err != nil { + return nil, fmt.Errorf("failed to fetch sender order token: %w", err) + } + + // Get recipient address (onramp: crypto destination for settleIn) + recipientAddress := order.RefundOrRecipientAddress + if recipientAddress == "" { + return nil, fmt.Errorf("recipient address not set on order") + } + + // Convert amounts to big.Int + amountBig := u.ToSubunit(order.Amount, order.Edges.Token.Decimals) + senderFeeBig := u.ToSubunit(order.SenderFee, order.Edges.Token.Decimals) + + // Convert rate to uint96 (rate is stored as decimal, need to convert to basis points * 100) + // Rate format: Gateway expects rate as uint96 where 100 = 1.00 (local transfer) + // For FX transfers, rate represents the conversion rate scaled appropriately + rateBig := order.Rate.Mul(decimal.NewFromInt(100)).BigInt() + if rateBig.Cmp(big.NewInt(0)) == 0 { + rateBig = big.NewInt(100) // Default to 1.00 (100 basis points) + } + + // Generate Gateway order ID bytes + orderIDBytes, err := hex.DecodeString(strings.TrimPrefix(gatewayOrderID, "0x")) + if err != nil { + return nil, fmt.Errorf("failed to decode gateway order ID: %w", err) + } + var orderIDByte32 [32]byte + copy(orderIDByte32[:], orderIDBytes) + + // Pack settleIn call data + // settleIn(bytes32 _orderId, address _token, uint256 _amount, address _senderFeeRecipient, uint96 _senderFee, address _recipient, uint96 _rate) + data, err := gatewayABI.Pack( + "settleIn", + orderIDByte32, + ethcommon.HexToAddress(order.Edges.Token.ContractAddress), + amountBig, + ethcommon.HexToAddress(senderOrderToken.FeeAddress), + senderFeeBig, + ethcommon.HexToAddress(recipientAddress), + rateBig, + ) + if err != nil { + return nil, fmt.Errorf("failed to pack settleIn ABI: %w", err) + } + + return data, nil +} + +// executePayinSettlement executes the EIP-7702 transaction for payin settlement via Engine write/transaction with authorizationList. +func (ctrl *ProviderController) executePayinSettlement(ctx *gin.Context, order *ent.PaymentOrder, authorization *types.SetCodeAuthorization, settleInData []byte) error { + if authorization == nil { + return fmt.Errorf("authorization is required") + } + if cryptoConf.AggregatorAccountEVM == "" { + return fmt.Errorf("aggregator EVM address not configured") + } + gatewayAddress := order.Edges.Token.Edges.Network.GatewayContractAddress + if gatewayAddress == "" { + return fmt.Errorf("gateway contract address not set for network") + } + orderChainID := order.Edges.Token.Edges.Network.ChainID + if fmt.Sprintf("%d", orderChainID) != authorization.ChainID { + return fmt.Errorf("authorization chain ID does not match order network") + } + + // Validate V and derive yParity: only 0, 1, 27, 28 are accepted (EIP-155 and legacy) + var yParity int + switch authorization.V { + case 0: + yParity = 0 + case 1: + yParity = 1 + case 27: + yParity = 0 + case 28: + yParity = 1 + default: + return fmt.Errorf("authorization V must be 0, 1, 27, or 28; got %d", authorization.V) + } + + authEntry := map[string]interface{}{ + "chainId": authorization.ChainID, + "address": authorization.Address, + "nonce": authorization.Nonce, + "yParity": yParity, + "r": authorization.R, + "s": authorization.S, + } + authorizationList := []map[string]interface{}{authEntry} + + params := []map[string]interface{}{ + {"authorizationList": authorizationList}, + { + "to": gatewayAddress, + "data": fmt.Sprintf("0x%x", settleInData), + "value": "0", + }, + } + + engineSvc := services.NewEngineService() + chainID := order.Edges.Token.Edges.Network.ChainID + reqCtx := ctx.Request.Context() + _, err := engineSvc.SendTransactionBatch(reqCtx, chainID, cryptoConf.AggregatorAccountEVM, params) + if err != nil { + return fmt.Errorf("send transaction with authorizationList: %w", err) + } + return nil +} + // CancelOrder controller cancels an order func (ctrl *ProviderController) CancelOrder(ctx *gin.Context) { var payload types.CancelOrderPayload @@ -1457,18 +2189,21 @@ func (ctrl *ProviderController) GetMarketRate(ctx *gin.Context) { var response *types.MarketRateResponse if !strings.EqualFold(tokenObj.BaseCurrency, currency.Code) { - deviation := currency.MarketRate.Mul(orderConf.PercentDeviationFromMarketRate.Div(decimal.NewFromInt(100))) + // Use sell rate for deviation calculation (offramp perspective) + deviation := currency.MarketSellRate.Mul(orderConf.PercentDeviationFromMarketRate.Div(decimal.NewFromInt(100))) response = &types.MarketRateResponse{ - MarketRate: currency.MarketRate, - MinimumRate: currency.MarketRate.Sub(deviation), - MaximumRate: currency.MarketRate.Add(deviation), + MarketBuyRate: currency.MarketBuyRate, + MarketSellRate: currency.MarketSellRate, + MinimumRate: currency.MarketSellRate.Sub(deviation), + MaximumRate: currency.MarketSellRate.Add(deviation), } } else { response = &types.MarketRateResponse{ - MarketRate: decimal.NewFromInt(1), - MinimumRate: decimal.NewFromInt(1), - MaximumRate: decimal.NewFromInt(1), + MarketBuyRate: decimal.NewFromInt(1), + MarketSellRate: decimal.NewFromInt(1), + MinimumRate: decimal.NewFromInt(1), + MaximumRate: decimal.NewFromInt(1), } } @@ -1592,7 +2327,7 @@ func (ctrl *ProviderController) Stats(ctx *gin.Context) { u.APIResponse(ctx, http.StatusInternalServerError, "error", "Failed to fetch provider stats", nil) return } - localStablecoinVolume[0].Sum = localStablecoinVolume[0].Sum.Div(fiatCurrency.MarketRate) + localStablecoinVolume[0].Sum = localStablecoinVolume[0].Sum.Div(fiatCurrency.MarketSellRate) } var totalFiatVolume decimal.Decimal @@ -1827,6 +2562,228 @@ func (ctrl *ProviderController) GetPaymentOrderByID(ctx *gin.Context) { }) } +// GetPaymentOrderByIDV2 returns a single payment order in v2 API schema (providerAccount, source, destination). +func (ctrl *ProviderController) GetPaymentOrderByIDV2(ctx *gin.Context) { + orderID := ctx.Param("id") + id, err := uuid.Parse(orderID) + if err != nil { + logger.WithFields(logger.Fields{"Error": fmt.Sprintf("%v", err), "Order ID": orderID}).Errorf("Failed to parse order ID: %v", err) + u.APIResponse(ctx, http.StatusBadRequest, "error", "Invalid order ID", nil) + return + } + providerCtx, ok := ctx.Get("provider") + if !ok { + u.APIResponse(ctx, http.StatusUnauthorized, "error", "Invalid API key or token", nil) + return + } + provider := providerCtx.(*ent.ProviderProfile) + + paymentOrder, err := storage.Client.PaymentOrder. + Query(). + Where( + paymentorder.IDEQ(id), + paymentorder.HasProviderWith(providerprofile.IDEQ(provider.ID)), + ). + WithProvider(). + WithToken(func(tq *ent.TokenQuery) { tq.WithNetwork() }). + WithTransactions(). + Only(ctx) + if err != nil { + logger.WithFields(logger.Fields{"Error": fmt.Sprintf("%v", err), "Order ID": orderID}).Errorf("Failed to fetch payment order: %v", err) + u.APIResponse(ctx, http.StatusNotFound, "error", "Payment order not found", nil) + return + } + + institution, err := storage.Client.Institution. + Query(). + Where(institution.CodeEQ(paymentOrder.Institution)). + WithFiatCurrency(). + Only(ctx) + if err != nil { + logger.Errorf("Failed to fetch institution: %v", err) + u.APIResponse(ctx, http.StatusInternalServerError, "error", "Failed to fetch payment order", nil) + return + } + + var transactionLogs []types.TransactionLog + for _, tx := range paymentOrder.Edges.Transactions { + transactionLogs = append(transactionLogs, types.TransactionLog{ + ID: tx.ID, + GatewayId: tx.GatewayID, + Status: tx.Status, + TxHash: tx.TxHash, + CreatedAt: tx.CreatedAt, + }) + } + var otcExpiry *time.Time + if paymentOrder.OrderType == paymentorder.OrderTypeOtc { + switch paymentOrder.Status { + case paymentorder.StatusPending: + exp := paymentOrder.UpdatedAt.Add(orderConf.OrderRequestValidityOtc) + otcExpiry = &exp + case paymentorder.StatusFulfilling: + exp := paymentOrder.UpdatedAt.Add(orderConf.OrderFulfillmentValidityOtc) + otcExpiry = &exp + } + } + resp := u.BuildV2PaymentOrderGetResponse(paymentOrder, institution, transactionLogs, paymentOrder.CancellationReasons, otcExpiry) + u.APIResponse(ctx, http.StatusOK, "success", "The order has been successfully retrieved", resp) +} + +// GetPaymentOrdersV2 returns a list of payment orders in v2 API schema (list only; currency required like v1). +func (ctrl *ProviderController) GetPaymentOrdersV2(ctx *gin.Context) { + providerCtx, ok := ctx.Get("provider") + if !ok { + u.APIResponse(ctx, http.StatusUnauthorized, "error", "Invalid API key or token", nil) + return + } + provider := providerCtx.(*ent.ProviderProfile) + ctrl.handleListPaymentOrdersV2(ctx, provider) +} + +// handleListPaymentOrdersV2 handles v2 payment order listing with pagination and v2 response shape (currency required). +func (ctrl *ProviderController) handleListPaymentOrdersV2(ctx *gin.Context, provider *ent.ProviderProfile) { + page, offset, pageSize := u.Paginate(ctx) + ordering := ctx.Query("ordering") + order := ent.Desc(paymentorder.FieldCreatedAt) + if ordering == "asc" { + order = ent.Asc(paymentorder.FieldCreatedAt) + } + + paymentOrderQuery := storage.Client.PaymentOrder.Query().Where( + paymentorder.HasProviderWith(providerprofile.IDEQ(provider.ID)), + ) + + currency := ctx.Query("currency") + if currency == "" { + u.APIResponse(ctx, http.StatusBadRequest, "error", "Currency is required", nil) + return + } + currencyExists, err := provider.QueryProviderBalances(). + Where(providerbalances.HasFiatCurrencyWith(fiatcurrency.CodeEQ(currency))). + Exist(ctx) + if err != nil { + logger.Errorf("error checking provider currency: %v", err) + u.APIResponse(ctx, http.StatusInternalServerError, "error", "Failed to check currency", nil) + return + } + if !currencyExists { + u.APIResponse(ctx, http.StatusBadRequest, "error", "Currency not found", nil) + return + } + institutionCodes, err := storage.Client.Institution. + Query(). + Where(institution.HasFiatCurrencyWith(fiatcurrency.CodeEQ(currency))). + Select(institution.FieldCode). + Strings(ctx) + if err != nil { + logger.Errorf("error fetching institution codes: %v", err) + u.APIResponse(ctx, http.StatusInternalServerError, "error", "Failed to fetch orders", nil) + return + } + paymentOrderQuery = paymentOrderQuery.Where(paymentorder.InstitutionIn(institutionCodes...)) + + statusMap := map[string]paymentorder.Status{ + "pending": paymentorder.StatusPending, + "validated": paymentorder.StatusValidated, + "fulfilling": paymentorder.StatusFulfilling, + "fulfilled": paymentorder.StatusFulfilled, + "cancelled": paymentorder.StatusCancelled, + "settling": paymentorder.StatusSettling, + "settled": paymentorder.StatusSettled, + "refunding": paymentorder.StatusRefunding, + "refunded": paymentorder.StatusRefunded, + } + if status, ok := statusMap[ctx.Query("status")]; ok { + paymentOrderQuery = paymentOrderQuery.Where(paymentorder.StatusEQ(status)) + } + + count, err := paymentOrderQuery.Count(ctx) + if err != nil { + logger.Errorf("error: %v", err) + u.APIResponse(ctx, http.StatusInternalServerError, "error", "Failed to fetch orders", nil) + return + } + + paymentOrders, err := paymentOrderQuery. + WithProvider(). + WithToken(func(tq *ent.TokenQuery) { tq.WithNetwork() }). + WithTransactions(). + Limit(pageSize). + Offset(offset). + Order(order). + All(ctx) + if err != nil { + logger.Errorf("error fetching orders: %v", err) + u.APIResponse(ctx, http.StatusInternalServerError, "error", "Failed to fetch orders", nil) + return + } + + orders, err := ctrl.buildV2ProviderOrderGetResponses(ctx, paymentOrders) + if err != nil { + logger.Errorf("error: %v", err) + u.APIResponse(ctx, http.StatusInternalServerError, "error", "Failed to fetch orders", nil) + return + } + + u.APIResponse(ctx, http.StatusOK, "success", "Orders successfully retrieved", types.V2PaymentOrderListResponse{ + Page: page, + PageSize: pageSize, + TotalRecords: count, + Orders: orders, + }) +} + +// buildV2ProviderOrderGetResponses converts payment orders to v2 get response list with OTC expiry and cancellation reasons. +func (ctrl *ProviderController) buildV2ProviderOrderGetResponses(ctx *gin.Context, paymentOrders []*ent.PaymentOrder) ([]types.V2PaymentOrderGetResponse, error) { + if len(paymentOrders) == 0 { + return nil, nil + } + codes := make(map[string]bool) + for _, po := range paymentOrders { + codes[po.Institution] = true + } + codeSlice := make([]string, 0, len(codes)) + for c := range codes { + codeSlice = append(codeSlice, c) + } + institutions, err := storage.Client.Institution. + Query(). + Where(institution.CodeIn(codeSlice...)). + WithFiatCurrency(). + All(ctx) + if err != nil { + return nil, err + } + instMap := make(map[string]*ent.Institution) + for _, inst := range institutions { + instMap[inst.Code] = inst + } + + out := make([]types.V2PaymentOrderGetResponse, 0, len(paymentOrders)) + for _, po := range paymentOrders { + inst, _ := instMap[po.Institution] + var txLogs []types.TransactionLog + for _, tx := range po.Edges.Transactions { + txLogs = append(txLogs, types.TransactionLog{ID: tx.ID, GatewayId: tx.GatewayID, Status: tx.Status, TxHash: tx.TxHash, CreatedAt: tx.CreatedAt}) + } + var otcExpiry *time.Time + if po.OrderType == paymentorder.OrderTypeOtc { + switch po.Status { + case paymentorder.StatusPending: + exp := po.UpdatedAt.Add(orderConf.OrderRequestValidityOtc) + otcExpiry = &exp + case paymentorder.StatusFulfilling: + exp := po.UpdatedAt.Add(orderConf.OrderFulfillmentValidityOtc) + otcExpiry = &exp + } + } + resp := u.BuildV2PaymentOrderGetResponse(po, inst, txLogs, po.CancellationReasons, otcExpiry) + out = append(out, *resp) + } + return out, nil +} + // UpdateProviderBalance handles the update of provider balance func (ctrl *ProviderController) UpdateProviderBalance(ctx *gin.Context) { // Extract provider from HMAC middleware context diff --git a/controllers/provider/provider_test.go b/controllers/provider/provider_test.go index e6008ef3f..358f40711 100644 --- a/controllers/provider/provider_test.go +++ b/controllers/provider/provider_test.go @@ -959,7 +959,7 @@ func TestProvider(t *testing.T) { err = json.Unmarshal(res.Body.Bytes(), &response) assert.NoError(t, err) assert.Equal(t, "Rate fetched successfully", response.Message) - assert.Equal(t, "950.0", response.Data.MarketRate.StringFixed(1)) + assert.Equal(t, "950.0", response.Data.MarketSellRate.StringFixed(1)) }) }) diff --git a/controllers/sender/sender.go b/controllers/sender/sender.go index 7464f672f..1bbd37cb5 100644 --- a/controllers/sender/sender.go +++ b/controllers/sender/sender.go @@ -2,6 +2,7 @@ package sender import ( "encoding/csv" + "encoding/json" "fmt" "net/http" "regexp" @@ -23,6 +24,7 @@ import ( tokenEnt "github.com/paycrest/aggregator/ent/token" "github.com/paycrest/aggregator/ent/transactionlog" svc "github.com/paycrest/aggregator/services" + "github.com/paycrest/aggregator/services/balance" orderSvc "github.com/paycrest/aggregator/services/order" "github.com/paycrest/aggregator/services/starknet" "github.com/paycrest/aggregator/storage" @@ -309,7 +311,7 @@ func (ctrl *SenderController) InitiatePaymentOrder(ctx *gin.Context) { }() go func() { - rateResult, err := u.ValidateRate(ctx, token, institutionObj.Edges.FiatCurrency, payload.Amount, payload.Recipient.ProviderID, payload.Network) + rateResult, err := u.ValidateRate(ctx, token, institutionObj.Edges.FiatCurrency, payload.Amount, payload.Recipient.ProviderID, payload.Network, u.RateSideSell) rateChan <- RateResult{rateResult, err} }() @@ -356,7 +358,7 @@ func (ctrl *SenderController) InitiatePaymentOrder(ctx *gin.Context) { return } - amountInUSD := u.CalculatePaymentOrderAmountInUSD(payload.Amount, token, institutionObj) + amountInUSD := u.CalculatePaymentOrderAmountInUSD(payload.Amount, token, institutionObj, paymentorder.DirectionOfframp) // Use order type from ValidateRate result (already determined based on OTC limits) orderType := rateValidationResult.OrderType @@ -513,7 +515,8 @@ func (ctrl *SenderController) InitiatePaymentOrder(ctx *gin.Context) { SetReceiveAddressExpiry(receiveAddressExpiry). SetFeePercent(feePercent). SetFeeAddress(feeAddress). - SetReturnAddress(returnAddress). + SetRefundOrRecipientAddress(returnAddress). + SetDirection(paymentorder.DirectionOfframp). SetReference(payload.Reference). SetOrderType(orderType). SetInstitution(payload.Recipient.Institution). @@ -599,6 +602,7 @@ func (ctrl *SenderController) InitiatePaymentOrder(ctx *gin.Context) { } // InitiatePaymentOrderV2 controller creates a payment order using v2 schema +// Supports both offramp (crypto->fiat) and onramp (fiat->crypto) flows func (ctrl *SenderController) InitiatePaymentOrderV2(ctx *gin.Context) { var payload types.V2PaymentOrderPayload @@ -609,9 +613,61 @@ func (ctrl *SenderController) InitiatePaymentOrderV2(ctx *gin.Context) { return } - // Default amountIn to "crypto" if not provided - if payload.AmountIn == "" { - payload.AmountIn = "crypto" + // amountIn defaulting is done in flow-specific helpers (offramp -> "crypto", onramp -> "fiat") + + // Detect flow type from source and destination + var sourceType, destType struct { + Type string `json:"type"` + } + if err := json.Unmarshal(payload.Source, &sourceType); err != nil { + u.APIResponse(ctx, http.StatusBadRequest, "error", "Failed to validate payload", types.ErrorData{ + Field: "Source", + Message: "Invalid source format", + }) + return + } + if err := json.Unmarshal(payload.Destination, &destType); err != nil { + u.APIResponse(ctx, http.StatusBadRequest, "error", "Failed to validate payload", types.ErrorData{ + Field: "Destination", + Message: "Invalid destination format", + }) + return + } + + // Branch based on flow type + if sourceType.Type == "crypto" && destType.Type == "fiat" { + // Offramp flow + ctrl.initiateOfframpOrderV2(ctx, payload) + } else if sourceType.Type == "fiat" && destType.Type == "crypto" { + // Onramp flow + ctrl.initiateOnrampOrderV2(ctx, payload) + } else { + u.APIResponse(ctx, http.StatusBadRequest, "error", "Failed to validate payload", types.ErrorData{ + Field: "Source/Destination", + Message: fmt.Sprintf("Invalid flow combination: source.type=%s, destination.type=%s. Expected (crypto,fiat) for offramp or (fiat,crypto) for onramp", sourceType.Type, destType.Type), + }) + return + } +} + +// initiateOfframpOrderV2 handles offramp (crypto->fiat) payment order creation +func (ctrl *SenderController) initiateOfframpOrderV2(ctx *gin.Context, payload types.V2PaymentOrderPayload) { + // Unmarshal source and destination + var source types.V2CryptoSource + var destination types.V2FiatDestination + if err := json.Unmarshal(payload.Source, &source); err != nil { + u.APIResponse(ctx, http.StatusBadRequest, "error", "Failed to validate payload", types.ErrorData{ + Field: "Source", + Message: "Invalid crypto source format", + }) + return + } + if err := json.Unmarshal(payload.Destination, &destination); err != nil { + u.APIResponse(ctx, http.StatusBadRequest, "error", "Failed to validate payload", types.ErrorData{ + Field: "Destination", + Message: "Invalid fiat destination format", + }) + return } // Validate mutually exclusive fields: senderFee and senderFeePercent cannot both be provided @@ -654,8 +710,8 @@ func (ctrl *SenderController) InitiatePaymentOrderV2(ctx *gin.Context) { token, err := storage.Client.Token. Query(). Where( - tokenEnt.SymbolEQ(payload.Source.Currency), - tokenEnt.HasNetworkWith(network.IdentifierEQ(payload.Source.PaymentRail)), + tokenEnt.SymbolEQ(source.Currency), + tokenEnt.HasNetworkWith(network.IdentifierEQ(source.Network)), tokenEnt.IsEnabledEQ(true), ). WithNetwork(). @@ -674,18 +730,18 @@ func (ctrl *SenderController) InitiatePaymentOrderV2(ctx *gin.Context) { } // Validate refund address format - if strings.HasPrefix(payload.Source.PaymentRail, "tron") { - if !u.IsValidTronAddress(payload.Source.RefundAddress) { + if strings.HasPrefix(source.Network, "tron") { + if !u.IsValidTronAddress(source.RefundAddress) { u.APIResponse(ctx, http.StatusBadRequest, "error", "Failed to validate payload", types.ErrorData{ Field: "Source", Message: "Invalid Tron refund address", }) return } - } else if strings.HasPrefix(payload.Source.PaymentRail, "starknet") { + } else if strings.HasPrefix(source.Network, "starknet") { // Starknet addresses are 65 or 66 characters (0x + 63-64 hex characters) pattern := `^0x[a-fA-F0-9]{63,64}$` - matched, _ := regexp.MatchString(pattern, payload.Source.RefundAddress) + matched, _ := regexp.MatchString(pattern, source.RefundAddress) if !matched { u.APIResponse(ctx, http.StatusBadRequest, "error", "Failed to validate payload", types.ErrorData{ Field: "Source", @@ -695,7 +751,7 @@ func (ctrl *SenderController) InitiatePaymentOrderV2(ctx *gin.Context) { } } else { // EVM networks (Ethereum, Base, Polygon, etc.) - if !u.IsValidEthereumAddress(payload.Source.RefundAddress) { + if !u.IsValidEthereumAddress(source.RefundAddress) { u.APIResponse(ctx, http.StatusBadRequest, "error", "Failed to validate payload", types.ErrorData{ Field: "Source", Message: "Invalid Ethereum refund address", @@ -736,15 +792,15 @@ func (ctrl *SenderController) InitiatePaymentOrderV2(ctx *gin.Context) { feeAddress := senderOrderToken.FeeAddress returnAddress := senderOrderToken.RefundAddress - // Use refund address from payload if provided - returnAddress = payload.Source.RefundAddress + // Use refund address from source if provided + returnAddress = source.RefundAddress // Validate destination configuration // Validate institution exists institutionObj, err := storage.Client.Institution. Query(). Where( - institution.CodeEQ(payload.Destination.Recipient.Institution), + institution.CodeEQ(destination.Recipient.Institution), ). WithFiatCurrency( func(q *ent.FiatCurrencyQuery) { @@ -765,8 +821,8 @@ func (ctrl *SenderController) InitiatePaymentOrderV2(ctx *gin.Context) { return } - // Validate destination currency matches institution currency - if institutionObj.Edges.FiatCurrency.Code != payload.Destination.Currency { + // Validate destination currency matches institution currency (case-insensitive) + if !strings.EqualFold(institutionObj.Edges.FiatCurrency.Code, destination.Currency) { u.APIResponse(ctx, http.StatusBadRequest, "error", "Failed to validate payload", types.ErrorData{ Field: "Destination", Message: "Destination currency does not match institution currency", @@ -822,10 +878,15 @@ func (ctrl *SenderController) InitiatePaymentOrderV2(ctx *gin.Context) { } accountChan := make(chan AccountResult, 1) go func() { - accountName, err := u.ValidateAccount(ctx, payload.Destination.Recipient.Institution, payload.Destination.Recipient.AccountIdentifier) + accountName, err := u.ValidateAccount(ctx, destination.Recipient.Institution, destination.Recipient.AccountIdentifier) accountChan <- AccountResult{accountName, err} }() + // Default amountIn to "crypto" for offramp if not provided + if payload.AmountIn == "" { + payload.AmountIn = "crypto" + } + // Handle rate logic based on amountIn var cryptoAmount decimal.Decimal var orderRate decimal.Decimal @@ -834,30 +895,72 @@ func (ctrl *SenderController) InitiatePaymentOrderV2(ctx *gin.Context) { amountIn := payload.AmountIn switch amountIn { case "fiat": - // For fiat amounts, convert to crypto first, then ValidateRate will validate with crypto amount - // For direct currency matches (e.g., cNGN->NGN), rate is always 1:1 - isDirectMatch := strings.EqualFold(token.BaseCurrency, currency.Code) - if isDirectMatch { - orderRate = decimal.NewFromInt(1) + // Offramp + amountIn=fiat: user fixes fiat payout. Rate is optional: if provided use it (crypto = fiat/rate); else pick system rate. + if payload.Rate != "" { + providedRate, err := decimal.NewFromString(payload.Rate) + if err != nil { + u.APIResponse(ctx, http.StatusBadRequest, "error", "Failed to validate payload", types.ErrorData{ + Field: "Rate", + Message: "Invalid rate format", + }) + return + } + if providedRate.LessThanOrEqual(decimal.Zero) { + u.APIResponse(ctx, http.StatusBadRequest, "error", "Failed to validate payload", types.ErrorData{ + Field: "Rate", + Message: "Rate must be greater than zero", + }) + return + } + cryptoAmount = amount.Div(providedRate) + rateResult, err := u.ValidateRate(ctx, token, currency, cryptoAmount, destination.ProviderID, source.Network, u.RateSideSell) + if err != nil { + u.APIResponse(ctx, http.StatusBadRequest, "error", "Failed to validate payload", types.ErrorData{ + Field: "Rate", + Message: fmt.Sprintf("Rate validation failed: %s", err.Error()), + }) + return + } + rateValidationResult = rateResult + achievableRate := rateValidationResult.Rate + tolerance := achievableRate.Mul(decimal.NewFromFloat(0.001)) + if providedRate.LessThan(achievableRate.Sub(tolerance)) { + u.APIResponse(ctx, http.StatusBadRequest, "error", "Failed to validate payload", types.ErrorData{ + Field: "Rate", + Message: fmt.Sprintf("Provided rate %s is not achievable. Available rate is %s", providedRate, achievableRate), + }) + return + } + orderRate = providedRate + cryptoAmount = amount.Div(orderRate) } else { - // Use market rate as approximation for non-direct matches - orderRate = currency.MarketRate - } - cryptoAmount = amount.Div(orderRate) - - // ValidateRate expects crypto units, so pass cryptoAmount - rateResult, err := u.ValidateRate(ctx, token, currency, cryptoAmount, payload.Destination.ProviderID, payload.Source.PaymentRail) - if err != nil { - u.APIResponse(ctx, http.StatusBadRequest, "error", "Failed to validate payload", types.ErrorData{ - Field: "Rate", - Message: fmt.Sprintf("Rate validation failed: %s", err.Error()), - }) - return + // No rate provided: pick valid system rate and determine crypto that yields user's fiat amount + isDirectMatch := strings.EqualFold(token.BaseCurrency, currency.Code) + if isDirectMatch { + orderRate = decimal.NewFromInt(1) + } else { + if currency.MarketSellRate.IsZero() { + u.APIResponse(ctx, http.StatusBadRequest, "error", "Failed to validate payload", types.ErrorData{ + Field: "Rate", + Message: "Rate not available for this currency", + }) + return + } + orderRate = currency.MarketSellRate + } + cryptoAmount = amount.Div(orderRate) + rateResult, err := u.ValidateRate(ctx, token, currency, cryptoAmount, destination.ProviderID, source.Network, u.RateSideSell) + if err != nil { + u.APIResponse(ctx, http.StatusBadRequest, "error", "Failed to validate payload", types.ErrorData{ + Field: "Rate", + Message: fmt.Sprintf("Rate validation failed: %s", err.Error()), + }) + return + } + rateValidationResult = rateResult + orderRate = rateValidationResult.Rate + cryptoAmount = amount.Div(orderRate) } - rateValidationResult = rateResult - orderRate = rateValidationResult.Rate - // Recalculate cryptoAmount with the validated rate - cryptoAmount = amount.Div(orderRate) case "crypto": cryptoAmount = amount if payload.Rate != "" { @@ -879,7 +982,7 @@ func (ctrl *SenderController) InitiatePaymentOrderV2(ctx *gin.Context) { } // Validate rate is achievable - rateResult, err := u.ValidateRate(ctx, token, currency, cryptoAmount, payload.Destination.ProviderID, payload.Source.PaymentRail) + rateResult, err := u.ValidateRate(ctx, token, currency, cryptoAmount, destination.ProviderID, source.Network, u.RateSideSell) if err != nil { u.APIResponse(ctx, http.StatusBadRequest, "error", "Failed to validate payload", types.ErrorData{ Field: "Rate", @@ -900,7 +1003,7 @@ func (ctrl *SenderController) InitiatePaymentOrderV2(ctx *gin.Context) { orderRate = providedRate } else { // Fetch rate from ValidateRate - rateResult, err := u.ValidateRate(ctx, token, currency, cryptoAmount, payload.Destination.ProviderID, payload.Source.PaymentRail) + rateResult, err := u.ValidateRate(ctx, token, currency, cryptoAmount, destination.ProviderID, source.Network, u.RateSideSell) if err != nil { u.APIResponse(ctx, http.StatusBadRequest, "error", "Failed to validate payload", types.ErrorData{ Field: "Rate", @@ -930,9 +1033,9 @@ func (ctrl *SenderController) InitiatePaymentOrderV2(ctx *gin.Context) { } // Set account name from validation - payload.Destination.Recipient.AccountName = accountResult.accountName + destination.Recipient.AccountName = accountResult.accountName - amountInUSD := u.CalculatePaymentOrderAmountInUSD(cryptoAmount, token, institutionObj) + amountInUSD := u.CalculatePaymentOrderAmountInUSD(cryptoAmount, token, institutionObj, paymentorder.DirectionOfframp) // Use order type from ValidateRate result orderType := rateValidationResult.OrderType @@ -1010,12 +1113,12 @@ func (ctrl *SenderController) InitiatePaymentOrderV2(ctx *gin.Context) { var receiveAddressSalt []byte var receiveAddressExpiry time.Time - if strings.HasPrefix(payload.Source.PaymentRail, "tron") { + if strings.HasPrefix(source.Network, "tron") { address, salt, err := ctrl.receiveAddressService.CreateTronAddress(ctx) if err != nil { logger.WithFields(logger.Fields{ "error": err, - "network": payload.Source.PaymentRail, + "network": source.Network, }).Errorf("Failed to create receive address") u.APIResponse(ctx, http.StatusInternalServerError, "error", "Failed to initiate payment order", types.ErrorData{ Field: "Source", @@ -1026,11 +1129,11 @@ func (ctrl *SenderController) InitiatePaymentOrderV2(ctx *gin.Context) { receiveAddress = address receiveAddressSalt = salt receiveAddressExpiry = time.Now().Add(orderConf.ReceiveAddressValidity) - } else if strings.HasPrefix(payload.Source.PaymentRail, "starknet") { + } else if strings.HasPrefix(source.Network, "starknet") { if ctrl.starknetClient == nil { logger.WithFields(logger.Fields{ "error": "Starknet client not initialized -- disable Starknet tokens if not in use", - "network": payload.Source.PaymentRail, + "network": source.Network, }).Errorf("Failed to create receive address") u.APIResponse(ctx, http.StatusInternalServerError, "error", "Failed to initiate payment order", types.ErrorData{ Field: "Source", @@ -1042,7 +1145,7 @@ func (ctrl *SenderController) InitiatePaymentOrderV2(ctx *gin.Context) { if err != nil { logger.WithFields(logger.Fields{ "error": err, - "network": payload.Source.PaymentRail, + "network": source.Network, }).Errorf("Failed to create receive address") u.APIResponse(ctx, http.StatusInternalServerError, "error", "Failed to initiate payment order", types.ErrorData{ Field: "Source", @@ -1050,34 +1153,599 @@ func (ctrl *SenderController) InitiatePaymentOrderV2(ctx *gin.Context) { }) return } - receiveAddress = address - receiveAddressSalt = salt - receiveAddressExpiry = time.Now().Add(orderConf.ReceiveAddressValidity) - } else { - // Generate unique label for smart address - uniqueLabel := fmt.Sprintf("payment_order_%d_%s", time.Now().UnixNano(), uuid.New().String()[:8]) - address, err := ctrl.receiveAddressService.CreateSmartAddress(ctx, uniqueLabel) + receiveAddress = address + receiveAddressSalt = salt + receiveAddressExpiry = time.Now().Add(orderConf.ReceiveAddressValidity) + } else { + // Generate unique label for smart address + uniqueLabel := fmt.Sprintf("payment_order_%d_%s", time.Now().UnixNano(), uuid.New().String()[:8]) + address, err := ctrl.receiveAddressService.CreateSmartAddress(ctx, uniqueLabel) + if err != nil { + logger.WithFields(logger.Fields{ + "error": err, + "network": source.Network, + }).Errorf("Failed to create receive address") + u.APIResponse(ctx, http.StatusInternalServerError, "error", "Failed to initiate payment order", types.ErrorData{ + Field: "Source", + Message: fmt.Sprintf("%s currently not available", source.Network), + }) + return + } + receiveAddress = address + receiveAddressExpiry = time.Now().Add(orderConf.ReceiveAddressValidity) + } + + // Set extended expiry for private orders (10x normal validity) + if strings.HasPrefix(destination.Recipient.Memo, "P#P") { + receiveAddressExpiry = time.Now().Add(10 * orderConf.ReceiveAddressValidity) + } + + // Create payment order in a transaction + tx, err := storage.Client.Tx(ctx) + if err != nil { + logger.Errorf("error: %v", err) + u.APIResponse(ctx, http.StatusInternalServerError, "error", "Failed to initiate payment order", nil) + return + } + + // Create transaction log + transactionLog, err := tx.TransactionLog. + Create(). + SetStatus(transactionlog.StatusOrderInitiated). + SetNetwork(token.Edges.Network.Identifier). + Save(ctx) + if err != nil { + logger.Errorf("error: %v", err) + u.APIResponse(ctx, http.StatusInternalServerError, "error", "Failed to initiate payment order", nil) + _ = tx.Rollback() + return + } + + // Build metadata with KYC and recipient metadata + metadata := make(map[string]interface{}) + if destination.Recipient.Metadata != nil { + metadata["recipientMetadata"] = destination.Recipient.Metadata + } + // KYC is now nested in source and destination + if source.KYC != nil { + metadata["sourceKyc"] = source.KYC + } + if destination.KYC != nil { + metadata["destinationKyc"] = destination.KYC + } + if destination.Country != "" { + metadata["country"] = destination.Country + } + + // Create payment order + paymentOrderBuilder := tx.PaymentOrder. + Create(). + SetSenderProfile(sender). + SetAmount(cryptoAmount). + SetAmountInUsd(amountInUSD). + SetNetworkFee(token.Edges.Network.Fee). + SetSenderFee(senderFee). + SetToken(token). + SetRate(orderRate). + SetReceiveAddress(receiveAddress). + SetReceiveAddressExpiry(receiveAddressExpiry). + SetFeePercent(feePercent). + SetFeeAddress(feeAddress). + SetRefundOrRecipientAddress(returnAddress). + SetDirection(paymentorder.DirectionOfframp). + SetReference(payload.Reference). + SetOrderType(orderType). + SetInstitution(destination.Recipient.Institution). + SetAccountIdentifier(destination.Recipient.AccountIdentifier). + SetAccountName(destination.Recipient.AccountName). + SetMemo(destination.Recipient.Memo). + SetMetadata(metadata). + AddTransactions(transactionLog) + + // Set provider ID if available from rate validation result + if rateValidationResult.ProviderID != "" { + paymentOrderBuilder = paymentOrderBuilder.SetProviderID(rateValidationResult.ProviderID) + } + + // Set salt for Tron addresses + if receiveAddressSalt != nil { + paymentOrderBuilder = paymentOrderBuilder.SetReceiveAddressSalt(receiveAddressSalt) + } + + paymentOrder, err := paymentOrderBuilder.Save(ctx) + if err != nil { + logger.Errorf("error: %v", err) + u.APIResponse(ctx, http.StatusInternalServerError, "error", "Failed to initiate payment order", nil) + _ = tx.Rollback() + return + } + + // Create webhook for the smart address to monitor transfers (only for EVM networks) + if !strings.HasPrefix(source.Network, "tron") && !strings.HasPrefix(source.Network, "starknet") { + engineService := svc.NewEngineService() + webhookID, webhookSecret, err := engineService.CreateTransferWebhook( + ctx, + token.Edges.Network.ChainID, + token.ContractAddress, + receiveAddress, + paymentOrder.ID.String(), + ) + if err != nil { + // Check if this is BNB Smart Chain (chain ID 56) or Lisk (chain ID 1135) which is not supported by Thirdweb + if token.Edges.Network.ChainID != 56 && token.Edges.Network.ChainID != 1135 { + logger.WithFields(logger.Fields{ + "ChainID": token.Edges.Network.ChainID, + "Network": token.Edges.Network.Identifier, + "Error": err.Error(), + }).Errorf("Failed to create transfer webhook: %v", err) + u.APIResponse(ctx, http.StatusInternalServerError, "error", "Failed to initiate payment order", nil) + _ = tx.Rollback() + return + } + } else { + // Create PaymentWebhook record in database only if webhook was created successfully + _, err = tx.PaymentWebhook. + Create(). + SetWebhookID(webhookID). + SetWebhookSecret(webhookSecret). + SetCallbackURL(fmt.Sprintf("%s/v1/insight/webhook", serverConf.ServerURL)). + SetPaymentOrder(paymentOrder). + Save(ctx) + if err != nil { + logger.Errorf("Failed to save payment webhook record: %v", err) + u.APIResponse(ctx, http.StatusInternalServerError, "error", "Failed to initiate payment order", nil) + _ = tx.Rollback() + return + } + } + } + + // Commit the transaction + if err := tx.Commit(); err != nil { + logger.Errorf("error: %v", err) + u.APIResponse(ctx, http.StatusInternalServerError, "error", "Failed to initiate payment order", nil) + return + } + + // Format sender fee percent for response + senderFeePercentStr := "" + if !feePercent.IsZero() { + senderFeePercentStr = feePercent.String() + } + + // Build response + transactionFee := paymentOrder.NetworkFee.Add(paymentOrder.ProtocolFee) + response := &types.V2PaymentOrderResponse{ + ID: paymentOrder.ID, + Status: string(paymentOrder.Status), + Timestamp: paymentOrder.CreatedAt, + Amount: cryptoAmount.String(), + AmountIn: payload.AmountIn, + SenderFee: senderFee.String(), + SenderFeePercent: senderFeePercentStr, + TransactionFee: transactionFee.String(), + Reference: paymentOrder.Reference, + ProviderAccount: types.V2CryptoProviderAccount{ + Network: source.Network, + ReceiveAddress: receiveAddress, + ValidUntil: receiveAddressExpiry, + }, + Source: source, + Destination: destination, + } + + // Add rate to response if available + if !orderRate.IsZero() { + response.Rate = orderRate.String() + } + + u.APIResponse(ctx, http.StatusCreated, "success", "Payment order initiated successfully", response) +} + +// initiateOnrampOrderV2 handles onramp (fiat->crypto) payment order creation +func (ctrl *SenderController) initiateOnrampOrderV2(ctx *gin.Context, payload types.V2PaymentOrderPayload) { + // Unmarshal source and destination + var source types.V2FiatSource + var destination types.V2CryptoDestination + if err := json.Unmarshal(payload.Source, &source); err != nil { + u.APIResponse(ctx, http.StatusBadRequest, "error", "Failed to validate payload", types.ErrorData{ + Field: "Source", + Message: "Invalid fiat source format", + }) + return + } + if err := json.Unmarshal(payload.Destination, &destination); err != nil { + u.APIResponse(ctx, http.StatusBadRequest, "error", "Failed to validate payload", types.ErrorData{ + Field: "Destination", + Message: "Invalid crypto destination format", + }) + return + } + + // Get sender profile from the context + senderCtx, ok := ctx.Get("sender") + if !ok { + u.APIResponse(ctx, http.StatusUnauthorized, "error", "Invalid API key or token", nil) + return + } + sender := senderCtx.(*ent.SenderProfile) + + // Validate mutually exclusive fields: senderFee and senderFeePercent cannot both be provided + if payload.SenderFee != "" && payload.SenderFeePercent != "" { + u.APIResponse(ctx, http.StatusBadRequest, "error", "Failed to validate payload", types.ErrorData{ + Field: "SenderFee", + Message: "Cannot provide both senderFee and senderFeePercent", + }) + return + } + + // Parse amount from string + amount, err := decimal.NewFromString(payload.Amount) + if err != nil { + u.APIResponse(ctx, http.StatusBadRequest, "error", "Failed to validate payload", types.ErrorData{ + Field: "Amount", + Message: "Invalid amount format", + }) + return + } + + // Validate amount is greater than zero + if amount.LessThanOrEqual(decimal.Zero) { + u.APIResponse(ctx, http.StatusBadRequest, "error", "Failed to validate payload", types.ErrorData{ + Field: "Amount", + Message: "Amount must be greater than zero", + }) + return + } + + // Default amountIn to "fiat" for onramp if not provided + if payload.AmountIn == "" { + payload.AmountIn = "fiat" + } + + // Validate fiat source (currency + refund account details) + currency, err := storage.Client.FiatCurrency. + Query(). + Where( + fiatcurrency.CodeEQ(source.Currency), + fiatcurrency.IsEnabledEQ(true), + ). + Only(ctx) + if err != nil { + if ent.IsNotFound(err) { + u.APIResponse(ctx, http.StatusBadRequest, "error", "Failed to validate payload", types.ErrorData{ + Field: "Source", + Message: "Currency not supported", + }) + } else { + logger.Errorf("Failed to fetch currency: %v", err) + u.APIResponse(ctx, http.StatusInternalServerError, "error", "Failed to fetch currency", nil) + } + return + } + + // Validate refund account institution + refundInstitution, err := storage.Client.Institution. + Query(). + Where( + institution.CodeEQ(source.RefundAccount.Institution), + ). + WithFiatCurrency(). + First(ctx) + if err != nil { + if ent.IsNotFound(err) { + u.APIResponse(ctx, http.StatusBadRequest, "error", "Failed to validate payload", types.ErrorData{ + Field: "Source", + Message: "Refund institution not supported", + }) + } else { + logger.Errorf("Failed to fetch refund institution: %v", err) + u.APIResponse(ctx, http.StatusInternalServerError, "error", "Failed to validate refund institution", nil) + } + return + } + + // Validate refund account currency matches source currency + if refundInstitution.Edges.FiatCurrency.Code != source.Currency { + u.APIResponse(ctx, http.StatusBadRequest, "error", "Failed to validate payload", types.ErrorData{ + Field: "Source", + Message: "Refund account currency does not match source currency", + }) + return + } + + // Validate crypto destination (recipient payment rail + address + token/network) + token, err := storage.Client.Token. + Query(). + Where( + tokenEnt.SymbolEQ(destination.Currency), + tokenEnt.HasNetworkWith(network.IdentifierEQ(destination.Network)), + tokenEnt.IsEnabledEQ(true), + ). + WithNetwork(). + Only(ctx) + if err != nil { + if ent.IsNotFound(err) { + u.APIResponse(ctx, http.StatusBadRequest, "error", "Failed to validate payload", types.ErrorData{ + Field: "Destination", + Message: "Provided token or payment rail is not supported", + }) + } else { + logger.Errorf("Failed to fetch token: %v", err) + u.APIResponse(ctx, http.StatusInternalServerError, "error", "Failed to fetch token", nil) + } + return + } + + // Validate recipient address format + if strings.HasPrefix(destination.Network, "tron") { + if !u.IsValidTronAddress(destination.Recipient.Address) { + u.APIResponse(ctx, http.StatusBadRequest, "error", "Failed to validate payload", types.ErrorData{ + Field: "Destination", + Message: "Invalid Tron recipient address", + }) + return + } + } else if strings.HasPrefix(destination.Network, "starknet") { + pattern := `^0x[a-fA-F0-9]{63,64}$` + matched, _ := regexp.MatchString(pattern, destination.Recipient.Address) + if !matched { + u.APIResponse(ctx, http.StatusBadRequest, "error", "Failed to validate payload", types.ErrorData{ + Field: "Destination", + Message: "Invalid Starknet recipient address", + }) + return + } + } else { + if !u.IsValidEthereumAddress(destination.Recipient.Address) { + u.APIResponse(ctx, http.StatusBadRequest, "error", "Failed to validate payload", types.ErrorData{ + Field: "Destination", + Message: "Invalid Ethereum recipient address", + }) + return + } + } + + // Validate network matches + if destination.Recipient.Network != destination.Network { + u.APIResponse(ctx, http.StatusBadRequest, "error", "Failed to validate payload", types.ErrorData{ + Field: "Destination", + Message: "Recipient network does not match payment rail", + }) + return + } + + // Handle rate logic based on amountIn + var cryptoAmountOut decimal.Decimal + var orderRate decimal.Decimal + var rateValidationResult u.RateValidationResult + + amountIn := payload.AmountIn + switch amountIn { + case "fiat": + // For fiat amounts, convert to crypto using buy rate + isDirectMatch := strings.EqualFold(token.BaseCurrency, currency.Code) + if isDirectMatch { + orderRate = decimal.NewFromInt(1) + } else { + if currency.MarketBuyRate.IsZero() { + u.APIResponse(ctx, http.StatusBadRequest, "error", "Failed to validate payload", types.ErrorData{ + Field: "Rate", + Message: "Rate not available for this currency", + }) + return + } + // Use market buy rate as approximation for onramp + orderRate = currency.MarketBuyRate + } + cryptoAmountOut = amount.Div(orderRate) + + // ValidateRate expects crypto units, so pass cryptoAmountOut with buy side + rateResult, err := u.ValidateRate(ctx, token, currency, cryptoAmountOut, destination.ProviderID, destination.Network, u.RateSideBuy) + if err != nil { + u.APIResponse(ctx, http.StatusBadRequest, "error", "Failed to validate payload", types.ErrorData{ + Field: "Rate", + Message: fmt.Sprintf("Rate validation failed: %s", err.Error()), + }) + return + } + rateValidationResult = rateResult + orderRate = rateValidationResult.Rate + // Recalculate cryptoAmountOut with the validated rate + cryptoAmountOut = amount.Div(orderRate) + case "crypto": + cryptoAmountOut = amount + if payload.Rate != "" { + providedRate, err := decimal.NewFromString(payload.Rate) + if err != nil { + u.APIResponse(ctx, http.StatusBadRequest, "error", "Failed to validate payload", types.ErrorData{ + Field: "Rate", + Message: "Invalid rate format", + }) + return + } + if providedRate.LessThanOrEqual(decimal.Zero) { + u.APIResponse(ctx, http.StatusBadRequest, "error", "Failed to validate payload", types.ErrorData{ + Field: "Rate", + Message: "Rate must be greater than zero", + }) + return + } + + // Validate rate is achievable (using buy side) + rateResult, err := u.ValidateRate(ctx, token, currency, cryptoAmountOut, destination.ProviderID, destination.Network, u.RateSideBuy) + if err != nil { + u.APIResponse(ctx, http.StatusBadRequest, "error", "Failed to validate payload", types.ErrorData{ + Field: "Rate", + Message: fmt.Sprintf("Rate validation failed: %s", err.Error()), + }) + return + } + rateValidationResult = rateResult + achievableRate := rateValidationResult.Rate + tolerance := achievableRate.Mul(decimal.NewFromFloat(0.001)) + if providedRate.GreaterThan(achievableRate.Add(tolerance)) { + u.APIResponse(ctx, http.StatusBadRequest, "error", "Failed to validate payload", types.ErrorData{ + Field: "Rate", + Message: fmt.Sprintf("Provided rate %s is not achievable. Available rate is %s", providedRate, achievableRate), + }) + return + } + orderRate = providedRate + } else { + // Fetch rate from ValidateRate (buy side) + rateResult, err := u.ValidateRate(ctx, token, currency, cryptoAmountOut, destination.ProviderID, destination.Network, u.RateSideBuy) + if err != nil { + u.APIResponse(ctx, http.StatusBadRequest, "error", "Failed to validate payload", types.ErrorData{ + Field: "Rate", + Message: fmt.Sprintf("Rate validation failed: %s", err.Error()), + }) + return + } + rateValidationResult = rateResult + orderRate = rateValidationResult.Rate + } + default: + u.APIResponse(ctx, http.StatusBadRequest, "error", "Failed to validate payload", types.ErrorData{ + Field: "AmountIn", + Message: "amountIn must be 'crypto' or 'fiat'", + }) + return + } + + // Handle fee calculation (priority: senderFee > senderFeePercent > configured defaults) + // Note: For onramp, we need to get sender order token config for the crypto token + senderOrderToken, err := storage.Client.SenderOrderToken. + Query(). + Where( + senderordertoken.HasTokenWith(tokenEnt.IDEQ(token.ID)), + senderordertoken.HasSenderWith(senderprofile.IDEQ(sender.ID)), + ). + Only(ctx) + if err != nil { + u.APIResponse(ctx, http.StatusBadRequest, "error", "Failed to validate payload", types.ErrorData{ + Field: "Destination", + Message: "Provided token is not configured", + }) + return + } + + if senderOrderToken.FeeAddress == "" { + u.APIResponse(ctx, http.StatusBadRequest, "error", "Failed to validate payload", types.ErrorData{ + Field: "Destination", + Message: "Fee address is not configured", + }) + return + } + + feePercent := senderOrderToken.FeePercent + feeAddress := senderOrderToken.FeeAddress + + var senderFeeCrypto decimal.Decimal + if payload.SenderFee != "" { + fixedFee, err := decimal.NewFromString(payload.SenderFee) + if err != nil { + u.APIResponse(ctx, http.StatusBadRequest, "error", "Failed to validate payload", types.ErrorData{ + Field: "SenderFee", + Message: "Invalid sender fee format", + }) + return + } + if fixedFee.LessThanOrEqual(decimal.Zero) { + u.APIResponse(ctx, http.StatusBadRequest, "error", "Failed to validate payload", types.ErrorData{ + Field: "SenderFee", + Message: "Sender fee must be greater than zero", + }) + return + } + senderFeeCrypto = fixedFee + feePercent = decimal.Zero + } else if payload.SenderFeePercent != "" { + feePercentValue, err := decimal.NewFromString(payload.SenderFeePercent) + if err != nil { + u.APIResponse(ctx, http.StatusBadRequest, "error", "Failed to validate payload", types.ErrorData{ + Field: "SenderFeePercent", + Message: "Invalid sender fee percent format", + }) + return + } + if feePercentValue.LessThanOrEqual(decimal.Zero) { + u.APIResponse(ctx, http.StatusBadRequest, "error", "Failed to validate payload", types.ErrorData{ + Field: "SenderFeePercent", + Message: "Sender fee percent must be greater than zero", + }) + return + } + feePercent = feePercentValue + calculatedFee := feePercent.Mul(cryptoAmountOut).Div(decimal.NewFromInt(100)).Round(4) + senderFeeCrypto = calculatedFee + if senderOrderToken.MaxFeeCap.GreaterThan(decimal.Zero) { + if calculatedFee.GreaterThan(senderOrderToken.MaxFeeCap) { + senderFeeCrypto = senderOrderToken.MaxFeeCap + } + } + } else { + calculatedFee := feePercent.Mul(cryptoAmountOut).Div(decimal.NewFromInt(100)).Round(4) + senderFeeCrypto = calculatedFee + if senderOrderToken.MaxFeeCap.GreaterThan(decimal.Zero) { + if calculatedFee.GreaterThan(senderOrderToken.MaxFeeCap) { + senderFeeCrypto = senderOrderToken.MaxFeeCap + } + } + } + + // Calculate fiat amounts + // senderFeeFiat = senderFeeCrypto * buyRate + senderFeeFiat := senderFeeCrypto.Mul(orderRate).RoundBank(int32(currency.Decimals)) + // fiatOrderAmount: when amountIn=crypto, amount is crypto so convert with rate; when amountIn=fiat, amount is fiat so cryptoAmountOut.Mul(rate) gives fiat + fiatOrderAmount := cryptoAmountOut.Mul(orderRate).RoundBank(int32(currency.Decimals)) + // totalFiatToPay = fiatOrderAmount + senderFeeFiat + totalFiatToPay := fiatOrderAmount.Add(senderFeeFiat).RoundBank(int32(currency.Decimals)) + + // Validate reference if provided + if payload.Reference != "" { + if !regexp.MustCompile(`^[a-zA-Z0-9\-_]+$`).MatchString(payload.Reference) { + u.APIResponse(ctx, http.StatusBadRequest, "error", "Failed to validate payload", types.ErrorData{ + Field: "Reference", + Message: "Reference must be alphanumeric", + }) + return + } + + referenceExists, err := storage.Client.PaymentOrder. + Query(). + Where( + paymentorder.ReferenceEQ(payload.Reference), + paymentorder.HasSenderProfileWith(senderprofile.IDEQ(sender.ID)), + ). + Exist(ctx) if err != nil { - logger.WithFields(logger.Fields{ - "error": err, - "network": payload.Source.PaymentRail, - }).Errorf("Failed to create receive address") - u.APIResponse(ctx, http.StatusInternalServerError, "error", "Failed to initiate payment order", types.ErrorData{ - Field: "Source", - Message: fmt.Sprintf("%s currently not available", payload.Source.PaymentRail), + logger.Errorf("Reference check error: %v", err) + u.APIResponse(ctx, http.StatusInternalServerError, "error", "Failed to initiate payment order", nil) + return + } + + if referenceExists { + u.APIResponse(ctx, http.StatusBadRequest, "error", "Failed to validate payload", types.ErrorData{ + Field: "Reference", + Message: "Reference already exists", }) return } - receiveAddress = address - receiveAddressExpiry = time.Now().Add(orderConf.ReceiveAddressValidity) } - // Set extended expiry for private orders (10x normal validity) - if strings.HasPrefix(payload.Destination.Recipient.Memo, "P#P") { - receiveAddressExpiry = time.Now().Add(10 * orderConf.ReceiveAddressValidity) + // Provider selection: destination.providerId is optional + providerID := destination.ProviderID + if providerID == "" { + providerID = rateValidationResult.ProviderID + } + if providerID == "" { + u.APIResponse(ctx, http.StatusBadRequest, "error", "No provider available for this order", nil) + return } - // Create payment order in a transaction + // Reserve provider token liquidity at order creation + balanceService := balance.New() tx, err := storage.Client.Tx(ctx) if err != nil { logger.Errorf("error: %v", err) @@ -1085,6 +1753,19 @@ func (ctrl *SenderController) InitiatePaymentOrderV2(ctx *gin.Context) { return } + // Reserve token balance: amount + senderFee (both in crypto) + totalCryptoToReserve := cryptoAmountOut.Add(senderFeeCrypto) + err = balanceService.ReserveTokenBalance(ctx, providerID, token.ID, totalCryptoToReserve, tx) + if err != nil { + logger.Errorf("Failed to reserve token balance: %v", err) + _ = tx.Rollback() + u.APIResponse(ctx, http.StatusBadRequest, "error", "Insufficient provider token balance", types.ErrorData{ + Field: "Destination", + Message: fmt.Sprintf("Provider does not have sufficient token balance: %s", err.Error()), + }) + return + } + // Create transaction log transactionLog, err := tx.TransactionLog. Create(). @@ -1098,111 +1779,160 @@ func (ctrl *SenderController) InitiatePaymentOrderV2(ctx *gin.Context) { return } - // Build metadata with KYC and recipient metadata + // Build metadata metadata := make(map[string]interface{}) - if payload.Destination.Recipient.Metadata != nil { - metadata["recipientMetadata"] = payload.Destination.Recipient.Metadata + if source.RefundAccount.Metadata != nil { + metadata["refundAccountMetadata"] = source.RefundAccount.Metadata } - if payload.KYC != nil { - kycData := make(map[string]interface{}) - if payload.KYC.Recipient != nil { - kycData["recipient"] = payload.KYC.Recipient - } - if payload.KYC.Sender != nil { - kycData["sender"] = payload.KYC.Sender - } - metadata["kyc"] = kycData + if source.KYC != nil { + metadata["sourceKyc"] = source.KYC } + if destination.KYC != nil { + metadata["destinationKyc"] = destination.KYC + } + if source.Country != "" { + metadata["country"] = source.Country + } + + // Use order type from ValidateRate result + orderType := rateValidationResult.OrderType + + // Calculate amount in USD (onramp: use buy rate when available) + amountInUSD := u.CalculatePaymentOrderAmountInUSD(cryptoAmountOut, token, refundInstitution, paymentorder.DirectionOnramp) // Create payment order paymentOrderBuilder := tx.PaymentOrder. Create(). SetSenderProfile(sender). - SetAmount(cryptoAmount). + SetAmount(cryptoAmountOut). SetAmountInUsd(amountInUSD). SetNetworkFee(token.Edges.Network.Fee). - SetSenderFee(senderFee). + SetSenderFee(senderFeeCrypto). SetToken(token). SetRate(orderRate). - SetReceiveAddress(receiveAddress). - SetReceiveAddressExpiry(receiveAddressExpiry). SetFeePercent(feePercent). SetFeeAddress(feeAddress). - SetReturnAddress(returnAddress). + SetRefundOrRecipientAddress(destination.Recipient.Address). // Onramp: crypto recipient for settleIn + SetDirection(paymentorder.DirectionOnramp). SetReference(payload.Reference). SetOrderType(orderType). - SetInstitution(payload.Destination.Recipient.Institution). - SetAccountIdentifier(payload.Destination.Recipient.AccountIdentifier). - SetAccountName(payload.Destination.Recipient.AccountName). - SetMemo(payload.Destination.Recipient.Memo). + SetInstitution(source.RefundAccount.Institution). + SetAccountIdentifier(source.RefundAccount.AccountIdentifier). + SetAccountName(source.RefundAccount.AccountName). + SetMemo(""). // Onramp doesn't use memo SetMetadata(metadata). + SetProviderID(providerID). + SetStatus(paymentorder.StatusPending). // VA issued at init; provider already assigned at this point. AddTransactions(transactionLog) - // Set provider ID if available from rate validation result - if rateValidationResult.ProviderID != "" { - paymentOrderBuilder = paymentOrderBuilder.SetProviderID(rateValidationResult.ProviderID) + paymentOrder, err := paymentOrderBuilder.Save(ctx) + if err != nil { + logger.Errorf("error: %v", err) + u.APIResponse(ctx, http.StatusInternalServerError, "error", "Failed to initiate payment order", nil) + _ = tx.Rollback() + return } - // Set salt for Tron addresses - if receiveAddressSalt != nil { - paymentOrderBuilder = paymentOrderBuilder.SetReceiveAddressSalt(receiveAddressSalt) + // Generate Gateway order ID now that we have the payment order ID + // For onramp, Gateway order ID will be generated by the provider when calling settleIn + // We'll store a placeholder that can be updated later, or generate it deterministically + // For now, we'll store it in metadata and it will be set when settleIn is called + + // Call provider /new_order with direction=payin to create a virtual account + orderRequestData := map[string]interface{}{ + "orderId": paymentOrder.ID.String(), + "direction": "payin", + "amount": totalFiatToPay.String(), + "currency": source.Currency, + "institution": source.RefundAccount.Institution, + } + if source.KYC != nil { + orderRequestData["kyc"] = source.KYC } - paymentOrder, err := paymentOrderBuilder.Save(ctx) + // Call provider new_order endpoint + providerResponse, err := u.CallProviderWithHMAC(ctx, providerID, "POST", "/new_order", orderRequestData) if err != nil { - logger.Errorf("error: %v", err) - u.APIResponse(ctx, http.StatusInternalServerError, "error", "Failed to initiate payment order", nil) + logger.Errorf("Failed to call provider new_order: %v", err) _ = tx.Rollback() + u.APIResponse(ctx, http.StatusInternalServerError, "error", "Failed to create virtual account", nil) return } - // Create webhook for the smart address to monitor transfers (only for EVM networks) - if !strings.HasPrefix(payload.Source.PaymentRail, "tron") && !strings.HasPrefix(payload.Source.PaymentRail, "starknet") { - engineService := svc.NewEngineService() - webhookID, webhookSecret, err := engineService.CreateTransferWebhook( - ctx, - token.Edges.Network.ChainID, - token.ContractAddress, - receiveAddress, - paymentOrder.ID.String(), - ) + // Extract virtual account details from provider response + accountIdentifier, ok := providerResponse["accountIdentifier"].(string) + if !ok { + logger.Errorf("Invalid provider response: missing accountIdentifier") + _ = tx.Rollback() + u.APIResponse(ctx, http.StatusInternalServerError, "error", "Invalid provider response", nil) + return + } + accountName, _ := providerResponse["accountName"].(string) + institutionName, _ := providerResponse["institutionName"].(string) + reference, _ := providerResponse["reference"].(string) + + var validUntil time.Time + if validUntilStr, ok := providerResponse["validUntil"].(string); ok { + var err error + validUntil, err = time.Parse(time.RFC3339, validUntilStr) if err != nil { - // Check if this is BNB Smart Chain (chain ID 56) or Lisk (chain ID 1135) which is not supported by Thirdweb - if token.Edges.Network.ChainID != 56 && token.Edges.Network.ChainID != 1135 { - logger.WithFields(logger.Fields{ - "ChainID": token.Edges.Network.ChainID, - "Network": token.Edges.Network.Identifier, - "Error": err.Error(), - }).Errorf("Failed to create transfer webhook: %v", err) - u.APIResponse(ctx, http.StatusInternalServerError, "error", "Failed to initiate payment order", nil) - _ = tx.Rollback() - return - } - } else { - // Create PaymentWebhook record in database only if webhook was created successfully - _, err = tx.PaymentWebhook. - Create(). - SetWebhookID(webhookID). - SetWebhookSecret(webhookSecret). - SetCallbackURL(fmt.Sprintf("%s/v1/insight/webhook", serverConf.ServerURL)). - SetPaymentOrder(paymentOrder). - Save(ctx) - if err != nil { - logger.Errorf("Failed to save payment webhook record: %v", err) - u.APIResponse(ctx, http.StatusInternalServerError, "error", "Failed to initiate payment order", nil) - _ = tx.Rollback() - return - } + validUntil = time.Now().Add(orderConf.OrderFulfillmentValidity) } + } else { + validUntil = time.Now().Add(orderConf.OrderFulfillmentValidity) // Default validity } - // Commit the transaction + // Update payment order with virtual account details (use tx so update participates in transaction) + orderMetadata := paymentOrder.Metadata + if orderMetadata == nil { + orderMetadata = make(map[string]interface{}) + } + providerAccountMap := map[string]interface{}{ + "institution": institutionName, + "accountIdentifier": accountIdentifier, + "accountName": accountName, + "validUntil": validUntil.Format(time.RFC3339), + } + if reference != "" { + providerAccountMap["reference"] = reference + } + orderMetadata["providerAccount"] = providerAccountMap + if _, err := tx.PaymentOrder.UpdateOneID(paymentOrder.ID).SetMetadata(orderMetadata).Save(ctx); err != nil { + logger.Errorf("Failed to save provider account to order metadata: %v", err) + _ = tx.Rollback() + u.APIResponse(ctx, http.StatusInternalServerError, "error", "Failed to initiate payment order", nil) + return + } + + // Commit the transaction before setting Redis key so we never leave an orphaned key if commit fails if err := tx.Commit(); err != nil { logger.Errorf("error: %v", err) u.APIResponse(ctx, http.StatusInternalServerError, "error", "Failed to initiate payment order", nil) return } + // Seed order_request_%s (same key as offramp) so payin AcceptOrder can validate provider + orderRequestKey := fmt.Sprintf("order_request_%s", paymentOrder.ID.String()) + if err := storage.RedisClient.HSet(ctx, orderRequestKey, + "providerId", providerID, + "direction", "payin", + "amount", totalFiatToPay.String(), + "orderId", paymentOrder.ID.String(), + ).Err(); err != nil { + logger.Errorf("Failed to set order_request for payin: %v", err) + // Compensate: release reserved token and cancel order so we don't leave reserved-but-unservable state + totalCryptoReserved := paymentOrder.Amount.Add(paymentOrder.SenderFee) + if relErr := balanceService.ReleaseTokenBalance(ctx, providerID, token.ID, totalCryptoReserved, nil); relErr != nil { + logger.Errorf("Failed to release token balance after Redis HSet failure (order %s): %v", paymentOrder.ID, relErr) + } + if _, updErr := storage.Client.PaymentOrder.UpdateOneID(paymentOrder.ID).SetStatus(paymentorder.StatusCancelled).Save(ctx); updErr != nil { + logger.Errorf("Failed to cancel order after Redis HSet failure (order %s): %v", paymentOrder.ID, updErr) + } + u.APIResponse(ctx, http.StatusInternalServerError, "error", "Failed to initiate payment order", nil) + return + } + _ = storage.RedisClient.Expire(ctx, orderRequestKey, 24*time.Hour).Err() + // Format sender fee percent for response senderFeePercentStr := "" if !feePercent.IsZero() { @@ -1210,23 +1940,25 @@ func (ctrl *SenderController) InitiatePaymentOrderV2(ctx *gin.Context) { } // Build response + transactionFee := paymentOrder.NetworkFee.Add(paymentOrder.ProtocolFee) response := &types.V2PaymentOrderResponse{ ID: paymentOrder.ID, Status: string(paymentOrder.Status), Timestamp: paymentOrder.CreatedAt, - Amount: cryptoAmount.String(), - SenderFee: senderFee.String(), + Amount: cryptoAmountOut.String(), + AmountIn: payload.AmountIn, + SenderFee: senderFeeCrypto.String(), SenderFeePercent: senderFeePercentStr, - TransactionFee: token.Edges.Network.Fee.String(), + TransactionFee: transactionFee.String(), Reference: paymentOrder.Reference, - ProviderAccount: types.V2ProviderAccount{ - PaymentRail: payload.Source.PaymentRail, - ReceiveAddress: receiveAddress, - ValidUntil: receiveAddressExpiry, + ProviderAccount: types.V2FiatProviderAccount{ + Institution: institutionName, + AccountIdentifier: accountIdentifier, + AccountName: accountName, + ValidUntil: validUntil, }, - Source: payload.Source, - Destination: payload.Destination, - KYC: payload.KYC, + Source: source, + Destination: destination, } // Add rate to response if available @@ -1317,7 +2049,7 @@ func (ctrl *SenderController) GetPaymentOrderByID(ctx *gin.Context) { AmountReturned: paymentOrder.AmountReturned, Token: paymentOrder.Edges.Token.Symbol, SenderFee: paymentOrder.SenderFee, - TransactionFee: paymentOrder.NetworkFee, + TransactionFee: paymentOrder.NetworkFee.Add(paymentOrder.ProtocolFee), Rate: paymentOrder.Rate, Network: paymentOrder.Edges.Token.Edges.Network.Identifier, Recipient: types.PaymentOrderRecipient{ @@ -1335,7 +2067,8 @@ func (ctrl *SenderController) GetPaymentOrderByID(ctx *gin.Context) { }, Transactions: transactions, FromAddress: paymentOrder.FromAddress, - ReturnAddress: paymentOrder.ReturnAddress, + ReturnAddress: paymentOrder.RefundOrRecipientAddress, + RefundAddress: paymentOrder.RefundOrRecipientAddress, ReceiveAddress: paymentOrder.ReceiveAddress, FeeAddress: paymentOrder.FeeAddress, Reference: paymentOrder.Reference, @@ -1348,6 +2081,170 @@ func (ctrl *SenderController) GetPaymentOrderByID(ctx *gin.Context) { }) } +// GetPaymentOrderByIDV2 returns a single payment order in v2 API schema (providerAccount, source, destination). +func (ctrl *SenderController) GetPaymentOrderByIDV2(ctx *gin.Context) { + orderID := ctx.Param("id") + isUUID := true + id, err := uuid.Parse(orderID) + if err != nil { + isUUID = false + } + senderCtx, ok := ctx.Get("sender") + if !ok { + u.APIResponse(ctx, http.StatusUnauthorized, "error", "Invalid API key or token", nil) + return + } + sender := senderCtx.(*ent.SenderProfile) + + paymentOrderQuery := storage.Client.PaymentOrder.Query() + if isUUID { + paymentOrderQuery = paymentOrderQuery.Where(paymentorder.IDEQ(id)) + } else { + paymentOrderQuery = paymentOrderQuery.Where(paymentorder.ReferenceEQ(orderID)) + } + paymentOrder, err := paymentOrderQuery. + Where(paymentorder.HasSenderProfileWith(senderprofile.IDEQ(sender.ID))). + WithProvider(). + WithToken(func(tq *ent.TokenQuery) { tq.WithNetwork() }). + WithTransactions(). + Only(ctx) + if err != nil { + if ent.IsNotFound(err) { + u.APIResponse(ctx, http.StatusNotFound, "error", "Payment order not found", nil) + } else { + logger.Errorf("error: %v", err) + u.APIResponse(ctx, http.StatusInternalServerError, "error", "Failed to fetch payment order", nil) + } + return + } + + institution, err := storage.Client.Institution. + Query(). + Where(institution.CodeEQ(paymentOrder.Institution)). + WithFiatCurrency(). + Only(ctx) + if err != nil { + logger.Errorf("error: %v", err) + u.APIResponse(ctx, http.StatusInternalServerError, "error", "Failed to fetch payment order", nil) + return + } + + var transactionLogs []types.TransactionLog + for _, tx := range paymentOrder.Edges.Transactions { + transactionLogs = append(transactionLogs, types.TransactionLog{ + ID: tx.ID, + GatewayId: tx.GatewayID, + Status: tx.Status, + TxHash: tx.TxHash, + CreatedAt: tx.CreatedAt, + }) + } + resp := u.BuildV2PaymentOrderGetResponse(paymentOrder, institution, transactionLogs, nil, nil) + u.APIResponse(ctx, http.StatusOK, "success", "The order has been successfully retrieved", resp) +} + +// GetPaymentOrdersV2 returns a list of payment orders in v2 API schema (no search/export; list only). +func (ctrl *SenderController) GetPaymentOrdersV2(ctx *gin.Context) { + senderCtx, ok := ctx.Get("sender") + if !ok { + u.APIResponse(ctx, http.StatusUnauthorized, "error", "Invalid API key or token", nil) + return + } + sender := senderCtx.(*ent.SenderProfile) + ctrl.handleListPaymentOrdersV2(ctx, sender) +} + +// handleListPaymentOrdersV2 handles v2 payment order listing with pagination and v2 response shape. +func (ctrl *SenderController) handleListPaymentOrdersV2(ctx *gin.Context, sender *ent.SenderProfile) { + ordering := ctx.Query("ordering") + order := ent.Desc(paymentorder.FieldCreatedAt) + if ordering == "asc" { + order = ent.Asc(paymentorder.FieldCreatedAt) + } + page, offset, pageSize := u.Paginate(ctx) + + paymentOrderQuery := storage.Client.PaymentOrder.Query(). + Where(paymentorder.HasSenderProfileWith(senderprofile.IDEQ(sender.ID))) + paymentOrderQuery = ctrl.applyFilters(ctx, paymentOrderQuery) + + count, err := paymentOrderQuery.Count(ctx) + if err != nil { + logger.Errorf("error: %v", err) + u.APIResponse(ctx, http.StatusInternalServerError, "error", "Failed to fetch payment orders", nil) + return + } + + paymentOrders, err := paymentOrderQuery. + WithProvider(). + WithToken(func(tq *ent.TokenQuery) { tq.WithNetwork() }). + WithTransactions(). + Limit(pageSize). + Offset(offset). + Order(order). + All(ctx) + if err != nil { + logger.Errorf("error: %v", err) + u.APIResponse(ctx, http.StatusInternalServerError, "error", "Failed to fetch payment orders", nil) + return + } + + orders, err := ctrl.buildV2PaymentOrderGetResponses(ctx, paymentOrders) + if err != nil { + logger.Errorf("error: %v", err) + u.APIResponse(ctx, http.StatusInternalServerError, "error", "Failed to fetch payment orders", nil) + return + } + + u.APIResponse(ctx, http.StatusOK, "success", "Payment orders retrieved successfully", types.V2PaymentOrderListResponse{ + Page: page, + PageSize: pageSize, + TotalRecords: count, + Orders: orders, + }) +} + +// buildV2PaymentOrderGetResponses converts payment orders to v2 get response list (batch-fetches institutions). +func (ctrl *SenderController) buildV2PaymentOrderGetResponses(ctx *gin.Context, paymentOrders []*ent.PaymentOrder) ([]types.V2PaymentOrderGetResponse, error) { + if len(paymentOrders) == 0 { + return nil, nil + } + codes := make(map[string]bool) + for _, po := range paymentOrders { + codes[po.Institution] = true + } + codeSlice := make([]string, 0, len(codes)) + for c := range codes { + codeSlice = append(codeSlice, c) + } + institutions, err := storage.Client.Institution. + Query(). + Where(institution.CodeIn(codeSlice...)). + WithFiatCurrency(). + All(ctx) + if err != nil { + return nil, err + } + instMap := make(map[string]*ent.Institution) + for _, inst := range institutions { + instMap[inst.Code] = inst + } + + out := make([]types.V2PaymentOrderGetResponse, 0, len(paymentOrders)) + for _, po := range paymentOrders { + inst, ok := instMap[po.Institution] + if !ok { + inst = nil // institution code not in map; builder handles nil + } + var txLogs []types.TransactionLog + for _, tx := range po.Edges.Transactions { + txLogs = append(txLogs, types.TransactionLog{ID: tx.ID, GatewayId: tx.GatewayID, Status: tx.Status, TxHash: tx.TxHash, CreatedAt: tx.CreatedAt}) + } + resp := u.BuildV2PaymentOrderGetResponse(po, inst, txLogs, nil, nil) + out = append(out, *resp) + } + return out, nil +} + // GetPaymentOrders controller fetches all payment orders with support for search and export func (ctrl *SenderController) GetPaymentOrders(ctx *gin.Context) { // Get sender profile from the context @@ -1475,7 +2372,7 @@ func (ctrl *SenderController) handleSearchPaymentOrders(ctx *gin.Context, sender searchPredicates = append(searchPredicates, paymentorder.ReceiveAddressContainsFold(searchText), paymentorder.FromAddressContainsFold(searchText), - paymentorder.ReturnAddressContainsFold(searchText), + paymentorder.RefundOrRecipientAddressContainsFold(searchText), paymentorder.Or( paymentorder.AccountIdentifierContainsFold(searchText), paymentorder.AccountNameContainsFold(searchText), @@ -1752,7 +2649,7 @@ func (ctrl *SenderController) buildPaymentOrderResponses(ctx *gin.Context, payme AmountReturned: paymentOrder.AmountReturned, Token: paymentOrder.Edges.Token.Symbol, SenderFee: paymentOrder.SenderFee, - TransactionFee: paymentOrder.NetworkFee, + TransactionFee: paymentOrder.NetworkFee.Add(paymentOrder.ProtocolFee), Rate: paymentOrder.Rate, Network: paymentOrder.Edges.Token.Edges.Network.Identifier, Recipient: types.PaymentOrderRecipient{ @@ -1769,7 +2666,8 @@ func (ctrl *SenderController) buildPaymentOrderResponses(ctx *gin.Context, payme Memo: paymentOrder.Memo, }, FromAddress: paymentOrder.FromAddress, - ReturnAddress: paymentOrder.ReturnAddress, + ReturnAddress: paymentOrder.RefundOrRecipientAddress, + RefundAddress: paymentOrder.RefundOrRecipientAddress, ReceiveAddress: paymentOrder.ReceiveAddress, FeeAddress: paymentOrder.FeeAddress, Reference: paymentOrder.Reference, @@ -1886,7 +2784,7 @@ func (ctrl *SenderController) generateCSVResponse(ctx *gin.Context, paymentOrder paymentOrder.AccountName, paymentOrder.FromAddress, paymentOrder.ReceiveAddress, - paymentOrder.ReturnAddress, + paymentOrder.RefundOrRecipientAddress, paymentOrder.FeeAddress, paymentOrder.TxHash, paymentOrder.CreatedAt.Format("2006-01-02 15:04:05"), @@ -1956,18 +2854,29 @@ func (ctrl *SenderController) Stats(ctx *gin.Context) { var localStablecoinSum decimal.Decimal var localStablecoinSenderFee decimal.Decimal - // Convert local stablecoin volume to USD + // Convert local stablecoin volume to USD (direction-aware: onramp use buy rate, offramp use sell rate) for _, paymentOrder := range paymentOrders { - institution, err := u.GetInstitutionByCode(ctx, paymentOrder.Institution, false) + institution, err := u.GetInstitutionByCode(ctx, paymentOrder.Institution, true) if err != nil { logger.Errorf("error: %v", err) u.APIResponse(ctx, http.StatusInternalServerError, "error", "Failed to fetch sender stats", nil) return } - - paymentOrder.Amount = paymentOrder.Amount.Div(institution.Edges.FiatCurrency.MarketRate) + if institution == nil || institution.Edges.FiatCurrency == nil { + continue + } + fiatCurrency := institution.Edges.FiatCurrency + var rate decimal.Decimal + if paymentOrder.Direction == paymentorder.DirectionOnramp && !fiatCurrency.MarketBuyRate.IsZero() { + rate = fiatCurrency.MarketBuyRate + } else if !fiatCurrency.MarketSellRate.IsZero() { + rate = fiatCurrency.MarketSellRate + } else { + continue + } + paymentOrder.Amount = paymentOrder.Amount.Div(rate) if paymentOrder.SenderFee.GreaterThan(decimal.Zero) { - paymentOrder.SenderFee = paymentOrder.SenderFee.Div(institution.Edges.FiatCurrency.MarketRate) + paymentOrder.SenderFee = paymentOrder.SenderFee.Div(rate) } localStablecoinSum = localStablecoinSum.Add(paymentOrder.Amount) diff --git a/controllers/sender/sender_test.go b/controllers/sender/sender_test.go index 8357ad4aa..07ff09a06 100644 --- a/controllers/sender/sender_test.go +++ b/controllers/sender/sender_test.go @@ -114,19 +114,19 @@ func setup() error { return fmt.Errorf("CreateTestProviderProfile.sender_test: %w", err) } - // Create ProviderOrderToken for rate validation + // Create ProviderOrderToken for rate validation (two-sided rates; match test payload rate 750) providerOrderToken, err := test.AddProviderOrderTokenToProvider(map[string]interface{}{ - "provider": providerProfile, - "token_id": int(tokenId), - "currency_id": currency.ID, - "fixed_conversion_rate": decimal.NewFromFloat(750.0), // Match test payload rate - "conversion_rate_type": "fixed", - "max_order_amount": decimal.NewFromFloat(10000.0), - "min_order_amount": decimal.NewFromFloat(1.0), - "max_order_amount_otc": decimal.NewFromFloat(10000.0), - "min_order_amount_otc": decimal.NewFromFloat(100.0), - "settlement_address": "0x1234567890123456789012345678901234567890", - "network": testCtx.networkIdentifier, + "provider": providerProfile, + "token_id": int(tokenId), + "currency_id": currency.ID, + "fixed_buy_rate": decimal.NewFromFloat(750.0), + "fixed_sell_rate": decimal.NewFromFloat(750.0), + "max_order_amount": decimal.NewFromFloat(10000.0), + "min_order_amount": decimal.NewFromFloat(1.0), + "max_order_amount_otc": decimal.NewFromFloat(10000.0), + "min_order_amount_otc": decimal.NewFromFloat(100.0), + "settlement_address": "0x1234567890123456789012345678901234567890", + "network": testCtx.networkIdentifier, }) if err != nil { return fmt.Errorf("AddProviderOrderTokenToProvider.sender_test: %w", err) @@ -158,12 +158,12 @@ func setup() error { } // Populate Redis bucket with provider data for validateBucketRate - redisKey := fmt.Sprintf("bucket_%s_%s_%s", currency.Code, bucket.MinAmount, bucket.MaxAmount) + redisKey := fmt.Sprintf("bucket_%s_%s_%s_sell", currency.Code, bucket.MinAmount, bucket.MaxAmount) providerData := fmt.Sprintf("%s:%s:%s:%s:%s:%s", providerProfile.ID, token.Symbol, providerOrderToken.Network, - providerOrderToken.FixedConversionRate.String(), + providerOrderToken.FixedSellRate.String(), providerOrderToken.MinOrderAmount.String(), providerOrderToken.MaxOrderAmount.String(), ) @@ -224,7 +224,7 @@ func setup() error { SetReceiveAddressExpiry(expiry). SetFeePercent(decimal.NewFromFloat(0)). SetFeeAddress("0x1234567890123456789012345678901234567890"). - SetReturnAddress("0x0987654321098765432109876543210987654321"). + SetRefundOrRecipientAddress("0x0987654321098765432109876543210987654321"). SetInstitution("MOMONGPC"). SetAccountIdentifier("1234567890"). SetAccountName("OK"). @@ -618,20 +618,21 @@ func TestSender(t *testing.T) { Save(context.Background()) assert.NoError(t, err, "Failed to update provider fiat balance") - // Add provider order token (required for rate validation) + // Add provider order token (required for rate validation; two-sided rates, 750 for Redis bucket) providerOrderToken, err := test.AddProviderOrderTokenToProvider(map[string]interface{}{ - "provider": providerProfile, - "token_id": int(testCtx.token.ID), // Ensure int type - "currency_id": currency.ID, - "network": testCtx.networkIdentifier, - "conversion_rate_type": "floating", - "fixed_conversion_rate": decimal.Zero, - "floating_conversion_rate": decimal.NewFromFloat(750), - "max_order_amount": decimal.NewFromFloat(10000), - "min_order_amount": decimal.NewFromFloat(1), - "max_order_amount_otc": decimal.Zero, - "min_order_amount_otc": decimal.Zero, - "settlement_address": "0x1234567890123456789012345678901234567890", + "provider": providerProfile, + "token_id": int(testCtx.token.ID), + "currency_id": currency.ID, + "network": testCtx.networkIdentifier, + "fixed_buy_rate": decimal.NewFromFloat(750), + "fixed_sell_rate": decimal.NewFromFloat(750), + "floating_buy_delta": decimal.Zero, + "floating_sell_delta": decimal.Zero, + "max_order_amount": decimal.NewFromFloat(10000), + "min_order_amount": decimal.NewFromFloat(1), + "max_order_amount_otc": decimal.Zero, + "min_order_amount_otc": decimal.Zero, + "settlement_address": "0x1234567890123456789012345678901234567890", }) if err != nil { t.Logf("Failed to create provider order token: %v", err) @@ -650,12 +651,13 @@ func TestSender(t *testing.T) { assert.NoError(t, err) // Populate Redis bucket with provider data for validateBucketRate - redisKey := fmt.Sprintf("bucket_%s_%s_%s", currency.Code, bucket.MinAmount, bucket.MaxAmount) + // In floating-rate tests, approximate current provider rate using fixed_sell_rate for deterministic behavior + redisKey := fmt.Sprintf("bucket_%s_%s_%s_sell", currency.Code, bucket.MinAmount, bucket.MaxAmount) providerData := fmt.Sprintf("%s:%s:%s:%s:%s:%s", providerProfile.ID, testCtx.token.Symbol, providerOrderToken.Network, - providerOrderToken.FloatingConversionRate.String(), + providerOrderToken.FixedSellRate.String(), providerOrderToken.MinOrderAmount.String(), providerOrderToken.MaxOrderAmount.String(), ) @@ -836,12 +838,12 @@ func TestSender(t *testing.T) { assert.NoError(t, err) // Populate Redis bucket with provider data for validateBucketRate - redisKey := fmt.Sprintf("bucket_%s_%s_%s", currency.Code, bucket.MinAmount, bucket.MaxAmount) + redisKey := fmt.Sprintf("bucket_%s_%s_%s_sell", currency.Code, bucket.MinAmount, bucket.MaxAmount) providerData := fmt.Sprintf("%s:%s:%s:%s:%s:%s", providerProfile.ID, testCtx.token.Symbol, providerOrderToken.Network, - providerOrderToken.FloatingConversionRate.String(), + providerOrderToken.FixedSellRate.String(), providerOrderToken.MinOrderAmount.String(), providerOrderToken.MaxOrderAmount.String(), ) @@ -1022,12 +1024,12 @@ func TestSender(t *testing.T) { assert.NoError(t, err) // Populate Redis bucket with provider data for validateBucketRate - redisKey := fmt.Sprintf("bucket_%s_%s_%s", currency.Code, bucket.MinAmount, bucket.MaxAmount) + redisKey := fmt.Sprintf("bucket_%s_%s_%s_sell", currency.Code, bucket.MinAmount, bucket.MaxAmount) providerData := fmt.Sprintf("%s:%s:%s:%s:%s:%s", providerProfile.ID, testCtx.token.Symbol, providerOrderToken.Network, - providerOrderToken.FixedConversionRate.String(), + providerOrderToken.FixedSellRate.String(), providerOrderToken.MinOrderAmount.String(), providerOrderToken.MaxOrderAmount.String(), ) @@ -1527,7 +1529,7 @@ func TestSender(t *testing.T) { SetReceiveAddressExpiry(expiry). SetFeePercent(decimal.NewFromFloat(5.0)). SetFeeAddress("0x1234567890123456789012345678901234567890"). - SetReturnAddress("0x0987654321098765432109876543210987654321"). + SetRefundOrRecipientAddress("0x0987654321098765432109876543210987654321"). SetInstitution("MOMONGPC"). SetAccountIdentifier("1234567890"). SetAccountName("OK"). @@ -1718,7 +1720,7 @@ func TestSender(t *testing.T) { SetReceiveAddressExpiry(expiry2). SetFeePercent(decimal.NewFromFloat(0)). SetFeeAddress("0x1234567890123456789012345678901234567890"). - SetReturnAddress("0x0987654321098765432109876543210987654321"). + SetRefundOrRecipientAddress("0x0987654321098765432109876543210987654321"). SetReference("unique_ref_second_sender"). SetInstitution("MOMONGPC"). SetAccountIdentifier("9876543210"). @@ -2015,7 +2017,7 @@ func TestSender(t *testing.T) { "source": map[string]interface{}{ "type": "crypto", "currency": testCtx.token.Symbol, - "paymentRail": network.Identifier, + "network": network.Identifier, "refundAddress": "0x1234567890123456789012345678901234567890", }, "destination": map[string]interface{}{ @@ -2060,7 +2062,7 @@ func TestSender(t *testing.T) { "source": map[string]interface{}{ "type": "crypto", "currency": testCtx.token.Symbol, - "paymentRail": network.Identifier, + "network": network.Identifier, "refundAddress": "0x1234567890123456789012345678901234567890", }, "destination": map[string]interface{}{ @@ -2114,7 +2116,7 @@ func TestSender(t *testing.T) { "source": map[string]interface{}{ "type": "crypto", "currency": testCtx.token.Symbol, - "paymentRail": network.Identifier, + "network": network.Identifier, "refundAddress": "0x1234567890123456789012345678901234567890", }, "destination": map[string]interface{}{ @@ -2172,6 +2174,7 @@ func TestSender(t *testing.T) { providerAccount, ok := data["providerAccount"].(map[string]interface{}) assert.True(t, ok, "providerAccount should be present") + assert.NotEmpty(t, providerAccount["network"]) assert.NotEmpty(t, providerAccount["receiveAddress"]) assert.NotEmpty(t, providerAccount["validUntil"]) }) diff --git a/ent/fiatcurrency.go b/ent/fiatcurrency.go index 9afdc5ded..bc7db84e9 100644 --- a/ent/fiatcurrency.go +++ b/ent/fiatcurrency.go @@ -33,8 +33,10 @@ type FiatCurrency struct { Symbol string `json:"symbol,omitempty"` // Name holds the value of the "name" field. Name string `json:"name,omitempty"` - // MarketRate holds the value of the "market_rate" field. - MarketRate decimal.Decimal `json:"market_rate,omitempty"` + // MarketBuyRate holds the value of the "market_buy_rate" field. + MarketBuyRate decimal.Decimal `json:"market_buy_rate,omitempty"` + // MarketSellRate holds the value of the "market_sell_rate" field. + MarketSellRate decimal.Decimal `json:"market_sell_rate,omitempty"` // IsEnabled holds the value of the "is_enabled" field. IsEnabled bool `json:"is_enabled,omitempty"` // Edges holds the relations/edges for other nodes in the graph. @@ -99,7 +101,7 @@ func (*FiatCurrency) scanValues(columns []string) ([]any, error) { values := make([]any, len(columns)) for i := range columns { switch columns[i] { - case fiatcurrency.FieldMarketRate: + case fiatcurrency.FieldMarketBuyRate, fiatcurrency.FieldMarketSellRate: values[i] = new(decimal.Decimal) case fiatcurrency.FieldIsEnabled: values[i] = new(sql.NullBool) @@ -174,11 +176,17 @@ func (_m *FiatCurrency) assignValues(columns []string, values []any) error { } else if value.Valid { _m.Name = value.String } - case fiatcurrency.FieldMarketRate: + case fiatcurrency.FieldMarketBuyRate: if value, ok := values[i].(*decimal.Decimal); !ok { - return fmt.Errorf("unexpected type %T for field market_rate", values[i]) + return fmt.Errorf("unexpected type %T for field market_buy_rate", values[i]) } else if value != nil { - _m.MarketRate = *value + _m.MarketBuyRate = *value + } + case fiatcurrency.FieldMarketSellRate: + if value, ok := values[i].(*decimal.Decimal); !ok { + return fmt.Errorf("unexpected type %T for field market_sell_rate", values[i]) + } else if value != nil { + _m.MarketSellRate = *value } case fiatcurrency.FieldIsEnabled: if value, ok := values[i].(*sql.NullBool); !ok { @@ -263,8 +271,11 @@ func (_m *FiatCurrency) String() string { builder.WriteString("name=") builder.WriteString(_m.Name) builder.WriteString(", ") - builder.WriteString("market_rate=") - builder.WriteString(fmt.Sprintf("%v", _m.MarketRate)) + builder.WriteString("market_buy_rate=") + builder.WriteString(fmt.Sprintf("%v", _m.MarketBuyRate)) + builder.WriteString(", ") + builder.WriteString("market_sell_rate=") + builder.WriteString(fmt.Sprintf("%v", _m.MarketSellRate)) builder.WriteString(", ") builder.WriteString("is_enabled=") builder.WriteString(fmt.Sprintf("%v", _m.IsEnabled)) diff --git a/ent/fiatcurrency/fiatcurrency.go b/ent/fiatcurrency/fiatcurrency.go index d182c6b95..93e8d5dea 100644 --- a/ent/fiatcurrency/fiatcurrency.go +++ b/ent/fiatcurrency/fiatcurrency.go @@ -29,8 +29,10 @@ const ( FieldSymbol = "symbol" // FieldName holds the string denoting the name field in the database. FieldName = "name" - // FieldMarketRate holds the string denoting the market_rate field in the database. - FieldMarketRate = "market_rate" + // FieldMarketBuyRate holds the string denoting the market_buy_rate field in the database. + FieldMarketBuyRate = "market_buy_rate" + // FieldMarketSellRate holds the string denoting the market_sell_rate field in the database. + FieldMarketSellRate = "market_sell_rate" // FieldIsEnabled holds the string denoting the is_enabled field in the database. FieldIsEnabled = "is_enabled" // EdgeProviderBalances holds the string denoting the provider_balances edge name in mutations. @@ -83,7 +85,8 @@ var Columns = []string{ FieldDecimals, FieldSymbol, FieldName, - FieldMarketRate, + FieldMarketBuyRate, + FieldMarketSellRate, FieldIsEnabled, } @@ -155,9 +158,14 @@ func ByName(opts ...sql.OrderTermOption) OrderOption { return sql.OrderByField(FieldName, opts...).ToFunc() } -// ByMarketRate orders the results by the market_rate field. -func ByMarketRate(opts ...sql.OrderTermOption) OrderOption { - return sql.OrderByField(FieldMarketRate, opts...).ToFunc() +// ByMarketBuyRate orders the results by the market_buy_rate field. +func ByMarketBuyRate(opts ...sql.OrderTermOption) OrderOption { + return sql.OrderByField(FieldMarketBuyRate, opts...).ToFunc() +} + +// ByMarketSellRate orders the results by the market_sell_rate field. +func ByMarketSellRate(opts ...sql.OrderTermOption) OrderOption { + return sql.OrderByField(FieldMarketSellRate, opts...).ToFunc() } // ByIsEnabled orders the results by the is_enabled field. diff --git a/ent/fiatcurrency/where.go b/ent/fiatcurrency/where.go index b1fec3e58..7266d961f 100644 --- a/ent/fiatcurrency/where.go +++ b/ent/fiatcurrency/where.go @@ -92,9 +92,14 @@ func Name(v string) predicate.FiatCurrency { return predicate.FiatCurrency(sql.FieldEQ(FieldName, v)) } -// MarketRate applies equality check predicate on the "market_rate" field. It's identical to MarketRateEQ. -func MarketRate(v decimal.Decimal) predicate.FiatCurrency { - return predicate.FiatCurrency(sql.FieldEQ(FieldMarketRate, v)) +// MarketBuyRate applies equality check predicate on the "market_buy_rate" field. It's identical to MarketBuyRateEQ. +func MarketBuyRate(v decimal.Decimal) predicate.FiatCurrency { + return predicate.FiatCurrency(sql.FieldEQ(FieldMarketBuyRate, v)) +} + +// MarketSellRate applies equality check predicate on the "market_sell_rate" field. It's identical to MarketSellRateEQ. +func MarketSellRate(v decimal.Decimal) predicate.FiatCurrency { + return predicate.FiatCurrency(sql.FieldEQ(FieldMarketSellRate, v)) } // IsEnabled applies equality check predicate on the "is_enabled" field. It's identical to IsEnabledEQ. @@ -482,44 +487,104 @@ func NameContainsFold(v string) predicate.FiatCurrency { return predicate.FiatCurrency(sql.FieldContainsFold(FieldName, v)) } -// MarketRateEQ applies the EQ predicate on the "market_rate" field. -func MarketRateEQ(v decimal.Decimal) predicate.FiatCurrency { - return predicate.FiatCurrency(sql.FieldEQ(FieldMarketRate, v)) +// MarketBuyRateEQ applies the EQ predicate on the "market_buy_rate" field. +func MarketBuyRateEQ(v decimal.Decimal) predicate.FiatCurrency { + return predicate.FiatCurrency(sql.FieldEQ(FieldMarketBuyRate, v)) +} + +// MarketBuyRateNEQ applies the NEQ predicate on the "market_buy_rate" field. +func MarketBuyRateNEQ(v decimal.Decimal) predicate.FiatCurrency { + return predicate.FiatCurrency(sql.FieldNEQ(FieldMarketBuyRate, v)) +} + +// MarketBuyRateIn applies the In predicate on the "market_buy_rate" field. +func MarketBuyRateIn(vs ...decimal.Decimal) predicate.FiatCurrency { + return predicate.FiatCurrency(sql.FieldIn(FieldMarketBuyRate, vs...)) +} + +// MarketBuyRateNotIn applies the NotIn predicate on the "market_buy_rate" field. +func MarketBuyRateNotIn(vs ...decimal.Decimal) predicate.FiatCurrency { + return predicate.FiatCurrency(sql.FieldNotIn(FieldMarketBuyRate, vs...)) +} + +// MarketBuyRateGT applies the GT predicate on the "market_buy_rate" field. +func MarketBuyRateGT(v decimal.Decimal) predicate.FiatCurrency { + return predicate.FiatCurrency(sql.FieldGT(FieldMarketBuyRate, v)) +} + +// MarketBuyRateGTE applies the GTE predicate on the "market_buy_rate" field. +func MarketBuyRateGTE(v decimal.Decimal) predicate.FiatCurrency { + return predicate.FiatCurrency(sql.FieldGTE(FieldMarketBuyRate, v)) +} + +// MarketBuyRateLT applies the LT predicate on the "market_buy_rate" field. +func MarketBuyRateLT(v decimal.Decimal) predicate.FiatCurrency { + return predicate.FiatCurrency(sql.FieldLT(FieldMarketBuyRate, v)) +} + +// MarketBuyRateLTE applies the LTE predicate on the "market_buy_rate" field. +func MarketBuyRateLTE(v decimal.Decimal) predicate.FiatCurrency { + return predicate.FiatCurrency(sql.FieldLTE(FieldMarketBuyRate, v)) +} + +// MarketBuyRateIsNil applies the IsNil predicate on the "market_buy_rate" field. +func MarketBuyRateIsNil() predicate.FiatCurrency { + return predicate.FiatCurrency(sql.FieldIsNull(FieldMarketBuyRate)) +} + +// MarketBuyRateNotNil applies the NotNil predicate on the "market_buy_rate" field. +func MarketBuyRateNotNil() predicate.FiatCurrency { + return predicate.FiatCurrency(sql.FieldNotNull(FieldMarketBuyRate)) +} + +// MarketSellRateEQ applies the EQ predicate on the "market_sell_rate" field. +func MarketSellRateEQ(v decimal.Decimal) predicate.FiatCurrency { + return predicate.FiatCurrency(sql.FieldEQ(FieldMarketSellRate, v)) +} + +// MarketSellRateNEQ applies the NEQ predicate on the "market_sell_rate" field. +func MarketSellRateNEQ(v decimal.Decimal) predicate.FiatCurrency { + return predicate.FiatCurrency(sql.FieldNEQ(FieldMarketSellRate, v)) +} + +// MarketSellRateIn applies the In predicate on the "market_sell_rate" field. +func MarketSellRateIn(vs ...decimal.Decimal) predicate.FiatCurrency { + return predicate.FiatCurrency(sql.FieldIn(FieldMarketSellRate, vs...)) } -// MarketRateNEQ applies the NEQ predicate on the "market_rate" field. -func MarketRateNEQ(v decimal.Decimal) predicate.FiatCurrency { - return predicate.FiatCurrency(sql.FieldNEQ(FieldMarketRate, v)) +// MarketSellRateNotIn applies the NotIn predicate on the "market_sell_rate" field. +func MarketSellRateNotIn(vs ...decimal.Decimal) predicate.FiatCurrency { + return predicate.FiatCurrency(sql.FieldNotIn(FieldMarketSellRate, vs...)) } -// MarketRateIn applies the In predicate on the "market_rate" field. -func MarketRateIn(vs ...decimal.Decimal) predicate.FiatCurrency { - return predicate.FiatCurrency(sql.FieldIn(FieldMarketRate, vs...)) +// MarketSellRateGT applies the GT predicate on the "market_sell_rate" field. +func MarketSellRateGT(v decimal.Decimal) predicate.FiatCurrency { + return predicate.FiatCurrency(sql.FieldGT(FieldMarketSellRate, v)) } -// MarketRateNotIn applies the NotIn predicate on the "market_rate" field. -func MarketRateNotIn(vs ...decimal.Decimal) predicate.FiatCurrency { - return predicate.FiatCurrency(sql.FieldNotIn(FieldMarketRate, vs...)) +// MarketSellRateGTE applies the GTE predicate on the "market_sell_rate" field. +func MarketSellRateGTE(v decimal.Decimal) predicate.FiatCurrency { + return predicate.FiatCurrency(sql.FieldGTE(FieldMarketSellRate, v)) } -// MarketRateGT applies the GT predicate on the "market_rate" field. -func MarketRateGT(v decimal.Decimal) predicate.FiatCurrency { - return predicate.FiatCurrency(sql.FieldGT(FieldMarketRate, v)) +// MarketSellRateLT applies the LT predicate on the "market_sell_rate" field. +func MarketSellRateLT(v decimal.Decimal) predicate.FiatCurrency { + return predicate.FiatCurrency(sql.FieldLT(FieldMarketSellRate, v)) } -// MarketRateGTE applies the GTE predicate on the "market_rate" field. -func MarketRateGTE(v decimal.Decimal) predicate.FiatCurrency { - return predicate.FiatCurrency(sql.FieldGTE(FieldMarketRate, v)) +// MarketSellRateLTE applies the LTE predicate on the "market_sell_rate" field. +func MarketSellRateLTE(v decimal.Decimal) predicate.FiatCurrency { + return predicate.FiatCurrency(sql.FieldLTE(FieldMarketSellRate, v)) } -// MarketRateLT applies the LT predicate on the "market_rate" field. -func MarketRateLT(v decimal.Decimal) predicate.FiatCurrency { - return predicate.FiatCurrency(sql.FieldLT(FieldMarketRate, v)) +// MarketSellRateIsNil applies the IsNil predicate on the "market_sell_rate" field. +func MarketSellRateIsNil() predicate.FiatCurrency { + return predicate.FiatCurrency(sql.FieldIsNull(FieldMarketSellRate)) } -// MarketRateLTE applies the LTE predicate on the "market_rate" field. -func MarketRateLTE(v decimal.Decimal) predicate.FiatCurrency { - return predicate.FiatCurrency(sql.FieldLTE(FieldMarketRate, v)) +// MarketSellRateNotNil applies the NotNil predicate on the "market_sell_rate" field. +func MarketSellRateNotNil() predicate.FiatCurrency { + return predicate.FiatCurrency(sql.FieldNotNull(FieldMarketSellRate)) } // IsEnabledEQ applies the EQ predicate on the "is_enabled" field. diff --git a/ent/fiatcurrency_create.go b/ent/fiatcurrency_create.go index 7446ea0dc..334dd619f 100644 --- a/ent/fiatcurrency_create.go +++ b/ent/fiatcurrency_create.go @@ -95,9 +95,31 @@ func (_c *FiatCurrencyCreate) SetName(v string) *FiatCurrencyCreate { return _c } -// SetMarketRate sets the "market_rate" field. -func (_c *FiatCurrencyCreate) SetMarketRate(v decimal.Decimal) *FiatCurrencyCreate { - _c.mutation.SetMarketRate(v) +// SetMarketBuyRate sets the "market_buy_rate" field. +func (_c *FiatCurrencyCreate) SetMarketBuyRate(v decimal.Decimal) *FiatCurrencyCreate { + _c.mutation.SetMarketBuyRate(v) + return _c +} + +// SetNillableMarketBuyRate sets the "market_buy_rate" field if the given value is not nil. +func (_c *FiatCurrencyCreate) SetNillableMarketBuyRate(v *decimal.Decimal) *FiatCurrencyCreate { + if v != nil { + _c.SetMarketBuyRate(*v) + } + return _c +} + +// SetMarketSellRate sets the "market_sell_rate" field. +func (_c *FiatCurrencyCreate) SetMarketSellRate(v decimal.Decimal) *FiatCurrencyCreate { + _c.mutation.SetMarketSellRate(v) + return _c +} + +// SetNillableMarketSellRate sets the "market_sell_rate" field if the given value is not nil. +func (_c *FiatCurrencyCreate) SetNillableMarketSellRate(v *decimal.Decimal) *FiatCurrencyCreate { + if v != nil { + _c.SetMarketSellRate(*v) + } return _c } @@ -269,9 +291,6 @@ func (_c *FiatCurrencyCreate) check() error { if _, ok := _c.mutation.Name(); !ok { return &ValidationError{Name: "name", err: errors.New(`ent: missing required field "FiatCurrency.name"`)} } - if _, ok := _c.mutation.MarketRate(); !ok { - return &ValidationError{Name: "market_rate", err: errors.New(`ent: missing required field "FiatCurrency.market_rate"`)} - } if _, ok := _c.mutation.IsEnabled(); !ok { return &ValidationError{Name: "is_enabled", err: errors.New(`ent: missing required field "FiatCurrency.is_enabled"`)} } @@ -339,9 +358,13 @@ func (_c *FiatCurrencyCreate) createSpec() (*FiatCurrency, *sqlgraph.CreateSpec) _spec.SetField(fiatcurrency.FieldName, field.TypeString, value) _node.Name = value } - if value, ok := _c.mutation.MarketRate(); ok { - _spec.SetField(fiatcurrency.FieldMarketRate, field.TypeFloat64, value) - _node.MarketRate = value + if value, ok := _c.mutation.MarketBuyRate(); ok { + _spec.SetField(fiatcurrency.FieldMarketBuyRate, field.TypeFloat64, value) + _node.MarketBuyRate = value + } + if value, ok := _c.mutation.MarketSellRate(); ok { + _spec.SetField(fiatcurrency.FieldMarketSellRate, field.TypeFloat64, value) + _node.MarketSellRate = value } if value, ok := _c.mutation.IsEnabled(); ok { _spec.SetField(fiatcurrency.FieldIsEnabled, field.TypeBool, value) @@ -541,21 +564,51 @@ func (u *FiatCurrencyUpsert) UpdateName() *FiatCurrencyUpsert { return u } -// SetMarketRate sets the "market_rate" field. -func (u *FiatCurrencyUpsert) SetMarketRate(v decimal.Decimal) *FiatCurrencyUpsert { - u.Set(fiatcurrency.FieldMarketRate, v) +// SetMarketBuyRate sets the "market_buy_rate" field. +func (u *FiatCurrencyUpsert) SetMarketBuyRate(v decimal.Decimal) *FiatCurrencyUpsert { + u.Set(fiatcurrency.FieldMarketBuyRate, v) + return u +} + +// UpdateMarketBuyRate sets the "market_buy_rate" field to the value that was provided on create. +func (u *FiatCurrencyUpsert) UpdateMarketBuyRate() *FiatCurrencyUpsert { + u.SetExcluded(fiatcurrency.FieldMarketBuyRate) return u } -// UpdateMarketRate sets the "market_rate" field to the value that was provided on create. -func (u *FiatCurrencyUpsert) UpdateMarketRate() *FiatCurrencyUpsert { - u.SetExcluded(fiatcurrency.FieldMarketRate) +// AddMarketBuyRate adds v to the "market_buy_rate" field. +func (u *FiatCurrencyUpsert) AddMarketBuyRate(v decimal.Decimal) *FiatCurrencyUpsert { + u.Add(fiatcurrency.FieldMarketBuyRate, v) return u } -// AddMarketRate adds v to the "market_rate" field. -func (u *FiatCurrencyUpsert) AddMarketRate(v decimal.Decimal) *FiatCurrencyUpsert { - u.Add(fiatcurrency.FieldMarketRate, v) +// ClearMarketBuyRate clears the value of the "market_buy_rate" field. +func (u *FiatCurrencyUpsert) ClearMarketBuyRate() *FiatCurrencyUpsert { + u.SetNull(fiatcurrency.FieldMarketBuyRate) + return u +} + +// SetMarketSellRate sets the "market_sell_rate" field. +func (u *FiatCurrencyUpsert) SetMarketSellRate(v decimal.Decimal) *FiatCurrencyUpsert { + u.Set(fiatcurrency.FieldMarketSellRate, v) + return u +} + +// UpdateMarketSellRate sets the "market_sell_rate" field to the value that was provided on create. +func (u *FiatCurrencyUpsert) UpdateMarketSellRate() *FiatCurrencyUpsert { + u.SetExcluded(fiatcurrency.FieldMarketSellRate) + return u +} + +// AddMarketSellRate adds v to the "market_sell_rate" field. +func (u *FiatCurrencyUpsert) AddMarketSellRate(v decimal.Decimal) *FiatCurrencyUpsert { + u.Add(fiatcurrency.FieldMarketSellRate, v) + return u +} + +// ClearMarketSellRate clears the value of the "market_sell_rate" field. +func (u *FiatCurrencyUpsert) ClearMarketSellRate() *FiatCurrencyUpsert { + u.SetNull(fiatcurrency.FieldMarketSellRate) return u } @@ -713,24 +766,59 @@ func (u *FiatCurrencyUpsertOne) UpdateName() *FiatCurrencyUpsertOne { }) } -// SetMarketRate sets the "market_rate" field. -func (u *FiatCurrencyUpsertOne) SetMarketRate(v decimal.Decimal) *FiatCurrencyUpsertOne { +// SetMarketBuyRate sets the "market_buy_rate" field. +func (u *FiatCurrencyUpsertOne) SetMarketBuyRate(v decimal.Decimal) *FiatCurrencyUpsertOne { + return u.Update(func(s *FiatCurrencyUpsert) { + s.SetMarketBuyRate(v) + }) +} + +// AddMarketBuyRate adds v to the "market_buy_rate" field. +func (u *FiatCurrencyUpsertOne) AddMarketBuyRate(v decimal.Decimal) *FiatCurrencyUpsertOne { + return u.Update(func(s *FiatCurrencyUpsert) { + s.AddMarketBuyRate(v) + }) +} + +// UpdateMarketBuyRate sets the "market_buy_rate" field to the value that was provided on create. +func (u *FiatCurrencyUpsertOne) UpdateMarketBuyRate() *FiatCurrencyUpsertOne { + return u.Update(func(s *FiatCurrencyUpsert) { + s.UpdateMarketBuyRate() + }) +} + +// ClearMarketBuyRate clears the value of the "market_buy_rate" field. +func (u *FiatCurrencyUpsertOne) ClearMarketBuyRate() *FiatCurrencyUpsertOne { + return u.Update(func(s *FiatCurrencyUpsert) { + s.ClearMarketBuyRate() + }) +} + +// SetMarketSellRate sets the "market_sell_rate" field. +func (u *FiatCurrencyUpsertOne) SetMarketSellRate(v decimal.Decimal) *FiatCurrencyUpsertOne { return u.Update(func(s *FiatCurrencyUpsert) { - s.SetMarketRate(v) + s.SetMarketSellRate(v) }) } -// AddMarketRate adds v to the "market_rate" field. -func (u *FiatCurrencyUpsertOne) AddMarketRate(v decimal.Decimal) *FiatCurrencyUpsertOne { +// AddMarketSellRate adds v to the "market_sell_rate" field. +func (u *FiatCurrencyUpsertOne) AddMarketSellRate(v decimal.Decimal) *FiatCurrencyUpsertOne { return u.Update(func(s *FiatCurrencyUpsert) { - s.AddMarketRate(v) + s.AddMarketSellRate(v) }) } -// UpdateMarketRate sets the "market_rate" field to the value that was provided on create. -func (u *FiatCurrencyUpsertOne) UpdateMarketRate() *FiatCurrencyUpsertOne { +// UpdateMarketSellRate sets the "market_sell_rate" field to the value that was provided on create. +func (u *FiatCurrencyUpsertOne) UpdateMarketSellRate() *FiatCurrencyUpsertOne { return u.Update(func(s *FiatCurrencyUpsert) { - s.UpdateMarketRate() + s.UpdateMarketSellRate() + }) +} + +// ClearMarketSellRate clears the value of the "market_sell_rate" field. +func (u *FiatCurrencyUpsertOne) ClearMarketSellRate() *FiatCurrencyUpsertOne { + return u.Update(func(s *FiatCurrencyUpsert) { + s.ClearMarketSellRate() }) } @@ -1057,24 +1145,59 @@ func (u *FiatCurrencyUpsertBulk) UpdateName() *FiatCurrencyUpsertBulk { }) } -// SetMarketRate sets the "market_rate" field. -func (u *FiatCurrencyUpsertBulk) SetMarketRate(v decimal.Decimal) *FiatCurrencyUpsertBulk { +// SetMarketBuyRate sets the "market_buy_rate" field. +func (u *FiatCurrencyUpsertBulk) SetMarketBuyRate(v decimal.Decimal) *FiatCurrencyUpsertBulk { + return u.Update(func(s *FiatCurrencyUpsert) { + s.SetMarketBuyRate(v) + }) +} + +// AddMarketBuyRate adds v to the "market_buy_rate" field. +func (u *FiatCurrencyUpsertBulk) AddMarketBuyRate(v decimal.Decimal) *FiatCurrencyUpsertBulk { + return u.Update(func(s *FiatCurrencyUpsert) { + s.AddMarketBuyRate(v) + }) +} + +// UpdateMarketBuyRate sets the "market_buy_rate" field to the value that was provided on create. +func (u *FiatCurrencyUpsertBulk) UpdateMarketBuyRate() *FiatCurrencyUpsertBulk { + return u.Update(func(s *FiatCurrencyUpsert) { + s.UpdateMarketBuyRate() + }) +} + +// ClearMarketBuyRate clears the value of the "market_buy_rate" field. +func (u *FiatCurrencyUpsertBulk) ClearMarketBuyRate() *FiatCurrencyUpsertBulk { + return u.Update(func(s *FiatCurrencyUpsert) { + s.ClearMarketBuyRate() + }) +} + +// SetMarketSellRate sets the "market_sell_rate" field. +func (u *FiatCurrencyUpsertBulk) SetMarketSellRate(v decimal.Decimal) *FiatCurrencyUpsertBulk { + return u.Update(func(s *FiatCurrencyUpsert) { + s.SetMarketSellRate(v) + }) +} + +// AddMarketSellRate adds v to the "market_sell_rate" field. +func (u *FiatCurrencyUpsertBulk) AddMarketSellRate(v decimal.Decimal) *FiatCurrencyUpsertBulk { return u.Update(func(s *FiatCurrencyUpsert) { - s.SetMarketRate(v) + s.AddMarketSellRate(v) }) } -// AddMarketRate adds v to the "market_rate" field. -func (u *FiatCurrencyUpsertBulk) AddMarketRate(v decimal.Decimal) *FiatCurrencyUpsertBulk { +// UpdateMarketSellRate sets the "market_sell_rate" field to the value that was provided on create. +func (u *FiatCurrencyUpsertBulk) UpdateMarketSellRate() *FiatCurrencyUpsertBulk { return u.Update(func(s *FiatCurrencyUpsert) { - s.AddMarketRate(v) + s.UpdateMarketSellRate() }) } -// UpdateMarketRate sets the "market_rate" field to the value that was provided on create. -func (u *FiatCurrencyUpsertBulk) UpdateMarketRate() *FiatCurrencyUpsertBulk { +// ClearMarketSellRate clears the value of the "market_sell_rate" field. +func (u *FiatCurrencyUpsertBulk) ClearMarketSellRate() *FiatCurrencyUpsertBulk { return u.Update(func(s *FiatCurrencyUpsert) { - s.UpdateMarketRate() + s.ClearMarketSellRate() }) } diff --git a/ent/fiatcurrency_update.go b/ent/fiatcurrency_update.go index a7dbe5956..baa229305 100644 --- a/ent/fiatcurrency_update.go +++ b/ent/fiatcurrency_update.go @@ -117,24 +117,57 @@ func (_u *FiatCurrencyUpdate) SetNillableName(v *string) *FiatCurrencyUpdate { return _u } -// SetMarketRate sets the "market_rate" field. -func (_u *FiatCurrencyUpdate) SetMarketRate(v decimal.Decimal) *FiatCurrencyUpdate { - _u.mutation.ResetMarketRate() - _u.mutation.SetMarketRate(v) +// SetMarketBuyRate sets the "market_buy_rate" field. +func (_u *FiatCurrencyUpdate) SetMarketBuyRate(v decimal.Decimal) *FiatCurrencyUpdate { + _u.mutation.ResetMarketBuyRate() + _u.mutation.SetMarketBuyRate(v) return _u } -// SetNillableMarketRate sets the "market_rate" field if the given value is not nil. -func (_u *FiatCurrencyUpdate) SetNillableMarketRate(v *decimal.Decimal) *FiatCurrencyUpdate { +// SetNillableMarketBuyRate sets the "market_buy_rate" field if the given value is not nil. +func (_u *FiatCurrencyUpdate) SetNillableMarketBuyRate(v *decimal.Decimal) *FiatCurrencyUpdate { if v != nil { - _u.SetMarketRate(*v) + _u.SetMarketBuyRate(*v) } return _u } -// AddMarketRate adds value to the "market_rate" field. -func (_u *FiatCurrencyUpdate) AddMarketRate(v decimal.Decimal) *FiatCurrencyUpdate { - _u.mutation.AddMarketRate(v) +// AddMarketBuyRate adds value to the "market_buy_rate" field. +func (_u *FiatCurrencyUpdate) AddMarketBuyRate(v decimal.Decimal) *FiatCurrencyUpdate { + _u.mutation.AddMarketBuyRate(v) + return _u +} + +// ClearMarketBuyRate clears the value of the "market_buy_rate" field. +func (_u *FiatCurrencyUpdate) ClearMarketBuyRate() *FiatCurrencyUpdate { + _u.mutation.ClearMarketBuyRate() + return _u +} + +// SetMarketSellRate sets the "market_sell_rate" field. +func (_u *FiatCurrencyUpdate) SetMarketSellRate(v decimal.Decimal) *FiatCurrencyUpdate { + _u.mutation.ResetMarketSellRate() + _u.mutation.SetMarketSellRate(v) + return _u +} + +// SetNillableMarketSellRate sets the "market_sell_rate" field if the given value is not nil. +func (_u *FiatCurrencyUpdate) SetNillableMarketSellRate(v *decimal.Decimal) *FiatCurrencyUpdate { + if v != nil { + _u.SetMarketSellRate(*v) + } + return _u +} + +// AddMarketSellRate adds value to the "market_sell_rate" field. +func (_u *FiatCurrencyUpdate) AddMarketSellRate(v decimal.Decimal) *FiatCurrencyUpdate { + _u.mutation.AddMarketSellRate(v) + return _u +} + +// ClearMarketSellRate clears the value of the "market_sell_rate" field. +func (_u *FiatCurrencyUpdate) ClearMarketSellRate() *FiatCurrencyUpdate { + _u.mutation.ClearMarketSellRate() return _u } @@ -367,11 +400,23 @@ func (_u *FiatCurrencyUpdate) sqlSave(ctx context.Context) (_node int, err error if value, ok := _u.mutation.Name(); ok { _spec.SetField(fiatcurrency.FieldName, field.TypeString, value) } - if value, ok := _u.mutation.MarketRate(); ok { - _spec.SetField(fiatcurrency.FieldMarketRate, field.TypeFloat64, value) + if value, ok := _u.mutation.MarketBuyRate(); ok { + _spec.SetField(fiatcurrency.FieldMarketBuyRate, field.TypeFloat64, value) + } + if value, ok := _u.mutation.AddedMarketBuyRate(); ok { + _spec.AddField(fiatcurrency.FieldMarketBuyRate, field.TypeFloat64, value) + } + if _u.mutation.MarketBuyRateCleared() { + _spec.ClearField(fiatcurrency.FieldMarketBuyRate, field.TypeFloat64) + } + if value, ok := _u.mutation.MarketSellRate(); ok { + _spec.SetField(fiatcurrency.FieldMarketSellRate, field.TypeFloat64, value) + } + if value, ok := _u.mutation.AddedMarketSellRate(); ok { + _spec.AddField(fiatcurrency.FieldMarketSellRate, field.TypeFloat64, value) } - if value, ok := _u.mutation.AddedMarketRate(); ok { - _spec.AddField(fiatcurrency.FieldMarketRate, field.TypeFloat64, value) + if _u.mutation.MarketSellRateCleared() { + _spec.ClearField(fiatcurrency.FieldMarketSellRate, field.TypeFloat64) } if value, ok := _u.mutation.IsEnabled(); ok { _spec.SetField(fiatcurrency.FieldIsEnabled, field.TypeBool, value) @@ -659,24 +704,57 @@ func (_u *FiatCurrencyUpdateOne) SetNillableName(v *string) *FiatCurrencyUpdateO return _u } -// SetMarketRate sets the "market_rate" field. -func (_u *FiatCurrencyUpdateOne) SetMarketRate(v decimal.Decimal) *FiatCurrencyUpdateOne { - _u.mutation.ResetMarketRate() - _u.mutation.SetMarketRate(v) +// SetMarketBuyRate sets the "market_buy_rate" field. +func (_u *FiatCurrencyUpdateOne) SetMarketBuyRate(v decimal.Decimal) *FiatCurrencyUpdateOne { + _u.mutation.ResetMarketBuyRate() + _u.mutation.SetMarketBuyRate(v) return _u } -// SetNillableMarketRate sets the "market_rate" field if the given value is not nil. -func (_u *FiatCurrencyUpdateOne) SetNillableMarketRate(v *decimal.Decimal) *FiatCurrencyUpdateOne { +// SetNillableMarketBuyRate sets the "market_buy_rate" field if the given value is not nil. +func (_u *FiatCurrencyUpdateOne) SetNillableMarketBuyRate(v *decimal.Decimal) *FiatCurrencyUpdateOne { if v != nil { - _u.SetMarketRate(*v) + _u.SetMarketBuyRate(*v) } return _u } -// AddMarketRate adds value to the "market_rate" field. -func (_u *FiatCurrencyUpdateOne) AddMarketRate(v decimal.Decimal) *FiatCurrencyUpdateOne { - _u.mutation.AddMarketRate(v) +// AddMarketBuyRate adds value to the "market_buy_rate" field. +func (_u *FiatCurrencyUpdateOne) AddMarketBuyRate(v decimal.Decimal) *FiatCurrencyUpdateOne { + _u.mutation.AddMarketBuyRate(v) + return _u +} + +// ClearMarketBuyRate clears the value of the "market_buy_rate" field. +func (_u *FiatCurrencyUpdateOne) ClearMarketBuyRate() *FiatCurrencyUpdateOne { + _u.mutation.ClearMarketBuyRate() + return _u +} + +// SetMarketSellRate sets the "market_sell_rate" field. +func (_u *FiatCurrencyUpdateOne) SetMarketSellRate(v decimal.Decimal) *FiatCurrencyUpdateOne { + _u.mutation.ResetMarketSellRate() + _u.mutation.SetMarketSellRate(v) + return _u +} + +// SetNillableMarketSellRate sets the "market_sell_rate" field if the given value is not nil. +func (_u *FiatCurrencyUpdateOne) SetNillableMarketSellRate(v *decimal.Decimal) *FiatCurrencyUpdateOne { + if v != nil { + _u.SetMarketSellRate(*v) + } + return _u +} + +// AddMarketSellRate adds value to the "market_sell_rate" field. +func (_u *FiatCurrencyUpdateOne) AddMarketSellRate(v decimal.Decimal) *FiatCurrencyUpdateOne { + _u.mutation.AddMarketSellRate(v) + return _u +} + +// ClearMarketSellRate clears the value of the "market_sell_rate" field. +func (_u *FiatCurrencyUpdateOne) ClearMarketSellRate() *FiatCurrencyUpdateOne { + _u.mutation.ClearMarketSellRate() return _u } @@ -939,11 +1017,23 @@ func (_u *FiatCurrencyUpdateOne) sqlSave(ctx context.Context) (_node *FiatCurren if value, ok := _u.mutation.Name(); ok { _spec.SetField(fiatcurrency.FieldName, field.TypeString, value) } - if value, ok := _u.mutation.MarketRate(); ok { - _spec.SetField(fiatcurrency.FieldMarketRate, field.TypeFloat64, value) + if value, ok := _u.mutation.MarketBuyRate(); ok { + _spec.SetField(fiatcurrency.FieldMarketBuyRate, field.TypeFloat64, value) + } + if value, ok := _u.mutation.AddedMarketBuyRate(); ok { + _spec.AddField(fiatcurrency.FieldMarketBuyRate, field.TypeFloat64, value) + } + if _u.mutation.MarketBuyRateCleared() { + _spec.ClearField(fiatcurrency.FieldMarketBuyRate, field.TypeFloat64) + } + if value, ok := _u.mutation.MarketSellRate(); ok { + _spec.SetField(fiatcurrency.FieldMarketSellRate, field.TypeFloat64, value) + } + if value, ok := _u.mutation.AddedMarketSellRate(); ok { + _spec.AddField(fiatcurrency.FieldMarketSellRate, field.TypeFloat64, value) } - if value, ok := _u.mutation.AddedMarketRate(); ok { - _spec.AddField(fiatcurrency.FieldMarketRate, field.TypeFloat64, value) + if _u.mutation.MarketSellRateCleared() { + _spec.ClearField(fiatcurrency.FieldMarketSellRate, field.TypeFloat64) } if value, ok := _u.mutation.IsEnabled(); ok { _spec.SetField(fiatcurrency.FieldIsEnabled, field.TypeBool, value) diff --git a/ent/migrate/schema.go b/ent/migrate/schema.go index acc028226..959b05489 100644 --- a/ent/migrate/schema.go +++ b/ent/migrate/schema.go @@ -71,7 +71,8 @@ var ( {Name: "decimals", Type: field.TypeInt, Default: 2}, {Name: "symbol", Type: field.TypeString}, {Name: "name", Type: field.TypeString}, - {Name: "market_rate", Type: field.TypeFloat64}, + {Name: "market_buy_rate", Type: field.TypeFloat64, Nullable: true}, + {Name: "market_sell_rate", Type: field.TypeFloat64, Nullable: true}, {Name: "is_enabled", Type: field.TypeBool, Default: false}, } // FiatCurrenciesTable holds the schema information for the "fiat_currencies" table. @@ -196,7 +197,7 @@ var ( {Name: "message_hash", Type: field.TypeString, Nullable: true}, {Name: "gateway_id", Type: field.TypeString, Nullable: true, Size: 255}, {Name: "from_address", Type: field.TypeString, Nullable: true, Size: 70}, - {Name: "return_address", Type: field.TypeString, Nullable: true, Size: 70}, + {Name: "refund_or_recipient_address", Type: field.TypeString, Nullable: true, Size: 70}, {Name: "receive_address", Type: field.TypeString, Unique: true, Nullable: true, Size: 70}, {Name: "receive_address_salt", Type: field.TypeBytes, Nullable: true}, {Name: "receive_address_expiry", Type: field.TypeTime, Nullable: true}, @@ -205,13 +206,14 @@ var ( {Name: "institution", Type: field.TypeString, Size: 255}, {Name: "account_identifier", Type: field.TypeString, Size: 255}, {Name: "account_name", Type: field.TypeString, Size: 255}, - {Name: "memo", Type: field.TypeString, Nullable: true, Size: 255}, {Name: "metadata", Type: field.TypeJSON, Nullable: true}, {Name: "sender", Type: field.TypeString, Nullable: true, Size: 255}, {Name: "reference", Type: field.TypeString, Nullable: true, Size: 70}, {Name: "cancellation_count", Type: field.TypeInt, Nullable: true, Default: 0}, {Name: "cancellation_reasons", Type: field.TypeJSON, Nullable: true}, + {Name: "memo", Type: field.TypeString, Nullable: true, Size: 255}, {Name: "status", Type: field.TypeEnum, Enums: []string{"initiated", "deposited", "pending", "fulfilling", "fulfilled", "validated", "settling", "settled", "cancelled", "refunding", "refunded", "expired"}, Default: "initiated"}, + {Name: "direction", Type: field.TypeEnum, Enums: []string{"offramp", "onramp"}, Default: "offramp"}, {Name: "order_type", Type: field.TypeEnum, Enums: []string{"otc", "regular"}, Default: "regular"}, {Name: "api_key_payment_orders", Type: field.TypeUUID, Nullable: true}, {Name: "provider_profile_assigned_orders", Type: field.TypeString, Nullable: true}, @@ -227,31 +229,31 @@ var ( ForeignKeys: []*schema.ForeignKey{ { Symbol: "payment_orders_api_keys_payment_orders", - Columns: []*schema.Column{PaymentOrdersColumns[36]}, + Columns: []*schema.Column{PaymentOrdersColumns[37]}, RefColumns: []*schema.Column{APIKeysColumns[0]}, OnDelete: schema.SetNull, }, { Symbol: "payment_orders_provider_profiles_assigned_orders", - Columns: []*schema.Column{PaymentOrdersColumns[37]}, + Columns: []*schema.Column{PaymentOrdersColumns[38]}, RefColumns: []*schema.Column{ProviderProfilesColumns[0]}, OnDelete: schema.Cascade, }, { Symbol: "payment_orders_provision_buckets_payment_orders", - Columns: []*schema.Column{PaymentOrdersColumns[38]}, + Columns: []*schema.Column{PaymentOrdersColumns[39]}, RefColumns: []*schema.Column{ProvisionBucketsColumns[0]}, OnDelete: schema.SetNull, }, { Symbol: "payment_orders_sender_profiles_payment_orders", - Columns: []*schema.Column{PaymentOrdersColumns[39]}, + Columns: []*schema.Column{PaymentOrdersColumns[40]}, RefColumns: []*schema.Column{SenderProfilesColumns[0]}, OnDelete: schema.SetNull, }, { Symbol: "payment_orders_tokens_payment_orders", - Columns: []*schema.Column{PaymentOrdersColumns[40]}, + Columns: []*schema.Column{PaymentOrdersColumns[41]}, RefColumns: []*schema.Column{TokensColumns[0]}, OnDelete: schema.Cascade, }, @@ -260,7 +262,7 @@ var ( { Name: "paymentorder_gateway_id_rate_tx_hash_block_number_institution_account_identifier_account_name_memo_token_payment_orders", Unique: true, - Columns: []*schema.Column{PaymentOrdersColumns[17], PaymentOrdersColumns[4], PaymentOrdersColumns[14], PaymentOrdersColumns[15], PaymentOrdersColumns[25], PaymentOrdersColumns[26], PaymentOrdersColumns[27], PaymentOrdersColumns[28], PaymentOrdersColumns[40]}, + Columns: []*schema.Column{PaymentOrdersColumns[17], PaymentOrdersColumns[4], PaymentOrdersColumns[14], PaymentOrdersColumns[15], PaymentOrdersColumns[25], PaymentOrdersColumns[26], PaymentOrdersColumns[27], PaymentOrdersColumns[33], PaymentOrdersColumns[41]}, }, }, } @@ -271,7 +273,7 @@ var ( {Name: "updated_at", Type: field.TypeTime}, {Name: "tx_id", Type: field.TypeString, Nullable: true}, {Name: "psp", Type: field.TypeString, Nullable: true}, - {Name: "validation_status", Type: field.TypeEnum, Enums: []string{"pending", "success", "failed"}, Default: "pending"}, + {Name: "validation_status", Type: field.TypeEnum, Enums: []string{"pending", "success", "failed", "refunded"}, Default: "pending"}, {Name: "validation_error", Type: field.TypeString, Nullable: true}, {Name: "payment_order_fulfillments", Type: field.TypeUUID}, } @@ -406,9 +408,10 @@ var ( {Name: "id", Type: field.TypeInt, Increment: true}, {Name: "created_at", Type: field.TypeTime}, {Name: "updated_at", Type: field.TypeTime}, - {Name: "fixed_conversion_rate", Type: field.TypeFloat64}, - {Name: "floating_conversion_rate", Type: field.TypeFloat64}, - {Name: "conversion_rate_type", Type: field.TypeEnum, Enums: []string{"fixed", "floating"}}, + {Name: "fixed_buy_rate", Type: field.TypeFloat64, Nullable: true}, + {Name: "fixed_sell_rate", Type: field.TypeFloat64, Nullable: true}, + {Name: "floating_buy_delta", Type: field.TypeFloat64, Nullable: true}, + {Name: "floating_sell_delta", Type: field.TypeFloat64, Nullable: true}, {Name: "max_order_amount", Type: field.TypeFloat64}, {Name: "min_order_amount", Type: field.TypeFloat64}, {Name: "max_order_amount_otc", Type: field.TypeFloat64}, @@ -429,19 +432,19 @@ var ( ForeignKeys: []*schema.ForeignKey{ { Symbol: "provider_order_tokens_fiat_currencies_provider_order_tokens", - Columns: []*schema.Column{ProviderOrderTokensColumns[14]}, + Columns: []*schema.Column{ProviderOrderTokensColumns[15]}, RefColumns: []*schema.Column{FiatCurrenciesColumns[0]}, OnDelete: schema.Cascade, }, { Symbol: "provider_order_tokens_provider_profiles_order_tokens", - Columns: []*schema.Column{ProviderOrderTokensColumns[15]}, + Columns: []*schema.Column{ProviderOrderTokensColumns[16]}, RefColumns: []*schema.Column{ProviderProfilesColumns[0]}, OnDelete: schema.Cascade, }, { Symbol: "provider_order_tokens_tokens_provider_order_tokens", - Columns: []*schema.Column{ProviderOrderTokensColumns[16]}, + Columns: []*schema.Column{ProviderOrderTokensColumns[17]}, RefColumns: []*schema.Column{TokensColumns[0]}, OnDelete: schema.Cascade, }, @@ -450,7 +453,7 @@ var ( { Name: "providerordertoken_network_provider_profile_order_tokens_token_provider_order_tokens_fiat_currency_provider_order_tokens", Unique: true, - Columns: []*schema.Column{ProviderOrderTokensColumns[13], ProviderOrderTokensColumns[15], ProviderOrderTokensColumns[16], ProviderOrderTokensColumns[14]}, + Columns: []*schema.Column{ProviderOrderTokensColumns[14], ProviderOrderTokensColumns[16], ProviderOrderTokensColumns[17], ProviderOrderTokensColumns[15]}, }, }, } @@ -567,6 +570,7 @@ var ( SenderProfilesColumns = []*schema.Column{ {Name: "id", Type: field.TypeUUID}, {Name: "webhook_url", Type: field.TypeString, Nullable: true}, + {Name: "webhook_version", Type: field.TypeString, Nullable: true, Default: "1"}, {Name: "domain_whitelist", Type: field.TypeJSON}, {Name: "provider_id", Type: field.TypeString, Nullable: true}, {Name: "is_partner", Type: field.TypeBool, Default: false}, @@ -582,7 +586,7 @@ var ( ForeignKeys: []*schema.ForeignKey{ { Symbol: "sender_profiles_users_sender_profile", - Columns: []*schema.Column{SenderProfilesColumns[7]}, + Columns: []*schema.Column{SenderProfilesColumns[8]}, RefColumns: []*schema.Column{UsersColumns[0]}, OnDelete: schema.Cascade, }, @@ -618,7 +622,7 @@ var ( TransactionLogsColumns = []*schema.Column{ {Name: "id", Type: field.TypeUUID}, {Name: "gateway_id", Type: field.TypeString, Nullable: true}, - {Name: "status", Type: field.TypeEnum, Enums: []string{"order_initiated", "crypto_deposited", "order_created", "order_processing", "order_fulfilled", "order_validated", "order_settled", "order_refunded", "gas_prefunded", "gateway_approved"}, Default: "order_initiated"}, + {Name: "status", Type: field.TypeEnum, Enums: []string{"order_initiated", "crypto_deposited", "order_created", "order_fulfilling", "order_fulfilled", "order_validated", "order_settling", "order_settled", "order_refunding", "order_refunded", "gas_prefunded", "gateway_approved"}, Default: "order_initiated"}, {Name: "network", Type: field.TypeString, Nullable: true}, {Name: "tx_hash", Type: field.TypeString, Nullable: true}, {Name: "created_at", Type: field.TypeTime}, diff --git a/ent/mutation.go b/ent/mutation.go index e69f6fd9a..4f43c77ce 100644 --- a/ent/mutation.go +++ b/ent/mutation.go @@ -1411,8 +1411,10 @@ type FiatCurrencyMutation struct { adddecimals *int symbol *string name *string - market_rate *decimal.Decimal - addmarket_rate *decimal.Decimal + market_buy_rate *decimal.Decimal + addmarket_buy_rate *decimal.Decimal + market_sell_rate *decimal.Decimal + addmarket_sell_rate *decimal.Decimal is_enabled *bool clearedFields map[string]struct{} provider_balances map[uuid.UUID]struct{} @@ -1808,60 +1810,144 @@ func (m *FiatCurrencyMutation) ResetName() { m.name = nil } -// SetMarketRate sets the "market_rate" field. -func (m *FiatCurrencyMutation) SetMarketRate(d decimal.Decimal) { - m.market_rate = &d - m.addmarket_rate = nil +// SetMarketBuyRate sets the "market_buy_rate" field. +func (m *FiatCurrencyMutation) SetMarketBuyRate(d decimal.Decimal) { + m.market_buy_rate = &d + m.addmarket_buy_rate = nil } -// MarketRate returns the value of the "market_rate" field in the mutation. -func (m *FiatCurrencyMutation) MarketRate() (r decimal.Decimal, exists bool) { - v := m.market_rate +// MarketBuyRate returns the value of the "market_buy_rate" field in the mutation. +func (m *FiatCurrencyMutation) MarketBuyRate() (r decimal.Decimal, exists bool) { + v := m.market_buy_rate if v == nil { return } return *v, true } -// OldMarketRate returns the old "market_rate" field's value of the FiatCurrency entity. +// OldMarketBuyRate returns the old "market_buy_rate" field's value of the FiatCurrency entity. // If the FiatCurrency object wasn't provided to the builder, the object is fetched from the database. // An error is returned if the mutation operation is not UpdateOne, or the database query fails. -func (m *FiatCurrencyMutation) OldMarketRate(ctx context.Context) (v decimal.Decimal, err error) { +func (m *FiatCurrencyMutation) OldMarketBuyRate(ctx context.Context) (v decimal.Decimal, err error) { if !m.op.Is(OpUpdateOne) { - return v, errors.New("OldMarketRate is only allowed on UpdateOne operations") + return v, errors.New("OldMarketBuyRate is only allowed on UpdateOne operations") } if m.id == nil || m.oldValue == nil { - return v, errors.New("OldMarketRate requires an ID field in the mutation") + return v, errors.New("OldMarketBuyRate requires an ID field in the mutation") } oldValue, err := m.oldValue(ctx) if err != nil { - return v, fmt.Errorf("querying old value for OldMarketRate: %w", err) + return v, fmt.Errorf("querying old value for OldMarketBuyRate: %w", err) } - return oldValue.MarketRate, nil + return oldValue.MarketBuyRate, nil } -// AddMarketRate adds d to the "market_rate" field. -func (m *FiatCurrencyMutation) AddMarketRate(d decimal.Decimal) { - if m.addmarket_rate != nil { - *m.addmarket_rate = m.addmarket_rate.Add(d) +// AddMarketBuyRate adds d to the "market_buy_rate" field. +func (m *FiatCurrencyMutation) AddMarketBuyRate(d decimal.Decimal) { + if m.addmarket_buy_rate != nil { + *m.addmarket_buy_rate = m.addmarket_buy_rate.Add(d) } else { - m.addmarket_rate = &d + m.addmarket_buy_rate = &d } } -// AddedMarketRate returns the value that was added to the "market_rate" field in this mutation. -func (m *FiatCurrencyMutation) AddedMarketRate() (r decimal.Decimal, exists bool) { - v := m.addmarket_rate +// AddedMarketBuyRate returns the value that was added to the "market_buy_rate" field in this mutation. +func (m *FiatCurrencyMutation) AddedMarketBuyRate() (r decimal.Decimal, exists bool) { + v := m.addmarket_buy_rate if v == nil { return } return *v, true } -// ResetMarketRate resets all changes to the "market_rate" field. -func (m *FiatCurrencyMutation) ResetMarketRate() { - m.market_rate = nil - m.addmarket_rate = nil +// ClearMarketBuyRate clears the value of the "market_buy_rate" field. +func (m *FiatCurrencyMutation) ClearMarketBuyRate() { + m.market_buy_rate = nil + m.addmarket_buy_rate = nil + m.clearedFields[fiatcurrency.FieldMarketBuyRate] = struct{}{} +} + +// MarketBuyRateCleared returns if the "market_buy_rate" field was cleared in this mutation. +func (m *FiatCurrencyMutation) MarketBuyRateCleared() bool { + _, ok := m.clearedFields[fiatcurrency.FieldMarketBuyRate] + return ok +} + +// ResetMarketBuyRate resets all changes to the "market_buy_rate" field. +func (m *FiatCurrencyMutation) ResetMarketBuyRate() { + m.market_buy_rate = nil + m.addmarket_buy_rate = nil + delete(m.clearedFields, fiatcurrency.FieldMarketBuyRate) +} + +// SetMarketSellRate sets the "market_sell_rate" field. +func (m *FiatCurrencyMutation) SetMarketSellRate(d decimal.Decimal) { + m.market_sell_rate = &d + m.addmarket_sell_rate = nil +} + +// MarketSellRate returns the value of the "market_sell_rate" field in the mutation. +func (m *FiatCurrencyMutation) MarketSellRate() (r decimal.Decimal, exists bool) { + v := m.market_sell_rate + if v == nil { + return + } + return *v, true +} + +// OldMarketSellRate returns the old "market_sell_rate" field's value of the FiatCurrency entity. +// If the FiatCurrency object wasn't provided to the builder, the object is fetched from the database. +// An error is returned if the mutation operation is not UpdateOne, or the database query fails. +func (m *FiatCurrencyMutation) OldMarketSellRate(ctx context.Context) (v decimal.Decimal, err error) { + if !m.op.Is(OpUpdateOne) { + return v, errors.New("OldMarketSellRate is only allowed on UpdateOne operations") + } + if m.id == nil || m.oldValue == nil { + return v, errors.New("OldMarketSellRate requires an ID field in the mutation") + } + oldValue, err := m.oldValue(ctx) + if err != nil { + return v, fmt.Errorf("querying old value for OldMarketSellRate: %w", err) + } + return oldValue.MarketSellRate, nil +} + +// AddMarketSellRate adds d to the "market_sell_rate" field. +func (m *FiatCurrencyMutation) AddMarketSellRate(d decimal.Decimal) { + if m.addmarket_sell_rate != nil { + *m.addmarket_sell_rate = m.addmarket_sell_rate.Add(d) + } else { + m.addmarket_sell_rate = &d + } +} + +// AddedMarketSellRate returns the value that was added to the "market_sell_rate" field in this mutation. +func (m *FiatCurrencyMutation) AddedMarketSellRate() (r decimal.Decimal, exists bool) { + v := m.addmarket_sell_rate + if v == nil { + return + } + return *v, true +} + +// ClearMarketSellRate clears the value of the "market_sell_rate" field. +func (m *FiatCurrencyMutation) ClearMarketSellRate() { + m.market_sell_rate = nil + m.addmarket_sell_rate = nil + m.clearedFields[fiatcurrency.FieldMarketSellRate] = struct{}{} +} + +// MarketSellRateCleared returns if the "market_sell_rate" field was cleared in this mutation. +func (m *FiatCurrencyMutation) MarketSellRateCleared() bool { + _, ok := m.clearedFields[fiatcurrency.FieldMarketSellRate] + return ok +} + +// ResetMarketSellRate resets all changes to the "market_sell_rate" field. +func (m *FiatCurrencyMutation) ResetMarketSellRate() { + m.market_sell_rate = nil + m.addmarket_sell_rate = nil + delete(m.clearedFields, fiatcurrency.FieldMarketSellRate) } // SetIsEnabled sets the "is_enabled" field. @@ -2150,7 +2236,7 @@ func (m *FiatCurrencyMutation) Type() string { // order to get all numeric fields that were incremented/decremented, call // AddedFields(). func (m *FiatCurrencyMutation) Fields() []string { - fields := make([]string, 0, 9) + fields := make([]string, 0, 10) if m.created_at != nil { fields = append(fields, fiatcurrency.FieldCreatedAt) } @@ -2172,8 +2258,11 @@ func (m *FiatCurrencyMutation) Fields() []string { if m.name != nil { fields = append(fields, fiatcurrency.FieldName) } - if m.market_rate != nil { - fields = append(fields, fiatcurrency.FieldMarketRate) + if m.market_buy_rate != nil { + fields = append(fields, fiatcurrency.FieldMarketBuyRate) + } + if m.market_sell_rate != nil { + fields = append(fields, fiatcurrency.FieldMarketSellRate) } if m.is_enabled != nil { fields = append(fields, fiatcurrency.FieldIsEnabled) @@ -2200,8 +2289,10 @@ func (m *FiatCurrencyMutation) Field(name string) (ent.Value, bool) { return m.Symbol() case fiatcurrency.FieldName: return m.Name() - case fiatcurrency.FieldMarketRate: - return m.MarketRate() + case fiatcurrency.FieldMarketBuyRate: + return m.MarketBuyRate() + case fiatcurrency.FieldMarketSellRate: + return m.MarketSellRate() case fiatcurrency.FieldIsEnabled: return m.IsEnabled() } @@ -2227,8 +2318,10 @@ func (m *FiatCurrencyMutation) OldField(ctx context.Context, name string) (ent.V return m.OldSymbol(ctx) case fiatcurrency.FieldName: return m.OldName(ctx) - case fiatcurrency.FieldMarketRate: - return m.OldMarketRate(ctx) + case fiatcurrency.FieldMarketBuyRate: + return m.OldMarketBuyRate(ctx) + case fiatcurrency.FieldMarketSellRate: + return m.OldMarketSellRate(ctx) case fiatcurrency.FieldIsEnabled: return m.OldIsEnabled(ctx) } @@ -2289,12 +2382,19 @@ func (m *FiatCurrencyMutation) SetField(name string, value ent.Value) error { } m.SetName(v) return nil - case fiatcurrency.FieldMarketRate: + case fiatcurrency.FieldMarketBuyRate: v, ok := value.(decimal.Decimal) if !ok { return fmt.Errorf("unexpected type %T for field %s", value, name) } - m.SetMarketRate(v) + m.SetMarketBuyRate(v) + return nil + case fiatcurrency.FieldMarketSellRate: + v, ok := value.(decimal.Decimal) + if !ok { + return fmt.Errorf("unexpected type %T for field %s", value, name) + } + m.SetMarketSellRate(v) return nil case fiatcurrency.FieldIsEnabled: v, ok := value.(bool) @@ -2314,8 +2414,11 @@ func (m *FiatCurrencyMutation) AddedFields() []string { if m.adddecimals != nil { fields = append(fields, fiatcurrency.FieldDecimals) } - if m.addmarket_rate != nil { - fields = append(fields, fiatcurrency.FieldMarketRate) + if m.addmarket_buy_rate != nil { + fields = append(fields, fiatcurrency.FieldMarketBuyRate) + } + if m.addmarket_sell_rate != nil { + fields = append(fields, fiatcurrency.FieldMarketSellRate) } return fields } @@ -2327,8 +2430,10 @@ func (m *FiatCurrencyMutation) AddedField(name string) (ent.Value, bool) { switch name { case fiatcurrency.FieldDecimals: return m.AddedDecimals() - case fiatcurrency.FieldMarketRate: - return m.AddedMarketRate() + case fiatcurrency.FieldMarketBuyRate: + return m.AddedMarketBuyRate() + case fiatcurrency.FieldMarketSellRate: + return m.AddedMarketSellRate() } return nil, false } @@ -2345,12 +2450,19 @@ func (m *FiatCurrencyMutation) AddField(name string, value ent.Value) error { } m.AddDecimals(v) return nil - case fiatcurrency.FieldMarketRate: + case fiatcurrency.FieldMarketBuyRate: v, ok := value.(decimal.Decimal) if !ok { return fmt.Errorf("unexpected type %T for field %s", value, name) } - m.AddMarketRate(v) + m.AddMarketBuyRate(v) + return nil + case fiatcurrency.FieldMarketSellRate: + v, ok := value.(decimal.Decimal) + if !ok { + return fmt.Errorf("unexpected type %T for field %s", value, name) + } + m.AddMarketSellRate(v) return nil } return fmt.Errorf("unknown FiatCurrency numeric field %s", name) @@ -2359,7 +2471,14 @@ func (m *FiatCurrencyMutation) AddField(name string, value ent.Value) error { // ClearedFields returns all nullable fields that were cleared during this // mutation. func (m *FiatCurrencyMutation) ClearedFields() []string { - return nil + var fields []string + if m.FieldCleared(fiatcurrency.FieldMarketBuyRate) { + fields = append(fields, fiatcurrency.FieldMarketBuyRate) + } + if m.FieldCleared(fiatcurrency.FieldMarketSellRate) { + fields = append(fields, fiatcurrency.FieldMarketSellRate) + } + return fields } // FieldCleared returns a boolean indicating if a field with the given name was @@ -2372,6 +2491,14 @@ func (m *FiatCurrencyMutation) FieldCleared(name string) bool { // ClearField clears the value of the field with the given name. It returns an // error if the field is not defined in the schema. func (m *FiatCurrencyMutation) ClearField(name string) error { + switch name { + case fiatcurrency.FieldMarketBuyRate: + m.ClearMarketBuyRate() + return nil + case fiatcurrency.FieldMarketSellRate: + m.ClearMarketSellRate() + return nil + } return fmt.Errorf("unknown FiatCurrency nullable field %s", name) } @@ -2400,8 +2527,11 @@ func (m *FiatCurrencyMutation) ResetField(name string) error { case fiatcurrency.FieldName: m.ResetName() return nil - case fiatcurrency.FieldMarketRate: - m.ResetMarketRate() + case fiatcurrency.FieldMarketBuyRate: + m.ResetMarketBuyRate() + return nil + case fiatcurrency.FieldMarketSellRate: + m.ResetMarketSellRate() return nil case fiatcurrency.FieldIsEnabled: m.ResetIsEnabled() @@ -6266,78 +6396,79 @@ func (m *NetworkMutation) ResetEdge(name string) error { // PaymentOrderMutation represents an operation that mutates the PaymentOrder nodes in the graph. type PaymentOrderMutation struct { config - op Op - typ string - id *uuid.UUID - created_at *time.Time - updated_at *time.Time - amount *decimal.Decimal - addamount *decimal.Decimal - rate *decimal.Decimal - addrate *decimal.Decimal - amount_in_usd *decimal.Decimal - addamount_in_usd *decimal.Decimal - amount_paid *decimal.Decimal - addamount_paid *decimal.Decimal - amount_returned *decimal.Decimal - addamount_returned *decimal.Decimal - percent_settled *decimal.Decimal - addpercent_settled *decimal.Decimal - sender_fee *decimal.Decimal - addsender_fee *decimal.Decimal - network_fee *decimal.Decimal - addnetwork_fee *decimal.Decimal - protocol_fee *decimal.Decimal - addprotocol_fee *decimal.Decimal - order_percent *decimal.Decimal - addorder_percent *decimal.Decimal - fee_percent *decimal.Decimal - addfee_percent *decimal.Decimal - tx_hash *string - block_number *int64 - addblock_number *int64 - message_hash *string - gateway_id *string - from_address *string - return_address *string - receive_address *string - receive_address_salt *[]byte - receive_address_expiry *time.Time - fee_address *string - indexer_created_at *time.Time - institution *string - account_identifier *string - account_name *string - memo *string - metadata *map[string]interface{} - sender *string - reference *string - cancellation_count *int - addcancellation_count *int - cancellation_reasons *[]string - appendcancellation_reasons []string - status *paymentorder.Status - order_type *paymentorder.OrderType - clearedFields map[string]struct{} - token *int - clearedtoken bool - sender_profile *uuid.UUID - clearedsender_profile bool - payment_webhook *uuid.UUID - clearedpayment_webhook bool - provider *string - clearedprovider bool - provision_bucket *int - clearedprovision_bucket bool - fulfillments map[uuid.UUID]struct{} - removedfulfillments map[uuid.UUID]struct{} - clearedfulfillments bool - transactions map[uuid.UUID]struct{} - removedtransactions map[uuid.UUID]struct{} - clearedtransactions bool - done bool - oldValue func(context.Context) (*PaymentOrder, error) - predicates []predicate.PaymentOrder + op Op + typ string + id *uuid.UUID + created_at *time.Time + updated_at *time.Time + amount *decimal.Decimal + addamount *decimal.Decimal + rate *decimal.Decimal + addrate *decimal.Decimal + amount_in_usd *decimal.Decimal + addamount_in_usd *decimal.Decimal + amount_paid *decimal.Decimal + addamount_paid *decimal.Decimal + amount_returned *decimal.Decimal + addamount_returned *decimal.Decimal + percent_settled *decimal.Decimal + addpercent_settled *decimal.Decimal + sender_fee *decimal.Decimal + addsender_fee *decimal.Decimal + network_fee *decimal.Decimal + addnetwork_fee *decimal.Decimal + protocol_fee *decimal.Decimal + addprotocol_fee *decimal.Decimal + order_percent *decimal.Decimal + addorder_percent *decimal.Decimal + fee_percent *decimal.Decimal + addfee_percent *decimal.Decimal + tx_hash *string + block_number *int64 + addblock_number *int64 + message_hash *string + gateway_id *string + from_address *string + refund_or_recipient_address *string + receive_address *string + receive_address_salt *[]byte + receive_address_expiry *time.Time + fee_address *string + indexer_created_at *time.Time + institution *string + account_identifier *string + account_name *string + metadata *map[string]interface{} + sender *string + reference *string + cancellation_count *int + addcancellation_count *int + cancellation_reasons *[]string + appendcancellation_reasons []string + memo *string + status *paymentorder.Status + direction *paymentorder.Direction + order_type *paymentorder.OrderType + clearedFields map[string]struct{} + token *int + clearedtoken bool + sender_profile *uuid.UUID + clearedsender_profile bool + payment_webhook *uuid.UUID + clearedpayment_webhook bool + provider *string + clearedprovider bool + provision_bucket *int + clearedprovision_bucket bool + fulfillments map[uuid.UUID]struct{} + removedfulfillments map[uuid.UUID]struct{} + clearedfulfillments bool + transactions map[uuid.UUID]struct{} + removedtransactions map[uuid.UUID]struct{} + clearedtransactions bool + done bool + oldValue func(context.Context) (*PaymentOrder, error) + predicates []predicate.PaymentOrder } var _ ent.Mutation = (*PaymentOrderMutation)(nil) @@ -7384,53 +7515,53 @@ func (m *PaymentOrderMutation) ResetFromAddress() { delete(m.clearedFields, paymentorder.FieldFromAddress) } -// SetReturnAddress sets the "return_address" field. -func (m *PaymentOrderMutation) SetReturnAddress(s string) { - m.return_address = &s +// SetRefundOrRecipientAddress sets the "refund_or_recipient_address" field. +func (m *PaymentOrderMutation) SetRefundOrRecipientAddress(s string) { + m.refund_or_recipient_address = &s } -// ReturnAddress returns the value of the "return_address" field in the mutation. -func (m *PaymentOrderMutation) ReturnAddress() (r string, exists bool) { - v := m.return_address +// RefundOrRecipientAddress returns the value of the "refund_or_recipient_address" field in the mutation. +func (m *PaymentOrderMutation) RefundOrRecipientAddress() (r string, exists bool) { + v := m.refund_or_recipient_address if v == nil { return } return *v, true } -// OldReturnAddress returns the old "return_address" field's value of the PaymentOrder entity. +// OldRefundOrRecipientAddress returns the old "refund_or_recipient_address" field's value of the PaymentOrder entity. // If the PaymentOrder object wasn't provided to the builder, the object is fetched from the database. // An error is returned if the mutation operation is not UpdateOne, or the database query fails. -func (m *PaymentOrderMutation) OldReturnAddress(ctx context.Context) (v string, err error) { +func (m *PaymentOrderMutation) OldRefundOrRecipientAddress(ctx context.Context) (v string, err error) { if !m.op.Is(OpUpdateOne) { - return v, errors.New("OldReturnAddress is only allowed on UpdateOne operations") + return v, errors.New("OldRefundOrRecipientAddress is only allowed on UpdateOne operations") } if m.id == nil || m.oldValue == nil { - return v, errors.New("OldReturnAddress requires an ID field in the mutation") + return v, errors.New("OldRefundOrRecipientAddress requires an ID field in the mutation") } oldValue, err := m.oldValue(ctx) if err != nil { - return v, fmt.Errorf("querying old value for OldReturnAddress: %w", err) + return v, fmt.Errorf("querying old value for OldRefundOrRecipientAddress: %w", err) } - return oldValue.ReturnAddress, nil + return oldValue.RefundOrRecipientAddress, nil } -// ClearReturnAddress clears the value of the "return_address" field. -func (m *PaymentOrderMutation) ClearReturnAddress() { - m.return_address = nil - m.clearedFields[paymentorder.FieldReturnAddress] = struct{}{} +// ClearRefundOrRecipientAddress clears the value of the "refund_or_recipient_address" field. +func (m *PaymentOrderMutation) ClearRefundOrRecipientAddress() { + m.refund_or_recipient_address = nil + m.clearedFields[paymentorder.FieldRefundOrRecipientAddress] = struct{}{} } -// ReturnAddressCleared returns if the "return_address" field was cleared in this mutation. -func (m *PaymentOrderMutation) ReturnAddressCleared() bool { - _, ok := m.clearedFields[paymentorder.FieldReturnAddress] +// RefundOrRecipientAddressCleared returns if the "refund_or_recipient_address" field was cleared in this mutation. +func (m *PaymentOrderMutation) RefundOrRecipientAddressCleared() bool { + _, ok := m.clearedFields[paymentorder.FieldRefundOrRecipientAddress] return ok } -// ResetReturnAddress resets all changes to the "return_address" field. -func (m *PaymentOrderMutation) ResetReturnAddress() { - m.return_address = nil - delete(m.clearedFields, paymentorder.FieldReturnAddress) +// ResetRefundOrRecipientAddress resets all changes to the "refund_or_recipient_address" field. +func (m *PaymentOrderMutation) ResetRefundOrRecipientAddress() { + m.refund_or_recipient_address = nil + delete(m.clearedFields, paymentorder.FieldRefundOrRecipientAddress) } // SetReceiveAddress sets the "receive_address" field. @@ -7786,55 +7917,6 @@ func (m *PaymentOrderMutation) ResetAccountName() { m.account_name = nil } -// SetMemo sets the "memo" field. -func (m *PaymentOrderMutation) SetMemo(s string) { - m.memo = &s -} - -// Memo returns the value of the "memo" field in the mutation. -func (m *PaymentOrderMutation) Memo() (r string, exists bool) { - v := m.memo - if v == nil { - return - } - return *v, true -} - -// OldMemo returns the old "memo" field's value of the PaymentOrder entity. -// If the PaymentOrder object wasn't provided to the builder, the object is fetched from the database. -// An error is returned if the mutation operation is not UpdateOne, or the database query fails. -func (m *PaymentOrderMutation) OldMemo(ctx context.Context) (v string, err error) { - if !m.op.Is(OpUpdateOne) { - return v, errors.New("OldMemo is only allowed on UpdateOne operations") - } - if m.id == nil || m.oldValue == nil { - return v, errors.New("OldMemo requires an ID field in the mutation") - } - oldValue, err := m.oldValue(ctx) - if err != nil { - return v, fmt.Errorf("querying old value for OldMemo: %w", err) - } - return oldValue.Memo, nil -} - -// ClearMemo clears the value of the "memo" field. -func (m *PaymentOrderMutation) ClearMemo() { - m.memo = nil - m.clearedFields[paymentorder.FieldMemo] = struct{}{} -} - -// MemoCleared returns if the "memo" field was cleared in this mutation. -func (m *PaymentOrderMutation) MemoCleared() bool { - _, ok := m.clearedFields[paymentorder.FieldMemo] - return ok -} - -// ResetMemo resets all changes to the "memo" field. -func (m *PaymentOrderMutation) ResetMemo() { - m.memo = nil - delete(m.clearedFields, paymentorder.FieldMemo) -} - // SetMetadata sets the "metadata" field. func (m *PaymentOrderMutation) SetMetadata(value map[string]interface{}) { m.metadata = &value @@ -8117,6 +8199,55 @@ func (m *PaymentOrderMutation) ResetCancellationReasons() { delete(m.clearedFields, paymentorder.FieldCancellationReasons) } +// SetMemo sets the "memo" field. +func (m *PaymentOrderMutation) SetMemo(s string) { + m.memo = &s +} + +// Memo returns the value of the "memo" field in the mutation. +func (m *PaymentOrderMutation) Memo() (r string, exists bool) { + v := m.memo + if v == nil { + return + } + return *v, true +} + +// OldMemo returns the old "memo" field's value of the PaymentOrder entity. +// If the PaymentOrder object wasn't provided to the builder, the object is fetched from the database. +// An error is returned if the mutation operation is not UpdateOne, or the database query fails. +func (m *PaymentOrderMutation) OldMemo(ctx context.Context) (v string, err error) { + if !m.op.Is(OpUpdateOne) { + return v, errors.New("OldMemo is only allowed on UpdateOne operations") + } + if m.id == nil || m.oldValue == nil { + return v, errors.New("OldMemo requires an ID field in the mutation") + } + oldValue, err := m.oldValue(ctx) + if err != nil { + return v, fmt.Errorf("querying old value for OldMemo: %w", err) + } + return oldValue.Memo, nil +} + +// ClearMemo clears the value of the "memo" field. +func (m *PaymentOrderMutation) ClearMemo() { + m.memo = nil + m.clearedFields[paymentorder.FieldMemo] = struct{}{} +} + +// MemoCleared returns if the "memo" field was cleared in this mutation. +func (m *PaymentOrderMutation) MemoCleared() bool { + _, ok := m.clearedFields[paymentorder.FieldMemo] + return ok +} + +// ResetMemo resets all changes to the "memo" field. +func (m *PaymentOrderMutation) ResetMemo() { + m.memo = nil + delete(m.clearedFields, paymentorder.FieldMemo) +} + // SetStatus sets the "status" field. func (m *PaymentOrderMutation) SetStatus(pa paymentorder.Status) { m.status = &pa @@ -8153,6 +8284,42 @@ func (m *PaymentOrderMutation) ResetStatus() { m.status = nil } +// SetDirection sets the "direction" field. +func (m *PaymentOrderMutation) SetDirection(pa paymentorder.Direction) { + m.direction = &pa +} + +// Direction returns the value of the "direction" field in the mutation. +func (m *PaymentOrderMutation) Direction() (r paymentorder.Direction, exists bool) { + v := m.direction + if v == nil { + return + } + return *v, true +} + +// OldDirection returns the old "direction" field's value of the PaymentOrder entity. +// If the PaymentOrder object wasn't provided to the builder, the object is fetched from the database. +// An error is returned if the mutation operation is not UpdateOne, or the database query fails. +func (m *PaymentOrderMutation) OldDirection(ctx context.Context) (v paymentorder.Direction, err error) { + if !m.op.Is(OpUpdateOne) { + return v, errors.New("OldDirection is only allowed on UpdateOne operations") + } + if m.id == nil || m.oldValue == nil { + return v, errors.New("OldDirection requires an ID field in the mutation") + } + oldValue, err := m.oldValue(ctx) + if err != nil { + return v, fmt.Errorf("querying old value for OldDirection: %w", err) + } + return oldValue.Direction, nil +} + +// ResetDirection resets all changes to the "direction" field. +func (m *PaymentOrderMutation) ResetDirection() { + m.direction = nil +} + // SetOrderType sets the "order_type" field. func (m *PaymentOrderMutation) SetOrderType(pt paymentorder.OrderType) { m.order_type = &pt @@ -8526,7 +8693,7 @@ func (m *PaymentOrderMutation) Type() string { // order to get all numeric fields that were incremented/decremented, call // AddedFields(). func (m *PaymentOrderMutation) Fields() []string { - fields := make([]string, 0, 35) + fields := make([]string, 0, 36) if m.created_at != nil { fields = append(fields, paymentorder.FieldCreatedAt) } @@ -8581,8 +8748,8 @@ func (m *PaymentOrderMutation) Fields() []string { if m.from_address != nil { fields = append(fields, paymentorder.FieldFromAddress) } - if m.return_address != nil { - fields = append(fields, paymentorder.FieldReturnAddress) + if m.refund_or_recipient_address != nil { + fields = append(fields, paymentorder.FieldRefundOrRecipientAddress) } if m.receive_address != nil { fields = append(fields, paymentorder.FieldReceiveAddress) @@ -8608,9 +8775,6 @@ func (m *PaymentOrderMutation) Fields() []string { if m.account_name != nil { fields = append(fields, paymentorder.FieldAccountName) } - if m.memo != nil { - fields = append(fields, paymentorder.FieldMemo) - } if m.metadata != nil { fields = append(fields, paymentorder.FieldMetadata) } @@ -8626,9 +8790,15 @@ func (m *PaymentOrderMutation) Fields() []string { if m.cancellation_reasons != nil { fields = append(fields, paymentorder.FieldCancellationReasons) } + if m.memo != nil { + fields = append(fields, paymentorder.FieldMemo) + } if m.status != nil { fields = append(fields, paymentorder.FieldStatus) } + if m.direction != nil { + fields = append(fields, paymentorder.FieldDirection) + } if m.order_type != nil { fields = append(fields, paymentorder.FieldOrderType) } @@ -8676,8 +8846,8 @@ func (m *PaymentOrderMutation) Field(name string) (ent.Value, bool) { return m.GatewayID() case paymentorder.FieldFromAddress: return m.FromAddress() - case paymentorder.FieldReturnAddress: - return m.ReturnAddress() + case paymentorder.FieldRefundOrRecipientAddress: + return m.RefundOrRecipientAddress() case paymentorder.FieldReceiveAddress: return m.ReceiveAddress() case paymentorder.FieldReceiveAddressSalt: @@ -8694,8 +8864,6 @@ func (m *PaymentOrderMutation) Field(name string) (ent.Value, bool) { return m.AccountIdentifier() case paymentorder.FieldAccountName: return m.AccountName() - case paymentorder.FieldMemo: - return m.Memo() case paymentorder.FieldMetadata: return m.Metadata() case paymentorder.FieldSender: @@ -8706,8 +8874,12 @@ func (m *PaymentOrderMutation) Field(name string) (ent.Value, bool) { return m.CancellationCount() case paymentorder.FieldCancellationReasons: return m.CancellationReasons() + case paymentorder.FieldMemo: + return m.Memo() case paymentorder.FieldStatus: return m.Status() + case paymentorder.FieldDirection: + return m.Direction() case paymentorder.FieldOrderType: return m.OrderType() } @@ -8755,8 +8927,8 @@ func (m *PaymentOrderMutation) OldField(ctx context.Context, name string) (ent.V return m.OldGatewayID(ctx) case paymentorder.FieldFromAddress: return m.OldFromAddress(ctx) - case paymentorder.FieldReturnAddress: - return m.OldReturnAddress(ctx) + case paymentorder.FieldRefundOrRecipientAddress: + return m.OldRefundOrRecipientAddress(ctx) case paymentorder.FieldReceiveAddress: return m.OldReceiveAddress(ctx) case paymentorder.FieldReceiveAddressSalt: @@ -8773,8 +8945,6 @@ func (m *PaymentOrderMutation) OldField(ctx context.Context, name string) (ent.V return m.OldAccountIdentifier(ctx) case paymentorder.FieldAccountName: return m.OldAccountName(ctx) - case paymentorder.FieldMemo: - return m.OldMemo(ctx) case paymentorder.FieldMetadata: return m.OldMetadata(ctx) case paymentorder.FieldSender: @@ -8785,8 +8955,12 @@ func (m *PaymentOrderMutation) OldField(ctx context.Context, name string) (ent.V return m.OldCancellationCount(ctx) case paymentorder.FieldCancellationReasons: return m.OldCancellationReasons(ctx) + case paymentorder.FieldMemo: + return m.OldMemo(ctx) case paymentorder.FieldStatus: return m.OldStatus(ctx) + case paymentorder.FieldDirection: + return m.OldDirection(ctx) case paymentorder.FieldOrderType: return m.OldOrderType(ctx) } @@ -8924,12 +9098,12 @@ func (m *PaymentOrderMutation) SetField(name string, value ent.Value) error { } m.SetFromAddress(v) return nil - case paymentorder.FieldReturnAddress: + case paymentorder.FieldRefundOrRecipientAddress: v, ok := value.(string) if !ok { return fmt.Errorf("unexpected type %T for field %s", value, name) } - m.SetReturnAddress(v) + m.SetRefundOrRecipientAddress(v) return nil case paymentorder.FieldReceiveAddress: v, ok := value.(string) @@ -8987,13 +9161,6 @@ func (m *PaymentOrderMutation) SetField(name string, value ent.Value) error { } m.SetAccountName(v) return nil - case paymentorder.FieldMemo: - v, ok := value.(string) - if !ok { - return fmt.Errorf("unexpected type %T for field %s", value, name) - } - m.SetMemo(v) - return nil case paymentorder.FieldMetadata: v, ok := value.(map[string]interface{}) if !ok { @@ -9029,6 +9196,13 @@ func (m *PaymentOrderMutation) SetField(name string, value ent.Value) error { } m.SetCancellationReasons(v) return nil + case paymentorder.FieldMemo: + v, ok := value.(string) + if !ok { + return fmt.Errorf("unexpected type %T for field %s", value, name) + } + m.SetMemo(v) + return nil case paymentorder.FieldStatus: v, ok := value.(paymentorder.Status) if !ok { @@ -9036,6 +9210,13 @@ func (m *PaymentOrderMutation) SetField(name string, value ent.Value) error { } m.SetStatus(v) return nil + case paymentorder.FieldDirection: + v, ok := value.(paymentorder.Direction) + if !ok { + return fmt.Errorf("unexpected type %T for field %s", value, name) + } + m.SetDirection(v) + return nil case paymentorder.FieldOrderType: v, ok := value.(paymentorder.OrderType) if !ok { @@ -9244,8 +9425,8 @@ func (m *PaymentOrderMutation) ClearedFields() []string { if m.FieldCleared(paymentorder.FieldFromAddress) { fields = append(fields, paymentorder.FieldFromAddress) } - if m.FieldCleared(paymentorder.FieldReturnAddress) { - fields = append(fields, paymentorder.FieldReturnAddress) + if m.FieldCleared(paymentorder.FieldRefundOrRecipientAddress) { + fields = append(fields, paymentorder.FieldRefundOrRecipientAddress) } if m.FieldCleared(paymentorder.FieldReceiveAddress) { fields = append(fields, paymentorder.FieldReceiveAddress) @@ -9262,9 +9443,6 @@ func (m *PaymentOrderMutation) ClearedFields() []string { if m.FieldCleared(paymentorder.FieldIndexerCreatedAt) { fields = append(fields, paymentorder.FieldIndexerCreatedAt) } - if m.FieldCleared(paymentorder.FieldMemo) { - fields = append(fields, paymentorder.FieldMemo) - } if m.FieldCleared(paymentorder.FieldMetadata) { fields = append(fields, paymentorder.FieldMetadata) } @@ -9280,6 +9458,9 @@ func (m *PaymentOrderMutation) ClearedFields() []string { if m.FieldCleared(paymentorder.FieldCancellationReasons) { fields = append(fields, paymentorder.FieldCancellationReasons) } + if m.FieldCleared(paymentorder.FieldMemo) { + fields = append(fields, paymentorder.FieldMemo) + } return fields } @@ -9306,8 +9487,8 @@ func (m *PaymentOrderMutation) ClearField(name string) error { case paymentorder.FieldFromAddress: m.ClearFromAddress() return nil - case paymentorder.FieldReturnAddress: - m.ClearReturnAddress() + case paymentorder.FieldRefundOrRecipientAddress: + m.ClearRefundOrRecipientAddress() return nil case paymentorder.FieldReceiveAddress: m.ClearReceiveAddress() @@ -9324,9 +9505,6 @@ func (m *PaymentOrderMutation) ClearField(name string) error { case paymentorder.FieldIndexerCreatedAt: m.ClearIndexerCreatedAt() return nil - case paymentorder.FieldMemo: - m.ClearMemo() - return nil case paymentorder.FieldMetadata: m.ClearMetadata() return nil @@ -9342,6 +9520,9 @@ func (m *PaymentOrderMutation) ClearField(name string) error { case paymentorder.FieldCancellationReasons: m.ClearCancellationReasons() return nil + case paymentorder.FieldMemo: + m.ClearMemo() + return nil } return fmt.Errorf("unknown PaymentOrder nullable field %s", name) } @@ -9404,8 +9585,8 @@ func (m *PaymentOrderMutation) ResetField(name string) error { case paymentorder.FieldFromAddress: m.ResetFromAddress() return nil - case paymentorder.FieldReturnAddress: - m.ResetReturnAddress() + case paymentorder.FieldRefundOrRecipientAddress: + m.ResetRefundOrRecipientAddress() return nil case paymentorder.FieldReceiveAddress: m.ResetReceiveAddress() @@ -9431,9 +9612,6 @@ func (m *PaymentOrderMutation) ResetField(name string) error { case paymentorder.FieldAccountName: m.ResetAccountName() return nil - case paymentorder.FieldMemo: - m.ResetMemo() - return nil case paymentorder.FieldMetadata: m.ResetMetadata() return nil @@ -9449,9 +9627,15 @@ func (m *PaymentOrderMutation) ResetField(name string) error { case paymentorder.FieldCancellationReasons: m.ResetCancellationReasons() return nil + case paymentorder.FieldMemo: + m.ResetMemo() + return nil case paymentorder.FieldStatus: m.ResetStatus() return nil + case paymentorder.FieldDirection: + m.ResetDirection() + return nil case paymentorder.FieldOrderType: m.ResetOrderType() return nil @@ -12515,39 +12699,42 @@ func (m *ProviderFiatAccountMutation) ResetEdge(name string) error { // ProviderOrderTokenMutation represents an operation that mutates the ProviderOrderToken nodes in the graph. type ProviderOrderTokenMutation struct { config - op Op - typ string - id *int - created_at *time.Time - updated_at *time.Time - fixed_conversion_rate *decimal.Decimal - addfixed_conversion_rate *decimal.Decimal - floating_conversion_rate *decimal.Decimal - addfloating_conversion_rate *decimal.Decimal - conversion_rate_type *providerordertoken.ConversionRateType - max_order_amount *decimal.Decimal - addmax_order_amount *decimal.Decimal - min_order_amount *decimal.Decimal - addmin_order_amount *decimal.Decimal - max_order_amount_otc *decimal.Decimal - addmax_order_amount_otc *decimal.Decimal - min_order_amount_otc *decimal.Decimal - addmin_order_amount_otc *decimal.Decimal - rate_slippage *decimal.Decimal - addrate_slippage *decimal.Decimal - settlement_address *string - payout_address *string - network *string - clearedFields map[string]struct{} - provider *string - clearedprovider bool - token *int - clearedtoken bool - currency *uuid.UUID - clearedcurrency bool - done bool - oldValue func(context.Context) (*ProviderOrderToken, error) - predicates []predicate.ProviderOrderToken + op Op + typ string + id *int + created_at *time.Time + updated_at *time.Time + fixed_buy_rate *decimal.Decimal + addfixed_buy_rate *decimal.Decimal + fixed_sell_rate *decimal.Decimal + addfixed_sell_rate *decimal.Decimal + floating_buy_delta *decimal.Decimal + addfloating_buy_delta *decimal.Decimal + floating_sell_delta *decimal.Decimal + addfloating_sell_delta *decimal.Decimal + max_order_amount *decimal.Decimal + addmax_order_amount *decimal.Decimal + min_order_amount *decimal.Decimal + addmin_order_amount *decimal.Decimal + max_order_amount_otc *decimal.Decimal + addmax_order_amount_otc *decimal.Decimal + min_order_amount_otc *decimal.Decimal + addmin_order_amount_otc *decimal.Decimal + rate_slippage *decimal.Decimal + addrate_slippage *decimal.Decimal + settlement_address *string + payout_address *string + network *string + clearedFields map[string]struct{} + provider *string + clearedprovider bool + token *int + clearedtoken bool + currency *uuid.UUID + clearedcurrency bool + done bool + oldValue func(context.Context) (*ProviderOrderToken, error) + predicates []predicate.ProviderOrderToken } var _ ent.Mutation = (*ProviderOrderTokenMutation)(nil) @@ -12720,152 +12907,284 @@ func (m *ProviderOrderTokenMutation) ResetUpdatedAt() { m.updated_at = nil } -// SetFixedConversionRate sets the "fixed_conversion_rate" field. -func (m *ProviderOrderTokenMutation) SetFixedConversionRate(d decimal.Decimal) { - m.fixed_conversion_rate = &d - m.addfixed_conversion_rate = nil +// SetFixedBuyRate sets the "fixed_buy_rate" field. +func (m *ProviderOrderTokenMutation) SetFixedBuyRate(d decimal.Decimal) { + m.fixed_buy_rate = &d + m.addfixed_buy_rate = nil +} + +// FixedBuyRate returns the value of the "fixed_buy_rate" field in the mutation. +func (m *ProviderOrderTokenMutation) FixedBuyRate() (r decimal.Decimal, exists bool) { + v := m.fixed_buy_rate + if v == nil { + return + } + return *v, true +} + +// OldFixedBuyRate returns the old "fixed_buy_rate" field's value of the ProviderOrderToken entity. +// If the ProviderOrderToken object wasn't provided to the builder, the object is fetched from the database. +// An error is returned if the mutation operation is not UpdateOne, or the database query fails. +func (m *ProviderOrderTokenMutation) OldFixedBuyRate(ctx context.Context) (v decimal.Decimal, err error) { + if !m.op.Is(OpUpdateOne) { + return v, errors.New("OldFixedBuyRate is only allowed on UpdateOne operations") + } + if m.id == nil || m.oldValue == nil { + return v, errors.New("OldFixedBuyRate requires an ID field in the mutation") + } + oldValue, err := m.oldValue(ctx) + if err != nil { + return v, fmt.Errorf("querying old value for OldFixedBuyRate: %w", err) + } + return oldValue.FixedBuyRate, nil +} + +// AddFixedBuyRate adds d to the "fixed_buy_rate" field. +func (m *ProviderOrderTokenMutation) AddFixedBuyRate(d decimal.Decimal) { + if m.addfixed_buy_rate != nil { + *m.addfixed_buy_rate = m.addfixed_buy_rate.Add(d) + } else { + m.addfixed_buy_rate = &d + } +} + +// AddedFixedBuyRate returns the value that was added to the "fixed_buy_rate" field in this mutation. +func (m *ProviderOrderTokenMutation) AddedFixedBuyRate() (r decimal.Decimal, exists bool) { + v := m.addfixed_buy_rate + if v == nil { + return + } + return *v, true +} + +// ClearFixedBuyRate clears the value of the "fixed_buy_rate" field. +func (m *ProviderOrderTokenMutation) ClearFixedBuyRate() { + m.fixed_buy_rate = nil + m.addfixed_buy_rate = nil + m.clearedFields[providerordertoken.FieldFixedBuyRate] = struct{}{} } -// FixedConversionRate returns the value of the "fixed_conversion_rate" field in the mutation. -func (m *ProviderOrderTokenMutation) FixedConversionRate() (r decimal.Decimal, exists bool) { - v := m.fixed_conversion_rate +// FixedBuyRateCleared returns if the "fixed_buy_rate" field was cleared in this mutation. +func (m *ProviderOrderTokenMutation) FixedBuyRateCleared() bool { + _, ok := m.clearedFields[providerordertoken.FieldFixedBuyRate] + return ok +} + +// ResetFixedBuyRate resets all changes to the "fixed_buy_rate" field. +func (m *ProviderOrderTokenMutation) ResetFixedBuyRate() { + m.fixed_buy_rate = nil + m.addfixed_buy_rate = nil + delete(m.clearedFields, providerordertoken.FieldFixedBuyRate) +} + +// SetFixedSellRate sets the "fixed_sell_rate" field. +func (m *ProviderOrderTokenMutation) SetFixedSellRate(d decimal.Decimal) { + m.fixed_sell_rate = &d + m.addfixed_sell_rate = nil +} + +// FixedSellRate returns the value of the "fixed_sell_rate" field in the mutation. +func (m *ProviderOrderTokenMutation) FixedSellRate() (r decimal.Decimal, exists bool) { + v := m.fixed_sell_rate if v == nil { return } return *v, true } -// OldFixedConversionRate returns the old "fixed_conversion_rate" field's value of the ProviderOrderToken entity. +// OldFixedSellRate returns the old "fixed_sell_rate" field's value of the ProviderOrderToken entity. // If the ProviderOrderToken object wasn't provided to the builder, the object is fetched from the database. // An error is returned if the mutation operation is not UpdateOne, or the database query fails. -func (m *ProviderOrderTokenMutation) OldFixedConversionRate(ctx context.Context) (v decimal.Decimal, err error) { +func (m *ProviderOrderTokenMutation) OldFixedSellRate(ctx context.Context) (v decimal.Decimal, err error) { if !m.op.Is(OpUpdateOne) { - return v, errors.New("OldFixedConversionRate is only allowed on UpdateOne operations") + return v, errors.New("OldFixedSellRate is only allowed on UpdateOne operations") } if m.id == nil || m.oldValue == nil { - return v, errors.New("OldFixedConversionRate requires an ID field in the mutation") + return v, errors.New("OldFixedSellRate requires an ID field in the mutation") } oldValue, err := m.oldValue(ctx) if err != nil { - return v, fmt.Errorf("querying old value for OldFixedConversionRate: %w", err) + return v, fmt.Errorf("querying old value for OldFixedSellRate: %w", err) } - return oldValue.FixedConversionRate, nil + return oldValue.FixedSellRate, nil } -// AddFixedConversionRate adds d to the "fixed_conversion_rate" field. -func (m *ProviderOrderTokenMutation) AddFixedConversionRate(d decimal.Decimal) { - if m.addfixed_conversion_rate != nil { - *m.addfixed_conversion_rate = m.addfixed_conversion_rate.Add(d) +// AddFixedSellRate adds d to the "fixed_sell_rate" field. +func (m *ProviderOrderTokenMutation) AddFixedSellRate(d decimal.Decimal) { + if m.addfixed_sell_rate != nil { + *m.addfixed_sell_rate = m.addfixed_sell_rate.Add(d) } else { - m.addfixed_conversion_rate = &d + m.addfixed_sell_rate = &d } } -// AddedFixedConversionRate returns the value that was added to the "fixed_conversion_rate" field in this mutation. -func (m *ProviderOrderTokenMutation) AddedFixedConversionRate() (r decimal.Decimal, exists bool) { - v := m.addfixed_conversion_rate +// AddedFixedSellRate returns the value that was added to the "fixed_sell_rate" field in this mutation. +func (m *ProviderOrderTokenMutation) AddedFixedSellRate() (r decimal.Decimal, exists bool) { + v := m.addfixed_sell_rate if v == nil { return } return *v, true } -// ResetFixedConversionRate resets all changes to the "fixed_conversion_rate" field. -func (m *ProviderOrderTokenMutation) ResetFixedConversionRate() { - m.fixed_conversion_rate = nil - m.addfixed_conversion_rate = nil +// ClearFixedSellRate clears the value of the "fixed_sell_rate" field. +func (m *ProviderOrderTokenMutation) ClearFixedSellRate() { + m.fixed_sell_rate = nil + m.addfixed_sell_rate = nil + m.clearedFields[providerordertoken.FieldFixedSellRate] = struct{}{} } -// SetFloatingConversionRate sets the "floating_conversion_rate" field. -func (m *ProviderOrderTokenMutation) SetFloatingConversionRate(d decimal.Decimal) { - m.floating_conversion_rate = &d - m.addfloating_conversion_rate = nil +// FixedSellRateCleared returns if the "fixed_sell_rate" field was cleared in this mutation. +func (m *ProviderOrderTokenMutation) FixedSellRateCleared() bool { + _, ok := m.clearedFields[providerordertoken.FieldFixedSellRate] + return ok +} + +// ResetFixedSellRate resets all changes to the "fixed_sell_rate" field. +func (m *ProviderOrderTokenMutation) ResetFixedSellRate() { + m.fixed_sell_rate = nil + m.addfixed_sell_rate = nil + delete(m.clearedFields, providerordertoken.FieldFixedSellRate) } -// FloatingConversionRate returns the value of the "floating_conversion_rate" field in the mutation. -func (m *ProviderOrderTokenMutation) FloatingConversionRate() (r decimal.Decimal, exists bool) { - v := m.floating_conversion_rate +// SetFloatingBuyDelta sets the "floating_buy_delta" field. +func (m *ProviderOrderTokenMutation) SetFloatingBuyDelta(d decimal.Decimal) { + m.floating_buy_delta = &d + m.addfloating_buy_delta = nil +} + +// FloatingBuyDelta returns the value of the "floating_buy_delta" field in the mutation. +func (m *ProviderOrderTokenMutation) FloatingBuyDelta() (r decimal.Decimal, exists bool) { + v := m.floating_buy_delta if v == nil { return } return *v, true } -// OldFloatingConversionRate returns the old "floating_conversion_rate" field's value of the ProviderOrderToken entity. +// OldFloatingBuyDelta returns the old "floating_buy_delta" field's value of the ProviderOrderToken entity. // If the ProviderOrderToken object wasn't provided to the builder, the object is fetched from the database. // An error is returned if the mutation operation is not UpdateOne, or the database query fails. -func (m *ProviderOrderTokenMutation) OldFloatingConversionRate(ctx context.Context) (v decimal.Decimal, err error) { +func (m *ProviderOrderTokenMutation) OldFloatingBuyDelta(ctx context.Context) (v decimal.Decimal, err error) { if !m.op.Is(OpUpdateOne) { - return v, errors.New("OldFloatingConversionRate is only allowed on UpdateOne operations") + return v, errors.New("OldFloatingBuyDelta is only allowed on UpdateOne operations") } if m.id == nil || m.oldValue == nil { - return v, errors.New("OldFloatingConversionRate requires an ID field in the mutation") + return v, errors.New("OldFloatingBuyDelta requires an ID field in the mutation") } oldValue, err := m.oldValue(ctx) if err != nil { - return v, fmt.Errorf("querying old value for OldFloatingConversionRate: %w", err) + return v, fmt.Errorf("querying old value for OldFloatingBuyDelta: %w", err) } - return oldValue.FloatingConversionRate, nil + return oldValue.FloatingBuyDelta, nil } -// AddFloatingConversionRate adds d to the "floating_conversion_rate" field. -func (m *ProviderOrderTokenMutation) AddFloatingConversionRate(d decimal.Decimal) { - if m.addfloating_conversion_rate != nil { - *m.addfloating_conversion_rate = m.addfloating_conversion_rate.Add(d) +// AddFloatingBuyDelta adds d to the "floating_buy_delta" field. +func (m *ProviderOrderTokenMutation) AddFloatingBuyDelta(d decimal.Decimal) { + if m.addfloating_buy_delta != nil { + *m.addfloating_buy_delta = m.addfloating_buy_delta.Add(d) } else { - m.addfloating_conversion_rate = &d + m.addfloating_buy_delta = &d } } -// AddedFloatingConversionRate returns the value that was added to the "floating_conversion_rate" field in this mutation. -func (m *ProviderOrderTokenMutation) AddedFloatingConversionRate() (r decimal.Decimal, exists bool) { - v := m.addfloating_conversion_rate +// AddedFloatingBuyDelta returns the value that was added to the "floating_buy_delta" field in this mutation. +func (m *ProviderOrderTokenMutation) AddedFloatingBuyDelta() (r decimal.Decimal, exists bool) { + v := m.addfloating_buy_delta if v == nil { return } return *v, true } -// ResetFloatingConversionRate resets all changes to the "floating_conversion_rate" field. -func (m *ProviderOrderTokenMutation) ResetFloatingConversionRate() { - m.floating_conversion_rate = nil - m.addfloating_conversion_rate = nil +// ClearFloatingBuyDelta clears the value of the "floating_buy_delta" field. +func (m *ProviderOrderTokenMutation) ClearFloatingBuyDelta() { + m.floating_buy_delta = nil + m.addfloating_buy_delta = nil + m.clearedFields[providerordertoken.FieldFloatingBuyDelta] = struct{}{} } -// SetConversionRateType sets the "conversion_rate_type" field. -func (m *ProviderOrderTokenMutation) SetConversionRateType(prt providerordertoken.ConversionRateType) { - m.conversion_rate_type = &prt +// FloatingBuyDeltaCleared returns if the "floating_buy_delta" field was cleared in this mutation. +func (m *ProviderOrderTokenMutation) FloatingBuyDeltaCleared() bool { + _, ok := m.clearedFields[providerordertoken.FieldFloatingBuyDelta] + return ok } -// ConversionRateType returns the value of the "conversion_rate_type" field in the mutation. -func (m *ProviderOrderTokenMutation) ConversionRateType() (r providerordertoken.ConversionRateType, exists bool) { - v := m.conversion_rate_type +// ResetFloatingBuyDelta resets all changes to the "floating_buy_delta" field. +func (m *ProviderOrderTokenMutation) ResetFloatingBuyDelta() { + m.floating_buy_delta = nil + m.addfloating_buy_delta = nil + delete(m.clearedFields, providerordertoken.FieldFloatingBuyDelta) +} + +// SetFloatingSellDelta sets the "floating_sell_delta" field. +func (m *ProviderOrderTokenMutation) SetFloatingSellDelta(d decimal.Decimal) { + m.floating_sell_delta = &d + m.addfloating_sell_delta = nil +} + +// FloatingSellDelta returns the value of the "floating_sell_delta" field in the mutation. +func (m *ProviderOrderTokenMutation) FloatingSellDelta() (r decimal.Decimal, exists bool) { + v := m.floating_sell_delta if v == nil { return } return *v, true } -// OldConversionRateType returns the old "conversion_rate_type" field's value of the ProviderOrderToken entity. +// OldFloatingSellDelta returns the old "floating_sell_delta" field's value of the ProviderOrderToken entity. // If the ProviderOrderToken object wasn't provided to the builder, the object is fetched from the database. // An error is returned if the mutation operation is not UpdateOne, or the database query fails. -func (m *ProviderOrderTokenMutation) OldConversionRateType(ctx context.Context) (v providerordertoken.ConversionRateType, err error) { +func (m *ProviderOrderTokenMutation) OldFloatingSellDelta(ctx context.Context) (v decimal.Decimal, err error) { if !m.op.Is(OpUpdateOne) { - return v, errors.New("OldConversionRateType is only allowed on UpdateOne operations") + return v, errors.New("OldFloatingSellDelta is only allowed on UpdateOne operations") } if m.id == nil || m.oldValue == nil { - return v, errors.New("OldConversionRateType requires an ID field in the mutation") + return v, errors.New("OldFloatingSellDelta requires an ID field in the mutation") } oldValue, err := m.oldValue(ctx) if err != nil { - return v, fmt.Errorf("querying old value for OldConversionRateType: %w", err) + return v, fmt.Errorf("querying old value for OldFloatingSellDelta: %w", err) + } + return oldValue.FloatingSellDelta, nil +} + +// AddFloatingSellDelta adds d to the "floating_sell_delta" field. +func (m *ProviderOrderTokenMutation) AddFloatingSellDelta(d decimal.Decimal) { + if m.addfloating_sell_delta != nil { + *m.addfloating_sell_delta = m.addfloating_sell_delta.Add(d) + } else { + m.addfloating_sell_delta = &d } - return oldValue.ConversionRateType, nil } -// ResetConversionRateType resets all changes to the "conversion_rate_type" field. -func (m *ProviderOrderTokenMutation) ResetConversionRateType() { - m.conversion_rate_type = nil +// AddedFloatingSellDelta returns the value that was added to the "floating_sell_delta" field in this mutation. +func (m *ProviderOrderTokenMutation) AddedFloatingSellDelta() (r decimal.Decimal, exists bool) { + v := m.addfloating_sell_delta + if v == nil { + return + } + return *v, true +} + +// ClearFloatingSellDelta clears the value of the "floating_sell_delta" field. +func (m *ProviderOrderTokenMutation) ClearFloatingSellDelta() { + m.floating_sell_delta = nil + m.addfloating_sell_delta = nil + m.clearedFields[providerordertoken.FieldFloatingSellDelta] = struct{}{} +} + +// FloatingSellDeltaCleared returns if the "floating_sell_delta" field was cleared in this mutation. +func (m *ProviderOrderTokenMutation) FloatingSellDeltaCleared() bool { + _, ok := m.clearedFields[providerordertoken.FieldFloatingSellDelta] + return ok +} + +// ResetFloatingSellDelta resets all changes to the "floating_sell_delta" field. +func (m *ProviderOrderTokenMutation) ResetFloatingSellDelta() { + m.floating_sell_delta = nil + m.addfloating_sell_delta = nil + delete(m.clearedFields, providerordertoken.FieldFloatingSellDelta) } // SetMaxOrderAmount sets the "max_order_amount" field. @@ -13433,21 +13752,24 @@ func (m *ProviderOrderTokenMutation) Type() string { // order to get all numeric fields that were incremented/decremented, call // AddedFields(). func (m *ProviderOrderTokenMutation) Fields() []string { - fields := make([]string, 0, 13) + fields := make([]string, 0, 14) if m.created_at != nil { fields = append(fields, providerordertoken.FieldCreatedAt) } if m.updated_at != nil { fields = append(fields, providerordertoken.FieldUpdatedAt) } - if m.fixed_conversion_rate != nil { - fields = append(fields, providerordertoken.FieldFixedConversionRate) + if m.fixed_buy_rate != nil { + fields = append(fields, providerordertoken.FieldFixedBuyRate) + } + if m.fixed_sell_rate != nil { + fields = append(fields, providerordertoken.FieldFixedSellRate) } - if m.floating_conversion_rate != nil { - fields = append(fields, providerordertoken.FieldFloatingConversionRate) + if m.floating_buy_delta != nil { + fields = append(fields, providerordertoken.FieldFloatingBuyDelta) } - if m.conversion_rate_type != nil { - fields = append(fields, providerordertoken.FieldConversionRateType) + if m.floating_sell_delta != nil { + fields = append(fields, providerordertoken.FieldFloatingSellDelta) } if m.max_order_amount != nil { fields = append(fields, providerordertoken.FieldMaxOrderAmount) @@ -13485,12 +13807,14 @@ func (m *ProviderOrderTokenMutation) Field(name string) (ent.Value, bool) { return m.CreatedAt() case providerordertoken.FieldUpdatedAt: return m.UpdatedAt() - case providerordertoken.FieldFixedConversionRate: - return m.FixedConversionRate() - case providerordertoken.FieldFloatingConversionRate: - return m.FloatingConversionRate() - case providerordertoken.FieldConversionRateType: - return m.ConversionRateType() + case providerordertoken.FieldFixedBuyRate: + return m.FixedBuyRate() + case providerordertoken.FieldFixedSellRate: + return m.FixedSellRate() + case providerordertoken.FieldFloatingBuyDelta: + return m.FloatingBuyDelta() + case providerordertoken.FieldFloatingSellDelta: + return m.FloatingSellDelta() case providerordertoken.FieldMaxOrderAmount: return m.MaxOrderAmount() case providerordertoken.FieldMinOrderAmount: @@ -13520,12 +13844,14 @@ func (m *ProviderOrderTokenMutation) OldField(ctx context.Context, name string) return m.OldCreatedAt(ctx) case providerordertoken.FieldUpdatedAt: return m.OldUpdatedAt(ctx) - case providerordertoken.FieldFixedConversionRate: - return m.OldFixedConversionRate(ctx) - case providerordertoken.FieldFloatingConversionRate: - return m.OldFloatingConversionRate(ctx) - case providerordertoken.FieldConversionRateType: - return m.OldConversionRateType(ctx) + case providerordertoken.FieldFixedBuyRate: + return m.OldFixedBuyRate(ctx) + case providerordertoken.FieldFixedSellRate: + return m.OldFixedSellRate(ctx) + case providerordertoken.FieldFloatingBuyDelta: + return m.OldFloatingBuyDelta(ctx) + case providerordertoken.FieldFloatingSellDelta: + return m.OldFloatingSellDelta(ctx) case providerordertoken.FieldMaxOrderAmount: return m.OldMaxOrderAmount(ctx) case providerordertoken.FieldMinOrderAmount: @@ -13565,26 +13891,33 @@ func (m *ProviderOrderTokenMutation) SetField(name string, value ent.Value) erro } m.SetUpdatedAt(v) return nil - case providerordertoken.FieldFixedConversionRate: + case providerordertoken.FieldFixedBuyRate: + v, ok := value.(decimal.Decimal) + if !ok { + return fmt.Errorf("unexpected type %T for field %s", value, name) + } + m.SetFixedBuyRate(v) + return nil + case providerordertoken.FieldFixedSellRate: v, ok := value.(decimal.Decimal) if !ok { return fmt.Errorf("unexpected type %T for field %s", value, name) } - m.SetFixedConversionRate(v) + m.SetFixedSellRate(v) return nil - case providerordertoken.FieldFloatingConversionRate: + case providerordertoken.FieldFloatingBuyDelta: v, ok := value.(decimal.Decimal) if !ok { return fmt.Errorf("unexpected type %T for field %s", value, name) } - m.SetFloatingConversionRate(v) + m.SetFloatingBuyDelta(v) return nil - case providerordertoken.FieldConversionRateType: - v, ok := value.(providerordertoken.ConversionRateType) + case providerordertoken.FieldFloatingSellDelta: + v, ok := value.(decimal.Decimal) if !ok { return fmt.Errorf("unexpected type %T for field %s", value, name) } - m.SetConversionRateType(v) + m.SetFloatingSellDelta(v) return nil case providerordertoken.FieldMaxOrderAmount: v, ok := value.(decimal.Decimal) @@ -13650,11 +13983,17 @@ func (m *ProviderOrderTokenMutation) SetField(name string, value ent.Value) erro // this mutation. func (m *ProviderOrderTokenMutation) AddedFields() []string { var fields []string - if m.addfixed_conversion_rate != nil { - fields = append(fields, providerordertoken.FieldFixedConversionRate) + if m.addfixed_buy_rate != nil { + fields = append(fields, providerordertoken.FieldFixedBuyRate) + } + if m.addfixed_sell_rate != nil { + fields = append(fields, providerordertoken.FieldFixedSellRate) } - if m.addfloating_conversion_rate != nil { - fields = append(fields, providerordertoken.FieldFloatingConversionRate) + if m.addfloating_buy_delta != nil { + fields = append(fields, providerordertoken.FieldFloatingBuyDelta) + } + if m.addfloating_sell_delta != nil { + fields = append(fields, providerordertoken.FieldFloatingSellDelta) } if m.addmax_order_amount != nil { fields = append(fields, providerordertoken.FieldMaxOrderAmount) @@ -13679,10 +14018,14 @@ func (m *ProviderOrderTokenMutation) AddedFields() []string { // was not set, or was not defined in the schema. func (m *ProviderOrderTokenMutation) AddedField(name string) (ent.Value, bool) { switch name { - case providerordertoken.FieldFixedConversionRate: - return m.AddedFixedConversionRate() - case providerordertoken.FieldFloatingConversionRate: - return m.AddedFloatingConversionRate() + case providerordertoken.FieldFixedBuyRate: + return m.AddedFixedBuyRate() + case providerordertoken.FieldFixedSellRate: + return m.AddedFixedSellRate() + case providerordertoken.FieldFloatingBuyDelta: + return m.AddedFloatingBuyDelta() + case providerordertoken.FieldFloatingSellDelta: + return m.AddedFloatingSellDelta() case providerordertoken.FieldMaxOrderAmount: return m.AddedMaxOrderAmount() case providerordertoken.FieldMinOrderAmount: @@ -13702,19 +14045,33 @@ func (m *ProviderOrderTokenMutation) AddedField(name string) (ent.Value, bool) { // type. func (m *ProviderOrderTokenMutation) AddField(name string, value ent.Value) error { switch name { - case providerordertoken.FieldFixedConversionRate: + case providerordertoken.FieldFixedBuyRate: + v, ok := value.(decimal.Decimal) + if !ok { + return fmt.Errorf("unexpected type %T for field %s", value, name) + } + m.AddFixedBuyRate(v) + return nil + case providerordertoken.FieldFixedSellRate: v, ok := value.(decimal.Decimal) if !ok { return fmt.Errorf("unexpected type %T for field %s", value, name) } - m.AddFixedConversionRate(v) + m.AddFixedSellRate(v) return nil - case providerordertoken.FieldFloatingConversionRate: + case providerordertoken.FieldFloatingBuyDelta: v, ok := value.(decimal.Decimal) if !ok { return fmt.Errorf("unexpected type %T for field %s", value, name) } - m.AddFloatingConversionRate(v) + m.AddFloatingBuyDelta(v) + return nil + case providerordertoken.FieldFloatingSellDelta: + v, ok := value.(decimal.Decimal) + if !ok { + return fmt.Errorf("unexpected type %T for field %s", value, name) + } + m.AddFloatingSellDelta(v) return nil case providerordertoken.FieldMaxOrderAmount: v, ok := value.(decimal.Decimal) @@ -13759,6 +14116,18 @@ func (m *ProviderOrderTokenMutation) AddField(name string, value ent.Value) erro // mutation. func (m *ProviderOrderTokenMutation) ClearedFields() []string { var fields []string + if m.FieldCleared(providerordertoken.FieldFixedBuyRate) { + fields = append(fields, providerordertoken.FieldFixedBuyRate) + } + if m.FieldCleared(providerordertoken.FieldFixedSellRate) { + fields = append(fields, providerordertoken.FieldFixedSellRate) + } + if m.FieldCleared(providerordertoken.FieldFloatingBuyDelta) { + fields = append(fields, providerordertoken.FieldFloatingBuyDelta) + } + if m.FieldCleared(providerordertoken.FieldFloatingSellDelta) { + fields = append(fields, providerordertoken.FieldFloatingSellDelta) + } if m.FieldCleared(providerordertoken.FieldSettlementAddress) { fields = append(fields, providerordertoken.FieldSettlementAddress) } @@ -13779,6 +14148,18 @@ func (m *ProviderOrderTokenMutation) FieldCleared(name string) bool { // error if the field is not defined in the schema. func (m *ProviderOrderTokenMutation) ClearField(name string) error { switch name { + case providerordertoken.FieldFixedBuyRate: + m.ClearFixedBuyRate() + return nil + case providerordertoken.FieldFixedSellRate: + m.ClearFixedSellRate() + return nil + case providerordertoken.FieldFloatingBuyDelta: + m.ClearFloatingBuyDelta() + return nil + case providerordertoken.FieldFloatingSellDelta: + m.ClearFloatingSellDelta() + return nil case providerordertoken.FieldSettlementAddress: m.ClearSettlementAddress() return nil @@ -13799,14 +14180,17 @@ func (m *ProviderOrderTokenMutation) ResetField(name string) error { case providerordertoken.FieldUpdatedAt: m.ResetUpdatedAt() return nil - case providerordertoken.FieldFixedConversionRate: - m.ResetFixedConversionRate() + case providerordertoken.FieldFixedBuyRate: + m.ResetFixedBuyRate() + return nil + case providerordertoken.FieldFixedSellRate: + m.ResetFixedSellRate() return nil - case providerordertoken.FieldFloatingConversionRate: - m.ResetFloatingConversionRate() + case providerordertoken.FieldFloatingBuyDelta: + m.ResetFloatingBuyDelta() return nil - case providerordertoken.FieldConversionRateType: - m.ResetConversionRateType() + case providerordertoken.FieldFloatingSellDelta: + m.ResetFloatingSellDelta() return nil case providerordertoken.FieldMaxOrderAmount: m.ResetMaxOrderAmount() @@ -17318,6 +17702,7 @@ type SenderProfileMutation struct { typ string id *uuid.UUID webhook_url *string + webhook_version *string domain_whitelist *[]string appenddomain_whitelist []string provider_id *string @@ -17493,6 +17878,55 @@ func (m *SenderProfileMutation) ResetWebhookURL() { delete(m.clearedFields, senderprofile.FieldWebhookURL) } +// SetWebhookVersion sets the "webhook_version" field. +func (m *SenderProfileMutation) SetWebhookVersion(s string) { + m.webhook_version = &s +} + +// WebhookVersion returns the value of the "webhook_version" field in the mutation. +func (m *SenderProfileMutation) WebhookVersion() (r string, exists bool) { + v := m.webhook_version + if v == nil { + return + } + return *v, true +} + +// OldWebhookVersion returns the old "webhook_version" field's value of the SenderProfile entity. +// If the SenderProfile object wasn't provided to the builder, the object is fetched from the database. +// An error is returned if the mutation operation is not UpdateOne, or the database query fails. +func (m *SenderProfileMutation) OldWebhookVersion(ctx context.Context) (v string, err error) { + if !m.op.Is(OpUpdateOne) { + return v, errors.New("OldWebhookVersion is only allowed on UpdateOne operations") + } + if m.id == nil || m.oldValue == nil { + return v, errors.New("OldWebhookVersion requires an ID field in the mutation") + } + oldValue, err := m.oldValue(ctx) + if err != nil { + return v, fmt.Errorf("querying old value for OldWebhookVersion: %w", err) + } + return oldValue.WebhookVersion, nil +} + +// ClearWebhookVersion clears the value of the "webhook_version" field. +func (m *SenderProfileMutation) ClearWebhookVersion() { + m.webhook_version = nil + m.clearedFields[senderprofile.FieldWebhookVersion] = struct{}{} +} + +// WebhookVersionCleared returns if the "webhook_version" field was cleared in this mutation. +func (m *SenderProfileMutation) WebhookVersionCleared() bool { + _, ok := m.clearedFields[senderprofile.FieldWebhookVersion] + return ok +} + +// ResetWebhookVersion resets all changes to the "webhook_version" field. +func (m *SenderProfileMutation) ResetWebhookVersion() { + m.webhook_version = nil + delete(m.clearedFields, senderprofile.FieldWebhookVersion) +} + // SetDomainWhitelist sets the "domain_whitelist" field. func (m *SenderProfileMutation) SetDomainWhitelist(s []string) { m.domain_whitelist = &s @@ -17921,10 +18355,13 @@ func (m *SenderProfileMutation) Type() string { // order to get all numeric fields that were incremented/decremented, call // AddedFields(). func (m *SenderProfileMutation) Fields() []string { - fields := make([]string, 0, 6) + fields := make([]string, 0, 7) if m.webhook_url != nil { fields = append(fields, senderprofile.FieldWebhookURL) } + if m.webhook_version != nil { + fields = append(fields, senderprofile.FieldWebhookVersion) + } if m.domain_whitelist != nil { fields = append(fields, senderprofile.FieldDomainWhitelist) } @@ -17950,6 +18387,8 @@ func (m *SenderProfileMutation) Field(name string) (ent.Value, bool) { switch name { case senderprofile.FieldWebhookURL: return m.WebhookURL() + case senderprofile.FieldWebhookVersion: + return m.WebhookVersion() case senderprofile.FieldDomainWhitelist: return m.DomainWhitelist() case senderprofile.FieldProviderID: @@ -17971,6 +18410,8 @@ func (m *SenderProfileMutation) OldField(ctx context.Context, name string) (ent. switch name { case senderprofile.FieldWebhookURL: return m.OldWebhookURL(ctx) + case senderprofile.FieldWebhookVersion: + return m.OldWebhookVersion(ctx) case senderprofile.FieldDomainWhitelist: return m.OldDomainWhitelist(ctx) case senderprofile.FieldProviderID: @@ -17997,6 +18438,13 @@ func (m *SenderProfileMutation) SetField(name string, value ent.Value) error { } m.SetWebhookURL(v) return nil + case senderprofile.FieldWebhookVersion: + v, ok := value.(string) + if !ok { + return fmt.Errorf("unexpected type %T for field %s", value, name) + } + m.SetWebhookVersion(v) + return nil case senderprofile.FieldDomainWhitelist: v, ok := value.([]string) if !ok { @@ -18065,6 +18513,9 @@ func (m *SenderProfileMutation) ClearedFields() []string { if m.FieldCleared(senderprofile.FieldWebhookURL) { fields = append(fields, senderprofile.FieldWebhookURL) } + if m.FieldCleared(senderprofile.FieldWebhookVersion) { + fields = append(fields, senderprofile.FieldWebhookVersion) + } if m.FieldCleared(senderprofile.FieldProviderID) { fields = append(fields, senderprofile.FieldProviderID) } @@ -18085,6 +18536,9 @@ func (m *SenderProfileMutation) ClearField(name string) error { case senderprofile.FieldWebhookURL: m.ClearWebhookURL() return nil + case senderprofile.FieldWebhookVersion: + m.ClearWebhookVersion() + return nil case senderprofile.FieldProviderID: m.ClearProviderID() return nil @@ -18099,6 +18553,9 @@ func (m *SenderProfileMutation) ResetField(name string) error { case senderprofile.FieldWebhookURL: m.ResetWebhookURL() return nil + case senderprofile.FieldWebhookVersion: + m.ResetWebhookVersion() + return nil case senderprofile.FieldDomainWhitelist: m.ResetDomainWhitelist() return nil diff --git a/ent/paymentorder.go b/ent/paymentorder.go index ac3f60c6b..94bdff389 100644 --- a/ent/paymentorder.go +++ b/ent/paymentorder.go @@ -61,8 +61,8 @@ type PaymentOrder struct { GatewayID string `json:"gateway_id,omitempty"` // FromAddress holds the value of the "from_address" field. FromAddress string `json:"from_address,omitempty"` - // ReturnAddress holds the value of the "return_address" field. - ReturnAddress string `json:"return_address,omitempty"` + // RefundOrRecipientAddress holds the value of the "refund_or_recipient_address" field. + RefundOrRecipientAddress string `json:"refund_or_recipient_address,omitempty"` // ReceiveAddress holds the value of the "receive_address" field. ReceiveAddress string `json:"receive_address,omitempty"` // ReceiveAddressSalt holds the value of the "receive_address_salt" field. @@ -79,8 +79,6 @@ type PaymentOrder struct { AccountIdentifier string `json:"account_identifier,omitempty"` // AccountName holds the value of the "account_name" field. AccountName string `json:"account_name,omitempty"` - // Memo holds the value of the "memo" field. - Memo string `json:"memo,omitempty"` // Metadata holds the value of the "metadata" field. Metadata map[string]interface{} `json:"metadata,omitempty"` // Sender holds the value of the "sender" field. @@ -91,8 +89,12 @@ type PaymentOrder struct { CancellationCount int `json:"cancellation_count,omitempty"` // CancellationReasons holds the value of the "cancellation_reasons" field. CancellationReasons []string `json:"cancellation_reasons,omitempty"` + // Memo holds the value of the "memo" field. + Memo string `json:"memo,omitempty"` // Status holds the value of the "status" field. Status paymentorder.Status `json:"status,omitempty"` + // Direction holds the value of the "direction" field. + Direction paymentorder.Direction `json:"direction,omitempty"` // OrderType holds the value of the "order_type" field. OrderType paymentorder.OrderType `json:"order_type,omitempty"` // Edges holds the relations/edges for other nodes in the graph. @@ -211,7 +213,7 @@ func (*PaymentOrder) scanValues(columns []string) ([]any, error) { values[i] = new(decimal.Decimal) case paymentorder.FieldBlockNumber, paymentorder.FieldCancellationCount: values[i] = new(sql.NullInt64) - case paymentorder.FieldTxHash, paymentorder.FieldMessageHash, paymentorder.FieldGatewayID, paymentorder.FieldFromAddress, paymentorder.FieldReturnAddress, paymentorder.FieldReceiveAddress, paymentorder.FieldFeeAddress, paymentorder.FieldInstitution, paymentorder.FieldAccountIdentifier, paymentorder.FieldAccountName, paymentorder.FieldMemo, paymentorder.FieldSender, paymentorder.FieldReference, paymentorder.FieldStatus, paymentorder.FieldOrderType: + case paymentorder.FieldTxHash, paymentorder.FieldMessageHash, paymentorder.FieldGatewayID, paymentorder.FieldFromAddress, paymentorder.FieldRefundOrRecipientAddress, paymentorder.FieldReceiveAddress, paymentorder.FieldFeeAddress, paymentorder.FieldInstitution, paymentorder.FieldAccountIdentifier, paymentorder.FieldAccountName, paymentorder.FieldSender, paymentorder.FieldReference, paymentorder.FieldMemo, paymentorder.FieldStatus, paymentorder.FieldDirection, paymentorder.FieldOrderType: values[i] = new(sql.NullString) case paymentorder.FieldCreatedAt, paymentorder.FieldUpdatedAt, paymentorder.FieldReceiveAddressExpiry, paymentorder.FieldIndexerCreatedAt: values[i] = new(sql.NullTime) @@ -356,11 +358,11 @@ func (_m *PaymentOrder) assignValues(columns []string, values []any) error { } else if value.Valid { _m.FromAddress = value.String } - case paymentorder.FieldReturnAddress: + case paymentorder.FieldRefundOrRecipientAddress: if value, ok := values[i].(*sql.NullString); !ok { - return fmt.Errorf("unexpected type %T for field return_address", values[i]) + return fmt.Errorf("unexpected type %T for field refund_or_recipient_address", values[i]) } else if value.Valid { - _m.ReturnAddress = value.String + _m.RefundOrRecipientAddress = value.String } case paymentorder.FieldReceiveAddress: if value, ok := values[i].(*sql.NullString); !ok { @@ -410,12 +412,6 @@ func (_m *PaymentOrder) assignValues(columns []string, values []any) error { } else if value.Valid { _m.AccountName = value.String } - case paymentorder.FieldMemo: - if value, ok := values[i].(*sql.NullString); !ok { - return fmt.Errorf("unexpected type %T for field memo", values[i]) - } else if value.Valid { - _m.Memo = value.String - } case paymentorder.FieldMetadata: if value, ok := values[i].(*[]byte); !ok { return fmt.Errorf("unexpected type %T for field metadata", values[i]) @@ -450,12 +446,24 @@ func (_m *PaymentOrder) assignValues(columns []string, values []any) error { return fmt.Errorf("unmarshal field cancellation_reasons: %w", err) } } + case paymentorder.FieldMemo: + if value, ok := values[i].(*sql.NullString); !ok { + return fmt.Errorf("unexpected type %T for field memo", values[i]) + } else if value.Valid { + _m.Memo = value.String + } case paymentorder.FieldStatus: if value, ok := values[i].(*sql.NullString); !ok { return fmt.Errorf("unexpected type %T for field status", values[i]) } else if value.Valid { _m.Status = paymentorder.Status(value.String) } + case paymentorder.FieldDirection: + if value, ok := values[i].(*sql.NullString); !ok { + return fmt.Errorf("unexpected type %T for field direction", values[i]) + } else if value.Valid { + _m.Direction = paymentorder.Direction(value.String) + } case paymentorder.FieldOrderType: if value, ok := values[i].(*sql.NullString); !ok { return fmt.Errorf("unexpected type %T for field order_type", values[i]) @@ -622,8 +630,8 @@ func (_m *PaymentOrder) String() string { builder.WriteString("from_address=") builder.WriteString(_m.FromAddress) builder.WriteString(", ") - builder.WriteString("return_address=") - builder.WriteString(_m.ReturnAddress) + builder.WriteString("refund_or_recipient_address=") + builder.WriteString(_m.RefundOrRecipientAddress) builder.WriteString(", ") builder.WriteString("receive_address=") builder.WriteString(_m.ReceiveAddress) @@ -649,9 +657,6 @@ func (_m *PaymentOrder) String() string { builder.WriteString("account_name=") builder.WriteString(_m.AccountName) builder.WriteString(", ") - builder.WriteString("memo=") - builder.WriteString(_m.Memo) - builder.WriteString(", ") builder.WriteString("metadata=") builder.WriteString(fmt.Sprintf("%v", _m.Metadata)) builder.WriteString(", ") @@ -667,9 +672,15 @@ func (_m *PaymentOrder) String() string { builder.WriteString("cancellation_reasons=") builder.WriteString(fmt.Sprintf("%v", _m.CancellationReasons)) builder.WriteString(", ") + builder.WriteString("memo=") + builder.WriteString(_m.Memo) + builder.WriteString(", ") builder.WriteString("status=") builder.WriteString(fmt.Sprintf("%v", _m.Status)) builder.WriteString(", ") + builder.WriteString("direction=") + builder.WriteString(fmt.Sprintf("%v", _m.Direction)) + builder.WriteString(", ") builder.WriteString("order_type=") builder.WriteString(fmt.Sprintf("%v", _m.OrderType)) builder.WriteByte(')') diff --git a/ent/paymentorder/paymentorder.go b/ent/paymentorder/paymentorder.go index be81f4f02..5dc17ac61 100644 --- a/ent/paymentorder/paymentorder.go +++ b/ent/paymentorder/paymentorder.go @@ -53,8 +53,8 @@ const ( FieldGatewayID = "gateway_id" // FieldFromAddress holds the string denoting the from_address field in the database. FieldFromAddress = "from_address" - // FieldReturnAddress holds the string denoting the return_address field in the database. - FieldReturnAddress = "return_address" + // FieldRefundOrRecipientAddress holds the string denoting the refund_or_recipient_address field in the database. + FieldRefundOrRecipientAddress = "refund_or_recipient_address" // FieldReceiveAddress holds the string denoting the receive_address field in the database. FieldReceiveAddress = "receive_address" // FieldReceiveAddressSalt holds the string denoting the receive_address_salt field in the database. @@ -71,8 +71,6 @@ const ( FieldAccountIdentifier = "account_identifier" // FieldAccountName holds the string denoting the account_name field in the database. FieldAccountName = "account_name" - // FieldMemo holds the string denoting the memo field in the database. - FieldMemo = "memo" // FieldMetadata holds the string denoting the metadata field in the database. FieldMetadata = "metadata" // FieldSender holds the string denoting the sender field in the database. @@ -83,8 +81,12 @@ const ( FieldCancellationCount = "cancellation_count" // FieldCancellationReasons holds the string denoting the cancellation_reasons field in the database. FieldCancellationReasons = "cancellation_reasons" + // FieldMemo holds the string denoting the memo field in the database. + FieldMemo = "memo" // FieldStatus holds the string denoting the status field in the database. FieldStatus = "status" + // FieldDirection holds the string denoting the direction field in the database. + FieldDirection = "direction" // FieldOrderType holds the string denoting the order_type field in the database. FieldOrderType = "order_type" // EdgeToken holds the string denoting the token edge name in mutations. @@ -175,7 +177,7 @@ var Columns = []string{ FieldMessageHash, FieldGatewayID, FieldFromAddress, - FieldReturnAddress, + FieldRefundOrRecipientAddress, FieldReceiveAddress, FieldReceiveAddressSalt, FieldReceiveAddressExpiry, @@ -184,13 +186,14 @@ var Columns = []string{ FieldInstitution, FieldAccountIdentifier, FieldAccountName, - FieldMemo, FieldMetadata, FieldSender, FieldReference, FieldCancellationCount, FieldCancellationReasons, + FieldMemo, FieldStatus, + FieldDirection, FieldOrderType, } @@ -250,8 +253,8 @@ var ( GatewayIDValidator func(string) error // FromAddressValidator is a validator for the "from_address" field. It is called by the builders before save. FromAddressValidator func(string) error - // ReturnAddressValidator is a validator for the "return_address" field. It is called by the builders before save. - ReturnAddressValidator func(string) error + // RefundOrRecipientAddressValidator is a validator for the "refund_or_recipient_address" field. It is called by the builders before save. + RefundOrRecipientAddressValidator func(string) error // ReceiveAddressValidator is a validator for the "receive_address" field. It is called by the builders before save. ReceiveAddressValidator func(string) error // FeeAddressValidator is a validator for the "fee_address" field. It is called by the builders before save. @@ -262,8 +265,6 @@ var ( AccountIdentifierValidator func(string) error // AccountNameValidator is a validator for the "account_name" field. It is called by the builders before save. AccountNameValidator func(string) error - // MemoValidator is a validator for the "memo" field. It is called by the builders before save. - MemoValidator func(string) error // SenderValidator is a validator for the "sender" field. It is called by the builders before save. SenderValidator func(string) error // ReferenceValidator is a validator for the "reference" field. It is called by the builders before save. @@ -272,6 +273,8 @@ var ( DefaultCancellationCount int // DefaultCancellationReasons holds the default value on creation for the "cancellation_reasons" field. DefaultCancellationReasons []string + // MemoValidator is a validator for the "memo" field. It is called by the builders before save. + MemoValidator func(string) error // DefaultID holds the default value on creation for the "id" field. DefaultID func() uuid.UUID ) @@ -312,6 +315,32 @@ func StatusValidator(s Status) error { } } +// Direction defines the type for the "direction" enum field. +type Direction string + +// DirectionOfframp is the default value of the Direction enum. +const DefaultDirection = DirectionOfframp + +// Direction values. +const ( + DirectionOfframp Direction = "offramp" + DirectionOnramp Direction = "onramp" +) + +func (d Direction) String() string { + return string(d) +} + +// DirectionValidator is a validator for the "direction" field enum values. It is called by the builders before save. +func DirectionValidator(d Direction) error { + switch d { + case DirectionOfframp, DirectionOnramp: + return nil + default: + return fmt.Errorf("paymentorder: invalid enum value for direction field: %q", d) + } +} + // OrderType defines the type for the "order_type" enum field. type OrderType string @@ -436,9 +465,9 @@ func ByFromAddress(opts ...sql.OrderTermOption) OrderOption { return sql.OrderByField(FieldFromAddress, opts...).ToFunc() } -// ByReturnAddress orders the results by the return_address field. -func ByReturnAddress(opts ...sql.OrderTermOption) OrderOption { - return sql.OrderByField(FieldReturnAddress, opts...).ToFunc() +// ByRefundOrRecipientAddress orders the results by the refund_or_recipient_address field. +func ByRefundOrRecipientAddress(opts ...sql.OrderTermOption) OrderOption { + return sql.OrderByField(FieldRefundOrRecipientAddress, opts...).ToFunc() } // ByReceiveAddress orders the results by the receive_address field. @@ -476,11 +505,6 @@ func ByAccountName(opts ...sql.OrderTermOption) OrderOption { return sql.OrderByField(FieldAccountName, opts...).ToFunc() } -// ByMemo orders the results by the memo field. -func ByMemo(opts ...sql.OrderTermOption) OrderOption { - return sql.OrderByField(FieldMemo, opts...).ToFunc() -} - // BySender orders the results by the sender field. func BySender(opts ...sql.OrderTermOption) OrderOption { return sql.OrderByField(FieldSender, opts...).ToFunc() @@ -496,11 +520,21 @@ func ByCancellationCount(opts ...sql.OrderTermOption) OrderOption { return sql.OrderByField(FieldCancellationCount, opts...).ToFunc() } +// ByMemo orders the results by the memo field. +func ByMemo(opts ...sql.OrderTermOption) OrderOption { + return sql.OrderByField(FieldMemo, opts...).ToFunc() +} + // ByStatus orders the results by the status field. func ByStatus(opts ...sql.OrderTermOption) OrderOption { return sql.OrderByField(FieldStatus, opts...).ToFunc() } +// ByDirection orders the results by the direction field. +func ByDirection(opts ...sql.OrderTermOption) OrderOption { + return sql.OrderByField(FieldDirection, opts...).ToFunc() +} + // ByOrderType orders the results by the order_type field. func ByOrderType(opts ...sql.OrderTermOption) OrderOption { return sql.OrderByField(FieldOrderType, opts...).ToFunc() diff --git a/ent/paymentorder/where.go b/ent/paymentorder/where.go index d098b2f58..e34c42414 100644 --- a/ent/paymentorder/where.go +++ b/ent/paymentorder/where.go @@ -147,9 +147,9 @@ func FromAddress(v string) predicate.PaymentOrder { return predicate.PaymentOrder(sql.FieldEQ(FieldFromAddress, v)) } -// ReturnAddress applies equality check predicate on the "return_address" field. It's identical to ReturnAddressEQ. -func ReturnAddress(v string) predicate.PaymentOrder { - return predicate.PaymentOrder(sql.FieldEQ(FieldReturnAddress, v)) +// RefundOrRecipientAddress applies equality check predicate on the "refund_or_recipient_address" field. It's identical to RefundOrRecipientAddressEQ. +func RefundOrRecipientAddress(v string) predicate.PaymentOrder { + return predicate.PaymentOrder(sql.FieldEQ(FieldRefundOrRecipientAddress, v)) } // ReceiveAddress applies equality check predicate on the "receive_address" field. It's identical to ReceiveAddressEQ. @@ -192,11 +192,6 @@ func AccountName(v string) predicate.PaymentOrder { return predicate.PaymentOrder(sql.FieldEQ(FieldAccountName, v)) } -// Memo applies equality check predicate on the "memo" field. It's identical to MemoEQ. -func Memo(v string) predicate.PaymentOrder { - return predicate.PaymentOrder(sql.FieldEQ(FieldMemo, v)) -} - // Sender applies equality check predicate on the "sender" field. It's identical to SenderEQ. func Sender(v string) predicate.PaymentOrder { return predicate.PaymentOrder(sql.FieldEQ(FieldSender, v)) @@ -212,6 +207,11 @@ func CancellationCount(v int) predicate.PaymentOrder { return predicate.PaymentOrder(sql.FieldEQ(FieldCancellationCount, v)) } +// Memo applies equality check predicate on the "memo" field. It's identical to MemoEQ. +func Memo(v string) predicate.PaymentOrder { + return predicate.PaymentOrder(sql.FieldEQ(FieldMemo, v)) +} + // CreatedAtEQ applies the EQ predicate on the "created_at" field. func CreatedAtEQ(v time.Time) predicate.PaymentOrder { return predicate.PaymentOrder(sql.FieldEQ(FieldCreatedAt, v)) @@ -1072,79 +1072,79 @@ func FromAddressContainsFold(v string) predicate.PaymentOrder { return predicate.PaymentOrder(sql.FieldContainsFold(FieldFromAddress, v)) } -// ReturnAddressEQ applies the EQ predicate on the "return_address" field. -func ReturnAddressEQ(v string) predicate.PaymentOrder { - return predicate.PaymentOrder(sql.FieldEQ(FieldReturnAddress, v)) +// RefundOrRecipientAddressEQ applies the EQ predicate on the "refund_or_recipient_address" field. +func RefundOrRecipientAddressEQ(v string) predicate.PaymentOrder { + return predicate.PaymentOrder(sql.FieldEQ(FieldRefundOrRecipientAddress, v)) } -// ReturnAddressNEQ applies the NEQ predicate on the "return_address" field. -func ReturnAddressNEQ(v string) predicate.PaymentOrder { - return predicate.PaymentOrder(sql.FieldNEQ(FieldReturnAddress, v)) +// RefundOrRecipientAddressNEQ applies the NEQ predicate on the "refund_or_recipient_address" field. +func RefundOrRecipientAddressNEQ(v string) predicate.PaymentOrder { + return predicate.PaymentOrder(sql.FieldNEQ(FieldRefundOrRecipientAddress, v)) } -// ReturnAddressIn applies the In predicate on the "return_address" field. -func ReturnAddressIn(vs ...string) predicate.PaymentOrder { - return predicate.PaymentOrder(sql.FieldIn(FieldReturnAddress, vs...)) +// RefundOrRecipientAddressIn applies the In predicate on the "refund_or_recipient_address" field. +func RefundOrRecipientAddressIn(vs ...string) predicate.PaymentOrder { + return predicate.PaymentOrder(sql.FieldIn(FieldRefundOrRecipientAddress, vs...)) } -// ReturnAddressNotIn applies the NotIn predicate on the "return_address" field. -func ReturnAddressNotIn(vs ...string) predicate.PaymentOrder { - return predicate.PaymentOrder(sql.FieldNotIn(FieldReturnAddress, vs...)) +// RefundOrRecipientAddressNotIn applies the NotIn predicate on the "refund_or_recipient_address" field. +func RefundOrRecipientAddressNotIn(vs ...string) predicate.PaymentOrder { + return predicate.PaymentOrder(sql.FieldNotIn(FieldRefundOrRecipientAddress, vs...)) } -// ReturnAddressGT applies the GT predicate on the "return_address" field. -func ReturnAddressGT(v string) predicate.PaymentOrder { - return predicate.PaymentOrder(sql.FieldGT(FieldReturnAddress, v)) +// RefundOrRecipientAddressGT applies the GT predicate on the "refund_or_recipient_address" field. +func RefundOrRecipientAddressGT(v string) predicate.PaymentOrder { + return predicate.PaymentOrder(sql.FieldGT(FieldRefundOrRecipientAddress, v)) } -// ReturnAddressGTE applies the GTE predicate on the "return_address" field. -func ReturnAddressGTE(v string) predicate.PaymentOrder { - return predicate.PaymentOrder(sql.FieldGTE(FieldReturnAddress, v)) +// RefundOrRecipientAddressGTE applies the GTE predicate on the "refund_or_recipient_address" field. +func RefundOrRecipientAddressGTE(v string) predicate.PaymentOrder { + return predicate.PaymentOrder(sql.FieldGTE(FieldRefundOrRecipientAddress, v)) } -// ReturnAddressLT applies the LT predicate on the "return_address" field. -func ReturnAddressLT(v string) predicate.PaymentOrder { - return predicate.PaymentOrder(sql.FieldLT(FieldReturnAddress, v)) +// RefundOrRecipientAddressLT applies the LT predicate on the "refund_or_recipient_address" field. +func RefundOrRecipientAddressLT(v string) predicate.PaymentOrder { + return predicate.PaymentOrder(sql.FieldLT(FieldRefundOrRecipientAddress, v)) } -// ReturnAddressLTE applies the LTE predicate on the "return_address" field. -func ReturnAddressLTE(v string) predicate.PaymentOrder { - return predicate.PaymentOrder(sql.FieldLTE(FieldReturnAddress, v)) +// RefundOrRecipientAddressLTE applies the LTE predicate on the "refund_or_recipient_address" field. +func RefundOrRecipientAddressLTE(v string) predicate.PaymentOrder { + return predicate.PaymentOrder(sql.FieldLTE(FieldRefundOrRecipientAddress, v)) } -// ReturnAddressContains applies the Contains predicate on the "return_address" field. -func ReturnAddressContains(v string) predicate.PaymentOrder { - return predicate.PaymentOrder(sql.FieldContains(FieldReturnAddress, v)) +// RefundOrRecipientAddressContains applies the Contains predicate on the "refund_or_recipient_address" field. +func RefundOrRecipientAddressContains(v string) predicate.PaymentOrder { + return predicate.PaymentOrder(sql.FieldContains(FieldRefundOrRecipientAddress, v)) } -// ReturnAddressHasPrefix applies the HasPrefix predicate on the "return_address" field. -func ReturnAddressHasPrefix(v string) predicate.PaymentOrder { - return predicate.PaymentOrder(sql.FieldHasPrefix(FieldReturnAddress, v)) +// RefundOrRecipientAddressHasPrefix applies the HasPrefix predicate on the "refund_or_recipient_address" field. +func RefundOrRecipientAddressHasPrefix(v string) predicate.PaymentOrder { + return predicate.PaymentOrder(sql.FieldHasPrefix(FieldRefundOrRecipientAddress, v)) } -// ReturnAddressHasSuffix applies the HasSuffix predicate on the "return_address" field. -func ReturnAddressHasSuffix(v string) predicate.PaymentOrder { - return predicate.PaymentOrder(sql.FieldHasSuffix(FieldReturnAddress, v)) +// RefundOrRecipientAddressHasSuffix applies the HasSuffix predicate on the "refund_or_recipient_address" field. +func RefundOrRecipientAddressHasSuffix(v string) predicate.PaymentOrder { + return predicate.PaymentOrder(sql.FieldHasSuffix(FieldRefundOrRecipientAddress, v)) } -// ReturnAddressIsNil applies the IsNil predicate on the "return_address" field. -func ReturnAddressIsNil() predicate.PaymentOrder { - return predicate.PaymentOrder(sql.FieldIsNull(FieldReturnAddress)) +// RefundOrRecipientAddressIsNil applies the IsNil predicate on the "refund_or_recipient_address" field. +func RefundOrRecipientAddressIsNil() predicate.PaymentOrder { + return predicate.PaymentOrder(sql.FieldIsNull(FieldRefundOrRecipientAddress)) } -// ReturnAddressNotNil applies the NotNil predicate on the "return_address" field. -func ReturnAddressNotNil() predicate.PaymentOrder { - return predicate.PaymentOrder(sql.FieldNotNull(FieldReturnAddress)) +// RefundOrRecipientAddressNotNil applies the NotNil predicate on the "refund_or_recipient_address" field. +func RefundOrRecipientAddressNotNil() predicate.PaymentOrder { + return predicate.PaymentOrder(sql.FieldNotNull(FieldRefundOrRecipientAddress)) } -// ReturnAddressEqualFold applies the EqualFold predicate on the "return_address" field. -func ReturnAddressEqualFold(v string) predicate.PaymentOrder { - return predicate.PaymentOrder(sql.FieldEqualFold(FieldReturnAddress, v)) +// RefundOrRecipientAddressEqualFold applies the EqualFold predicate on the "refund_or_recipient_address" field. +func RefundOrRecipientAddressEqualFold(v string) predicate.PaymentOrder { + return predicate.PaymentOrder(sql.FieldEqualFold(FieldRefundOrRecipientAddress, v)) } -// ReturnAddressContainsFold applies the ContainsFold predicate on the "return_address" field. -func ReturnAddressContainsFold(v string) predicate.PaymentOrder { - return predicate.PaymentOrder(sql.FieldContainsFold(FieldReturnAddress, v)) +// RefundOrRecipientAddressContainsFold applies the ContainsFold predicate on the "refund_or_recipient_address" field. +func RefundOrRecipientAddressContainsFold(v string) predicate.PaymentOrder { + return predicate.PaymentOrder(sql.FieldContainsFold(FieldRefundOrRecipientAddress, v)) } // ReceiveAddressEQ applies the EQ predicate on the "receive_address" field. @@ -1642,81 +1642,6 @@ func AccountNameContainsFold(v string) predicate.PaymentOrder { return predicate.PaymentOrder(sql.FieldContainsFold(FieldAccountName, v)) } -// MemoEQ applies the EQ predicate on the "memo" field. -func MemoEQ(v string) predicate.PaymentOrder { - return predicate.PaymentOrder(sql.FieldEQ(FieldMemo, v)) -} - -// MemoNEQ applies the NEQ predicate on the "memo" field. -func MemoNEQ(v string) predicate.PaymentOrder { - return predicate.PaymentOrder(sql.FieldNEQ(FieldMemo, v)) -} - -// MemoIn applies the In predicate on the "memo" field. -func MemoIn(vs ...string) predicate.PaymentOrder { - return predicate.PaymentOrder(sql.FieldIn(FieldMemo, vs...)) -} - -// MemoNotIn applies the NotIn predicate on the "memo" field. -func MemoNotIn(vs ...string) predicate.PaymentOrder { - return predicate.PaymentOrder(sql.FieldNotIn(FieldMemo, vs...)) -} - -// MemoGT applies the GT predicate on the "memo" field. -func MemoGT(v string) predicate.PaymentOrder { - return predicate.PaymentOrder(sql.FieldGT(FieldMemo, v)) -} - -// MemoGTE applies the GTE predicate on the "memo" field. -func MemoGTE(v string) predicate.PaymentOrder { - return predicate.PaymentOrder(sql.FieldGTE(FieldMemo, v)) -} - -// MemoLT applies the LT predicate on the "memo" field. -func MemoLT(v string) predicate.PaymentOrder { - return predicate.PaymentOrder(sql.FieldLT(FieldMemo, v)) -} - -// MemoLTE applies the LTE predicate on the "memo" field. -func MemoLTE(v string) predicate.PaymentOrder { - return predicate.PaymentOrder(sql.FieldLTE(FieldMemo, v)) -} - -// MemoContains applies the Contains predicate on the "memo" field. -func MemoContains(v string) predicate.PaymentOrder { - return predicate.PaymentOrder(sql.FieldContains(FieldMemo, v)) -} - -// MemoHasPrefix applies the HasPrefix predicate on the "memo" field. -func MemoHasPrefix(v string) predicate.PaymentOrder { - return predicate.PaymentOrder(sql.FieldHasPrefix(FieldMemo, v)) -} - -// MemoHasSuffix applies the HasSuffix predicate on the "memo" field. -func MemoHasSuffix(v string) predicate.PaymentOrder { - return predicate.PaymentOrder(sql.FieldHasSuffix(FieldMemo, v)) -} - -// MemoIsNil applies the IsNil predicate on the "memo" field. -func MemoIsNil() predicate.PaymentOrder { - return predicate.PaymentOrder(sql.FieldIsNull(FieldMemo)) -} - -// MemoNotNil applies the NotNil predicate on the "memo" field. -func MemoNotNil() predicate.PaymentOrder { - return predicate.PaymentOrder(sql.FieldNotNull(FieldMemo)) -} - -// MemoEqualFold applies the EqualFold predicate on the "memo" field. -func MemoEqualFold(v string) predicate.PaymentOrder { - return predicate.PaymentOrder(sql.FieldEqualFold(FieldMemo, v)) -} - -// MemoContainsFold applies the ContainsFold predicate on the "memo" field. -func MemoContainsFold(v string) predicate.PaymentOrder { - return predicate.PaymentOrder(sql.FieldContainsFold(FieldMemo, v)) -} - // MetadataIsNil applies the IsNil predicate on the "metadata" field. func MetadataIsNil() predicate.PaymentOrder { return predicate.PaymentOrder(sql.FieldIsNull(FieldMetadata)) @@ -1937,6 +1862,81 @@ func CancellationReasonsNotNil() predicate.PaymentOrder { return predicate.PaymentOrder(sql.FieldNotNull(FieldCancellationReasons)) } +// MemoEQ applies the EQ predicate on the "memo" field. +func MemoEQ(v string) predicate.PaymentOrder { + return predicate.PaymentOrder(sql.FieldEQ(FieldMemo, v)) +} + +// MemoNEQ applies the NEQ predicate on the "memo" field. +func MemoNEQ(v string) predicate.PaymentOrder { + return predicate.PaymentOrder(sql.FieldNEQ(FieldMemo, v)) +} + +// MemoIn applies the In predicate on the "memo" field. +func MemoIn(vs ...string) predicate.PaymentOrder { + return predicate.PaymentOrder(sql.FieldIn(FieldMemo, vs...)) +} + +// MemoNotIn applies the NotIn predicate on the "memo" field. +func MemoNotIn(vs ...string) predicate.PaymentOrder { + return predicate.PaymentOrder(sql.FieldNotIn(FieldMemo, vs...)) +} + +// MemoGT applies the GT predicate on the "memo" field. +func MemoGT(v string) predicate.PaymentOrder { + return predicate.PaymentOrder(sql.FieldGT(FieldMemo, v)) +} + +// MemoGTE applies the GTE predicate on the "memo" field. +func MemoGTE(v string) predicate.PaymentOrder { + return predicate.PaymentOrder(sql.FieldGTE(FieldMemo, v)) +} + +// MemoLT applies the LT predicate on the "memo" field. +func MemoLT(v string) predicate.PaymentOrder { + return predicate.PaymentOrder(sql.FieldLT(FieldMemo, v)) +} + +// MemoLTE applies the LTE predicate on the "memo" field. +func MemoLTE(v string) predicate.PaymentOrder { + return predicate.PaymentOrder(sql.FieldLTE(FieldMemo, v)) +} + +// MemoContains applies the Contains predicate on the "memo" field. +func MemoContains(v string) predicate.PaymentOrder { + return predicate.PaymentOrder(sql.FieldContains(FieldMemo, v)) +} + +// MemoHasPrefix applies the HasPrefix predicate on the "memo" field. +func MemoHasPrefix(v string) predicate.PaymentOrder { + return predicate.PaymentOrder(sql.FieldHasPrefix(FieldMemo, v)) +} + +// MemoHasSuffix applies the HasSuffix predicate on the "memo" field. +func MemoHasSuffix(v string) predicate.PaymentOrder { + return predicate.PaymentOrder(sql.FieldHasSuffix(FieldMemo, v)) +} + +// MemoIsNil applies the IsNil predicate on the "memo" field. +func MemoIsNil() predicate.PaymentOrder { + return predicate.PaymentOrder(sql.FieldIsNull(FieldMemo)) +} + +// MemoNotNil applies the NotNil predicate on the "memo" field. +func MemoNotNil() predicate.PaymentOrder { + return predicate.PaymentOrder(sql.FieldNotNull(FieldMemo)) +} + +// MemoEqualFold applies the EqualFold predicate on the "memo" field. +func MemoEqualFold(v string) predicate.PaymentOrder { + return predicate.PaymentOrder(sql.FieldEqualFold(FieldMemo, v)) +} + +// MemoContainsFold applies the ContainsFold predicate on the "memo" field. +func MemoContainsFold(v string) predicate.PaymentOrder { + return predicate.PaymentOrder(sql.FieldContainsFold(FieldMemo, v)) +} + // StatusEQ applies the EQ predicate on the "status" field. func StatusEQ(v Status) predicate.PaymentOrder { return predicate.PaymentOrder(sql.FieldEQ(FieldStatus, v)) @@ -1957,6 +1957,26 @@ func StatusNotIn(vs ...Status) predicate.PaymentOrder { return predicate.PaymentOrder(sql.FieldNotIn(FieldStatus, vs...)) } +// DirectionEQ applies the EQ predicate on the "direction" field. +func DirectionEQ(v Direction) predicate.PaymentOrder { + return predicate.PaymentOrder(sql.FieldEQ(FieldDirection, v)) +} + +// DirectionNEQ applies the NEQ predicate on the "direction" field. +func DirectionNEQ(v Direction) predicate.PaymentOrder { + return predicate.PaymentOrder(sql.FieldNEQ(FieldDirection, v)) +} + +// DirectionIn applies the In predicate on the "direction" field. +func DirectionIn(vs ...Direction) predicate.PaymentOrder { + return predicate.PaymentOrder(sql.FieldIn(FieldDirection, vs...)) +} + +// DirectionNotIn applies the NotIn predicate on the "direction" field. +func DirectionNotIn(vs ...Direction) predicate.PaymentOrder { + return predicate.PaymentOrder(sql.FieldNotIn(FieldDirection, vs...)) +} + // OrderTypeEQ applies the EQ predicate on the "order_type" field. func OrderTypeEQ(v OrderType) predicate.PaymentOrder { return predicate.PaymentOrder(sql.FieldEQ(FieldOrderType, v)) diff --git a/ent/paymentorder_create.go b/ent/paymentorder_create.go index baef8e1e4..8733de045 100644 --- a/ent/paymentorder_create.go +++ b/ent/paymentorder_create.go @@ -260,16 +260,16 @@ func (_c *PaymentOrderCreate) SetNillableFromAddress(v *string) *PaymentOrderCre return _c } -// SetReturnAddress sets the "return_address" field. -func (_c *PaymentOrderCreate) SetReturnAddress(v string) *PaymentOrderCreate { - _c.mutation.SetReturnAddress(v) +// SetRefundOrRecipientAddress sets the "refund_or_recipient_address" field. +func (_c *PaymentOrderCreate) SetRefundOrRecipientAddress(v string) *PaymentOrderCreate { + _c.mutation.SetRefundOrRecipientAddress(v) return _c } -// SetNillableReturnAddress sets the "return_address" field if the given value is not nil. -func (_c *PaymentOrderCreate) SetNillableReturnAddress(v *string) *PaymentOrderCreate { +// SetNillableRefundOrRecipientAddress sets the "refund_or_recipient_address" field if the given value is not nil. +func (_c *PaymentOrderCreate) SetNillableRefundOrRecipientAddress(v *string) *PaymentOrderCreate { if v != nil { - _c.SetReturnAddress(*v) + _c.SetRefundOrRecipientAddress(*v) } return _c } @@ -354,20 +354,6 @@ func (_c *PaymentOrderCreate) SetAccountName(v string) *PaymentOrderCreate { return _c } -// SetMemo sets the "memo" field. -func (_c *PaymentOrderCreate) SetMemo(v string) *PaymentOrderCreate { - _c.mutation.SetMemo(v) - return _c -} - -// SetNillableMemo sets the "memo" field if the given value is not nil. -func (_c *PaymentOrderCreate) SetNillableMemo(v *string) *PaymentOrderCreate { - if v != nil { - _c.SetMemo(*v) - } - return _c -} - // SetMetadata sets the "metadata" field. func (_c *PaymentOrderCreate) SetMetadata(v map[string]interface{}) *PaymentOrderCreate { _c.mutation.SetMetadata(v) @@ -422,6 +408,20 @@ func (_c *PaymentOrderCreate) SetCancellationReasons(v []string) *PaymentOrderCr return _c } +// SetMemo sets the "memo" field. +func (_c *PaymentOrderCreate) SetMemo(v string) *PaymentOrderCreate { + _c.mutation.SetMemo(v) + return _c +} + +// SetNillableMemo sets the "memo" field if the given value is not nil. +func (_c *PaymentOrderCreate) SetNillableMemo(v *string) *PaymentOrderCreate { + if v != nil { + _c.SetMemo(*v) + } + return _c +} + // SetStatus sets the "status" field. func (_c *PaymentOrderCreate) SetStatus(v paymentorder.Status) *PaymentOrderCreate { _c.mutation.SetStatus(v) @@ -436,6 +436,20 @@ func (_c *PaymentOrderCreate) SetNillableStatus(v *paymentorder.Status) *Payment return _c } +// SetDirection sets the "direction" field. +func (_c *PaymentOrderCreate) SetDirection(v paymentorder.Direction) *PaymentOrderCreate { + _c.mutation.SetDirection(v) + return _c +} + +// SetNillableDirection sets the "direction" field if the given value is not nil. +func (_c *PaymentOrderCreate) SetNillableDirection(v *paymentorder.Direction) *PaymentOrderCreate { + if v != nil { + _c.SetDirection(*v) + } + return _c +} + // SetOrderType sets the "order_type" field. func (_c *PaymentOrderCreate) SetOrderType(v paymentorder.OrderType) *PaymentOrderCreate { _c.mutation.SetOrderType(v) @@ -672,6 +686,10 @@ func (_c *PaymentOrderCreate) defaults() { v := paymentorder.DefaultStatus _c.mutation.SetStatus(v) } + if _, ok := _c.mutation.Direction(); !ok { + v := paymentorder.DefaultDirection + _c.mutation.SetDirection(v) + } if _, ok := _c.mutation.OrderType(); !ok { v := paymentorder.DefaultOrderType _c.mutation.SetOrderType(v) @@ -741,9 +759,9 @@ func (_c *PaymentOrderCreate) check() error { return &ValidationError{Name: "from_address", err: fmt.Errorf(`ent: validator failed for field "PaymentOrder.from_address": %w`, err)} } } - if v, ok := _c.mutation.ReturnAddress(); ok { - if err := paymentorder.ReturnAddressValidator(v); err != nil { - return &ValidationError{Name: "return_address", err: fmt.Errorf(`ent: validator failed for field "PaymentOrder.return_address": %w`, err)} + if v, ok := _c.mutation.RefundOrRecipientAddress(); ok { + if err := paymentorder.RefundOrRecipientAddressValidator(v); err != nil { + return &ValidationError{Name: "refund_or_recipient_address", err: fmt.Errorf(`ent: validator failed for field "PaymentOrder.refund_or_recipient_address": %w`, err)} } } if v, ok := _c.mutation.ReceiveAddress(); ok { @@ -780,11 +798,6 @@ func (_c *PaymentOrderCreate) check() error { return &ValidationError{Name: "account_name", err: fmt.Errorf(`ent: validator failed for field "PaymentOrder.account_name": %w`, err)} } } - if v, ok := _c.mutation.Memo(); ok { - if err := paymentorder.MemoValidator(v); err != nil { - return &ValidationError{Name: "memo", err: fmt.Errorf(`ent: validator failed for field "PaymentOrder.memo": %w`, err)} - } - } if v, ok := _c.mutation.Sender(); ok { if err := paymentorder.SenderValidator(v); err != nil { return &ValidationError{Name: "sender", err: fmt.Errorf(`ent: validator failed for field "PaymentOrder.sender": %w`, err)} @@ -795,6 +808,11 @@ func (_c *PaymentOrderCreate) check() error { return &ValidationError{Name: "reference", err: fmt.Errorf(`ent: validator failed for field "PaymentOrder.reference": %w`, err)} } } + if v, ok := _c.mutation.Memo(); ok { + if err := paymentorder.MemoValidator(v); err != nil { + return &ValidationError{Name: "memo", err: fmt.Errorf(`ent: validator failed for field "PaymentOrder.memo": %w`, err)} + } + } if _, ok := _c.mutation.Status(); !ok { return &ValidationError{Name: "status", err: errors.New(`ent: missing required field "PaymentOrder.status"`)} } @@ -803,6 +821,14 @@ func (_c *PaymentOrderCreate) check() error { return &ValidationError{Name: "status", err: fmt.Errorf(`ent: validator failed for field "PaymentOrder.status": %w`, err)} } } + if _, ok := _c.mutation.Direction(); !ok { + return &ValidationError{Name: "direction", err: errors.New(`ent: missing required field "PaymentOrder.direction"`)} + } + if v, ok := _c.mutation.Direction(); ok { + if err := paymentorder.DirectionValidator(v); err != nil { + return &ValidationError{Name: "direction", err: fmt.Errorf(`ent: validator failed for field "PaymentOrder.direction": %w`, err)} + } + } if _, ok := _c.mutation.OrderType(); !ok { return &ValidationError{Name: "order_type", err: errors.New(`ent: missing required field "PaymentOrder.order_type"`)} } @@ -922,9 +948,9 @@ func (_c *PaymentOrderCreate) createSpec() (*PaymentOrder, *sqlgraph.CreateSpec) _spec.SetField(paymentorder.FieldFromAddress, field.TypeString, value) _node.FromAddress = value } - if value, ok := _c.mutation.ReturnAddress(); ok { - _spec.SetField(paymentorder.FieldReturnAddress, field.TypeString, value) - _node.ReturnAddress = value + if value, ok := _c.mutation.RefundOrRecipientAddress(); ok { + _spec.SetField(paymentorder.FieldRefundOrRecipientAddress, field.TypeString, value) + _node.RefundOrRecipientAddress = value } if value, ok := _c.mutation.ReceiveAddress(); ok { _spec.SetField(paymentorder.FieldReceiveAddress, field.TypeString, value) @@ -958,10 +984,6 @@ func (_c *PaymentOrderCreate) createSpec() (*PaymentOrder, *sqlgraph.CreateSpec) _spec.SetField(paymentorder.FieldAccountName, field.TypeString, value) _node.AccountName = value } - if value, ok := _c.mutation.Memo(); ok { - _spec.SetField(paymentorder.FieldMemo, field.TypeString, value) - _node.Memo = value - } if value, ok := _c.mutation.Metadata(); ok { _spec.SetField(paymentorder.FieldMetadata, field.TypeJSON, value) _node.Metadata = value @@ -982,10 +1004,18 @@ func (_c *PaymentOrderCreate) createSpec() (*PaymentOrder, *sqlgraph.CreateSpec) _spec.SetField(paymentorder.FieldCancellationReasons, field.TypeJSON, value) _node.CancellationReasons = value } + if value, ok := _c.mutation.Memo(); ok { + _spec.SetField(paymentorder.FieldMemo, field.TypeString, value) + _node.Memo = value + } if value, ok := _c.mutation.Status(); ok { _spec.SetField(paymentorder.FieldStatus, field.TypeEnum, value) _node.Status = value } + if value, ok := _c.mutation.Direction(); ok { + _spec.SetField(paymentorder.FieldDirection, field.TypeEnum, value) + _node.Direction = value + } if value, ok := _c.mutation.OrderType(); ok { _spec.SetField(paymentorder.FieldOrderType, field.TypeEnum, value) _node.OrderType = value @@ -1458,21 +1488,21 @@ func (u *PaymentOrderUpsert) ClearFromAddress() *PaymentOrderUpsert { return u } -// SetReturnAddress sets the "return_address" field. -func (u *PaymentOrderUpsert) SetReturnAddress(v string) *PaymentOrderUpsert { - u.Set(paymentorder.FieldReturnAddress, v) +// SetRefundOrRecipientAddress sets the "refund_or_recipient_address" field. +func (u *PaymentOrderUpsert) SetRefundOrRecipientAddress(v string) *PaymentOrderUpsert { + u.Set(paymentorder.FieldRefundOrRecipientAddress, v) return u } -// UpdateReturnAddress sets the "return_address" field to the value that was provided on create. -func (u *PaymentOrderUpsert) UpdateReturnAddress() *PaymentOrderUpsert { - u.SetExcluded(paymentorder.FieldReturnAddress) +// UpdateRefundOrRecipientAddress sets the "refund_or_recipient_address" field to the value that was provided on create. +func (u *PaymentOrderUpsert) UpdateRefundOrRecipientAddress() *PaymentOrderUpsert { + u.SetExcluded(paymentorder.FieldRefundOrRecipientAddress) return u } -// ClearReturnAddress clears the value of the "return_address" field. -func (u *PaymentOrderUpsert) ClearReturnAddress() *PaymentOrderUpsert { - u.SetNull(paymentorder.FieldReturnAddress) +// ClearRefundOrRecipientAddress clears the value of the "refund_or_recipient_address" field. +func (u *PaymentOrderUpsert) ClearRefundOrRecipientAddress() *PaymentOrderUpsert { + u.SetNull(paymentorder.FieldRefundOrRecipientAddress) return u } @@ -1602,24 +1632,6 @@ func (u *PaymentOrderUpsert) UpdateAccountName() *PaymentOrderUpsert { return u } -// SetMemo sets the "memo" field. -func (u *PaymentOrderUpsert) SetMemo(v string) *PaymentOrderUpsert { - u.Set(paymentorder.FieldMemo, v) - return u -} - -// UpdateMemo sets the "memo" field to the value that was provided on create. -func (u *PaymentOrderUpsert) UpdateMemo() *PaymentOrderUpsert { - u.SetExcluded(paymentorder.FieldMemo) - return u -} - -// ClearMemo clears the value of the "memo" field. -func (u *PaymentOrderUpsert) ClearMemo() *PaymentOrderUpsert { - u.SetNull(paymentorder.FieldMemo) - return u -} - // SetMetadata sets the "metadata" field. func (u *PaymentOrderUpsert) SetMetadata(v map[string]interface{}) *PaymentOrderUpsert { u.Set(paymentorder.FieldMetadata, v) @@ -1716,6 +1728,24 @@ func (u *PaymentOrderUpsert) ClearCancellationReasons() *PaymentOrderUpsert { return u } +// SetMemo sets the "memo" field. +func (u *PaymentOrderUpsert) SetMemo(v string) *PaymentOrderUpsert { + u.Set(paymentorder.FieldMemo, v) + return u +} + +// UpdateMemo sets the "memo" field to the value that was provided on create. +func (u *PaymentOrderUpsert) UpdateMemo() *PaymentOrderUpsert { + u.SetExcluded(paymentorder.FieldMemo) + return u +} + +// ClearMemo clears the value of the "memo" field. +func (u *PaymentOrderUpsert) ClearMemo() *PaymentOrderUpsert { + u.SetNull(paymentorder.FieldMemo) + return u +} + // SetStatus sets the "status" field. func (u *PaymentOrderUpsert) SetStatus(v paymentorder.Status) *PaymentOrderUpsert { u.Set(paymentorder.FieldStatus, v) @@ -1728,6 +1758,18 @@ func (u *PaymentOrderUpsert) UpdateStatus() *PaymentOrderUpsert { return u } +// SetDirection sets the "direction" field. +func (u *PaymentOrderUpsert) SetDirection(v paymentorder.Direction) *PaymentOrderUpsert { + u.Set(paymentorder.FieldDirection, v) + return u +} + +// UpdateDirection sets the "direction" field to the value that was provided on create. +func (u *PaymentOrderUpsert) UpdateDirection() *PaymentOrderUpsert { + u.SetExcluded(paymentorder.FieldDirection) + return u +} + // SetOrderType sets the "order_type" field. func (u *PaymentOrderUpsert) SetOrderType(v paymentorder.OrderType) *PaymentOrderUpsert { u.Set(paymentorder.FieldOrderType, v) @@ -2141,24 +2183,24 @@ func (u *PaymentOrderUpsertOne) ClearFromAddress() *PaymentOrderUpsertOne { }) } -// SetReturnAddress sets the "return_address" field. -func (u *PaymentOrderUpsertOne) SetReturnAddress(v string) *PaymentOrderUpsertOne { +// SetRefundOrRecipientAddress sets the "refund_or_recipient_address" field. +func (u *PaymentOrderUpsertOne) SetRefundOrRecipientAddress(v string) *PaymentOrderUpsertOne { return u.Update(func(s *PaymentOrderUpsert) { - s.SetReturnAddress(v) + s.SetRefundOrRecipientAddress(v) }) } -// UpdateReturnAddress sets the "return_address" field to the value that was provided on create. -func (u *PaymentOrderUpsertOne) UpdateReturnAddress() *PaymentOrderUpsertOne { +// UpdateRefundOrRecipientAddress sets the "refund_or_recipient_address" field to the value that was provided on create. +func (u *PaymentOrderUpsertOne) UpdateRefundOrRecipientAddress() *PaymentOrderUpsertOne { return u.Update(func(s *PaymentOrderUpsert) { - s.UpdateReturnAddress() + s.UpdateRefundOrRecipientAddress() }) } -// ClearReturnAddress clears the value of the "return_address" field. -func (u *PaymentOrderUpsertOne) ClearReturnAddress() *PaymentOrderUpsertOne { +// ClearRefundOrRecipientAddress clears the value of the "refund_or_recipient_address" field. +func (u *PaymentOrderUpsertOne) ClearRefundOrRecipientAddress() *PaymentOrderUpsertOne { return u.Update(func(s *PaymentOrderUpsert) { - s.ClearReturnAddress() + s.ClearRefundOrRecipientAddress() }) } @@ -2309,27 +2351,6 @@ func (u *PaymentOrderUpsertOne) UpdateAccountName() *PaymentOrderUpsertOne { }) } -// SetMemo sets the "memo" field. -func (u *PaymentOrderUpsertOne) SetMemo(v string) *PaymentOrderUpsertOne { - return u.Update(func(s *PaymentOrderUpsert) { - s.SetMemo(v) - }) -} - -// UpdateMemo sets the "memo" field to the value that was provided on create. -func (u *PaymentOrderUpsertOne) UpdateMemo() *PaymentOrderUpsertOne { - return u.Update(func(s *PaymentOrderUpsert) { - s.UpdateMemo() - }) -} - -// ClearMemo clears the value of the "memo" field. -func (u *PaymentOrderUpsertOne) ClearMemo() *PaymentOrderUpsertOne { - return u.Update(func(s *PaymentOrderUpsert) { - s.ClearMemo() - }) -} - // SetMetadata sets the "metadata" field. func (u *PaymentOrderUpsertOne) SetMetadata(v map[string]interface{}) *PaymentOrderUpsertOne { return u.Update(func(s *PaymentOrderUpsert) { @@ -2442,6 +2463,27 @@ func (u *PaymentOrderUpsertOne) ClearCancellationReasons() *PaymentOrderUpsertOn }) } +// SetMemo sets the "memo" field. +func (u *PaymentOrderUpsertOne) SetMemo(v string) *PaymentOrderUpsertOne { + return u.Update(func(s *PaymentOrderUpsert) { + s.SetMemo(v) + }) +} + +// UpdateMemo sets the "memo" field to the value that was provided on create. +func (u *PaymentOrderUpsertOne) UpdateMemo() *PaymentOrderUpsertOne { + return u.Update(func(s *PaymentOrderUpsert) { + s.UpdateMemo() + }) +} + +// ClearMemo clears the value of the "memo" field. +func (u *PaymentOrderUpsertOne) ClearMemo() *PaymentOrderUpsertOne { + return u.Update(func(s *PaymentOrderUpsert) { + s.ClearMemo() + }) +} + // SetStatus sets the "status" field. func (u *PaymentOrderUpsertOne) SetStatus(v paymentorder.Status) *PaymentOrderUpsertOne { return u.Update(func(s *PaymentOrderUpsert) { @@ -2456,6 +2498,20 @@ func (u *PaymentOrderUpsertOne) UpdateStatus() *PaymentOrderUpsertOne { }) } +// SetDirection sets the "direction" field. +func (u *PaymentOrderUpsertOne) SetDirection(v paymentorder.Direction) *PaymentOrderUpsertOne { + return u.Update(func(s *PaymentOrderUpsert) { + s.SetDirection(v) + }) +} + +// UpdateDirection sets the "direction" field to the value that was provided on create. +func (u *PaymentOrderUpsertOne) UpdateDirection() *PaymentOrderUpsertOne { + return u.Update(func(s *PaymentOrderUpsert) { + s.UpdateDirection() + }) +} + // SetOrderType sets the "order_type" field. func (u *PaymentOrderUpsertOne) SetOrderType(v paymentorder.OrderType) *PaymentOrderUpsertOne { return u.Update(func(s *PaymentOrderUpsert) { @@ -3038,24 +3094,24 @@ func (u *PaymentOrderUpsertBulk) ClearFromAddress() *PaymentOrderUpsertBulk { }) } -// SetReturnAddress sets the "return_address" field. -func (u *PaymentOrderUpsertBulk) SetReturnAddress(v string) *PaymentOrderUpsertBulk { +// SetRefundOrRecipientAddress sets the "refund_or_recipient_address" field. +func (u *PaymentOrderUpsertBulk) SetRefundOrRecipientAddress(v string) *PaymentOrderUpsertBulk { return u.Update(func(s *PaymentOrderUpsert) { - s.SetReturnAddress(v) + s.SetRefundOrRecipientAddress(v) }) } -// UpdateReturnAddress sets the "return_address" field to the value that was provided on create. -func (u *PaymentOrderUpsertBulk) UpdateReturnAddress() *PaymentOrderUpsertBulk { +// UpdateRefundOrRecipientAddress sets the "refund_or_recipient_address" field to the value that was provided on create. +func (u *PaymentOrderUpsertBulk) UpdateRefundOrRecipientAddress() *PaymentOrderUpsertBulk { return u.Update(func(s *PaymentOrderUpsert) { - s.UpdateReturnAddress() + s.UpdateRefundOrRecipientAddress() }) } -// ClearReturnAddress clears the value of the "return_address" field. -func (u *PaymentOrderUpsertBulk) ClearReturnAddress() *PaymentOrderUpsertBulk { +// ClearRefundOrRecipientAddress clears the value of the "refund_or_recipient_address" field. +func (u *PaymentOrderUpsertBulk) ClearRefundOrRecipientAddress() *PaymentOrderUpsertBulk { return u.Update(func(s *PaymentOrderUpsert) { - s.ClearReturnAddress() + s.ClearRefundOrRecipientAddress() }) } @@ -3206,27 +3262,6 @@ func (u *PaymentOrderUpsertBulk) UpdateAccountName() *PaymentOrderUpsertBulk { }) } -// SetMemo sets the "memo" field. -func (u *PaymentOrderUpsertBulk) SetMemo(v string) *PaymentOrderUpsertBulk { - return u.Update(func(s *PaymentOrderUpsert) { - s.SetMemo(v) - }) -} - -// UpdateMemo sets the "memo" field to the value that was provided on create. -func (u *PaymentOrderUpsertBulk) UpdateMemo() *PaymentOrderUpsertBulk { - return u.Update(func(s *PaymentOrderUpsert) { - s.UpdateMemo() - }) -} - -// ClearMemo clears the value of the "memo" field. -func (u *PaymentOrderUpsertBulk) ClearMemo() *PaymentOrderUpsertBulk { - return u.Update(func(s *PaymentOrderUpsert) { - s.ClearMemo() - }) -} - // SetMetadata sets the "metadata" field. func (u *PaymentOrderUpsertBulk) SetMetadata(v map[string]interface{}) *PaymentOrderUpsertBulk { return u.Update(func(s *PaymentOrderUpsert) { @@ -3339,6 +3374,27 @@ func (u *PaymentOrderUpsertBulk) ClearCancellationReasons() *PaymentOrderUpsertB }) } +// SetMemo sets the "memo" field. +func (u *PaymentOrderUpsertBulk) SetMemo(v string) *PaymentOrderUpsertBulk { + return u.Update(func(s *PaymentOrderUpsert) { + s.SetMemo(v) + }) +} + +// UpdateMemo sets the "memo" field to the value that was provided on create. +func (u *PaymentOrderUpsertBulk) UpdateMemo() *PaymentOrderUpsertBulk { + return u.Update(func(s *PaymentOrderUpsert) { + s.UpdateMemo() + }) +} + +// ClearMemo clears the value of the "memo" field. +func (u *PaymentOrderUpsertBulk) ClearMemo() *PaymentOrderUpsertBulk { + return u.Update(func(s *PaymentOrderUpsert) { + s.ClearMemo() + }) +} + // SetStatus sets the "status" field. func (u *PaymentOrderUpsertBulk) SetStatus(v paymentorder.Status) *PaymentOrderUpsertBulk { return u.Update(func(s *PaymentOrderUpsert) { @@ -3353,6 +3409,20 @@ func (u *PaymentOrderUpsertBulk) UpdateStatus() *PaymentOrderUpsertBulk { }) } +// SetDirection sets the "direction" field. +func (u *PaymentOrderUpsertBulk) SetDirection(v paymentorder.Direction) *PaymentOrderUpsertBulk { + return u.Update(func(s *PaymentOrderUpsert) { + s.SetDirection(v) + }) +} + +// UpdateDirection sets the "direction" field to the value that was provided on create. +func (u *PaymentOrderUpsertBulk) UpdateDirection() *PaymentOrderUpsertBulk { + return u.Update(func(s *PaymentOrderUpsert) { + s.UpdateDirection() + }) +} + // SetOrderType sets the "order_type" field. func (u *PaymentOrderUpsertBulk) SetOrderType(v paymentorder.OrderType) *PaymentOrderUpsertBulk { return u.Update(func(s *PaymentOrderUpsert) { diff --git a/ent/paymentorder_update.go b/ent/paymentorder_update.go index 46c9f5f9b..b2ed3bec5 100644 --- a/ent/paymentorder_update.go +++ b/ent/paymentorder_update.go @@ -376,23 +376,23 @@ func (_u *PaymentOrderUpdate) ClearFromAddress() *PaymentOrderUpdate { return _u } -// SetReturnAddress sets the "return_address" field. -func (_u *PaymentOrderUpdate) SetReturnAddress(v string) *PaymentOrderUpdate { - _u.mutation.SetReturnAddress(v) +// SetRefundOrRecipientAddress sets the "refund_or_recipient_address" field. +func (_u *PaymentOrderUpdate) SetRefundOrRecipientAddress(v string) *PaymentOrderUpdate { + _u.mutation.SetRefundOrRecipientAddress(v) return _u } -// SetNillableReturnAddress sets the "return_address" field if the given value is not nil. -func (_u *PaymentOrderUpdate) SetNillableReturnAddress(v *string) *PaymentOrderUpdate { +// SetNillableRefundOrRecipientAddress sets the "refund_or_recipient_address" field if the given value is not nil. +func (_u *PaymentOrderUpdate) SetNillableRefundOrRecipientAddress(v *string) *PaymentOrderUpdate { if v != nil { - _u.SetReturnAddress(*v) + _u.SetRefundOrRecipientAddress(*v) } return _u } -// ClearReturnAddress clears the value of the "return_address" field. -func (_u *PaymentOrderUpdate) ClearReturnAddress() *PaymentOrderUpdate { - _u.mutation.ClearReturnAddress() +// ClearRefundOrRecipientAddress clears the value of the "refund_or_recipient_address" field. +func (_u *PaymentOrderUpdate) ClearRefundOrRecipientAddress() *PaymentOrderUpdate { + _u.mutation.ClearRefundOrRecipientAddress() return _u } @@ -530,26 +530,6 @@ func (_u *PaymentOrderUpdate) SetNillableAccountName(v *string) *PaymentOrderUpd return _u } -// SetMemo sets the "memo" field. -func (_u *PaymentOrderUpdate) SetMemo(v string) *PaymentOrderUpdate { - _u.mutation.SetMemo(v) - return _u -} - -// SetNillableMemo sets the "memo" field if the given value is not nil. -func (_u *PaymentOrderUpdate) SetNillableMemo(v *string) *PaymentOrderUpdate { - if v != nil { - _u.SetMemo(*v) - } - return _u -} - -// ClearMemo clears the value of the "memo" field. -func (_u *PaymentOrderUpdate) ClearMemo() *PaymentOrderUpdate { - _u.mutation.ClearMemo() - return _u -} - // SetMetadata sets the "metadata" field. func (_u *PaymentOrderUpdate) SetMetadata(v map[string]interface{}) *PaymentOrderUpdate { _u.mutation.SetMetadata(v) @@ -647,6 +627,26 @@ func (_u *PaymentOrderUpdate) ClearCancellationReasons() *PaymentOrderUpdate { return _u } +// SetMemo sets the "memo" field. +func (_u *PaymentOrderUpdate) SetMemo(v string) *PaymentOrderUpdate { + _u.mutation.SetMemo(v) + return _u +} + +// SetNillableMemo sets the "memo" field if the given value is not nil. +func (_u *PaymentOrderUpdate) SetNillableMemo(v *string) *PaymentOrderUpdate { + if v != nil { + _u.SetMemo(*v) + } + return _u +} + +// ClearMemo clears the value of the "memo" field. +func (_u *PaymentOrderUpdate) ClearMemo() *PaymentOrderUpdate { + _u.mutation.ClearMemo() + return _u +} + // SetStatus sets the "status" field. func (_u *PaymentOrderUpdate) SetStatus(v paymentorder.Status) *PaymentOrderUpdate { _u.mutation.SetStatus(v) @@ -661,6 +661,20 @@ func (_u *PaymentOrderUpdate) SetNillableStatus(v *paymentorder.Status) *Payment return _u } +// SetDirection sets the "direction" field. +func (_u *PaymentOrderUpdate) SetDirection(v paymentorder.Direction) *PaymentOrderUpdate { + _u.mutation.SetDirection(v) + return _u +} + +// SetNillableDirection sets the "direction" field if the given value is not nil. +func (_u *PaymentOrderUpdate) SetNillableDirection(v *paymentorder.Direction) *PaymentOrderUpdate { + if v != nil { + _u.SetDirection(*v) + } + return _u +} + // SetOrderType sets the "order_type" field. func (_u *PaymentOrderUpdate) SetOrderType(v paymentorder.OrderType) *PaymentOrderUpdate { _u.mutation.SetOrderType(v) @@ -922,9 +936,9 @@ func (_u *PaymentOrderUpdate) check() error { return &ValidationError{Name: "from_address", err: fmt.Errorf(`ent: validator failed for field "PaymentOrder.from_address": %w`, err)} } } - if v, ok := _u.mutation.ReturnAddress(); ok { - if err := paymentorder.ReturnAddressValidator(v); err != nil { - return &ValidationError{Name: "return_address", err: fmt.Errorf(`ent: validator failed for field "PaymentOrder.return_address": %w`, err)} + if v, ok := _u.mutation.RefundOrRecipientAddress(); ok { + if err := paymentorder.RefundOrRecipientAddressValidator(v); err != nil { + return &ValidationError{Name: "refund_or_recipient_address", err: fmt.Errorf(`ent: validator failed for field "PaymentOrder.refund_or_recipient_address": %w`, err)} } } if v, ok := _u.mutation.ReceiveAddress(); ok { @@ -952,11 +966,6 @@ func (_u *PaymentOrderUpdate) check() error { return &ValidationError{Name: "account_name", err: fmt.Errorf(`ent: validator failed for field "PaymentOrder.account_name": %w`, err)} } } - if v, ok := _u.mutation.Memo(); ok { - if err := paymentorder.MemoValidator(v); err != nil { - return &ValidationError{Name: "memo", err: fmt.Errorf(`ent: validator failed for field "PaymentOrder.memo": %w`, err)} - } - } if v, ok := _u.mutation.Sender(); ok { if err := paymentorder.SenderValidator(v); err != nil { return &ValidationError{Name: "sender", err: fmt.Errorf(`ent: validator failed for field "PaymentOrder.sender": %w`, err)} @@ -967,11 +976,21 @@ func (_u *PaymentOrderUpdate) check() error { return &ValidationError{Name: "reference", err: fmt.Errorf(`ent: validator failed for field "PaymentOrder.reference": %w`, err)} } } + if v, ok := _u.mutation.Memo(); ok { + if err := paymentorder.MemoValidator(v); err != nil { + return &ValidationError{Name: "memo", err: fmt.Errorf(`ent: validator failed for field "PaymentOrder.memo": %w`, err)} + } + } if v, ok := _u.mutation.Status(); ok { if err := paymentorder.StatusValidator(v); err != nil { return &ValidationError{Name: "status", err: fmt.Errorf(`ent: validator failed for field "PaymentOrder.status": %w`, err)} } } + if v, ok := _u.mutation.Direction(); ok { + if err := paymentorder.DirectionValidator(v); err != nil { + return &ValidationError{Name: "direction", err: fmt.Errorf(`ent: validator failed for field "PaymentOrder.direction": %w`, err)} + } + } if v, ok := _u.mutation.OrderType(); ok { if err := paymentorder.OrderTypeValidator(v); err != nil { return &ValidationError{Name: "order_type", err: fmt.Errorf(`ent: validator failed for field "PaymentOrder.order_type": %w`, err)} @@ -1094,11 +1113,11 @@ func (_u *PaymentOrderUpdate) sqlSave(ctx context.Context) (_node int, err error if _u.mutation.FromAddressCleared() { _spec.ClearField(paymentorder.FieldFromAddress, field.TypeString) } - if value, ok := _u.mutation.ReturnAddress(); ok { - _spec.SetField(paymentorder.FieldReturnAddress, field.TypeString, value) + if value, ok := _u.mutation.RefundOrRecipientAddress(); ok { + _spec.SetField(paymentorder.FieldRefundOrRecipientAddress, field.TypeString, value) } - if _u.mutation.ReturnAddressCleared() { - _spec.ClearField(paymentorder.FieldReturnAddress, field.TypeString) + if _u.mutation.RefundOrRecipientAddressCleared() { + _spec.ClearField(paymentorder.FieldRefundOrRecipientAddress, field.TypeString) } if value, ok := _u.mutation.ReceiveAddress(); ok { _spec.SetField(paymentorder.FieldReceiveAddress, field.TypeString, value) @@ -1139,12 +1158,6 @@ func (_u *PaymentOrderUpdate) sqlSave(ctx context.Context) (_node int, err error if value, ok := _u.mutation.AccountName(); ok { _spec.SetField(paymentorder.FieldAccountName, field.TypeString, value) } - if value, ok := _u.mutation.Memo(); ok { - _spec.SetField(paymentorder.FieldMemo, field.TypeString, value) - } - if _u.mutation.MemoCleared() { - _spec.ClearField(paymentorder.FieldMemo, field.TypeString) - } if value, ok := _u.mutation.Metadata(); ok { _spec.SetField(paymentorder.FieldMetadata, field.TypeJSON, value) } @@ -1183,9 +1196,18 @@ func (_u *PaymentOrderUpdate) sqlSave(ctx context.Context) (_node int, err error if _u.mutation.CancellationReasonsCleared() { _spec.ClearField(paymentorder.FieldCancellationReasons, field.TypeJSON) } + if value, ok := _u.mutation.Memo(); ok { + _spec.SetField(paymentorder.FieldMemo, field.TypeString, value) + } + if _u.mutation.MemoCleared() { + _spec.ClearField(paymentorder.FieldMemo, field.TypeString) + } if value, ok := _u.mutation.Status(); ok { _spec.SetField(paymentorder.FieldStatus, field.TypeEnum, value) } + if value, ok := _u.mutation.Direction(); ok { + _spec.SetField(paymentorder.FieldDirection, field.TypeEnum, value) + } if value, ok := _u.mutation.OrderType(); ok { _spec.SetField(paymentorder.FieldOrderType, field.TypeEnum, value) } @@ -1782,23 +1804,23 @@ func (_u *PaymentOrderUpdateOne) ClearFromAddress() *PaymentOrderUpdateOne { return _u } -// SetReturnAddress sets the "return_address" field. -func (_u *PaymentOrderUpdateOne) SetReturnAddress(v string) *PaymentOrderUpdateOne { - _u.mutation.SetReturnAddress(v) +// SetRefundOrRecipientAddress sets the "refund_or_recipient_address" field. +func (_u *PaymentOrderUpdateOne) SetRefundOrRecipientAddress(v string) *PaymentOrderUpdateOne { + _u.mutation.SetRefundOrRecipientAddress(v) return _u } -// SetNillableReturnAddress sets the "return_address" field if the given value is not nil. -func (_u *PaymentOrderUpdateOne) SetNillableReturnAddress(v *string) *PaymentOrderUpdateOne { +// SetNillableRefundOrRecipientAddress sets the "refund_or_recipient_address" field if the given value is not nil. +func (_u *PaymentOrderUpdateOne) SetNillableRefundOrRecipientAddress(v *string) *PaymentOrderUpdateOne { if v != nil { - _u.SetReturnAddress(*v) + _u.SetRefundOrRecipientAddress(*v) } return _u } -// ClearReturnAddress clears the value of the "return_address" field. -func (_u *PaymentOrderUpdateOne) ClearReturnAddress() *PaymentOrderUpdateOne { - _u.mutation.ClearReturnAddress() +// ClearRefundOrRecipientAddress clears the value of the "refund_or_recipient_address" field. +func (_u *PaymentOrderUpdateOne) ClearRefundOrRecipientAddress() *PaymentOrderUpdateOne { + _u.mutation.ClearRefundOrRecipientAddress() return _u } @@ -1936,26 +1958,6 @@ func (_u *PaymentOrderUpdateOne) SetNillableAccountName(v *string) *PaymentOrder return _u } -// SetMemo sets the "memo" field. -func (_u *PaymentOrderUpdateOne) SetMemo(v string) *PaymentOrderUpdateOne { - _u.mutation.SetMemo(v) - return _u -} - -// SetNillableMemo sets the "memo" field if the given value is not nil. -func (_u *PaymentOrderUpdateOne) SetNillableMemo(v *string) *PaymentOrderUpdateOne { - if v != nil { - _u.SetMemo(*v) - } - return _u -} - -// ClearMemo clears the value of the "memo" field. -func (_u *PaymentOrderUpdateOne) ClearMemo() *PaymentOrderUpdateOne { - _u.mutation.ClearMemo() - return _u -} - // SetMetadata sets the "metadata" field. func (_u *PaymentOrderUpdateOne) SetMetadata(v map[string]interface{}) *PaymentOrderUpdateOne { _u.mutation.SetMetadata(v) @@ -2053,6 +2055,26 @@ func (_u *PaymentOrderUpdateOne) ClearCancellationReasons() *PaymentOrderUpdateO return _u } +// SetMemo sets the "memo" field. +func (_u *PaymentOrderUpdateOne) SetMemo(v string) *PaymentOrderUpdateOne { + _u.mutation.SetMemo(v) + return _u +} + +// SetNillableMemo sets the "memo" field if the given value is not nil. +func (_u *PaymentOrderUpdateOne) SetNillableMemo(v *string) *PaymentOrderUpdateOne { + if v != nil { + _u.SetMemo(*v) + } + return _u +} + +// ClearMemo clears the value of the "memo" field. +func (_u *PaymentOrderUpdateOne) ClearMemo() *PaymentOrderUpdateOne { + _u.mutation.ClearMemo() + return _u +} + // SetStatus sets the "status" field. func (_u *PaymentOrderUpdateOne) SetStatus(v paymentorder.Status) *PaymentOrderUpdateOne { _u.mutation.SetStatus(v) @@ -2067,6 +2089,20 @@ func (_u *PaymentOrderUpdateOne) SetNillableStatus(v *paymentorder.Status) *Paym return _u } +// SetDirection sets the "direction" field. +func (_u *PaymentOrderUpdateOne) SetDirection(v paymentorder.Direction) *PaymentOrderUpdateOne { + _u.mutation.SetDirection(v) + return _u +} + +// SetNillableDirection sets the "direction" field if the given value is not nil. +func (_u *PaymentOrderUpdateOne) SetNillableDirection(v *paymentorder.Direction) *PaymentOrderUpdateOne { + if v != nil { + _u.SetDirection(*v) + } + return _u +} + // SetOrderType sets the "order_type" field. func (_u *PaymentOrderUpdateOne) SetOrderType(v paymentorder.OrderType) *PaymentOrderUpdateOne { _u.mutation.SetOrderType(v) @@ -2341,9 +2377,9 @@ func (_u *PaymentOrderUpdateOne) check() error { return &ValidationError{Name: "from_address", err: fmt.Errorf(`ent: validator failed for field "PaymentOrder.from_address": %w`, err)} } } - if v, ok := _u.mutation.ReturnAddress(); ok { - if err := paymentorder.ReturnAddressValidator(v); err != nil { - return &ValidationError{Name: "return_address", err: fmt.Errorf(`ent: validator failed for field "PaymentOrder.return_address": %w`, err)} + if v, ok := _u.mutation.RefundOrRecipientAddress(); ok { + if err := paymentorder.RefundOrRecipientAddressValidator(v); err != nil { + return &ValidationError{Name: "refund_or_recipient_address", err: fmt.Errorf(`ent: validator failed for field "PaymentOrder.refund_or_recipient_address": %w`, err)} } } if v, ok := _u.mutation.ReceiveAddress(); ok { @@ -2371,11 +2407,6 @@ func (_u *PaymentOrderUpdateOne) check() error { return &ValidationError{Name: "account_name", err: fmt.Errorf(`ent: validator failed for field "PaymentOrder.account_name": %w`, err)} } } - if v, ok := _u.mutation.Memo(); ok { - if err := paymentorder.MemoValidator(v); err != nil { - return &ValidationError{Name: "memo", err: fmt.Errorf(`ent: validator failed for field "PaymentOrder.memo": %w`, err)} - } - } if v, ok := _u.mutation.Sender(); ok { if err := paymentorder.SenderValidator(v); err != nil { return &ValidationError{Name: "sender", err: fmt.Errorf(`ent: validator failed for field "PaymentOrder.sender": %w`, err)} @@ -2386,11 +2417,21 @@ func (_u *PaymentOrderUpdateOne) check() error { return &ValidationError{Name: "reference", err: fmt.Errorf(`ent: validator failed for field "PaymentOrder.reference": %w`, err)} } } + if v, ok := _u.mutation.Memo(); ok { + if err := paymentorder.MemoValidator(v); err != nil { + return &ValidationError{Name: "memo", err: fmt.Errorf(`ent: validator failed for field "PaymentOrder.memo": %w`, err)} + } + } if v, ok := _u.mutation.Status(); ok { if err := paymentorder.StatusValidator(v); err != nil { return &ValidationError{Name: "status", err: fmt.Errorf(`ent: validator failed for field "PaymentOrder.status": %w`, err)} } } + if v, ok := _u.mutation.Direction(); ok { + if err := paymentorder.DirectionValidator(v); err != nil { + return &ValidationError{Name: "direction", err: fmt.Errorf(`ent: validator failed for field "PaymentOrder.direction": %w`, err)} + } + } if v, ok := _u.mutation.OrderType(); ok { if err := paymentorder.OrderTypeValidator(v); err != nil { return &ValidationError{Name: "order_type", err: fmt.Errorf(`ent: validator failed for field "PaymentOrder.order_type": %w`, err)} @@ -2530,11 +2571,11 @@ func (_u *PaymentOrderUpdateOne) sqlSave(ctx context.Context) (_node *PaymentOrd if _u.mutation.FromAddressCleared() { _spec.ClearField(paymentorder.FieldFromAddress, field.TypeString) } - if value, ok := _u.mutation.ReturnAddress(); ok { - _spec.SetField(paymentorder.FieldReturnAddress, field.TypeString, value) + if value, ok := _u.mutation.RefundOrRecipientAddress(); ok { + _spec.SetField(paymentorder.FieldRefundOrRecipientAddress, field.TypeString, value) } - if _u.mutation.ReturnAddressCleared() { - _spec.ClearField(paymentorder.FieldReturnAddress, field.TypeString) + if _u.mutation.RefundOrRecipientAddressCleared() { + _spec.ClearField(paymentorder.FieldRefundOrRecipientAddress, field.TypeString) } if value, ok := _u.mutation.ReceiveAddress(); ok { _spec.SetField(paymentorder.FieldReceiveAddress, field.TypeString, value) @@ -2575,12 +2616,6 @@ func (_u *PaymentOrderUpdateOne) sqlSave(ctx context.Context) (_node *PaymentOrd if value, ok := _u.mutation.AccountName(); ok { _spec.SetField(paymentorder.FieldAccountName, field.TypeString, value) } - if value, ok := _u.mutation.Memo(); ok { - _spec.SetField(paymentorder.FieldMemo, field.TypeString, value) - } - if _u.mutation.MemoCleared() { - _spec.ClearField(paymentorder.FieldMemo, field.TypeString) - } if value, ok := _u.mutation.Metadata(); ok { _spec.SetField(paymentorder.FieldMetadata, field.TypeJSON, value) } @@ -2619,9 +2654,18 @@ func (_u *PaymentOrderUpdateOne) sqlSave(ctx context.Context) (_node *PaymentOrd if _u.mutation.CancellationReasonsCleared() { _spec.ClearField(paymentorder.FieldCancellationReasons, field.TypeJSON) } + if value, ok := _u.mutation.Memo(); ok { + _spec.SetField(paymentorder.FieldMemo, field.TypeString, value) + } + if _u.mutation.MemoCleared() { + _spec.ClearField(paymentorder.FieldMemo, field.TypeString) + } if value, ok := _u.mutation.Status(); ok { _spec.SetField(paymentorder.FieldStatus, field.TypeEnum, value) } + if value, ok := _u.mutation.Direction(); ok { + _spec.SetField(paymentorder.FieldDirection, field.TypeEnum, value) + } if value, ok := _u.mutation.OrderType(); ok { _spec.SetField(paymentorder.FieldOrderType, field.TypeEnum, value) } diff --git a/ent/paymentorderfulfillment/paymentorderfulfillment.go b/ent/paymentorderfulfillment/paymentorderfulfillment.go index c16b9b9b0..e3ecda5cf 100644 --- a/ent/paymentorderfulfillment/paymentorderfulfillment.go +++ b/ent/paymentorderfulfillment/paymentorderfulfillment.go @@ -92,9 +92,10 @@ const DefaultValidationStatus = ValidationStatusPending // ValidationStatus values. const ( - ValidationStatusPending ValidationStatus = "pending" - ValidationStatusSuccess ValidationStatus = "success" - ValidationStatusFailed ValidationStatus = "failed" + ValidationStatusPending ValidationStatus = "pending" + ValidationStatusSuccess ValidationStatus = "success" + ValidationStatusFailed ValidationStatus = "failed" + ValidationStatusRefunded ValidationStatus = "refunded" ) func (vs ValidationStatus) String() string { @@ -104,7 +105,7 @@ func (vs ValidationStatus) String() string { // ValidationStatusValidator is a validator for the "validation_status" field enum values. It is called by the builders before save. func ValidationStatusValidator(vs ValidationStatus) error { switch vs { - case ValidationStatusPending, ValidationStatusSuccess, ValidationStatusFailed: + case ValidationStatusPending, ValidationStatusSuccess, ValidationStatusFailed, ValidationStatusRefunded: return nil default: return fmt.Errorf("paymentorderfulfillment: invalid enum value for validation_status field: %q", vs) diff --git a/ent/providerordertoken.go b/ent/providerordertoken.go index 8e567a466..5118ab8d3 100644 --- a/ent/providerordertoken.go +++ b/ent/providerordertoken.go @@ -26,12 +26,14 @@ type ProviderOrderToken struct { CreatedAt time.Time `json:"created_at,omitempty"` // UpdatedAt holds the value of the "updated_at" field. UpdatedAt time.Time `json:"updated_at,omitempty"` - // FixedConversionRate holds the value of the "fixed_conversion_rate" field. - FixedConversionRate decimal.Decimal `json:"fixed_conversion_rate,omitempty"` - // FloatingConversionRate holds the value of the "floating_conversion_rate" field. - FloatingConversionRate decimal.Decimal `json:"floating_conversion_rate,omitempty"` - // ConversionRateType holds the value of the "conversion_rate_type" field. - ConversionRateType providerordertoken.ConversionRateType `json:"conversion_rate_type,omitempty"` + // FixedBuyRate holds the value of the "fixed_buy_rate" field. + FixedBuyRate decimal.Decimal `json:"fixed_buy_rate,omitempty"` + // FixedSellRate holds the value of the "fixed_sell_rate" field. + FixedSellRate decimal.Decimal `json:"fixed_sell_rate,omitempty"` + // FloatingBuyDelta holds the value of the "floating_buy_delta" field. + FloatingBuyDelta decimal.Decimal `json:"floating_buy_delta,omitempty"` + // FloatingSellDelta holds the value of the "floating_sell_delta" field. + FloatingSellDelta decimal.Decimal `json:"floating_sell_delta,omitempty"` // MaxOrderAmount holds the value of the "max_order_amount" field. MaxOrderAmount decimal.Decimal `json:"max_order_amount,omitempty"` // MinOrderAmount holds the value of the "min_order_amount" field. @@ -108,11 +110,11 @@ func (*ProviderOrderToken) scanValues(columns []string) ([]any, error) { values := make([]any, len(columns)) for i := range columns { switch columns[i] { - case providerordertoken.FieldFixedConversionRate, providerordertoken.FieldFloatingConversionRate, providerordertoken.FieldMaxOrderAmount, providerordertoken.FieldMinOrderAmount, providerordertoken.FieldMaxOrderAmountOtc, providerordertoken.FieldMinOrderAmountOtc, providerordertoken.FieldRateSlippage: + case providerordertoken.FieldFixedBuyRate, providerordertoken.FieldFixedSellRate, providerordertoken.FieldFloatingBuyDelta, providerordertoken.FieldFloatingSellDelta, providerordertoken.FieldMaxOrderAmount, providerordertoken.FieldMinOrderAmount, providerordertoken.FieldMaxOrderAmountOtc, providerordertoken.FieldMinOrderAmountOtc, providerordertoken.FieldRateSlippage: values[i] = new(decimal.Decimal) case providerordertoken.FieldID: values[i] = new(sql.NullInt64) - case providerordertoken.FieldConversionRateType, providerordertoken.FieldSettlementAddress, providerordertoken.FieldPayoutAddress, providerordertoken.FieldNetwork: + case providerordertoken.FieldSettlementAddress, providerordertoken.FieldPayoutAddress, providerordertoken.FieldNetwork: values[i] = new(sql.NullString) case providerordertoken.FieldCreatedAt, providerordertoken.FieldUpdatedAt: values[i] = new(sql.NullTime) @@ -155,23 +157,29 @@ func (_m *ProviderOrderToken) assignValues(columns []string, values []any) error } else if value.Valid { _m.UpdatedAt = value.Time } - case providerordertoken.FieldFixedConversionRate: + case providerordertoken.FieldFixedBuyRate: if value, ok := values[i].(*decimal.Decimal); !ok { - return fmt.Errorf("unexpected type %T for field fixed_conversion_rate", values[i]) + return fmt.Errorf("unexpected type %T for field fixed_buy_rate", values[i]) } else if value != nil { - _m.FixedConversionRate = *value + _m.FixedBuyRate = *value } - case providerordertoken.FieldFloatingConversionRate: + case providerordertoken.FieldFixedSellRate: if value, ok := values[i].(*decimal.Decimal); !ok { - return fmt.Errorf("unexpected type %T for field floating_conversion_rate", values[i]) + return fmt.Errorf("unexpected type %T for field fixed_sell_rate", values[i]) } else if value != nil { - _m.FloatingConversionRate = *value + _m.FixedSellRate = *value } - case providerordertoken.FieldConversionRateType: - if value, ok := values[i].(*sql.NullString); !ok { - return fmt.Errorf("unexpected type %T for field conversion_rate_type", values[i]) - } else if value.Valid { - _m.ConversionRateType = providerordertoken.ConversionRateType(value.String) + case providerordertoken.FieldFloatingBuyDelta: + if value, ok := values[i].(*decimal.Decimal); !ok { + return fmt.Errorf("unexpected type %T for field floating_buy_delta", values[i]) + } else if value != nil { + _m.FloatingBuyDelta = *value + } + case providerordertoken.FieldFloatingSellDelta: + if value, ok := values[i].(*decimal.Decimal); !ok { + return fmt.Errorf("unexpected type %T for field floating_sell_delta", values[i]) + } else if value != nil { + _m.FloatingSellDelta = *value } case providerordertoken.FieldMaxOrderAmount: if value, ok := values[i].(*decimal.Decimal); !ok { @@ -299,14 +307,17 @@ func (_m *ProviderOrderToken) String() string { builder.WriteString("updated_at=") builder.WriteString(_m.UpdatedAt.Format(time.ANSIC)) builder.WriteString(", ") - builder.WriteString("fixed_conversion_rate=") - builder.WriteString(fmt.Sprintf("%v", _m.FixedConversionRate)) + builder.WriteString("fixed_buy_rate=") + builder.WriteString(fmt.Sprintf("%v", _m.FixedBuyRate)) + builder.WriteString(", ") + builder.WriteString("fixed_sell_rate=") + builder.WriteString(fmt.Sprintf("%v", _m.FixedSellRate)) builder.WriteString(", ") - builder.WriteString("floating_conversion_rate=") - builder.WriteString(fmt.Sprintf("%v", _m.FloatingConversionRate)) + builder.WriteString("floating_buy_delta=") + builder.WriteString(fmt.Sprintf("%v", _m.FloatingBuyDelta)) builder.WriteString(", ") - builder.WriteString("conversion_rate_type=") - builder.WriteString(fmt.Sprintf("%v", _m.ConversionRateType)) + builder.WriteString("floating_sell_delta=") + builder.WriteString(fmt.Sprintf("%v", _m.FloatingSellDelta)) builder.WriteString(", ") builder.WriteString("max_order_amount=") builder.WriteString(fmt.Sprintf("%v", _m.MaxOrderAmount)) diff --git a/ent/providerordertoken/providerordertoken.go b/ent/providerordertoken/providerordertoken.go index 0f0f244d7..9dfcf1b5c 100644 --- a/ent/providerordertoken/providerordertoken.go +++ b/ent/providerordertoken/providerordertoken.go @@ -3,7 +3,6 @@ package providerordertoken import ( - "fmt" "time" "entgo.io/ent/dialect/sql" @@ -19,12 +18,14 @@ const ( FieldCreatedAt = "created_at" // FieldUpdatedAt holds the string denoting the updated_at field in the database. FieldUpdatedAt = "updated_at" - // FieldFixedConversionRate holds the string denoting the fixed_conversion_rate field in the database. - FieldFixedConversionRate = "fixed_conversion_rate" - // FieldFloatingConversionRate holds the string denoting the floating_conversion_rate field in the database. - FieldFloatingConversionRate = "floating_conversion_rate" - // FieldConversionRateType holds the string denoting the conversion_rate_type field in the database. - FieldConversionRateType = "conversion_rate_type" + // FieldFixedBuyRate holds the string denoting the fixed_buy_rate field in the database. + FieldFixedBuyRate = "fixed_buy_rate" + // FieldFixedSellRate holds the string denoting the fixed_sell_rate field in the database. + FieldFixedSellRate = "fixed_sell_rate" + // FieldFloatingBuyDelta holds the string denoting the floating_buy_delta field in the database. + FieldFloatingBuyDelta = "floating_buy_delta" + // FieldFloatingSellDelta holds the string denoting the floating_sell_delta field in the database. + FieldFloatingSellDelta = "floating_sell_delta" // FieldMaxOrderAmount holds the string denoting the max_order_amount field in the database. FieldMaxOrderAmount = "max_order_amount" // FieldMinOrderAmount holds the string denoting the min_order_amount field in the database. @@ -77,9 +78,10 @@ var Columns = []string{ FieldID, FieldCreatedAt, FieldUpdatedAt, - FieldFixedConversionRate, - FieldFloatingConversionRate, - FieldConversionRateType, + FieldFixedBuyRate, + FieldFixedSellRate, + FieldFloatingBuyDelta, + FieldFloatingSellDelta, FieldMaxOrderAmount, FieldMinOrderAmount, FieldMaxOrderAmountOtc, @@ -122,29 +124,6 @@ var ( UpdateDefaultUpdatedAt func() time.Time ) -// ConversionRateType defines the type for the "conversion_rate_type" enum field. -type ConversionRateType string - -// ConversionRateType values. -const ( - ConversionRateTypeFixed ConversionRateType = "fixed" - ConversionRateTypeFloating ConversionRateType = "floating" -) - -func (crt ConversionRateType) String() string { - return string(crt) -} - -// ConversionRateTypeValidator is a validator for the "conversion_rate_type" field enum values. It is called by the builders before save. -func ConversionRateTypeValidator(crt ConversionRateType) error { - switch crt { - case ConversionRateTypeFixed, ConversionRateTypeFloating: - return nil - default: - return fmt.Errorf("providerordertoken: invalid enum value for conversion_rate_type field: %q", crt) - } -} - // OrderOption defines the ordering options for the ProviderOrderToken queries. type OrderOption func(*sql.Selector) @@ -163,19 +142,24 @@ func ByUpdatedAt(opts ...sql.OrderTermOption) OrderOption { return sql.OrderByField(FieldUpdatedAt, opts...).ToFunc() } -// ByFixedConversionRate orders the results by the fixed_conversion_rate field. -func ByFixedConversionRate(opts ...sql.OrderTermOption) OrderOption { - return sql.OrderByField(FieldFixedConversionRate, opts...).ToFunc() +// ByFixedBuyRate orders the results by the fixed_buy_rate field. +func ByFixedBuyRate(opts ...sql.OrderTermOption) OrderOption { + return sql.OrderByField(FieldFixedBuyRate, opts...).ToFunc() +} + +// ByFixedSellRate orders the results by the fixed_sell_rate field. +func ByFixedSellRate(opts ...sql.OrderTermOption) OrderOption { + return sql.OrderByField(FieldFixedSellRate, opts...).ToFunc() } -// ByFloatingConversionRate orders the results by the floating_conversion_rate field. -func ByFloatingConversionRate(opts ...sql.OrderTermOption) OrderOption { - return sql.OrderByField(FieldFloatingConversionRate, opts...).ToFunc() +// ByFloatingBuyDelta orders the results by the floating_buy_delta field. +func ByFloatingBuyDelta(opts ...sql.OrderTermOption) OrderOption { + return sql.OrderByField(FieldFloatingBuyDelta, opts...).ToFunc() } -// ByConversionRateType orders the results by the conversion_rate_type field. -func ByConversionRateType(opts ...sql.OrderTermOption) OrderOption { - return sql.OrderByField(FieldConversionRateType, opts...).ToFunc() +// ByFloatingSellDelta orders the results by the floating_sell_delta field. +func ByFloatingSellDelta(opts ...sql.OrderTermOption) OrderOption { + return sql.OrderByField(FieldFloatingSellDelta, opts...).ToFunc() } // ByMaxOrderAmount orders the results by the max_order_amount field. diff --git a/ent/providerordertoken/where.go b/ent/providerordertoken/where.go index 33ed99b06..d573c95d0 100644 --- a/ent/providerordertoken/where.go +++ b/ent/providerordertoken/where.go @@ -66,14 +66,24 @@ func UpdatedAt(v time.Time) predicate.ProviderOrderToken { return predicate.ProviderOrderToken(sql.FieldEQ(FieldUpdatedAt, v)) } -// FixedConversionRate applies equality check predicate on the "fixed_conversion_rate" field. It's identical to FixedConversionRateEQ. -func FixedConversionRate(v decimal.Decimal) predicate.ProviderOrderToken { - return predicate.ProviderOrderToken(sql.FieldEQ(FieldFixedConversionRate, v)) +// FixedBuyRate applies equality check predicate on the "fixed_buy_rate" field. It's identical to FixedBuyRateEQ. +func FixedBuyRate(v decimal.Decimal) predicate.ProviderOrderToken { + return predicate.ProviderOrderToken(sql.FieldEQ(FieldFixedBuyRate, v)) } -// FloatingConversionRate applies equality check predicate on the "floating_conversion_rate" field. It's identical to FloatingConversionRateEQ. -func FloatingConversionRate(v decimal.Decimal) predicate.ProviderOrderToken { - return predicate.ProviderOrderToken(sql.FieldEQ(FieldFloatingConversionRate, v)) +// FixedSellRate applies equality check predicate on the "fixed_sell_rate" field. It's identical to FixedSellRateEQ. +func FixedSellRate(v decimal.Decimal) predicate.ProviderOrderToken { + return predicate.ProviderOrderToken(sql.FieldEQ(FieldFixedSellRate, v)) +} + +// FloatingBuyDelta applies equality check predicate on the "floating_buy_delta" field. It's identical to FloatingBuyDeltaEQ. +func FloatingBuyDelta(v decimal.Decimal) predicate.ProviderOrderToken { + return predicate.ProviderOrderToken(sql.FieldEQ(FieldFloatingBuyDelta, v)) +} + +// FloatingSellDelta applies equality check predicate on the "floating_sell_delta" field. It's identical to FloatingSellDeltaEQ. +func FloatingSellDelta(v decimal.Decimal) predicate.ProviderOrderToken { + return predicate.ProviderOrderToken(sql.FieldEQ(FieldFloatingSellDelta, v)) } // MaxOrderAmount applies equality check predicate on the "max_order_amount" field. It's identical to MaxOrderAmountEQ. @@ -196,104 +206,204 @@ func UpdatedAtLTE(v time.Time) predicate.ProviderOrderToken { return predicate.ProviderOrderToken(sql.FieldLTE(FieldUpdatedAt, v)) } -// FixedConversionRateEQ applies the EQ predicate on the "fixed_conversion_rate" field. -func FixedConversionRateEQ(v decimal.Decimal) predicate.ProviderOrderToken { - return predicate.ProviderOrderToken(sql.FieldEQ(FieldFixedConversionRate, v)) +// FixedBuyRateEQ applies the EQ predicate on the "fixed_buy_rate" field. +func FixedBuyRateEQ(v decimal.Decimal) predicate.ProviderOrderToken { + return predicate.ProviderOrderToken(sql.FieldEQ(FieldFixedBuyRate, v)) +} + +// FixedBuyRateNEQ applies the NEQ predicate on the "fixed_buy_rate" field. +func FixedBuyRateNEQ(v decimal.Decimal) predicate.ProviderOrderToken { + return predicate.ProviderOrderToken(sql.FieldNEQ(FieldFixedBuyRate, v)) +} + +// FixedBuyRateIn applies the In predicate on the "fixed_buy_rate" field. +func FixedBuyRateIn(vs ...decimal.Decimal) predicate.ProviderOrderToken { + return predicate.ProviderOrderToken(sql.FieldIn(FieldFixedBuyRate, vs...)) +} + +// FixedBuyRateNotIn applies the NotIn predicate on the "fixed_buy_rate" field. +func FixedBuyRateNotIn(vs ...decimal.Decimal) predicate.ProviderOrderToken { + return predicate.ProviderOrderToken(sql.FieldNotIn(FieldFixedBuyRate, vs...)) +} + +// FixedBuyRateGT applies the GT predicate on the "fixed_buy_rate" field. +func FixedBuyRateGT(v decimal.Decimal) predicate.ProviderOrderToken { + return predicate.ProviderOrderToken(sql.FieldGT(FieldFixedBuyRate, v)) +} + +// FixedBuyRateGTE applies the GTE predicate on the "fixed_buy_rate" field. +func FixedBuyRateGTE(v decimal.Decimal) predicate.ProviderOrderToken { + return predicate.ProviderOrderToken(sql.FieldGTE(FieldFixedBuyRate, v)) +} + +// FixedBuyRateLT applies the LT predicate on the "fixed_buy_rate" field. +func FixedBuyRateLT(v decimal.Decimal) predicate.ProviderOrderToken { + return predicate.ProviderOrderToken(sql.FieldLT(FieldFixedBuyRate, v)) +} + +// FixedBuyRateLTE applies the LTE predicate on the "fixed_buy_rate" field. +func FixedBuyRateLTE(v decimal.Decimal) predicate.ProviderOrderToken { + return predicate.ProviderOrderToken(sql.FieldLTE(FieldFixedBuyRate, v)) +} + +// FixedBuyRateIsNil applies the IsNil predicate on the "fixed_buy_rate" field. +func FixedBuyRateIsNil() predicate.ProviderOrderToken { + return predicate.ProviderOrderToken(sql.FieldIsNull(FieldFixedBuyRate)) +} + +// FixedBuyRateNotNil applies the NotNil predicate on the "fixed_buy_rate" field. +func FixedBuyRateNotNil() predicate.ProviderOrderToken { + return predicate.ProviderOrderToken(sql.FieldNotNull(FieldFixedBuyRate)) +} + +// FixedSellRateEQ applies the EQ predicate on the "fixed_sell_rate" field. +func FixedSellRateEQ(v decimal.Decimal) predicate.ProviderOrderToken { + return predicate.ProviderOrderToken(sql.FieldEQ(FieldFixedSellRate, v)) +} + +// FixedSellRateNEQ applies the NEQ predicate on the "fixed_sell_rate" field. +func FixedSellRateNEQ(v decimal.Decimal) predicate.ProviderOrderToken { + return predicate.ProviderOrderToken(sql.FieldNEQ(FieldFixedSellRate, v)) +} + +// FixedSellRateIn applies the In predicate on the "fixed_sell_rate" field. +func FixedSellRateIn(vs ...decimal.Decimal) predicate.ProviderOrderToken { + return predicate.ProviderOrderToken(sql.FieldIn(FieldFixedSellRate, vs...)) +} + +// FixedSellRateNotIn applies the NotIn predicate on the "fixed_sell_rate" field. +func FixedSellRateNotIn(vs ...decimal.Decimal) predicate.ProviderOrderToken { + return predicate.ProviderOrderToken(sql.FieldNotIn(FieldFixedSellRate, vs...)) +} + +// FixedSellRateGT applies the GT predicate on the "fixed_sell_rate" field. +func FixedSellRateGT(v decimal.Decimal) predicate.ProviderOrderToken { + return predicate.ProviderOrderToken(sql.FieldGT(FieldFixedSellRate, v)) +} + +// FixedSellRateGTE applies the GTE predicate on the "fixed_sell_rate" field. +func FixedSellRateGTE(v decimal.Decimal) predicate.ProviderOrderToken { + return predicate.ProviderOrderToken(sql.FieldGTE(FieldFixedSellRate, v)) +} + +// FixedSellRateLT applies the LT predicate on the "fixed_sell_rate" field. +func FixedSellRateLT(v decimal.Decimal) predicate.ProviderOrderToken { + return predicate.ProviderOrderToken(sql.FieldLT(FieldFixedSellRate, v)) +} + +// FixedSellRateLTE applies the LTE predicate on the "fixed_sell_rate" field. +func FixedSellRateLTE(v decimal.Decimal) predicate.ProviderOrderToken { + return predicate.ProviderOrderToken(sql.FieldLTE(FieldFixedSellRate, v)) +} + +// FixedSellRateIsNil applies the IsNil predicate on the "fixed_sell_rate" field. +func FixedSellRateIsNil() predicate.ProviderOrderToken { + return predicate.ProviderOrderToken(sql.FieldIsNull(FieldFixedSellRate)) +} + +// FixedSellRateNotNil applies the NotNil predicate on the "fixed_sell_rate" field. +func FixedSellRateNotNil() predicate.ProviderOrderToken { + return predicate.ProviderOrderToken(sql.FieldNotNull(FieldFixedSellRate)) +} + +// FloatingBuyDeltaEQ applies the EQ predicate on the "floating_buy_delta" field. +func FloatingBuyDeltaEQ(v decimal.Decimal) predicate.ProviderOrderToken { + return predicate.ProviderOrderToken(sql.FieldEQ(FieldFloatingBuyDelta, v)) } -// FixedConversionRateNEQ applies the NEQ predicate on the "fixed_conversion_rate" field. -func FixedConversionRateNEQ(v decimal.Decimal) predicate.ProviderOrderToken { - return predicate.ProviderOrderToken(sql.FieldNEQ(FieldFixedConversionRate, v)) +// FloatingBuyDeltaNEQ applies the NEQ predicate on the "floating_buy_delta" field. +func FloatingBuyDeltaNEQ(v decimal.Decimal) predicate.ProviderOrderToken { + return predicate.ProviderOrderToken(sql.FieldNEQ(FieldFloatingBuyDelta, v)) } -// FixedConversionRateIn applies the In predicate on the "fixed_conversion_rate" field. -func FixedConversionRateIn(vs ...decimal.Decimal) predicate.ProviderOrderToken { - return predicate.ProviderOrderToken(sql.FieldIn(FieldFixedConversionRate, vs...)) +// FloatingBuyDeltaIn applies the In predicate on the "floating_buy_delta" field. +func FloatingBuyDeltaIn(vs ...decimal.Decimal) predicate.ProviderOrderToken { + return predicate.ProviderOrderToken(sql.FieldIn(FieldFloatingBuyDelta, vs...)) } -// FixedConversionRateNotIn applies the NotIn predicate on the "fixed_conversion_rate" field. -func FixedConversionRateNotIn(vs ...decimal.Decimal) predicate.ProviderOrderToken { - return predicate.ProviderOrderToken(sql.FieldNotIn(FieldFixedConversionRate, vs...)) +// FloatingBuyDeltaNotIn applies the NotIn predicate on the "floating_buy_delta" field. +func FloatingBuyDeltaNotIn(vs ...decimal.Decimal) predicate.ProviderOrderToken { + return predicate.ProviderOrderToken(sql.FieldNotIn(FieldFloatingBuyDelta, vs...)) } -// FixedConversionRateGT applies the GT predicate on the "fixed_conversion_rate" field. -func FixedConversionRateGT(v decimal.Decimal) predicate.ProviderOrderToken { - return predicate.ProviderOrderToken(sql.FieldGT(FieldFixedConversionRate, v)) +// FloatingBuyDeltaGT applies the GT predicate on the "floating_buy_delta" field. +func FloatingBuyDeltaGT(v decimal.Decimal) predicate.ProviderOrderToken { + return predicate.ProviderOrderToken(sql.FieldGT(FieldFloatingBuyDelta, v)) } -// FixedConversionRateGTE applies the GTE predicate on the "fixed_conversion_rate" field. -func FixedConversionRateGTE(v decimal.Decimal) predicate.ProviderOrderToken { - return predicate.ProviderOrderToken(sql.FieldGTE(FieldFixedConversionRate, v)) +// FloatingBuyDeltaGTE applies the GTE predicate on the "floating_buy_delta" field. +func FloatingBuyDeltaGTE(v decimal.Decimal) predicate.ProviderOrderToken { + return predicate.ProviderOrderToken(sql.FieldGTE(FieldFloatingBuyDelta, v)) } -// FixedConversionRateLT applies the LT predicate on the "fixed_conversion_rate" field. -func FixedConversionRateLT(v decimal.Decimal) predicate.ProviderOrderToken { - return predicate.ProviderOrderToken(sql.FieldLT(FieldFixedConversionRate, v)) +// FloatingBuyDeltaLT applies the LT predicate on the "floating_buy_delta" field. +func FloatingBuyDeltaLT(v decimal.Decimal) predicate.ProviderOrderToken { + return predicate.ProviderOrderToken(sql.FieldLT(FieldFloatingBuyDelta, v)) } -// FixedConversionRateLTE applies the LTE predicate on the "fixed_conversion_rate" field. -func FixedConversionRateLTE(v decimal.Decimal) predicate.ProviderOrderToken { - return predicate.ProviderOrderToken(sql.FieldLTE(FieldFixedConversionRate, v)) +// FloatingBuyDeltaLTE applies the LTE predicate on the "floating_buy_delta" field. +func FloatingBuyDeltaLTE(v decimal.Decimal) predicate.ProviderOrderToken { + return predicate.ProviderOrderToken(sql.FieldLTE(FieldFloatingBuyDelta, v)) } -// FloatingConversionRateEQ applies the EQ predicate on the "floating_conversion_rate" field. -func FloatingConversionRateEQ(v decimal.Decimal) predicate.ProviderOrderToken { - return predicate.ProviderOrderToken(sql.FieldEQ(FieldFloatingConversionRate, v)) +// FloatingBuyDeltaIsNil applies the IsNil predicate on the "floating_buy_delta" field. +func FloatingBuyDeltaIsNil() predicate.ProviderOrderToken { + return predicate.ProviderOrderToken(sql.FieldIsNull(FieldFloatingBuyDelta)) } -// FloatingConversionRateNEQ applies the NEQ predicate on the "floating_conversion_rate" field. -func FloatingConversionRateNEQ(v decimal.Decimal) predicate.ProviderOrderToken { - return predicate.ProviderOrderToken(sql.FieldNEQ(FieldFloatingConversionRate, v)) +// FloatingBuyDeltaNotNil applies the NotNil predicate on the "floating_buy_delta" field. +func FloatingBuyDeltaNotNil() predicate.ProviderOrderToken { + return predicate.ProviderOrderToken(sql.FieldNotNull(FieldFloatingBuyDelta)) } -// FloatingConversionRateIn applies the In predicate on the "floating_conversion_rate" field. -func FloatingConversionRateIn(vs ...decimal.Decimal) predicate.ProviderOrderToken { - return predicate.ProviderOrderToken(sql.FieldIn(FieldFloatingConversionRate, vs...)) +// FloatingSellDeltaEQ applies the EQ predicate on the "floating_sell_delta" field. +func FloatingSellDeltaEQ(v decimal.Decimal) predicate.ProviderOrderToken { + return predicate.ProviderOrderToken(sql.FieldEQ(FieldFloatingSellDelta, v)) } -// FloatingConversionRateNotIn applies the NotIn predicate on the "floating_conversion_rate" field. -func FloatingConversionRateNotIn(vs ...decimal.Decimal) predicate.ProviderOrderToken { - return predicate.ProviderOrderToken(sql.FieldNotIn(FieldFloatingConversionRate, vs...)) +// FloatingSellDeltaNEQ applies the NEQ predicate on the "floating_sell_delta" field. +func FloatingSellDeltaNEQ(v decimal.Decimal) predicate.ProviderOrderToken { + return predicate.ProviderOrderToken(sql.FieldNEQ(FieldFloatingSellDelta, v)) } -// FloatingConversionRateGT applies the GT predicate on the "floating_conversion_rate" field. -func FloatingConversionRateGT(v decimal.Decimal) predicate.ProviderOrderToken { - return predicate.ProviderOrderToken(sql.FieldGT(FieldFloatingConversionRate, v)) +// FloatingSellDeltaIn applies the In predicate on the "floating_sell_delta" field. +func FloatingSellDeltaIn(vs ...decimal.Decimal) predicate.ProviderOrderToken { + return predicate.ProviderOrderToken(sql.FieldIn(FieldFloatingSellDelta, vs...)) } -// FloatingConversionRateGTE applies the GTE predicate on the "floating_conversion_rate" field. -func FloatingConversionRateGTE(v decimal.Decimal) predicate.ProviderOrderToken { - return predicate.ProviderOrderToken(sql.FieldGTE(FieldFloatingConversionRate, v)) +// FloatingSellDeltaNotIn applies the NotIn predicate on the "floating_sell_delta" field. +func FloatingSellDeltaNotIn(vs ...decimal.Decimal) predicate.ProviderOrderToken { + return predicate.ProviderOrderToken(sql.FieldNotIn(FieldFloatingSellDelta, vs...)) } -// FloatingConversionRateLT applies the LT predicate on the "floating_conversion_rate" field. -func FloatingConversionRateLT(v decimal.Decimal) predicate.ProviderOrderToken { - return predicate.ProviderOrderToken(sql.FieldLT(FieldFloatingConversionRate, v)) +// FloatingSellDeltaGT applies the GT predicate on the "floating_sell_delta" field. +func FloatingSellDeltaGT(v decimal.Decimal) predicate.ProviderOrderToken { + return predicate.ProviderOrderToken(sql.FieldGT(FieldFloatingSellDelta, v)) } -// FloatingConversionRateLTE applies the LTE predicate on the "floating_conversion_rate" field. -func FloatingConversionRateLTE(v decimal.Decimal) predicate.ProviderOrderToken { - return predicate.ProviderOrderToken(sql.FieldLTE(FieldFloatingConversionRate, v)) +// FloatingSellDeltaGTE applies the GTE predicate on the "floating_sell_delta" field. +func FloatingSellDeltaGTE(v decimal.Decimal) predicate.ProviderOrderToken { + return predicate.ProviderOrderToken(sql.FieldGTE(FieldFloatingSellDelta, v)) } -// ConversionRateTypeEQ applies the EQ predicate on the "conversion_rate_type" field. -func ConversionRateTypeEQ(v ConversionRateType) predicate.ProviderOrderToken { - return predicate.ProviderOrderToken(sql.FieldEQ(FieldConversionRateType, v)) +// FloatingSellDeltaLT applies the LT predicate on the "floating_sell_delta" field. +func FloatingSellDeltaLT(v decimal.Decimal) predicate.ProviderOrderToken { + return predicate.ProviderOrderToken(sql.FieldLT(FieldFloatingSellDelta, v)) } -// ConversionRateTypeNEQ applies the NEQ predicate on the "conversion_rate_type" field. -func ConversionRateTypeNEQ(v ConversionRateType) predicate.ProviderOrderToken { - return predicate.ProviderOrderToken(sql.FieldNEQ(FieldConversionRateType, v)) +// FloatingSellDeltaLTE applies the LTE predicate on the "floating_sell_delta" field. +func FloatingSellDeltaLTE(v decimal.Decimal) predicate.ProviderOrderToken { + return predicate.ProviderOrderToken(sql.FieldLTE(FieldFloatingSellDelta, v)) } -// ConversionRateTypeIn applies the In predicate on the "conversion_rate_type" field. -func ConversionRateTypeIn(vs ...ConversionRateType) predicate.ProviderOrderToken { - return predicate.ProviderOrderToken(sql.FieldIn(FieldConversionRateType, vs...)) +// FloatingSellDeltaIsNil applies the IsNil predicate on the "floating_sell_delta" field. +func FloatingSellDeltaIsNil() predicate.ProviderOrderToken { + return predicate.ProviderOrderToken(sql.FieldIsNull(FieldFloatingSellDelta)) } -// ConversionRateTypeNotIn applies the NotIn predicate on the "conversion_rate_type" field. -func ConversionRateTypeNotIn(vs ...ConversionRateType) predicate.ProviderOrderToken { - return predicate.ProviderOrderToken(sql.FieldNotIn(FieldConversionRateType, vs...)) +// FloatingSellDeltaNotNil applies the NotNil predicate on the "floating_sell_delta" field. +func FloatingSellDeltaNotNil() predicate.ProviderOrderToken { + return predicate.ProviderOrderToken(sql.FieldNotNull(FieldFloatingSellDelta)) } // MaxOrderAmountEQ applies the EQ predicate on the "max_order_amount" field. diff --git a/ent/providerordertoken_create.go b/ent/providerordertoken_create.go index baee2202a..6e5348b0c 100644 --- a/ent/providerordertoken_create.go +++ b/ent/providerordertoken_create.go @@ -55,21 +55,59 @@ func (_c *ProviderOrderTokenCreate) SetNillableUpdatedAt(v *time.Time) *Provider return _c } -// SetFixedConversionRate sets the "fixed_conversion_rate" field. -func (_c *ProviderOrderTokenCreate) SetFixedConversionRate(v decimal.Decimal) *ProviderOrderTokenCreate { - _c.mutation.SetFixedConversionRate(v) +// SetFixedBuyRate sets the "fixed_buy_rate" field. +func (_c *ProviderOrderTokenCreate) SetFixedBuyRate(v decimal.Decimal) *ProviderOrderTokenCreate { + _c.mutation.SetFixedBuyRate(v) return _c } -// SetFloatingConversionRate sets the "floating_conversion_rate" field. -func (_c *ProviderOrderTokenCreate) SetFloatingConversionRate(v decimal.Decimal) *ProviderOrderTokenCreate { - _c.mutation.SetFloatingConversionRate(v) +// SetNillableFixedBuyRate sets the "fixed_buy_rate" field if the given value is not nil. +func (_c *ProviderOrderTokenCreate) SetNillableFixedBuyRate(v *decimal.Decimal) *ProviderOrderTokenCreate { + if v != nil { + _c.SetFixedBuyRate(*v) + } + return _c +} + +// SetFixedSellRate sets the "fixed_sell_rate" field. +func (_c *ProviderOrderTokenCreate) SetFixedSellRate(v decimal.Decimal) *ProviderOrderTokenCreate { + _c.mutation.SetFixedSellRate(v) + return _c +} + +// SetNillableFixedSellRate sets the "fixed_sell_rate" field if the given value is not nil. +func (_c *ProviderOrderTokenCreate) SetNillableFixedSellRate(v *decimal.Decimal) *ProviderOrderTokenCreate { + if v != nil { + _c.SetFixedSellRate(*v) + } + return _c +} + +// SetFloatingBuyDelta sets the "floating_buy_delta" field. +func (_c *ProviderOrderTokenCreate) SetFloatingBuyDelta(v decimal.Decimal) *ProviderOrderTokenCreate { + _c.mutation.SetFloatingBuyDelta(v) + return _c +} + +// SetNillableFloatingBuyDelta sets the "floating_buy_delta" field if the given value is not nil. +func (_c *ProviderOrderTokenCreate) SetNillableFloatingBuyDelta(v *decimal.Decimal) *ProviderOrderTokenCreate { + if v != nil { + _c.SetFloatingBuyDelta(*v) + } + return _c +} + +// SetFloatingSellDelta sets the "floating_sell_delta" field. +func (_c *ProviderOrderTokenCreate) SetFloatingSellDelta(v decimal.Decimal) *ProviderOrderTokenCreate { + _c.mutation.SetFloatingSellDelta(v) return _c } -// SetConversionRateType sets the "conversion_rate_type" field. -func (_c *ProviderOrderTokenCreate) SetConversionRateType(v providerordertoken.ConversionRateType) *ProviderOrderTokenCreate { - _c.mutation.SetConversionRateType(v) +// SetNillableFloatingSellDelta sets the "floating_sell_delta" field if the given value is not nil. +func (_c *ProviderOrderTokenCreate) SetNillableFloatingSellDelta(v *decimal.Decimal) *ProviderOrderTokenCreate { + if v != nil { + _c.SetFloatingSellDelta(*v) + } return _c } @@ -223,20 +261,6 @@ func (_c *ProviderOrderTokenCreate) check() error { if _, ok := _c.mutation.UpdatedAt(); !ok { return &ValidationError{Name: "updated_at", err: errors.New(`ent: missing required field "ProviderOrderToken.updated_at"`)} } - if _, ok := _c.mutation.FixedConversionRate(); !ok { - return &ValidationError{Name: "fixed_conversion_rate", err: errors.New(`ent: missing required field "ProviderOrderToken.fixed_conversion_rate"`)} - } - if _, ok := _c.mutation.FloatingConversionRate(); !ok { - return &ValidationError{Name: "floating_conversion_rate", err: errors.New(`ent: missing required field "ProviderOrderToken.floating_conversion_rate"`)} - } - if _, ok := _c.mutation.ConversionRateType(); !ok { - return &ValidationError{Name: "conversion_rate_type", err: errors.New(`ent: missing required field "ProviderOrderToken.conversion_rate_type"`)} - } - if v, ok := _c.mutation.ConversionRateType(); ok { - if err := providerordertoken.ConversionRateTypeValidator(v); err != nil { - return &ValidationError{Name: "conversion_rate_type", err: fmt.Errorf(`ent: validator failed for field "ProviderOrderToken.conversion_rate_type": %w`, err)} - } - } if _, ok := _c.mutation.MaxOrderAmount(); !ok { return &ValidationError{Name: "max_order_amount", err: errors.New(`ent: missing required field "ProviderOrderToken.max_order_amount"`)} } @@ -299,17 +323,21 @@ func (_c *ProviderOrderTokenCreate) createSpec() (*ProviderOrderToken, *sqlgraph _spec.SetField(providerordertoken.FieldUpdatedAt, field.TypeTime, value) _node.UpdatedAt = value } - if value, ok := _c.mutation.FixedConversionRate(); ok { - _spec.SetField(providerordertoken.FieldFixedConversionRate, field.TypeFloat64, value) - _node.FixedConversionRate = value + if value, ok := _c.mutation.FixedBuyRate(); ok { + _spec.SetField(providerordertoken.FieldFixedBuyRate, field.TypeFloat64, value) + _node.FixedBuyRate = value + } + if value, ok := _c.mutation.FixedSellRate(); ok { + _spec.SetField(providerordertoken.FieldFixedSellRate, field.TypeFloat64, value) + _node.FixedSellRate = value } - if value, ok := _c.mutation.FloatingConversionRate(); ok { - _spec.SetField(providerordertoken.FieldFloatingConversionRate, field.TypeFloat64, value) - _node.FloatingConversionRate = value + if value, ok := _c.mutation.FloatingBuyDelta(); ok { + _spec.SetField(providerordertoken.FieldFloatingBuyDelta, field.TypeFloat64, value) + _node.FloatingBuyDelta = value } - if value, ok := _c.mutation.ConversionRateType(); ok { - _spec.SetField(providerordertoken.FieldConversionRateType, field.TypeEnum, value) - _node.ConversionRateType = value + if value, ok := _c.mutation.FloatingSellDelta(); ok { + _spec.SetField(providerordertoken.FieldFloatingSellDelta, field.TypeFloat64, value) + _node.FloatingSellDelta = value } if value, ok := _c.mutation.MaxOrderAmount(); ok { _spec.SetField(providerordertoken.FieldMaxOrderAmount, field.TypeFloat64, value) @@ -458,51 +486,99 @@ func (u *ProviderOrderTokenUpsert) UpdateUpdatedAt() *ProviderOrderTokenUpsert { return u } -// SetFixedConversionRate sets the "fixed_conversion_rate" field. -func (u *ProviderOrderTokenUpsert) SetFixedConversionRate(v decimal.Decimal) *ProviderOrderTokenUpsert { - u.Set(providerordertoken.FieldFixedConversionRate, v) +// SetFixedBuyRate sets the "fixed_buy_rate" field. +func (u *ProviderOrderTokenUpsert) SetFixedBuyRate(v decimal.Decimal) *ProviderOrderTokenUpsert { + u.Set(providerordertoken.FieldFixedBuyRate, v) return u } -// UpdateFixedConversionRate sets the "fixed_conversion_rate" field to the value that was provided on create. -func (u *ProviderOrderTokenUpsert) UpdateFixedConversionRate() *ProviderOrderTokenUpsert { - u.SetExcluded(providerordertoken.FieldFixedConversionRate) +// UpdateFixedBuyRate sets the "fixed_buy_rate" field to the value that was provided on create. +func (u *ProviderOrderTokenUpsert) UpdateFixedBuyRate() *ProviderOrderTokenUpsert { + u.SetExcluded(providerordertoken.FieldFixedBuyRate) return u } -// AddFixedConversionRate adds v to the "fixed_conversion_rate" field. -func (u *ProviderOrderTokenUpsert) AddFixedConversionRate(v decimal.Decimal) *ProviderOrderTokenUpsert { - u.Add(providerordertoken.FieldFixedConversionRate, v) +// AddFixedBuyRate adds v to the "fixed_buy_rate" field. +func (u *ProviderOrderTokenUpsert) AddFixedBuyRate(v decimal.Decimal) *ProviderOrderTokenUpsert { + u.Add(providerordertoken.FieldFixedBuyRate, v) return u } -// SetFloatingConversionRate sets the "floating_conversion_rate" field. -func (u *ProviderOrderTokenUpsert) SetFloatingConversionRate(v decimal.Decimal) *ProviderOrderTokenUpsert { - u.Set(providerordertoken.FieldFloatingConversionRate, v) +// ClearFixedBuyRate clears the value of the "fixed_buy_rate" field. +func (u *ProviderOrderTokenUpsert) ClearFixedBuyRate() *ProviderOrderTokenUpsert { + u.SetNull(providerordertoken.FieldFixedBuyRate) return u } -// UpdateFloatingConversionRate sets the "floating_conversion_rate" field to the value that was provided on create. -func (u *ProviderOrderTokenUpsert) UpdateFloatingConversionRate() *ProviderOrderTokenUpsert { - u.SetExcluded(providerordertoken.FieldFloatingConversionRate) +// SetFixedSellRate sets the "fixed_sell_rate" field. +func (u *ProviderOrderTokenUpsert) SetFixedSellRate(v decimal.Decimal) *ProviderOrderTokenUpsert { + u.Set(providerordertoken.FieldFixedSellRate, v) return u } -// AddFloatingConversionRate adds v to the "floating_conversion_rate" field. -func (u *ProviderOrderTokenUpsert) AddFloatingConversionRate(v decimal.Decimal) *ProviderOrderTokenUpsert { - u.Add(providerordertoken.FieldFloatingConversionRate, v) +// UpdateFixedSellRate sets the "fixed_sell_rate" field to the value that was provided on create. +func (u *ProviderOrderTokenUpsert) UpdateFixedSellRate() *ProviderOrderTokenUpsert { + u.SetExcluded(providerordertoken.FieldFixedSellRate) return u } -// SetConversionRateType sets the "conversion_rate_type" field. -func (u *ProviderOrderTokenUpsert) SetConversionRateType(v providerordertoken.ConversionRateType) *ProviderOrderTokenUpsert { - u.Set(providerordertoken.FieldConversionRateType, v) +// AddFixedSellRate adds v to the "fixed_sell_rate" field. +func (u *ProviderOrderTokenUpsert) AddFixedSellRate(v decimal.Decimal) *ProviderOrderTokenUpsert { + u.Add(providerordertoken.FieldFixedSellRate, v) return u } -// UpdateConversionRateType sets the "conversion_rate_type" field to the value that was provided on create. -func (u *ProviderOrderTokenUpsert) UpdateConversionRateType() *ProviderOrderTokenUpsert { - u.SetExcluded(providerordertoken.FieldConversionRateType) +// ClearFixedSellRate clears the value of the "fixed_sell_rate" field. +func (u *ProviderOrderTokenUpsert) ClearFixedSellRate() *ProviderOrderTokenUpsert { + u.SetNull(providerordertoken.FieldFixedSellRate) + return u +} + +// SetFloatingBuyDelta sets the "floating_buy_delta" field. +func (u *ProviderOrderTokenUpsert) SetFloatingBuyDelta(v decimal.Decimal) *ProviderOrderTokenUpsert { + u.Set(providerordertoken.FieldFloatingBuyDelta, v) + return u +} + +// UpdateFloatingBuyDelta sets the "floating_buy_delta" field to the value that was provided on create. +func (u *ProviderOrderTokenUpsert) UpdateFloatingBuyDelta() *ProviderOrderTokenUpsert { + u.SetExcluded(providerordertoken.FieldFloatingBuyDelta) + return u +} + +// AddFloatingBuyDelta adds v to the "floating_buy_delta" field. +func (u *ProviderOrderTokenUpsert) AddFloatingBuyDelta(v decimal.Decimal) *ProviderOrderTokenUpsert { + u.Add(providerordertoken.FieldFloatingBuyDelta, v) + return u +} + +// ClearFloatingBuyDelta clears the value of the "floating_buy_delta" field. +func (u *ProviderOrderTokenUpsert) ClearFloatingBuyDelta() *ProviderOrderTokenUpsert { + u.SetNull(providerordertoken.FieldFloatingBuyDelta) + return u +} + +// SetFloatingSellDelta sets the "floating_sell_delta" field. +func (u *ProviderOrderTokenUpsert) SetFloatingSellDelta(v decimal.Decimal) *ProviderOrderTokenUpsert { + u.Set(providerordertoken.FieldFloatingSellDelta, v) + return u +} + +// UpdateFloatingSellDelta sets the "floating_sell_delta" field to the value that was provided on create. +func (u *ProviderOrderTokenUpsert) UpdateFloatingSellDelta() *ProviderOrderTokenUpsert { + u.SetExcluded(providerordertoken.FieldFloatingSellDelta) + return u +} + +// AddFloatingSellDelta adds v to the "floating_sell_delta" field. +func (u *ProviderOrderTokenUpsert) AddFloatingSellDelta(v decimal.Decimal) *ProviderOrderTokenUpsert { + u.Add(providerordertoken.FieldFloatingSellDelta, v) + return u +} + +// ClearFloatingSellDelta clears the value of the "floating_sell_delta" field. +func (u *ProviderOrderTokenUpsert) ClearFloatingSellDelta() *ProviderOrderTokenUpsert { + u.SetNull(providerordertoken.FieldFloatingSellDelta) return u } @@ -703,59 +779,115 @@ func (u *ProviderOrderTokenUpsertOne) UpdateUpdatedAt() *ProviderOrderTokenUpser }) } -// SetFixedConversionRate sets the "fixed_conversion_rate" field. -func (u *ProviderOrderTokenUpsertOne) SetFixedConversionRate(v decimal.Decimal) *ProviderOrderTokenUpsertOne { +// SetFixedBuyRate sets the "fixed_buy_rate" field. +func (u *ProviderOrderTokenUpsertOne) SetFixedBuyRate(v decimal.Decimal) *ProviderOrderTokenUpsertOne { return u.Update(func(s *ProviderOrderTokenUpsert) { - s.SetFixedConversionRate(v) + s.SetFixedBuyRate(v) }) } -// AddFixedConversionRate adds v to the "fixed_conversion_rate" field. -func (u *ProviderOrderTokenUpsertOne) AddFixedConversionRate(v decimal.Decimal) *ProviderOrderTokenUpsertOne { +// AddFixedBuyRate adds v to the "fixed_buy_rate" field. +func (u *ProviderOrderTokenUpsertOne) AddFixedBuyRate(v decimal.Decimal) *ProviderOrderTokenUpsertOne { return u.Update(func(s *ProviderOrderTokenUpsert) { - s.AddFixedConversionRate(v) + s.AddFixedBuyRate(v) }) } -// UpdateFixedConversionRate sets the "fixed_conversion_rate" field to the value that was provided on create. -func (u *ProviderOrderTokenUpsertOne) UpdateFixedConversionRate() *ProviderOrderTokenUpsertOne { +// UpdateFixedBuyRate sets the "fixed_buy_rate" field to the value that was provided on create. +func (u *ProviderOrderTokenUpsertOne) UpdateFixedBuyRate() *ProviderOrderTokenUpsertOne { return u.Update(func(s *ProviderOrderTokenUpsert) { - s.UpdateFixedConversionRate() + s.UpdateFixedBuyRate() }) } -// SetFloatingConversionRate sets the "floating_conversion_rate" field. -func (u *ProviderOrderTokenUpsertOne) SetFloatingConversionRate(v decimal.Decimal) *ProviderOrderTokenUpsertOne { +// ClearFixedBuyRate clears the value of the "fixed_buy_rate" field. +func (u *ProviderOrderTokenUpsertOne) ClearFixedBuyRate() *ProviderOrderTokenUpsertOne { return u.Update(func(s *ProviderOrderTokenUpsert) { - s.SetFloatingConversionRate(v) + s.ClearFixedBuyRate() }) } -// AddFloatingConversionRate adds v to the "floating_conversion_rate" field. -func (u *ProviderOrderTokenUpsertOne) AddFloatingConversionRate(v decimal.Decimal) *ProviderOrderTokenUpsertOne { +// SetFixedSellRate sets the "fixed_sell_rate" field. +func (u *ProviderOrderTokenUpsertOne) SetFixedSellRate(v decimal.Decimal) *ProviderOrderTokenUpsertOne { return u.Update(func(s *ProviderOrderTokenUpsert) { - s.AddFloatingConversionRate(v) + s.SetFixedSellRate(v) }) } -// UpdateFloatingConversionRate sets the "floating_conversion_rate" field to the value that was provided on create. -func (u *ProviderOrderTokenUpsertOne) UpdateFloatingConversionRate() *ProviderOrderTokenUpsertOne { +// AddFixedSellRate adds v to the "fixed_sell_rate" field. +func (u *ProviderOrderTokenUpsertOne) AddFixedSellRate(v decimal.Decimal) *ProviderOrderTokenUpsertOne { return u.Update(func(s *ProviderOrderTokenUpsert) { - s.UpdateFloatingConversionRate() + s.AddFixedSellRate(v) }) } -// SetConversionRateType sets the "conversion_rate_type" field. -func (u *ProviderOrderTokenUpsertOne) SetConversionRateType(v providerordertoken.ConversionRateType) *ProviderOrderTokenUpsertOne { +// UpdateFixedSellRate sets the "fixed_sell_rate" field to the value that was provided on create. +func (u *ProviderOrderTokenUpsertOne) UpdateFixedSellRate() *ProviderOrderTokenUpsertOne { return u.Update(func(s *ProviderOrderTokenUpsert) { - s.SetConversionRateType(v) + s.UpdateFixedSellRate() }) } -// UpdateConversionRateType sets the "conversion_rate_type" field to the value that was provided on create. -func (u *ProviderOrderTokenUpsertOne) UpdateConversionRateType() *ProviderOrderTokenUpsertOne { +// ClearFixedSellRate clears the value of the "fixed_sell_rate" field. +func (u *ProviderOrderTokenUpsertOne) ClearFixedSellRate() *ProviderOrderTokenUpsertOne { return u.Update(func(s *ProviderOrderTokenUpsert) { - s.UpdateConversionRateType() + s.ClearFixedSellRate() + }) +} + +// SetFloatingBuyDelta sets the "floating_buy_delta" field. +func (u *ProviderOrderTokenUpsertOne) SetFloatingBuyDelta(v decimal.Decimal) *ProviderOrderTokenUpsertOne { + return u.Update(func(s *ProviderOrderTokenUpsert) { + s.SetFloatingBuyDelta(v) + }) +} + +// AddFloatingBuyDelta adds v to the "floating_buy_delta" field. +func (u *ProviderOrderTokenUpsertOne) AddFloatingBuyDelta(v decimal.Decimal) *ProviderOrderTokenUpsertOne { + return u.Update(func(s *ProviderOrderTokenUpsert) { + s.AddFloatingBuyDelta(v) + }) +} + +// UpdateFloatingBuyDelta sets the "floating_buy_delta" field to the value that was provided on create. +func (u *ProviderOrderTokenUpsertOne) UpdateFloatingBuyDelta() *ProviderOrderTokenUpsertOne { + return u.Update(func(s *ProviderOrderTokenUpsert) { + s.UpdateFloatingBuyDelta() + }) +} + +// ClearFloatingBuyDelta clears the value of the "floating_buy_delta" field. +func (u *ProviderOrderTokenUpsertOne) ClearFloatingBuyDelta() *ProviderOrderTokenUpsertOne { + return u.Update(func(s *ProviderOrderTokenUpsert) { + s.ClearFloatingBuyDelta() + }) +} + +// SetFloatingSellDelta sets the "floating_sell_delta" field. +func (u *ProviderOrderTokenUpsertOne) SetFloatingSellDelta(v decimal.Decimal) *ProviderOrderTokenUpsertOne { + return u.Update(func(s *ProviderOrderTokenUpsert) { + s.SetFloatingSellDelta(v) + }) +} + +// AddFloatingSellDelta adds v to the "floating_sell_delta" field. +func (u *ProviderOrderTokenUpsertOne) AddFloatingSellDelta(v decimal.Decimal) *ProviderOrderTokenUpsertOne { + return u.Update(func(s *ProviderOrderTokenUpsert) { + s.AddFloatingSellDelta(v) + }) +} + +// UpdateFloatingSellDelta sets the "floating_sell_delta" field to the value that was provided on create. +func (u *ProviderOrderTokenUpsertOne) UpdateFloatingSellDelta() *ProviderOrderTokenUpsertOne { + return u.Update(func(s *ProviderOrderTokenUpsert) { + s.UpdateFloatingSellDelta() + }) +} + +// ClearFloatingSellDelta clears the value of the "floating_sell_delta" field. +func (u *ProviderOrderTokenUpsertOne) ClearFloatingSellDelta() *ProviderOrderTokenUpsertOne { + return u.Update(func(s *ProviderOrderTokenUpsert) { + s.ClearFloatingSellDelta() }) } @@ -1145,59 +1277,115 @@ func (u *ProviderOrderTokenUpsertBulk) UpdateUpdatedAt() *ProviderOrderTokenUpse }) } -// SetFixedConversionRate sets the "fixed_conversion_rate" field. -func (u *ProviderOrderTokenUpsertBulk) SetFixedConversionRate(v decimal.Decimal) *ProviderOrderTokenUpsertBulk { +// SetFixedBuyRate sets the "fixed_buy_rate" field. +func (u *ProviderOrderTokenUpsertBulk) SetFixedBuyRate(v decimal.Decimal) *ProviderOrderTokenUpsertBulk { + return u.Update(func(s *ProviderOrderTokenUpsert) { + s.SetFixedBuyRate(v) + }) +} + +// AddFixedBuyRate adds v to the "fixed_buy_rate" field. +func (u *ProviderOrderTokenUpsertBulk) AddFixedBuyRate(v decimal.Decimal) *ProviderOrderTokenUpsertBulk { + return u.Update(func(s *ProviderOrderTokenUpsert) { + s.AddFixedBuyRate(v) + }) +} + +// UpdateFixedBuyRate sets the "fixed_buy_rate" field to the value that was provided on create. +func (u *ProviderOrderTokenUpsertBulk) UpdateFixedBuyRate() *ProviderOrderTokenUpsertBulk { + return u.Update(func(s *ProviderOrderTokenUpsert) { + s.UpdateFixedBuyRate() + }) +} + +// ClearFixedBuyRate clears the value of the "fixed_buy_rate" field. +func (u *ProviderOrderTokenUpsertBulk) ClearFixedBuyRate() *ProviderOrderTokenUpsertBulk { + return u.Update(func(s *ProviderOrderTokenUpsert) { + s.ClearFixedBuyRate() + }) +} + +// SetFixedSellRate sets the "fixed_sell_rate" field. +func (u *ProviderOrderTokenUpsertBulk) SetFixedSellRate(v decimal.Decimal) *ProviderOrderTokenUpsertBulk { + return u.Update(func(s *ProviderOrderTokenUpsert) { + s.SetFixedSellRate(v) + }) +} + +// AddFixedSellRate adds v to the "fixed_sell_rate" field. +func (u *ProviderOrderTokenUpsertBulk) AddFixedSellRate(v decimal.Decimal) *ProviderOrderTokenUpsertBulk { + return u.Update(func(s *ProviderOrderTokenUpsert) { + s.AddFixedSellRate(v) + }) +} + +// UpdateFixedSellRate sets the "fixed_sell_rate" field to the value that was provided on create. +func (u *ProviderOrderTokenUpsertBulk) UpdateFixedSellRate() *ProviderOrderTokenUpsertBulk { + return u.Update(func(s *ProviderOrderTokenUpsert) { + s.UpdateFixedSellRate() + }) +} + +// ClearFixedSellRate clears the value of the "fixed_sell_rate" field. +func (u *ProviderOrderTokenUpsertBulk) ClearFixedSellRate() *ProviderOrderTokenUpsertBulk { + return u.Update(func(s *ProviderOrderTokenUpsert) { + s.ClearFixedSellRate() + }) +} + +// SetFloatingBuyDelta sets the "floating_buy_delta" field. +func (u *ProviderOrderTokenUpsertBulk) SetFloatingBuyDelta(v decimal.Decimal) *ProviderOrderTokenUpsertBulk { return u.Update(func(s *ProviderOrderTokenUpsert) { - s.SetFixedConversionRate(v) + s.SetFloatingBuyDelta(v) }) } -// AddFixedConversionRate adds v to the "fixed_conversion_rate" field. -func (u *ProviderOrderTokenUpsertBulk) AddFixedConversionRate(v decimal.Decimal) *ProviderOrderTokenUpsertBulk { +// AddFloatingBuyDelta adds v to the "floating_buy_delta" field. +func (u *ProviderOrderTokenUpsertBulk) AddFloatingBuyDelta(v decimal.Decimal) *ProviderOrderTokenUpsertBulk { return u.Update(func(s *ProviderOrderTokenUpsert) { - s.AddFixedConversionRate(v) + s.AddFloatingBuyDelta(v) }) } -// UpdateFixedConversionRate sets the "fixed_conversion_rate" field to the value that was provided on create. -func (u *ProviderOrderTokenUpsertBulk) UpdateFixedConversionRate() *ProviderOrderTokenUpsertBulk { +// UpdateFloatingBuyDelta sets the "floating_buy_delta" field to the value that was provided on create. +func (u *ProviderOrderTokenUpsertBulk) UpdateFloatingBuyDelta() *ProviderOrderTokenUpsertBulk { return u.Update(func(s *ProviderOrderTokenUpsert) { - s.UpdateFixedConversionRate() + s.UpdateFloatingBuyDelta() }) } -// SetFloatingConversionRate sets the "floating_conversion_rate" field. -func (u *ProviderOrderTokenUpsertBulk) SetFloatingConversionRate(v decimal.Decimal) *ProviderOrderTokenUpsertBulk { +// ClearFloatingBuyDelta clears the value of the "floating_buy_delta" field. +func (u *ProviderOrderTokenUpsertBulk) ClearFloatingBuyDelta() *ProviderOrderTokenUpsertBulk { return u.Update(func(s *ProviderOrderTokenUpsert) { - s.SetFloatingConversionRate(v) + s.ClearFloatingBuyDelta() }) } -// AddFloatingConversionRate adds v to the "floating_conversion_rate" field. -func (u *ProviderOrderTokenUpsertBulk) AddFloatingConversionRate(v decimal.Decimal) *ProviderOrderTokenUpsertBulk { +// SetFloatingSellDelta sets the "floating_sell_delta" field. +func (u *ProviderOrderTokenUpsertBulk) SetFloatingSellDelta(v decimal.Decimal) *ProviderOrderTokenUpsertBulk { return u.Update(func(s *ProviderOrderTokenUpsert) { - s.AddFloatingConversionRate(v) + s.SetFloatingSellDelta(v) }) } -// UpdateFloatingConversionRate sets the "floating_conversion_rate" field to the value that was provided on create. -func (u *ProviderOrderTokenUpsertBulk) UpdateFloatingConversionRate() *ProviderOrderTokenUpsertBulk { +// AddFloatingSellDelta adds v to the "floating_sell_delta" field. +func (u *ProviderOrderTokenUpsertBulk) AddFloatingSellDelta(v decimal.Decimal) *ProviderOrderTokenUpsertBulk { return u.Update(func(s *ProviderOrderTokenUpsert) { - s.UpdateFloatingConversionRate() + s.AddFloatingSellDelta(v) }) } -// SetConversionRateType sets the "conversion_rate_type" field. -func (u *ProviderOrderTokenUpsertBulk) SetConversionRateType(v providerordertoken.ConversionRateType) *ProviderOrderTokenUpsertBulk { +// UpdateFloatingSellDelta sets the "floating_sell_delta" field to the value that was provided on create. +func (u *ProviderOrderTokenUpsertBulk) UpdateFloatingSellDelta() *ProviderOrderTokenUpsertBulk { return u.Update(func(s *ProviderOrderTokenUpsert) { - s.SetConversionRateType(v) + s.UpdateFloatingSellDelta() }) } -// UpdateConversionRateType sets the "conversion_rate_type" field to the value that was provided on create. -func (u *ProviderOrderTokenUpsertBulk) UpdateConversionRateType() *ProviderOrderTokenUpsertBulk { +// ClearFloatingSellDelta clears the value of the "floating_sell_delta" field. +func (u *ProviderOrderTokenUpsertBulk) ClearFloatingSellDelta() *ProviderOrderTokenUpsertBulk { return u.Update(func(s *ProviderOrderTokenUpsert) { - s.UpdateConversionRateType() + s.ClearFloatingSellDelta() }) } diff --git a/ent/providerordertoken_update.go b/ent/providerordertoken_update.go index 9116f4377..5cb2e32f7 100644 --- a/ent/providerordertoken_update.go +++ b/ent/providerordertoken_update.go @@ -39,62 +39,114 @@ func (_u *ProviderOrderTokenUpdate) SetUpdatedAt(v time.Time) *ProviderOrderToke return _u } -// SetFixedConversionRate sets the "fixed_conversion_rate" field. -func (_u *ProviderOrderTokenUpdate) SetFixedConversionRate(v decimal.Decimal) *ProviderOrderTokenUpdate { - _u.mutation.ResetFixedConversionRate() - _u.mutation.SetFixedConversionRate(v) +// SetFixedBuyRate sets the "fixed_buy_rate" field. +func (_u *ProviderOrderTokenUpdate) SetFixedBuyRate(v decimal.Decimal) *ProviderOrderTokenUpdate { + _u.mutation.ResetFixedBuyRate() + _u.mutation.SetFixedBuyRate(v) return _u } -// SetNillableFixedConversionRate sets the "fixed_conversion_rate" field if the given value is not nil. -func (_u *ProviderOrderTokenUpdate) SetNillableFixedConversionRate(v *decimal.Decimal) *ProviderOrderTokenUpdate { +// SetNillableFixedBuyRate sets the "fixed_buy_rate" field if the given value is not nil. +func (_u *ProviderOrderTokenUpdate) SetNillableFixedBuyRate(v *decimal.Decimal) *ProviderOrderTokenUpdate { if v != nil { - _u.SetFixedConversionRate(*v) + _u.SetFixedBuyRate(*v) } return _u } -// AddFixedConversionRate adds value to the "fixed_conversion_rate" field. -func (_u *ProviderOrderTokenUpdate) AddFixedConversionRate(v decimal.Decimal) *ProviderOrderTokenUpdate { - _u.mutation.AddFixedConversionRate(v) +// AddFixedBuyRate adds value to the "fixed_buy_rate" field. +func (_u *ProviderOrderTokenUpdate) AddFixedBuyRate(v decimal.Decimal) *ProviderOrderTokenUpdate { + _u.mutation.AddFixedBuyRate(v) return _u } -// SetFloatingConversionRate sets the "floating_conversion_rate" field. -func (_u *ProviderOrderTokenUpdate) SetFloatingConversionRate(v decimal.Decimal) *ProviderOrderTokenUpdate { - _u.mutation.ResetFloatingConversionRate() - _u.mutation.SetFloatingConversionRate(v) +// ClearFixedBuyRate clears the value of the "fixed_buy_rate" field. +func (_u *ProviderOrderTokenUpdate) ClearFixedBuyRate() *ProviderOrderTokenUpdate { + _u.mutation.ClearFixedBuyRate() return _u } -// SetNillableFloatingConversionRate sets the "floating_conversion_rate" field if the given value is not nil. -func (_u *ProviderOrderTokenUpdate) SetNillableFloatingConversionRate(v *decimal.Decimal) *ProviderOrderTokenUpdate { +// SetFixedSellRate sets the "fixed_sell_rate" field. +func (_u *ProviderOrderTokenUpdate) SetFixedSellRate(v decimal.Decimal) *ProviderOrderTokenUpdate { + _u.mutation.ResetFixedSellRate() + _u.mutation.SetFixedSellRate(v) + return _u +} + +// SetNillableFixedSellRate sets the "fixed_sell_rate" field if the given value is not nil. +func (_u *ProviderOrderTokenUpdate) SetNillableFixedSellRate(v *decimal.Decimal) *ProviderOrderTokenUpdate { if v != nil { - _u.SetFloatingConversionRate(*v) + _u.SetFixedSellRate(*v) } return _u } -// AddFloatingConversionRate adds value to the "floating_conversion_rate" field. -func (_u *ProviderOrderTokenUpdate) AddFloatingConversionRate(v decimal.Decimal) *ProviderOrderTokenUpdate { - _u.mutation.AddFloatingConversionRate(v) +// AddFixedSellRate adds value to the "fixed_sell_rate" field. +func (_u *ProviderOrderTokenUpdate) AddFixedSellRate(v decimal.Decimal) *ProviderOrderTokenUpdate { + _u.mutation.AddFixedSellRate(v) return _u } -// SetConversionRateType sets the "conversion_rate_type" field. -func (_u *ProviderOrderTokenUpdate) SetConversionRateType(v providerordertoken.ConversionRateType) *ProviderOrderTokenUpdate { - _u.mutation.SetConversionRateType(v) +// ClearFixedSellRate clears the value of the "fixed_sell_rate" field. +func (_u *ProviderOrderTokenUpdate) ClearFixedSellRate() *ProviderOrderTokenUpdate { + _u.mutation.ClearFixedSellRate() return _u } -// SetNillableConversionRateType sets the "conversion_rate_type" field if the given value is not nil. -func (_u *ProviderOrderTokenUpdate) SetNillableConversionRateType(v *providerordertoken.ConversionRateType) *ProviderOrderTokenUpdate { +// SetFloatingBuyDelta sets the "floating_buy_delta" field. +func (_u *ProviderOrderTokenUpdate) SetFloatingBuyDelta(v decimal.Decimal) *ProviderOrderTokenUpdate { + _u.mutation.ResetFloatingBuyDelta() + _u.mutation.SetFloatingBuyDelta(v) + return _u +} + +// SetNillableFloatingBuyDelta sets the "floating_buy_delta" field if the given value is not nil. +func (_u *ProviderOrderTokenUpdate) SetNillableFloatingBuyDelta(v *decimal.Decimal) *ProviderOrderTokenUpdate { if v != nil { - _u.SetConversionRateType(*v) + _u.SetFloatingBuyDelta(*v) } return _u } +// AddFloatingBuyDelta adds value to the "floating_buy_delta" field. +func (_u *ProviderOrderTokenUpdate) AddFloatingBuyDelta(v decimal.Decimal) *ProviderOrderTokenUpdate { + _u.mutation.AddFloatingBuyDelta(v) + return _u +} + +// ClearFloatingBuyDelta clears the value of the "floating_buy_delta" field. +func (_u *ProviderOrderTokenUpdate) ClearFloatingBuyDelta() *ProviderOrderTokenUpdate { + _u.mutation.ClearFloatingBuyDelta() + return _u +} + +// SetFloatingSellDelta sets the "floating_sell_delta" field. +func (_u *ProviderOrderTokenUpdate) SetFloatingSellDelta(v decimal.Decimal) *ProviderOrderTokenUpdate { + _u.mutation.ResetFloatingSellDelta() + _u.mutation.SetFloatingSellDelta(v) + return _u +} + +// SetNillableFloatingSellDelta sets the "floating_sell_delta" field if the given value is not nil. +func (_u *ProviderOrderTokenUpdate) SetNillableFloatingSellDelta(v *decimal.Decimal) *ProviderOrderTokenUpdate { + if v != nil { + _u.SetFloatingSellDelta(*v) + } + return _u +} + +// AddFloatingSellDelta adds value to the "floating_sell_delta" field. +func (_u *ProviderOrderTokenUpdate) AddFloatingSellDelta(v decimal.Decimal) *ProviderOrderTokenUpdate { + _u.mutation.AddFloatingSellDelta(v) + return _u +} + +// ClearFloatingSellDelta clears the value of the "floating_sell_delta" field. +func (_u *ProviderOrderTokenUpdate) ClearFloatingSellDelta() *ProviderOrderTokenUpdate { + _u.mutation.ClearFloatingSellDelta() + return _u +} + // SetMaxOrderAmount sets the "max_order_amount" field. func (_u *ProviderOrderTokenUpdate) SetMaxOrderAmount(v decimal.Decimal) *ProviderOrderTokenUpdate { _u.mutation.ResetMaxOrderAmount() @@ -348,11 +400,6 @@ func (_u *ProviderOrderTokenUpdate) defaults() { // check runs all checks and user-defined validators on the builder. func (_u *ProviderOrderTokenUpdate) check() error { - if v, ok := _u.mutation.ConversionRateType(); ok { - if err := providerordertoken.ConversionRateTypeValidator(v); err != nil { - return &ValidationError{Name: "conversion_rate_type", err: fmt.Errorf(`ent: validator failed for field "ProviderOrderToken.conversion_rate_type": %w`, err)} - } - } if _u.mutation.ProviderCleared() && len(_u.mutation.ProviderIDs()) > 0 { return errors.New(`ent: clearing a required unique edge "ProviderOrderToken.provider"`) } @@ -380,20 +427,41 @@ func (_u *ProviderOrderTokenUpdate) sqlSave(ctx context.Context) (_node int, err if value, ok := _u.mutation.UpdatedAt(); ok { _spec.SetField(providerordertoken.FieldUpdatedAt, field.TypeTime, value) } - if value, ok := _u.mutation.FixedConversionRate(); ok { - _spec.SetField(providerordertoken.FieldFixedConversionRate, field.TypeFloat64, value) + if value, ok := _u.mutation.FixedBuyRate(); ok { + _spec.SetField(providerordertoken.FieldFixedBuyRate, field.TypeFloat64, value) + } + if value, ok := _u.mutation.AddedFixedBuyRate(); ok { + _spec.AddField(providerordertoken.FieldFixedBuyRate, field.TypeFloat64, value) + } + if _u.mutation.FixedBuyRateCleared() { + _spec.ClearField(providerordertoken.FieldFixedBuyRate, field.TypeFloat64) + } + if value, ok := _u.mutation.FixedSellRate(); ok { + _spec.SetField(providerordertoken.FieldFixedSellRate, field.TypeFloat64, value) + } + if value, ok := _u.mutation.AddedFixedSellRate(); ok { + _spec.AddField(providerordertoken.FieldFixedSellRate, field.TypeFloat64, value) + } + if _u.mutation.FixedSellRateCleared() { + _spec.ClearField(providerordertoken.FieldFixedSellRate, field.TypeFloat64) + } + if value, ok := _u.mutation.FloatingBuyDelta(); ok { + _spec.SetField(providerordertoken.FieldFloatingBuyDelta, field.TypeFloat64, value) + } + if value, ok := _u.mutation.AddedFloatingBuyDelta(); ok { + _spec.AddField(providerordertoken.FieldFloatingBuyDelta, field.TypeFloat64, value) } - if value, ok := _u.mutation.AddedFixedConversionRate(); ok { - _spec.AddField(providerordertoken.FieldFixedConversionRate, field.TypeFloat64, value) + if _u.mutation.FloatingBuyDeltaCleared() { + _spec.ClearField(providerordertoken.FieldFloatingBuyDelta, field.TypeFloat64) } - if value, ok := _u.mutation.FloatingConversionRate(); ok { - _spec.SetField(providerordertoken.FieldFloatingConversionRate, field.TypeFloat64, value) + if value, ok := _u.mutation.FloatingSellDelta(); ok { + _spec.SetField(providerordertoken.FieldFloatingSellDelta, field.TypeFloat64, value) } - if value, ok := _u.mutation.AddedFloatingConversionRate(); ok { - _spec.AddField(providerordertoken.FieldFloatingConversionRate, field.TypeFloat64, value) + if value, ok := _u.mutation.AddedFloatingSellDelta(); ok { + _spec.AddField(providerordertoken.FieldFloatingSellDelta, field.TypeFloat64, value) } - if value, ok := _u.mutation.ConversionRateType(); ok { - _spec.SetField(providerordertoken.FieldConversionRateType, field.TypeEnum, value) + if _u.mutation.FloatingSellDeltaCleared() { + _spec.ClearField(providerordertoken.FieldFloatingSellDelta, field.TypeFloat64) } if value, ok := _u.mutation.MaxOrderAmount(); ok { _spec.SetField(providerordertoken.FieldMaxOrderAmount, field.TypeFloat64, value) @@ -553,62 +621,114 @@ func (_u *ProviderOrderTokenUpdateOne) SetUpdatedAt(v time.Time) *ProviderOrderT return _u } -// SetFixedConversionRate sets the "fixed_conversion_rate" field. -func (_u *ProviderOrderTokenUpdateOne) SetFixedConversionRate(v decimal.Decimal) *ProviderOrderTokenUpdateOne { - _u.mutation.ResetFixedConversionRate() - _u.mutation.SetFixedConversionRate(v) +// SetFixedBuyRate sets the "fixed_buy_rate" field. +func (_u *ProviderOrderTokenUpdateOne) SetFixedBuyRate(v decimal.Decimal) *ProviderOrderTokenUpdateOne { + _u.mutation.ResetFixedBuyRate() + _u.mutation.SetFixedBuyRate(v) return _u } -// SetNillableFixedConversionRate sets the "fixed_conversion_rate" field if the given value is not nil. -func (_u *ProviderOrderTokenUpdateOne) SetNillableFixedConversionRate(v *decimal.Decimal) *ProviderOrderTokenUpdateOne { +// SetNillableFixedBuyRate sets the "fixed_buy_rate" field if the given value is not nil. +func (_u *ProviderOrderTokenUpdateOne) SetNillableFixedBuyRate(v *decimal.Decimal) *ProviderOrderTokenUpdateOne { if v != nil { - _u.SetFixedConversionRate(*v) + _u.SetFixedBuyRate(*v) } return _u } -// AddFixedConversionRate adds value to the "fixed_conversion_rate" field. -func (_u *ProviderOrderTokenUpdateOne) AddFixedConversionRate(v decimal.Decimal) *ProviderOrderTokenUpdateOne { - _u.mutation.AddFixedConversionRate(v) +// AddFixedBuyRate adds value to the "fixed_buy_rate" field. +func (_u *ProviderOrderTokenUpdateOne) AddFixedBuyRate(v decimal.Decimal) *ProviderOrderTokenUpdateOne { + _u.mutation.AddFixedBuyRate(v) return _u } -// SetFloatingConversionRate sets the "floating_conversion_rate" field. -func (_u *ProviderOrderTokenUpdateOne) SetFloatingConversionRate(v decimal.Decimal) *ProviderOrderTokenUpdateOne { - _u.mutation.ResetFloatingConversionRate() - _u.mutation.SetFloatingConversionRate(v) +// ClearFixedBuyRate clears the value of the "fixed_buy_rate" field. +func (_u *ProviderOrderTokenUpdateOne) ClearFixedBuyRate() *ProviderOrderTokenUpdateOne { + _u.mutation.ClearFixedBuyRate() return _u } -// SetNillableFloatingConversionRate sets the "floating_conversion_rate" field if the given value is not nil. -func (_u *ProviderOrderTokenUpdateOne) SetNillableFloatingConversionRate(v *decimal.Decimal) *ProviderOrderTokenUpdateOne { +// SetFixedSellRate sets the "fixed_sell_rate" field. +func (_u *ProviderOrderTokenUpdateOne) SetFixedSellRate(v decimal.Decimal) *ProviderOrderTokenUpdateOne { + _u.mutation.ResetFixedSellRate() + _u.mutation.SetFixedSellRate(v) + return _u +} + +// SetNillableFixedSellRate sets the "fixed_sell_rate" field if the given value is not nil. +func (_u *ProviderOrderTokenUpdateOne) SetNillableFixedSellRate(v *decimal.Decimal) *ProviderOrderTokenUpdateOne { if v != nil { - _u.SetFloatingConversionRate(*v) + _u.SetFixedSellRate(*v) } return _u } -// AddFloatingConversionRate adds value to the "floating_conversion_rate" field. -func (_u *ProviderOrderTokenUpdateOne) AddFloatingConversionRate(v decimal.Decimal) *ProviderOrderTokenUpdateOne { - _u.mutation.AddFloatingConversionRate(v) +// AddFixedSellRate adds value to the "fixed_sell_rate" field. +func (_u *ProviderOrderTokenUpdateOne) AddFixedSellRate(v decimal.Decimal) *ProviderOrderTokenUpdateOne { + _u.mutation.AddFixedSellRate(v) return _u } -// SetConversionRateType sets the "conversion_rate_type" field. -func (_u *ProviderOrderTokenUpdateOne) SetConversionRateType(v providerordertoken.ConversionRateType) *ProviderOrderTokenUpdateOne { - _u.mutation.SetConversionRateType(v) +// ClearFixedSellRate clears the value of the "fixed_sell_rate" field. +func (_u *ProviderOrderTokenUpdateOne) ClearFixedSellRate() *ProviderOrderTokenUpdateOne { + _u.mutation.ClearFixedSellRate() return _u } -// SetNillableConversionRateType sets the "conversion_rate_type" field if the given value is not nil. -func (_u *ProviderOrderTokenUpdateOne) SetNillableConversionRateType(v *providerordertoken.ConversionRateType) *ProviderOrderTokenUpdateOne { +// SetFloatingBuyDelta sets the "floating_buy_delta" field. +func (_u *ProviderOrderTokenUpdateOne) SetFloatingBuyDelta(v decimal.Decimal) *ProviderOrderTokenUpdateOne { + _u.mutation.ResetFloatingBuyDelta() + _u.mutation.SetFloatingBuyDelta(v) + return _u +} + +// SetNillableFloatingBuyDelta sets the "floating_buy_delta" field if the given value is not nil. +func (_u *ProviderOrderTokenUpdateOne) SetNillableFloatingBuyDelta(v *decimal.Decimal) *ProviderOrderTokenUpdateOne { if v != nil { - _u.SetConversionRateType(*v) + _u.SetFloatingBuyDelta(*v) } return _u } +// AddFloatingBuyDelta adds value to the "floating_buy_delta" field. +func (_u *ProviderOrderTokenUpdateOne) AddFloatingBuyDelta(v decimal.Decimal) *ProviderOrderTokenUpdateOne { + _u.mutation.AddFloatingBuyDelta(v) + return _u +} + +// ClearFloatingBuyDelta clears the value of the "floating_buy_delta" field. +func (_u *ProviderOrderTokenUpdateOne) ClearFloatingBuyDelta() *ProviderOrderTokenUpdateOne { + _u.mutation.ClearFloatingBuyDelta() + return _u +} + +// SetFloatingSellDelta sets the "floating_sell_delta" field. +func (_u *ProviderOrderTokenUpdateOne) SetFloatingSellDelta(v decimal.Decimal) *ProviderOrderTokenUpdateOne { + _u.mutation.ResetFloatingSellDelta() + _u.mutation.SetFloatingSellDelta(v) + return _u +} + +// SetNillableFloatingSellDelta sets the "floating_sell_delta" field if the given value is not nil. +func (_u *ProviderOrderTokenUpdateOne) SetNillableFloatingSellDelta(v *decimal.Decimal) *ProviderOrderTokenUpdateOne { + if v != nil { + _u.SetFloatingSellDelta(*v) + } + return _u +} + +// AddFloatingSellDelta adds value to the "floating_sell_delta" field. +func (_u *ProviderOrderTokenUpdateOne) AddFloatingSellDelta(v decimal.Decimal) *ProviderOrderTokenUpdateOne { + _u.mutation.AddFloatingSellDelta(v) + return _u +} + +// ClearFloatingSellDelta clears the value of the "floating_sell_delta" field. +func (_u *ProviderOrderTokenUpdateOne) ClearFloatingSellDelta() *ProviderOrderTokenUpdateOne { + _u.mutation.ClearFloatingSellDelta() + return _u +} + // SetMaxOrderAmount sets the "max_order_amount" field. func (_u *ProviderOrderTokenUpdateOne) SetMaxOrderAmount(v decimal.Decimal) *ProviderOrderTokenUpdateOne { _u.mutation.ResetMaxOrderAmount() @@ -875,11 +995,6 @@ func (_u *ProviderOrderTokenUpdateOne) defaults() { // check runs all checks and user-defined validators on the builder. func (_u *ProviderOrderTokenUpdateOne) check() error { - if v, ok := _u.mutation.ConversionRateType(); ok { - if err := providerordertoken.ConversionRateTypeValidator(v); err != nil { - return &ValidationError{Name: "conversion_rate_type", err: fmt.Errorf(`ent: validator failed for field "ProviderOrderToken.conversion_rate_type": %w`, err)} - } - } if _u.mutation.ProviderCleared() && len(_u.mutation.ProviderIDs()) > 0 { return errors.New(`ent: clearing a required unique edge "ProviderOrderToken.provider"`) } @@ -924,20 +1039,41 @@ func (_u *ProviderOrderTokenUpdateOne) sqlSave(ctx context.Context) (_node *Prov if value, ok := _u.mutation.UpdatedAt(); ok { _spec.SetField(providerordertoken.FieldUpdatedAt, field.TypeTime, value) } - if value, ok := _u.mutation.FixedConversionRate(); ok { - _spec.SetField(providerordertoken.FieldFixedConversionRate, field.TypeFloat64, value) + if value, ok := _u.mutation.FixedBuyRate(); ok { + _spec.SetField(providerordertoken.FieldFixedBuyRate, field.TypeFloat64, value) + } + if value, ok := _u.mutation.AddedFixedBuyRate(); ok { + _spec.AddField(providerordertoken.FieldFixedBuyRate, field.TypeFloat64, value) + } + if _u.mutation.FixedBuyRateCleared() { + _spec.ClearField(providerordertoken.FieldFixedBuyRate, field.TypeFloat64) + } + if value, ok := _u.mutation.FixedSellRate(); ok { + _spec.SetField(providerordertoken.FieldFixedSellRate, field.TypeFloat64, value) + } + if value, ok := _u.mutation.AddedFixedSellRate(); ok { + _spec.AddField(providerordertoken.FieldFixedSellRate, field.TypeFloat64, value) + } + if _u.mutation.FixedSellRateCleared() { + _spec.ClearField(providerordertoken.FieldFixedSellRate, field.TypeFloat64) + } + if value, ok := _u.mutation.FloatingBuyDelta(); ok { + _spec.SetField(providerordertoken.FieldFloatingBuyDelta, field.TypeFloat64, value) + } + if value, ok := _u.mutation.AddedFloatingBuyDelta(); ok { + _spec.AddField(providerordertoken.FieldFloatingBuyDelta, field.TypeFloat64, value) } - if value, ok := _u.mutation.AddedFixedConversionRate(); ok { - _spec.AddField(providerordertoken.FieldFixedConversionRate, field.TypeFloat64, value) + if _u.mutation.FloatingBuyDeltaCleared() { + _spec.ClearField(providerordertoken.FieldFloatingBuyDelta, field.TypeFloat64) } - if value, ok := _u.mutation.FloatingConversionRate(); ok { - _spec.SetField(providerordertoken.FieldFloatingConversionRate, field.TypeFloat64, value) + if value, ok := _u.mutation.FloatingSellDelta(); ok { + _spec.SetField(providerordertoken.FieldFloatingSellDelta, field.TypeFloat64, value) } - if value, ok := _u.mutation.AddedFloatingConversionRate(); ok { - _spec.AddField(providerordertoken.FieldFloatingConversionRate, field.TypeFloat64, value) + if value, ok := _u.mutation.AddedFloatingSellDelta(); ok { + _spec.AddField(providerordertoken.FieldFloatingSellDelta, field.TypeFloat64, value) } - if value, ok := _u.mutation.ConversionRateType(); ok { - _spec.SetField(providerordertoken.FieldConversionRateType, field.TypeEnum, value) + if _u.mutation.FloatingSellDeltaCleared() { + _spec.ClearField(providerordertoken.FieldFloatingSellDelta, field.TypeFloat64) } if value, ok := _u.mutation.MaxOrderAmount(); ok { _spec.SetField(providerordertoken.FieldMaxOrderAmount, field.TypeFloat64, value) diff --git a/ent/runtime/runtime.go b/ent/runtime/runtime.go index d53d0ee1c..6b17061bd 100644 --- a/ent/runtime/runtime.go +++ b/ent/runtime/runtime.go @@ -77,7 +77,7 @@ func init() { // fiatcurrency.DefaultDecimals holds the default value on creation for the decimals field. fiatcurrency.DefaultDecimals = fiatcurrencyDescDecimals.Default.(int) // fiatcurrencyDescIsEnabled is the schema descriptor for is_enabled field. - fiatcurrencyDescIsEnabled := fiatcurrencyFields[7].Descriptor() + fiatcurrencyDescIsEnabled := fiatcurrencyFields[8].Descriptor() // fiatcurrency.DefaultIsEnabled holds the default value on creation for the is_enabled field. fiatcurrency.DefaultIsEnabled = fiatcurrencyDescIsEnabled.Default.(bool) // fiatcurrencyDescID is the schema descriptor for id field. @@ -220,10 +220,10 @@ func init() { paymentorderDescFromAddress := paymentorderFields[16].Descriptor() // paymentorder.FromAddressValidator is a validator for the "from_address" field. It is called by the builders before save. paymentorder.FromAddressValidator = paymentorderDescFromAddress.Validators[0].(func(string) error) - // paymentorderDescReturnAddress is the schema descriptor for return_address field. - paymentorderDescReturnAddress := paymentorderFields[17].Descriptor() - // paymentorder.ReturnAddressValidator is a validator for the "return_address" field. It is called by the builders before save. - paymentorder.ReturnAddressValidator = paymentorderDescReturnAddress.Validators[0].(func(string) error) + // paymentorderDescRefundOrRecipientAddress is the schema descriptor for refund_or_recipient_address field. + paymentorderDescRefundOrRecipientAddress := paymentorderFields[17].Descriptor() + // paymentorder.RefundOrRecipientAddressValidator is a validator for the "refund_or_recipient_address" field. It is called by the builders before save. + paymentorder.RefundOrRecipientAddressValidator = paymentorderDescRefundOrRecipientAddress.Validators[0].(func(string) error) // paymentorderDescReceiveAddress is the schema descriptor for receive_address field. paymentorderDescReceiveAddress := paymentorderFields[18].Descriptor() // paymentorder.ReceiveAddressValidator is a validator for the "receive_address" field. It is called by the builders before save. @@ -244,26 +244,26 @@ func init() { paymentorderDescAccountName := paymentorderFields[25].Descriptor() // paymentorder.AccountNameValidator is a validator for the "account_name" field. It is called by the builders before save. paymentorder.AccountNameValidator = paymentorderDescAccountName.Validators[0].(func(string) error) - // paymentorderDescMemo is the schema descriptor for memo field. - paymentorderDescMemo := paymentorderFields[26].Descriptor() - // paymentorder.MemoValidator is a validator for the "memo" field. It is called by the builders before save. - paymentorder.MemoValidator = paymentorderDescMemo.Validators[0].(func(string) error) // paymentorderDescSender is the schema descriptor for sender field. - paymentorderDescSender := paymentorderFields[28].Descriptor() + paymentorderDescSender := paymentorderFields[27].Descriptor() // paymentorder.SenderValidator is a validator for the "sender" field. It is called by the builders before save. paymentorder.SenderValidator = paymentorderDescSender.Validators[0].(func(string) error) // paymentorderDescReference is the schema descriptor for reference field. - paymentorderDescReference := paymentorderFields[29].Descriptor() + paymentorderDescReference := paymentorderFields[28].Descriptor() // paymentorder.ReferenceValidator is a validator for the "reference" field. It is called by the builders before save. paymentorder.ReferenceValidator = paymentorderDescReference.Validators[0].(func(string) error) // paymentorderDescCancellationCount is the schema descriptor for cancellation_count field. - paymentorderDescCancellationCount := paymentorderFields[30].Descriptor() + paymentorderDescCancellationCount := paymentorderFields[29].Descriptor() // paymentorder.DefaultCancellationCount holds the default value on creation for the cancellation_count field. paymentorder.DefaultCancellationCount = paymentorderDescCancellationCount.Default.(int) // paymentorderDescCancellationReasons is the schema descriptor for cancellation_reasons field. - paymentorderDescCancellationReasons := paymentorderFields[31].Descriptor() + paymentorderDescCancellationReasons := paymentorderFields[30].Descriptor() // paymentorder.DefaultCancellationReasons holds the default value on creation for the cancellation_reasons field. paymentorder.DefaultCancellationReasons = paymentorderDescCancellationReasons.Default.([]string) + // paymentorderDescMemo is the schema descriptor for memo field. + paymentorderDescMemo := paymentorderFields[31].Descriptor() + // paymentorder.MemoValidator is a validator for the "memo" field. It is called by the builders before save. + paymentorder.MemoValidator = paymentorderDescMemo.Validators[0].(func(string) error) // paymentorderDescID is the schema descriptor for id field. paymentorderDescID := paymentorderFields[0].Descriptor() // paymentorder.DefaultID holds the default value on creation for the id field. @@ -534,20 +534,24 @@ func init() { senderordertoken.RefundAddressValidator = senderordertokenDescRefundAddress.Validators[0].(func(string) error) senderprofileFields := schema.SenderProfile{}.Fields() _ = senderprofileFields + // senderprofileDescWebhookVersion is the schema descriptor for webhook_version field. + senderprofileDescWebhookVersion := senderprofileFields[2].Descriptor() + // senderprofile.DefaultWebhookVersion holds the default value on creation for the webhook_version field. + senderprofile.DefaultWebhookVersion = senderprofileDescWebhookVersion.Default.(string) // senderprofileDescDomainWhitelist is the schema descriptor for domain_whitelist field. - senderprofileDescDomainWhitelist := senderprofileFields[2].Descriptor() + senderprofileDescDomainWhitelist := senderprofileFields[3].Descriptor() // senderprofile.DefaultDomainWhitelist holds the default value on creation for the domain_whitelist field. senderprofile.DefaultDomainWhitelist = senderprofileDescDomainWhitelist.Default.([]string) // senderprofileDescIsPartner is the schema descriptor for is_partner field. - senderprofileDescIsPartner := senderprofileFields[4].Descriptor() + senderprofileDescIsPartner := senderprofileFields[5].Descriptor() // senderprofile.DefaultIsPartner holds the default value on creation for the is_partner field. senderprofile.DefaultIsPartner = senderprofileDescIsPartner.Default.(bool) // senderprofileDescIsActive is the schema descriptor for is_active field. - senderprofileDescIsActive := senderprofileFields[5].Descriptor() + senderprofileDescIsActive := senderprofileFields[6].Descriptor() // senderprofile.DefaultIsActive holds the default value on creation for the is_active field. senderprofile.DefaultIsActive = senderprofileDescIsActive.Default.(bool) // senderprofileDescUpdatedAt is the schema descriptor for updated_at field. - senderprofileDescUpdatedAt := senderprofileFields[6].Descriptor() + senderprofileDescUpdatedAt := senderprofileFields[7].Descriptor() // senderprofile.DefaultUpdatedAt holds the default value on creation for the updated_at field. senderprofile.DefaultUpdatedAt = senderprofileDescUpdatedAt.Default.(func() time.Time) // senderprofile.UpdateDefaultUpdatedAt holds the default value on update for the updated_at field. diff --git a/ent/schema/fiatcurrency.go b/ent/schema/fiatcurrency.go index e4df34d8c..c1de49dcf 100644 --- a/ent/schema/fiatcurrency.go +++ b/ent/schema/fiatcurrency.go @@ -31,8 +31,12 @@ func (FiatCurrency) Fields() []ent.Field { field.Int("decimals").Default(2), field.String("symbol"), field.String("name"), - field.Float("market_rate"). - GoType(decimal.Decimal{}), + field.Float("market_buy_rate"). + GoType(decimal.Decimal{}). + Optional(), + field.Float("market_sell_rate"). + GoType(decimal.Decimal{}). + Optional(), field.Bool("is_enabled").Default(false), } } diff --git a/ent/schema/paymentorder.go b/ent/schema/paymentorder.go index 0b364af5b..535c5ced6 100644 --- a/ent/schema/paymentorder.go +++ b/ent/schema/paymentorder.go @@ -68,7 +68,7 @@ func (PaymentOrder) Fields() []ent.Field { field.String("from_address"). MaxLen(70). Optional(), - field.String("return_address"). + field.String("refund_or_recipient_address"). MaxLen(70). Optional(), field.String("receive_address"). @@ -84,16 +84,13 @@ func (PaymentOrder) Fields() []ent.Field { Optional(), field.Time("indexer_created_at"). Optional(), - // Recipient info + // Recipient or Refund info field.String("institution"). MaxLen(255), field.String("account_identifier"). MaxLen(255), field.String("account_name"). MaxLen(255), - field.String("memo"). - MaxLen(255). - Optional(), field.JSON("metadata", map[string]interface{}{}). Optional(), // Order management @@ -109,10 +106,16 @@ func (PaymentOrder) Fields() []ent.Field { field.Strings("cancellation_reasons"). Default([]string{}). Optional(), + field.String("memo"). + MaxLen(255). + Optional(), // Status & type field.Enum("status"). Values("initiated", "deposited", "pending", "fulfilling", "fulfilled", "validated", "settling", "settled", "cancelled", "refunding", "refunded", "expired"). Default("initiated"), + field.Enum("direction"). + Values("offramp", "onramp"). + Default("offramp"), field.Enum("order_type"). Values("otc", "regular"). Default("regular"), diff --git a/ent/schema/paymentorderfulfillment.go b/ent/schema/paymentorderfulfillment.go index 5a8a3db21..431824a76 100644 --- a/ent/schema/paymentorderfulfillment.go +++ b/ent/schema/paymentorderfulfillment.go @@ -29,7 +29,7 @@ func (PaymentOrderFulfillment) Fields() []ent.Field { field.String("psp"). Optional(), field.Enum("validation_status"). - Values("pending", "success", "failed"). + Values("pending", "success", "failed", "refunded"). Default("pending"), field.String("validation_error"). Optional(), diff --git a/ent/schema/providerordertoken.go b/ent/schema/providerordertoken.go index 626c0f3f7..db003c042 100644 --- a/ent/schema/providerordertoken.go +++ b/ent/schema/providerordertoken.go @@ -23,12 +23,18 @@ func (ProviderOrderToken) Mixin() []ent.Mixin { // Fields of the ProviderOrderToken. func (ProviderOrderToken) Fields() []ent.Field { return []ent.Field{ - field.Float("fixed_conversion_rate"). - GoType(decimal.Decimal{}), - field.Float("floating_conversion_rate"). - GoType(decimal.Decimal{}), - field.Enum("conversion_rate_type"). - Values("fixed", "floating"), + field.Float("fixed_buy_rate"). + GoType(decimal.Decimal{}). + Optional(), + field.Float("fixed_sell_rate"). + GoType(decimal.Decimal{}). + Optional(), + field.Float("floating_buy_delta"). + GoType(decimal.Decimal{}). + Optional(), + field.Float("floating_sell_delta"). + GoType(decimal.Decimal{}). + Optional(), field.Float("max_order_amount"). GoType(decimal.Decimal{}), field.Float("min_order_amount"). diff --git a/ent/schema/senderprofile.go b/ent/schema/senderprofile.go index 2b743067f..a6e074523 100644 --- a/ent/schema/senderprofile.go +++ b/ent/schema/senderprofile.go @@ -21,6 +21,9 @@ func (SenderProfile) Fields() []ent.Field { field.UUID("id", uuid.UUID{}). Default(uuid.New), field.String("webhook_url").Optional(), + field.String("webhook_version"). + Default("1"). + Optional(), field.Strings("domain_whitelist"). Default([]string{}), field.String("provider_id").Optional(), diff --git a/ent/schema/transactionlog.go b/ent/schema/transactionlog.go index 39d5482ac..755093655 100644 --- a/ent/schema/transactionlog.go +++ b/ent/schema/transactionlog.go @@ -21,7 +21,7 @@ func (TransactionLog) Fields() []ent.Field { Immutable(), field.String("gateway_id").Optional(), field.Enum("status"). - Values("order_initiated", "crypto_deposited", "order_created", "order_processing", "order_fulfilled", "order_validated", "order_settled", "order_refunded", "gas_prefunded", "gateway_approved"). + Values("order_initiated", "crypto_deposited", "order_created", "order_fulfilling", "order_fulfilled", "order_validated", "order_settling", "order_settled", "order_refunding", "order_refunded", "gas_prefunded", "gateway_approved"). Default("order_initiated"). Immutable(), field.String("network").Optional(), diff --git a/ent/senderprofile.go b/ent/senderprofile.go index 21f03e52a..2a656e5c9 100644 --- a/ent/senderprofile.go +++ b/ent/senderprofile.go @@ -23,6 +23,8 @@ type SenderProfile struct { ID uuid.UUID `json:"id,omitempty"` // WebhookURL holds the value of the "webhook_url" field. WebhookURL string `json:"webhook_url,omitempty"` + // WebhookVersion holds the value of the "webhook_version" field. + WebhookVersion string `json:"webhook_version,omitempty"` // DomainWhitelist holds the value of the "domain_whitelist" field. DomainWhitelist []string `json:"domain_whitelist,omitempty"` // ProviderID holds the value of the "provider_id" field. @@ -104,7 +106,7 @@ func (*SenderProfile) scanValues(columns []string) ([]any, error) { values[i] = new([]byte) case senderprofile.FieldIsPartner, senderprofile.FieldIsActive: values[i] = new(sql.NullBool) - case senderprofile.FieldWebhookURL, senderprofile.FieldProviderID: + case senderprofile.FieldWebhookURL, senderprofile.FieldWebhookVersion, senderprofile.FieldProviderID: values[i] = new(sql.NullString) case senderprofile.FieldUpdatedAt: values[i] = new(sql.NullTime) @@ -139,6 +141,12 @@ func (_m *SenderProfile) assignValues(columns []string, values []any) error { } else if value.Valid { _m.WebhookURL = value.String } + case senderprofile.FieldWebhookVersion: + if value, ok := values[i].(*sql.NullString); !ok { + return fmt.Errorf("unexpected type %T for field webhook_version", values[i]) + } else if value.Valid { + _m.WebhookVersion = value.String + } case senderprofile.FieldDomainWhitelist: if value, ok := values[i].(*[]byte); !ok { return fmt.Errorf("unexpected type %T for field domain_whitelist", values[i]) @@ -237,6 +245,9 @@ func (_m *SenderProfile) String() string { builder.WriteString("webhook_url=") builder.WriteString(_m.WebhookURL) builder.WriteString(", ") + builder.WriteString("webhook_version=") + builder.WriteString(_m.WebhookVersion) + builder.WriteString(", ") builder.WriteString("domain_whitelist=") builder.WriteString(fmt.Sprintf("%v", _m.DomainWhitelist)) builder.WriteString(", ") diff --git a/ent/senderprofile/senderprofile.go b/ent/senderprofile/senderprofile.go index b588446a4..ce658682f 100644 --- a/ent/senderprofile/senderprofile.go +++ b/ent/senderprofile/senderprofile.go @@ -17,6 +17,8 @@ const ( FieldID = "id" // FieldWebhookURL holds the string denoting the webhook_url field in the database. FieldWebhookURL = "webhook_url" + // FieldWebhookVersion holds the string denoting the webhook_version field in the database. + FieldWebhookVersion = "webhook_version" // FieldDomainWhitelist holds the string denoting the domain_whitelist field in the database. FieldDomainWhitelist = "domain_whitelist" // FieldProviderID holds the string denoting the provider_id field in the database. @@ -71,6 +73,7 @@ const ( var Columns = []string{ FieldID, FieldWebhookURL, + FieldWebhookVersion, FieldDomainWhitelist, FieldProviderID, FieldIsPartner, @@ -100,6 +103,8 @@ func ValidColumn(column string) bool { } var ( + // DefaultWebhookVersion holds the default value on creation for the "webhook_version" field. + DefaultWebhookVersion string // DefaultDomainWhitelist holds the default value on creation for the "domain_whitelist" field. DefaultDomainWhitelist []string // DefaultIsPartner holds the default value on creation for the "is_partner" field. @@ -127,6 +132,11 @@ func ByWebhookURL(opts ...sql.OrderTermOption) OrderOption { return sql.OrderByField(FieldWebhookURL, opts...).ToFunc() } +// ByWebhookVersion orders the results by the webhook_version field. +func ByWebhookVersion(opts ...sql.OrderTermOption) OrderOption { + return sql.OrderByField(FieldWebhookVersion, opts...).ToFunc() +} + // ByProviderID orders the results by the provider_id field. func ByProviderID(opts ...sql.OrderTermOption) OrderOption { return sql.OrderByField(FieldProviderID, opts...).ToFunc() diff --git a/ent/senderprofile/where.go b/ent/senderprofile/where.go index a32c5d52d..c4261c1cd 100644 --- a/ent/senderprofile/where.go +++ b/ent/senderprofile/where.go @@ -61,6 +61,11 @@ func WebhookURL(v string) predicate.SenderProfile { return predicate.SenderProfile(sql.FieldEQ(FieldWebhookURL, v)) } +// WebhookVersion applies equality check predicate on the "webhook_version" field. It's identical to WebhookVersionEQ. +func WebhookVersion(v string) predicate.SenderProfile { + return predicate.SenderProfile(sql.FieldEQ(FieldWebhookVersion, v)) +} + // ProviderID applies equality check predicate on the "provider_id" field. It's identical to ProviderIDEQ. func ProviderID(v string) predicate.SenderProfile { return predicate.SenderProfile(sql.FieldEQ(FieldProviderID, v)) @@ -156,6 +161,81 @@ func WebhookURLContainsFold(v string) predicate.SenderProfile { return predicate.SenderProfile(sql.FieldContainsFold(FieldWebhookURL, v)) } +// WebhookVersionEQ applies the EQ predicate on the "webhook_version" field. +func WebhookVersionEQ(v string) predicate.SenderProfile { + return predicate.SenderProfile(sql.FieldEQ(FieldWebhookVersion, v)) +} + +// WebhookVersionNEQ applies the NEQ predicate on the "webhook_version" field. +func WebhookVersionNEQ(v string) predicate.SenderProfile { + return predicate.SenderProfile(sql.FieldNEQ(FieldWebhookVersion, v)) +} + +// WebhookVersionIn applies the In predicate on the "webhook_version" field. +func WebhookVersionIn(vs ...string) predicate.SenderProfile { + return predicate.SenderProfile(sql.FieldIn(FieldWebhookVersion, vs...)) +} + +// WebhookVersionNotIn applies the NotIn predicate on the "webhook_version" field. +func WebhookVersionNotIn(vs ...string) predicate.SenderProfile { + return predicate.SenderProfile(sql.FieldNotIn(FieldWebhookVersion, vs...)) +} + +// WebhookVersionGT applies the GT predicate on the "webhook_version" field. +func WebhookVersionGT(v string) predicate.SenderProfile { + return predicate.SenderProfile(sql.FieldGT(FieldWebhookVersion, v)) +} + +// WebhookVersionGTE applies the GTE predicate on the "webhook_version" field. +func WebhookVersionGTE(v string) predicate.SenderProfile { + return predicate.SenderProfile(sql.FieldGTE(FieldWebhookVersion, v)) +} + +// WebhookVersionLT applies the LT predicate on the "webhook_version" field. +func WebhookVersionLT(v string) predicate.SenderProfile { + return predicate.SenderProfile(sql.FieldLT(FieldWebhookVersion, v)) +} + +// WebhookVersionLTE applies the LTE predicate on the "webhook_version" field. +func WebhookVersionLTE(v string) predicate.SenderProfile { + return predicate.SenderProfile(sql.FieldLTE(FieldWebhookVersion, v)) +} + +// WebhookVersionContains applies the Contains predicate on the "webhook_version" field. +func WebhookVersionContains(v string) predicate.SenderProfile { + return predicate.SenderProfile(sql.FieldContains(FieldWebhookVersion, v)) +} + +// WebhookVersionHasPrefix applies the HasPrefix predicate on the "webhook_version" field. +func WebhookVersionHasPrefix(v string) predicate.SenderProfile { + return predicate.SenderProfile(sql.FieldHasPrefix(FieldWebhookVersion, v)) +} + +// WebhookVersionHasSuffix applies the HasSuffix predicate on the "webhook_version" field. +func WebhookVersionHasSuffix(v string) predicate.SenderProfile { + return predicate.SenderProfile(sql.FieldHasSuffix(FieldWebhookVersion, v)) +} + +// WebhookVersionIsNil applies the IsNil predicate on the "webhook_version" field. +func WebhookVersionIsNil() predicate.SenderProfile { + return predicate.SenderProfile(sql.FieldIsNull(FieldWebhookVersion)) +} + +// WebhookVersionNotNil applies the NotNil predicate on the "webhook_version" field. +func WebhookVersionNotNil() predicate.SenderProfile { + return predicate.SenderProfile(sql.FieldNotNull(FieldWebhookVersion)) +} + +// WebhookVersionEqualFold applies the EqualFold predicate on the "webhook_version" field. +func WebhookVersionEqualFold(v string) predicate.SenderProfile { + return predicate.SenderProfile(sql.FieldEqualFold(FieldWebhookVersion, v)) +} + +// WebhookVersionContainsFold applies the ContainsFold predicate on the "webhook_version" field. +func WebhookVersionContainsFold(v string) predicate.SenderProfile { + return predicate.SenderProfile(sql.FieldContainsFold(FieldWebhookVersion, v)) +} + // ProviderIDEQ applies the EQ predicate on the "provider_id" field. func ProviderIDEQ(v string) predicate.SenderProfile { return predicate.SenderProfile(sql.FieldEQ(FieldProviderID, v)) diff --git a/ent/senderprofile_create.go b/ent/senderprofile_create.go index 1783ffb18..8d4764ef0 100644 --- a/ent/senderprofile_create.go +++ b/ent/senderprofile_create.go @@ -42,6 +42,20 @@ func (_c *SenderProfileCreate) SetNillableWebhookURL(v *string) *SenderProfileCr return _c } +// SetWebhookVersion sets the "webhook_version" field. +func (_c *SenderProfileCreate) SetWebhookVersion(v string) *SenderProfileCreate { + _c.mutation.SetWebhookVersion(v) + return _c +} + +// SetNillableWebhookVersion sets the "webhook_version" field if the given value is not nil. +func (_c *SenderProfileCreate) SetNillableWebhookVersion(v *string) *SenderProfileCreate { + if v != nil { + _c.SetWebhookVersion(*v) + } + return _c +} + // SetDomainWhitelist sets the "domain_whitelist" field. func (_c *SenderProfileCreate) SetDomainWhitelist(v []string) *SenderProfileCreate { _c.mutation.SetDomainWhitelist(v) @@ -213,6 +227,10 @@ func (_c *SenderProfileCreate) ExecX(ctx context.Context) { // defaults sets the default values of the builder before save. func (_c *SenderProfileCreate) defaults() { + if _, ok := _c.mutation.WebhookVersion(); !ok { + v := senderprofile.DefaultWebhookVersion + _c.mutation.SetWebhookVersion(v) + } if _, ok := _c.mutation.DomainWhitelist(); !ok { v := senderprofile.DefaultDomainWhitelist _c.mutation.SetDomainWhitelist(v) @@ -292,6 +310,10 @@ func (_c *SenderProfileCreate) createSpec() (*SenderProfile, *sqlgraph.CreateSpe _spec.SetField(senderprofile.FieldWebhookURL, field.TypeString, value) _node.WebhookURL = value } + if value, ok := _c.mutation.WebhookVersion(); ok { + _spec.SetField(senderprofile.FieldWebhookVersion, field.TypeString, value) + _node.WebhookVersion = value + } if value, ok := _c.mutation.DomainWhitelist(); ok { _spec.SetField(senderprofile.FieldDomainWhitelist, field.TypeJSON, value) _node.DomainWhitelist = value @@ -447,6 +469,24 @@ func (u *SenderProfileUpsert) ClearWebhookURL() *SenderProfileUpsert { return u } +// SetWebhookVersion sets the "webhook_version" field. +func (u *SenderProfileUpsert) SetWebhookVersion(v string) *SenderProfileUpsert { + u.Set(senderprofile.FieldWebhookVersion, v) + return u +} + +// UpdateWebhookVersion sets the "webhook_version" field to the value that was provided on create. +func (u *SenderProfileUpsert) UpdateWebhookVersion() *SenderProfileUpsert { + u.SetExcluded(senderprofile.FieldWebhookVersion) + return u +} + +// ClearWebhookVersion clears the value of the "webhook_version" field. +func (u *SenderProfileUpsert) ClearWebhookVersion() *SenderProfileUpsert { + u.SetNull(senderprofile.FieldWebhookVersion) + return u +} + // SetDomainWhitelist sets the "domain_whitelist" field. func (u *SenderProfileUpsert) SetDomainWhitelist(v []string) *SenderProfileUpsert { u.Set(senderprofile.FieldDomainWhitelist, v) @@ -582,6 +622,27 @@ func (u *SenderProfileUpsertOne) ClearWebhookURL() *SenderProfileUpsertOne { }) } +// SetWebhookVersion sets the "webhook_version" field. +func (u *SenderProfileUpsertOne) SetWebhookVersion(v string) *SenderProfileUpsertOne { + return u.Update(func(s *SenderProfileUpsert) { + s.SetWebhookVersion(v) + }) +} + +// UpdateWebhookVersion sets the "webhook_version" field to the value that was provided on create. +func (u *SenderProfileUpsertOne) UpdateWebhookVersion() *SenderProfileUpsertOne { + return u.Update(func(s *SenderProfileUpsert) { + s.UpdateWebhookVersion() + }) +} + +// ClearWebhookVersion clears the value of the "webhook_version" field. +func (u *SenderProfileUpsertOne) ClearWebhookVersion() *SenderProfileUpsertOne { + return u.Update(func(s *SenderProfileUpsert) { + s.ClearWebhookVersion() + }) +} + // SetDomainWhitelist sets the "domain_whitelist" field. func (u *SenderProfileUpsertOne) SetDomainWhitelist(v []string) *SenderProfileUpsertOne { return u.Update(func(s *SenderProfileUpsert) { @@ -895,6 +956,27 @@ func (u *SenderProfileUpsertBulk) ClearWebhookURL() *SenderProfileUpsertBulk { }) } +// SetWebhookVersion sets the "webhook_version" field. +func (u *SenderProfileUpsertBulk) SetWebhookVersion(v string) *SenderProfileUpsertBulk { + return u.Update(func(s *SenderProfileUpsert) { + s.SetWebhookVersion(v) + }) +} + +// UpdateWebhookVersion sets the "webhook_version" field to the value that was provided on create. +func (u *SenderProfileUpsertBulk) UpdateWebhookVersion() *SenderProfileUpsertBulk { + return u.Update(func(s *SenderProfileUpsert) { + s.UpdateWebhookVersion() + }) +} + +// ClearWebhookVersion clears the value of the "webhook_version" field. +func (u *SenderProfileUpsertBulk) ClearWebhookVersion() *SenderProfileUpsertBulk { + return u.Update(func(s *SenderProfileUpsert) { + s.ClearWebhookVersion() + }) +} + // SetDomainWhitelist sets the "domain_whitelist" field. func (u *SenderProfileUpsertBulk) SetDomainWhitelist(v []string) *SenderProfileUpsertBulk { return u.Update(func(s *SenderProfileUpsert) { diff --git a/ent/senderprofile_update.go b/ent/senderprofile_update.go index f1bb4490e..a000ed187 100644 --- a/ent/senderprofile_update.go +++ b/ent/senderprofile_update.go @@ -53,6 +53,26 @@ func (_u *SenderProfileUpdate) ClearWebhookURL() *SenderProfileUpdate { return _u } +// SetWebhookVersion sets the "webhook_version" field. +func (_u *SenderProfileUpdate) SetWebhookVersion(v string) *SenderProfileUpdate { + _u.mutation.SetWebhookVersion(v) + return _u +} + +// SetNillableWebhookVersion sets the "webhook_version" field if the given value is not nil. +func (_u *SenderProfileUpdate) SetNillableWebhookVersion(v *string) *SenderProfileUpdate { + if v != nil { + _u.SetWebhookVersion(*v) + } + return _u +} + +// ClearWebhookVersion clears the value of the "webhook_version" field. +func (_u *SenderProfileUpdate) ClearWebhookVersion() *SenderProfileUpdate { + _u.mutation.ClearWebhookVersion() + return _u +} + // SetDomainWhitelist sets the "domain_whitelist" field. func (_u *SenderProfileUpdate) SetDomainWhitelist(v []string) *SenderProfileUpdate { _u.mutation.SetDomainWhitelist(v) @@ -283,6 +303,12 @@ func (_u *SenderProfileUpdate) sqlSave(ctx context.Context) (_node int, err erro if _u.mutation.WebhookURLCleared() { _spec.ClearField(senderprofile.FieldWebhookURL, field.TypeString) } + if value, ok := _u.mutation.WebhookVersion(); ok { + _spec.SetField(senderprofile.FieldWebhookVersion, field.TypeString, value) + } + if _u.mutation.WebhookVersionCleared() { + _spec.ClearField(senderprofile.FieldWebhookVersion, field.TypeString) + } if value, ok := _u.mutation.DomainWhitelist(); ok { _spec.SetField(senderprofile.FieldDomainWhitelist, field.TypeJSON, value) } @@ -465,6 +491,26 @@ func (_u *SenderProfileUpdateOne) ClearWebhookURL() *SenderProfileUpdateOne { return _u } +// SetWebhookVersion sets the "webhook_version" field. +func (_u *SenderProfileUpdateOne) SetWebhookVersion(v string) *SenderProfileUpdateOne { + _u.mutation.SetWebhookVersion(v) + return _u +} + +// SetNillableWebhookVersion sets the "webhook_version" field if the given value is not nil. +func (_u *SenderProfileUpdateOne) SetNillableWebhookVersion(v *string) *SenderProfileUpdateOne { + if v != nil { + _u.SetWebhookVersion(*v) + } + return _u +} + +// ClearWebhookVersion clears the value of the "webhook_version" field. +func (_u *SenderProfileUpdateOne) ClearWebhookVersion() *SenderProfileUpdateOne { + _u.mutation.ClearWebhookVersion() + return _u +} + // SetDomainWhitelist sets the "domain_whitelist" field. func (_u *SenderProfileUpdateOne) SetDomainWhitelist(v []string) *SenderProfileUpdateOne { _u.mutation.SetDomainWhitelist(v) @@ -725,6 +771,12 @@ func (_u *SenderProfileUpdateOne) sqlSave(ctx context.Context) (_node *SenderPro if _u.mutation.WebhookURLCleared() { _spec.ClearField(senderprofile.FieldWebhookURL, field.TypeString) } + if value, ok := _u.mutation.WebhookVersion(); ok { + _spec.SetField(senderprofile.FieldWebhookVersion, field.TypeString, value) + } + if _u.mutation.WebhookVersionCleared() { + _spec.ClearField(senderprofile.FieldWebhookVersion, field.TypeString) + } if value, ok := _u.mutation.DomainWhitelist(); ok { _spec.SetField(senderprofile.FieldDomainWhitelist, field.TypeJSON, value) } diff --git a/ent/transactionlog/transactionlog.go b/ent/transactionlog/transactionlog.go index f9bb5f3c3..d1c05a6bf 100644 --- a/ent/transactionlog/transactionlog.go +++ b/ent/transactionlog/transactionlog.go @@ -78,10 +78,12 @@ const ( StatusOrderInitiated Status = "order_initiated" StatusCryptoDeposited Status = "crypto_deposited" StatusOrderCreated Status = "order_created" - StatusOrderProcessing Status = "order_processing" + StatusOrderFulfilling Status = "order_fulfilling" StatusOrderFulfilled Status = "order_fulfilled" StatusOrderValidated Status = "order_validated" + StatusOrderSettling Status = "order_settling" StatusOrderSettled Status = "order_settled" + StatusOrderRefunding Status = "order_refunding" StatusOrderRefunded Status = "order_refunded" StatusGasPrefunded Status = "gas_prefunded" StatusGatewayApproved Status = "gateway_approved" @@ -94,7 +96,7 @@ func (s Status) String() string { // StatusValidator is a validator for the "status" field enum values. It is called by the builders before save. func StatusValidator(s Status) error { switch s { - case StatusOrderInitiated, StatusCryptoDeposited, StatusOrderCreated, StatusOrderProcessing, StatusOrderFulfilled, StatusOrderValidated, StatusOrderSettled, StatusOrderRefunded, StatusGasPrefunded, StatusGatewayApproved: + case StatusOrderInitiated, StatusCryptoDeposited, StatusOrderCreated, StatusOrderFulfilling, StatusOrderFulfilled, StatusOrderValidated, StatusOrderSettling, StatusOrderSettled, StatusOrderRefunding, StatusOrderRefunded, StatusGasPrefunded, StatusGatewayApproved: return nil default: return fmt.Errorf("transactionlog: invalid enum value for status field: %q", s) diff --git a/go.sum b/go.sum index e6f6f7283..268eef39a 100644 --- a/go.sum +++ b/go.sum @@ -422,6 +422,8 @@ github.com/spf13/afero v1.15.0 h1:b/YBCLWAJdFWJTN9cLhiXXcD7mzKn9Dm86dNnfyQw1I= github.com/spf13/afero v1.15.0/go.mod h1:NC2ByUVxtQs4b3sIUphxK0NioZnmxgyCrfzeuq8lxMg= github.com/spf13/cast v1.10.0 h1:h2x0u2shc1QuLHfxi+cTJvs30+ZAHOGRic8uyGTDWxY= github.com/spf13/cast v1.10.0/go.mod h1:jNfB8QC9IA6ZuY2ZjDp0KtFO2LZZlg4S/7bzP6qqeHo= +github.com/spf13/cobra v1.9.1 h1:CXSaggrXdbHK9CF+8ywj8Amf7PBRmPCOJugH954Nnlo= +github.com/spf13/cobra v1.9.1/go.mod h1:nDyEzZ8ogv936Cinf6g1RU9MRY64Ir93oCnqb9wxYW0= github.com/spf13/pflag v1.0.10 h1:4EBh2KAYBwaONj6b2Ye1GiHfwjqyROoF4RwYO+vPwFk= github.com/spf13/pflag v1.0.10/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= github.com/spf13/viper v1.21.0 h1:x5S+0EU27Lbphp4UKm1C+1oQO+rKx36vfCoaVebLFSU= @@ -554,6 +556,8 @@ golang.org/x/text v0.28.0/go.mod h1:U8nCwOR8jO/marOQ0QbDiOngZVEBB7MAiitBuMjXiNU= golang.org/x/time v0.12.0 h1:ScB/8o8olJvc+CQPWrK3fPZNfh7qgwCrY0zJmoEQLSE= golang.org/x/time v0.12.0/go.mod h1:CDIdPxbZBQxdj6cxyCIdrNogrJKMJ7pr37NYpMcMDSg= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.36.0 h1:kWS0uv/zsvHEle1LbV5LE8QujrxB3wfQyxHfhOk0Qkg= +golang.org/x/tools v0.36.0/go.mod h1:WBDiHKJK8YgLHlcQPYQzNCkUxUypCaa5ZegCVutKm+s= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= google.golang.org/genproto v0.0.0-20230410155749-daa745c078e1 h1:KpwkzHKEF7B9Zxg18WzOa7djJ+Ha5DzthMyZYQfEn2A= diff --git a/routers/index.go b/routers/index.go index 6fff01f83..43eca1e0d 100644 --- a/routers/index.go +++ b/routers/index.go @@ -62,9 +62,6 @@ func RegisterRoutes(route *gin.Engine) { // Reindex transaction endpoint v1.GET("reindex/:network/:tx_hash_or_address", ctrl.IndexTransaction) - // Index provider address endpoint - v1.POST("index-provider-address", ctrl.IndexProviderAddress) - // Etherscan queue monitoring endpoint v1.GET("etherscan/stats", ctrl.GetEtherscanQueueStats) @@ -147,6 +144,8 @@ func senderRoutes(route *gin.Engine) { v2.Use(middleware.OnlySenderMiddleware) v2.POST("orders", senderCtrl.InitiatePaymentOrderV2) + v2.GET("orders/:id", senderCtrl.GetPaymentOrderByIDV2) + v2.GET("orders", senderCtrl.GetPaymentOrdersV2) } func providerRoutes(route *gin.Engine) { @@ -157,6 +156,7 @@ func providerRoutes(route *gin.Engine) { v1.Use(middleware.OnlyProviderMiddleware) v1.GET("orders", providerCtrl.GetPaymentOrders) + v1.GET("orders/:id", providerCtrl.GetPaymentOrderByID) v1.POST("orders/:id/accept", providerCtrl.AcceptOrder) v1.POST("orders/:id/decline", providerCtrl.DeclineOrder) v1.POST("orders/:id/fulfill", providerCtrl.FulfillOrder) @@ -165,4 +165,11 @@ func providerRoutes(route *gin.Engine) { v1.GET("rates/:token/:fiat", providerCtrl.GetMarketRate) v1.GET("stats", providerCtrl.Stats) v1.GET("node-info", providerCtrl.NodeInfo) + + v2Provider := route.Group("/v2/provider/") + v2Provider.Use(middleware.DynamicAuthMiddleware) + v2Provider.Use(middleware.OnlyProviderMiddleware) + + v2Provider.GET("orders", providerCtrl.GetPaymentOrdersV2) + v2Provider.GET("orders/:id", providerCtrl.GetPaymentOrderByIDV2) } diff --git a/services/common/indexer.go b/services/common/indexer.go index dbeafacc6..23c504c4e 100644 --- a/services/common/indexer.go +++ b/services/common/indexer.go @@ -59,7 +59,10 @@ func ProcessTransfers( return } - _, err := UpdateReceiveAddressStatus(ctx, order, transferEvent, orderService.CreateOrder, priorityQueueService.GetProviderRate) + _, err := UpdateReceiveAddressStatus(ctx, order, transferEvent, orderService.CreateOrder, func(ctx context.Context, providerProfile *ent.ProviderProfile, tokenSymbol string, currency string) (decimal.Decimal, error) { + // Offramp context: use sell side rates + return priorityQueueService.GetProviderRate(ctx, providerProfile, tokenSymbol, currency, services.RateSideSell) + }) if err != nil { if !strings.Contains(fmt.Sprintf("%v", err), "Duplicate payment order") && !strings.Contains(fmt.Sprintf("%v", err), "Receive address not found") { logger.WithFields(logger.Fields{ @@ -115,8 +118,8 @@ func ProcessCreatedOrders( return nil } -// ProcessSettledOrders processes settled orders for a network -func ProcessSettledOrders(ctx context.Context, network *ent.Network, orderIds []string, orderIdToEvent map[string]*types.OrderSettledEvent) error { +// ProcessSettleOutOrders processes offramp (SettleOut) events and updates orders to settled. +func ProcessSettleOutOrders(ctx context.Context, network *ent.Network, orderIds []string, orderIdToEvent map[string]*types.SettleOutEvent) error { lockOrders, err := storage.Client.PaymentOrder. Query(). Where( @@ -128,7 +131,7 @@ func ProcessSettledOrders(ctx context.Context, network *ent.Network, orderIds [] }). All(ctx) if err != nil { - return fmt.Errorf("IndexOrderSettled.fetchLockOrders: %w", err) + return fmt.Errorf("ProcessSettleOutOrders.fetchLockOrders: %w", err) } lockOrderDetails := make([]map[string]interface{}, len(lockOrders)) @@ -153,11 +156,11 @@ func ProcessSettledOrders(ctx context.Context, network *ent.Network, orderIds [] } wg.Add(1) - go func(lo *ent.PaymentOrder, se *types.OrderSettledEvent) { + go func(lo *ent.PaymentOrder, se *types.SettleOutEvent) { defer wg.Done() // Update order status - err := UpdateOrderStatusSettled(ctx, network, se, lockOrder.MessageHash) + err := UpdateOrderStatusSettleOut(ctx, network, se, lockOrder.MessageHash) if err != nil { logger.WithFields(logger.Fields{ "Error": fmt.Sprintf("%v", err), @@ -173,6 +176,86 @@ func ProcessSettledOrders(ctx context.Context, network *ent.Network, orderIds [] return nil } +// ProcessOrderSettledOrders processes OrderSettled events (Starknet) and updates orders to settled. +// Starknet contract still uses OrderSettled; events are converted to SettleOutEvent shape and use UpdateOrderStatusSettleOut. +func ProcessOrderSettledOrders(ctx context.Context, network *ent.Network, orderIds []string, orderIdToEvent map[string]*types.OrderSettledEvent) error { + lockOrders, err := storage.Client.PaymentOrder. + Query(). + Where( + paymentorder.GatewayIDIn(orderIds...), + paymentorder.StatusEQ(paymentorder.StatusSettling), + ). + WithToken(func(tq *ent.TokenQuery) { + tq.WithNetwork() + }). + All(ctx) + if err != nil { + return fmt.Errorf("ProcessOrderSettledOrders.fetchLockOrders: %w", err) + } + + var wg sync.WaitGroup + for _, lockOrder := range lockOrders { + ev, ok := orderIdToEvent[lockOrder.GatewayID] + if !ok { + continue + } + settledEvent := &types.SettleOutEvent{ + BlockNumber: ev.BlockNumber, + TxHash: ev.TxHash, + SplitOrderId: ev.SplitOrderId, + OrderId: ev.OrderId, + LiquidityProvider: ev.LiquidityProvider, + SettlePercent: ev.SettlePercent, + RebatePercent: ev.RebatePercent, + } + wg.Add(1) + go func(lo *ent.PaymentOrder, se *types.SettleOutEvent) { + defer wg.Done() + err := UpdateOrderStatusSettleOut(ctx, network, se, lo.MessageHash) + if err != nil { + logger.WithFields(logger.Fields{ + "Error": fmt.Sprintf("%v", err), + "OrderID": se.OrderId, + "TxHash": se.TxHash, + "Network": network.Identifier, + }).Errorf("Failed to update order status when indexing OrderSettled events for %s", network.Identifier) + } + }(lockOrder, settledEvent) + } + wg.Wait() + return nil +} + +// ProcessSettleInOrders processes SettleIn (onramp) events and updates payin orders to settled. +func ProcessSettleInOrders(ctx context.Context, network *ent.Network, orderIds []string, orderIdToEvent map[string]*types.SettleInEvent) error { + if len(orderIds) == 0 { + return nil + } + + var wg sync.WaitGroup + for _, orderId := range orderIds { + event, ok := orderIdToEvent[orderId] + if !ok { + continue + } + wg.Add(1) + go func(ev *types.SettleInEvent) { + defer wg.Done() + err := UpdateOrderStatusSettleIn(ctx, network, ev) + if err != nil { + logger.WithFields(logger.Fields{ + "Error": fmt.Sprintf("%v", err), + "OrderID": ev.OrderId, + "TxHash": ev.TxHash, + "Network": network.Identifier, + }).Errorf("Failed to update payin order when indexing SettleIn events for %s", network.Identifier) + } + }(event) + } + wg.Wait() + return nil +} + // ProcessRefundedOrders processes refunded orders for a network func ProcessRefundedOrders(ctx context.Context, network *ent.Network, orderIds []string, orderIdToEvent map[string]*types.OrderRefundedEvent) error { lockOrders, err := storage.Client.PaymentOrder. @@ -255,8 +338,8 @@ func UpdateReceiveAddressStatus( } paymentOrderUpdate := tx.PaymentOrder.Update().Where(paymentorder.IDEQ(paymentOrder.ID)) - if paymentOrder.ReturnAddress == "" { - paymentOrderUpdate = paymentOrderUpdate.SetReturnAddress(event.From) + if paymentOrder.RefundOrRecipientAddress == "" { + paymentOrderUpdate = paymentOrderUpdate.SetRefundOrRecipientAddress(event.From) } if !transferMatchesOrderAmount { @@ -355,8 +438,8 @@ func UpdateReceiveAddressStatus( return false, nil } -// GetProviderAddresses gets provider addresses for a given token, network, and currency -func GetProviderAddresses(ctx context.Context, token *ent.Token, currencyCode string) ([]string, error) { +// GetProviderSettlementAddresses returns provider settlement addresses for a given token and currency. +func GetProviderSettlementAddresses(ctx context.Context, token *ent.Token, currencyCode string) ([]string, error) { providerOrderTokens, err := storage.Client.ProviderOrderToken. Query(). Where( diff --git a/services/common/order.go b/services/common/order.go index db40790a1..a798a41ad 100644 --- a/services/common/order.go +++ b/services/common/order.go @@ -41,6 +41,15 @@ var ( orderConf = config.OrderConfig() ) +// normalizeGatewayID ensures "0x" prefix and lowercases the value for case-insensitive DB comparisons. +func normalizeGatewayID(orderID string) string { + s := orderID + if !strings.HasPrefix(s, "0x") { + s = "0x" + s + } + return strings.ToLower(s) +} + // ProcessPaymentOrderFromBlockchain processes a payment order from blockchain event. // It either creates a new order or updates an existing API-created order (with messageHash but no gatewayID) // with on-chain details when the OrderCreatedEvent is indexed. @@ -51,11 +60,13 @@ func ProcessPaymentOrderFromBlockchain( refundOrder func(context.Context, *ent.Network, string) error, assignPaymentOrder func(context.Context, types.PaymentOrderFields) error, ) error { + normalizedGatewayID := normalizeGatewayID(event.OrderId) + // Check if order already exists with gatewayID (already indexed from blockchain) existingOrderWithGatewayID, err := db.Client.PaymentOrder. Query(). Where( - paymentorder.GatewayIDEQ(event.OrderId), + paymentorder.GatewayIDEQ(normalizedGatewayID), paymentorder.HasTokenWith( tokenent.HasNetworkWith( networkent.IdentifierEQ(network.Identifier), @@ -96,7 +107,7 @@ func ProcessPaymentOrderFromBlockchain( _, err = db.Client.PaymentOrder. Update(). Where(paymentorder.IDEQ(existingOrderWithMessageHash.ID)). - SetGatewayID(event.OrderId). + SetGatewayID(normalizedGatewayID). SetTxHash(event.TxHash). SetBlockNumber(int64(event.BlockNumber)). SetStatus(paymentorder.StatusPending). @@ -285,56 +296,72 @@ func UpdateOrderStatusRefunded(ctx context.Context, network *ent.Network, event return fmt.Errorf("UpdateOrderStatusRefunded.dbtransaction %v", err) } - // Attempt to update an existing log + gatewayID := normalizeGatewayID(event.OrderId) var transactionLog *ent.TransactionLog - updatedLogRows, err := tx.TransactionLog. + + // Update any existing order_refunding log (created when refund tx was submitted) by setting tx_hash. + updatedRefundingRows, err := tx.TransactionLog. Update(). Where( - transactionlog.StatusEQ(transactionlog.StatusOrderRefunded), - transactionlog.GatewayIDEQ(event.OrderId), + transactionlog.StatusEQ(transactionlog.StatusOrderRefunding), + transactionlog.GatewayIDEQ(gatewayID), transactionlog.NetworkEQ(network.Identifier), - transactionlog.TxHashEQ(event.TxHash), ). SetTxHash(event.TxHash). Save(ctx) if err != nil { _ = tx.Rollback() - return fmt.Errorf("UpdateOrderStatusRefunded.update: %v", err) + return fmt.Errorf("UpdateOrderStatusRefunded.updateRefunding: %v", err) } - // If no rows were updated, check if log already exists (race condition protection) - if updatedLogRows == 0 { - // Check if transaction log already exists to prevent duplicates - existingLog, err := tx.TransactionLog. - Query(). + // If no order_refunding log was updated, do idempotency: match or create order_refunded log for this event + if updatedRefundingRows == 0 { + // Idempotency: try to match an existing order_refunded log for this event (same gateway_id, network, tx_hash) + updatedLogRows, err := tx.TransactionLog. + Update(). Where( transactionlog.StatusEQ(transactionlog.StatusOrderRefunded), - transactionlog.GatewayIDEQ(event.OrderId), + transactionlog.GatewayIDEQ(gatewayID), transactionlog.NetworkEQ(network.Identifier), transactionlog.TxHashEQ(event.TxHash), ). - Only(ctx) + SetTxHash(event.TxHash). + Save(ctx) if err != nil { - if ent.IsNotFound(err) { - // Log doesn't exist, create new one - transactionLog, err = tx.TransactionLog. - Create(). - SetStatus(transactionlog.StatusOrderRefunded). - SetTxHash(event.TxHash). - SetGatewayID(event.OrderId). - SetNetwork(network.Identifier). - Save(ctx) - if err != nil { + _ = tx.Rollback() + return fmt.Errorf("UpdateOrderStatusRefunded.update: %v", err) + } + + if updatedLogRows == 0 { + existingLog, err := tx.TransactionLog. + Query(). + Where( + transactionlog.StatusEQ(transactionlog.StatusOrderRefunded), + transactionlog.GatewayIDEQ(gatewayID), + transactionlog.NetworkEQ(network.Identifier), + transactionlog.TxHashEQ(event.TxHash), + ). + Only(ctx) + if err != nil { + if ent.IsNotFound(err) { + transactionLog, err = tx.TransactionLog. + Create(). + SetStatus(transactionlog.StatusOrderRefunded). + SetTxHash(event.TxHash). + SetGatewayID(gatewayID). + SetNetwork(network.Identifier). + Save(ctx) + if err != nil { + _ = tx.Rollback() + return fmt.Errorf("UpdateOrderStatusRefunded.create: %v", err) + } + } else { _ = tx.Rollback() - return fmt.Errorf("UpdateOrderStatusRefunded.create: %v", err) + return fmt.Errorf("UpdateOrderStatusRefunded.query: %v", err) } } else { - _ = tx.Rollback() - return fmt.Errorf("UpdateOrderStatusRefunded.query: %v", err) + transactionLog = existingLog } - } else { - // Log already exists (created by another concurrent transaction) - transactionLog = existingLog } } @@ -342,7 +369,7 @@ func UpdateOrderStatusRefunded(ctx context.Context, network *ent.Network, event paymentOrderUpdate := tx.PaymentOrder. Update(). Where( - paymentorder.GatewayIDEQ(event.OrderId), + paymentorder.GatewayIDEQ(gatewayID), paymentorder.Or( paymentorder.StatusEQ(paymentorder.StatusRefunding), paymentorder.StatusEQ(paymentorder.StatusPending), @@ -372,7 +399,7 @@ func UpdateOrderStatusRefunded(ctx context.Context, network *ent.Network, event paymentOrder, err := tx.PaymentOrder. Query(). Where( - paymentorder.GatewayIDEQ(event.OrderId), + paymentorder.GatewayIDEQ(gatewayID), paymentorder.HasTokenWith( tokenent.HasNetworkWith( networkent.IdentifierEQ(network.Identifier), @@ -426,39 +453,43 @@ func UpdateOrderStatusRefunded(ctx context.Context, network *ent.Network, event return nil } -// UpdateOrderStatusSettled updates the status of a payment order to settled -func UpdateOrderStatusSettled(ctx context.Context, network *ent.Network, event *types.OrderSettledEvent, messageHash string) error { +// UpdateOrderStatusSettleOut updates the status of a payment order to settled (offramp / SettleOut). +func UpdateOrderStatusSettleOut(ctx context.Context, network *ent.Network, event *types.SettleOutEvent, messageHash string) error { tx, err := db.Client.Tx(ctx) if err != nil { - return fmt.Errorf("UpdateOrderStatusSettled.db: %v", err) + return fmt.Errorf("UpdateOrderStatusSettleOut.db: %v", err) } - // Attempt to update an existing log + gatewayID := normalizeGatewayID(event.OrderId) + + // Update any existing order_settling log(s) for this event (same gateway_id, network) by setting tx_hash. + // Row count is used to decide whether to create a new order_settled log below. var transactionLog *ent.TransactionLog updatedLogRows, err := tx.TransactionLog. Update(). Where( - transactionlog.StatusEQ(transactionlog.StatusOrderSettled), - transactionlog.GatewayIDEQ(event.OrderId), + transactionlog.StatusEQ(transactionlog.StatusOrderSettling), + transactionlog.GatewayIDEQ(gatewayID), transactionlog.NetworkEQ(network.Identifier), ). SetTxHash(event.TxHash). Save(ctx) if err != nil { - return fmt.Errorf("UpdateOrderStatusSettled.update: %v", err) + return fmt.Errorf("UpdateOrderStatusSettleOut.update: %v", err) } - // If no rows were updated, create a new log + // If no existing order_settling log was updated, create a new order_settled log (e.g. legacy or indexer-first path). + // When updatedLogRows > 0, order already has that log linked; no need to AddTransactions again. if updatedLogRows == 0 { transactionLog, err = tx.TransactionLog. Create(). SetStatus(transactionlog.StatusOrderSettled). SetTxHash(event.TxHash). - SetGatewayID(event.OrderId). + SetGatewayID(gatewayID). SetNetwork(network.Identifier). Save(ctx) if err != nil { - return fmt.Errorf("UpdateOrderStatusSettled.create: %v", err) + return fmt.Errorf("UpdateOrderStatusSettleOut.create: %v", err) } } @@ -468,12 +499,12 @@ func UpdateOrderStatusSettled(ctx context.Context, network *ent.Network, event * if strings.HasPrefix(network.Identifier, "starknet") { splitOrderId, err = uuid.Parse(strings.ReplaceAll(event.SplitOrderId, "0x", "")) if err != nil { - return fmt.Errorf("UpdateOrderStatusSettled.splitOrderId: %v", err) + return fmt.Errorf("UpdateOrderStatusSettleOut.splitOrderId: %v", err) } } else { splitOrderId, err = uuid.Parse(string(ethcommon.FromHex(event.SplitOrderId))) if err != nil { - return fmt.Errorf("UpdateOrderStatusSettled.splitOrderId: %v", err) + return fmt.Errorf("UpdateOrderStatusSettleOut.splitOrderId: %v", err) } } @@ -499,7 +530,7 @@ func UpdateOrderStatusSettled(ctx context.Context, network *ent.Network, event * _, err = paymentOrderUpdate.Save(ctx) if err != nil { - return fmt.Errorf("UpdateOrderStatusSettled.aggregator: %v", err) + return fmt.Errorf("UpdateOrderStatusSettleOut.aggregator: %v", err) } // Update provider balance for settled orders @@ -563,7 +594,7 @@ func UpdateOrderStatusSettled(ctx context.Context, network *ent.Network, event * // Commit the transaction if err := tx.Commit(); err != nil { - return fmt.Errorf("UpdateOrderStatusSettled.sender %v", err) + return fmt.Errorf("UpdateOrderStatusSettleOut commit failed: %w", err) } // Clean up order exclude list from Redis (best effort, don't fail if it errors) @@ -575,12 +606,114 @@ func UpdateOrderStatusSettled(ctx context.Context, network *ent.Network, event * // Send webhook notification to sender err = utils.SendPaymentOrderWebhook(ctx, paymentOrder) if err != nil { - return fmt.Errorf("UpdateOrderStatusSettled.webhook: %v", err) + return fmt.Errorf("UpdateOrderStatusSettleOut.webhook: %v", err) } return nil } +// UpdateOrderStatusSettleIn updates a payin (onramp) order to settled when a SettleIn event is indexed. +func UpdateOrderStatusSettleIn(ctx context.Context, network *ent.Network, event *types.SettleInEvent) error { + tx, err := db.Client.Tx(ctx) + if err != nil { + return fmt.Errorf("UpdateOrderStatusSettleIn.db: %v", err) + } + + gatewayID := normalizeGatewayID(event.OrderId) + + // Update any existing order_settling log(s) for this gateway order by setting tx_hash + var transactionLog *ent.TransactionLog + updatedLogRows, err := tx.TransactionLog. + Update(). + Where( + transactionlog.StatusEQ(transactionlog.StatusOrderSettling), + transactionlog.GatewayIDEQ(gatewayID), + transactionlog.NetworkEQ(network.Identifier), + ). + SetTxHash(event.TxHash). + Save(ctx) + if err != nil { + return fmt.Errorf("UpdateOrderStatusSettleIn.update: %v", err) + } + + if updatedLogRows == 0 { + transactionLog, err = tx.TransactionLog. + Create(). + SetStatus(transactionlog.StatusOrderSettled). + SetTxHash(event.TxHash). + SetGatewayID(gatewayID). + SetNetwork(network.Identifier). + Save(ctx) + if err != nil { + return fmt.Errorf("UpdateOrderStatusSettleIn.create: %v", err) + } + } + + // Find payin order by gateway ID and status settling + + paymentOrderUpdate := tx.PaymentOrder. + Update(). + Where( + paymentorder.GatewayIDEQ(gatewayID), + paymentorder.StatusEQ(paymentorder.StatusSettling), + paymentorder.DirectionEQ(paymentorder.DirectionOnramp), + paymentorder.HasTokenWith( + tokenent.HasNetworkWith( + networkent.IdentifierEQ(network.Identifier), + ), + ), + ). + SetBlockNumber(event.BlockNumber). + SetTxHash(event.TxHash). + SetStatus(paymentorder.StatusSettled) + + if transactionLog != nil { + paymentOrderUpdate = paymentOrderUpdate.AddTransactions(transactionLog) + } + + updatedOrderRows, err := paymentOrderUpdate.Save(ctx) + if err != nil { + return fmt.Errorf("UpdateOrderStatusSettleIn.aggregator: %v", err) + } + + if updatedOrderRows == 0 { + _ = tx.Rollback() + return nil // No matching payin order in settling (e.g. already settled or wrong network) + } + + if err := tx.Commit(); err != nil { + return fmt.Errorf("UpdateOrderStatusSettleIn.commit: %v", err) + } + + // Reload payment order for webhook + paymentOrder, err := db.Client.PaymentOrder. + Query(). + Where( + paymentorder.GatewayIDEQ(gatewayID), + paymentorder.HasTokenWith( + tokenent.HasNetworkWith( + networkent.IdentifierEQ(network.Identifier), + ), + ), + ). + WithSenderProfile(). + WithToken(func(tq *ent.TokenQuery) { + tq.WithNetwork() + }). + Only(ctx) + if err != nil { + return nil // Order updated; webhook best-effort + } + + if err := utils.SendPaymentOrderWebhook(ctx, paymentOrder); err != nil { + logger.WithFields(logger.Fields{ + "OrderID": gatewayID, + "Error": err.Error(), + }).Errorf("UpdateOrderStatusSettleIn.webhook: failed to send webhook") + } + return nil +} + // GetProvisionBucket gets a provision bucket for the given amount and currency. func GetProvisionBucket(ctx context.Context, amount decimal.Decimal, currency *ent.FiatCurrency) (*ent.ProvisionBucket, bool, error) { provisionBucket, err := db.Client.ProvisionBucket. @@ -1034,7 +1167,7 @@ func validateAndPreparePaymentOrderData( senderAPIKey, err := getAPIKeyFromMetadata(recipient.Metadata) if err != nil { // TODO: enforce sender API key presence in metadata in the future if needed - } else if err == nil && senderAPIKey != uuid.Nil { + } else if senderAPIKey != uuid.Nil { _, err := getSenderProfileFromAPIKey(ctx, senderAPIKey) if err != nil { logger.WithFields(logger.Fields{ @@ -1088,15 +1221,16 @@ func validateAndPreparePaymentOrderData( return nil, nil, nil, nil, nil, createBasicPaymentOrderAndCancel(ctx, event, network, token, recipient, "Provision bucket lookup failed", refundOrder, existingOrder) } - // Create payment order fields + // Create payment order fields (normalize gateway ID for case-insensitive DB storage/lookup) + // Chain-created orders from OrderCreatedEvent are offramp (SettleOut flow) paymentOrderFields := &types.PaymentOrderFields{ Token: token, Network: network, - GatewayID: event.OrderId, + GatewayID: normalizeGatewayID(event.OrderId), Amount: event.Amount, Rate: event.Rate, ProtocolFee: event.ProtocolFee, - AmountInUSD: utils.CalculatePaymentOrderAmountInUSD(event.Amount, token, institution), + AmountInUSD: utils.CalculatePaymentOrderAmountInUSD(event.Amount, token, institution, paymentorder.DirectionOfframp), BlockNumber: int64(event.BlockNumber), TxHash: event.TxHash, Institution: recipient.Institution, @@ -1127,6 +1261,7 @@ func validateAndPreparePaymentOrderData( event.Amount, paymentOrderFields.ProviderID, token.Edges.Network.Identifier, + utils.RateSideSell, // This is for offramp orders ) if rateResult.Rate == decimal.NewFromInt(1) && paymentOrderFields.Rate != decimal.NewFromInt(1) { @@ -1232,16 +1367,18 @@ func createBasicPaymentOrderAndCancel( refundOrder func(context.Context, *ent.Network, string) error, existingOrder *ent.PaymentOrder, ) error { + normalizedGatewayID := normalizeGatewayID(event.OrderId) + // Check if order already exists with gatewayID (should exist if we updated it before validation) var orderToCancel *ent.PaymentOrder - if existingOrder != nil && existingOrder.GatewayID == event.OrderId { + if existingOrder != nil && normalizeGatewayID(existingOrder.GatewayID) == normalizedGatewayID { orderToCancel = existingOrder } else { // Try to find existing order by gatewayID as fallback foundOrder, err := db.Client.PaymentOrder. Query(). Where( - paymentorder.GatewayIDEQ(event.OrderId), + paymentorder.GatewayIDEQ(normalizedGatewayID), paymentorder.HasTokenWith( tokenent.HasNetworkWith( networkent.IdentifierEQ(network.Identifier), @@ -1274,7 +1411,7 @@ func createBasicPaymentOrderAndCancel( paymentOrder := types.PaymentOrderFields{ Token: token, Network: network, - GatewayID: event.OrderId, + GatewayID: normalizedGatewayID, Amount: adjustedAmount, Rate: event.Rate, ProtocolFee: adjustedProtocolFee, @@ -1286,7 +1423,7 @@ func createBasicPaymentOrderAndCancel( if err != nil { return decimal.Zero } - return utils.CalculatePaymentOrderAmountInUSD(adjustedAmount, token, institution) + return utils.CalculatePaymentOrderAmountInUSD(adjustedAmount, token, institution, paymentorder.DirectionOfframp) }(), BlockNumber: int64(event.BlockNumber), TxHash: event.TxHash, diff --git a/services/contracts/Gateway.go b/services/contracts/Gateway.go index 706e00002..1d82802f5 100644 --- a/services/contracts/Gateway.go +++ b/services/contracts/Gateway.go @@ -53,8 +53,8 @@ type IGatewayOrder struct { // GatewayMetaData contains all meta data concerning the Gateway contract. var GatewayMetaData = &bind.MetaData{ - ABI: "[{\"inputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"constructor\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":true,\"internalType\":\"bytes32\",\"name\":\"orderId\",\"type\":\"bytes32\"},{\"indexed\":false,\"internalType\":\"uint256\",\"name\":\"senderAmount\",\"type\":\"uint256\"},{\"indexed\":false,\"internalType\":\"uint256\",\"name\":\"aggregatorAmount\",\"type\":\"uint256\"}],\"name\":\"FxTransferFeeSplit\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":false,\"internalType\":\"uint8\",\"name\":\"version\",\"type\":\"uint8\"}],\"name\":\"Initialized\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":true,\"internalType\":\"bytes32\",\"name\":\"orderId\",\"type\":\"bytes32\"},{\"indexed\":false,\"internalType\":\"uint256\",\"name\":\"senderAmount\",\"type\":\"uint256\"},{\"indexed\":false,\"internalType\":\"uint256\",\"name\":\"providerAmount\",\"type\":\"uint256\"},{\"indexed\":false,\"internalType\":\"uint256\",\"name\":\"aggregatorAmount\",\"type\":\"uint256\"}],\"name\":\"LocalTransferFeeSplit\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":true,\"internalType\":\"address\",\"name\":\"sender\",\"type\":\"address\"},{\"indexed\":true,\"internalType\":\"address\",\"name\":\"token\",\"type\":\"address\"},{\"indexed\":true,\"internalType\":\"uint256\",\"name\":\"amount\",\"type\":\"uint256\"},{\"indexed\":false,\"internalType\":\"uint256\",\"name\":\"protocolFee\",\"type\":\"uint256\"},{\"indexed\":false,\"internalType\":\"bytes32\",\"name\":\"orderId\",\"type\":\"bytes32\"},{\"indexed\":false,\"internalType\":\"uint256\",\"name\":\"rate\",\"type\":\"uint256\"},{\"indexed\":false,\"internalType\":\"string\",\"name\":\"messageHash\",\"type\":\"string\"}],\"name\":\"OrderCreated\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":false,\"internalType\":\"uint256\",\"name\":\"fee\",\"type\":\"uint256\"},{\"indexed\":true,\"internalType\":\"bytes32\",\"name\":\"orderId\",\"type\":\"bytes32\"}],\"name\":\"OrderRefunded\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":false,\"internalType\":\"bytes32\",\"name\":\"splitOrderId\",\"type\":\"bytes32\"},{\"indexed\":true,\"internalType\":\"bytes32\",\"name\":\"orderId\",\"type\":\"bytes32\"},{\"indexed\":true,\"internalType\":\"address\",\"name\":\"liquidityProvider\",\"type\":\"address\"},{\"indexed\":false,\"internalType\":\"uint64\",\"name\":\"settlePercent\",\"type\":\"uint64\"},{\"indexed\":false,\"internalType\":\"uint64\",\"name\":\"rebatePercent\",\"type\":\"uint64\"}],\"name\":\"OrderSettled\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":true,\"internalType\":\"address\",\"name\":\"previousOwner\",\"type\":\"address\"},{\"indexed\":true,\"internalType\":\"address\",\"name\":\"newOwner\",\"type\":\"address\"}],\"name\":\"OwnershipTransferStarted\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":true,\"internalType\":\"address\",\"name\":\"previousOwner\",\"type\":\"address\"},{\"indexed\":true,\"internalType\":\"address\",\"name\":\"newOwner\",\"type\":\"address\"}],\"name\":\"OwnershipTransferred\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":false,\"internalType\":\"address\",\"name\":\"account\",\"type\":\"address\"}],\"name\":\"Paused\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":true,\"internalType\":\"bytes32\",\"name\":\"what\",\"type\":\"bytes32\"},{\"indexed\":true,\"internalType\":\"address\",\"name\":\"treasuryAddress\",\"type\":\"address\"}],\"name\":\"ProtocolAddressUpdated\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":true,\"internalType\":\"address\",\"name\":\"sender\",\"type\":\"address\"},{\"indexed\":true,\"internalType\":\"uint256\",\"name\":\"amount\",\"type\":\"uint256\"}],\"name\":\"SenderFeeTransferred\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":true,\"internalType\":\"address\",\"name\":\"treasuryAddress\",\"type\":\"address\"}],\"name\":\"SetFeeRecipient\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":true,\"internalType\":\"bytes32\",\"name\":\"what\",\"type\":\"bytes32\"},{\"indexed\":true,\"internalType\":\"address\",\"name\":\"value\",\"type\":\"address\"},{\"indexed\":false,\"internalType\":\"uint256\",\"name\":\"status\",\"type\":\"uint256\"}],\"name\":\"SettingManagerBool\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":true,\"internalType\":\"address\",\"name\":\"token\",\"type\":\"address\"},{\"indexed\":false,\"internalType\":\"uint256\",\"name\":\"senderToProvider\",\"type\":\"uint256\"},{\"indexed\":false,\"internalType\":\"uint256\",\"name\":\"providerToAggregator\",\"type\":\"uint256\"},{\"indexed\":false,\"internalType\":\"uint256\",\"name\":\"senderToAggregator\",\"type\":\"uint256\"},{\"indexed\":false,\"internalType\":\"uint256\",\"name\":\"providerToAggregatorFx\",\"type\":\"uint256\"}],\"name\":\"TokenFeeSettingsUpdated\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":false,\"internalType\":\"address\",\"name\":\"account\",\"type\":\"address\"}],\"name\":\"Unpaused\",\"type\":\"event\"},{\"inputs\":[],\"name\":\"acceptOwnership\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"_token\",\"type\":\"address\"},{\"internalType\":\"uint256\",\"name\":\"_amount\",\"type\":\"uint256\"},{\"internalType\":\"uint96\",\"name\":\"_rate\",\"type\":\"uint96\"},{\"internalType\":\"address\",\"name\":\"_senderFeeRecipient\",\"type\":\"address\"},{\"internalType\":\"uint256\",\"name\":\"_senderFee\",\"type\":\"uint256\"},{\"internalType\":\"address\",\"name\":\"_refundAddress\",\"type\":\"address\"},{\"internalType\":\"string\",\"name\":\"messageHash\",\"type\":\"string\"}],\"name\":\"createOrder\",\"outputs\":[{\"internalType\":\"bytes32\",\"name\":\"orderId\",\"type\":\"bytes32\"}],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"bytes32\",\"name\":\"_orderId\",\"type\":\"bytes32\"}],\"name\":\"getOrderInfo\",\"outputs\":[{\"components\":[{\"internalType\":\"address\",\"name\":\"sender\",\"type\":\"address\"},{\"internalType\":\"address\",\"name\":\"token\",\"type\":\"address\"},{\"internalType\":\"address\",\"name\":\"senderFeeRecipient\",\"type\":\"address\"},{\"internalType\":\"uint256\",\"name\":\"senderFee\",\"type\":\"uint256\"},{\"internalType\":\"uint256\",\"name\":\"protocolFee\",\"type\":\"uint256\"},{\"internalType\":\"bool\",\"name\":\"isFulfilled\",\"type\":\"bool\"},{\"internalType\":\"bool\",\"name\":\"isRefunded\",\"type\":\"bool\"},{\"internalType\":\"address\",\"name\":\"refundAddress\",\"type\":\"address\"},{\"internalType\":\"uint96\",\"name\":\"currentBPS\",\"type\":\"uint96\"},{\"internalType\":\"uint256\",\"name\":\"amount\",\"type\":\"uint256\"}],\"internalType\":\"structIGateway.Order\",\"name\":\"\",\"type\":\"tuple\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"token\",\"type\":\"address\"}],\"name\":\"getTokenFeeSettings\",\"outputs\":[{\"components\":[{\"internalType\":\"uint256\",\"name\":\"senderToProvider\",\"type\":\"uint256\"},{\"internalType\":\"uint256\",\"name\":\"providerToAggregator\",\"type\":\"uint256\"},{\"internalType\":\"uint256\",\"name\":\"senderToAggregator\",\"type\":\"uint256\"},{\"internalType\":\"uint256\",\"name\":\"providerToAggregatorFx\",\"type\":\"uint256\"}],\"internalType\":\"structGatewaySettingManager.TokenFeeSettings\",\"name\":\"\",\"type\":\"tuple\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"initialize\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"_token\",\"type\":\"address\"}],\"name\":\"isTokenSupported\",\"outputs\":[{\"internalType\":\"bool\",\"name\":\"\",\"type\":\"bool\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"owner\",\"outputs\":[{\"internalType\":\"address\",\"name\":\"\",\"type\":\"address\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"pause\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"paused\",\"outputs\":[{\"internalType\":\"bool\",\"name\":\"\",\"type\":\"bool\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"pendingOwner\",\"outputs\":[{\"internalType\":\"address\",\"name\":\"\",\"type\":\"address\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"uint256\",\"name\":\"_fee\",\"type\":\"uint256\"},{\"internalType\":\"bytes32\",\"name\":\"_orderId\",\"type\":\"bytes32\"}],\"name\":\"refund\",\"outputs\":[{\"internalType\":\"bool\",\"name\":\"\",\"type\":\"bool\"}],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"renounceOwnership\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"token\",\"type\":\"address\"},{\"internalType\":\"uint256\",\"name\":\"senderToProvider\",\"type\":\"uint256\"},{\"internalType\":\"uint256\",\"name\":\"providerToAggregator\",\"type\":\"uint256\"},{\"internalType\":\"uint256\",\"name\":\"senderToAggregator\",\"type\":\"uint256\"},{\"internalType\":\"uint256\",\"name\":\"providerToAggregatorFx\",\"type\":\"uint256\"}],\"name\":\"setTokenFeeSettings\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"bytes32\",\"name\":\"what\",\"type\":\"bytes32\"},{\"internalType\":\"address\",\"name\":\"value\",\"type\":\"address\"},{\"internalType\":\"uint256\",\"name\":\"status\",\"type\":\"uint256\"}],\"name\":\"settingManagerBool\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"bytes32\",\"name\":\"_splitOrderId\",\"type\":\"bytes32\"},{\"internalType\":\"bytes32\",\"name\":\"_orderId\",\"type\":\"bytes32\"},{\"internalType\":\"address\",\"name\":\"_liquidityProvider\",\"type\":\"address\"},{\"internalType\":\"uint64\",\"name\":\"_settlePercent\",\"type\":\"uint64\"},{\"internalType\":\"uint64\",\"name\":\"_rebatePercent\",\"type\":\"uint64\"}],\"name\":\"settle\",\"outputs\":[{\"internalType\":\"bool\",\"name\":\"\",\"type\":\"bool\"}],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"newOwner\",\"type\":\"address\"}],\"name\":\"transferOwnership\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"unpause\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"bytes32\",\"name\":\"what\",\"type\":\"bytes32\"},{\"internalType\":\"address\",\"name\":\"value\",\"type\":\"address\"}],\"name\":\"updateProtocolAddress\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"}]", - Bin: "0x608060405234801561000f575f5ffd5b5061001e61002360201b60201c565b6101b3565b5f60019054906101000a900460ff1615610072576040517f08c379a000000000000000000000000000000000000000000000000000000000815260040161006990610161565b60405180910390fd5b60ff80165f5f9054906101000a900460ff1660ff16146100df5760ff5f5f6101000a81548160ff021916908360ff1602179055507f7f26b83ff96e1f2b6a682f133852f6798a09c465da95921460cefb384740249860ff6040516100d6919061019a565b60405180910390a15b565b5f82825260208201905092915050565b7f496e697469616c697a61626c653a20636f6e747261637420697320696e6974695f8201527f616c697a696e6700000000000000000000000000000000000000000000000000602082015250565b5f61014b6027836100e1565b9150610156826100f1565b604082019050919050565b5f6020820190508181035f8301526101788161013f565b9050919050565b5f60ff82169050919050565b6101948161017f565b82525050565b5f6020820190506101ad5f83018461018b565b92915050565b6148fd806101c05f395ff3fe608060405234801561000f575f5ffd5b5060043610610114575f3560e01c80638129fc1c116100a05780638da5cb5b1161006f5780638da5cb5b14610290578063cd992400146102ae578063df51b359146102ca578063e30c3978146102fa578063f2fde38b1461031857610114565b80638129fc1c146102305780638456cb591461023a578063898861b0146102445780638bfa05491461026057610114565b806371eedb88116100e757806371eedb881461016657806375151b6314610196578063768c6ec0146101c657806379ba5097146101f6578063809804f71461020057610114565b80633f4ba83a1461011857806340ebc677146101225780635c975abb1461013e578063715018a61461015c575b5f5ffd5b610120610334565b005b61013c6004803603810190610137919061313e565b610346565b005b61014661060a565b6040516101539190613196565b60405180910390f35b61016461061f565b005b610180600480360381019061017b91906131e2565b610632565b60405161018d9190613196565b60405180910390f35b6101b060048036038101906101ab9190613220565b610a8d565b6040516101bd9190613196565b60405180910390f35b6101e060048036038101906101db919061324b565b610ae5565b6040516101ed9190613393565b60405180910390f35b6101fe610cf5565b005b61021a60048036038101906102159190613438565b610d81565b6040516102279190613504565b60405180910390f35b610238611408565b005b610242611554565b005b61025e6004803603810190610259919061351d565b611566565b005b61027a60048036038101906102759190613220565b6117e4565b60405161028791906135e7565b60405180910390f35b610298611862565b6040516102a5919061360f565b60405180910390f35b6102c860048036038101906102c39190613628565b61188a565b005b6102e460048036038101906102df91906136b5565b611a0c565b6040516102f19190613196565b60405180910390f35b6103026120b5565b60405161030f919061360f565b60405180910390f35b610332600480360381019061032d9190613220565b6120dd565b005b61033c612189565b610344612207565b565b61034e612189565b5f73ffffffffffffffffffffffffffffffffffffffff168173ffffffffffffffffffffffffffffffffffffffff16036103bc576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004016103b390613786565b60405180910390fd5b5f7f747265617375727900000000000000000000000000000000000000000000000083036104be578173ffffffffffffffffffffffffffffffffffffffff16609860089054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1603610474576040517f08c379a000000000000000000000000000000000000000000000000000000000815260040161046b90613814565b60405180910390fd5b81609860086101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908373ffffffffffffffffffffffffffffffffffffffff160217905550600190506105ba565b7f61676772656761746f720000000000000000000000000000000000000000000083036105b9578173ffffffffffffffffffffffffffffffffffffffff1660995f9054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1603610574576040517f08c379a000000000000000000000000000000000000000000000000000000000815260040161056b906138a2565b60405180910390fd5b8160995f6101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908373ffffffffffffffffffffffffffffffffffffffff160217905550600190505b5b8015610605578173ffffffffffffffffffffffffffffffffffffffff16837fbbc5b96e57cfecb3dbeeadf92e87f15e58e64fcd75cbe256dcc5d9ef2e51e8a460405160405180910390a35b505050565b5f60cd5f9054906101000a900460ff16905090565b610627612189565b6106305f612268565b565b5f60995f9054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff163373ffffffffffffffffffffffffffffffffffffffff16146106c2576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004016106b99061390a565b60405180910390fd5b60ff5f8381526020019081526020015f206005015f9054906101000a900460ff1615610723576040517f08c379a000000000000000000000000000000000000000000000000000000000815260040161071a90613972565b60405180910390fd5b60ff5f8381526020019081526020015f2060050160019054906101000a900460ff1615610785576040517f08c379a000000000000000000000000000000000000000000000000000000000815260040161077c906139da565b60405180910390fd5b8260ff5f8481526020019081526020015f206004015410156107dc576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004016107d390613a42565b60405180910390fd5b5f8311156108b65760ff5f8381526020019081526020015f206001015f9054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1663a9059cbb609860089054906101000a900473ffffffffffffffffffffffffffffffffffffffff16856040518363ffffffff1660e01b8152600401610874929190613a6f565b6020604051808303815f875af1158015610890573d5f5f3e3d5ffd5b505050506040513d601f19601f820116820180604052508101906108b49190613ac0565b505b600160ff5f8481526020019081526020015f2060050160016101000a81548160ff0219169083151502179055505f60ff5f8481526020019081526020015f206006015f6101000a8154816bffffffffffffffffffffffff02191690836bffffffffffffffffffffffff1602179055505f8360ff5f8581526020019081526020015f20600701546109469190613b18565b905060ff5f8481526020019081526020015f206001015f9054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1663a9059cbb60ff5f8681526020019081526020015f2060050160029054906101000a900473ffffffffffffffffffffffffffffffffffffffff1660ff5f8781526020019081526020015f2060030154846109ec9190613b4b565b6040518363ffffffff1660e01b8152600401610a09929190613a6f565b6020604051808303815f875af1158015610a25573d5f5f3e3d5ffd5b505050506040513d601f19601f82011682018060405250810190610a499190613ac0565b50827f0736fe428e1747ca8d387c2e6fa1a31a0cde62d3a167c40a46ade59a3cdc828e85604051610a7a9190613b7e565b60405180910390a2600191505092915050565b5f6001609a5f8473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020019081526020015f205403610adc5760019050610ae0565b5f90505b919050565b610aed612fd2565b60ff5f8381526020019081526020015f20604051806101400160405290815f82015f9054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff168152602001600182015f9054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff168152602001600282015f9054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020016003820154815260200160048201548152602001600582015f9054906101000a900460ff161515151581526020016005820160019054906101000a900460ff161515151581526020016005820160029054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff168152602001600682015f9054906101000a90046bffffffffffffffffffffffff166bffffffffffffffffffffffff166bffffffffffffffffffffffff1681526020016007820154815250509050919050565b5f610cfe612298565b90508073ffffffffffffffffffffffffffffffffffffffff16610d1f6120b5565b73ffffffffffffffffffffffffffffffffffffffff1614610d75576040517f08c379a0000000000000000000000000000000000000000000000000000000008152600401610d6c90613c07565b60405180910390fd5b610d7e81612268565b50565b5f610d8a61229f565b610d9789898689896122e9565b5f8383905003610ddc576040517f08c379a0000000000000000000000000000000000000000000000000000000008152600401610dd390613c6f565b60405180910390fd5b8873ffffffffffffffffffffffffffffffffffffffff166323b872dd3330888c610e069190613b4b565b6040518463ffffffff1660e01b8152600401610e2493929190613c8d565b6020604051808303815f875af1158015610e40573d5f5f3e3d5ffd5b505050506040513d601f19601f82011682018060405250810190610e649190613ac0565b506101005f3373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020019081526020015f205f815480929190610eb390613cc2565b9190505550336101005f3373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020019081526020015f205446604051602001610f0b93929190613d09565b6040516020818303038152906040528051906020012090505f73ffffffffffffffffffffffffffffffffffffffff1660ff5f8381526020019081526020015f205f015f9054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1614610fc3576040517f08c379a0000000000000000000000000000000000000000000000000000000008152600401610fba90613d88565b60405180910390fd5b5f6064886bffffffffffffffffffffffff1603611024575f90505f861161101f576040517f08c379a000000000000000000000000000000000000000000000000000000000815260040161101690613df0565b60405180910390fd5b6110fd565b5f609b5f8c73ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020019081526020015f206040518060800160405290815f8201548152602001600182015481526020016002820154815260200160038201548152505090505f8160600151116110dc576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004016110d390613e58565b60405180910390fd5b60975481606001518b6110ef9190613e76565b6110f99190613ee4565b9150505b6040518061014001604052803373ffffffffffffffffffffffffffffffffffffffff1681526020018b73ffffffffffffffffffffffffffffffffffffffff1681526020018873ffffffffffffffffffffffffffffffffffffffff1681526020018781526020018281526020015f151581526020015f151581526020018673ffffffffffffffffffffffffffffffffffffffff16815260200160975467ffffffffffffffff166bffffffffffffffffffffffff1681526020018a81525060ff5f8481526020019081526020015f205f820151815f015f6101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908373ffffffffffffffffffffffffffffffffffffffff1602179055506020820151816001015f6101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908373ffffffffffffffffffffffffffffffffffffffff1602179055506040820151816002015f6101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908373ffffffffffffffffffffffffffffffffffffffff160217905550606082015181600301556080820151816004015560a0820151816005015f6101000a81548160ff02191690831515021790555060c08201518160050160016101000a81548160ff02191690831515021790555060e08201518160050160026101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908373ffffffffffffffffffffffffffffffffffffffff160217905550610100820151816006015f6101000a8154816bffffffffffffffffffffffff02191690836bffffffffffffffffffffffff160217905550610120820151816007015590505060ff5f8381526020019081526020015f20600701548a73ffffffffffffffffffffffffffffffffffffffff168673ffffffffffffffffffffffffffffffffffffffff167f40ccd1ceb111a3c186ef9911e1b876dc1f789ed331b86097b3b8851055b6a13784868d8a8a6040516113f3959493929190613f97565b60405180910390a45098975050505050505050565b5f5f60019054906101000a900460ff16159050808015611438575060015f5f9054906101000a900460ff1660ff16105b80611465575061144730612496565b158015611464575060015f5f9054906101000a900460ff1660ff16145b5b6114a4576040517f08c379a000000000000000000000000000000000000000000000000000000000815260040161149b90614053565b60405180910390fd5b60015f5f6101000a81548160ff021916908360ff16021790555080156114df5760015f60016101000a81548160ff0219169083151502179055505b620186a06097819055506114f16124b8565b6114f9612510565b8015611551575f5f60016101000a81548160ff0219169083151502179055507f7f26b83ff96e1f2b6a682f133852f6798a09c465da95921460cefb3847402498600160405161154891906140b6565b60405180910390a15b50565b61155c612189565b611564612568565b565b61156e612189565b6001609a5f8773ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020019081526020015f2054146115ee576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004016115e590614119565b60405180910390fd5b609754841115611633576040517f08c379a000000000000000000000000000000000000000000000000000000000815260040161162a906141a7565b60405180910390fd5b609754831115611678576040517f08c379a000000000000000000000000000000000000000000000000000000000815260040161166f90614235565b60405180910390fd5b6097548211156116bd576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004016116b4906142c3565b60405180910390fd5b609754811115611702576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004016116f990614351565b60405180910390fd5b604051806080016040528085815260200184815260200183815260200182815250609b5f8773ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020019081526020015f205f820151815f01556020820151816001015560408201518160020155606082015181600301559050508473ffffffffffffffffffffffffffffffffffffffff167fd4d646cffa66ebf695b792bd660c97076ed45a889e14d544eb8ab8a44b34a59c858585856040516117d5949392919061436f565b60405180910390a25050505050565b6117ec613085565b609b5f8373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020019081526020015f206040518060800160405290815f820154815260200160018201548152602001600282015481526020016003820154815250509050919050565b5f60335f9054906101000a900473ffffffffffffffffffffffffffffffffffffffff16905090565b611892612189565b5f73ffffffffffffffffffffffffffffffffffffffff168273ffffffffffffffffffffffffffffffffffffffff1603611900576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004016118f790613786565b60405180910390fd5b600181148061190f5750600281145b61194e576040517f08c379a0000000000000000000000000000000000000000000000000000000008152600401611945906143fc565b60405180910390fd5b7f746f6b656e0000000000000000000000000000000000000000000000000000008303611a075780609a5f8473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020019081526020015f20819055508173ffffffffffffffffffffffffffffffffffffffff16837fcfa976492af7c14a916cc3a239f4c9c75bbd7f5f0e398beb41d892c7eeccae4c836040516119fe9190613b7e565b60405180910390a35b505050565b5f60995f9054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff163373ffffffffffffffffffffffffffffffffffffffff1614611a9c576040517f08c379a0000000000000000000000000000000000000000000000000000000008152600401611a939061390a565b60405180910390fd5b60ff5f8681526020019081526020015f206005015f9054906101000a900460ff1615611afd576040517f08c379a0000000000000000000000000000000000000000000000000000000008152600401611af490613972565b60405180910390fd5b60ff5f8681526020019081526020015f2060050160019054906101000a900460ff1615611b5f576040517f08c379a0000000000000000000000000000000000000000000000000000000008152600401611b56906139da565b60405180910390fd5b6097548267ffffffffffffffff161115611bae576040517f08c379a0000000000000000000000000000000000000000000000000000000008152600401611ba590614464565b60405180910390fd5b5f60ff5f8781526020019081526020015f206001015f9054906101000a900473ffffffffffffffffffffffffffffffffffffffff1690505f60ff5f8881526020019081526020015f206006015f9054906101000a90046bffffffffffffffffffffffff166bffffffffffffffffffffffff1690508467ffffffffffffffff1660ff5f8981526020019081526020015f206006015f8282829054906101000a90046bffffffffffffffffffffffff16611c669190614482565b92506101000a8154816bffffffffffffffffffffffff02191690836bffffffffffffffffffffffff1602179055505f60ff5f8981526020019081526020015f206006015f9054906101000a90046bffffffffffffffffffffffff166bffffffffffffffffffffffff1603611d4857600160ff5f8981526020019081526020015f206005015f6101000a81548160ff0219169083151502179055505f60ff5f8981526020019081526020015f206003015414158015611d3857505f60ff5f8981526020019081526020015f206004015414155b15611d4757611d46876125ca565b5b5b5f60ff5f8981526020019081526020015f206003015414158015611d7f57505f60ff5f8981526020019081526020015f2060040154145b15611d9057611d8f878787612940565b5b5f818667ffffffffffffffff1660ff5f8b81526020019081526020015f2060070154611dbc9190613e76565b611dc69190613ee4565b90508060ff5f8a81526020019081526020015f206007015f828254611deb9190613b18565b925050819055505f60ff5f8a81526020019081526020015f206004015414611fd6575f609b5f60ff5f8c81526020019081526020015f206001015f9054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020019081526020015f206040518060800160405290815f8201548152602001600182015481526020016002820154815260200160038201548152505090505f609754826060015184611ec69190613e76565b611ed09190613ee4565b90508083611ede9190613b18565b92505f8767ffffffffffffffff1614611f35575f6097548867ffffffffffffffff1683611f0b9190613e76565b611f159190613ee4565b90508082611f239190613b18565b91508084611f319190613b4b565b9350505b8473ffffffffffffffffffffffffffffffffffffffff1663a9059cbb609860089054906101000a900473ffffffffffffffffffffffffffffffffffffffff16836040518363ffffffff1660e01b8152600401611f92929190613a6f565b6020604051808303815f875af1158015611fae573d5f5f3e3d5ffd5b505050506040513d601f19601f82011682018060405250810190611fd29190613ac0565b5050505b8273ffffffffffffffffffffffffffffffffffffffff1663a9059cbb88836040518363ffffffff1660e01b8152600401612011929190613a6f565b6020604051808303815f875af115801561202d573d5f5f3e3d5ffd5b505050506040513d601f19601f820116820180604052508101906120519190613ac0565b508673ffffffffffffffffffffffffffffffffffffffff16887f57c683de2e7c8263c7f57fd108416b9bdaa7a6e7f2e4e7102c3b6f9e37f1cc378b898960405161209d939291906144d0565b60405180910390a36001935050505095945050505050565b5f60655f9054906101000a900473ffffffffffffffffffffffffffffffffffffffff16905090565b6120e5612189565b8060655f6101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908373ffffffffffffffffffffffffffffffffffffffff1602179055508073ffffffffffffffffffffffffffffffffffffffff16612144611862565b73ffffffffffffffffffffffffffffffffffffffff167f38d16b8cac22d99fc7c124b9cd0de2d3fa1faef420bfe791d8c362d765e2270060405160405180910390a350565b612191612298565b73ffffffffffffffffffffffffffffffffffffffff166121af611862565b73ffffffffffffffffffffffffffffffffffffffff1614612205576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004016121fc9061454f565b60405180910390fd5b565b61220f612dfd565b5f60cd5f6101000a81548160ff0219169083151502179055507f5db9ee0a495bf2e6ff9c91a7834c1ba4fdd244a5e8aa4e537bd38aeae4b073aa612251612298565b60405161225e919061360f565b60405180910390a1565b60655f6101000a81549073ffffffffffffffffffffffffffffffffffffffff021916905561229581612e46565b50565b5f33905090565b6122a761060a565b156122e7576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004016122de906145b7565b60405180910390fd5b565b6001609a5f8773ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020019081526020015f205414612369576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004016123609061461f565b60405180910390fd5b5f84036123ab576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004016123a290614687565b60405180910390fd5b5f73ffffffffffffffffffffffffffffffffffffffff168373ffffffffffffffffffffffffffffffffffffffff1603612419576040517f08c379a0000000000000000000000000000000000000000000000000000000008152600401612410906146ef565b60405180910390fd5b5f811461248f575f73ffffffffffffffffffffffffffffffffffffffff168273ffffffffffffffffffffffffffffffffffffffff160361248e576040517f08c379a000000000000000000000000000000000000000000000000000000000815260040161248590614757565b60405180910390fd5b5b5050505050565b5f5f8273ffffffffffffffffffffffffffffffffffffffff163b119050919050565b5f60019054906101000a900460ff16612506576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004016124fd906147e5565b60405180910390fd5b61250e612f09565b565b5f60019054906101000a900460ff1661255e576040517f08c379a0000000000000000000000000000000000000000000000000000000008152600401612555906147e5565b60405180910390fd5b612566612f69565b565b61257061229f565b600160cd5f6101000a81548160ff0219169083151502179055507f62e78cea01bee320cd4e420270b5ea74000d11b0c9f74754ebdbfc544b05a2586125b3612298565b6040516125c0919061360f565b60405180910390a1565b5f609b5f60ff5f8581526020019081526020015f206001015f9054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020019081526020015f206040518060800160405290815f8201548152602001600182015481526020016002820154815260200160038201548152505090505f60ff5f8481526020019081526020015f206003015490505f609754836040015160975461269d9190613b18565b836126a89190613e76565b6126b29190613ee4565b90505f81836126c19190613b18565b90505f8211156127ae5760ff5f8681526020019081526020015f206001015f9054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1663a9059cbb60ff5f8881526020019081526020015f206002015f9054906101000a900473ffffffffffffffffffffffffffffffffffffffff16846040518363ffffffff1660e01b815260040161276c929190613a6f565b6020604051808303815f875af1158015612788573d5f5f3e3d5ffd5b505050506040513d601f19601f820116820180604052508101906127ac9190613ac0565b505b5f8111156128885760ff5f8681526020019081526020015f206001015f9054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1663a9059cbb609860089054906101000a900473ffffffffffffffffffffffffffffffffffffffff16836040518363ffffffff1660e01b8152600401612846929190613a6f565b6020604051808303815f875af1158015612862573d5f5f3e3d5ffd5b505050506040513d601f19601f820116820180604052508101906128869190613ac0565b505b8160ff5f8781526020019081526020015f206002015f9054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff167f44f6938ca4a10313aabb76f874cced61e35710a734a126e4afb34461bf8c250160405160405180910390a3847f88592047496a7850992dc5e8cd92a9b633cef0d191a4f5e87fd745c7d382630a8383604051612931929190614803565b60405180910390a25050505050565b5f609b5f60ff5f8781526020019081526020015f206001015f9054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020019081526020015f206040518060800160405290815f8201548152602001600182015481526020016002820154815260200160038201548152505090505f60ff5f8681526020019081526020015f206003015490505f609754835f015183612a109190613e76565b612a1a9190613ee4565b90505f6097548567ffffffffffffffff1683612a369190613e76565b612a409190613ee4565b90505f609754856020015183612a569190613e76565b612a609190613ee4565b90505f8385612a6f9190613b18565b90505f8114158015612ab957505f60ff5f8b81526020019081526020015f206006015f9054906101000a90046bffffffffffffffffffffffff166bffffffffffffffffffffffff16145b15612ba15760ff5f8a81526020019081526020015f206001015f9054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1663a9059cbb60ff5f8c81526020019081526020015f206002015f9054906101000a900473ffffffffffffffffffffffffffffffffffffffff16836040518363ffffffff1660e01b8152600401612b5f929190613a6f565b6020604051808303815f875af1158015612b7b573d5f5f3e3d5ffd5b505050506040513d601f19601f82011682018060405250810190612b9f9190613ac0565b505b5f8214612c7a5760ff5f8a81526020019081526020015f206001015f9054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1663a9059cbb609860089054906101000a900473ffffffffffffffffffffffffffffffffffffffff16846040518363ffffffff1660e01b8152600401612c38929190613a6f565b6020604051808303815f875af1158015612c54573d5f5f3e3d5ffd5b505050506040513d601f19601f82011682018060405250810190612c789190613ac0565b505b8183612c869190613b18565b92505f8314612d3f5760ff5f8a81526020019081526020015f206001015f9054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1663a9059cbb89856040518363ffffffff1660e01b8152600401612cfd929190613a6f565b6020604051808303815f875af1158015612d19573d5f5f3e3d5ffd5b505050506040513d601f19601f82011682018060405250810190612d3d9190613ac0565b505b8060ff5f8b81526020019081526020015f206002015f9054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff167f44f6938ca4a10313aabb76f874cced61e35710a734a126e4afb34461bf8c250160405160405180910390a3887f831c7cc0006d91462607c476603366c48469d125de6228c0791a7090efd7f7a4828585604051612dea9392919061482a565b60405180910390a2505050505050505050565b612e0561060a565b612e44576040517f08c379a0000000000000000000000000000000000000000000000000000000008152600401612e3b906148a9565b60405180910390fd5b565b5f60335f9054906101000a900473ffffffffffffffffffffffffffffffffffffffff1690508160335f6101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908373ffffffffffffffffffffffffffffffffffffffff1602179055508173ffffffffffffffffffffffffffffffffffffffff168173ffffffffffffffffffffffffffffffffffffffff167f8be0079c531659141344cd1fd0a4f28419497f9722a3daafe3b4186f6b6457e060405160405180910390a35050565b5f60019054906101000a900460ff16612f57576040517f08c379a0000000000000000000000000000000000000000000000000000000008152600401612f4e906147e5565b60405180910390fd5b612f67612f62612298565b612268565b565b5f60019054906101000a900460ff16612fb7576040517f08c379a0000000000000000000000000000000000000000000000000000000008152600401612fae906147e5565b60405180910390fd5b5f60cd5f6101000a81548160ff021916908315150217905550565b6040518061014001604052805f73ffffffffffffffffffffffffffffffffffffffff1681526020015f73ffffffffffffffffffffffffffffffffffffffff1681526020015f73ffffffffffffffffffffffffffffffffffffffff1681526020015f81526020015f81526020015f151581526020015f151581526020015f73ffffffffffffffffffffffffffffffffffffffff1681526020015f6bffffffffffffffffffffffff1681526020015f81525090565b60405180608001604052805f81526020015f81526020015f81526020015f81525090565b5f5ffd5b5f5ffd5b5f819050919050565b6130c3816130b1565b81146130cd575f5ffd5b50565b5f813590506130de816130ba565b92915050565b5f73ffffffffffffffffffffffffffffffffffffffff82169050919050565b5f61310d826130e4565b9050919050565b61311d81613103565b8114613127575f5ffd5b50565b5f8135905061313881613114565b92915050565b5f5f60408385031215613154576131536130a9565b5b5f613161858286016130d0565b92505060206131728582860161312a565b9150509250929050565b5f8115159050919050565b6131908161317c565b82525050565b5f6020820190506131a95f830184613187565b92915050565b5f819050919050565b6131c1816131af565b81146131cb575f5ffd5b50565b5f813590506131dc816131b8565b92915050565b5f5f604083850312156131f8576131f76130a9565b5b5f613205858286016131ce565b9250506020613216858286016130d0565b9150509250929050565b5f60208284031215613235576132346130a9565b5b5f6132428482850161312a565b91505092915050565b5f602082840312156132605761325f6130a9565b5b5f61326d848285016130d0565b91505092915050565b61327f81613103565b82525050565b61328e816131af565b82525050565b61329d8161317c565b82525050565b5f6bffffffffffffffffffffffff82169050919050565b6132c3816132a3565b82525050565b61014082015f8201516132de5f850182613276565b5060208201516132f16020850182613276565b5060408201516133046040850182613276565b5060608201516133176060850182613285565b50608082015161332a6080850182613285565b5060a082015161333d60a0850182613294565b5060c082015161335060c0850182613294565b5060e082015161336360e0850182613276565b506101008201516133786101008501826132ba565b5061012082015161338d610120850182613285565b50505050565b5f610140820190506133a75f8301846132c9565b92915050565b6133b6816132a3565b81146133c0575f5ffd5b50565b5f813590506133d1816133ad565b92915050565b5f5ffd5b5f5ffd5b5f5ffd5b5f5f83601f8401126133f8576133f76133d7565b5b8235905067ffffffffffffffff811115613415576134146133db565b5b602083019150836001820283011115613431576134306133df565b5b9250929050565b5f5f5f5f5f5f5f5f60e0898b031215613454576134536130a9565b5b5f6134618b828c0161312a565b98505060206134728b828c016131ce565b97505060406134838b828c016133c3565b96505060606134948b828c0161312a565b95505060806134a58b828c016131ce565b94505060a06134b68b828c0161312a565b93505060c089013567ffffffffffffffff8111156134d7576134d66130ad565b5b6134e38b828c016133e3565b92509250509295985092959890939650565b6134fe816130b1565b82525050565b5f6020820190506135175f8301846134f5565b92915050565b5f5f5f5f5f60a08688031215613536576135356130a9565b5b5f6135438882890161312a565b9550506020613554888289016131ce565b9450506040613565888289016131ce565b9350506060613576888289016131ce565b9250506080613587888289016131ce565b9150509295509295909350565b608082015f8201516135a85f850182613285565b5060208201516135bb6020850182613285565b5060408201516135ce6040850182613285565b5060608201516135e16060850182613285565b50505050565b5f6080820190506135fa5f830184613594565b92915050565b61360981613103565b82525050565b5f6020820190506136225f830184613600565b92915050565b5f5f5f6060848603121561363f5761363e6130a9565b5b5f61364c868287016130d0565b935050602061365d8682870161312a565b925050604061366e868287016131ce565b9150509250925092565b5f67ffffffffffffffff82169050919050565b61369481613678565b811461369e575f5ffd5b50565b5f813590506136af8161368b565b92915050565b5f5f5f5f5f60a086880312156136ce576136cd6130a9565b5b5f6136db888289016130d0565b95505060206136ec888289016130d0565b94505060406136fd8882890161312a565b935050606061370e888289016136a1565b925050608061371f888289016136a1565b9150509295509295909350565b5f82825260208201905092915050565b7f476174657761793a207a65726f206164647265737300000000000000000000005f82015250565b5f61377060158361372c565b915061377b8261373c565b602082019050919050565b5f6020820190508181035f83015261379d81613764565b9050919050565b7f476174657761793a207472656173757279206164647265737320616c726561645f8201527f7920736574000000000000000000000000000000000000000000000000000000602082015250565b5f6137fe60258361372c565b9150613809826137a4565b604082019050919050565b5f6020820190508181035f83015261382b816137f2565b9050919050565b7f476174657761793a2061676772656761746f72206164647265737320616c72655f8201527f6164792073657400000000000000000000000000000000000000000000000000602082015250565b5f61388c60278361372c565b915061389782613832565b604082019050919050565b5f6020820190508181035f8301526138b981613880565b9050919050565b7f4f6e6c7941676772656761746f720000000000000000000000000000000000005f82015250565b5f6138f4600e8361372c565b91506138ff826138c0565b602082019050919050565b5f6020820190508181035f830152613921816138e8565b9050919050565b7f4f7264657246756c66696c6c65640000000000000000000000000000000000005f82015250565b5f61395c600e8361372c565b915061396782613928565b602082019050919050565b5f6020820190508181035f83015261398981613950565b9050919050565b7f4f72646572526566756e646564000000000000000000000000000000000000005f82015250565b5f6139c4600d8361372c565b91506139cf82613990565b602082019050919050565b5f6020820190508181035f8301526139f1816139b8565b9050919050565b7f4665654578636565647350726f746f636f6c46656500000000000000000000005f82015250565b5f613a2c60158361372c565b9150613a37826139f8565b602082019050919050565b5f6020820190508181035f830152613a5981613a20565b9050919050565b613a69816131af565b82525050565b5f604082019050613a825f830185613600565b613a8f6020830184613a60565b9392505050565b613a9f8161317c565b8114613aa9575f5ffd5b50565b5f81519050613aba81613a96565b92915050565b5f60208284031215613ad557613ad46130a9565b5b5f613ae284828501613aac565b91505092915050565b7f4e487b71000000000000000000000000000000000000000000000000000000005f52601160045260245ffd5b5f613b22826131af565b9150613b2d836131af565b9250828203905081811115613b4557613b44613aeb565b5b92915050565b5f613b55826131af565b9150613b60836131af565b9250828201905080821115613b7857613b77613aeb565b5b92915050565b5f602082019050613b915f830184613a60565b92915050565b7f4f776e61626c6532537465703a2063616c6c6572206973206e6f7420746865205f8201527f6e6577206f776e65720000000000000000000000000000000000000000000000602082015250565b5f613bf160298361372c565b9150613bfc82613b97565b604082019050919050565b5f6020820190508181035f830152613c1e81613be5565b9050919050565b7f496e76616c69644d6573736167654861736800000000000000000000000000005f82015250565b5f613c5960128361372c565b9150613c6482613c25565b602082019050919050565b5f6020820190508181035f830152613c8681613c4d565b9050919050565b5f606082019050613ca05f830186613600565b613cad6020830185613600565b613cba6040830184613a60565b949350505050565b5f613ccc826131af565b91507fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff8203613cfe57613cfd613aeb565b5b600182019050919050565b5f606082019050613d1c5f830186613600565b613d296020830185613a60565b613d366040830184613a60565b949350505050565b7f4f72646572416c726561647945786973747300000000000000000000000000005f82015250565b5f613d7260128361372c565b9150613d7d82613d3e565b602082019050919050565b5f6020820190508181035f830152613d9f81613d66565b9050919050565b7f53656e64657246656549735a65726f00000000000000000000000000000000005f82015250565b5f613dda600f8361372c565b9150613de582613da6565b602082019050919050565b5f6020820190508181035f830152613e0781613dce565b9050919050565b7f546f6b656e46656553657474696e67734e6f74436f6e666967757265640000005f82015250565b5f613e42601d8361372c565b9150613e4d82613e0e565b602082019050919050565b5f6020820190508181035f830152613e6f81613e36565b9050919050565b5f613e80826131af565b9150613e8b836131af565b9250828202613e99816131af565b91508282048414831517613eb057613eaf613aeb565b5b5092915050565b7f4e487b71000000000000000000000000000000000000000000000000000000005f52601260045260245ffd5b5f613eee826131af565b9150613ef9836131af565b925082613f0957613f08613eb7565b5b828204905092915050565b5f819050919050565b5f613f37613f32613f2d846132a3565b613f14565b6131af565b9050919050565b613f4781613f1d565b82525050565b828183375f83830152505050565b5f601f19601f8301169050919050565b5f613f76838561372c565b9350613f83838584613f4d565b613f8c83613f5b565b840190509392505050565b5f608082019050613faa5f830188613a60565b613fb760208301876134f5565b613fc46040830186613f3e565b8181036060830152613fd7818486613f6b565b90509695505050505050565b7f496e697469616c697a61626c653a20636f6e747261637420697320616c7265615f8201527f647920696e697469616c697a6564000000000000000000000000000000000000602082015250565b5f61403d602e8361372c565b915061404882613fe3565b604082019050919050565b5f6020820190508181035f83015261406a81614031565b9050919050565b5f819050919050565b5f60ff82169050919050565b5f6140a061409b61409684614071565b613f14565b61407a565b9050919050565b6140b081614086565b82525050565b5f6020820190506140c95f8301846140a7565b92915050565b7f476174657761793a20746f6b656e206e6f7420737570706f72746564000000005f82015250565b5f614103601c8361372c565b915061410e826140cf565b602082019050919050565b5f6020820190508181035f830152614130816140f7565b9050919050565b7f476174657761793a20696e76616c69642073656e64657220746f2070726f76695f8201527f6465720000000000000000000000000000000000000000000000000000000000602082015250565b5f61419160238361372c565b915061419c82614137565b604082019050919050565b5f6020820190508181035f8301526141be81614185565b9050919050565b7f476174657761793a20696e76616c69642070726f766964657220746f206167675f8201527f72656761746f7200000000000000000000000000000000000000000000000000602082015250565b5f61421f60278361372c565b915061422a826141c5565b604082019050919050565b5f6020820190508181035f83015261424c81614213565b9050919050565b7f476174657761793a20696e76616c69642073656e64657220746f2061676772655f8201527f6761746f72000000000000000000000000000000000000000000000000000000602082015250565b5f6142ad60258361372c565b91506142b882614253565b604082019050919050565b5f6020820190508181035f8301526142da816142a1565b9050919050565b7f476174657761793a20696e76616c69642070726f766964657220746f206167675f8201527f72656761746f7220667800000000000000000000000000000000000000000000602082015250565b5f61433b602a8361372c565b9150614346826142e1565b604082019050919050565b5f6020820190508181035f8301526143688161432f565b9050919050565b5f6080820190506143825f830187613a60565b61438f6020830186613a60565b61439c6040830185613a60565b6143a96060830184613a60565b95945050505050565b7f476174657761793a20696e76616c6964207374617475730000000000000000005f82015250565b5f6143e660178361372c565b91506143f1826143b2565b602082019050919050565b5f6020820190508181035f830152614413816143da565b9050919050565b7f496e76616c696452656261746550657263656e740000000000000000000000005f82015250565b5f61444e60148361372c565b91506144598261441a565b602082019050919050565b5f6020820190508181035f83015261447b81614442565b9050919050565b5f61448c826132a3565b9150614497836132a3565b925082820390506bffffffffffffffffffffffff8111156144bb576144ba613aeb565b5b92915050565b6144ca81613678565b82525050565b5f6060820190506144e35f8301866134f5565b6144f060208301856144c1565b6144fd60408301846144c1565b949350505050565b7f4f776e61626c653a2063616c6c6572206973206e6f7420746865206f776e65725f82015250565b5f61453960208361372c565b915061454482614505565b602082019050919050565b5f6020820190508181035f8301526145668161452d565b9050919050565b7f5061757361626c653a20706175736564000000000000000000000000000000005f82015250565b5f6145a160108361372c565b91506145ac8261456d565b602082019050919050565b5f6020820190508181035f8301526145ce81614595565b9050919050565b7f546f6b656e4e6f74537570706f727465640000000000000000000000000000005f82015250565b5f61460960118361372c565b9150614614826145d5565b602082019050919050565b5f6020820190508181035f830152614636816145fd565b9050919050565b7f416d6f756e7449735a65726f00000000000000000000000000000000000000005f82015250565b5f614671600c8361372c565b915061467c8261463d565b602082019050919050565b5f6020820190508181035f83015261469e81614665565b9050919050565b7f5468726f775a65726f41646472657373000000000000000000000000000000005f82015250565b5f6146d960108361372c565b91506146e4826146a5565b602082019050919050565b5f6020820190508181035f830152614706816146cd565b9050919050565b7f496e76616c696453656e646572466565526563697069656e74000000000000005f82015250565b5f61474160198361372c565b915061474c8261470d565b602082019050919050565b5f6020820190508181035f83015261476e81614735565b9050919050565b7f496e697469616c697a61626c653a20636f6e7472616374206973206e6f7420695f8201527f6e697469616c697a696e67000000000000000000000000000000000000000000602082015250565b5f6147cf602b8361372c565b91506147da82614775565b604082019050919050565b5f6020820190508181035f8301526147fc816147c3565b9050919050565b5f6040820190506148165f830185613a60565b6148236020830184613a60565b9392505050565b5f60608201905061483d5f830186613a60565b61484a6020830185613a60565b6148576040830184613a60565b949350505050565b7f5061757361626c653a206e6f74207061757365640000000000000000000000005f82015250565b5f61489360148361372c565b915061489e8261485f565b602082019050919050565b5f6020820190508181035f8301526148c081614887565b905091905056fea264697066735822122081f60e8ee10a838980c19edadaabadc031c394028bcd90a1e9521e89722d277264736f6c634300081e0033", + ABI: "[{\"inputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"constructor\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":true,\"internalType\":\"bytes32\",\"name\":\"orderId\",\"type\":\"bytes32\"},{\"indexed\":false,\"internalType\":\"uint256\",\"name\":\"senderAmount\",\"type\":\"uint256\"},{\"indexed\":false,\"internalType\":\"uint256\",\"name\":\"aggregatorAmount\",\"type\":\"uint256\"}],\"name\":\"FxTransferFeeSplit\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":false,\"internalType\":\"uint8\",\"name\":\"version\",\"type\":\"uint8\"}],\"name\":\"Initialized\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":true,\"internalType\":\"bytes32\",\"name\":\"orderId\",\"type\":\"bytes32\"},{\"indexed\":false,\"internalType\":\"uint256\",\"name\":\"senderAmount\",\"type\":\"uint256\"},{\"indexed\":false,\"internalType\":\"uint256\",\"name\":\"providerAmount\",\"type\":\"uint256\"},{\"indexed\":false,\"internalType\":\"uint256\",\"name\":\"aggregatorAmount\",\"type\":\"uint256\"}],\"name\":\"LocalTransferFeeSplit\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":true,\"internalType\":\"address\",\"name\":\"sender\",\"type\":\"address\"},{\"indexed\":true,\"internalType\":\"address\",\"name\":\"token\",\"type\":\"address\"},{\"indexed\":true,\"internalType\":\"uint256\",\"name\":\"amount\",\"type\":\"uint256\"},{\"indexed\":false,\"internalType\":\"uint256\",\"name\":\"protocolFee\",\"type\":\"uint256\"},{\"indexed\":false,\"internalType\":\"bytes32\",\"name\":\"orderId\",\"type\":\"bytes32\"},{\"indexed\":false,\"internalType\":\"uint256\",\"name\":\"rate\",\"type\":\"uint256\"},{\"indexed\":false,\"internalType\":\"string\",\"name\":\"messageHash\",\"type\":\"string\"}],\"name\":\"OrderCreated\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":false,\"internalType\":\"uint256\",\"name\":\"fee\",\"type\":\"uint256\"},{\"indexed\":true,\"internalType\":\"bytes32\",\"name\":\"orderId\",\"type\":\"bytes32\"}],\"name\":\"OrderRefunded\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":true,\"internalType\":\"address\",\"name\":\"previousOwner\",\"type\":\"address\"},{\"indexed\":true,\"internalType\":\"address\",\"name\":\"newOwner\",\"type\":\"address\"}],\"name\":\"OwnershipTransferStarted\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":true,\"internalType\":\"address\",\"name\":\"previousOwner\",\"type\":\"address\"},{\"indexed\":true,\"internalType\":\"address\",\"name\":\"newOwner\",\"type\":\"address\"}],\"name\":\"OwnershipTransferred\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":false,\"internalType\":\"address\",\"name\":\"account\",\"type\":\"address\"}],\"name\":\"Paused\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":true,\"internalType\":\"bytes32\",\"name\":\"what\",\"type\":\"bytes32\"},{\"indexed\":true,\"internalType\":\"address\",\"name\":\"treasuryAddress\",\"type\":\"address\"}],\"name\":\"ProtocolAddressUpdated\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":true,\"internalType\":\"bytes32\",\"name\":\"orderId\",\"type\":\"bytes32\"},{\"indexed\":true,\"internalType\":\"address\",\"name\":\"sender\",\"type\":\"address\"},{\"indexed\":true,\"internalType\":\"uint256\",\"name\":\"amount\",\"type\":\"uint256\"}],\"name\":\"SenderFeeTransferred\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":true,\"internalType\":\"address\",\"name\":\"treasuryAddress\",\"type\":\"address\"}],\"name\":\"SetFeeRecipient\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":true,\"internalType\":\"bytes32\",\"name\":\"what\",\"type\":\"bytes32\"},{\"indexed\":true,\"internalType\":\"address\",\"name\":\"value\",\"type\":\"address\"},{\"indexed\":false,\"internalType\":\"uint256\",\"name\":\"status\",\"type\":\"uint256\"}],\"name\":\"SettingManagerBool\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":true,\"internalType\":\"bytes32\",\"name\":\"orderId\",\"type\":\"bytes32\"},{\"indexed\":true,\"internalType\":\"address\",\"name\":\"liquidityProvider\",\"type\":\"address\"},{\"indexed\":true,\"internalType\":\"address\",\"name\":\"recipient\",\"type\":\"address\"},{\"indexed\":false,\"internalType\":\"uint256\",\"name\":\"amount\",\"type\":\"uint256\"},{\"indexed\":false,\"internalType\":\"address\",\"name\":\"token\",\"type\":\"address\"},{\"indexed\":false,\"internalType\":\"uint256\",\"name\":\"aggregatorFee\",\"type\":\"uint256\"},{\"indexed\":false,\"internalType\":\"uint96\",\"name\":\"rate\",\"type\":\"uint96\"}],\"name\":\"SettleIn\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":false,\"internalType\":\"bytes32\",\"name\":\"splitOrderId\",\"type\":\"bytes32\"},{\"indexed\":true,\"internalType\":\"bytes32\",\"name\":\"orderId\",\"type\":\"bytes32\"},{\"indexed\":true,\"internalType\":\"address\",\"name\":\"liquidityProvider\",\"type\":\"address\"},{\"indexed\":false,\"internalType\":\"uint64\",\"name\":\"settlePercent\",\"type\":\"uint64\"},{\"indexed\":false,\"internalType\":\"uint64\",\"name\":\"rebatePercent\",\"type\":\"uint64\"}],\"name\":\"SettleOut\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":true,\"internalType\":\"address\",\"name\":\"token\",\"type\":\"address\"},{\"indexed\":false,\"internalType\":\"uint256\",\"name\":\"senderToProvider\",\"type\":\"uint256\"},{\"indexed\":false,\"internalType\":\"uint256\",\"name\":\"providerToAggregator\",\"type\":\"uint256\"},{\"indexed\":false,\"internalType\":\"uint256\",\"name\":\"senderToAggregator\",\"type\":\"uint256\"},{\"indexed\":false,\"internalType\":\"uint256\",\"name\":\"providerToAggregatorFx\",\"type\":\"uint256\"}],\"name\":\"TokenFeeSettingsUpdated\",\"type\":\"event\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":false,\"internalType\":\"address\",\"name\":\"account\",\"type\":\"address\"}],\"name\":\"Unpaused\",\"type\":\"event\"},{\"inputs\":[],\"name\":\"acceptOwnership\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"_token\",\"type\":\"address\"},{\"internalType\":\"uint256\",\"name\":\"_amount\",\"type\":\"uint256\"},{\"internalType\":\"uint96\",\"name\":\"_rate\",\"type\":\"uint96\"},{\"internalType\":\"address\",\"name\":\"_senderFeeRecipient\",\"type\":\"address\"},{\"internalType\":\"uint256\",\"name\":\"_senderFee\",\"type\":\"uint256\"},{\"internalType\":\"address\",\"name\":\"_refundAddress\",\"type\":\"address\"},{\"internalType\":\"string\",\"name\":\"messageHash\",\"type\":\"string\"}],\"name\":\"createOrder\",\"outputs\":[{\"internalType\":\"bytes32\",\"name\":\"orderId\",\"type\":\"bytes32\"}],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"getAggregator\",\"outputs\":[{\"internalType\":\"address\",\"name\":\"\",\"type\":\"address\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"bytes32\",\"name\":\"_orderId\",\"type\":\"bytes32\"}],\"name\":\"getOrderInfo\",\"outputs\":[{\"components\":[{\"internalType\":\"address\",\"name\":\"sender\",\"type\":\"address\"},{\"internalType\":\"address\",\"name\":\"token\",\"type\":\"address\"},{\"internalType\":\"address\",\"name\":\"senderFeeRecipient\",\"type\":\"address\"},{\"internalType\":\"uint256\",\"name\":\"senderFee\",\"type\":\"uint256\"},{\"internalType\":\"uint256\",\"name\":\"protocolFee\",\"type\":\"uint256\"},{\"internalType\":\"bool\",\"name\":\"isFulfilled\",\"type\":\"bool\"},{\"internalType\":\"bool\",\"name\":\"isRefunded\",\"type\":\"bool\"},{\"internalType\":\"address\",\"name\":\"refundAddress\",\"type\":\"address\"},{\"internalType\":\"uint96\",\"name\":\"currentBPS\",\"type\":\"uint96\"},{\"internalType\":\"uint256\",\"name\":\"amount\",\"type\":\"uint256\"}],\"internalType\":\"structIGateway.Order\",\"name\":\"\",\"type\":\"tuple\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"token\",\"type\":\"address\"}],\"name\":\"getTokenFeeSettings\",\"outputs\":[{\"components\":[{\"internalType\":\"uint256\",\"name\":\"senderToProvider\",\"type\":\"uint256\"},{\"internalType\":\"uint256\",\"name\":\"providerToAggregator\",\"type\":\"uint256\"},{\"internalType\":\"uint256\",\"name\":\"senderToAggregator\",\"type\":\"uint256\"},{\"internalType\":\"uint256\",\"name\":\"providerToAggregatorFx\",\"type\":\"uint256\"}],\"internalType\":\"structGatewaySettingManager.TokenFeeSettings\",\"name\":\"\",\"type\":\"tuple\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"initialize\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"_token\",\"type\":\"address\"}],\"name\":\"isTokenSupported\",\"outputs\":[{\"internalType\":\"bool\",\"name\":\"\",\"type\":\"bool\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"owner\",\"outputs\":[{\"internalType\":\"address\",\"name\":\"\",\"type\":\"address\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"pause\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"paused\",\"outputs\":[{\"internalType\":\"bool\",\"name\":\"\",\"type\":\"bool\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"pendingOwner\",\"outputs\":[{\"internalType\":\"address\",\"name\":\"\",\"type\":\"address\"}],\"stateMutability\":\"view\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"uint256\",\"name\":\"_fee\",\"type\":\"uint256\"},{\"internalType\":\"bytes32\",\"name\":\"_orderId\",\"type\":\"bytes32\"}],\"name\":\"refund\",\"outputs\":[{\"internalType\":\"bool\",\"name\":\"\",\"type\":\"bool\"}],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"renounceOwnership\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"token\",\"type\":\"address\"},{\"internalType\":\"uint256\",\"name\":\"senderToProvider\",\"type\":\"uint256\"},{\"internalType\":\"uint256\",\"name\":\"providerToAggregator\",\"type\":\"uint256\"},{\"internalType\":\"uint256\",\"name\":\"senderToAggregator\",\"type\":\"uint256\"},{\"internalType\":\"uint256\",\"name\":\"providerToAggregatorFx\",\"type\":\"uint256\"}],\"name\":\"setTokenFeeSettings\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"bytes32\",\"name\":\"what\",\"type\":\"bytes32\"},{\"internalType\":\"address\",\"name\":\"value\",\"type\":\"address\"},{\"internalType\":\"uint256\",\"name\":\"status\",\"type\":\"uint256\"}],\"name\":\"settingManagerBool\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"bytes32\",\"name\":\"_orderId\",\"type\":\"bytes32\"},{\"internalType\":\"address\",\"name\":\"_token\",\"type\":\"address\"},{\"internalType\":\"uint256\",\"name\":\"_amount\",\"type\":\"uint256\"},{\"internalType\":\"address\",\"name\":\"_senderFeeRecipient\",\"type\":\"address\"},{\"internalType\":\"uint96\",\"name\":\"_senderFee\",\"type\":\"uint96\"},{\"internalType\":\"address\",\"name\":\"_recipient\",\"type\":\"address\"},{\"internalType\":\"uint96\",\"name\":\"_rate\",\"type\":\"uint96\"}],\"name\":\"settleIn\",\"outputs\":[{\"internalType\":\"bool\",\"name\":\"\",\"type\":\"bool\"}],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"bytes32\",\"name\":\"_splitOrderId\",\"type\":\"bytes32\"},{\"internalType\":\"bytes32\",\"name\":\"_orderId\",\"type\":\"bytes32\"},{\"internalType\":\"address\",\"name\":\"_liquidityProvider\",\"type\":\"address\"},{\"internalType\":\"uint64\",\"name\":\"_settlePercent\",\"type\":\"uint64\"},{\"internalType\":\"uint64\",\"name\":\"_rebatePercent\",\"type\":\"uint64\"}],\"name\":\"settleOut\",\"outputs\":[{\"internalType\":\"bool\",\"name\":\"\",\"type\":\"bool\"}],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address\",\"name\":\"newOwner\",\"type\":\"address\"}],\"name\":\"transferOwnership\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[],\"name\":\"unpause\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"bytes32\",\"name\":\"what\",\"type\":\"bytes32\"},{\"internalType\":\"address\",\"name\":\"value\",\"type\":\"address\"}],\"name\":\"updateProtocolAddress\",\"outputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"function\"}]", + Bin: "0x608060405234801561000f575f5ffd5b5061001e61002360201b60201c565b6101b3565b5f60019054906101000a900460ff1615610072576040517f08c379a000000000000000000000000000000000000000000000000000000000815260040161006990610161565b60405180910390fd5b60ff80165f5f9054906101000a900460ff1660ff16146100df5760ff5f5f6101000a81548160ff021916908360ff1602179055507f7f26b83ff96e1f2b6a682f133852f6798a09c465da95921460cefb384740249860ff6040516100d6919061019a565b60405180910390a15b565b5f82825260208201905092915050565b7f496e697469616c697a61626c653a20636f6e747261637420697320696e6974695f8201527f616c697a696e6700000000000000000000000000000000000000000000000000602082015250565b5f61014b6027836100e1565b9150610156826100f1565b604082019050919050565b5f6020820190508181035f8301526101788161013f565b9050919050565b5f60ff82169050919050565b6101948161017f565b82525050565b5f6020820190506101ad5f83018461018b565b92915050565b6150aa806101c05f395ff3fe608060405234801561000f575f5ffd5b506004361061012a575f3560e01c8063809804f7116100ab5780638da5cb5b1161006f5780638da5cb5b146102f4578063cd99240014610312578063d839de631461032e578063e30c39781461035e578063f2fde38b1461037c5761012a565b8063809804f7146102645780638129fc1c146102945780638456cb591461029e578063898861b0146102a85780638bfa0549146102c45761012a565b8063715018a6116100f2578063715018a6146101c057806371eedb88146101ca57806375151b63146101fa578063768c6ec01461022a57806379ba50971461025a5761012a565b806332553efa1461012e5780633ad59dbc1461015e5780633f4ba83a1461017c57806340ebc677146101865780635c975abb146101a2575b5f5ffd5b61014860048036038101906101439190613769565b610398565b60405161015591906137fa565b60405180910390f35b610166610b21565b6040516101739190613822565b60405180910390f35b610184610b49565b005b6101a0600480360381019061019b919061383b565b610b5b565b005b6101aa610e1f565b6040516101b791906137fa565b60405180910390f35b6101c8610e34565b005b6101e460048036038101906101df91906138ac565b610e47565b6040516101f191906137fa565b60405180910390f35b610214600480360381019061020f91906138ea565b6112a2565b60405161022191906137fa565b60405180910390f35b610244600480360381019061023f9190613915565b6112fa565b6040516102519190613a5d565b60405180910390f35b61026261150a565b005b61027e60048036038101906102799190613b02565b611596565b60405161028b9190613bce565b60405180910390f35b61029c611c1d565b005b6102a6611d69565b005b6102c260048036038101906102bd9190613be7565b611d7b565b005b6102de60048036038101906102d991906138ea565b611ff9565b6040516102eb9190613cb1565b60405180910390f35b6102fc612077565b6040516103099190613822565b60405180910390f35b61032c60048036038101906103279190613cca565b61209f565b005b61034860048036038101906103439190613d1a565b612221565b60405161035591906137fa565b60405180910390f35b61036661287c565b6040516103739190613822565b60405180910390f35b610396600480360381019061039191906138ea565b6128a4565b005b5f60995f9054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff163373ffffffffffffffffffffffffffffffffffffffff1614610428576040517f08c379a000000000000000000000000000000000000000000000000000000000815260040161041f90613e11565b60405180910390fd5b60ff5f8681526020019081526020015f206005015f9054906101000a900460ff1615610489576040517f08c379a000000000000000000000000000000000000000000000000000000000815260040161048090613e79565b60405180910390fd5b60ff5f8681526020019081526020015f2060050160019054906101000a900460ff16156104eb576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004016104e290613ee1565b60405180910390fd5b6097548267ffffffffffffffff16111561053a576040517f08c379a000000000000000000000000000000000000000000000000000000000815260040161053190613f49565b60405180910390fd5b5f60ff5f8781526020019081526020015f206001015f9054906101000a900473ffffffffffffffffffffffffffffffffffffffff1690505f60ff5f8881526020019081526020015f206006015f9054906101000a90046bffffffffffffffffffffffff166bffffffffffffffffffffffff1690505f8567ffffffffffffffff161180156105d15750808567ffffffffffffffff1611155b610610576040517f08c379a000000000000000000000000000000000000000000000000000000000815260040161060790613fb1565b60405180910390fd5b8467ffffffffffffffff1660ff5f8981526020019081526020015f206006015f8282829054906101000a90046bffffffffffffffffffffffff166106549190613ffc565b92506101000a8154816bffffffffffffffffffffffff02191690836bffffffffffffffffffffffff1602179055505f60ff5f8981526020019081526020015f206006015f9054906101000a90046bffffffffffffffffffffffff166bffffffffffffffffffffffff160361078057600160ff5f8981526020019081526020015f206005015f6101000a81548160ff0219169083151502179055505f60ff5f8981526020019081526020015f20600301541415801561072657505f60ff5f8981526020019081526020015f206004015414155b1561077f5761077e878360ff5f8b81526020019081526020015f206002015f9054906101000a900473ffffffffffffffffffffffffffffffffffffffff1660ff5f8c81526020019081526020015f2060030154612950565b5b5b5f60ff5f8981526020019081526020015f2060030154141580156107b757505f60ff5f8981526020019081526020015f2060040154145b156107fc576107fb878760ff5f8b81526020019081526020015f206002015f9054906101000a900473ffffffffffffffffffffffffffffffffffffffff1688612bb2565b5b5f818667ffffffffffffffff1660ff5f8b81526020019081526020015f2060070154610828919061403b565b61083291906140a9565b90508060ff5f8a81526020019081526020015f206007015f82825461085791906140d9565b925050819055505f60ff5f8a81526020019081526020015f206004015414610a42575f609b5f60ff5f8c81526020019081526020015f206001015f9054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020019081526020015f206040518060800160405290815f8201548152602001600182015481526020016002820154815260200160038201548152505090505f609754826060015184610932919061403b565b61093c91906140a9565b9050808361094a91906140d9565b92505f8767ffffffffffffffff16146109a1575f6097548867ffffffffffffffff1683610977919061403b565b61098191906140a9565b9050808261098f91906140d9565b9150808461099d919061410c565b9350505b8473ffffffffffffffffffffffffffffffffffffffff1663a9059cbb609860089054906101000a900473ffffffffffffffffffffffffffffffffffffffff16836040518363ffffffff1660e01b81526004016109fe92919061414e565b6020604051808303815f875af1158015610a1a573d5f5f3e3d5ffd5b505050506040513d601f19601f82011682018060405250810190610a3e919061419f565b5050505b8273ffffffffffffffffffffffffffffffffffffffff1663a9059cbb88836040518363ffffffff1660e01b8152600401610a7d92919061414e565b6020604051808303815f875af1158015610a99573d5f5f3e3d5ffd5b505050506040513d601f19601f82011682018060405250810190610abd919061419f565b508673ffffffffffffffffffffffffffffffffffffffff16887f1e4a1a8ad772d3f0dbb387879bc5e8faadf16e0513bf77d50620741ab92b4c458b8989604051610b09939291906141d9565b60405180910390a36001935050505095945050505050565b5f60995f9054906101000a900473ffffffffffffffffffffffffffffffffffffffff16905090565b610b51612faa565b610b59613028565b565b610b63612faa565b5f73ffffffffffffffffffffffffffffffffffffffff168173ffffffffffffffffffffffffffffffffffffffff1603610bd1576040517f08c379a0000000000000000000000000000000000000000000000000000000008152600401610bc890614258565b60405180910390fd5b5f7f74726561737572790000000000000000000000000000000000000000000000008303610cd3578173ffffffffffffffffffffffffffffffffffffffff16609860089054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1603610c89576040517f08c379a0000000000000000000000000000000000000000000000000000000008152600401610c80906142e6565b60405180910390fd5b81609860086101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908373ffffffffffffffffffffffffffffffffffffffff16021790555060019050610dcf565b7f61676772656761746f72000000000000000000000000000000000000000000008303610dce578173ffffffffffffffffffffffffffffffffffffffff1660995f9054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1603610d89576040517f08c379a0000000000000000000000000000000000000000000000000000000008152600401610d8090614374565b60405180910390fd5b8160995f6101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908373ffffffffffffffffffffffffffffffffffffffff160217905550600190505b5b8015610e1a578173ffffffffffffffffffffffffffffffffffffffff16837fbbc5b96e57cfecb3dbeeadf92e87f15e58e64fcd75cbe256dcc5d9ef2e51e8a460405160405180910390a35b505050565b5f60cd5f9054906101000a900460ff16905090565b610e3c612faa565b610e455f613089565b565b5f60995f9054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff163373ffffffffffffffffffffffffffffffffffffffff1614610ed7576040517f08c379a0000000000000000000000000000000000000000000000000000000008152600401610ece90613e11565b60405180910390fd5b60ff5f8381526020019081526020015f206005015f9054906101000a900460ff1615610f38576040517f08c379a0000000000000000000000000000000000000000000000000000000008152600401610f2f90613e79565b60405180910390fd5b60ff5f8381526020019081526020015f2060050160019054906101000a900460ff1615610f9a576040517f08c379a0000000000000000000000000000000000000000000000000000000008152600401610f9190613ee1565b60405180910390fd5b8260ff5f8481526020019081526020015f20600401541015610ff1576040517f08c379a0000000000000000000000000000000000000000000000000000000008152600401610fe8906143dc565b60405180910390fd5b5f8311156110cb5760ff5f8381526020019081526020015f206001015f9054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1663a9059cbb609860089054906101000a900473ffffffffffffffffffffffffffffffffffffffff16856040518363ffffffff1660e01b815260040161108992919061414e565b6020604051808303815f875af11580156110a5573d5f5f3e3d5ffd5b505050506040513d601f19601f820116820180604052508101906110c9919061419f565b505b600160ff5f8481526020019081526020015f2060050160016101000a81548160ff0219169083151502179055505f60ff5f8481526020019081526020015f206006015f6101000a8154816bffffffffffffffffffffffff02191690836bffffffffffffffffffffffff1602179055505f8360ff5f8581526020019081526020015f206007015461115b91906140d9565b905060ff5f8481526020019081526020015f206001015f9054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1663a9059cbb60ff5f8681526020019081526020015f2060050160029054906101000a900473ffffffffffffffffffffffffffffffffffffffff1660ff5f8781526020019081526020015f206003015484611201919061410c565b6040518363ffffffff1660e01b815260040161121e92919061414e565b6020604051808303815f875af115801561123a573d5f5f3e3d5ffd5b505050506040513d601f19601f8201168201806040525081019061125e919061419f565b50827f0736fe428e1747ca8d387c2e6fa1a31a0cde62d3a167c40a46ade59a3cdc828e8560405161128f91906143fa565b60405180910390a2600191505092915050565b5f6001609a5f8473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020019081526020015f2054036112f157600190506112f5565b5f90505b919050565b6113026135c0565b60ff5f8381526020019081526020015f20604051806101400160405290815f82015f9054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff168152602001600182015f9054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff168152602001600282015f9054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020016003820154815260200160048201548152602001600582015f9054906101000a900460ff161515151581526020016005820160019054906101000a900460ff161515151581526020016005820160029054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff168152602001600682015f9054906101000a90046bffffffffffffffffffffffff166bffffffffffffffffffffffff166bffffffffffffffffffffffff1681526020016007820154815250509050919050565b5f6115136130b9565b90508073ffffffffffffffffffffffffffffffffffffffff1661153461287c565b73ffffffffffffffffffffffffffffffffffffffff161461158a576040517f08c379a000000000000000000000000000000000000000000000000000000000815260040161158190614483565b60405180910390fd5b61159381613089565b50565b5f61159f6130c0565b6115ac898986898961310a565b5f83839050036115f1576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004016115e8906144eb565b60405180910390fd5b8873ffffffffffffffffffffffffffffffffffffffff166323b872dd3330888c61161b919061410c565b6040518463ffffffff1660e01b815260040161163993929190614509565b6020604051808303815f875af1158015611655573d5f5f3e3d5ffd5b505050506040513d601f19601f82011682018060405250810190611679919061419f565b506101005f3373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020019081526020015f205f8154809291906116c89061453e565b9190505550336101005f3373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020019081526020015f20544660405160200161172093929190614585565b6040516020818303038152906040528051906020012090505f73ffffffffffffffffffffffffffffffffffffffff1660ff5f8381526020019081526020015f205f015f9054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16146117d8576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004016117cf90614604565b60405180910390fd5b5f6064886bffffffffffffffffffffffff1603611839575f90505f8611611834576040517f08c379a000000000000000000000000000000000000000000000000000000000815260040161182b9061466c565b60405180910390fd5b611912565b5f609b5f8c73ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020019081526020015f206040518060800160405290815f8201548152602001600182015481526020016002820154815260200160038201548152505090505f8160600151116118f1576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004016118e8906146d4565b60405180910390fd5b60975481606001518b611904919061403b565b61190e91906140a9565b9150505b6040518061014001604052803373ffffffffffffffffffffffffffffffffffffffff1681526020018b73ffffffffffffffffffffffffffffffffffffffff1681526020018873ffffffffffffffffffffffffffffffffffffffff1681526020018781526020018281526020015f151581526020015f151581526020018673ffffffffffffffffffffffffffffffffffffffff16815260200160975467ffffffffffffffff166bffffffffffffffffffffffff1681526020018a81525060ff5f8481526020019081526020015f205f820151815f015f6101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908373ffffffffffffffffffffffffffffffffffffffff1602179055506020820151816001015f6101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908373ffffffffffffffffffffffffffffffffffffffff1602179055506040820151816002015f6101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908373ffffffffffffffffffffffffffffffffffffffff160217905550606082015181600301556080820151816004015560a0820151816005015f6101000a81548160ff02191690831515021790555060c08201518160050160016101000a81548160ff02191690831515021790555060e08201518160050160026101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908373ffffffffffffffffffffffffffffffffffffffff160217905550610100820151816006015f6101000a8154816bffffffffffffffffffffffff02191690836bffffffffffffffffffffffff160217905550610120820151816007015590505060ff5f8381526020019081526020015f20600701548a73ffffffffffffffffffffffffffffffffffffffff168673ffffffffffffffffffffffffffffffffffffffff167f40ccd1ceb111a3c186ef9911e1b876dc1f789ed331b86097b3b8851055b6a13784868d8a8a604051611c08959493929190614775565b60405180910390a45098975050505050505050565b5f5f60019054906101000a900460ff16159050808015611c4d575060015f5f9054906101000a900460ff1660ff16105b80611c7a5750611c5c306132b7565b158015611c79575060015f5f9054906101000a900460ff1660ff16145b5b611cb9576040517f08c379a0000000000000000000000000000000000000000000000000000000008152600401611cb090614831565b60405180910390fd5b60015f5f6101000a81548160ff021916908360ff1602179055508015611cf45760015f60016101000a81548160ff0219169083151502179055505b620186a0609781905550611d066132d9565b611d0e613331565b8015611d66575f5f60016101000a81548160ff0219169083151502179055507f7f26b83ff96e1f2b6a682f133852f6798a09c465da95921460cefb38474024986001604051611d5d9190614894565b60405180910390a15b50565b611d71612faa565b611d79613389565b565b611d83612faa565b6001609a5f8773ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020019081526020015f205414611e03576040517f08c379a0000000000000000000000000000000000000000000000000000000008152600401611dfa906148f7565b60405180910390fd5b609754841115611e48576040517f08c379a0000000000000000000000000000000000000000000000000000000008152600401611e3f90614985565b60405180910390fd5b609754831115611e8d576040517f08c379a0000000000000000000000000000000000000000000000000000000008152600401611e8490614a13565b60405180910390fd5b609754821115611ed2576040517f08c379a0000000000000000000000000000000000000000000000000000000008152600401611ec990614aa1565b60405180910390fd5b609754811115611f17576040517f08c379a0000000000000000000000000000000000000000000000000000000008152600401611f0e90614b2f565b60405180910390fd5b604051806080016040528085815260200184815260200183815260200182815250609b5f8773ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020019081526020015f205f820151815f01556020820151816001015560408201518160020155606082015181600301559050508473ffffffffffffffffffffffffffffffffffffffff167fd4d646cffa66ebf695b792bd660c97076ed45a889e14d544eb8ab8a44b34a59c85858585604051611fea9493929190614b4d565b60405180910390a25050505050565b612001613673565b609b5f8373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020019081526020015f206040518060800160405290815f820154815260200160018201548152602001600282015481526020016003820154815250509050919050565b5f60335f9054906101000a900473ffffffffffffffffffffffffffffffffffffffff16905090565b6120a7612faa565b5f73ffffffffffffffffffffffffffffffffffffffff168273ffffffffffffffffffffffffffffffffffffffff1603612115576040517f08c379a000000000000000000000000000000000000000000000000000000000815260040161210c90614258565b60405180910390fd5b60018114806121245750600281145b612163576040517f08c379a000000000000000000000000000000000000000000000000000000000815260040161215a90614bda565b60405180910390fd5b7f746f6b656e000000000000000000000000000000000000000000000000000000830361221c5780609a5f8473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020019081526020015f20819055508173ffffffffffffffffffffffffffffffffffffffff16837fcfa976492af7c14a916cc3a239f4c9c75bbd7f5f0e398beb41d892c7eeccae4c8360405161221391906143fa565b60405180910390a35b505050565b5f61222a6130c0565b5f73ffffffffffffffffffffffffffffffffffffffff1660ff5f8a81526020019081526020015f205f015f9054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16146122ca576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004016122c190614604565b60405180910390fd5b609754861161230e576040517f08c379a000000000000000000000000000000000000000000000000000000000815260040161230590614c42565b60405180910390fd5b61232987878588886bffffffffffffffffffffffff1661310a565b8673ffffffffffffffffffffffffffffffffffffffff166323b872dd3330876bffffffffffffffffffffffff168a612361919061410c565b6040518463ffffffff1660e01b815260040161237f93929190614509565b6020604051808303815f875af115801561239b573d5f5f3e3d5ffd5b505050506040513d601f19601f820116820180604052508101906123bf919061419f565b505f8690505f6064846bffffffffffffffffffffffff1603612430575f866bffffffffffffffffffffffff161161242b576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004016124229061466c565b60405180910390fd5b6125be565b5f609b5f8b73ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020019081526020015f206040518060800160405290815f8201548152602001600182015481526020016002820154815260200160038201548152505090505f8160600151116124e8576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004016124df906146d4565b60405180910390fd5b60975481606001518a6124fb919061403b565b61250591906140a9565b91505f8211156125bc57818361251b91906140d9565b92508973ffffffffffffffffffffffffffffffffffffffff1663a9059cbb609860089054906101000a900473ffffffffffffffffffffffffffffffffffffffff16846040518363ffffffff1660e01b815260040161257a92919061414e565b6020604051808303815f875af1158015612596573d5f5f3e3d5ffd5b505050506040513d601f19601f820116820180604052508101906125ba919061419f565b505b505b8460ff5f8c81526020019081526020015f205f015f6101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908373ffffffffffffffffffffffffffffffffffffffff1602179055508860ff5f8c81526020019081526020015f206001015f6101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908373ffffffffffffffffffffffffffffffffffffffff1602179055508660ff5f8c81526020019081526020015f206002015f6101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908373ffffffffffffffffffffffffffffffffffffffff160217905550856bffffffffffffffffffffffff1660ff5f8c81526020019081526020015f20600301819055508060ff5f8c81526020019081526020015f2060040181905550600160ff5f8c81526020019081526020015f206005015f6101000a81548160ff0219169083151502179055508160ff5f8c81526020019081526020015f20600701819055508873ffffffffffffffffffffffffffffffffffffffff1663a9059cbb86846040518363ffffffff1660e01b815260040161277392919061414e565b6020604051808303815f875af115801561278f573d5f5f3e3d5ffd5b505050506040513d601f19601f820116820180604052508101906127b3919061419f565b505f866bffffffffffffffffffffffff16146127ff575f81036127e3576127de8a3389609754612bb2565b6127fe565b6127fd8a8a89896bffffffffffffffffffffffff16612950565b5b5b8473ffffffffffffffffffffffffffffffffffffffff163373ffffffffffffffffffffffffffffffffffffffff168b7fb5273ccce1412b056c9246e834895f9d717974c505f8e5a6c7d08cd0300a066b858d868a6040516128639493929190614c6f565b60405180910390a4600192505050979650505050505050565b5f60655f9054906101000a900473ffffffffffffffffffffffffffffffffffffffff16905090565b6128ac612faa565b8060655f6101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908373ffffffffffffffffffffffffffffffffffffffff1602179055508073ffffffffffffffffffffffffffffffffffffffff1661290b612077565b73ffffffffffffffffffffffffffffffffffffffff167f38d16b8cac22d99fc7c124b9cd0de2d3fa1faef420bfe791d8c362d765e2270060405160405180910390a350565b5f609b5f8573ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020019081526020015f206040518060800160405290815f8201548152602001600182015481526020016002820154815260200160038201548152505090505f60975482604001516097546129d891906140d9565b846129e3919061403b565b6129ed91906140a9565b90505f81846129fc91906140d9565b90505f821115612a83578573ffffffffffffffffffffffffffffffffffffffff1663a9059cbb86846040518363ffffffff1660e01b8152600401612a4192919061414e565b6020604051808303815f875af1158015612a5d573d5f5f3e3d5ffd5b505050506040513d601f19601f82011682018060405250810190612a81919061419f565b505b5f811115612b2a578573ffffffffffffffffffffffffffffffffffffffff1663a9059cbb609860089054906101000a900473ffffffffffffffffffffffffffffffffffffffff16836040518363ffffffff1660e01b8152600401612ae892919061414e565b6020604051808303815f875af1158015612b04573d5f5f3e3d5ffd5b505050506040513d601f19601f82011682018060405250810190612b28919061419f565b505b818573ffffffffffffffffffffffffffffffffffffffff16887f879f6eb4f1506eb3029982039d90b0e82b07d54f5e911a3c644a974863a98a6c60405160405180910390a4867f88592047496a7850992dc5e8cd92a9b633cef0d191a4f5e87fd745c7d382630a8383604051612ba1929190614cb2565b60405180910390a250505050505050565b5f609b5f60ff5f8881526020019081526020015f206001015f9054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020019081526020015f206040518060800160405290815f8201548152602001600182015481526020016002820154815260200160038201548152505090505f60ff5f8781526020019081526020015f206003015490505f60ff5f8881526020019081526020015f206001015f9054906101000a900473ffffffffffffffffffffffffffffffffffffffff1690505f609754845f015184612cb9919061403b565b612cc391906140a9565b90505f6097548667ffffffffffffffff1683612cdf919061403b565b612ce991906140a9565b90505f609754866020015183612cff919061403b565b612d0991906140a9565b90505f8386612d1891906140d9565b90505f8114158015612d6257505f60ff5f8d81526020019081526020015f206006015f9054906101000a90046bffffffffffffffffffffffff166bffffffffffffffffffffffff16145b15612de4578473ffffffffffffffffffffffffffffffffffffffff1663a9059cbb8a836040518363ffffffff1660e01b8152600401612da292919061414e565b6020604051808303815f875af1158015612dbe573d5f5f3e3d5ffd5b505050506040513d601f19601f82011682018060405250810190612de2919061419f565b505b5f8214612e8a578473ffffffffffffffffffffffffffffffffffffffff1663a9059cbb609860089054906101000a900473ffffffffffffffffffffffffffffffffffffffff16846040518363ffffffff1660e01b8152600401612e4892919061414e565b6020604051808303815f875af1158015612e64573d5f5f3e3d5ffd5b505050506040513d601f19601f82011682018060405250810190612e88919061419f565b505b8183612e9691906140d9565b92505f8314612f1c578473ffffffffffffffffffffffffffffffffffffffff1663a9059cbb8b856040518363ffffffff1660e01b8152600401612eda92919061414e565b6020604051808303815f875af1158015612ef6573d5f5f3e3d5ffd5b505050506040513d601f19601f82011682018060405250810190612f1a919061419f565b505b808973ffffffffffffffffffffffffffffffffffffffff168c7f879f6eb4f1506eb3029982039d90b0e82b07d54f5e911a3c644a974863a98a6c60405160405180910390a48a7f831c7cc0006d91462607c476603366c48469d125de6228c0791a7090efd7f7a4828585604051612f9593929190614cd9565b60405180910390a25050505050505050505050565b612fb26130b9565b73ffffffffffffffffffffffffffffffffffffffff16612fd0612077565b73ffffffffffffffffffffffffffffffffffffffff1614613026576040517f08c379a000000000000000000000000000000000000000000000000000000000815260040161301d90614d58565b60405180910390fd5b565b6130306133eb565b5f60cd5f6101000a81548160ff0219169083151502179055507f5db9ee0a495bf2e6ff9c91a7834c1ba4fdd244a5e8aa4e537bd38aeae4b073aa6130726130b9565b60405161307f9190613822565b60405180910390a1565b60655f6101000a81549073ffffffffffffffffffffffffffffffffffffffff02191690556130b681613434565b50565b5f33905090565b6130c8610e1f565b15613108576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004016130ff90614dc0565b60405180910390fd5b565b6001609a5f8773ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020019081526020015f20541461318a576040517f08c379a000000000000000000000000000000000000000000000000000000000815260040161318190614e28565b60405180910390fd5b5f84036131cc576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004016131c390614e90565b60405180910390fd5b5f73ffffffffffffffffffffffffffffffffffffffff168373ffffffffffffffffffffffffffffffffffffffff160361323a576040517f08c379a000000000000000000000000000000000000000000000000000000000815260040161323190614ef8565b60405180910390fd5b5f81146132b0575f73ffffffffffffffffffffffffffffffffffffffff168273ffffffffffffffffffffffffffffffffffffffff16036132af576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004016132a690614f60565b60405180910390fd5b5b5050505050565b5f5f8273ffffffffffffffffffffffffffffffffffffffff163b119050919050565b5f60019054906101000a900460ff16613327576040517f08c379a000000000000000000000000000000000000000000000000000000000815260040161331e90614fee565b60405180910390fd5b61332f6134f7565b565b5f60019054906101000a900460ff1661337f576040517f08c379a000000000000000000000000000000000000000000000000000000000815260040161337690614fee565b60405180910390fd5b613387613557565b565b6133916130c0565b600160cd5f6101000a81548160ff0219169083151502179055507f62e78cea01bee320cd4e420270b5ea74000d11b0c9f74754ebdbfc544b05a2586133d46130b9565b6040516133e19190613822565b60405180910390a1565b6133f3610e1f565b613432576040517f08c379a000000000000000000000000000000000000000000000000000000000815260040161342990615056565b60405180910390fd5b565b5f60335f9054906101000a900473ffffffffffffffffffffffffffffffffffffffff1690508160335f6101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908373ffffffffffffffffffffffffffffffffffffffff1602179055508173ffffffffffffffffffffffffffffffffffffffff168173ffffffffffffffffffffffffffffffffffffffff167f8be0079c531659141344cd1fd0a4f28419497f9722a3daafe3b4186f6b6457e060405160405180910390a35050565b5f60019054906101000a900460ff16613545576040517f08c379a000000000000000000000000000000000000000000000000000000000815260040161353c90614fee565b60405180910390fd5b6135556135506130b9565b613089565b565b5f60019054906101000a900460ff166135a5576040517f08c379a000000000000000000000000000000000000000000000000000000000815260040161359c90614fee565b60405180910390fd5b5f60cd5f6101000a81548160ff021916908315150217905550565b6040518061014001604052805f73ffffffffffffffffffffffffffffffffffffffff1681526020015f73ffffffffffffffffffffffffffffffffffffffff1681526020015f73ffffffffffffffffffffffffffffffffffffffff1681526020015f81526020015f81526020015f151581526020015f151581526020015f73ffffffffffffffffffffffffffffffffffffffff1681526020015f6bffffffffffffffffffffffff1681526020015f81525090565b60405180608001604052805f81526020015f81526020015f81526020015f81525090565b5f5ffd5b5f5ffd5b5f819050919050565b6136b18161369f565b81146136bb575f5ffd5b50565b5f813590506136cc816136a8565b92915050565b5f73ffffffffffffffffffffffffffffffffffffffff82169050919050565b5f6136fb826136d2565b9050919050565b61370b816136f1565b8114613715575f5ffd5b50565b5f8135905061372681613702565b92915050565b5f67ffffffffffffffff82169050919050565b6137488161372c565b8114613752575f5ffd5b50565b5f813590506137638161373f565b92915050565b5f5f5f5f5f60a0868803121561378257613781613697565b5b5f61378f888289016136be565b95505060206137a0888289016136be565b94505060406137b188828901613718565b93505060606137c288828901613755565b92505060806137d388828901613755565b9150509295509295909350565b5f8115159050919050565b6137f4816137e0565b82525050565b5f60208201905061380d5f8301846137eb565b92915050565b61381c816136f1565b82525050565b5f6020820190506138355f830184613813565b92915050565b5f5f6040838503121561385157613850613697565b5b5f61385e858286016136be565b925050602061386f85828601613718565b9150509250929050565b5f819050919050565b61388b81613879565b8114613895575f5ffd5b50565b5f813590506138a681613882565b92915050565b5f5f604083850312156138c2576138c1613697565b5b5f6138cf85828601613898565b92505060206138e0858286016136be565b9150509250929050565b5f602082840312156138ff576138fe613697565b5b5f61390c84828501613718565b91505092915050565b5f6020828403121561392a57613929613697565b5b5f613937848285016136be565b91505092915050565b613949816136f1565b82525050565b61395881613879565b82525050565b613967816137e0565b82525050565b5f6bffffffffffffffffffffffff82169050919050565b61398d8161396d565b82525050565b61014082015f8201516139a85f850182613940565b5060208201516139bb6020850182613940565b5060408201516139ce6040850182613940565b5060608201516139e1606085018261394f565b5060808201516139f4608085018261394f565b5060a0820151613a0760a085018261395e565b5060c0820151613a1a60c085018261395e565b5060e0820151613a2d60e0850182613940565b50610100820151613a42610100850182613984565b50610120820151613a5761012085018261394f565b50505050565b5f61014082019050613a715f830184613993565b92915050565b613a808161396d565b8114613a8a575f5ffd5b50565b5f81359050613a9b81613a77565b92915050565b5f5ffd5b5f5ffd5b5f5ffd5b5f5f83601f840112613ac257613ac1613aa1565b5b8235905067ffffffffffffffff811115613adf57613ade613aa5565b5b602083019150836001820283011115613afb57613afa613aa9565b5b9250929050565b5f5f5f5f5f5f5f5f60e0898b031215613b1e57613b1d613697565b5b5f613b2b8b828c01613718565b9850506020613b3c8b828c01613898565b9750506040613b4d8b828c01613a8d565b9650506060613b5e8b828c01613718565b9550506080613b6f8b828c01613898565b94505060a0613b808b828c01613718565b93505060c089013567ffffffffffffffff811115613ba157613ba061369b565b5b613bad8b828c01613aad565b92509250509295985092959890939650565b613bc88161369f565b82525050565b5f602082019050613be15f830184613bbf565b92915050565b5f5f5f5f5f60a08688031215613c0057613bff613697565b5b5f613c0d88828901613718565b9550506020613c1e88828901613898565b9450506040613c2f88828901613898565b9350506060613c4088828901613898565b9250506080613c5188828901613898565b9150509295509295909350565b608082015f820151613c725f85018261394f565b506020820151613c85602085018261394f565b506040820151613c98604085018261394f565b506060820151613cab606085018261394f565b50505050565b5f608082019050613cc45f830184613c5e565b92915050565b5f5f5f60608486031215613ce157613ce0613697565b5b5f613cee868287016136be565b9350506020613cff86828701613718565b9250506040613d1086828701613898565b9150509250925092565b5f5f5f5f5f5f5f60e0888a031215613d3557613d34613697565b5b5f613d428a828b016136be565b9750506020613d538a828b01613718565b9650506040613d648a828b01613898565b9550506060613d758a828b01613718565b9450506080613d868a828b01613a8d565b93505060a0613d978a828b01613718565b92505060c0613da88a828b01613a8d565b91505092959891949750929550565b5f82825260208201905092915050565b7f4f6e6c7941676772656761746f720000000000000000000000000000000000005f82015250565b5f613dfb600e83613db7565b9150613e0682613dc7565b602082019050919050565b5f6020820190508181035f830152613e2881613def565b9050919050565b7f4f7264657246756c66696c6c65640000000000000000000000000000000000005f82015250565b5f613e63600e83613db7565b9150613e6e82613e2f565b602082019050919050565b5f6020820190508181035f830152613e9081613e57565b9050919050565b7f4f72646572526566756e646564000000000000000000000000000000000000005f82015250565b5f613ecb600d83613db7565b9150613ed682613e97565b602082019050919050565b5f6020820190508181035f830152613ef881613ebf565b9050919050565b7f496e76616c696452656261746550657263656e740000000000000000000000005f82015250565b5f613f33601483613db7565b9150613f3e82613eff565b602082019050919050565b5f6020820190508181035f830152613f6081613f27565b9050919050565b7f496e76616c6964536574746c6550657263656e740000000000000000000000005f82015250565b5f613f9b601483613db7565b9150613fa682613f67565b602082019050919050565b5f6020820190508181035f830152613fc881613f8f565b9050919050565b7f4e487b71000000000000000000000000000000000000000000000000000000005f52601160045260245ffd5b5f6140068261396d565b91506140118361396d565b925082820390506bffffffffffffffffffffffff81111561403557614034613fcf565b5b92915050565b5f61404582613879565b915061405083613879565b925082820261405e81613879565b9150828204841483151761407557614074613fcf565b5b5092915050565b7f4e487b71000000000000000000000000000000000000000000000000000000005f52601260045260245ffd5b5f6140b382613879565b91506140be83613879565b9250826140ce576140cd61407c565b5b828204905092915050565b5f6140e382613879565b91506140ee83613879565b925082820390508181111561410657614105613fcf565b5b92915050565b5f61411682613879565b915061412183613879565b925082820190508082111561413957614138613fcf565b5b92915050565b61414881613879565b82525050565b5f6040820190506141615f830185613813565b61416e602083018461413f565b9392505050565b61417e816137e0565b8114614188575f5ffd5b50565b5f8151905061419981614175565b92915050565b5f602082840312156141b4576141b3613697565b5b5f6141c18482850161418b565b91505092915050565b6141d38161372c565b82525050565b5f6060820190506141ec5f830186613bbf565b6141f960208301856141ca565b61420660408301846141ca565b949350505050565b7f476174657761793a207a65726f206164647265737300000000000000000000005f82015250565b5f614242601583613db7565b915061424d8261420e565b602082019050919050565b5f6020820190508181035f83015261426f81614236565b9050919050565b7f476174657761793a207472656173757279206164647265737320616c726561645f8201527f7920736574000000000000000000000000000000000000000000000000000000602082015250565b5f6142d0602583613db7565b91506142db82614276565b604082019050919050565b5f6020820190508181035f8301526142fd816142c4565b9050919050565b7f476174657761793a2061676772656761746f72206164647265737320616c72655f8201527f6164792073657400000000000000000000000000000000000000000000000000602082015250565b5f61435e602783613db7565b915061436982614304565b604082019050919050565b5f6020820190508181035f83015261438b81614352565b9050919050565b7f4665654578636565647350726f746f636f6c46656500000000000000000000005f82015250565b5f6143c6601583613db7565b91506143d182614392565b602082019050919050565b5f6020820190508181035f8301526143f3816143ba565b9050919050565b5f60208201905061440d5f83018461413f565b92915050565b7f4f776e61626c6532537465703a2063616c6c6572206973206e6f7420746865205f8201527f6e6577206f776e65720000000000000000000000000000000000000000000000602082015250565b5f61446d602983613db7565b915061447882614413565b604082019050919050565b5f6020820190508181035f83015261449a81614461565b9050919050565b7f496e76616c69644d6573736167654861736800000000000000000000000000005f82015250565b5f6144d5601283613db7565b91506144e0826144a1565b602082019050919050565b5f6020820190508181035f830152614502816144c9565b9050919050565b5f60608201905061451c5f830186613813565b6145296020830185613813565b614536604083018461413f565b949350505050565b5f61454882613879565b91507fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff820361457a57614579613fcf565b5b600182019050919050565b5f6060820190506145985f830186613813565b6145a5602083018561413f565b6145b2604083018461413f565b949350505050565b7f4f72646572416c726561647945786973747300000000000000000000000000005f82015250565b5f6145ee601283613db7565b91506145f9826145ba565b602082019050919050565b5f6020820190508181035f83015261461b816145e2565b9050919050565b7f53656e64657246656549735a65726f00000000000000000000000000000000005f82015250565b5f614656600f83613db7565b915061466182614622565b602082019050919050565b5f6020820190508181035f8301526146838161464a565b9050919050565b7f546f6b656e46656553657474696e67734e6f74436f6e666967757265640000005f82015250565b5f6146be601d83613db7565b91506146c98261468a565b602082019050919050565b5f6020820190508181035f8301526146eb816146b2565b9050919050565b5f819050919050565b5f61471561471061470b8461396d565b6146f2565b613879565b9050919050565b614725816146fb565b82525050565b828183375f83830152505050565b5f601f19601f8301169050919050565b5f6147548385613db7565b935061476183858461472b565b61476a83614739565b840190509392505050565b5f6080820190506147885f83018861413f565b6147956020830187613bbf565b6147a2604083018661471c565b81810360608301526147b5818486614749565b90509695505050505050565b7f496e697469616c697a61626c653a20636f6e747261637420697320616c7265615f8201527f647920696e697469616c697a6564000000000000000000000000000000000000602082015250565b5f61481b602e83613db7565b9150614826826147c1565b604082019050919050565b5f6020820190508181035f8301526148488161480f565b9050919050565b5f819050919050565b5f60ff82169050919050565b5f61487e6148796148748461484f565b6146f2565b614858565b9050919050565b61488e81614864565b82525050565b5f6020820190506148a75f830184614885565b92915050565b7f476174657761793a20746f6b656e206e6f7420737570706f72746564000000005f82015250565b5f6148e1601c83613db7565b91506148ec826148ad565b602082019050919050565b5f6020820190508181035f83015261490e816148d5565b9050919050565b7f476174657761793a20696e76616c69642073656e64657220746f2070726f76695f8201527f6465720000000000000000000000000000000000000000000000000000000000602082015250565b5f61496f602383613db7565b915061497a82614915565b604082019050919050565b5f6020820190508181035f83015261499c81614963565b9050919050565b7f476174657761793a20696e76616c69642070726f766964657220746f206167675f8201527f72656761746f7200000000000000000000000000000000000000000000000000602082015250565b5f6149fd602783613db7565b9150614a08826149a3565b604082019050919050565b5f6020820190508181035f830152614a2a816149f1565b9050919050565b7f476174657761793a20696e76616c69642073656e64657220746f2061676772655f8201527f6761746f72000000000000000000000000000000000000000000000000000000602082015250565b5f614a8b602583613db7565b9150614a9682614a31565b604082019050919050565b5f6020820190508181035f830152614ab881614a7f565b9050919050565b7f476174657761793a20696e76616c69642070726f766964657220746f206167675f8201527f72656761746f7220667800000000000000000000000000000000000000000000602082015250565b5f614b19602a83613db7565b9150614b2482614abf565b604082019050919050565b5f6020820190508181035f830152614b4681614b0d565b9050919050565b5f608082019050614b605f83018761413f565b614b6d602083018661413f565b614b7a604083018561413f565b614b87606083018461413f565b95945050505050565b7f476174657761793a20696e76616c6964207374617475730000000000000000005f82015250565b5f614bc4601783613db7565b9150614bcf82614b90565b602082019050919050565b5f6020820190508181035f830152614bf181614bb8565b9050919050565b7f496e76616c6964416d6f756e74000000000000000000000000000000000000005f82015250565b5f614c2c600d83613db7565b9150614c3782614bf8565b602082019050919050565b5f6020820190508181035f830152614c5981614c20565b9050919050565b614c698161396d565b82525050565b5f608082019050614c825f83018761413f565b614c8f6020830186613813565b614c9c604083018561413f565b614ca96060830184614c60565b95945050505050565b5f604082019050614cc55f83018561413f565b614cd2602083018461413f565b9392505050565b5f606082019050614cec5f83018661413f565b614cf9602083018561413f565b614d06604083018461413f565b949350505050565b7f4f776e61626c653a2063616c6c6572206973206e6f7420746865206f776e65725f82015250565b5f614d42602083613db7565b9150614d4d82614d0e565b602082019050919050565b5f6020820190508181035f830152614d6f81614d36565b9050919050565b7f5061757361626c653a20706175736564000000000000000000000000000000005f82015250565b5f614daa601083613db7565b9150614db582614d76565b602082019050919050565b5f6020820190508181035f830152614dd781614d9e565b9050919050565b7f546f6b656e4e6f74537570706f727465640000000000000000000000000000005f82015250565b5f614e12601183613db7565b9150614e1d82614dde565b602082019050919050565b5f6020820190508181035f830152614e3f81614e06565b9050919050565b7f416d6f756e7449735a65726f00000000000000000000000000000000000000005f82015250565b5f614e7a600c83613db7565b9150614e8582614e46565b602082019050919050565b5f6020820190508181035f830152614ea781614e6e565b9050919050565b7f5468726f775a65726f41646472657373000000000000000000000000000000005f82015250565b5f614ee2601083613db7565b9150614eed82614eae565b602082019050919050565b5f6020820190508181035f830152614f0f81614ed6565b9050919050565b7f496e76616c696453656e646572466565526563697069656e74000000000000005f82015250565b5f614f4a601983613db7565b9150614f5582614f16565b602082019050919050565b5f6020820190508181035f830152614f7781614f3e565b9050919050565b7f496e697469616c697a61626c653a20636f6e7472616374206973206e6f7420695f8201527f6e697469616c697a696e67000000000000000000000000000000000000000000602082015250565b5f614fd8602b83613db7565b9150614fe382614f7e565b604082019050919050565b5f6020820190508181035f83015261500581614fcc565b9050919050565b7f5061757361626c653a206e6f74207061757365640000000000000000000000005f82015250565b5f615040601483613db7565b915061504b8261500c565b602082019050919050565b5f6020820190508181035f83015261506d81615034565b905091905056fea2646970667358221220b93bcb97134b7a00def996fb68684bcef988bee715d5ecbb0fcceaa4c5a71df164736f6c634300081e0033", } // GatewayABI is the input ABI used to generate the binding from. @@ -107,7 +107,7 @@ type GatewayFilterer struct { // GatewaySession is an auto generated Go binding around an Ethereum contract, // with pre-set call and transact options. type GatewaySession struct { - Contract *Gateway // Generic contract binding to set the session for + Contract *Gateway // Generic contract binding to set the session for CallOpts bind.CallOpts // Call options to use throughout this session TransactOpts bind.TransactOpts // Transaction auth options to use throughout this session } @@ -115,15 +115,15 @@ type GatewaySession struct { // GatewayCallerSession is an auto generated read-only Go binding around an Ethereum contract, // with pre-set call options. type GatewayCallerSession struct { - Contract *GatewayCaller // Generic contract caller binding to set the session for - CallOpts bind.CallOpts // Call options to use throughout this session + Contract *GatewayCaller // Generic contract caller binding to set the session for + CallOpts bind.CallOpts // Call options to use throughout this session } // GatewayTransactorSession is an auto generated write-only Go binding around an Ethereum contract, // with pre-set transact options. type GatewayTransactorSession struct { - Contract *GatewayTransactor // Generic contract transactor binding to set the session for - TransactOpts bind.TransactOpts // Transaction auth options to use throughout this session + Contract *GatewayTransactor // Generic contract transactor binding to set the session for + TransactOpts bind.TransactOpts // Transaction auth options to use throughout this session } // GatewayRaw is an auto generated low-level Go binding around an Ethereum contract. @@ -224,6 +224,37 @@ func (_Gateway *GatewayTransactorRaw) Transact(opts *bind.TransactOpts, method s return _Gateway.Contract.contract.Transact(opts, method, params...) } +// GetAggregator is a free data retrieval call binding the contract method 0x3ad59dbc. +// +// Solidity: function getAggregator() view returns(address) +func (_Gateway *GatewayCaller) GetAggregator(opts *bind.CallOpts) (common.Address, error) { + var out []interface{} + err := _Gateway.contract.Call(opts, &out, "getAggregator") + + if err != nil { + return *new(common.Address), err + } + + out0 := *abi.ConvertType(out[0], new(common.Address)).(*common.Address) + + return out0, err + +} + +// GetAggregator is a free data retrieval call binding the contract method 0x3ad59dbc. +// +// Solidity: function getAggregator() view returns(address) +func (_Gateway *GatewaySession) GetAggregator() (common.Address, error) { + return _Gateway.Contract.GetAggregator(&_Gateway.CallOpts) +} + +// GetAggregator is a free data retrieval call binding the contract method 0x3ad59dbc. +// +// Solidity: function getAggregator() view returns(address) +func (_Gateway *GatewayCallerSession) GetAggregator() (common.Address, error) { + return _Gateway.Contract.GetAggregator(&_Gateway.CallOpts) +} + // GetOrderInfo is a free data retrieval call binding the contract method 0x768c6ec0. // // Solidity: function getOrderInfo(bytes32 _orderId) view returns((address,address,address,uint256,uint256,bool,bool,address,uint96,uint256)) @@ -578,25 +609,46 @@ func (_Gateway *GatewayTransactorSession) SettingManagerBool(what [32]byte, valu return _Gateway.Contract.SettingManagerBool(&_Gateway.TransactOpts, what, value, status) } -// Settle is a paid mutator transaction binding the contract method 0xdf51b359. +// SettleIn is a paid mutator transaction binding the contract method 0xd839de63. +// +// Solidity: function settleIn(bytes32 _orderId, address _token, uint256 _amount, address _senderFeeRecipient, uint96 _senderFee, address _recipient, uint96 _rate) returns(bool) +func (_Gateway *GatewayTransactor) SettleIn(opts *bind.TransactOpts, _orderId [32]byte, _token common.Address, _amount *big.Int, _senderFeeRecipient common.Address, _senderFee *big.Int, _recipient common.Address, _rate *big.Int) (*types.Transaction, error) { + return _Gateway.contract.Transact(opts, "settleIn", _orderId, _token, _amount, _senderFeeRecipient, _senderFee, _recipient, _rate) +} + +// SettleIn is a paid mutator transaction binding the contract method 0xd839de63. +// +// Solidity: function settleIn(bytes32 _orderId, address _token, uint256 _amount, address _senderFeeRecipient, uint96 _senderFee, address _recipient, uint96 _rate) returns(bool) +func (_Gateway *GatewaySession) SettleIn(_orderId [32]byte, _token common.Address, _amount *big.Int, _senderFeeRecipient common.Address, _senderFee *big.Int, _recipient common.Address, _rate *big.Int) (*types.Transaction, error) { + return _Gateway.Contract.SettleIn(&_Gateway.TransactOpts, _orderId, _token, _amount, _senderFeeRecipient, _senderFee, _recipient, _rate) +} + +// SettleIn is a paid mutator transaction binding the contract method 0xd839de63. +// +// Solidity: function settleIn(bytes32 _orderId, address _token, uint256 _amount, address _senderFeeRecipient, uint96 _senderFee, address _recipient, uint96 _rate) returns(bool) +func (_Gateway *GatewayTransactorSession) SettleIn(_orderId [32]byte, _token common.Address, _amount *big.Int, _senderFeeRecipient common.Address, _senderFee *big.Int, _recipient common.Address, _rate *big.Int) (*types.Transaction, error) { + return _Gateway.Contract.SettleIn(&_Gateway.TransactOpts, _orderId, _token, _amount, _senderFeeRecipient, _senderFee, _recipient, _rate) +} + +// SettleOut is a paid mutator transaction binding the contract method 0x32553efa. // -// Solidity: function settle(bytes32 _splitOrderId, bytes32 _orderId, address _liquidityProvider, uint64 _settlePercent, uint64 _rebatePercent) returns(bool) -func (_Gateway *GatewayTransactor) Settle(opts *bind.TransactOpts, _splitOrderId [32]byte, _orderId [32]byte, _liquidityProvider common.Address, _settlePercent uint64, _rebatePercent uint64) (*types.Transaction, error) { - return _Gateway.contract.Transact(opts, "settle", _splitOrderId, _orderId, _liquidityProvider, _settlePercent, _rebatePercent) +// Solidity: function settleOut(bytes32 _splitOrderId, bytes32 _orderId, address _liquidityProvider, uint64 _settlePercent, uint64 _rebatePercent) returns(bool) +func (_Gateway *GatewayTransactor) SettleOut(opts *bind.TransactOpts, _splitOrderId [32]byte, _orderId [32]byte, _liquidityProvider common.Address, _settlePercent uint64, _rebatePercent uint64) (*types.Transaction, error) { + return _Gateway.contract.Transact(opts, "settleOut", _splitOrderId, _orderId, _liquidityProvider, _settlePercent, _rebatePercent) } -// Settle is a paid mutator transaction binding the contract method 0xdf51b359. +// SettleOut is a paid mutator transaction binding the contract method 0x32553efa. // -// Solidity: function settle(bytes32 _splitOrderId, bytes32 _orderId, address _liquidityProvider, uint64 _settlePercent, uint64 _rebatePercent) returns(bool) -func (_Gateway *GatewaySession) Settle(_splitOrderId [32]byte, _orderId [32]byte, _liquidityProvider common.Address, _settlePercent uint64, _rebatePercent uint64) (*types.Transaction, error) { - return _Gateway.Contract.Settle(&_Gateway.TransactOpts, _splitOrderId, _orderId, _liquidityProvider, _settlePercent, _rebatePercent) +// Solidity: function settleOut(bytes32 _splitOrderId, bytes32 _orderId, address _liquidityProvider, uint64 _settlePercent, uint64 _rebatePercent) returns(bool) +func (_Gateway *GatewaySession) SettleOut(_splitOrderId [32]byte, _orderId [32]byte, _liquidityProvider common.Address, _settlePercent uint64, _rebatePercent uint64) (*types.Transaction, error) { + return _Gateway.Contract.SettleOut(&_Gateway.TransactOpts, _splitOrderId, _orderId, _liquidityProvider, _settlePercent, _rebatePercent) } -// Settle is a paid mutator transaction binding the contract method 0xdf51b359. +// SettleOut is a paid mutator transaction binding the contract method 0x32553efa. // -// Solidity: function settle(bytes32 _splitOrderId, bytes32 _orderId, address _liquidityProvider, uint64 _settlePercent, uint64 _rebatePercent) returns(bool) -func (_Gateway *GatewayTransactorSession) Settle(_splitOrderId [32]byte, _orderId [32]byte, _liquidityProvider common.Address, _settlePercent uint64, _rebatePercent uint64) (*types.Transaction, error) { - return _Gateway.Contract.Settle(&_Gateway.TransactOpts, _splitOrderId, _orderId, _liquidityProvider, _settlePercent, _rebatePercent) +// Solidity: function settleOut(bytes32 _splitOrderId, bytes32 _orderId, address _liquidityProvider, uint64 _settlePercent, uint64 _rebatePercent) returns(bool) +func (_Gateway *GatewayTransactorSession) SettleOut(_splitOrderId [32]byte, _orderId [32]byte, _liquidityProvider common.Address, _settlePercent uint64, _rebatePercent uint64) (*types.Transaction, error) { + return _Gateway.Contract.SettleOut(&_Gateway.TransactOpts, _splitOrderId, _orderId, _liquidityProvider, _settlePercent, _rebatePercent) } // TransferOwnership is a paid mutator transaction binding the contract method 0xf2fde38b. @@ -1400,162 +1452,6 @@ func (_Gateway *GatewayFilterer) ParseOrderRefunded(log types.Log) (*GatewayOrde return event, nil } -// GatewayOrderSettledIterator is returned from FilterOrderSettled and is used to iterate over the raw logs and unpacked data for OrderSettled events raised by the Gateway contract. -type GatewayOrderSettledIterator struct { - Event *GatewayOrderSettled // Event containing the contract specifics and raw log - - contract *bind.BoundContract // Generic contract to use for unpacking event data - event string // Event name to use for unpacking event data - - logs chan types.Log // Log channel receiving the found contract events - sub ethereum.Subscription // Subscription for errors, completion and termination - done bool // Whether the subscription completed delivering logs - fail error // Occurred error to stop iteration -} - -// Next advances the iterator to the subsequent event, returning whether there -// are any more events found. In case of a retrieval or parsing error, false is -// returned and Error() can be queried for the exact failure. -func (it *GatewayOrderSettledIterator) Next() bool { - // If the iterator failed, stop iterating - if it.fail != nil { - return false - } - // If the iterator completed, deliver directly whatever's available - if it.done { - select { - case log := <-it.logs: - it.Event = new(GatewayOrderSettled) - if err := it.contract.UnpackLog(it.Event, it.event, log); err != nil { - it.fail = err - return false - } - it.Event.Raw = log - return true - - default: - return false - } - } - // Iterator still in progress, wait for either a data or an error event - select { - case log := <-it.logs: - it.Event = new(GatewayOrderSettled) - if err := it.contract.UnpackLog(it.Event, it.event, log); err != nil { - it.fail = err - return false - } - it.Event.Raw = log - return true - - case err := <-it.sub.Err(): - it.done = true - it.fail = err - return it.Next() - } -} - -// Error returns any retrieval or parsing error occurred during filtering. -func (it *GatewayOrderSettledIterator) Error() error { - return it.fail -} - -// Close terminates the iteration process, releasing any pending underlying -// resources. -func (it *GatewayOrderSettledIterator) Close() error { - it.sub.Unsubscribe() - return nil -} - -// GatewayOrderSettled represents a OrderSettled event raised by the Gateway contract. -type GatewayOrderSettled struct { - SplitOrderId [32]byte - OrderId [32]byte - LiquidityProvider common.Address - SettlePercent uint64 - RebatePercent uint64 - Raw types.Log // Blockchain specific contextual infos -} - -// FilterOrderSettled is a free log retrieval operation binding the contract event 0x57c683de2e7c8263c7f57fd108416b9bdaa7a6e7f2e4e7102c3b6f9e37f1cc37. -// -// Solidity: event OrderSettled(bytes32 splitOrderId, bytes32 indexed orderId, address indexed liquidityProvider, uint64 settlePercent, uint64 rebatePercent) -func (_Gateway *GatewayFilterer) FilterOrderSettled(opts *bind.FilterOpts, orderId [][32]byte, liquidityProvider []common.Address) (*GatewayOrderSettledIterator, error) { - - var orderIdRule []interface{} - for _, orderIdItem := range orderId { - orderIdRule = append(orderIdRule, orderIdItem) - } - var liquidityProviderRule []interface{} - for _, liquidityProviderItem := range liquidityProvider { - liquidityProviderRule = append(liquidityProviderRule, liquidityProviderItem) - } - - logs, sub, err := _Gateway.contract.FilterLogs(opts, "OrderSettled", orderIdRule, liquidityProviderRule) - if err != nil { - return nil, err - } - return &GatewayOrderSettledIterator{contract: _Gateway.contract, event: "OrderSettled", logs: logs, sub: sub}, nil -} - -// WatchOrderSettled is a free log subscription operation binding the contract event 0x57c683de2e7c8263c7f57fd108416b9bdaa7a6e7f2e4e7102c3b6f9e37f1cc37. -// -// Solidity: event OrderSettled(bytes32 splitOrderId, bytes32 indexed orderId, address indexed liquidityProvider, uint64 settlePercent, uint64 rebatePercent) -func (_Gateway *GatewayFilterer) WatchOrderSettled(opts *bind.WatchOpts, sink chan<- *GatewayOrderSettled, orderId [][32]byte, liquidityProvider []common.Address) (event.Subscription, error) { - - var orderIdRule []interface{} - for _, orderIdItem := range orderId { - orderIdRule = append(orderIdRule, orderIdItem) - } - var liquidityProviderRule []interface{} - for _, liquidityProviderItem := range liquidityProvider { - liquidityProviderRule = append(liquidityProviderRule, liquidityProviderItem) - } - - logs, sub, err := _Gateway.contract.WatchLogs(opts, "OrderSettled", orderIdRule, liquidityProviderRule) - if err != nil { - return nil, err - } - return event.NewSubscription(func(quit <-chan struct{}) error { - defer sub.Unsubscribe() - for { - select { - case log := <-logs: - // New log arrived, parse the event and forward to the user - event := new(GatewayOrderSettled) - if err := _Gateway.contract.UnpackLog(event, "OrderSettled", log); err != nil { - return err - } - event.Raw = log - - select { - case sink <- event: - case err := <-sub.Err(): - return err - case <-quit: - return nil - } - case err := <-sub.Err(): - return err - case <-quit: - return nil - } - } - }), nil -} - -// ParseOrderSettled is a log parse operation binding the contract event 0x57c683de2e7c8263c7f57fd108416b9bdaa7a6e7f2e4e7102c3b6f9e37f1cc37. -// -// Solidity: event OrderSettled(bytes32 splitOrderId, bytes32 indexed orderId, address indexed liquidityProvider, uint64 settlePercent, uint64 rebatePercent) -func (_Gateway *GatewayFilterer) ParseOrderSettled(log types.Log) (*GatewayOrderSettled, error) { - event := new(GatewayOrderSettled) - if err := _Gateway.contract.UnpackLog(event, "OrderSettled", log); err != nil { - return nil, err - } - event.Raw = log - return event, nil -} - // GatewayOwnershipTransferStartedIterator is returned from FilterOwnershipTransferStarted and is used to iterate over the raw logs and unpacked data for OwnershipTransferStarted events raised by the Gateway contract. type GatewayOwnershipTransferStartedIterator struct { Event *GatewayOwnershipTransferStarted // Event containing the contract specifics and raw log @@ -2218,16 +2114,21 @@ func (it *GatewaySenderFeeTransferredIterator) Close() error { // GatewaySenderFeeTransferred represents a SenderFeeTransferred event raised by the Gateway contract. type GatewaySenderFeeTransferred struct { - Sender common.Address - Amount *big.Int - Raw types.Log // Blockchain specific contextual infos + OrderId [32]byte + Sender common.Address + Amount *big.Int + Raw types.Log // Blockchain specific contextual infos } -// FilterSenderFeeTransferred is a free log retrieval operation binding the contract event 0x44f6938ca4a10313aabb76f874cced61e35710a734a126e4afb34461bf8c2501. +// FilterSenderFeeTransferred is a free log retrieval operation binding the contract event 0x879f6eb4f1506eb3029982039d90b0e82b07d54f5e911a3c644a974863a98a6c. // -// Solidity: event SenderFeeTransferred(address indexed sender, uint256 indexed amount) -func (_Gateway *GatewayFilterer) FilterSenderFeeTransferred(opts *bind.FilterOpts, sender []common.Address, amount []*big.Int) (*GatewaySenderFeeTransferredIterator, error) { +// Solidity: event SenderFeeTransferred(bytes32 indexed orderId, address indexed sender, uint256 indexed amount) +func (_Gateway *GatewayFilterer) FilterSenderFeeTransferred(opts *bind.FilterOpts, orderId [][32]byte, sender []common.Address, amount []*big.Int) (*GatewaySenderFeeTransferredIterator, error) { + var orderIdRule []interface{} + for _, orderIdItem := range orderId { + orderIdRule = append(orderIdRule, orderIdItem) + } var senderRule []interface{} for _, senderItem := range sender { senderRule = append(senderRule, senderItem) @@ -2237,18 +2138,22 @@ func (_Gateway *GatewayFilterer) FilterSenderFeeTransferred(opts *bind.FilterOpt amountRule = append(amountRule, amountItem) } - logs, sub, err := _Gateway.contract.FilterLogs(opts, "SenderFeeTransferred", senderRule, amountRule) + logs, sub, err := _Gateway.contract.FilterLogs(opts, "SenderFeeTransferred", orderIdRule, senderRule, amountRule) if err != nil { return nil, err } return &GatewaySenderFeeTransferredIterator{contract: _Gateway.contract, event: "SenderFeeTransferred", logs: logs, sub: sub}, nil } -// WatchSenderFeeTransferred is a free log subscription operation binding the contract event 0x44f6938ca4a10313aabb76f874cced61e35710a734a126e4afb34461bf8c2501. +// WatchSenderFeeTransferred is a free log subscription operation binding the contract event 0x879f6eb4f1506eb3029982039d90b0e82b07d54f5e911a3c644a974863a98a6c. // -// Solidity: event SenderFeeTransferred(address indexed sender, uint256 indexed amount) -func (_Gateway *GatewayFilterer) WatchSenderFeeTransferred(opts *bind.WatchOpts, sink chan<- *GatewaySenderFeeTransferred, sender []common.Address, amount []*big.Int) (event.Subscription, error) { +// Solidity: event SenderFeeTransferred(bytes32 indexed orderId, address indexed sender, uint256 indexed amount) +func (_Gateway *GatewayFilterer) WatchSenderFeeTransferred(opts *bind.WatchOpts, sink chan<- *GatewaySenderFeeTransferred, orderId [][32]byte, sender []common.Address, amount []*big.Int) (event.Subscription, error) { + var orderIdRule []interface{} + for _, orderIdItem := range orderId { + orderIdRule = append(orderIdRule, orderIdItem) + } var senderRule []interface{} for _, senderItem := range sender { senderRule = append(senderRule, senderItem) @@ -2258,7 +2163,7 @@ func (_Gateway *GatewayFilterer) WatchSenderFeeTransferred(opts *bind.WatchOpts, amountRule = append(amountRule, amountItem) } - logs, sub, err := _Gateway.contract.WatchLogs(opts, "SenderFeeTransferred", senderRule, amountRule) + logs, sub, err := _Gateway.contract.WatchLogs(opts, "SenderFeeTransferred", orderIdRule, senderRule, amountRule) if err != nil { return nil, err } @@ -2290,9 +2195,9 @@ func (_Gateway *GatewayFilterer) WatchSenderFeeTransferred(opts *bind.WatchOpts, }), nil } -// ParseSenderFeeTransferred is a log parse operation binding the contract event 0x44f6938ca4a10313aabb76f874cced61e35710a734a126e4afb34461bf8c2501. +// ParseSenderFeeTransferred is a log parse operation binding the contract event 0x879f6eb4f1506eb3029982039d90b0e82b07d54f5e911a3c644a974863a98a6c. // -// Solidity: event SenderFeeTransferred(address indexed sender, uint256 indexed amount) +// Solidity: event SenderFeeTransferred(bytes32 indexed orderId, address indexed sender, uint256 indexed amount) func (_Gateway *GatewayFilterer) ParseSenderFeeTransferred(log types.Log) (*GatewaySenderFeeTransferred, error) { event := new(GatewaySenderFeeTransferred) if err := _Gateway.contract.UnpackLog(event, "SenderFeeTransferred", log); err != nil { @@ -2600,6 +2505,328 @@ func (_Gateway *GatewayFilterer) ParseSettingManagerBool(log types.Log) (*Gatewa return event, nil } +// GatewaySettleInIterator is returned from FilterSettleIn and is used to iterate over the raw logs and unpacked data for SettleIn events raised by the Gateway contract. +type GatewaySettleInIterator struct { + Event *GatewaySettleIn // Event containing the contract specifics and raw log + + contract *bind.BoundContract // Generic contract to use for unpacking event data + event string // Event name to use for unpacking event data + + logs chan types.Log // Log channel receiving the found contract events + sub ethereum.Subscription // Subscription for errors, completion and termination + done bool // Whether the subscription completed delivering logs + fail error // Occurred error to stop iteration +} + +// Next advances the iterator to the subsequent event, returning whether there +// are any more events found. In case of a retrieval or parsing error, false is +// returned and Error() can be queried for the exact failure. +func (it *GatewaySettleInIterator) Next() bool { + // If the iterator failed, stop iterating + if it.fail != nil { + return false + } + // If the iterator completed, deliver directly whatever's available + if it.done { + select { + case log := <-it.logs: + it.Event = new(GatewaySettleIn) + if err := it.contract.UnpackLog(it.Event, it.event, log); err != nil { + it.fail = err + return false + } + it.Event.Raw = log + return true + + default: + return false + } + } + // Iterator still in progress, wait for either a data or an error event + select { + case log := <-it.logs: + it.Event = new(GatewaySettleIn) + if err := it.contract.UnpackLog(it.Event, it.event, log); err != nil { + it.fail = err + return false + } + it.Event.Raw = log + return true + + case err := <-it.sub.Err(): + it.done = true + it.fail = err + return it.Next() + } +} + +// Error returns any retrieval or parsing error occurred during filtering. +func (it *GatewaySettleInIterator) Error() error { + return it.fail +} + +// Close terminates the iteration process, releasing any pending underlying +// resources. +func (it *GatewaySettleInIterator) Close() error { + it.sub.Unsubscribe() + return nil +} + +// GatewaySettleIn represents a SettleIn event raised by the Gateway contract. +type GatewaySettleIn struct { + OrderId [32]byte + LiquidityProvider common.Address + Recipient common.Address + Amount *big.Int + Token common.Address + AggregatorFee *big.Int + Rate *big.Int + Raw types.Log // Blockchain specific contextual infos +} + +// FilterSettleIn is a free log retrieval operation binding the contract event 0xb5273ccce1412b056c9246e834895f9d717974c505f8e5a6c7d08cd0300a066b. +// +// Solidity: event SettleIn(bytes32 indexed orderId, address indexed liquidityProvider, address indexed recipient, uint256 amount, address token, uint256 aggregatorFee, uint96 rate) +func (_Gateway *GatewayFilterer) FilterSettleIn(opts *bind.FilterOpts, orderId [][32]byte, liquidityProvider []common.Address, recipient []common.Address) (*GatewaySettleInIterator, error) { + + var orderIdRule []interface{} + for _, orderIdItem := range orderId { + orderIdRule = append(orderIdRule, orderIdItem) + } + var liquidityProviderRule []interface{} + for _, liquidityProviderItem := range liquidityProvider { + liquidityProviderRule = append(liquidityProviderRule, liquidityProviderItem) + } + var recipientRule []interface{} + for _, recipientItem := range recipient { + recipientRule = append(recipientRule, recipientItem) + } + + logs, sub, err := _Gateway.contract.FilterLogs(opts, "SettleIn", orderIdRule, liquidityProviderRule, recipientRule) + if err != nil { + return nil, err + } + return &GatewaySettleInIterator{contract: _Gateway.contract, event: "SettleIn", logs: logs, sub: sub}, nil +} + +// WatchSettleIn is a free log subscription operation binding the contract event 0xb5273ccce1412b056c9246e834895f9d717974c505f8e5a6c7d08cd0300a066b. +// +// Solidity: event SettleIn(bytes32 indexed orderId, address indexed liquidityProvider, address indexed recipient, uint256 amount, address token, uint256 aggregatorFee, uint96 rate) +func (_Gateway *GatewayFilterer) WatchSettleIn(opts *bind.WatchOpts, sink chan<- *GatewaySettleIn, orderId [][32]byte, liquidityProvider []common.Address, recipient []common.Address) (event.Subscription, error) { + + var orderIdRule []interface{} + for _, orderIdItem := range orderId { + orderIdRule = append(orderIdRule, orderIdItem) + } + var liquidityProviderRule []interface{} + for _, liquidityProviderItem := range liquidityProvider { + liquidityProviderRule = append(liquidityProviderRule, liquidityProviderItem) + } + var recipientRule []interface{} + for _, recipientItem := range recipient { + recipientRule = append(recipientRule, recipientItem) + } + + logs, sub, err := _Gateway.contract.WatchLogs(opts, "SettleIn", orderIdRule, liquidityProviderRule, recipientRule) + if err != nil { + return nil, err + } + return event.NewSubscription(func(quit <-chan struct{}) error { + defer sub.Unsubscribe() + for { + select { + case log := <-logs: + // New log arrived, parse the event and forward to the user + event := new(GatewaySettleIn) + if err := _Gateway.contract.UnpackLog(event, "SettleIn", log); err != nil { + return err + } + event.Raw = log + + select { + case sink <- event: + case err := <-sub.Err(): + return err + case <-quit: + return nil + } + case err := <-sub.Err(): + return err + case <-quit: + return nil + } + } + }), nil +} + +// ParseSettleIn is a log parse operation binding the contract event 0xb5273ccce1412b056c9246e834895f9d717974c505f8e5a6c7d08cd0300a066b. +// +// Solidity: event SettleIn(bytes32 indexed orderId, address indexed liquidityProvider, address indexed recipient, uint256 amount, address token, uint256 aggregatorFee, uint96 rate) +func (_Gateway *GatewayFilterer) ParseSettleIn(log types.Log) (*GatewaySettleIn, error) { + event := new(GatewaySettleIn) + if err := _Gateway.contract.UnpackLog(event, "SettleIn", log); err != nil { + return nil, err + } + event.Raw = log + return event, nil +} + +// GatewaySettleOutIterator is returned from FilterSettleOut and is used to iterate over the raw logs and unpacked data for SettleOut events raised by the Gateway contract. +type GatewaySettleOutIterator struct { + Event *GatewaySettleOut // Event containing the contract specifics and raw log + + contract *bind.BoundContract // Generic contract to use for unpacking event data + event string // Event name to use for unpacking event data + + logs chan types.Log // Log channel receiving the found contract events + sub ethereum.Subscription // Subscription for errors, completion and termination + done bool // Whether the subscription completed delivering logs + fail error // Occurred error to stop iteration +} + +// Next advances the iterator to the subsequent event, returning whether there +// are any more events found. In case of a retrieval or parsing error, false is +// returned and Error() can be queried for the exact failure. +func (it *GatewaySettleOutIterator) Next() bool { + // If the iterator failed, stop iterating + if it.fail != nil { + return false + } + // If the iterator completed, deliver directly whatever's available + if it.done { + select { + case log := <-it.logs: + it.Event = new(GatewaySettleOut) + if err := it.contract.UnpackLog(it.Event, it.event, log); err != nil { + it.fail = err + return false + } + it.Event.Raw = log + return true + + default: + return false + } + } + // Iterator still in progress, wait for either a data or an error event + select { + case log := <-it.logs: + it.Event = new(GatewaySettleOut) + if err := it.contract.UnpackLog(it.Event, it.event, log); err != nil { + it.fail = err + return false + } + it.Event.Raw = log + return true + + case err := <-it.sub.Err(): + it.done = true + it.fail = err + return it.Next() + } +} + +// Error returns any retrieval or parsing error occurred during filtering. +func (it *GatewaySettleOutIterator) Error() error { + return it.fail +} + +// Close terminates the iteration process, releasing any pending underlying +// resources. +func (it *GatewaySettleOutIterator) Close() error { + it.sub.Unsubscribe() + return nil +} + +// GatewaySettleOut represents a SettleOut event raised by the Gateway contract. +type GatewaySettleOut struct { + SplitOrderId [32]byte + OrderId [32]byte + LiquidityProvider common.Address + SettlePercent uint64 + RebatePercent uint64 + Raw types.Log // Blockchain specific contextual infos +} + +// FilterSettleOut is a free log retrieval operation binding the contract event 0x1e4a1a8ad772d3f0dbb387879bc5e8faadf16e0513bf77d50620741ab92b4c45. +// +// Solidity: event SettleOut(bytes32 splitOrderId, bytes32 indexed orderId, address indexed liquidityProvider, uint64 settlePercent, uint64 rebatePercent) +func (_Gateway *GatewayFilterer) FilterSettleOut(opts *bind.FilterOpts, orderId [][32]byte, liquidityProvider []common.Address) (*GatewaySettleOutIterator, error) { + + var orderIdRule []interface{} + for _, orderIdItem := range orderId { + orderIdRule = append(orderIdRule, orderIdItem) + } + var liquidityProviderRule []interface{} + for _, liquidityProviderItem := range liquidityProvider { + liquidityProviderRule = append(liquidityProviderRule, liquidityProviderItem) + } + + logs, sub, err := _Gateway.contract.FilterLogs(opts, "SettleOut", orderIdRule, liquidityProviderRule) + if err != nil { + return nil, err + } + return &GatewaySettleOutIterator{contract: _Gateway.contract, event: "SettleOut", logs: logs, sub: sub}, nil +} + +// WatchSettleOut is a free log subscription operation binding the contract event 0x1e4a1a8ad772d3f0dbb387879bc5e8faadf16e0513bf77d50620741ab92b4c45. +// +// Solidity: event SettleOut(bytes32 splitOrderId, bytes32 indexed orderId, address indexed liquidityProvider, uint64 settlePercent, uint64 rebatePercent) +func (_Gateway *GatewayFilterer) WatchSettleOut(opts *bind.WatchOpts, sink chan<- *GatewaySettleOut, orderId [][32]byte, liquidityProvider []common.Address) (event.Subscription, error) { + + var orderIdRule []interface{} + for _, orderIdItem := range orderId { + orderIdRule = append(orderIdRule, orderIdItem) + } + var liquidityProviderRule []interface{} + for _, liquidityProviderItem := range liquidityProvider { + liquidityProviderRule = append(liquidityProviderRule, liquidityProviderItem) + } + + logs, sub, err := _Gateway.contract.WatchLogs(opts, "SettleOut", orderIdRule, liquidityProviderRule) + if err != nil { + return nil, err + } + return event.NewSubscription(func(quit <-chan struct{}) error { + defer sub.Unsubscribe() + for { + select { + case log := <-logs: + // New log arrived, parse the event and forward to the user + event := new(GatewaySettleOut) + if err := _Gateway.contract.UnpackLog(event, "SettleOut", log); err != nil { + return err + } + event.Raw = log + + select { + case sink <- event: + case err := <-sub.Err(): + return err + case <-quit: + return nil + } + case err := <-sub.Err(): + return err + case <-quit: + return nil + } + } + }), nil +} + +// ParseSettleOut is a log parse operation binding the contract event 0x1e4a1a8ad772d3f0dbb387879bc5e8faadf16e0513bf77d50620741ab92b4c45. +// +// Solidity: event SettleOut(bytes32 splitOrderId, bytes32 indexed orderId, address indexed liquidityProvider, uint64 settlePercent, uint64 rebatePercent) +func (_Gateway *GatewayFilterer) ParseSettleOut(log types.Log) (*GatewaySettleOut, error) { + event := new(GatewaySettleOut) + if err := _Gateway.contract.UnpackLog(event, "SettleOut", log); err != nil { + return nil, err + } + event.Raw = log + return event, nil +} + // GatewayTokenFeeSettingsUpdatedIterator is returned from FilterTokenFeeSettingsUpdated and is used to iterate over the raw logs and unpacked data for TokenFeeSettingsUpdated events raised by the Gateway contract. type GatewayTokenFeeSettingsUpdatedIterator struct { Event *GatewayTokenFeeSettingsUpdated // Event containing the contract specifics and raw log diff --git a/services/engine.go b/services/engine.go index e6cf8989e..32a94adea 100644 --- a/services/engine.go +++ b/services/engine.go @@ -506,8 +506,12 @@ func (s *EngineService) CreateGatewayWebhook() error { "abi": "{\"inputs\":[{\"indexed\":true,\"internalType\":\"address\",\"name\":\"sender\",\"type\":\"address\"},{\"indexed\":true,\"internalType\":\"address\",\"name\":\"token\",\"type\":\"address\"},{\"indexed\":true,\"internalType\":\"uint256\",\"name\":\"amount\",\"type\":\"uint256\"},{\"indexed\":false,\"internalType\":\"uint256\",\"name\":\"protocolFee\",\"type\":\"uint256\"},{\"indexed\":false,\"internalType\":\"bytes32\",\"name\":\"orderId\",\"type\":\"bytes32\"},{\"indexed\":false,\"internalType\":\"uint256\",\"name\":\"rate\",\"type\":\"uint256\"},{\"indexed\":false,\"internalType\":\"string\",\"name\":\"messageHash\",\"type\":\"string\"}],\"name\":\"OrderCreated\",\"type\":\"event\"}", }, { - "sig_hash": "0x57c683de2e7c8263c7f57fd108416b9bdaa7a6e7f2e4e7102c3b6f9e37f1cc37", // OrderSettled event signature - "abi": "{\"inputs\":[{\"indexed\":false,\"internalType\":\"bytes32\",\"name\":\"splitOrderId\",\"type\":\"bytes32\"},{\"indexed\":true,\"internalType\":\"bytes32\",\"name\":\"orderId\",\"type\":\"bytes32\"},{\"indexed\":true,\"internalType\":\"address\",\"name\":\"liquidityProvider\",\"type\":\"address\"},{\"indexed\":false,\"internalType\":\"uint64\",\"name\":\"settlePercent\",\"type\":\"uint64\"},{\"indexed\":false,\"internalType\":\"uint64\",\"name\":\"rebatePercent\",\"type\":\"uint64\"}],\"name\":\"OrderSettled\",\"type\":\"event\"}", + "sig_hash": "0x1e4a1a8ad772d3f0dbb387879bc5e8faadf16e0513bf77d50620741ab92b4c45", // SettleOut (offramp) event signature + "abi": "{\"inputs\":[{\"indexed\":false,\"internalType\":\"bytes32\",\"name\":\"splitOrderId\",\"type\":\"bytes32\"},{\"indexed\":true,\"internalType\":\"bytes32\",\"name\":\"orderId\",\"type\":\"bytes32\"},{\"indexed\":true,\"internalType\":\"address\",\"name\":\"liquidityProvider\",\"type\":\"address\"},{\"indexed\":false,\"internalType\":\"uint64\",\"name\":\"settlePercent\",\"type\":\"uint64\"},{\"indexed\":false,\"internalType\":\"uint64\",\"name\":\"rebatePercent\",\"type\":\"uint64\"}],\"name\":\"SettleOut\",\"type\":\"event\"}", + }, + { + "sig_hash": "0x44de25d68888fdbe51bc67bbc990724fb5fa28119062e5f4ca623aefcaa70ecb", // SettleIn (onramp) event signature + "abi": "{\"inputs\":[{\"indexed\":true,\"internalType\":\"bytes32\",\"name\":\"orderId\",\"type\":\"bytes32\"},{\"indexed\":true,\"internalType\":\"address\",\"name\":\"liquidityProvider\",\"type\":\"address\"},{\"indexed\":true,\"internalType\":\"address\",\"name\":\"recipient\",\"type\":\"address\"},{\"indexed\":false,\"internalType\":\"uint256\",\"name\":\"amount\",\"type\":\"uint256\"},{\"indexed\":false,\"internalType\":\"address\",\"name\":\"token\",\"type\":\"address\"},{\"indexed\":false,\"internalType\":\"uint256\",\"name\":\"aggregatorFee\",\"type\":\"uint256\"},{\"indexed\":false,\"internalType\":\"uint96\",\"name\":\"rate\",\"type\":\"uint96\"}],\"name\":\"SettleIn\",\"type\":\"event\"}", }, { "sig_hash": "0x0736fe428e1747ca8d387c2e6fa1a31a0cde62d3a167c40a46ade59a3cdc828e", // OrderRefunded event signature @@ -776,10 +780,11 @@ func (s *EngineService) GetContractEventsRPC(ctx context.Context, rpcEndpoint st // If transfer event signature is provided, filter for transfer events eventSignatures = []string{utils.TransferEventSignature} } else { - // Default to gateway event signatures + // Default to gateway event signatures (SettleOut = offramp, SettleIn = onramp) eventSignatures = []string{ utils.OrderCreatedEventSignature, - utils.OrderSettledEventSignature, + utils.SettleOutEventSignature, + utils.SettleInEventSignature, utils.OrderRefundedEventSignature, } } @@ -982,18 +987,18 @@ func (s *EngineService) TransferToken(ctx context.Context, chainID int64, fromAd // Prepare the contract call parameters contractParams := map[string]interface{}{ "contractAddress": tokenAddress, - "method": "transfer", - "params": []interface{}{toAddress, amount}, - "abi": transferABI, - "value": "0", // No ETH value for ERC-20 transfers + "method": "transfer", + "params": []interface{}{toAddress, amount}, + "abi": transferABI, + "value": "0", // No ETH value for ERC-20 transfers } // Prepare execution options executionOptions := map[string]interface{}{ - "chainId": fmt.Sprintf("%d", chainID), - "idempotencyKey": idempotencyKey, - "from": fromAddress, - "type": "auto", + "chainId": fmt.Sprintf("%d", chainID), + "idempotencyKey": idempotencyKey, + "from": fromAddress, + "type": "auto", } // Prepare the request payload diff --git a/services/indexer/evm.go b/services/indexer/evm.go index 4e418c5ec..a9d39155a 100644 --- a/services/indexer/evm.go +++ b/services/indexer/evm.go @@ -456,7 +456,7 @@ func (s *IndexerEVM) indexReceiveAddressByUserAddressWithBypass(ctx context.Cont return eventCounts, nil } -// IndexGateway indexes all gateway events (OrderCreated, OrderSettled, OrderRefunded) in one efficient call +// IndexGateway indexes all gateway events (OrderCreated, SettleOut, SettleIn, OrderRefunded) in one efficient call func (s *IndexerEVM) IndexGateway(ctx context.Context, network *ent.Network, address string, fromBlock int64, toBlock int64, txHash string) (*types.EventCounts, error) { eventCounts := &types.EventCounts{} @@ -479,8 +479,8 @@ func (s *IndexerEVM) IndexGateway(ctx context.Context, network *ent.Network, add return eventCounts, nil } -// IndexProviderAddress indexes OrderSettled events for a provider address -func (s *IndexerEVM) IndexProviderAddress(ctx context.Context, network *ent.Network, address string, fromBlock int64, toBlock int64, txHash string) (*types.EventCounts, error) { +// IndexProviderSettlementAddress indexes SettleOut events for a provider address +func (s *IndexerEVM) IndexProviderSettlementAddress(ctx context.Context, network *ent.Network, address string, fromBlock int64, toBlock int64, txHash string) (*types.EventCounts, error) { eventCounts := &types.EventCounts{} if txHash != "" { @@ -567,7 +567,8 @@ func (s *IndexerEVM) indexGatewayByTransaction(ctx context.Context, network *ent // Process all events in a single pass orderCreatedEvents := []*types.OrderCreatedEvent{} - orderSettledEvents := []*types.OrderSettledEvent{} + settleOutEvents := []*types.SettleOutEvent{} + settleInEvents := []*types.SettleInEvent{} orderRefundedEvents := []*types.OrderRefundedEvent{} // Use GetContractEventsWithFallback to try Thirdweb first and fall back to RPC @@ -726,8 +727,8 @@ func (s *IndexerEVM) indexGatewayByTransaction(ctx context.Context, network *ent } orderCreatedEvents = append(orderCreatedEvents, createdEvent) - case utils.OrderSettledEventSignature: - // Safely extract required fields for OrderSettled + case utils.SettleOutEventSignature: + // Safely extract required fields for SettleOut settlePercentStr, ok := nonIndexedParams["settlePercent"].(string) if !ok || settlePercentStr == "" { continue @@ -761,7 +762,7 @@ func (s *IndexerEVM) indexGatewayByTransaction(ctx context.Context, network *ent continue } - settledEvent := &types.OrderSettledEvent{ + settledEvent := &types.SettleOutEvent{ BlockNumber: blockNumber, TxHash: txHashFromEvent, SplitOrderId: splitOrderIdStr, @@ -770,7 +771,77 @@ func (s *IndexerEVM) indexGatewayByTransaction(ctx context.Context, network *ent SettlePercent: settlePercent, RebatePercent: rebatePercent, } - orderSettledEvents = append(orderSettledEvents, settledEvent) + settleOutEvents = append(settleOutEvents, settledEvent) + + case utils.SettleInEventSignature: + // SettleIn (onramp) event + orderIdStr, ok := indexedParams["orderId"].(string) + if !ok || orderIdStr == "" { + continue + } + liquidityProviderStr, _ := indexedParams["liquidityProvider"].(string) + recipientStr, ok := indexedParams["recipient"].(string) + if !ok || recipientStr == "" { + continue + } + amountStr, ok := nonIndexedParams["amount"].(string) + if !ok || amountStr == "" { + continue + } + amount, err := decimal.NewFromString(amountStr) + if err != nil { + continue + } + tokenStr, ok := nonIndexedParams["token"].(string) + if !ok { + tokenStr = "" + } + aggregatorFeeStr, _ := nonIndexedParams["aggregatorFee"].(string) + var aggregatorFee decimal.Decimal + if aggregatorFeeStr == "" { + aggregatorFee = decimal.Zero + } else { + var parseErr error + aggregatorFee, parseErr = decimal.NewFromString(aggregatorFeeStr) + if parseErr != nil { + logger.WithFields(logger.Fields{ + "Error": fmt.Sprintf("%v", parseErr), + "aggregatorFee": aggregatorFeeStr, + "orderId": orderIdStr, + "txHash": txHashFromEvent, + }).Warnf("SettleIn: failed to parse aggregatorFee, skipping event") + continue + } + } + rateStr, _ := nonIndexedParams["rate"].(string) + var rate decimal.Decimal + if rateStr == "" { + rate = decimal.Zero + } else { + var parseErr error + rate, parseErr = decimal.NewFromString(rateStr) + if parseErr != nil { + logger.WithFields(logger.Fields{ + "Error": fmt.Sprintf("%v", parseErr), + "rate": rateStr, + "orderId": orderIdStr, + "txHash": txHashFromEvent, + }).Warnf("SettleIn: failed to parse rate, skipping event") + continue + } + } + settleInEvent := &types.SettleInEvent{ + BlockNumber: blockNumber, + TxHash: txHashFromEvent, + OrderId: orderIdStr, + LiquidityProvider: ethcommon.HexToAddress(liquidityProviderStr).Hex(), + Amount: amount, + Recipient: ethcommon.HexToAddress(recipientStr).Hex(), + Token: ethcommon.HexToAddress(tokenStr).Hex(), + AggregatorFee: aggregatorFee, + Rate: rate, + } + settleInEvents = append(settleInEvents, settleInEvent) case utils.OrderRefundedEventSignature: // Safely extract required fields for OrderRefunded @@ -817,24 +888,43 @@ func (s *IndexerEVM) indexGatewayByTransaction(ctx context.Context, network *ent } eventCounts.OrderCreated = len(orderCreatedEvents) - // Process OrderSettled events - if len(orderSettledEvents) > 0 { + // Process SettleOut (offramp) events + if len(settleOutEvents) > 0 { + orderIds := []string{} + orderIdToEvent := make(map[string]*types.SettleOutEvent) + for _, event := range settleOutEvents { + orderIds = append(orderIds, event.OrderId) + orderIdToEvent[event.OrderId] = event + } + err := common.ProcessSettleOutOrders(ctx, network, orderIds, orderIdToEvent) + if err != nil { + logger.Errorf("Failed to process SettleOut events: %v", err) + } else { + if network.ChainID != 56 && network.ChainID != 1135 { + logger.Infof("Successfully processed %d SettleOut events", len(settleOutEvents)) + } + } + } + eventCounts.SettleOut = len(settleOutEvents) + + // Process SettleIn (onramp) events + if len(settleInEvents) > 0 { orderIds := []string{} - orderIdToEvent := make(map[string]*types.OrderSettledEvent) - for _, event := range orderSettledEvents { + orderIdToEvent := make(map[string]*types.SettleInEvent) + for _, event := range settleInEvents { orderIds = append(orderIds, event.OrderId) orderIdToEvent[event.OrderId] = event } - err := common.ProcessSettledOrders(ctx, network, orderIds, orderIdToEvent) + err := common.ProcessSettleInOrders(ctx, network, orderIds, orderIdToEvent) if err != nil { - logger.Errorf("Failed to process OrderSettled events: %v", err) + logger.Errorf("Failed to process SettleIn events: %v", err) } else { if network.ChainID != 56 && network.ChainID != 1135 { - logger.Infof("Successfully processed %d OrderSettled events", len(orderSettledEvents)) + logger.Infof("Successfully processed %d SettleIn events", len(settleInEvents)) } } } - eventCounts.OrderSettled = len(orderSettledEvents) + eventCounts.SettleIn = len(settleInEvents) // Process OrderRefunded events if len(orderRefundedEvents) > 0 { @@ -858,18 +948,18 @@ func (s *IndexerEVM) indexGatewayByTransaction(ctx context.Context, network *ent return eventCounts, nil } -// indexProviderAddressByTransaction processes a specific transaction for provider address OrderSettled events +// indexProviderAddressByTransaction processes a specific transaction for provider address SettleOut events func (s *IndexerEVM) indexProviderAddressByTransaction(ctx context.Context, network *ent.Network, providerAddress string, txHash string) (*types.EventCounts, error) { eventCounts := &types.EventCounts{} - // Get OrderSettled events for this transaction + // Get SettleOut events for this transaction events, err := s.engineService.GetContractEventsWithFallback( ctx, network, network.GatewayContractAddress, 0, 0, - []string{utils.OrderSettledEventSignature}, + []string{utils.SettleOutEventSignature}, txHash, map[string]string{ "filter_transaction_hash": txHash, @@ -880,13 +970,13 @@ func (s *IndexerEVM) indexProviderAddressByTransaction(ctx context.Context, netw ) if err != nil { if err.Error() == "no events found" { - return eventCounts, nil // No OrderSettled events found for this transaction + return eventCounts, nil // No SettleOut events found for this transaction } - return eventCounts, fmt.Errorf("error getting OrderSettled events for transaction %s: %w", txHash[:10]+"...", err) + return eventCounts, fmt.Errorf("error getting SettleOut events for transaction %s: %w", txHash[:10]+"...", err) } - // Process OrderSettled events for the specific provider address - orderSettledEvents := []*types.OrderSettledEvent{} + // Process SettleOut events for the specific provider address + settleOutEvents := []*types.SettleOutEvent{} for _, event := range events { eventMap := event.(map[string]interface{}) @@ -944,6 +1034,15 @@ func (s *IndexerEVM) indexProviderAddressByTransaction(ctx context.Context, netw continue } + rebatePercentStr, ok := nonIndexedParams["rebatePercent"].(string) + if !ok || rebatePercentStr == "" { + continue + } + rebatePercent, err := decimal.NewFromString(rebatePercentStr) + if err != nil { + continue + } + splitOrderId, ok := nonIndexedParams["splitOrderId"].(string) if !ok || splitOrderId == "" { continue @@ -954,40 +1053,41 @@ func (s *IndexerEVM) indexProviderAddressByTransaction(ctx context.Context, netw continue } - settledEvent := &types.OrderSettledEvent{ + settledEvent := &types.SettleOutEvent{ BlockNumber: blockNumber, TxHash: txHash, SplitOrderId: splitOrderId, OrderId: orderId, LiquidityProvider: liquidityProvider, SettlePercent: settlePercent, + RebatePercent: rebatePercent, } - orderSettledEvents = append(orderSettledEvents, settledEvent) + settleOutEvents = append(settleOutEvents, settledEvent) } - // Process OrderSettled events - if len(orderSettledEvents) > 0 { + // Process SettleOut events + if len(settleOutEvents) > 0 { orderIds := []string{} - orderIdToEvent := make(map[string]*types.OrderSettledEvent) - for _, event := range orderSettledEvents { + orderIdToEvent := make(map[string]*types.SettleOutEvent) + for _, event := range settleOutEvents { orderIds = append(orderIds, event.OrderId) orderIdToEvent[event.OrderId] = event } - err = common.ProcessSettledOrders(ctx, network, orderIds, orderIdToEvent) + err = common.ProcessSettleOutOrders(ctx, network, orderIds, orderIdToEvent) if err != nil { - logger.Errorf("Failed to process OrderSettled events: %v", err) + logger.Errorf("Failed to process SettleOut events: %v", err) } else { if network.ChainID != 56 && network.ChainID != 1135 { - logger.Infof("Successfully processed %d OrderSettled events for provider %s", len(orderSettledEvents), providerAddress) + logger.Infof("Successfully processed %d SettleOut events for provider %s", len(settleOutEvents), providerAddress) } } } - eventCounts.OrderSettled = len(orderSettledEvents) + eventCounts.SettleOut = len(settleOutEvents) return eventCounts, nil } -// indexProviderAddressByAddress processes provider address's transaction history for OrderSettled events +// indexProviderAddressByAddress processes provider address's transaction history for SettleOut events func (s *IndexerEVM) indexProviderAddressByAddress(ctx context.Context, network *ent.Network, providerAddress string, fromBlock int64, toBlock int64) error { // Determine parameters based on whether block range is provided var limit int @@ -1025,7 +1125,7 @@ func (s *IndexerEVM) indexProviderAddressByAddress(ctx context.Context, network logger.Infof("%s", logMessage) } - // Process each transaction to find OrderSettled events + // Process each transaction to find SettleOut events for i, tx := range transactions { txHash, ok := tx["hash"].(string) if !ok || txHash == "" { diff --git a/services/indexer/starknet.go b/services/indexer/starknet.go index f7b05b97c..40c907b5e 100644 --- a/services/indexer/starknet.go +++ b/services/indexer/starknet.go @@ -498,7 +498,7 @@ func (s *IndexerStarknet) indexGatewayByTransaction(ctx context.Context, network orderIDs = append(orderIDs, event.OrderId) orderIDToEvent[event.OrderId] = event } - err := common.ProcessSettledOrders(ctx, network, orderIDs, orderIDToEvent) + err := common.ProcessOrderSettledOrders(ctx, network, orderIDs, orderIDToEvent) if err != nil { logger.Errorf("Failed to process OrderSettled events: %v", err) } else { @@ -584,8 +584,8 @@ func (s *IndexerStarknet) IndexGateway(ctx context.Context, network *ent.Network return eventCounts, nil } -// IndexProviderAddress indexes settlement events from providers -func (s *IndexerStarknet) IndexProviderAddress(ctx context.Context, network *ent.Network, address string, fromBlock int64, toBlock int64, txHash string) (*types.EventCounts, error) { +// IndexProviderSettlementAddress indexes settlement events from providers +func (s *IndexerStarknet) IndexProviderSettlementAddress(ctx context.Context, network *ent.Network, address string, fromBlock int64, toBlock int64, txHash string) (*types.EventCounts, error) { eventCounts := &types.EventCounts{} var transactionHashReceipt []map[string]interface{} @@ -750,7 +750,7 @@ func (s *IndexerStarknet) indexProviderAddressByTransaction(ctx context.Context, orderIds = append(orderIds, event.OrderId) orderIdToEvent[event.OrderId] = event } - err := common.ProcessSettledOrders(ctx, network, orderIds, orderIdToEvent) + err := common.ProcessOrderSettledOrders(ctx, network, orderIds, orderIdToEvent) if err != nil { logger.Errorf("Failed to process OrderSettled events: %v", err) } else { diff --git a/services/indexer/starknet_test.go b/services/indexer/starknet_test.go index fbe31c254..b6c75a54c 100644 --- a/services/indexer/starknet_test.go +++ b/services/indexer/starknet_test.go @@ -208,8 +208,8 @@ func TestIndexerStarknet_IndexGateway(t *testing.T) { }) } -// TestIndexerStarknet_IndexProviderAddress tests the IndexProviderAddress function with a real settlement transaction -func TestIndexerStarknet_IndexProviderAddress(t *testing.T) { +// TestIndexerStarknet_IndexProviderSettlementAddress tests the IndexProviderSettlementAddress function with a real settlement transaction +func TestIndexerStarknet_IndexProviderSettlementAddress(t *testing.T) { if testing.Short() { t.Skip("Skipping integration test that requires network access") } @@ -229,7 +229,7 @@ func TestIndexerStarknet_IndexProviderAddress(t *testing.T) { network := createMockNetwork() t.Run("Index Provider Settlement", func(t *testing.T) { - eventCounts, err := indexer.IndexProviderAddress( + eventCounts, err := indexer.IndexProviderSettlementAddress( ctx, network, providerAddress, @@ -250,7 +250,7 @@ func TestIndexerStarknet_IndexProviderAddress(t *testing.T) { t.Run("Index Non-Provider Transaction", func(t *testing.T) { // Try to index OrderCreated transaction for provider (should find nothing) - eventCounts, err := indexer.IndexProviderAddress( + eventCounts, err := indexer.IndexProviderSettlementAddress( ctx, network, providerAddress, @@ -300,8 +300,8 @@ func TestIndexerStarknet_Integration(t *testing.T) { require.NoError(t, err, "Failed to index order creation") t.Logf("✓ Step 2 - OrderCreated indexed: %d events", orderCreatedCounts.OrderCreated) - // Step 3: Provider settles the order (IndexProviderAddress) - settlementCounts, err := indexer.IndexProviderAddress(ctx, network, providerAddress, 0, 0, txHashOrderSettled) + // Step 3: Provider settles the order (IndexProviderSettlementAddress) + settlementCounts, err := indexer.IndexProviderSettlementAddress(ctx, network, providerAddress, 0, 0, txHashOrderSettled) require.NoError(t, err, "Failed to index provider settlement") t.Logf("✓ Step 3 - OrderSettled indexed: %d events", settlementCounts.OrderSettled) diff --git a/services/indexer/tron.go b/services/indexer/tron.go index 07e811600..7ec43b0a0 100644 --- a/services/indexer/tron.go +++ b/services/indexer/tron.go @@ -9,6 +9,7 @@ import ( "strings" "time" + ethcommon "github.com/ethereum/go-ethereum/common" fastshot "github.com/opus-domini/fast-shot" "github.com/paycrest/aggregator/ent" "github.com/paycrest/aggregator/services" @@ -155,7 +156,7 @@ func (s *IndexerTron) indexReceiveAddressByUserAddressInRange(ctx context.Contex return nil } -// IndexGateway indexes all Gateway contract events (OrderCreated, OrderSettled, OrderRefunded) in a single call +// IndexGateway indexes all Gateway contract events (OrderCreated, SettleOut, SettleIn, OrderRefunded) in a single call func (s *IndexerTron) IndexGateway(ctx context.Context, network *ent.Network, address string, fromBlock int64, toBlock int64, txHash string) (*types.EventCounts, error) { eventCounts := &types.EventCounts{} if txHash != "" { @@ -178,7 +179,8 @@ func (s *IndexerTron) IndexGateway(ctx context.Context, network *ent.Network, ad // Process all events from this transaction orderCreatedEvents := []*types.OrderCreatedEvent{} - orderSettledEvents := []*types.OrderSettledEvent{} + settleOutEvents := []*types.SettleOutEvent{} + settleInEvents := []*types.SettleInEvent{} orderRefundedEvents := []*types.OrderRefundedEvent{} for _, event := range data["log"].([]interface{}) { @@ -216,14 +218,14 @@ func (s *IndexerTron) IndexGateway(ctx context.Context, network *ent.Network, ad } orderCreatedEvents = append(orderCreatedEvents, createdEvent) - case "57c683de2e7c8263c7f57fd108416b9bdaa7a6e7f2e4e7102c3b6f9e37f1cc37": // OrderSettled - unpackedEventData, err := utils.UnpackEventData(eventData["data"].(string), contracts.GatewayMetaData.ABI, "OrderSettled") + case "1e4a1a8ad772d3f0dbb387879bc5e8faadf16e0513bf77d50620741ab92b4c45": // SettleOut (offramp) + unpackedEventData, err := utils.UnpackEventData(eventData["data"].(string), contracts.GatewayMetaData.ABI, "SettleOut") if err != nil { logger.WithFields(logger.Fields{ "Error": fmt.Sprintf("%v", err), "TxHash": data["id"].(string), "Network": network.Identifier, - }).Errorf("Failed to unpack OrderSettled event data for %s", network.Identifier) + }).Errorf("Failed to unpack SettleOut event data for %s", network.Identifier) continue } @@ -233,7 +235,7 @@ func (s *IndexerTron) IndexGateway(ctx context.Context, network *ent.Network, ad eventOrderId := utils.ParseTopicToByte32Flexible(eventData["topics"].([]interface{})[1]) liquidityProvider := utils.ParseTopicToTronAddress(eventData["topics"].([]interface{})[2].(string)) - settledEvent := &types.OrderSettledEvent{ + settledEvent := &types.SettleOutEvent{ BlockNumber: int64(data["blockNumber"].(float64)), TxHash: data["id"].(string), SplitOrderId: splitOrderId, @@ -242,7 +244,48 @@ func (s *IndexerTron) IndexGateway(ctx context.Context, network *ent.Network, ad SettlePercent: utils.FromSubunit(settlePercent, 0), RebatePercent: utils.FromSubunit(rebatePercent, 0), } - orderSettledEvents = append(orderSettledEvents, settledEvent) + settleOutEvents = append(settleOutEvents, settledEvent) + + case "44de25d68888fdbe51bc67bbc990724fb5fa28119062e5f4ca623aefcaa70ecb": // SettleIn (onramp) + unpackedEventData, err := utils.UnpackEventData(eventData["data"].(string), contracts.GatewayMetaData.ABI, "SettleIn") + if err != nil { + logger.WithFields(logger.Fields{ + "Error": fmt.Sprintf("%v", err), + "TxHash": data["id"].(string), + "Network": network.Identifier, + }).Errorf("Failed to unpack SettleIn event data for %s", network.Identifier) + continue + } + // SettleIn: indexed orderId, liquidityProvider, recipient; data: amount, token, aggregatorFee, rate + eventOrderId := utils.ParseTopicToByte32Flexible(eventData["topics"].([]interface{})[1]) + liquidityProviderStr := utils.ParseTopicToTronAddress(eventData["topics"].([]interface{})[2].(string)) + recipientStr := utils.ParseTopicToTronAddress(eventData["topics"].([]interface{})[3].(string)) + amountBig, _ := unpackedEventData[0].(*big.Int) + if amountBig == nil { + amountBig = new(big.Int) + } + tokenVal := unpackedEventData[1] + tokenStr := "" + if addr, ok := tokenVal.(ethcommon.Address); ok { + tokenStr = utils.ParseTopicToTronAddress(strings.TrimPrefix(addr.Hex(), "0x")) + } else if b, ok := tokenVal.([]byte); ok && len(b) >= 20 { + tokenStr = utils.ParseTopicToTronAddress(hex.EncodeToString(b[len(b)-20:])) + } + aggregatorFee := unpackedEventData[2].(*big.Int) + rate := unpackedEventData[3].(*big.Int) + orderIdStr := fmt.Sprintf("0x%v", hex.EncodeToString(eventOrderId[:])) + settleInEvent := &types.SettleInEvent{ + BlockNumber: int64(data["blockNumber"].(float64)), + TxHash: data["id"].(string), + OrderId: orderIdStr, + LiquidityProvider: liquidityProviderStr, + Amount: utils.FromSubunit(amountBig, 0), + Recipient: recipientStr, + Token: tokenStr, + AggregatorFee: utils.FromSubunit(aggregatorFee, 0), + Rate: utils.FromSubunit(rate, 0), + } + settleInEvents = append(settleInEvents, settleInEvent) case "0736fe428e1747ca8d387c2e6fa1a31a0cde62d3a167c40a46ade59a3cdc828e": // OrderRefunded unpackedEventData, err := utils.UnpackEventData(eventData["data"].(string), contracts.GatewayMetaData.ABI, "OrderRefunded") @@ -284,18 +327,33 @@ func (s *IndexerTron) IndexGateway(ctx context.Context, network *ent.Network, ad } } - if len(orderSettledEvents) > 0 { - txHashes := []string{} - hashToEvent := make(map[string]*types.OrderSettledEvent) - for _, event := range orderSettledEvents { - txHashes = append(txHashes, event.TxHash) - hashToEvent[event.TxHash] = event + if len(settleOutEvents) > 0 { + orderIds := []string{} + orderIdToEvent := make(map[string]*types.SettleOutEvent) + for _, event := range settleOutEvents { + orderIds = append(orderIds, event.OrderId) + orderIdToEvent[event.OrderId] = event } - err = common.ProcessSettledOrders(ctx, network, txHashes, hashToEvent) + err = common.ProcessSettleOutOrders(ctx, network, orderIds, orderIdToEvent) if err != nil { - logger.Errorf("Failed to process OrderSettled events: %v", err) + logger.Errorf("Failed to process SettleOut events: %v", err) } else { - logger.Infof("Successfully processed %d OrderSettled events", len(orderSettledEvents)) + logger.Infof("Successfully processed %d SettleOut events", len(settleOutEvents)) + } + } + + if len(settleInEvents) > 0 { + orderIds := []string{} + orderIdToEvent := make(map[string]*types.SettleInEvent) + for _, event := range settleInEvents { + orderIds = append(orderIds, event.OrderId) + orderIdToEvent[event.OrderId] = event + } + err = common.ProcessSettleInOrders(ctx, network, orderIds, orderIdToEvent) + if err != nil { + logger.Errorf("Failed to process SettleIn events: %v", err) + } else { + logger.Infof("Successfully processed %d SettleIn events", len(settleInEvents)) } } @@ -332,15 +390,26 @@ func (s *IndexerTron) IndexGateway(ctx context.Context, network *ent.Network, ad }).Errorf("Failed to index OrderCreated events") } - // Index OrderSettled events - if err := s.indexOrderSettledByBlockRange(ctx, network, fromBlock, toBlock); err != nil { + // Index SettleOut events + if err := s.indexSettleOutByBlockRange(ctx, network, fromBlock, toBlock); err != nil { + logger.WithFields(logger.Fields{ + "Error": fmt.Sprintf("%v", err), + "NetworkParam": network.Identifier, + "FromBlock": fromBlock, + "ToBlock": toBlock, + "EventType": "SettleOut", + }).Errorf("Failed to index SettleOut events") + } + + // Index SettleIn events + if err := s.indexSettleInByBlockRange(ctx, network, fromBlock, toBlock); err != nil { logger.WithFields(logger.Fields{ "Error": fmt.Sprintf("%v", err), "NetworkParam": network.Identifier, "FromBlock": fromBlock, "ToBlock": toBlock, - "EventType": "OrderSettled", - }).Errorf("Failed to index OrderSettled events") + "EventType": "SettleIn", + }).Errorf("Failed to index SettleIn events") } // Index OrderRefunded events @@ -357,8 +426,8 @@ func (s *IndexerTron) IndexGateway(ctx context.Context, network *ent.Network, ad return eventCounts, nil } -// IndexProviderAddress indexes OrderSettled events for a provider address -func (s *IndexerTron) IndexProviderAddress(ctx context.Context, network *ent.Network, address string, fromBlock int64, toBlock int64, txHash string) (*types.EventCounts, error) { +// IndexProviderSettlementAddress indexes SettleOut events for a provider address +func (s *IndexerTron) IndexProviderSettlementAddress(ctx context.Context, network *ent.Network, address string, fromBlock int64, toBlock int64, txHash string) (*types.EventCounts, error) { eventCounts := &types.EventCounts{} // For Tron, we need to implement a different approach since we don't have provider address transaction history @@ -439,8 +508,8 @@ func (s *IndexerTron) indexOrderCreatedByBlockRange(ctx context.Context, network return nil } -// indexOrderSettledByBlockRange indexes OrderSettled events for a block range -func (s *IndexerTron) indexOrderSettledByBlockRange(ctx context.Context, network *ent.Network, fromBlock int64, toBlock int64) error { +// indexSettleOutByBlockRange indexes SettleOut events for a block range +func (s *IndexerTron) indexSettleOutByBlockRange(ctx context.Context, network *ent.Network, fromBlock int64, toBlock int64) error { res, err := fastshot.NewClient(network.RPCEndpoint). Config().SetTimeout(15 * time.Second). Build().GET(fmt.Sprintf("/v1/contracts/%s/events", network.GatewayContractAddress)). @@ -452,32 +521,32 @@ func (s *IndexerTron) indexOrderSettledByBlockRange(ctx context.Context, network }). Send() if err != nil { - return fmt.Errorf("indexOrderSettledByBlockRange.getEvents: %w", err) + return fmt.Errorf("indexSettleOutByBlockRange.getEvents: %w", err) } data, err := utils.ParseJSONResponse(res.RawResponse) if err != nil { - return fmt.Errorf("indexOrderSettledByBlockRange.parseJSONResponse: %w", err) + return fmt.Errorf("indexSettleOutByBlockRange.parseJSONResponse: %w", err) } - txHashes := []string{} - hashToEvent := make(map[string]*types.OrderSettledEvent) + orderIds := []string{} + orderIdToEvent := make(map[string]*types.SettleOutEvent) for _, r := range data["data"].([]interface{}) { - if r.(map[string]interface{})["event_name"].(string) == "OrderSettled" { + if r.(map[string]interface{})["event_name"].(string) == "SettleOut" { res, err := fastshot.NewClient(network.RPCEndpoint). Config().SetTimeout(15 * time.Second). Build().POST("/wallet/gettransactioninfobyid"). Body().AsJSON(map[string]interface{}{"value": r.(map[string]interface{})["transaction_id"].(string)}). Send() if err != nil { - return fmt.Errorf("indexOrderSettledByBlockRange.getTransaction: %w", err) + return fmt.Errorf("indexSettleOutByBlockRange.getTransaction: %w", err) } data, err := utils.ParseJSONResponse(res.RawResponse) if err != nil { - return fmt.Errorf("indexOrderSettledByBlockRange.parseJSONResponse: %w", err) + return fmt.Errorf("indexSettleOutByBlockRange.parseJSONResponse: %w", err) } for _, event := range data["log"].([]interface{}) { eventData := event.(map[string]interface{}) - if eventData["topics"].([]interface{})[0] == "57c683de2e7c8263c7f57fd108416b9bdaa7a6e7f2e4e7102c3b6f9e37f1cc37" { - unpackedEventData, err := utils.UnpackEventData(eventData["data"].(string), contracts.GatewayMetaData.ABI, "OrderSettled") + if eventData["topics"].([]interface{})[0] == "1e4a1a8ad772d3f0dbb387879bc5e8faadf16e0513bf77d50620741ab92b4c45" { + unpackedEventData, err := utils.UnpackEventData(eventData["data"].(string), contracts.GatewayMetaData.ABI, "SettleOut") if err != nil { continue } @@ -486,7 +555,7 @@ func (s *IndexerTron) indexOrderSettledByBlockRange(ctx context.Context, network rebatePercent := unpackedEventData[2].(*big.Int) eventOrderId := utils.ParseTopicToByte32Flexible(eventData["topics"].([]interface{})[1]) liquidityProvider := utils.ParseTopicToTronAddress(eventData["topics"].([]interface{})[2].(string)) - settledEvent := &types.OrderSettledEvent{ + settledEvent := &types.SettleOutEvent{ BlockNumber: int64(data["blockNumber"].(float64)), TxHash: data["id"].(string), SplitOrderId: splitOrderId, @@ -495,19 +564,106 @@ func (s *IndexerTron) indexOrderSettledByBlockRange(ctx context.Context, network SettlePercent: utils.FromSubunit(settlePercent, 0), RebatePercent: utils.FromSubunit(rebatePercent, 0), } - txHashes = append(txHashes, settledEvent.TxHash) - hashToEvent[settledEvent.TxHash] = settledEvent + orderIds = append(orderIds, settledEvent.OrderId) + orderIdToEvent[settledEvent.OrderId] = settledEvent break } } } } - if len(txHashes) == 0 { + if len(orderIds) == 0 { + return nil + } + err = common.ProcessSettleOutOrders(ctx, network, orderIds, orderIdToEvent) + if err != nil { + return fmt.Errorf("indexSettleOutByBlockRange.processSettleOutOrders: %w", err) + } + return nil +} + +// indexSettleInByBlockRange indexes SettleIn events for a block range +func (s *IndexerTron) indexSettleInByBlockRange(ctx context.Context, network *ent.Network, fromBlock int64, toBlock int64) error { + res, err := fastshot.NewClient(network.RPCEndpoint). + Config().SetTimeout(15 * time.Second). + Build().GET(fmt.Sprintf("/v1/contracts/%s/events", network.GatewayContractAddress)). + Query().AddParams(map[string]string{ + "min_block_timestamp": strconv.FormatInt(fromBlock, 10), + "max_block_timestamp": strconv.FormatInt(toBlock, 10), + "order_by": "block_timestamp,asc", + "limit": "200", + }). + Send() + if err != nil { + return fmt.Errorf("indexSettleInByBlockRange.getEvents: %w", err) + } + data, err := utils.ParseJSONResponse(res.RawResponse) + if err != nil { + return fmt.Errorf("indexSettleInByBlockRange.parseJSONResponse: %w", err) + } + orderIds := []string{} + orderIdToEvent := make(map[string]*types.SettleInEvent) + for _, r := range data["data"].([]interface{}) { + if r.(map[string]interface{})["event_name"].(string) == "SettleIn" { + res, err := fastshot.NewClient(network.RPCEndpoint). + Config().SetTimeout(15 * time.Second). + Build().POST("/wallet/gettransactioninfobyid"). + Body().AsJSON(map[string]interface{}{"value": r.(map[string]interface{})["transaction_id"].(string)}). + Send() + if err != nil { + return fmt.Errorf("indexSettleInByBlockRange.getTransaction: %w", err) + } + data, err := utils.ParseJSONResponse(res.RawResponse) + if err != nil { + return fmt.Errorf("indexSettleInByBlockRange.parseJSONResponse: %w", err) + } + for _, event := range data["log"].([]interface{}) { + eventData := event.(map[string]interface{}) + if eventData["topics"].([]interface{})[0] == "44de25d68888fdbe51bc67bbc990724fb5fa28119062e5f4ca623aefcaa70ecb" { + unpackedEventData, err := utils.UnpackEventData(eventData["data"].(string), contracts.GatewayMetaData.ABI, "SettleIn") + if err != nil { + continue + } + eventOrderId := utils.ParseTopicToByte32Flexible(eventData["topics"].([]interface{})[1]) + liquidityProviderStr := utils.ParseTopicToTronAddress(eventData["topics"].([]interface{})[2].(string)) + recipientStr := utils.ParseTopicToTronAddress(eventData["topics"].([]interface{})[3].(string)) + amountBig, _ := unpackedEventData[0].(*big.Int) + if amountBig == nil { + amountBig = new(big.Int) + } + tokenVal := unpackedEventData[1] + tokenStr := "" + if addr, ok := tokenVal.(ethcommon.Address); ok { + tokenStr = utils.ParseTopicToTronAddress(strings.TrimPrefix(addr.Hex(), "0x")) + } else if b, ok := tokenVal.([]byte); ok && len(b) >= 20 { + tokenStr = utils.ParseTopicToTronAddress(hex.EncodeToString(b[len(b)-20:])) + } + aggregatorFee := unpackedEventData[2].(*big.Int) + rate := unpackedEventData[3].(*big.Int) + orderIdStr := fmt.Sprintf("0x%v", hex.EncodeToString(eventOrderId[:])) + settleInEvent := &types.SettleInEvent{ + BlockNumber: int64(data["blockNumber"].(float64)), + TxHash: data["id"].(string), + OrderId: orderIdStr, + LiquidityProvider: liquidityProviderStr, + Amount: utils.FromSubunit(amountBig, 0), + Recipient: recipientStr, + Token: tokenStr, + AggregatorFee: utils.FromSubunit(aggregatorFee, 0), + Rate: utils.FromSubunit(rate, 0), + } + orderIds = append(orderIds, settleInEvent.OrderId) + orderIdToEvent[settleInEvent.OrderId] = settleInEvent + break + } + } + } + } + if len(orderIds) == 0 { return nil } - err = common.ProcessSettledOrders(ctx, network, txHashes, hashToEvent) + err = common.ProcessSettleInOrders(ctx, network, orderIds, orderIdToEvent) if err != nil { - return fmt.Errorf("indexOrderSettledByBlockRange.processSettledOrders: %w", err) + return fmt.Errorf("indexSettleInByBlockRange.processSettleInOrders: %w", err) } return nil } diff --git a/services/order/evm.go b/services/order/evm.go index 119fe0dfd..b4d9df8be 100644 --- a/services/order/evm.go +++ b/services/order/evm.go @@ -24,6 +24,7 @@ import ( "github.com/paycrest/aggregator/ent/providerordertoken" "github.com/paycrest/aggregator/ent/providerprofile" tokenent "github.com/paycrest/aggregator/ent/token" + "github.com/paycrest/aggregator/ent/transactionlog" "github.com/paycrest/aggregator/types" "github.com/paycrest/aggregator/utils" cryptoUtils "github.com/paycrest/aggregator/utils/crypto" @@ -156,6 +157,7 @@ func (s *OrderEVM) RefundOrder(ctx context.Context, network *ent.Network, orderI paymentorder.GatewayIDEQ(orderID), paymentorder.StatusNEQ(paymentorder.StatusValidated), paymentorder.StatusNEQ(paymentorder.StatusRefunded), + paymentorder.StatusNEQ(paymentorder.StatusRefunding), paymentorder.StatusNEQ(paymentorder.StatusSettled), paymentorder.StatusNEQ(paymentorder.StatusFulfilling), paymentorder.StatusNEQ(paymentorder.StatusSettling), @@ -192,14 +194,33 @@ func (s *OrderEVM) RefundOrder(ctx context.Context, network *ent.Network, orderI return fmt.Errorf("%s - RefundOrder.sendTransaction: %w", orderIDPrefix, err) } - // Update order status to refunding after transaction submission - _, err = db.Client.PaymentOrder. + // Create order_refunding log (indexer will update to order_refunded when event is indexed) + tx, err := db.Client.Tx(ctx) + if err != nil { + return fmt.Errorf("%s - RefundOrder.tx: %w", orderIDPrefix, err) + } + transactionLog, err := tx.TransactionLog. + Create(). + SetStatus(transactionlog.StatusOrderRefunding). + SetGatewayID(lockOrder.GatewayID). + SetNetwork(lockOrder.Edges.Token.Edges.Network.Identifier). + Save(ctx) + if err != nil { + _ = tx.Rollback() + return fmt.Errorf("%s - RefundOrder.createLog: %w", orderIDPrefix, err) + } + _, err = tx.PaymentOrder. UpdateOneID(lockOrder.ID). SetStatus(paymentorder.StatusRefunding). + AddTransactions(transactionLog). Save(ctx) if err != nil { + _ = tx.Rollback() return fmt.Errorf("%s - RefundOrder.updateStatus: %w", orderIDPrefix, err) } + if err := tx.Commit(); err != nil { + return fmt.Errorf("%s - RefundOrder.commit: %w", orderIDPrefix, err) + } return nil } @@ -285,7 +306,7 @@ func (s *OrderEVM) createOrderCallData(order *ent.PaymentOrder, encryptedOrderRe Rate: order.Rate.Mul(decimal.NewFromInt(100)).BigInt(), SenderFeeRecipient: ethcommon.HexToAddress(order.FeeAddress), SenderFee: utils.ToSubunit(order.SenderFee, order.Edges.Token.Decimals), - RefundAddress: ethcommon.HexToAddress(order.ReturnAddress), + RefundAddress: ethcommon.HexToAddress(order.RefundOrRecipientAddress), MessageHash: encryptedOrderRecipient, } @@ -313,7 +334,7 @@ func (s *OrderEVM) createOrderCallData(order *ent.PaymentOrder, encryptedOrderRe return data, nil } -// settleCallData creates the data for the settle method in the gateway contract +// settleCallData creates the data for the settleOut method in the gateway contract func (s *OrderEVM) settleCallData(ctx context.Context, order *ent.PaymentOrder) ([]byte, error) { gatewayABI, err := abi.JSON(strings.NewReader(contracts.GatewayMetaData.ABI)) if err != nil { @@ -357,9 +378,9 @@ func (s *OrderEVM) settleCallData(ctx context.Context, order *ent.PaymentOrder) splitOrderID := strings.ReplaceAll(order.ID.String(), "-", "") - // Generate calldata for settlement + // Generate calldata for settlement (Gateway.settleOut) data, err := gatewayABI.Pack( - "settle", + "settleOut", utils.StringToByte32(splitOrderID), utils.StringToByte32(string(orderID)), ethcommon.HexToAddress(token.SettlementAddress), @@ -367,7 +388,7 @@ func (s *OrderEVM) settleCallData(ctx context.Context, order *ent.PaymentOrder) uint64(0), // rebatePercent - default to 0 for now ) if err != nil { - return nil, fmt.Errorf("failed to pack settle ABI: %w", err) + return nil, fmt.Errorf("failed to pack settleOut ABI: %w", err) } return data, nil diff --git a/services/order/starknet.go b/services/order/starknet.go index 33b1498b7..0c9f99736 100644 --- a/services/order/starknet.go +++ b/services/order/starknet.go @@ -13,6 +13,7 @@ import ( networkent "github.com/paycrest/aggregator/ent/network" "github.com/paycrest/aggregator/ent/paymentorder" "github.com/paycrest/aggregator/ent/paymentorderfulfillment" + "github.com/paycrest/aggregator/ent/transactionlog" "github.com/paycrest/aggregator/ent/providerordertoken" "github.com/paycrest/aggregator/ent/providerprofile" tokenent "github.com/paycrest/aggregator/ent/token" @@ -112,7 +113,7 @@ func (s *OrderStarknet) CreateOrder(ctx context.Context, orderID uuid.UUID) erro return fmt.Errorf("%s - CreateOrder.parseFeeAddress: %w", orderIDPrefix, err) } senderFeeFelt := new(felt.Felt).SetBigInt(u.ToSubunit(order.SenderFee, order.Edges.Token.Decimals)) - refundAddressFelt, err := utils.HexToFelt(order.ReturnAddress) + refundAddressFelt, err := utils.HexToFelt(order.RefundOrRecipientAddress) if err != nil { return fmt.Errorf("%s - CreateOrder.parseRefundAddress: %w", orderIDPrefix, err) } @@ -225,6 +226,34 @@ func (s *OrderStarknet) RefundOrder(ctx context.Context, network *ent.Network, o return fmt.Errorf("%s - RefundOrder.paymasterExecuteTransaction: %w", orderIDPrefix, err) } + // Create order_refunding log (indexer will update to order_refunded when event is indexed) + tx, err := db.Client.Tx(ctx) + if err != nil { + return fmt.Errorf("%s - RefundOrder.tx: %w", orderIDPrefix, err) + } + transactionLog, err := tx.TransactionLog. + Create(). + SetStatus(transactionlog.StatusOrderRefunding). + SetGatewayID(paymentOrder.GatewayID). + SetNetwork(paymentOrder.Edges.Token.Edges.Network.Identifier). + Save(ctx) + if err != nil { + _ = tx.Rollback() + return fmt.Errorf("%s - RefundOrder.createLog: %w", orderIDPrefix, err) + } + _, err = tx.PaymentOrder. + UpdateOneID(paymentOrder.ID). + SetStatus(paymentorder.StatusRefunding). + AddTransactions(transactionLog). + Save(ctx) + if err != nil { + _ = tx.Rollback() + return fmt.Errorf("%s - RefundOrder.updateStatus: %w", orderIDPrefix, err) + } + if err := tx.Commit(); err != nil { + return fmt.Errorf("%s - RefundOrder.commit: %w", orderIDPrefix, err) + } + return nil } @@ -341,5 +370,14 @@ func (s *OrderStarknet) SettleOrder(ctx context.Context, orderID uuid.UUID) erro return fmt.Errorf("%s - SettleOrder.paymasterExecuteTransaction: %w", orderIDPrefix, err) } + // Update order status to settling (indexer will set settled when event is indexed) + _, err = db.Client.PaymentOrder. + UpdateOneID(order.ID). + SetStatus(paymentorder.StatusSettling). + Save(ctx) + if err != nil { + return fmt.Errorf("%s - SettleOrder.updateStatus: %w", orderIDPrefix, err) + } + return nil } diff --git a/services/order/tron.go b/services/order/tron.go index 3c2a22164..2cb1777aa 100644 --- a/services/order/tron.go +++ b/services/order/tron.go @@ -30,6 +30,7 @@ import ( networkent "github.com/paycrest/aggregator/ent/network" "github.com/paycrest/aggregator/ent/paymentorder" "github.com/paycrest/aggregator/ent/paymentorderfulfillment" + "github.com/paycrest/aggregator/ent/transactionlog" "github.com/paycrest/aggregator/ent/providerordertoken" "github.com/paycrest/aggregator/ent/providerprofile" tokenent "github.com/paycrest/aggregator/ent/token" @@ -284,13 +285,34 @@ func (s *OrderTron) RefundOrder(ctx context.Context, network *ent.Network, order return fmt.Errorf("%s - Tron.RefundOrder.sendTransaction: %w", orderIDPrefix, err) } - // Update lock order - _, err = lockOrder.Update(). + // Create order_refunding log (indexer will update to order_refunded when event is indexed) + tx, err := db.Client.Tx(ctx) + if err != nil { + return fmt.Errorf("%s - Tron.RefundOrder.tx: %w", orderIDPrefix, err) + } + transactionLog, err := tx.TransactionLog. + Create(). + SetStatus(transactionlog.StatusOrderRefunding). + SetGatewayID(lockOrder.GatewayID). + SetTxHash(txHash). + SetNetwork(lockOrder.Edges.Token.Edges.Network.Identifier). + Save(ctx) + if err != nil { + _ = tx.Rollback() + return fmt.Errorf("%s - Tron.RefundOrder.createLog: %w", orderIDPrefix, err) + } + _, err = tx.PaymentOrder. + UpdateOneID(lockOrder.ID). SetTxHash(txHash). SetStatus(paymentorder.StatusRefunding). + AddTransactions(transactionLog). Save(ctx) if err != nil { - return fmt.Errorf("%s - Tron.RefundOrder.updateTxHash: %w", orderIDPrefix, err) + _ = tx.Rollback() + return fmt.Errorf("%s - Tron.RefundOrder.updateStatus: %w", orderIDPrefix, err) + } + if err := tx.Commit(); err != nil { + return fmt.Errorf("%s - Tron.RefundOrder.commit: %w", orderIDPrefix, err) } return nil @@ -417,7 +439,7 @@ func (s *OrderTron) createOrderCallData(order *ent.PaymentOrder) ([]byte, error) return nil, fmt.Errorf("failed to encrypt recipient details: %w", err) } - refundAddressTron, _ := util.Base58ToAddress(order.ReturnAddress) + refundAddressTron, _ := util.Base58ToAddress(order.RefundOrRecipientAddress) refundAddress := refundAddressTron.Hex()[4:] tokenContractAddressTron, _ := util.Base58ToAddress(order.Edges.Token.ContractAddress) senderFeeRecipientTron, _ := util.Base58ToAddress(order.FeeAddress) @@ -541,7 +563,7 @@ func (s *OrderTron) getOrderInfo(gatewayContractAddress util.Address, gatewayId }, nil } -// settleCallData creates the data for the settle method in the gateway contract +// settleCallData creates the data for the settleOut method in the gateway contract func (s *OrderTron) settleCallData(ctx context.Context, order *ent.PaymentOrder) ([]byte, error) { gatewayABI, err := abi.JSON(strings.NewReader(contracts.GatewayMetaData.ABI)) if err != nil { @@ -593,9 +615,9 @@ func (s *OrderTron) settleCallData(ctx context.Context, order *ent.PaymentOrder) splitOrderID := strings.ReplaceAll(order.ID.String(), "-", "") - // Generate calldata for settlement + // Generate calldata for settlement (Gateway.settleOut) data, err := gatewayABI.Pack( - "settle", + "settleOut", utils.StringToByte32(splitOrderID), utils.StringToByte32(string(orderID)), common.HexToAddress(providerAddress), @@ -603,7 +625,7 @@ func (s *OrderTron) settleCallData(ctx context.Context, order *ent.PaymentOrder) uint64(0), // rebatePercent - default to 0 for now ) if err != nil { - return nil, fmt.Errorf("failed to pack settle ABI: %w", err) + return nil, fmt.Errorf("failed to pack settleOut ABI: %w", err) } return data, nil diff --git a/services/priority_queue.go b/services/priority_queue.go index e47962d88..d388a144d 100644 --- a/services/priority_queue.go +++ b/services/priority_queue.go @@ -30,6 +30,14 @@ var ( orderConf = config.OrderConfig() ) +// RateSide represents the direction of the rate (buy for onramp, sell for offramp) +type RateSide string + +const ( + RateSideBuy RateSide = "buy" // Onramp: fiat per 1 token the sender pays to buy crypto + RateSideSell RateSide = "sell" // Offramp: fiat per 1 token the sender receives when selling crypto +) + type PriorityQueueService struct { balanceService *balance.Service } @@ -242,8 +250,8 @@ func (s *PriorityQueueService) GetProvisionBuckets(ctx context.Context) ([]*ent. return buckets, nil } -// GetProviderRate returns the rate for a provider -func (s *PriorityQueueService) GetProviderRate(ctx context.Context, provider *ent.ProviderProfile, tokenSymbol string, currency string) (decimal.Decimal, error) { +// GetProviderRate returns the rate for a provider based on the side (buy or sell) +func (s *PriorityQueueService) GetProviderRate(ctx context.Context, provider *ent.ProviderProfile, tokenSymbol string, currency string, side RateSide) (decimal.Decimal, error) { // Fetch the token config for the provider tokenConfig, err := provider.QueryOrderTokens(). Where( @@ -253,11 +261,6 @@ func (s *PriorityQueueService) GetProviderRate(ctx context.Context, provider *en ). WithProvider(). WithCurrency(). - Select( - providerordertoken.FieldConversionRateType, - providerordertoken.FieldFixedConversionRate, - providerordertoken.FieldFloatingConversionRate, - ). First(ctx) if err != nil { return decimal.Decimal{}, err @@ -265,15 +268,21 @@ func (s *PriorityQueueService) GetProviderRate(ctx context.Context, provider *en var rate decimal.Decimal - if tokenConfig.ConversionRateType == providerordertoken.ConversionRateTypeFixed { - rate = tokenConfig.FixedConversionRate - } else { - // Handle floating rate case - marketRate := tokenConfig.Edges.Currency.MarketRate - floatingRate := tokenConfig.FloatingConversionRate // in percentage - - // Calculate the floating rate based on the market rate - rate = marketRate.Add(floatingRate).RoundBank(2) + // Two-sided rate calculation based on side + if side == RateSideBuy { + if !tokenConfig.FixedBuyRate.IsZero() { + rate = tokenConfig.FixedBuyRate + } else if !tokenConfig.FloatingBuyDelta.IsZero() && !tokenConfig.Edges.Currency.MarketBuyRate.IsZero() { + // Floating buy rate: market_buy_rate + floating_buy_delta + rate = tokenConfig.Edges.Currency.MarketBuyRate.Add(tokenConfig.FloatingBuyDelta).RoundBank(2) + } + } else if side == RateSideSell { + if !tokenConfig.FixedSellRate.IsZero() { + rate = tokenConfig.FixedSellRate + } else if !tokenConfig.FloatingSellDelta.IsZero() && !tokenConfig.Edges.Currency.MarketSellRate.IsZero() { + // Floating sell rate: market_sell_rate + floating_sell_delta + rate = tokenConfig.Edges.Currency.MarketSellRate.Add(tokenConfig.FloatingSellDelta).RoundBank(2) + } } return rate, nil @@ -289,16 +298,10 @@ func (s *PriorityQueueService) deleteQueue(ctx context.Context, key string) erro return nil } -// CreatePriorityQueueForBucket creates a priority queue for a bucket and saves it to redis -func (s *PriorityQueueService) CreatePriorityQueueForBucket(ctx context.Context, bucket *ent.ProvisionBucket) { - providers := bucket.Edges.ProviderProfiles - - // Randomize the order of providers - rand.Shuffle(len(providers), func(i, j int) { - providers[i], providers[j] = providers[j], providers[i] - }) - - redisKey := fmt.Sprintf("bucket_%s_%s_%s", bucket.Edges.Currency.Code, bucket.MinAmount, bucket.MaxAmount) +// buildQueueForSide builds a priority queue for a specific side (buy or sell) and saves it to redis +func (s *PriorityQueueService) buildQueueForSide(ctx context.Context, bucket *ent.ProvisionBucket, providers []*ent.ProviderProfile, side RateSide) { + baseRedisKey := fmt.Sprintf("bucket_%s_%s_%s", bucket.Edges.Currency.Code, bucket.MinAmount, bucket.MaxAmount) + redisKey := baseRedisKey + "_" + string(side) prevRedisKey := redisKey + "_prev" tempRedisKey := redisKey + "_temp" @@ -351,8 +354,6 @@ func (s *PriorityQueueService) CreatePriorityQueueForBucket(ctx context.Context, }).Errorf("failed to delete temporary provider queue") } - // TODO: add also the checks for all the currencies that a provider has - // Build new queue in temporary key first newQueueEntries := 0 for _, provider := range providers { @@ -370,6 +371,7 @@ func (s *PriorityQueueService) CreatePriorityQueueForBucket(ctx context.Context, providerordertoken.SettlementAddressNEQ(""), ). WithToken(). + WithCurrency(). All(ctx) if err != nil { if err != context.Canceled { @@ -393,7 +395,7 @@ func (s *PriorityQueueService) CreatePriorityQueueForBucket(ctx context.Context, } tokenKeys[tokenKey] = true - rate, err := s.GetProviderRate(ctx, provider, orderToken.Edges.Token.Symbol, bucket.Edges.Currency.Code) + rate, err := s.GetProviderRate(ctx, provider, orderToken.Edges.Token.Symbol, bucket.Edges.Currency.Code, side) if err != nil { if err != context.Canceled { logger.WithFields(logger.Fields{ @@ -401,6 +403,7 @@ func (s *PriorityQueueService) CreatePriorityQueueForBucket(ctx context.Context, "ProviderID": provider.ID, "Token": orderToken.Edges.Token.Symbol, "Currency": bucket.Edges.Currency.Code, + "Side": string(side), }).Errorf("failed to get rate for provider") } continue @@ -411,7 +414,14 @@ func (s *PriorityQueueService) CreatePriorityQueueForBucket(ctx context.Context, } // Check provider's rate against the market rate to ensure it's not too far off - percentDeviation := utils.AbsPercentageDeviation(bucket.Edges.Currency.MarketRate, rate) + var marketRate decimal.Decimal + if side == RateSideBuy { + marketRate = bucket.Edges.Currency.MarketBuyRate + } else { + marketRate = bucket.Edges.Currency.MarketSellRate + } + + percentDeviation := utils.AbsPercentageDeviation(marketRate, rate) isLocalStablecoin := strings.Contains(orderToken.Edges.Token.Symbol, bucket.Edges.Currency.Code) if serverConf.Environment == "production" && percentDeviation.GreaterThan(orderConf.PercentDeviationFromMarketRate) && !isLocalStablecoin { @@ -496,6 +506,20 @@ func (s *PriorityQueueService) CreatePriorityQueueForBucket(ctx context.Context, } } +// CreatePriorityQueueForBucket creates priority queues for a bucket (both buy and sell sides) and saves them to redis +func (s *PriorityQueueService) CreatePriorityQueueForBucket(ctx context.Context, bucket *ent.ProvisionBucket) { + providers := bucket.Edges.ProviderProfiles + + // Randomize the order of providers + rand.Shuffle(len(providers), func(i, j int) { + providers[i], providers[j] = providers[j], providers[i] + }) + + // Build queues for both buy and sell sides + s.buildQueueForSide(ctx, bucket, providers, RateSideBuy) + s.buildQueueForSide(ctx, bucket, providers, RateSideSell) +} + // AssignPaymentOrder assigns payment orders to providers func (s *PriorityQueueService) AssignPaymentOrder(ctx context.Context, order types.PaymentOrderFields) error { orderIDPrefix := strings.Split(order.ID.String(), "-")[0] @@ -560,6 +584,21 @@ func (s *PriorityQueueService) AssignPaymentOrder(ctx context.Context, order typ return err } + // Compute rate side once from order direction (currentOrder if in DB, else default buy) for rate refresh and queue key + var rateSide RateSide + if currentOrder != nil { + switch currentOrder.Direction { + case paymentorder.DirectionOnramp: + rateSide = RateSideBuy + case paymentorder.DirectionOfframp: + rateSide = RateSideSell + default: + rateSide = RateSideBuy + } + } else { + rateSide = RateSideBuy // default when order not yet in DB (e.g. new order) + } + // Sends order directly to the specified provider in order. // Incase of failure, do nothing. The order will eventually refund // For OTC orders: skip if provider appears in exclude list at all @@ -588,7 +627,7 @@ func (s *PriorityQueueService) AssignPaymentOrder(ctx context.Context, order typ // TODO: check for provider's minimum and maximum rate for negotiation // Update the rate with the current rate if order was last updated more than 10 mins ago if !order.UpdatedAt.IsZero() && order.UpdatedAt.Before(time.Now().Add(-10*time.Minute)) { - order.Rate, err = s.GetProviderRate(ctx, provider, order.Token.Symbol, order.ProvisionBucket.Edges.Currency.Code) + order.Rate, err = s.GetProviderRate(ctx, provider, order.Token.Symbol, order.ProvisionBucket.Edges.Currency.Code, rateSide) if err != nil { logger.WithFields(logger.Fields{ "Error": fmt.Sprintf("%v", err), @@ -642,10 +681,9 @@ func (s *PriorityQueueService) AssignPaymentOrder(ctx context.Context, order typ } } - // Get the first provider from the circular queue - redisKey := fmt.Sprintf("bucket_%s_%s_%s", order.ProvisionBucket.Edges.Currency.Code, order.ProvisionBucket.MinAmount, order.ProvisionBucket.MaxAmount) - - // partnerProviders := []string{} + // Use same side-suffixed key as buildQueueForSide so reads/writes use identical keys (rateSide computed above) + baseRedisKey := fmt.Sprintf("bucket_%s_%s_%s", order.ProvisionBucket.Edges.Currency.Code, order.ProvisionBucket.MinAmount, order.ProvisionBucket.MaxAmount) + redisKey := baseRedisKey + "_" + string(rateSide) err = s.matchRate(ctx, redisKey, orderIDPrefix, order, excludeList) if err != nil { @@ -736,7 +774,7 @@ func (s *PriorityQueueService) assignOtcOrder(ctx context.Context, order types.P } orderRequestData := map[string]interface{}{ - "type": "otc", + "type": "otc", "providerId": order.ProviderID, } err = storage.RedisClient.HSet(ctx, orderKey, orderRequestData).Err() diff --git a/services/priority_queue_test.go b/services/priority_queue_test.go index f4357b97e..d683e55ec 100644 --- a/services/priority_queue_test.go +++ b/services/priority_queue_test.go @@ -38,6 +38,7 @@ import ( "github.com/shopspring/decimal" "github.com/spf13/viper" "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" ) var testCtxForPQ = struct { @@ -370,15 +371,14 @@ func setupForPQ() error { testCtxForPQ.publicProviderProfileAPIKey = apiKey _, err = test.AddProviderOrderTokenToProvider( map[string]interface{}{ - "fixed_conversion_rate": decimal.NewFromFloat(100), - "conversion_rate_type": "fixed", - "floating_conversion_rate": decimal.NewFromFloat(1.0), - "max_order_amount": decimal.NewFromFloat(1000), - "min_order_amount": decimal.NewFromFloat(1.0), - "provider": publicProviderProfile, - "currency_id": currency.ID, - "network": token.Edges.Network.Identifier, - "token_id": token.ID, + "fixed_buy_rate": decimal.NewFromFloat(100), + "fixed_sell_rate": decimal.NewFromFloat(100), + "max_order_amount": decimal.NewFromFloat(1000), + "min_order_amount": decimal.NewFromFloat(1.0), + "provider": publicProviderProfile, + "currency_id": currency.ID, + "network": token.Edges.Network.Identifier, + "token_id": token.ID, }, ) if err != nil { @@ -565,7 +565,7 @@ func TestPriorityQueueTest(t *testing.T) { service.CreatePriorityQueueForBucket(ctx, _bucket) - redisKey := fmt.Sprintf("bucket_%s_%s_%s", _bucket.Edges.Currency.Code, testCtxForPQ.minAmount, testCtxForPQ.maxAmount) + redisKey := fmt.Sprintf("bucket_%s_%s_%s_sell", _bucket.Edges.Currency.Code, testCtxForPQ.minAmount, testCtxForPQ.maxAmount) data, err := db.RedisClient.LRange(ctx, redisKey, 0, -1).Result() assert.NoError(t, err) @@ -582,7 +582,7 @@ func TestPriorityQueueTest(t *testing.T) { err = service.ProcessBucketQueues() assert.NoError(t, err) - redisKey := fmt.Sprintf("bucket_%s_%s_%s", testCtxForPQ.currency.Code, testCtxForPQ.minAmount, testCtxForPQ.maxAmount) + redisKey := fmt.Sprintf("bucket_%s_%s_%s_sell", testCtxForPQ.currency.Code, testCtxForPQ.minAmount, testCtxForPQ.maxAmount) // ProcessBucketQueues launches goroutines; wait until the queue is populated. assert.Eventually(t, func() bool { @@ -606,7 +606,8 @@ func TestPriorityQueueTest(t *testing.T) { "max_amount": testCtxForPQ.maxAmount, "currency_id": testCtxForPQ.currency.ID, }) - assert.NoError(t, err) + require.NoError(t, err) + require.NotNil(t, bucket, "CreateTestProvisionBucket returned nil") _bucket, err := db.Client.ProvisionBucket. Query(). @@ -614,7 +615,8 @@ func TestPriorityQueueTest(t *testing.T) { WithCurrency(). WithProviderProfiles(). Only(ctx) - assert.NoError(t, err) + require.NoError(t, err) + require.NotNil(t, _bucket) _order, err := test.CreateTestPaymentOrder(nil, map[string]interface{}{ "provider": testCtxForPQ.publicProviderProfile, @@ -622,9 +624,11 @@ func TestPriorityQueueTest(t *testing.T) { "token_id": testCtxForPQ.token.ID, "gateway_id": "order-1", }) - assert.NoError(t, err) + require.NoError(t, err) + require.NotNil(t, _order) + _, err = test.AddProvisionBucketToPaymentOrder(_order, bucket.ID) - assert.NoError(t, err) + require.NoError(t, err) order, err := db.Client.PaymentOrder. Query(). @@ -634,8 +638,8 @@ func TestPriorityQueueTest(t *testing.T) { }). WithToken(). Only(ctx) - - assert.NoError(t, err) + require.NoError(t, err) + require.NotNil(t, order) service.CreatePriorityQueueForBucket(ctx, _bucket) @@ -658,7 +662,7 @@ func TestPriorityQueueTest(t *testing.T) { t.Run("TestGetProviderRate", func(t *testing.T) { // Use the provider profile directly - it was created with db.Client which is the same as client // The relationship queries should work as long as db.Client is set correctly - rate, err := service.GetProviderRate(context.Background(), testCtxForPQ.publicProviderProfile, testCtxForPQ.token.Symbol, testCtxForPQ.currency.Code) + rate, err := service.GetProviderRate(context.Background(), testCtxForPQ.publicProviderProfile, testCtxForPQ.token.Symbol, testCtxForPQ.currency.Code, RateSideSell) assert.NoError(t, err) _rate, ok := rate.Float64() assert.True(t, ok) diff --git a/services/starknet/client.go b/services/starknet/client.go index 0eba6b54f..51aa6112b 100644 --- a/services/starknet/client.go +++ b/services/starknet/client.go @@ -750,7 +750,7 @@ func (c *Client) handleOrderSettled(emittedEvent rpc.EmittedEvent) (map[string]i if !ok { return nil, fmt.Errorf("failed to extract settle_percent as uint64") } - + rebatePercentStr, ok := extractUint64AsString(rebatePercent) if !ok { return nil, fmt.Errorf("failed to extract rebate_percent as uint64") @@ -865,7 +865,6 @@ func (c *Client) handleTransfer(emittedEvent rpc.EmittedEvent) (map[string]inter return event, nil } - func extractUint64AsString(val interface{}) (string, bool) { if uintVal, ok := val.(uint64); ok { return fmt.Sprintf("%d", uintVal), true @@ -945,4 +944,3 @@ func u256FromFelts(low, high *felt.Felt) *big.Int { return result } - diff --git a/tasks/fulfillments_webhooks.go b/tasks/fulfillments_webhooks.go index e6281545d..6f396d74e 100644 --- a/tasks/fulfillments_webhooks.go +++ b/tasks/fulfillments_webhooks.go @@ -16,6 +16,7 @@ import ( "github.com/paycrest/aggregator/ent/senderprofile" "github.com/paycrest/aggregator/ent/transactionlog" "github.com/paycrest/aggregator/ent/webhookretryattempt" + "github.com/paycrest/aggregator/services/balance" "github.com/paycrest/aggregator/services/email" "github.com/paycrest/aggregator/storage" "github.com/paycrest/aggregator/utils" @@ -82,6 +83,11 @@ func SyncPaymentOrderFulfillments() { paymentorder.UpdatedAtLTE(time.Now().Add(-30*time.Second)), paymentorder.Not(paymentorder.HasFulfillments()), ), + // Payin insufficient-balance refund: onramp orders in Refunding (no on-chain Refunded event) + paymentorder.And( + paymentorder.StatusEQ(paymentorder.StatusRefunding), + paymentorder.DirectionEQ(paymentorder.DirectionOnramp), + ), ), ). WithToken(func(tq *ent.TokenQuery) { @@ -140,8 +146,15 @@ func SyncPaymentOrderFulfillments() { continue } if len(order.Edges.Fulfillments) == 0 { + // Refunding (onramp): sync refund outcome via /tx_status + if order.Status == paymentorder.StatusRefunding { + syncRefundingOrder(ctx, order) + continue + } if order.Status == paymentorder.StatusCancelled { - reassignCancelledOrder(ctx, order, nil) + if order.Direction == paymentorder.DirectionOfframp { + reassignCancelledOrder(ctx, order, nil) + } continue } @@ -185,8 +198,8 @@ func SyncPaymentOrderFulfillments() { } payload := map[string]interface{}{ - "orderId": order.ID.String(), - "currency": order.Edges.ProvisionBucket.Edges.Currency.Code, + "reference": getTxStatusReferenceForVA(order), + "currency": order.Edges.ProvisionBucket.Edges.Currency.Code, } signature := tokenUtils.GenerateHMACSignature(payload, string(decryptedSecret)) @@ -199,11 +212,11 @@ func SyncPaymentOrderFulfillments() { Send() if err != nil { logger.WithFields(logger.Fields{ - "Error": fmt.Sprintf("%v", err), - "ProviderID": order.Edges.Provider.ID, - "PayloadOrderId": payload["orderId"], - "PayloadCurrency": payload["currency"], - "Reason": "internal: Failed to send tx_status request to provider", + "Error": fmt.Sprintf("%v", err), + "ProviderID": order.Edges.Provider.ID, + "PayloadReference": payload["reference"], + "PayloadCurrency": payload["currency"], + "Reason": "internal: Failed to send tx_status request to provider", }).Errorf("SyncPaymentOrderFulfillments.SendTxStatusRequest") // Set status to pending on 400 error @@ -229,12 +242,12 @@ func SyncPaymentOrderFulfillments() { // Instead of deleting the order, log the error and skip processing // The order will be retried in the next sync cycle or can be manually investigated logger.WithFields(logger.Fields{ - "Error": fmt.Sprintf("%v", err), - "ProviderID": order.Edges.Provider.ID, - "PayloadOrderId": payload["orderId"], - "PayloadCurrency": payload["currency"], - "OrderID": order.ID.String(), - "OrderStatus": order.Status.String(), + "Error": fmt.Sprintf("%v", err), + "ProviderID": order.Edges.Provider.ID, + "PayloadReference": payload["reference"], + "PayloadCurrency": payload["currency"], + "OrderID": order.ID.String(), + "OrderStatus": order.Status.String(), }).Errorf("SyncPaymentOrderFulfillments: Failed to parse JSON response after getting trx status from provider, skipping order") continue } @@ -242,6 +255,10 @@ func SyncPaymentOrderFulfillments() { status := data["data"].(map[string]interface{})["status"].(string) psp := data["data"].(map[string]interface{})["psp"].(string) txId := data["data"].(map[string]interface{})["txId"].(string) + validationError := "" + if errVal, ok := data["data"].(map[string]interface{})["error"].(string); ok { + validationError = errVal + } if status == "failed" { _, err = storage.Client.PaymentOrderFulfillment. @@ -250,19 +267,25 @@ func SyncPaymentOrderFulfillments() { SetPsp(psp). SetTxID(txId). SetValidationStatus(paymentorderfulfillment.ValidationStatusFailed). - SetValidationError(data["data"].(map[string]interface{})["error"].(string)). + SetValidationError(validationError). Save(ctx) if err != nil { continue } - _, err = order.Update(). SetStatus(paymentorder.StatusFulfilled). Save(ctx) if err != nil { continue } - + // Payin (onramp): release reserved token balance on failure + if order.Direction == paymentorder.DirectionOnramp && order.Edges.Token != nil && order.Edges.Provider != nil { + balanceService := balance.New() + totalCryptoReserved := order.Amount.Add(order.SenderFee) + if relErr := balanceService.ReleaseTokenBalance(ctx, order.Edges.Provider.ID, order.Edges.Token.ID, totalCryptoReserved, nil); relErr != nil { + logger.WithFields(logger.Fields{"OrderID": order.ID.String(), "Error": relErr}).Errorf("SyncPaymentOrderFulfillments: release balance on payin failed") + } + } } else if status == "success" { _, err = storage.Client.PaymentOrderFulfillment. Create(). @@ -274,7 +297,17 @@ func SyncPaymentOrderFulfillments() { if err != nil { continue } - + // Payin (onramp): ask provider to run AcceptOrder + FulfillOrder(Success) with EIP-7702 auth + if order.Direction == paymentorder.DirectionOnramp { + if reqErr := callRequestAuthorization(ctx, order, psp, txId, order.Amount.Add(order.SenderFee).String(), string(decryptedSecret)); reqErr != nil { + logger.WithFields(logger.Fields{ + "OrderID": order.ID.String(), + "Error": reqErr.Error(), + }).Warnf("SyncPaymentOrderFulfillments: request_authorization failed (will retry on next sync)") + } + continue + } + // Payout (offramp): set order to Validated and send webhook if order.Edges.Token == nil { logger.WithFields(logger.Fields{ "OrderID": order.ID.String(), @@ -291,7 +324,6 @@ func SyncPaymentOrderFulfillments() { }).Errorf("SyncPaymentOrderFulfillments.MissingNetwork") continue } - transactionLog, err := storage.Client.TransactionLog. Create(). SetStatus(transactionlog.StatusOrderValidated). @@ -300,7 +332,6 @@ func SyncPaymentOrderFulfillments() { if err != nil { continue } - _, err = storage.Client.PaymentOrder. UpdateOneID(order.ID). SetStatus(paymentorder.StatusValidated). @@ -309,7 +340,6 @@ func SyncPaymentOrderFulfillments() { if err != nil { continue } - if err := utils.SendPaymentOrderWebhook(ctx, order); err != nil { logger.WithFields(logger.Fields{ "Error": fmt.Sprintf("%v", err), @@ -359,10 +389,10 @@ func SyncPaymentOrderFulfillments() { } payload := map[string]interface{}{ - "orderId": order.ID.String(), - "currency": order.Edges.ProvisionBucket.Edges.Currency.Code, - "psp": fulfillment.Psp, - "txId": fulfillment.TxID, + "reference": getTxStatusReferenceForVA(order), + "currency": order.Edges.ProvisionBucket.Edges.Currency.Code, + "psp": fulfillment.Psp, + "txId": fulfillment.TxID, } signature := tokenUtils.GenerateHMACSignature(payload, string(decryptedSecret)) @@ -401,13 +431,13 @@ func SyncPaymentOrderFulfillments() { } logger.WithFields(logger.Fields{ - "Error": fmt.Sprintf("%v", err), - "OrderID": order.ID.String(), - "ProviderID": order.Edges.Provider.ID, - "PayloadOrderId": payload["orderId"], - "PayloadCurrency": payload["currency"], - "PayloadPsp": payload["psp"], - "PayloadTxId": payload["txId"], + "Error": fmt.Sprintf("%v", err), + "OrderID": order.ID.String(), + "ProviderID": order.Edges.Provider.ID, + "PayloadReference": payload["reference"], + "PayloadCurrency": payload["currency"], + "PayloadPsp": payload["psp"], + "PayloadTxId": payload["txId"], }).Errorf("Failed to parse JSON response after getting trx status from provider") continue } @@ -415,11 +445,15 @@ func SyncPaymentOrderFulfillments() { status := data["data"].(map[string]interface{})["status"].(string) if status == "failed" { + validationError := "" + if errVal, ok := data["data"].(map[string]interface{})["error"].(string); ok { + validationError = errVal + } _, err = storage.Client.PaymentOrderFulfillment. UpdateOneID(fulfillment.ID). SetTxID(fulfillment.TxID). SetValidationStatus(paymentorderfulfillment.ValidationStatusFailed). - SetValidationError(data["data"].(map[string]interface{})["error"].(string)). + SetValidationError(validationError). Save(ctx) if err != nil { continue @@ -431,7 +465,14 @@ func SyncPaymentOrderFulfillments() { if err != nil { continue } - + // Payin (onramp): release reserved token balance on failure + if order.Direction == paymentorder.DirectionOnramp && order.Edges.Token != nil && order.Edges.Provider != nil { + balanceService := balance.New() + totalCryptoReserved := order.Amount.Add(order.SenderFee) + if relErr := balanceService.ReleaseTokenBalance(ctx, order.Edges.Provider.ID, order.Edges.Token.ID, totalCryptoReserved, nil); relErr != nil { + logger.WithFields(logger.Fields{"OrderID": order.ID.String(), "Error": relErr}).Errorf("SyncPaymentOrderFulfillments: release balance on payin failed (pending→failed)") + } + } } else if status == "success" { _, err = storage.Client.PaymentOrderFulfillment. UpdateOneID(fulfillment.ID). @@ -441,7 +482,17 @@ func SyncPaymentOrderFulfillments() { if err != nil { continue } - + // Onramp: ask provider to run AcceptOrder + FulfillOrder(Success) with EIP-7702 auth + if order.Direction == paymentorder.DirectionOnramp { + if reqErr := callRequestAuthorization(ctx, order, fulfillment.Psp, fulfillment.TxID, order.Amount.Add(order.SenderFee).String(), string(decryptedSecret)); reqErr != nil { + logger.WithFields(logger.Fields{ + "OrderID": order.ID.String(), + "Error": reqErr.Error(), + }).Warnf("SyncPaymentOrderFulfillments: request_authorization failed for pending→success (will retry on next sync)") + } + continue + } + // Offramp: set order to Validated and send webhook if order.Edges.Token == nil { logger.WithFields(logger.Fields{ "OrderID": order.ID.String(), @@ -487,10 +538,20 @@ func SyncPaymentOrderFulfillments() { } } else if fulfillment.ValidationStatus == paymentorderfulfillment.ValidationStatusFailed { + // Onramp: no reassign on failed fulfillment (balance released in syncRefundingOrder or payin-failed path). + if order.Direction == paymentorder.DirectionOnramp { + continue + } reassignCancelledOrder(ctx, order, fulfillment) continue } else if fulfillment.ValidationStatus == paymentorderfulfillment.ValidationStatusSuccess { + // Onramp: no OrderValidated from sync; order stays Fulfilling until FulfillOrder(Success) from provider + if order.Direction == paymentorder.DirectionOnramp { + continue + } + + // Offramp: set order to Validated and send webhook if order.Edges.Token == nil { logger.WithFields(logger.Fields{ "OrderID": order.ID.String(), @@ -628,3 +689,270 @@ func RetryFailedWebhookNotifications() error { return nil } + +// syncRefundingOrder calls the provider /tx_status for an onramp order in Refunding and updates order/fulfillment to refunded/failed/pending. +func syncRefundingOrder(ctx context.Context, order *ent.PaymentOrder) { + if order.Edges.ProvisionBucket == nil || order.Edges.ProvisionBucket.Edges.Currency == nil { + logger.WithFields(logger.Fields{ + "OrderID": order.ID.String(), + "ProviderID": order.Edges.Provider.ID, + }).Errorf("SyncPaymentOrderFulfillments.syncRefundingOrder: missing ProvisionBucket or Currency") + return + } + decodedSecret, err := base64.StdEncoding.DecodeString(order.Edges.Provider.Edges.APIKey.Secret) + if err != nil { + logger.WithFields(logger.Fields{"OrderID": order.ID.String(), + "ProviderID": order.Edges.Provider.ID, + "Error": err, + }).Errorf("SyncPaymentOrderFulfillments.syncRefundingOrder: decode secret") + return + } + decryptedSecret, err := cryptoUtils.DecryptPlain(decodedSecret) + if err != nil { + logger.WithFields(logger.Fields{"OrderID": order.ID.String(), + "ProviderID": order.Edges.Provider.ID, + "Error": err, + }).Errorf("SyncPaymentOrderFulfillments.syncRefundingOrder: decrypt secret") + return + } + + // Use refundReference for tx_status (refund status). If missing, call /tx_refund first to get one. + refundReference := getRefundReferenceFromOrder(order) + if refundReference == "" { + refundReference, err = callTxRefundAndStore(ctx, order, string(decryptedSecret)) + if err != nil || refundReference == "" { + return + } + } + + payload := map[string]interface{}{ + "reference": refundReference, + "currency": order.Edges.ProvisionBucket.Edges.Currency.Code, + } + signature := tokenUtils.GenerateHMACSignature(payload, string(decryptedSecret)) + res, err := fastshot.NewClient(order.Edges.Provider.HostIdentifier). + Config().SetTimeout(10*time.Second). + Header().Add("X-Request-Signature", signature). + Build().POST("/tx_status"). + Body().AsJSON(payload). + Send() + if err != nil { + logger.WithFields(logger.Fields{ + "OrderID": order.ID.String(), + "ProviderID": order.Edges.Provider.ID, + "Error": err, + }).Errorf("SyncPaymentOrderFulfillments.syncRefundingOrder: tx_status request") + return + } + data, err := utils.ParseJSONResponse(res.RawResponse) + if err != nil { + logger.WithFields(logger.Fields{ + "OrderID": order.ID.String(), + "ProviderID": order.Edges.Provider.ID, + "Error": err, + }).Errorf("SyncPaymentOrderFulfillments.syncRefundingOrder: parse response") + return + } + dataMap, ok := data["data"].(map[string]interface{}) + if !ok { + return + } + statusVal, _ := dataMap["status"].(string) + pspVal, _ := dataMap["psp"].(string) + txIdVal, _ := dataMap["txId"].(string) + errorVal, _ := dataMap["error"].(string) + + switch statusVal { + case "success": + _, err = storage.Client.PaymentOrderFulfillment. + Create(). + SetOrderID(order.ID). + SetPsp(pspVal). + SetTxID(txIdVal). + SetValidationStatus(paymentorderfulfillment.ValidationStatusRefunded). + Save(ctx) + if err != nil { + logger.WithFields(logger.Fields{ + "OrderID": order.ID.String(), + "ProviderID": order.Edges.Provider.ID, + "Error": err, + }).Errorf("SyncPaymentOrderFulfillments.syncRefundingOrder: create fulfillment") + return + } + _, err = storage.Client.PaymentOrder.UpdateOneID(order.ID).SetStatus(paymentorder.StatusRefunded).Save(ctx) + if err != nil { + logger.WithFields(logger.Fields{ + "OrderID": order.ID.String(), + "ProviderID": order.Edges.Provider.ID, + "Error": err, + }).Errorf("SyncPaymentOrderFulfillments.syncRefundingOrder: set Refunded") + return + } + if order.Edges.Token != nil && order.Edges.Provider != nil { + balanceService := balance.New() + totalCryptoReserved := order.Amount.Add(order.SenderFee) + if relErr := balanceService.ReleaseTokenBalance(ctx, order.Edges.Provider.ID, order.Edges.Token.ID, totalCryptoReserved, nil); relErr != nil { + logger.WithFields(logger.Fields{ + "OrderID": order.ID.String(), + "ProviderID": order.Edges.Provider.ID, + "Error": relErr, + }).Errorf("SyncPaymentOrderFulfillments.syncRefundingOrder: release balance") + } + } + case "failed": + // Do not create failed fulfillment; call /tx_refund to retry. Validate refund account first. + if order.AccountIdentifier == "" || order.AccountName == "" || order.Institution == "" { + _, _ = storage.Client.PaymentOrderFulfillment. + Create(). + SetOrderID(order.ID). + SetPsp(pspVal). + SetTxID(txIdVal). + SetValidationStatus(paymentorderfulfillment.ValidationStatusFailed). + SetValidationError(errorVal). + Save(ctx) + if order.Edges.Token != nil && order.Edges.Provider != nil { + balanceService := balance.New() + totalCryptoReserved := order.Amount.Add(order.SenderFee) + _ = balanceService.ReleaseTokenBalance(ctx, order.Edges.Provider.ID, order.Edges.Token.ID, totalCryptoReserved, nil) + } + // Mark order as Fulfilled so it does not remain in Refunding indefinitely (consistent with payin-failed and handlePayinFulfillment). + _, err = storage.Client.PaymentOrder.UpdateOneID(order.ID).SetStatus(paymentorder.StatusFulfilled).Save(ctx) + if err != nil { + logger.WithFields(logger.Fields{ + "OrderID": order.ID.String(), + "ProviderID": order.Edges.Provider.ID, + "Error": err, + }).Errorf("SyncPaymentOrderFulfillments.syncRefundingOrder: set Fulfilled after refund failed") + } + return + } + + // On success, refundReference is stored in order metadata; next sync will poll /tx_status with it. + if _, err := callTxRefundAndStore(ctx, order, string(decryptedSecret)); err != nil { + return + } + } +} + +// getRefundReferenceFromOrder returns order.Metadata["providerAccount"]["refundReference"] if set. +func getRefundReferenceFromOrder(order *ent.PaymentOrder) string { + if order.Metadata == nil { + return "" + } + pa, _ := order.Metadata["providerAccount"].(map[string]interface{}) + if pa == nil { + return "" + } + ref, _ := pa["refundReference"].(string) + return ref +} + +// getTxStatusReferenceForVA returns the reference to use for /tx_status when querying VA/deposit (no-fulfillments or fulfillments>0). +// Onramp and present in providerAccount: return stored "reference"; else order ID. +func getTxStatusReferenceForVA(order *ent.PaymentOrder) string { + if order.Direction == paymentorder.DirectionOnramp && order.Metadata != nil { + pa, _ := order.Metadata["providerAccount"].(map[string]interface{}) + if pa != nil { + if ref, _ := pa["reference"].(string); ref != "" { + return ref + } + } + } + return order.ID.String() +} + +// callRequestAuthorization calls POST /request_authorization on the provider (same host, HMAC as tx_status). On 200 returns nil; on 4xx/5xx/network returns error for logging; no change to order/fulfillment. +func callRequestAuthorization(ctx context.Context, order *ent.PaymentOrder, psp, txId, amountStr, decryptedSecret string) error { + payload := map[string]interface{}{ + "orderId": order.ID.String(), + "amount": amountStr, + } + if psp != "" { + payload["psp"] = psp + } + if txId != "" { + payload["txId"] = txId + } + signature := tokenUtils.GenerateHMACSignature(payload, decryptedSecret) + res, err := fastshot.NewClient(order.Edges.Provider.HostIdentifier). + Config().SetTimeout(30*time.Second). + Header().Add("X-Request-Signature", signature). + Build().POST("/request_authorization"). + Body().AsJSON(payload). + Send() + if err != nil { + return err + } + if res.RawResponse.StatusCode != 200 { + return fmt.Errorf("request_authorization status %d", res.RawResponse.StatusCode) + } + return nil +} + +// callTxRefundAndStore calls POST /tx_refund, on 200 stores refundReference in order metadata and returns it. +func callTxRefundAndStore(ctx context.Context, order *ent.PaymentOrder, decryptedSecret string) (refundReference string, err error) { + fiatAmount := order.Amount.Add(order.SenderFee).Mul(order.Rate).RoundBank(0).String() + refundAccount := map[string]interface{}{ + "accountIdentifier": order.AccountIdentifier, + "accountName": order.AccountName, + "institution": order.Institution, + } + if order.Metadata != nil { + if m, ok := order.Metadata["refundAccountMetadata"].(map[string]interface{}); ok { + refundAccount["metadata"] = m + } + } + body := map[string]interface{}{ + "orderId": order.ID.String(), + "currency": order.Edges.ProvisionBucket.Edges.Currency.Code, + "amount": fiatAmount, + "refundAccount": refundAccount, + } + signature := tokenUtils.GenerateHMACSignature(body, decryptedSecret) + res, err := fastshot.NewClient(order.Edges.Provider.HostIdentifier). + Config().SetTimeout(10*time.Second). + Header().Add("X-Request-Signature", signature). + Build().POST("/tx_refund"). + Body().AsJSON(body). + Send() + if err != nil { + logger.WithFields(logger.Fields{ + "OrderID": order.ID.String(), + "Error": err, + }).Errorf("SyncPaymentOrderFulfillments.syncRefundingOrder: tx_refund request") + return "", err + } + data, err := utils.ParseJSONResponse(res.RawResponse) + if err != nil { + return "", err + } + if res.RawResponse.StatusCode != 200 { + return "", nil + } + dataMap, _ := data["data"].(map[string]interface{}) + if dataMap == nil { + return "", nil + } + refundReference, _ = dataMap["refundReference"].(string) + if refundReference == "" { + return "", nil + } + + // Store refundReference in order metadata + orderMetadata := order.Metadata + if orderMetadata == nil { + orderMetadata = make(map[string]interface{}) + } + providerAccount, _ := orderMetadata["providerAccount"].(map[string]interface{}) + if providerAccount == nil { + providerAccount = make(map[string]interface{}) + } + providerAccount["refundReference"] = refundReference + orderMetadata["providerAccount"] = providerAccount + _, err = storage.Client.PaymentOrder.UpdateOneID(order.ID).SetMetadata(orderMetadata).Save(ctx) + if err != nil { + logger.WithFields(logger.Fields{"OrderID": order.ID.String(), "Error": err}).Errorf("SyncPaymentOrderFulfillments.syncRefundingOrder: save refundReference") + return refundReference, err + } + return refundReference, nil +} diff --git a/tasks/indexing.go b/tasks/indexing.go index 4e1e1bef8..ee12e2d9c 100644 --- a/tasks/indexing.go +++ b/tasks/indexing.go @@ -478,8 +478,8 @@ func ProcessStuckValidatedOrders() error { continue } - // Index provider address for OrderSettled events - _, err = indexerInstance.IndexProviderAddress(ctx, network, providerAddress, 0, 0, "") + // Index provider address for SettleOut events + _, err = indexerInstance.IndexProviderSettlementAddress(ctx, network, providerAddress, 0, 0, "") if err != nil { logger.WithFields(logger.Fields{ "Error": fmt.Sprintf("%v", err), diff --git a/tasks/rates.go b/tasks/rates.go index 44ccf0b8f..a6ebbb8cd 100644 --- a/tasks/rates.go +++ b/tasks/rates.go @@ -18,8 +18,8 @@ import ( "github.com/shopspring/decimal" ) -// fetchExternalRate fetches the external rate for a fiat currency -func fetchExternalRate(currency string) (decimal.Decimal, error) { +// fetchExternalRate fetches the external rates (buy and sell) for a fiat currency +func fetchExternalRate(currency string) (buyRate, sellRate decimal.Decimal, err error) { currency = strings.ToUpper(currency) supportedCurrencies := []string{"KES", "NGN", "GHS", "MWK", "TZS", "UGX", "XOF", "BRL"} isSupported := false @@ -30,7 +30,7 @@ func fetchExternalRate(currency string) (decimal.Decimal, error) { } } if !isSupported { - return decimal.Zero, fmt.Errorf("ComputeMarketRate: currency not supported") + return decimal.Zero, decimal.Zero, fmt.Errorf("ComputeMarketRate: currency not supported") } // Fetch rates from noblocks rates API @@ -40,46 +40,46 @@ func fetchExternalRate(currency string) (decimal.Decimal, error) { Retry().Set(3, 5*time.Second). Send() if err != nil { - return decimal.Zero, fmt.Errorf("ComputeMarketRate: %w", err) + return decimal.Zero, decimal.Zero, fmt.Errorf("ComputeMarketRate: %w", err) } // Read the response body manually since we need to parse an array, not an object responseBody, err := io.ReadAll(res.RawResponse.Body) defer res.RawResponse.Body.Close() if err != nil { - return decimal.Zero, fmt.Errorf("ComputeMarketRate: failed to read response body: %w", err) + return decimal.Zero, decimal.Zero, fmt.Errorf("ComputeMarketRate: failed to read response body: %w", err) } var dataArray []map[string]interface{} err = json.Unmarshal(responseBody, &dataArray) if err != nil { - return decimal.Zero, fmt.Errorf("ComputeMarketRate: failed to parse JSON response: %w", err) + return decimal.Zero, decimal.Zero, fmt.Errorf("ComputeMarketRate: failed to parse JSON response: %w", err) } // Check if we have data if len(dataArray) == 0 { - return decimal.Zero, fmt.Errorf("ComputeMarketRate: No data in the response") + return decimal.Zero, decimal.Zero, fmt.Errorf("ComputeMarketRate: No data in the response") } // Get the first rate object rateData := dataArray[0] // Extract buy and sell rates - buyRate, ok := rateData["buyRate"].(float64) + buyRateFloat, ok := rateData["buyRate"].(float64) if !ok { - return decimal.Zero, fmt.Errorf("ComputeMarketRate: Invalid buyRate format") + return decimal.Zero, decimal.Zero, fmt.Errorf("ComputeMarketRate: Invalid buyRate format") } - sellRate, ok := rateData["sellRate"].(float64) + sellRateFloat, ok := rateData["sellRate"].(float64) if !ok { - return decimal.Zero, fmt.Errorf("ComputeMarketRate: Invalid sellRate format") + return decimal.Zero, decimal.Zero, fmt.Errorf("ComputeMarketRate: Invalid sellRate format") } - // Calculate the average of buy and sell rates for the external rate - avgRate := (buyRate + sellRate) / 2 - price := decimal.NewFromFloat(avgRate) + // Swap buy and sell rates to match sender perspective + buyRate = decimal.NewFromFloat(sellRateFloat) + sellRate = decimal.NewFromFloat(buyRateFloat) - return price, nil + return buyRate, sellRate, nil } // ComputeMarketRate computes the market price for fiat currencies @@ -98,48 +98,69 @@ func ComputeMarketRate() error { } for _, currency := range currencies { - // Fetch external rate - externalRate, err := fetchExternalRate(currency.Code) + // Fetch external rates (buy and sell) + externalBuyRate, externalSellRate, err := fetchExternalRate(currency.Code) if err != nil { continue } - // Fetch rates from token configs with fixed conversion rate + // Fetch rates from token configs with fixed conversion rates (both buy and sell) tokenConfigs, err := storage.Client.ProviderOrderToken. Query(). Where( providerordertoken.HasTokenWith( tokenent.SymbolIn("USDT", "USDC"), ), - providerordertoken.ConversionRateTypeEQ(providerordertoken.ConversionRateTypeFixed), + providerordertoken.HasCurrencyWith(fiatcurrency.CodeEQ(currency.Code)), providerordertoken.HasProviderWith( providerprofile.IsActiveEQ(true), ), ). - Select(providerordertoken.FieldFixedConversionRate). All(ctx) if err != nil { continue } - var rates []decimal.Decimal + // Collect buy and sell rates separately from provider configs + var buyRates []decimal.Decimal + var sellRates []decimal.Decimal for _, tokenConfig := range tokenConfigs { - rates = append(rates, tokenConfig.FixedConversionRate) + if !tokenConfig.FixedBuyRate.IsZero() { + buyRates = append(buyRates, tokenConfig.FixedBuyRate) + } + if !tokenConfig.FixedSellRate.IsZero() { + sellRates = append(sellRates, tokenConfig.FixedSellRate) + } } - // Calculate median - median := utils.Median(rates) + // Calculate medians for buy and sell + var medianBuyRate, medianSellRate decimal.Decimal + if len(buyRates) > 0 { + medianBuyRate = utils.Median(buyRates) + // Check against external buy rate + percentDeviation := utils.AbsPercentageDeviation(externalBuyRate, medianBuyRate) + if percentDeviation.GreaterThan(orderConf.PercentDeviationFromExternalRate) { + medianBuyRate = externalBuyRate + } + } else { + medianBuyRate = externalBuyRate + } - // Check the median rate against the external rate to ensure it's not too far off - percentDeviation := utils.AbsPercentageDeviation(externalRate, median) - if percentDeviation.GreaterThan(orderConf.PercentDeviationFromExternalRate) { - median = externalRate + if len(sellRates) > 0 { + medianSellRate = utils.Median(sellRates) + // Check against external sell rate + percentDeviation := utils.AbsPercentageDeviation(externalSellRate, medianSellRate) + if percentDeviation.GreaterThan(orderConf.PercentDeviationFromExternalRate) { + medianSellRate = externalSellRate + } + } else { + medianSellRate = externalSellRate } - // Update currency with median rate - _, err = storage.Client.FiatCurrency. - UpdateOneID(currency.ID). - SetMarketRate(median). + // Update currency with both buy and sell market rates + _, err = storage.Client.FiatCurrency.UpdateOneID(currency.ID). + SetMarketBuyRate(medianBuyRate). + SetMarketSellRate(medianSellRate). Save(ctx) if err != nil { continue diff --git a/tasks/rates_test.go b/tasks/rates_test.go index 214c08d18..000e133ea 100644 --- a/tasks/rates_test.go +++ b/tasks/rates_test.go @@ -14,9 +14,10 @@ func TestFetchExternalRate(t *testing.T) { // Test unsupported currency t.Run("UnsupportedCurrency", func(t *testing.T) { - value, err := fetchExternalRate("KSH") + buy, sell, err := fetchExternalRate("KSH") assert.Error(t, err) - assert.Equal(t, value, decimal.Zero) + assert.Equal(t, buy, decimal.Zero) + assert.Equal(t, sell, decimal.Zero) assert.Contains(t, err.Error(), "currency not supported") }) @@ -35,10 +36,12 @@ func TestFetchExternalRate(t *testing.T) { } ]`)) - value, err := fetchExternalRate("NGN") + buy, sell, err := fetchExternalRate("NGN") assert.NoError(t, err) - expectedRate := decimal.NewFromFloat((1444.36 + 1451.61) / 2) // Average of buy and sell - assert.True(t, value.Equal(expectedRate)) + expectedBuy := decimal.NewFromFloat(1451.61) // swapped in implementation + expectedSell := decimal.NewFromFloat(1444.36) + assert.True(t, buy.Equal(expectedBuy)) + assert.True(t, sell.Equal(expectedSell)) }) // Test API error @@ -46,9 +49,10 @@ func TestFetchExternalRate(t *testing.T) { httpmock.RegisterResponder("GET", "https://api.rates.noblocks.xyz/rates/usdt/kes", httpmock.NewStringResponder(500, `{"error": "Internal server error"}`)) - value, err := fetchExternalRate("KES") + buy, sell, err := fetchExternalRate("KES") assert.Error(t, err) - assert.Equal(t, value, decimal.Zero) + assert.Equal(t, buy, decimal.Zero) + assert.Equal(t, sell, decimal.Zero) assert.Contains(t, err.Error(), "ComputeMarketRate") }) @@ -57,9 +61,10 @@ func TestFetchExternalRate(t *testing.T) { httpmock.RegisterResponder("GET", "https://api.rates.noblocks.xyz/rates/usdt/ghs", httpmock.NewStringResponder(200, `[]`)) - value, err := fetchExternalRate("GHS") + buy, sell, err := fetchExternalRate("GHS") assert.Error(t, err) - assert.Equal(t, value, decimal.Zero) + assert.Equal(t, buy, decimal.Zero) + assert.Equal(t, sell, decimal.Zero) assert.Contains(t, err.Error(), "No data in the response") }) @@ -68,9 +73,10 @@ func TestFetchExternalRate(t *testing.T) { httpmock.RegisterResponder("GET", "https://api.rates.noblocks.xyz/rates/usdt/mwk", httpmock.NewStringResponder(200, `invalid json`)) - value, err := fetchExternalRate("MWK") + buy, sell, err := fetchExternalRate("MWK") assert.Error(t, err) - assert.Equal(t, value, decimal.Zero) + assert.Equal(t, buy, decimal.Zero) + assert.Equal(t, sell, decimal.Zero) }) // Test malformed rate data @@ -87,9 +93,10 @@ func TestFetchExternalRate(t *testing.T) { } ]`)) - value, err := fetchExternalRate("TZS") + buy, sell, err := fetchExternalRate("TZS") assert.Error(t, err) - assert.Equal(t, value, decimal.Zero) + assert.Equal(t, buy, decimal.Zero) + assert.Equal(t, sell, decimal.Zero) assert.Contains(t, err.Error(), "Invalid buyRate format") }) diff --git a/tasks/refunds.go b/tasks/refunds.go index 15d0ced79..1e49619b6 100644 --- a/tasks/refunds.go +++ b/tasks/refunds.go @@ -10,8 +10,10 @@ import ( "github.com/paycrest/aggregator/ent" "github.com/paycrest/aggregator/ent/paymentorder" "github.com/paycrest/aggregator/services" + "github.com/paycrest/aggregator/services/balance" "github.com/paycrest/aggregator/services/common" "github.com/paycrest/aggregator/storage" + "github.com/paycrest/aggregator/utils" blockchainUtils "github.com/paycrest/aggregator/utils/blockchain" "github.com/paycrest/aggregator/utils/logger" ) @@ -34,11 +36,11 @@ import ( // return nil // } -// HandleReceiveAddressValidity handles receive address validity -func HandleReceiveAddressValidity() error { +// ExpireStaleOrders expires orders past their validity: offramp Initiated with expired receive address, and onramp Pending past VA validity (metadata.providerAccount.validUntil). For onramp, releases reserved token balance. +func ExpireStaleOrders() error { ctx := context.Background() - // Fetch expired receive addresses that are due for validity check + // Offramp: receive address validity — Initiated orders with expired receive address orders, err := storage.Client.PaymentOrder. Query(). Where( @@ -51,9 +53,8 @@ func HandleReceiveAddressValidity() error { WithSenderProfile(). All(ctx) if err != nil { - return fmt.Errorf("HandleReceiveAddressValidity: %w", err) + return fmt.Errorf("ExpireStaleOrders.receiveAddress: %w", err) } - for _, order := range orders { err := common.HandleReceiveAddressValidity(ctx, order) if err != nil { @@ -61,6 +62,54 @@ func HandleReceiveAddressValidity() error { } } + // Onramp: Pending orders past VA validity (no deposit, provider never called AcceptOrder) — use validUntil from metadata, fallback to CreatedAt + OrderFulfillmentValidity + pendingOnramp, err := storage.Client.PaymentOrder. + Query(). + Where( + paymentorder.DirectionEQ(paymentorder.DirectionOnramp), + paymentorder.StatusEQ(paymentorder.StatusPending), + ). + WithProvider(). + WithToken(). + All(ctx) + if err != nil { + return fmt.Errorf("ExpireStaleOrders.pendingOnramp: %w", err) + } + balanceService := balance.New() + now := time.Now() + for _, order := range pendingOnramp { + var validUntil time.Time + if order.Metadata != nil { + if pa, ok := order.Metadata["providerAccount"].(map[string]interface{}); ok { + if t, ok := utils.ParseValidUntilFromMetadata(pa["validUntil"]); ok { + validUntil = t + } + } + } + if validUntil.IsZero() { + validUntil = order.CreatedAt.Add(orderConf.OrderFulfillmentValidity) + } + if !now.After(validUntil) { + continue + } + _, err := storage.Client.PaymentOrder.UpdateOneID(order.ID).SetStatus(paymentorder.StatusExpired).Save(ctx) + if err != nil { + logger.WithFields(logger.Fields{ + "OrderID": order.ID.String(), + "Error": err, + }).Errorf("ExpireStaleOrders: failed to set Expired for onramp Pending") + continue + } + if order.Edges.Provider != nil && order.Edges.Token != nil { + totalCryptoReserved := order.Amount.Add(order.SenderFee) + if relErr := balanceService.ReleaseTokenBalance(ctx, order.Edges.Provider.ID, order.Edges.Token.ID, totalCryptoReserved, nil); relErr != nil { + logger.WithFields(logger.Fields{ + "OrderID": order.ID.String(), + "Error": relErr, + }).Errorf("ExpireStaleOrders: failed to release token balance for onramp Pending") + } + } + } return nil } @@ -113,7 +162,7 @@ func ProcessExpiredOrdersRefunds() error { chainID := network.ChainID // Skip if no return address (nowhere to refund to) - if order.ReturnAddress == "" { + if order.RefundOrRecipientAddress == "" { return } @@ -137,8 +186,8 @@ func ProcessExpiredOrdersRefunds() error { // Prepare transfer method call method := "function transfer(address recipient, uint256 amount) public returns (bool)" params := []interface{}{ - order.ReturnAddress, // recipient address - balance.String(), // amount to transfer + order.RefundOrRecipientAddress, // recipient address + balance.String(), // amount to transfer } // Send the transfer transaction @@ -152,13 +201,13 @@ func ProcessExpiredOrdersRefunds() error { ) if err != nil { logger.WithFields(logger.Fields{ - "Error": err.Error(), - "OrderID": order.ID.String(), - "ReceiveAddress": receiveAddress, - "ReturnAddress": order.ReturnAddress, - "Balance": balance.String(), - "TokenContract": tokenContract, - "NetworkIdentifier": network.Identifier, + "Error": err.Error(), + "OrderID": order.ID.String(), + "ReceiveAddress": receiveAddress, + "RefundOrRecipientAddress": order.RefundOrRecipientAddress, + "Balance": balance.String(), + "TokenContract": tokenContract, + "NetworkIdentifier": network.Identifier, }).Errorf("Failed to send refund transfer transaction") return } diff --git a/tasks/setup_test.go b/tasks/setup_test.go index 743e2b1ad..ef6fb16f7 100644 --- a/tasks/setup_test.go +++ b/tasks/setup_test.go @@ -103,7 +103,7 @@ func setup() error { SetReceiveAddressExpiry(time.Now().Add(time.Hour)). SetFeePercent(decimal.NewFromFloat(5.0)). SetFeeAddress("0x1234567890123456789012345678901234567890"). - SetReturnAddress("0x0987654321098765432109876543210987654321"). + SetRefundOrRecipientAddress("0x0987654321098765432109876543210987654321"). SetInstitution("ABNGNGLA"). SetAccountIdentifier("1234567890"). SetAccountName("Test Account"). @@ -140,7 +140,8 @@ func setup() error { Memo: "", }, FromAddress: paymentOrder.FromAddress, - ReturnAddress: paymentOrder.ReturnAddress, + ReturnAddress: paymentOrder.RefundOrRecipientAddress, + RefundAddress: paymentOrder.RefundOrRecipientAddress, UpdatedAt: paymentOrder.UpdatedAt, CreatedAt: paymentOrder.CreatedAt, TxHash: paymentOrder.TxHash, diff --git a/tasks/stale_ops.go b/tasks/stale_ops.go index 16149d7e0..6c9d760a3 100644 --- a/tasks/stale_ops.go +++ b/tasks/stale_ops.go @@ -165,6 +165,7 @@ func RetryStaleUserOperations() error { lockOrders, err = storage.Client.PaymentOrder. Query(). Where( + paymentorder.DirectionEQ(paymentorder.DirectionOfframp), // On-chain refund only for offramp (user lock); onramp refund is provider-side paymentorder.GatewayIDNEQ(""), paymentorder.UpdatedAtGTE(time.Now().Add(-15*time.Minute)), paymentorder.StatusNEQ(paymentorder.StatusValidated), diff --git a/tasks/startup.go b/tasks/startup.go index 93071f20f..cea444dd8 100644 --- a/tasks/startup.go +++ b/tasks/startup.go @@ -69,10 +69,10 @@ func StartCronJobs() { logger.Errorf("StartCronJobs for SyncPaymentOrderFulfillments: %v", err) } - // Handle receive address validity every 6 minutes - _, err = scheduler.Every(6).Minutes().Do(HandleReceiveAddressValidity) + // Expire stale orders every 6 minutes: offramp receive-address validity + onramp Pending past VA validity (metadata.providerAccount.validUntil) + _, err = scheduler.Every(6).Minutes().Do(ExpireStaleOrders) if err != nil { - logger.Errorf("StartCronJobs for HandleReceiveAddressValidity: %v", err) + logger.Errorf("StartCronJobs for ExpireStaleOrders: %v", err) } // Retry stale user operations every 60 seconds diff --git a/types/types.go b/types/types.go index 0c24d8d32..04248ebc6 100644 --- a/types/types.go +++ b/types/types.go @@ -2,6 +2,7 @@ package types import ( "context" + "encoding/json" "math/big" "time" @@ -17,7 +18,6 @@ import ( "github.com/paycrest/aggregator/ent/institution" "github.com/paycrest/aggregator/ent/paymentorder" "github.com/paycrest/aggregator/ent/paymentorderfulfillment" - "github.com/paycrest/aggregator/ent/providerordertoken" "github.com/paycrest/aggregator/ent/providerprofile" "github.com/paycrest/aggregator/ent/transactionlog" "github.com/paycrest/aggregator/ent/user" @@ -80,7 +80,18 @@ type OrderCreatedEvent struct { Sender string } -// OrderSettledEvent represents a order settled event. +// SettleOutEvent represents an offramp settlement event (Gateway SettleOut). +type SettleOutEvent struct { + BlockNumber int64 + TxHash string + SplitOrderId string + OrderId string + LiquidityProvider string + SettlePercent decimal.Decimal + RebatePercent decimal.Decimal +} + +// OrderSettledEvent represents an order settled event. Only used for Starknet. type OrderSettledEvent struct { BlockNumber int64 TxHash string @@ -91,6 +102,19 @@ type OrderSettledEvent struct { RebatePercent decimal.Decimal } +// SettleInEvent represents an onramp settlement event (Gateway SettleIn). +type SettleInEvent struct { + BlockNumber int64 + TxHash string + OrderId string + LiquidityProvider string + Amount decimal.Decimal + Recipient string + Token string + AggregatorFee decimal.Decimal + Rate decimal.Decimal +} + // OrderRefundedEvent represents a order refunded event. type OrderRefundedEvent struct { BlockNumber int64 @@ -108,14 +132,14 @@ type OrderService interface { // Indexer provides an interface for indexing blockchain data to the database. type Indexer interface { - // Index all gateway events (OrderCreated, OrderSettled, OrderRefunded) in one efficient call + // Index all gateway events (OrderCreated, SettleOut, SettleIn, OrderRefunded) in one efficient call IndexGateway(ctx context.Context, network *ent.Network, address string, fromBlock int64, toBlock int64, txHash string) (*EventCounts, error) // Index receive address events IndexReceiveAddress(ctx context.Context, token *ent.Token, address string, fromBlock int64, toBlock int64, txHash string) (*EventCounts, error) - // Index provider address events (OrderSettled) - IndexProviderAddress(ctx context.Context, network *ent.Network, address string, fromBlock int64, toBlock int64, txHash string) (*EventCounts, error) + // Index provider address events (SettleOut) + IndexProviderSettlementAddress(ctx context.Context, network *ent.Network, address string, fromBlock int64, toBlock int64, txHash string) (*EventCounts, error) } // KYCProvider defines the interface for KYC verification providers @@ -176,15 +200,37 @@ type RegisterResponse struct { Email string `json:"email"` } -// AcceptOrderResponse is the response for the accept order endpoint +// AcceptOrderRequest is the request payload for the accept order endpoint +type AcceptOrderRequest struct { + Direction string `json:"direction,omitempty"` // "payin" or "payout" (default: "payout" for backward compatibility) + Amount string `json:"amount,omitempty"` // Required for payin orders +} + +// AcceptOrderResponse is the unified response for the accept order endpoint (payin and payout). +// Common: id, direction, amount, institution, accountIdentifier, accountName. +// Payout-only: memo, metadata. Payin-only: chainId, rpcUrl, delegationAddress. type AcceptOrderResponse struct { - ID uuid.UUID `json:"id"` + ID string `json:"id"` + Direction string `json:"direction"` // "payin" or "payout" Amount decimal.Decimal `json:"amount"` Institution string `json:"institution"` AccountIdentifier string `json:"accountIdentifier"` AccountName string `json:"accountName"` - Memo string `json:"memo"` - Metadata map[string]interface{} `json:"metadata"` + Memo string `json:"memo,omitempty"` + Metadata map[string]interface{} `json:"metadata,omitempty"` + ChainId string `json:"chainId,omitempty"` + RpcUrl string `json:"rpcUrl,omitempty"` + DelegationAddress string `json:"delegationAddress,omitempty"` +} + +// SetCodeAuthorization represents EIP-7702 SetCodeAuthorization for payin orders +type SetCodeAuthorization struct { + ChainID string `json:"chainId"` // Chain ID as string + Address string `json:"address"` // Delegation contract address + Nonce uint64 `json:"nonce"` // Authorization nonce + V uint8 `json:"v"` // Signature V component + R string `json:"r"` // Signature R component (hex string) + S string `json:"s"` // Signature S component (hex string) } // FulfillOrderPayload is the payload for the fulfill order endpoint @@ -193,6 +239,7 @@ type FulfillOrderPayload struct { TxID string `json:"txId" binding:"required"` ValidationStatus paymentorderfulfillment.ValidationStatus `json:"validationStatus"` ValidationError string `json:"validationError"` + Authorization *SetCodeAuthorization `json:"authorization,omitempty"` // Required for payin orders } // CancelOrderPayload is the payload for the cancel order endpoint @@ -236,23 +283,25 @@ type SenderOrderTokenPayload struct { // SenderProfilePayload is the payload for the sender profile endpoint type SenderProfilePayload struct { WebhookURL string `json:"webhookUrl"` + WebhookVersion string `json:"webhookVersion" binding:"omitempty,oneof=1 2"` DomainWhitelist []string `json:"domainWhitelist"` Tokens []SenderOrderTokenPayload `json:"tokens"` } // ProviderOrderTokenPayload defines the provider setting for a token type ProviderOrderTokenPayload struct { - Symbol string `json:"symbol" binding:"required"` - ConversionRateType providerordertoken.ConversionRateType `json:"conversionRateType" binding:"required,oneof=fixed floating"` - FixedConversionRate decimal.Decimal `json:"fixedConversionRate" binding:"required,gt=0"` - FloatingConversionRate decimal.Decimal `json:"floatingConversionRate" binding:"required"` - MaxOrderAmount decimal.Decimal `json:"maxOrderAmount" binding:"required,gt=0"` - MinOrderAmount decimal.Decimal `json:"minOrderAmount" binding:"required,gt=0"` - MaxOrderAmountOTC decimal.Decimal `json:"maxOrderAmountOtc" binding:"required,gte=0"` - MinOrderAmountOTC decimal.Decimal `json:"minOrderAmountOtc" binding:"required,gte=0"` - RateSlippage decimal.Decimal `json:"rateSlippage" binding:"omitempty,gte=0.1"` - SettlementAddress string `json:"settlementAddress" binding:"required"` - Network string `json:"network" binding:"required"` + Symbol string `json:"symbol" binding:"required"` + FixedBuyRate decimal.Decimal `json:"fixedBuyRate" binding:"omitempty,gte=0"` + FixedSellRate decimal.Decimal `json:"fixedSellRate" binding:"omitempty,gte=0"` + FloatingBuyDelta decimal.Decimal `json:"floatingBuyDelta" binding:"omitempty"` + FloatingSellDelta decimal.Decimal `json:"floatingSellDelta" binding:"omitempty"` + MaxOrderAmount decimal.Decimal `json:"maxOrderAmount" binding:"required,gt=0"` + MinOrderAmount decimal.Decimal `json:"minOrderAmount" binding:"required,gt=0"` + MaxOrderAmountOTC decimal.Decimal `json:"maxOrderAmountOtc" binding:"required,gte=0"` + MinOrderAmountOTC decimal.Decimal `json:"minOrderAmountOtc" binding:"required,gte=0"` + RateSlippage decimal.Decimal `json:"rateSlippage" binding:"omitempty,gte=0.1"` + SettlementAddress string `json:"settlementAddress" binding:"required"` + Network string `json:"network" binding:"required"` } // ProviderProfilePayload is the payload for the provider profile endpoint @@ -319,6 +368,7 @@ type SenderProfileResponse struct { LastName string `json:"lastName"` Email string `json:"email"` WebhookURL string `json:"webhookUrl"` + WebhookVersion string `json:"webhookVersion"` DomainWhitelist []string `json:"domainWhitelist"` Tokens []SenderOrderTokenResponse `json:"tokens"` APIKey APIKeyResponse `json:"apiKey"` @@ -454,7 +504,7 @@ type NewPaymentOrderPayload struct { Network string `json:"network" binding:"required"` Recipient PaymentOrderRecipient `json:"recipient" binding:"required"` Reference string `json:"reference"` - ReturnAddress string `json:"returnAddress"` + ReturnAddress string `json:"returnAddress"` // v1 API: kept for backward compatibility; maps to refund_or_recipient_address FeePercent decimal.Decimal `json:"feePercent"` FeeAddress string `json:"feeAddress"` } @@ -488,7 +538,8 @@ type SenderOrderResponse struct { GatewayID string `json:"gatewayId"` Recipient PaymentOrderRecipient `json:"recipient"` FromAddress string `json:"fromAddress"` - ReturnAddress string `json:"returnAddress"` + ReturnAddress string `json:"returnAddress"` // deprecated: use refundAddress; kept for backward compatibility + RefundAddress string `json:"refundAddress"` // preferred; same value as returnAddress ReceiveAddress string `json:"receiveAddress"` FeeAddress string `json:"feeAddress"` Reference string `json:"reference"` @@ -516,7 +567,8 @@ type PaymentOrderWebhookData struct { SenderID uuid.UUID `json:"senderId"` Recipient PaymentOrderRecipient `json:"recipient"` FromAddress string `json:"fromAddress"` - ReturnAddress string `json:"returnAddress"` + ReturnAddress string `json:"returnAddress"` // deprecated: use refundAddress; kept for backward compatibility + RefundAddress string `json:"refundAddress"` // preferred; same value as returnAddress Reference string `json:"reference"` UpdatedAt time.Time `json:"updatedAt"` CreatedAt time.Time `json:"createdAt"` @@ -526,20 +578,82 @@ type PaymentOrderWebhookData struct { // PaymentOrderWebhookPayload is the request type for a payment order webhook type PaymentOrderWebhookPayload struct { - Event string `json:"event"` - Data PaymentOrderWebhookData `json:"data"` + Event string `json:"event"` + WebhookVersion string `json:"webhookVersion"` // "1" or "2" + Data PaymentOrderWebhookData `json:"data"` } -// V2PaymentOrderSource represents the source configuration for v2 payment orders -type V2PaymentOrderSource struct { - Type string `json:"type" binding:"required,eq=crypto"` - Currency string `json:"currency" binding:"required"` - PaymentRail string `json:"paymentRail" binding:"required"` - RefundAddress string `json:"refundAddress" binding:"required"` +// V2PaymentOrderWebhookRecipient is the recipient in v2 webhook payload (no KYC). +type V2PaymentOrderWebhookRecipient struct { + Institution string `json:"institution"` + AccountIdentifier string `json:"accountIdentifier"` + AccountName string `json:"accountName"` + Memo string `json:"memo"` + ProviderID string `json:"providerId,omitempty"` + Metadata map[string]interface{} `json:"metadata,omitempty"` + Currency string `json:"currency,omitempty"` +} + +// V2PaymentOrderWebhookRefundAccount is the refund account in v2 webhook payload (onramp; no KYC). +type V2PaymentOrderWebhookRefundAccount struct { + Institution string `json:"institution"` + AccountIdentifier string `json:"accountIdentifier"` + AccountName string `json:"accountName"` + Metadata map[string]interface{} `json:"metadata,omitempty"` } -// V2PaymentOrderRecipient represents the recipient configuration for v2 payment orders -type V2PaymentOrderRecipient struct { +// V2PaymentOrderWebhookData is the v2 webhook data, aligned with API schema: providerAccount, source, destination (same types as V2PaymentOrderResponse). +type V2PaymentOrderWebhookData struct { + ID uuid.UUID `json:"id"` + Direction string `json:"direction"` // "offramp" or "onramp" + Amount decimal.Decimal `json:"amount"` + AmountInUSD decimal.Decimal `json:"amountInUsd"` + AmountPaid decimal.Decimal `json:"amountPaid"` + AmountReturned decimal.Decimal `json:"amountReturned"` + PercentSettled decimal.Decimal `json:"percentSettled"` + SenderFee decimal.Decimal `json:"senderFee"` + TransactionFee decimal.Decimal `json:"transactionFee"` // network fee + protocol fee + Rate decimal.Decimal `json:"rate"` + GatewayID string `json:"gatewayId"` + SenderID uuid.UUID `json:"senderId"` + ProviderAccount any `json:"providerAccount,omitempty"` // V2CryptoProviderAccount (offramp) or V2FiatProviderAccount (onramp) + Source any `json:"source,omitempty"` // V2CryptoSource (offramp) or V2FiatSource (onramp) + Destination any `json:"destination,omitempty"` // V2FiatDestination (offramp) or V2CryptoDestination (onramp) + FromAddress string `json:"fromAddress"` + Reference string `json:"reference"` + Timestamp time.Time `json:"timestamp"` // when this webhook event was sent (for ordering/idempotency) + TxHash string `json:"txHash"` + Status paymentorder.Status `json:"status"` +} + +// V2PaymentOrderWebhookPayload is the v2 payment order webhook payload. +type V2PaymentOrderWebhookPayload struct { + Event string `json:"event"` + WebhookVersion string `json:"webhookVersion"` + Data V2PaymentOrderWebhookData `json:"data"` +} + +// V2CryptoSource represents the crypto source configuration for offramp v2 payment orders +type V2CryptoSource struct { + Type string `json:"type" binding:"required,eq=crypto"` + Currency string `json:"currency" binding:"required"` + Network string `json:"network" binding:"required"` + RefundAddress string `json:"refundAddress" binding:"required"` + KYC *KYCDetail `json:"kyc,omitempty"` +} + +// V2FiatDestination represents the fiat destination configuration for offramp v2 payment orders +type V2FiatDestination struct { + Type string `json:"type" binding:"required,eq=fiat"` + Currency string `json:"currency" binding:"required"` + Country string `json:"country,omitempty"` + ProviderID string `json:"providerId,omitempty"` + Recipient V2FiatRecipient `json:"recipient" binding:"required"` + KYC *KYCDetail `json:"kyc,omitempty"` +} + +// V2FiatRecipient represents the fiat recipient configuration for offramp v2 payment orders +type V2FiatRecipient struct { Institution string `json:"institution" binding:"required"` AccountIdentifier string `json:"accountIdentifier" binding:"required"` AccountName string `json:"accountName" binding:"required"` @@ -547,19 +661,37 @@ type V2PaymentOrderRecipient struct { Metadata map[string]interface{} `json:"metadata,omitempty"` } -// V2PaymentOrderDestination represents the destination configuration for v2 payment orders -type V2PaymentOrderDestination struct { - Type string `json:"type" binding:"required,eq=fiat"` - Currency string `json:"currency" binding:"required"` - Country string `json:"country,omitempty"` - ProviderID string `json:"providerId,omitempty"` - Recipient V2PaymentOrderRecipient `json:"recipient" binding:"required"` +// V2FiatSource represents the fiat source configuration for onramp v2 payment orders +type V2FiatSource struct { + Type string `json:"type" binding:"required,eq=fiat"` + Currency string `json:"currency" binding:"required"` + Country string `json:"country,omitempty"` + RefundAccount V2FiatRefundAccount `json:"refundAccount" binding:"required"` + KYC *KYCDetail `json:"kyc,omitempty"` +} + +// V2FiatRefundAccount represents the refund account for onramp orders +type V2FiatRefundAccount struct { + Institution string `json:"institution" binding:"required"` + AccountIdentifier string `json:"accountIdentifier" binding:"required"` + AccountName string `json:"accountName" binding:"required"` + Metadata map[string]interface{} `json:"metadata,omitempty"` +} + +// V2CryptoDestination represents the crypto destination configuration for onramp v2 payment orders +type V2CryptoDestination struct { + Type string `json:"type" binding:"required,eq=crypto"` + Currency string `json:"currency" binding:"required"` + Network string `json:"network" binding:"required"` + ProviderID string `json:"providerId,omitempty"` + Recipient V2CryptoRecipient `json:"recipient" binding:"required"` + KYC *KYCDetail `json:"kyc,omitempty"` } -// V2PaymentOrderKYC represents KYC information for v2 payment orders -type V2PaymentOrderKYC struct { - Recipient *KYCDetail `json:"recipient,omitempty"` - Sender *KYCDetail `json:"sender,omitempty"` +// V2CryptoRecipient represents the crypto recipient configuration for onramp v2 payment orders +type V2CryptoRecipient struct { + Address string `json:"address" binding:"required"` + Network string `json:"network" binding:"required"` } // EntityType represents the type of entity @@ -640,40 +772,84 @@ type PrincipalAddress struct { } // V2PaymentOrderPayload is the payload for the v2 create payment order endpoint +// Source and Destination are polymorphic (json.RawMessage) to support both offramp and onramp flows type V2PaymentOrderPayload struct { - Amount string `json:"amount" binding:"required"` - Rate string `json:"rate,omitempty"` - AmountIn string `json:"amountIn,omitempty" binding:"omitempty,oneof=crypto fiat"` - SenderFee string `json:"senderFee,omitempty"` - SenderFeePercent string `json:"senderFeePercent,omitempty"` - Reference string `json:"reference,omitempty"` - Source V2PaymentOrderSource `json:"source" binding:"required"` - Destination V2PaymentOrderDestination `json:"destination" binding:"required"` - KYC *V2PaymentOrderKYC `json:"kyc,omitempty"` -} - -// V2ProviderAccount represents provider account details in v2 responses -type V2ProviderAccount struct { - PaymentRail string `json:"paymentRail"` + Amount string `json:"amount" binding:"required"` + Rate string `json:"rate,omitempty"` + AmountIn string `json:"amountIn,omitempty" binding:"omitempty,oneof=crypto fiat"` + SenderFee string `json:"senderFee,omitempty"` + SenderFeePercent string `json:"senderFeePercent,omitempty"` + Reference string `json:"reference,omitempty"` + Source json.RawMessage `json:"source" binding:"required"` + Destination json.RawMessage `json:"destination" binding:"required"` +} + +// V2CryptoProviderAccount represents provider account details for offramp (crypto source) in v2 responses +type V2CryptoProviderAccount struct { + Network string `json:"network"` ReceiveAddress string `json:"receiveAddress"` ValidUntil time.Time `json:"validUntil"` } +// V2FiatProviderAccount represents provider account details for onramp (fiat source) in v2 responses +type V2FiatProviderAccount struct { + Institution string `json:"institution"` + AccountIdentifier string `json:"accountIdentifier"` + AccountName string `json:"accountName"` + ValidUntil time.Time `json:"validUntil"` +} + // V2PaymentOrderResponse is the response type for v2 payment order creation +// ProviderAccount, Source, and Destination are polymorphic (any) to support both offramp and onramp flows type V2PaymentOrderResponse struct { - ID uuid.UUID `json:"id"` - Status string `json:"status"` - Timestamp time.Time `json:"timestamp"` - Amount string `json:"amount"` - Rate string `json:"rate,omitempty"` - SenderFee string `json:"senderFee"` - SenderFeePercent string `json:"senderFeePercent"` - TransactionFee string `json:"transactionFee"` - Reference string `json:"reference"` - ProviderAccount V2ProviderAccount `json:"providerAccount"` - Source V2PaymentOrderSource `json:"source"` - Destination V2PaymentOrderDestination `json:"destination"` - KYC *V2PaymentOrderKYC `json:"kyc,omitempty"` + ID uuid.UUID `json:"id"` + Status string `json:"status"` + Timestamp time.Time `json:"timestamp"` + Amount string `json:"amount"` // Crypto amount (token units) - consistent for both flows + AmountIn string `json:"amountIn"` // Echo/normalize the input amount (crypto | fiat) + Rate string `json:"rate,omitempty"` + SenderFee string `json:"senderFee"` // Crypto amount (token units) - consistent for both flows + SenderFeePercent string `json:"senderFeePercent"` + TransactionFee string `json:"transactionFee"` + Reference string `json:"reference"` + ProviderAccount any `json:"providerAccount"` // V2CryptoProviderAccount for offramp, V2FiatProviderAccount for onramp + Source any `json:"source"` // V2CryptoSource for offramp, V2FiatSource for onramp + Destination any `json:"destination"` // V2FiatDestination for offramp, V2CryptoDestination for onramp +} + +// V2PaymentOrderGetResponse is the v2 response for GET payment order (single or list item). Aligned with v2 API schema. +type V2PaymentOrderGetResponse struct { + ID uuid.UUID `json:"id"` + Status string `json:"status"` + Direction string `json:"direction"` // "offramp" or "onramp" + CreatedAt time.Time `json:"createdAt"` + UpdatedAt time.Time `json:"updatedAt"` + Amount string `json:"amount"` + AmountIn string `json:"amountIn"` + AmountInUsd string `json:"amountInUsd,omitempty"` + AmountPaid string `json:"amountPaid,omitempty"` + AmountReturned string `json:"amountReturned,omitempty"` + PercentSettled string `json:"percentSettled,omitempty"` + Rate string `json:"rate,omitempty"` + SenderFee string `json:"senderFee"` + SenderFeePercent string `json:"senderFeePercent"` + TransactionFee string `json:"transactionFee"` + Reference string `json:"reference"` + ProviderAccount any `json:"providerAccount"` + Source any `json:"source"` + Destination any `json:"destination"` + TxHash string `json:"txHash,omitempty"` + TransactionLogs []TransactionLog `json:"transactionLogs,omitempty"` + CancellationReasons []string `json:"cancellationReasons,omitempty"` // provider view + OTCExpiry *time.Time `json:"otcExpiry,omitempty"` // provider view +} + +// V2PaymentOrderListResponse is the v2 response for GET payment orders list. +type V2PaymentOrderListResponse struct { + TotalRecords int `json:"total"` + Page int `json:"page"` + PageSize int `json:"pageSize"` + Orders []V2PaymentOrderGetResponse `json:"orders"` } // ConfirmEmailPayload is the payload for the confirmEmail endpoint @@ -700,9 +876,10 @@ type SendEmailResponse struct { // MarketRateResponse is the response for the market rate endpoint type MarketRateResponse struct { - MarketRate decimal.Decimal `json:"marketRate"` - MinimumRate decimal.Decimal `json:"minimumRate"` - MaximumRate decimal.Decimal `json:"maximumRate"` + MarketBuyRate decimal.Decimal `json:"marketBuyRate"` + MarketSellRate decimal.Decimal `json:"marketSellRate"` + MinimumRate decimal.Decimal `json:"minimumRate"` + MaximumRate decimal.Decimal `json:"maximumRate"` } // RateMetadata contains optional metadata for rate responses @@ -724,12 +901,13 @@ type SupportedInstitutions struct { // SupportedCurrencies is the supported currencies response struct. type SupportedCurrencies struct { - Code string `json:"code"` - Name string `json:"name"` - ShortName string `json:"shortName"` - Decimals int8 `json:"decimals"` - Symbol string `json:"symbol"` - MarketRate decimal.Decimal `json:"marketRate"` + Code string `json:"code"` + Name string `json:"name"` + ShortName string `json:"shortName"` + Decimals int8 `json:"decimals"` + Symbol string `json:"symbol"` + MarketBuyRate decimal.Decimal `json:"marketBuyRate"` + MarketSellRate decimal.Decimal `json:"marketSellRate"` } // Response is the struct for an API response @@ -849,7 +1027,8 @@ type LinkedAddressTransaction struct { GatewayID string `json:"gatewayId"` Recipient LinkedAddressTransactionRecipient `json:"recipient"` FromAddress string `json:"fromAddress"` - ReturnAddress string `json:"returnAddress"` + ReturnAddress string `json:"returnAddress"` // deprecated: use refundAddress; kept for backward compatibility + RefundAddress string `json:"refundAddress"` // preferred; same value as returnAddress CreatedAt time.Time `json:"createdAt"` UpdatedAt time.Time `json:"updatedAt"` TxHash string `json:"txHash"` @@ -924,12 +1103,7 @@ type IndexTransactionRequest struct { // IndexTransactionResponse represents the response for the index transaction endpoint type IndexTransactionResponse struct { - Events struct { - Transfer int `json:"Transfer"` - OrderCreated int `json:"OrderCreated"` - OrderSettled int `json:"OrderSettled"` - OrderRefunded int `json:"OrderRefunded"` - } `json:"events"` + Events EventCounts `json:"events"` } // EventCounts represents the count of different event types found during indexing @@ -937,6 +1111,8 @@ type EventCounts struct { Transfer int `json:"Transfer"` OrderCreated int `json:"OrderCreated"` OrderSettled int `json:"OrderSettled"` + SettleOut int `json:"SettleOut"` // SettleOut (offramp) + SettleIn int `json:"SettleIn"` // SettleIn (onramp) OrderRefunded int `json:"OrderRefunded"` } diff --git a/utils/rpc_events.go b/utils/rpc_events.go index 7d46b8eef..356b8e435 100644 --- a/utils/rpc_events.go +++ b/utils/rpc_events.go @@ -8,12 +8,17 @@ import ( "github.com/ethereum/go-ethereum/core/types" ) -// Event signatures for Gateway and token contract events +// Event signatures for Gateway and token contract events (topic0 = keccak256(event signature)) const ( - TransferEventSignature = "0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef" - OrderCreatedEventSignature = "0x40ccd1ceb111a3c186ef9911e1b876dc1f789ed331b86097b3b8851055b6a137" - OrderSettledEventSignature = "0x57c683de2e7c8263c7f57fd108416b9bdaa7a6e7f2e4e7102c3b6f9e37f1cc37" - OrderRefundedEventSignature = "0x0736fe428e1747ca8d387c2e6fa1a31a0cde62d3a167c40a46ade59a3cdc828e" + TransferEventSignature = "0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef" + OrderCreatedEventSignature = "0x40ccd1ceb111a3c186ef9911e1b876dc1f789ed331b86097b3b8851055b6a137" + OrderRefundedEventSignature = "0x0736fe428e1747ca8d387c2e6fa1a31a0cde62d3a167c40a46ade59a3cdc828e" + + // SettleOut (offramp): Gateway emits SettleOut(bytes32 splitOrderId, bytes32 indexed orderId, address indexed liquidityProvider, uint64 settlePercent, uint64 rebatePercent) + SettleOutEventSignature = "0x1e4a1a8ad772d3f0dbb387879bc5e8faadf16e0513bf77d50620741ab92b4c45" + + // SettleIn (onramp): Gateway emits SettleIn(bytes32 indexed orderId, uint256 indexed amount, address indexed recipient, address token, uint256 aggregatorFee, uint96 rate) + SettleInEventSignature = "0x44de25d68888fdbe51bc67bbc990724fb5fa28119062e5f4ca623aefcaa70ecb" OrderCreatedStarknetSelector = "0x3427759bfd3b941f14e687e129519da3c9b0046c5b9aaa290bb1dede63753b3" OrderSettledStarknetSelector = "0x2f4d375c16c9a465e9396e640dcf9032795bee57646a3117d94b9304be0868c" OrderRefundedStarknetSelector = "0x2b1527a936433fc64df27b599aa49d8cbaac3a88b1b3888cf4384b9e8bea9cd" @@ -112,14 +117,14 @@ func DecodeOrderCreatedEvent(log types.Log) (map[string]interface{}, error) { }, nil } -// DecodeOrderSettledEvent decodes an OrderSettled event from RPC log -func DecodeOrderSettledEvent(log types.Log) (map[string]interface{}, error) { - // OrderSettled event: OrderSettled(bytes32 splitOrderId, bytes32 indexed orderId, address indexed liquidityProvider, uint64 settlePercent, uint64 rebatePercent) +// DecodeSettleOutEvent decodes a SettleOut event from RPC log (offramp settlement) +func DecodeSettleOutEvent(log types.Log) (map[string]interface{}, error) { + // SettleOut event: SettleOut(bytes32 splitOrderId, bytes32 indexed orderId, address indexed liquidityProvider, uint64 settlePercent, uint64 rebatePercent) // Topics: [eventSignature, orderId, liquidityProvider] // Data: [splitOrderId (32 bytes), settlePercent (32 bytes padded), rebatePercent (32 bytes padded)] if len(log.Topics) != 3 { - return nil, fmt.Errorf("invalid OrderSettled event: expected 3 topics, got %d", len(log.Topics)) + return nil, fmt.Errorf("invalid SettleOut event: expected 3 topics, got %d", len(log.Topics)) } orderId := common.BytesToHash(log.Topics[1].Bytes()) @@ -128,7 +133,7 @@ func DecodeOrderSettledEvent(log types.Log) (map[string]interface{}, error) { // Decode non-indexed parameters from data // The data contains: splitOrderId (32 bytes) + settlePercent (32 bytes) + rebatePercent (32 bytes) if len(log.Data) < 96 { - return nil, fmt.Errorf("invalid OrderSettled event data: too short") + return nil, fmt.Errorf("invalid SettleOut event data: too short") } splitOrderId := common.BytesToHash(log.Data[:32]) @@ -148,6 +153,44 @@ func DecodeOrderSettledEvent(log types.Log) (map[string]interface{}, error) { }, nil } +// DecodeSettleInEvent decodes a SettleIn event from RPC log (onramp settlement) +func DecodeSettleInEvent(log types.Log) (map[string]interface{}, error) { + // SettleIn event: SettleIn(bytes32 indexed orderId, address indexed liquidityProvider, address indexed recipient, uint256 amount, address token, uint256 aggregatorFee, uint96 rate) + // Topics: [eventSignature, orderId, liquidityProvider, recipient] + // Data: [amount (32), token (32), aggregatorFee (32), rate (32)] + + if len(log.Topics) != 4 { + return nil, fmt.Errorf("invalid SettleIn event: expected 4 topics, got %d", len(log.Topics)) + } + + orderId := common.BytesToHash(log.Topics[1].Bytes()) + liquidityProvider := common.HexToAddress(log.Topics[2].Hex()) + recipient := common.HexToAddress(log.Topics[3].Hex()) + + if len(log.Data) < 128 { + return nil, fmt.Errorf("invalid SettleIn event data: too short") + } + + amount := new(big.Int).SetBytes(log.Data[:32]) + token := common.BytesToAddress(log.Data[32:64]) + aggregatorFee := new(big.Int).SetBytes(log.Data[64:96]) + rate := new(big.Int).SetBytes(log.Data[96:128]) + + return map[string]interface{}{ + "indexed_params": map[string]interface{}{ + "orderId": orderId.Hex(), + "liquidityProvider": liquidityProvider.Hex(), + "recipient": recipient.Hex(), + }, + "non_indexed_params": map[string]interface{}{ + "amount": amount.String(), + "token": token.Hex(), + "aggregatorFee": aggregatorFee.String(), + "rate": rate.String(), + }, + }, nil +} + // DecodeOrderRefundedEvent decodes an OrderRefunded event from RPC log func DecodeOrderRefundedEvent(log types.Log) (map[string]interface{}, error) { // OrderRefunded event: OrderRefunded(uint256 fee, bytes32 indexed orderId) @@ -209,8 +252,10 @@ func ProcessRPCEvents(events []interface{}, eventSignature string) error { decoded, err = DecodeTransferEvent(mockLog) case OrderCreatedEventSignature: decoded, err = DecodeOrderCreatedEvent(mockLog) - case OrderSettledEventSignature: - decoded, err = DecodeOrderSettledEvent(mockLog) + case SettleOutEventSignature: + decoded, err = DecodeSettleOutEvent(mockLog) + case SettleInEventSignature: + decoded, err = DecodeSettleInEvent(mockLog) case OrderRefundedEventSignature: decoded, err = DecodeOrderRefundedEvent(mockLog) default: @@ -267,8 +312,10 @@ func ProcessRPCEventsBySignature(events []interface{}) error { decoded, err = DecodeTransferEvent(mockLog) case OrderCreatedEventSignature: decoded, err = DecodeOrderCreatedEvent(mockLog) - case OrderSettledEventSignature: - decoded, err = DecodeOrderSettledEvent(mockLog) + case SettleOutEventSignature: + decoded, err = DecodeSettleOutEvent(mockLog) + case SettleInEventSignature: + decoded, err = DecodeSettleInEvent(mockLog) case OrderRefundedEventSignature: decoded, err = DecodeOrderRefundedEvent(mockLog) default: diff --git a/utils/test/db.go b/utils/test/db.go index b87ad72e1..a89b479a6 100644 --- a/utils/test/db.go +++ b/utils/test/db.go @@ -212,7 +212,7 @@ func CreateTestPaymentOrder(token *ent.Token, overrides map[string]interface{}) if hasSender { payload["fee_percent"] = 0.0 payload["fee_address"] = "0x1234567890123456789012345678901234567890" - payload["return_address"] = "0x0987654321098765432109876543210987654321" + payload["refund_or_recipient_address"] = "0x0987654321098765432109876543210987654321" } // Apply overrides @@ -303,7 +303,7 @@ func CreateTestPaymentOrder(token *ent.Token, overrides map[string]interface{}) SetReceiveAddressExpiry(expiry). SetFeePercent(decimal.NewFromFloat(payload["fee_percent"].(float64))). SetFeeAddress(payload["fee_address"].(string)). - SetReturnAddress(payload["return_address"].(string)). + SetRefundOrRecipientAddress(payload["refund_or_recipient_address"].(string)). SetMemo(payload["memo"].(string)) } @@ -496,20 +496,21 @@ func AddProvisionBucketToPaymentOrder(order *ent.PaymentOrder, bucketId int) (*e } func AddProviderOrderTokenToProvider(overrides map[string]interface{}) (*ent.ProviderOrderToken, error) { - // Default payload + // Default payload using new two-sided rate fields payload := map[string]interface{}{ - "currency_id": uuid.New(), - "fixed_conversion_rate": decimal.NewFromFloat(1.0), - "conversion_rate_type": "fixed", - "floating_conversion_rate": decimal.NewFromFloat(1.0), - "max_order_amount": decimal.NewFromFloat(1.0), - "min_order_amount": decimal.NewFromFloat(1.0), - "max_order_amount_otc": decimal.NewFromFloat(10000.0), - "min_order_amount_otc": decimal.NewFromFloat(100.0), - "provider": nil, - "token_id": 0, - "settlement_address": "0x1234567890123456789012345678901234567890", - "network": "localhost", + "currency_id": uuid.New(), + "fixed_buy_rate": decimal.NewFromFloat(1.0), + "fixed_sell_rate": decimal.NewFromFloat(1.0), + "floating_buy_delta": decimal.NewFromFloat(0), + "floating_sell_delta": decimal.NewFromFloat(0), + "max_order_amount": decimal.NewFromFloat(1.0), + "min_order_amount": decimal.NewFromFloat(1.0), + "max_order_amount_otc": decimal.NewFromFloat(10000.0), + "min_order_amount_otc": decimal.NewFromFloat(100.0), + "provider": nil, + "token_id": 0, + "settlement_address": "0x1234567890123456789012345678901234567890", + "network": "localhost", } // Apply overrides @@ -533,9 +534,11 @@ func AddProviderOrderTokenToProvider(overrides map[string]interface{}) (*ent.Pro SetMinOrderAmount(payload["min_order_amount"].(decimal.Decimal)). SetMaxOrderAmountOtc(payload["max_order_amount_otc"].(decimal.Decimal)). SetMinOrderAmountOtc(payload["min_order_amount_otc"].(decimal.Decimal)). - SetConversionRateType(providerordertoken.ConversionRateType(payload["conversion_rate_type"].(string))). - SetFixedConversionRate(payload["fixed_conversion_rate"].(decimal.Decimal)). - SetFloatingConversionRate(payload["floating_conversion_rate"].(decimal.Decimal)). + // Two-sided rate config for tests (defaults to symmetric 1.0 buy/sell) + SetFixedBuyRate(payload["fixed_buy_rate"].(decimal.Decimal)). + SetFixedSellRate(payload["fixed_sell_rate"].(decimal.Decimal)). + SetFloatingBuyDelta(payload["floating_buy_delta"].(decimal.Decimal)). + SetFloatingSellDelta(payload["floating_sell_delta"].(decimal.Decimal)). SetSettlementAddress(payload["settlement_address"].(string)). SetNetwork(payload["network"].(string)). SetTokenID(payload["token_id"].(int)). @@ -705,7 +708,9 @@ func CreateTestFiatCurrency(overrides map[string]interface{}) (*ent.FiatCurrency SetDecimals(payload["decimals"].(int)). SetSymbol(payload["symbol"].(string)). SetName(payload["name"].(string)). - SetMarketRate(decimal.NewFromFloat(payload["market_rate"].(float64))). + // Use two-sided market rates for tests; default both to same value + SetMarketBuyRate(decimal.NewFromFloat(payload["market_rate"].(float64))). + SetMarketSellRate(decimal.NewFromFloat(payload["market_rate"].(float64))). SetIsEnabled(true). AddInstitutions(institutions...). Save(context.Background()) diff --git a/utils/utils.go b/utils/utils.go index 99e04cec8..2a94b67c2 100644 --- a/utils/utils.go +++ b/utils/utils.go @@ -210,8 +210,9 @@ func AbsPercentageDeviation(trueValue, measuredValue decimal.Decimal) decimal.De return deviation.Abs() } -// CalculatePaymentOrderAmountInUSD calculates the amount in USD for a payment order -func CalculatePaymentOrderAmountInUSD(amount decimal.Decimal, token *ent.Token, institution *ent.Institution) decimal.Decimal { +// CalculatePaymentOrderAmountInUSD calculates the amount in USD for a payment order. +// It uses MarketBuyRate for onramp (when non-zero) and MarketSellRate for offramp or when buy rate is zero. +func CalculatePaymentOrderAmountInUSD(amount decimal.Decimal, token *ent.Token, institution *ent.Institution, direction paymentorder.Direction) decimal.Decimal { // Guard against nil inputs if token == nil || institution == nil { return amount @@ -228,12 +229,20 @@ func CalculatePaymentOrderAmountInUSD(amount decimal.Decimal, token *ent.Token, fiatCurrency = institutionCurrency } - // Only multiply when the token matches the institution's fiat currency - if fiatCurrency != nil && token.BaseCurrency == fiatCurrency.Code && !fiatCurrency.MarketRate.IsZero() { - return amount.Div(fiatCurrency.MarketRate) + if fiatCurrency == nil || token.BaseCurrency != fiatCurrency.Code { + return amount } - return amount + // Onramp: use buy rate (crypto bought with fiat); offramp or zero buy rate: use sell rate + var rate decimal.Decimal + if direction == paymentorder.DirectionOnramp && !fiatCurrency.MarketBuyRate.IsZero() { + rate = fiatCurrency.MarketBuyRate + } else if !fiatCurrency.MarketSellRate.IsZero() { + rate = fiatCurrency.MarketSellRate + } else { + return amount + } + return amount.Div(rate) } // SendPaymentOrderWebhook notifies a sender when the status of a payment order changes @@ -244,6 +253,7 @@ func SendPaymentOrderWebhook(ctx context.Context, paymentOrder *ent.PaymentOrder Query(). Where(paymentorder.IDEQ(paymentOrder.ID)). WithSenderProfile(). + WithProvider(). WithToken(func(tq *ent.TokenQuery) { tq.WithNetwork() }). @@ -295,45 +305,86 @@ func SendPaymentOrderWebhook(ctx context.Context, paymentOrder *ent.PaymentOrder return err } - // Create the payload var providerID string if paymentOrder.Edges.Provider != nil { providerID = paymentOrder.Edges.Provider.ID } - payloadStruct := types.PaymentOrderWebhookPayload{ - Event: event, - Data: types.PaymentOrderWebhookData{ - ID: paymentOrder.ID, - Amount: paymentOrder.Amount, - AmountPaid: paymentOrder.AmountPaid, - AmountReturned: paymentOrder.AmountReturned, - PercentSettled: paymentOrder.PercentSettled, - SenderFee: paymentOrder.SenderFee, - NetworkFee: paymentOrder.NetworkFee, - Rate: paymentOrder.Rate, - Network: paymentOrder.Edges.Token.Edges.Network.Identifier, - GatewayID: paymentOrder.GatewayID, - SenderID: profile.ID, - Recipient: types.PaymentOrderRecipient{ - Currency: institution.Edges.FiatCurrency.Code, - Institution: paymentOrder.Institution, - AccountIdentifier: paymentOrder.AccountIdentifier, - AccountName: paymentOrder.AccountName, - ProviderID: providerID, - Memo: paymentOrder.Memo, + + webhookVersion := profile.WebhookVersion + if webhookVersion == "" { + webhookVersion = "1" + } + + var payload map[string]interface{} + if webhookVersion == "2" { + // V2: aligned with API schema — providerAccount, source, destination (same types as V2PaymentOrderResponse). + source, destination, providerAccount := BuildV2OrderSourceDestinationProviderAccount(paymentOrder, institution) + + payloadStructV2 := types.V2PaymentOrderWebhookPayload{ + Event: event, + WebhookVersion: webhookVersion, + Data: types.V2PaymentOrderWebhookData{ + ID: paymentOrder.ID, + Direction: string(paymentOrder.Direction), + Amount: paymentOrder.Amount, + AmountInUSD: paymentOrder.AmountInUsd, + AmountPaid: paymentOrder.AmountPaid, + AmountReturned: paymentOrder.AmountReturned, + PercentSettled: paymentOrder.PercentSettled, + SenderFee: paymentOrder.SenderFee, + TransactionFee: paymentOrder.NetworkFee.Add(paymentOrder.ProtocolFee), + Rate: paymentOrder.Rate, + GatewayID: paymentOrder.GatewayID, + SenderID: profile.ID, + ProviderAccount: providerAccount, + Source: source, + Destination: destination, + FromAddress: paymentOrder.FromAddress, + Reference: paymentOrder.Reference, + Timestamp: time.Now().UTC(), + TxHash: paymentOrder.TxHash, + Status: paymentOrder.Status, + }, + } + payload = StructToMap(payloadStructV2) + } else { + // V1: current payload (backward compatible) + payloadStruct := types.PaymentOrderWebhookPayload{ + Event: event, + WebhookVersion: webhookVersion, + Data: types.PaymentOrderWebhookData{ + ID: paymentOrder.ID, + Amount: paymentOrder.Amount, + AmountPaid: paymentOrder.AmountPaid, + AmountReturned: paymentOrder.AmountReturned, + PercentSettled: paymentOrder.PercentSettled, + SenderFee: paymentOrder.SenderFee, + NetworkFee: paymentOrder.NetworkFee, + Rate: paymentOrder.Rate, + Network: paymentOrder.Edges.Token.Edges.Network.Identifier, + GatewayID: paymentOrder.GatewayID, + SenderID: profile.ID, + Recipient: types.PaymentOrderRecipient{ + Currency: institution.Edges.FiatCurrency.Code, + Institution: paymentOrder.Institution, + AccountIdentifier: paymentOrder.AccountIdentifier, + AccountName: paymentOrder.AccountName, + ProviderID: providerID, + Memo: paymentOrder.Memo, + }, + FromAddress: paymentOrder.FromAddress, + ReturnAddress: paymentOrder.RefundOrRecipientAddress, + RefundAddress: paymentOrder.RefundOrRecipientAddress, + Reference: paymentOrder.Reference, + UpdatedAt: paymentOrder.UpdatedAt, + CreatedAt: paymentOrder.CreatedAt, + TxHash: paymentOrder.TxHash, + Status: paymentOrder.Status, }, - FromAddress: paymentOrder.FromAddress, - ReturnAddress: paymentOrder.ReturnAddress, - Reference: paymentOrder.Reference, - UpdatedAt: paymentOrder.UpdatedAt, - CreatedAt: paymentOrder.CreatedAt, - TxHash: paymentOrder.TxHash, - Status: paymentOrder.Status, - }, + } + payload = StructToMap(payloadStruct) } - payload := StructToMap(payloadStruct) - // Compute HMAC signature apiKey, err := profile.QueryAPIKey().Only(ctx) if err != nil { @@ -377,6 +428,196 @@ func SendPaymentOrderWebhook(ctx context.Context, paymentOrder *ent.PaymentOrder return nil } +// ParseValidUntilFromMetadata extracts a time from metadata validUntil (string RFC3339 or numeric Unix seconds). +func ParseValidUntilFromMetadata(v interface{}) (time.Time, bool) { + if v == nil { + return time.Time{}, false + } + switch x := v.(type) { + case string: + t, err := time.Parse(time.RFC3339, x) + if err != nil { + return time.Time{}, false + } + return t, true + case float64: + return time.Unix(int64(x), 0), true + case int: + return time.Unix(int64(x), 0), true + case int64: + return time.Unix(x, 0), true + case int32: + return time.Unix(int64(x), 0), true + default: + return time.Time{}, false + } +} + +// BuildV2OrderSourceDestinationProviderAccount builds source, destination, and providerAccount for v2 API/get responses. +// paymentOrder must have Token and Token.Edges.Network loaded. institution is required for fiat (currency); use nil only if unavailable (then currency/institution display may be empty). +func BuildV2OrderSourceDestinationProviderAccount(paymentOrder *ent.PaymentOrder, institution *ent.Institution) (source, destination, providerAccount any) { + networkID := "" + if paymentOrder.Edges.Token != nil && paymentOrder.Edges.Token.Edges.Network != nil { + networkID = paymentOrder.Edges.Token.Edges.Network.Identifier + } + tokenSymbol := "" + if paymentOrder.Edges.Token != nil { + tokenSymbol = paymentOrder.Edges.Token.Symbol + } + currencyCode := "" + if institution != nil && institution.Edges.FiatCurrency != nil { + currencyCode = institution.Edges.FiatCurrency.Code + } + providerID := "" + if paymentOrder.Edges.Provider != nil { + providerID = paymentOrder.Edges.Provider.ID + } + + isOnramp := paymentOrder.Direction == paymentorder.DirectionOnramp + if isOnramp { + if paymentOrder.Metadata != nil { + if pa, ok := paymentOrder.Metadata["providerAccount"].(map[string]interface{}); ok { + var inst, acctID, acctName string + if v, ok := pa["institution"].(string); ok { + inst = v + } + if v, ok := pa["accountIdentifier"].(string); ok { + acctID = v + } + if v, ok := pa["accountName"].(string); ok { + acctName = v + } + var validUntil time.Time + if t, ok := ParseValidUntilFromMetadata(pa["validUntil"]); ok { + validUntil = t + } + providerAccount = &types.V2FiatProviderAccount{ + Institution: inst, + AccountIdentifier: acctID, + AccountName: acctName, + ValidUntil: validUntil, + } + } + } + refundAccountMetadata := (map[string]interface{})(nil) + if paymentOrder.Metadata != nil { + if m, ok := paymentOrder.Metadata["refundAccountMetadata"].(map[string]interface{}); ok { + refundAccountMetadata = m + } + } + country := "" + if paymentOrder.Metadata != nil { + if c, ok := paymentOrder.Metadata["country"].(string); ok { + country = c + } + } + source = &types.V2FiatSource{ + Type: "fiat", + Currency: currencyCode, + Country: country, + RefundAccount: types.V2FiatRefundAccount{ + Institution: paymentOrder.Institution, + AccountIdentifier: paymentOrder.AccountIdentifier, + AccountName: paymentOrder.AccountName, + Metadata: refundAccountMetadata, + }, + } + destination = &types.V2CryptoDestination{ + Type: "crypto", + Currency: tokenSymbol, + Network: networkID, + ProviderID: providerID, + Recipient: types.V2CryptoRecipient{ + Address: paymentOrder.RefundOrRecipientAddress, + Network: networkID, + }, + } + } else { + if paymentOrder.ReceiveAddress != "" { + providerAccount = &types.V2CryptoProviderAccount{ + Network: networkID, + ReceiveAddress: paymentOrder.ReceiveAddress, + ValidUntil: paymentOrder.ReceiveAddressExpiry, + } + } + source = &types.V2CryptoSource{ + Type: "crypto", + Currency: tokenSymbol, + Network: networkID, + RefundAddress: paymentOrder.RefundOrRecipientAddress, + } + destCountry := "" + if paymentOrder.Metadata != nil { + if c, ok := paymentOrder.Metadata["country"].(string); ok { + destCountry = c + } + } + destination = &types.V2FiatDestination{ + Type: "fiat", + Currency: currencyCode, + Country: destCountry, + ProviderID: providerID, + Recipient: types.V2FiatRecipient{ + Institution: paymentOrder.Institution, + AccountIdentifier: paymentOrder.AccountIdentifier, + AccountName: paymentOrder.AccountName, + Memo: paymentOrder.Memo, + Metadata: paymentOrder.Metadata, + }, + } + } + return source, destination, providerAccount +} + +// BuildV2PaymentOrderGetResponse builds a full V2PaymentOrderGetResponse from payment order and optional provider fields. +func BuildV2PaymentOrderGetResponse( + paymentOrder *ent.PaymentOrder, + institution *ent.Institution, + transactionLogs []types.TransactionLog, + cancellationReasons []string, + otcExpiry *time.Time, +) *types.V2PaymentOrderGetResponse { + source, destination, providerAccount := BuildV2OrderSourceDestinationProviderAccount(paymentOrder, institution) + txFee := paymentOrder.NetworkFee.Add(paymentOrder.ProtocolFee) + senderFeePercentStr := "" + if !paymentOrder.Amount.IsZero() { + senderFeePercentStr = paymentOrder.SenderFee.Div(paymentOrder.Amount).Mul(decimal.NewFromInt(100)).String() + } + resp := &types.V2PaymentOrderGetResponse{ + ID: paymentOrder.ID, + Status: string(paymentOrder.Status), + Direction: string(paymentOrder.Direction), + CreatedAt: paymentOrder.CreatedAt, + UpdatedAt: paymentOrder.UpdatedAt, + Amount: paymentOrder.Amount.String(), + AmountInUsd: paymentOrder.AmountInUsd.String(), + AmountPaid: paymentOrder.AmountPaid.String(), + AmountReturned: paymentOrder.AmountReturned.String(), + PercentSettled: paymentOrder.PercentSettled.String(), + Rate: paymentOrder.Rate.String(), + SenderFee: paymentOrder.SenderFee.String(), + SenderFeePercent: senderFeePercentStr, + TransactionFee: txFee.String(), + Reference: paymentOrder.Reference, + ProviderAccount: providerAccount, + Source: source, + Destination: destination, + TxHash: paymentOrder.TxHash, + TransactionLogs: transactionLogs, + CancellationReasons: cancellationReasons, + OTCExpiry: otcExpiry, + } + if paymentOrder.Metadata != nil { + if amountIn, ok := paymentOrder.Metadata["amountIn"].(string); ok { + resp.AmountIn = amountIn + } + } + if resp.AmountIn == "" { + resp.AmountIn = paymentOrder.Amount.String() + } + return resp +} + // StructToMap converts a struct to a map[string]interface{} func StructToMap(input interface{}) map[string]interface{} { result := make(map[string]interface{}) @@ -765,6 +1006,14 @@ func IsValidHttpsUrl(urlStr string) bool { return parsedUrl.Scheme == "https" && parsedUrl.Host != "" } +// RateSide represents the direction of the rate (buy for onramp, sell for offramp) +type RateSide string + +const ( + RateSideBuy RateSide = "buy" // Onramp: fiat per 1 token the sender pays to buy crypto + RateSideSell RateSide = "sell" // Offramp: fiat per 1 token the sender receives when selling crypto +) + // RateValidationResult contains the result of rate validation type RateValidationResult struct { Rate decimal.Decimal @@ -774,16 +1023,17 @@ type RateValidationResult struct { // ValidateRate validates if a provided rate is achievable for the given parameters // Returns the rate, provider ID (if found), and order type (regular or OTC) -func ValidateRate(ctx context.Context, token *ent.Token, currency *ent.FiatCurrency, amount decimal.Decimal, providerID, networkFilter string) (RateValidationResult, error) { +// side parameter determines whether to use buy rates (onramp) or sell rates (offramp) +func ValidateRate(ctx context.Context, token *ent.Token, currency *ent.FiatCurrency, amount decimal.Decimal, providerID, networkFilter string, side RateSide) (RateValidationResult, error) { isDirectMatch := strings.EqualFold(token.BaseCurrency, currency.Code) // Determine which validation function to use var result RateValidationResult var err error if providerID != "" { - result, err = validateProviderRate(ctx, token, currency, amount, providerID, networkFilter) + result, err = validateProviderRate(ctx, token, currency, amount, providerID, networkFilter, side) } else { - result, err = validateBucketRate(ctx, token, currency, amount, networkFilter) + result, err = validateBucketRate(ctx, token, currency, amount, networkFilter, side) } if err != nil { @@ -801,7 +1051,7 @@ func ValidateRate(ctx context.Context, token *ent.Token, currency *ent.FiatCurre } // validateProviderRate handles provider-specific rate validation -func validateProviderRate(ctx context.Context, token *ent.Token, currency *ent.FiatCurrency, amount decimal.Decimal, providerID, networkFilter string) (RateValidationResult, error) { +func validateProviderRate(ctx context.Context, token *ent.Token, currency *ent.FiatCurrency, amount decimal.Decimal, providerID, networkFilter string, side RateSide) (RateValidationResult, error) { // Get the provider from the database provider, err := storage.Client.ProviderProfile. Query(). @@ -848,19 +1098,31 @@ func validateProviderRate(ctx context.Context, token *ent.Token, currency *ent.F // Get rate first (needed for fiat conversion and OTC validation) var rateResponse decimal.Decimal - redisRate, found := getProviderRateFromRedis(ctx, providerID, token.Symbol, currency.Code, amount, networkFilter) + redisRate, found := getProviderRateFromRedis(ctx, providerID, token.Symbol, currency.Code, amount, networkFilter, side) if found { rateResponse = redisRate } else { // Fallback to database rate if Redis rate not found - if providerOrderToken.ConversionRateType == "fixed" { - rateResponse = providerOrderToken.FixedConversionRate - } else { - // For floating rates, use market rate + floating adjustment - rateResponse = currency.MarketRate.Add(providerOrderToken.FloatingConversionRate) + switch side { + case RateSideBuy: + if !providerOrderToken.FixedBuyRate.IsZero() { + rateResponse = providerOrderToken.FixedBuyRate + } else if !providerOrderToken.FloatingBuyDelta.IsZero() && !currency.MarketBuyRate.IsZero() { + rateResponse = currency.MarketBuyRate.Add(providerOrderToken.FloatingBuyDelta).RoundBank(2) + } + case RateSideSell: + if !providerOrderToken.FixedSellRate.IsZero() { + rateResponse = providerOrderToken.FixedSellRate + } else if !providerOrderToken.FloatingSellDelta.IsZero() && !currency.MarketSellRate.IsZero() { + rateResponse = currency.MarketSellRate.Add(providerOrderToken.FloatingSellDelta).RoundBank(2) + } } } + if rateResponse.IsZero() { + return RateValidationResult{}, fmt.Errorf("provider rate not configured for this token/currency/side (set fixed or floating rate)") + } + // Validate amount limits: if exceeds regular max, check OTC limits // OTC limits are denominated in token amounts (same as regular limits) if amount.GreaterThan(providerOrderToken.MaxOrderAmount) { @@ -880,16 +1142,31 @@ func validateProviderRate(ctx context.Context, token *ent.Token, currency *ent.F // Amount is within regular limits - proceed normally } - _, err = storage.Client.ProviderBalances.Query(). - Where( - providerbalances.HasProviderWith(providerprofile.IDEQ(provider.ID)), - providerbalances.HasFiatCurrencyWith(fiatcurrency.CodeEQ(currency.Code)), - providerbalances.AvailableBalanceGT(amount.Mul(rateResponse)), - providerbalances.IsAvailableEQ(true), - ). - Only(ctx) + // Onramp (buy): check provider has sufficient token balance. Offramp (sell): check fiat balance. + if side == RateSideBuy { + _, err = storage.Client.ProviderBalances.Query(). + Where( + providerbalances.HasProviderWith(providerprofile.IDEQ(provider.ID)), + providerbalances.HasTokenWith(tokenEnt.IDEQ(token.ID)), + providerbalances.AvailableBalanceGT(amount), + providerbalances.IsAvailableEQ(true), + ). + Only(ctx) + } else { + _, err = storage.Client.ProviderBalances.Query(). + Where( + providerbalances.HasProviderWith(providerprofile.IDEQ(provider.ID)), + providerbalances.HasFiatCurrencyWith(fiatcurrency.CodeEQ(currency.Code)), + providerbalances.AvailableBalanceGT(amount.Mul(rateResponse)), + providerbalances.IsAvailableEQ(true), + ). + Only(ctx) + } if err != nil { if ent.IsNotFound(err) { + if side == RateSideBuy { + return RateValidationResult{}, fmt.Errorf("provider has insufficient %s balance", token.Symbol) + } return RateValidationResult{}, fmt.Errorf("provider has insufficient liquidity for %s", currency.Code) } return RateValidationResult{}, fmt.Errorf("internal server error") @@ -903,10 +1180,10 @@ func validateProviderRate(ctx context.Context, token *ent.Token, currency *ent.F } // getProviderRateFromRedis retrieves the provider's current rate from Redis queue. -// If networkFilter is non-empty, only entries where parts[2] (network) matches networkFilter are considered. -func getProviderRateFromRedis(ctx context.Context, providerID, tokenSymbol, currencyCode string, amount decimal.Decimal, networkFilter string) (decimal.Decimal, bool) { - // Get redis keys for provision buckets for this currency - keys, _, err := storage.RedisClient.Scan(ctx, uint64(0), "bucket_"+currencyCode+"_*_*", 100).Result() +func getProviderRateFromRedis(ctx context.Context, providerID, tokenSymbol, currencyCode string, amount decimal.Decimal, networkFilter string, side RateSide) (decimal.Decimal, bool) { + // Get redis keys for provision buckets for this currency and side + // Scan for side-specific bucket keys: bucket_{currency}_{min}_{max}_{side} + keys, _, err := storage.RedisClient.Scan(ctx, uint64(0), "bucket_"+currencyCode+"_*_*_"+string(side), 100).Result() if err != nil { logger.WithFields(logger.Fields{ "Error": fmt.Sprintf("%v", err), @@ -973,9 +1250,10 @@ func getProviderRateFromRedis(ctx context.Context, providerID, tokenSymbol, curr } // validateBucketRate handles bucket-based rate validation -func validateBucketRate(ctx context.Context, token *ent.Token, currency *ent.FiatCurrency, amount decimal.Decimal, networkIdentifier string) (RateValidationResult, error) { - // Get redis keys for provision buckets - keys, _, err := storage.RedisClient.Scan(ctx, uint64(0), "bucket_"+currency.Code+"_*_*", 100).Result() +func validateBucketRate(ctx context.Context, token *ent.Token, currency *ent.FiatCurrency, amount decimal.Decimal, networkIdentifier string, side RateSide) (RateValidationResult, error) { + // Get redis keys for provision buckets for the specific side + // Scan for side-specific bucket keys: bucket_{currency}_{min}_{max}_{side} + keys, _, err := storage.RedisClient.Scan(ctx, uint64(0), "bucket_"+currency.Code+"_*_*_"+string(side), 100).Result() if err != nil { logger.WithFields(logger.Fields{ "Error": fmt.Sprintf("%v", err), @@ -1013,7 +1291,7 @@ func validateBucketRate(ctx context.Context, token *ent.Token, currency *ent.Fia } // Find the first provider at the top of the queue that matches our criteria - result := findSuitableProviderRate(ctx, providers, token.Symbol, networkIdentifier, amount, bucketData) + result := findSuitableProviderRate(ctx, providers, token, networkIdentifier, amount, bucketData, side) if result.Found { foundExactMatch = true bestRate = result.Rate @@ -1062,10 +1340,10 @@ type BucketData struct { } func parseBucketKey(key string) (*BucketData, error) { - // Expected format: "bucket_{currency}_{minAmount}_{maxAmount}" + // Expected format: "bucket_{currency}_{minAmount}_{maxAmount}_{side}" parts := strings.Split(key, "_") if len(parts) != 4 && len(parts) != 5 { - return nil, fmt.Errorf("invalid bucket key format: expected 4 parts, got %d", len(parts)) + return nil, fmt.Errorf("invalid bucket key format: expected 4 or 5 parts, got %d", len(parts)) } if parts[0] != "bucket" { @@ -1091,6 +1369,9 @@ func parseBucketKey(key string) (*BucketData, error) { return nil, fmt.Errorf("min amount (%s) must be less than max amount (%s)", minAmount, maxAmount) } + // If there's a 5th part, it should be "buy" or "sell" (side), but we ignore it for parsing + // as the side is already known from the key pattern + return &BucketData{ Currency: currency, MinAmount: minAmount, @@ -1108,8 +1389,10 @@ type ProviderRateResult struct { // findSuitableProviderRate finds the first suitable provider rate from the provider list // Returns the rate, provider ID, order type, and a boolean indicating if an exact match was found -// An exact match means: amount within limits, within bucket range, and provider has sufficient balance -func findSuitableProviderRate(ctx context.Context, providers []string, tokenSymbol string, networkIdentifier string, tokenAmount decimal.Decimal, bucketData *BucketData) ProviderRateResult { +// An exact match means: amount within limits, within bucket range, and provider has sufficient balance. +// For onramp (RateSideBuy) balance is token; for offramp (RateSideSell) balance is fiat. +func findSuitableProviderRate(ctx context.Context, providers []string, token *ent.Token, networkIdentifier string, tokenAmount decimal.Decimal, bucketData *BucketData, side RateSide) ProviderRateResult { + tokenSymbol := token.Symbol var bestRate decimal.Decimal var foundExactMatch bool @@ -1143,13 +1426,12 @@ func findSuitableProviderRate(ctx context.Context, providers []string, tokenSymb var providerOrderToken *ent.ProviderOrderToken if networkIdentifier != "" { // Network filter provided - fetch with network constraint - pot, err := storage.Client.ProviderOrderToken. + potQuery := storage.Client.ProviderOrderToken. Query(). Where( providerordertoken.HasProviderWith( providerprofile.IDEQ(parts[0]), providerprofile.HasProviderBalancesWith( - providerbalances.HasFiatCurrencyWith(fiatcurrency.CodeEQ(bucketData.Currency)), providerbalances.IsAvailableEQ(true), ), ), @@ -1157,7 +1439,27 @@ func findSuitableProviderRate(ctx context.Context, providers []string, tokenSymb providerordertoken.HasCurrencyWith(fiatcurrency.CodeEQ(bucketData.Currency)), providerordertoken.NetworkEQ(networkIdentifier), providerordertoken.SettlementAddressNEQ(""), - ).Only(ctx) + ) + if side == RateSideBuy { + potQuery = potQuery.Where( + providerordertoken.HasProviderWith( + providerprofile.HasProviderBalancesWith( + providerbalances.HasTokenWith(tokenEnt.IDEQ(token.ID)), + providerbalances.IsAvailableEQ(true), + ), + ), + ) + } else { + potQuery = potQuery.Where( + providerordertoken.HasProviderWith( + providerprofile.HasProviderBalancesWith( + providerbalances.HasFiatCurrencyWith(fiatcurrency.CodeEQ(bucketData.Currency)), + providerbalances.IsAvailableEQ(true), + ), + ), + ) + } + pot, err := potQuery.Only(ctx) if err != nil { if ent.IsNotFound(err) { continue @@ -1171,20 +1473,39 @@ func findSuitableProviderRate(ctx context.Context, providers []string, tokenSymb providerOrderToken = pot } else { // No network filter - fetch first matching entry - pot, err := storage.Client.ProviderOrderToken. + potQuery := storage.Client.ProviderOrderToken. Query(). Where( providerordertoken.HasProviderWith( providerprofile.IDEQ(parts[0]), providerprofile.HasProviderBalancesWith( - providerbalances.HasFiatCurrencyWith(fiatcurrency.CodeEQ(bucketData.Currency)), providerbalances.IsAvailableEQ(true), ), ), providerordertoken.HasTokenWith(tokenEnt.SymbolEQ(parts[1])), providerordertoken.HasCurrencyWith(fiatcurrency.CodeEQ(bucketData.Currency)), providerordertoken.SettlementAddressNEQ(""), - ).First(ctx) + ) + if side == RateSideBuy { + potQuery = potQuery.Where( + providerordertoken.HasProviderWith( + providerprofile.HasProviderBalancesWith( + providerbalances.HasTokenWith(tokenEnt.IDEQ(token.ID)), + providerbalances.IsAvailableEQ(true), + ), + ), + ) + } else { + potQuery = potQuery.Where( + providerordertoken.HasProviderWith( + providerprofile.HasProviderBalancesWith( + providerbalances.HasFiatCurrencyWith(fiatcurrency.CodeEQ(bucketData.Currency)), + providerbalances.IsAvailableEQ(true), + ), + ), + ) + } + pot, err := potQuery.First(ctx) if err != nil { if ent.IsNotFound(err) { continue @@ -1263,17 +1584,33 @@ func findSuitableProviderRate(ctx context.Context, providers []string, tokenSymb // Check if fiat amount is within the bucket range if fiatAmount.GreaterThanOrEqual(bucketData.MinAmount) && fiatAmount.LessThanOrEqual(bucketData.MaxAmount) { - _, err := storage.Client.ProviderBalances.Query(). - Where( - providerbalances.HasProviderWith(providerprofile.IDEQ(providerID)), - providerbalances.HasFiatCurrencyWith(fiatcurrency.CodeEQ(bucketData.Currency)), - providerbalances.AvailableBalanceGT(fiatAmount), - providerbalances.IsAvailableEQ(true), - ). - Only(ctx) + // Onramp: check token balance; offramp: check fiat balance + if side == RateSideBuy { + _, err = storage.Client.ProviderBalances.Query(). + Where( + providerbalances.HasProviderWith(providerprofile.IDEQ(providerID)), + providerbalances.HasTokenWith(tokenEnt.IDEQ(token.ID)), + providerbalances.AvailableBalanceGT(tokenAmount), + providerbalances.IsAvailableEQ(true), + ). + Only(ctx) + } else { + _, err = storage.Client.ProviderBalances.Query(). + Where( + providerbalances.HasProviderWith(providerprofile.IDEQ(providerID)), + providerbalances.HasFiatCurrencyWith(fiatcurrency.CodeEQ(bucketData.Currency)), + providerbalances.AvailableBalanceGT(fiatAmount), + providerbalances.IsAvailableEQ(true), + ). + Only(ctx) + } if err != nil { if ent.IsNotFound(err) { - skipReasons = append(skipReasons, fmt.Sprintf("provider %s has insufficient balance (need %s %s)", providerID, fiatAmount, bucketData.Currency)) + if side == RateSideBuy { + skipReasons = append(skipReasons, fmt.Sprintf("provider %s has insufficient %s balance (need %s)", providerID, tokenSymbol, tokenAmount)) + } else { + skipReasons = append(skipReasons, fmt.Sprintf("provider %s has insufficient balance (need %s %s)", providerID, fiatAmount, bucketData.Currency)) + } continue } return ProviderRateResult{Found: false}