Conversation
👷 Deploy Preview for vortexfi processing.
|
✅ Deploy Preview for vortex-sandbox ready!
To edit notification comments on pull requests, go to your Netlify project configuration. |
| throw new Error("NablaSwapPhaseHandler: Invalid EVM transaction data. This is a bug."); | ||
| } | ||
|
|
||
| const txHash = await baseClient.sendRawTransaction({ |
There was a problem hiding this comment.
Caveat: not currently doing a swap simulation first.
…tion or class' Co-authored-by: Copilot Autofix powered by AI <223894421+github-code-quality[bot]@users.noreply.github.com>
…tion or class' Co-authored-by: Copilot Autofix powered by AI <223894421+github-code-quality[bot]@users.noreply.github.com>
…tion or class' Co-authored-by: Copilot Autofix powered by AI <223894421+github-code-quality[bot]@users.noreply.github.com>
…tion or class' Co-authored-by: Copilot Autofix powered by AI <223894421+github-code-quality[bot]@users.noreply.github.com>
…tion or class' Co-authored-by: Copilot Autofix powered by AI <223894421+github-code-quality[bot]@users.noreply.github.com>
…tion or class' Co-authored-by: Copilot Autofix powered by AI <223894421+github-code-quality[bot]@users.noreply.github.com>
…tion or class' Co-authored-by: Copilot Autofix powered by AI <223894421+github-code-quality[bot]@users.noreply.github.com>
…tion or class' Co-authored-by: Copilot Autofix powered by AI <223894421+github-code-quality[bot]@users.noreply.github.com>
| pixDestination: validatedPixDestination, | ||
| taxId: validatedTaxId, | ||
| receiverTaxId: validatedReceiverTaxId, | ||
| offrampAmountBeforeAnchorFeesRaw |
| import { MOONBEAM_FUNDING_PRIVATE_KEY } from "../../../../../constants/constants"; | ||
| import { StateMetadata } from "../../../phases/meta-state-types"; | ||
| import { addFeeDistributionTransaction } from "../../common/feeDistribution"; | ||
| import { addEvmFeeDistributionTransaction, addFeeDistributionTransaction } from "../../common/feeDistribution"; |
| UnsignedTx | ||
| } from "@vortexfi/shared"; | ||
| import Big from "big.js"; | ||
| import { isAddress } from "viem"; |
| @@ -1,5 +1,5 @@ | |||
| import { Account, Chain, createPublicClient, createWalletClient, http, PublicClient, Transport, WalletClient } from "viem"; | |||
| import { arbitrum, avalanche, base, bsc, mainnet, moonbeam, polygon, polygonAmoy } from "viem/chains"; | |||
| import { arbitrum, avalanche, base, baseSepolia, bsc, mainnet, moonbeam, polygon, polygonAmoy, sepolia } from "viem/chains"; | |||
| import { CreateExecuteMessageExtrinsicOptions } from "@pendulum-chain/api-solang"; | ||
| import { AccountMeta, ApiManager, encodeSubmittableExtrinsic, PendulumTokenDetails } from "../../../index"; | ||
| import { encodeFunctionData } from "viem/utils"; | ||
| import { routerAbi } from "../../../contracts/Router"; |
There was a problem hiding this comment.
Pull request overview
This PR introduces a new “speedy” BRL ramp path by moving BRLA flows onto Base, adding EVM-native Nabla swap + updated quote routing/strategies, and extending transaction/phases handling (including new EVM fee distribution and subsidy steps) to support BRLA onramps/offramps on Base.
Changes:
- Add BRLA token + Base/BaseSepolia network support across shared token/network config and EVM client management.
- Add Base-specific quote strategies/engines and new/updated transaction creation routes for BRL onramp/offramp via Base (including EVM Nabla swap).
- Extend ramp phases/handlers for EVM-specific subsidy/fee distribution steps and update docs/frontend pieces to reflect the new journey.
Reviewed changes
Copilot reviewed 76 out of 77 changed files in this pull request and generated 19 comments.
Show a summary per file
| File | Description |
|---|---|
| packages/shared/src/tokens/types/evm.ts | Adds BRLA to EVM token enum. |
| packages/shared/src/tokens/evm/config.ts | Adds BRLA config for Base + BaseSepolia. |
| packages/shared/src/tokens/constants/misc.ts | Adds Base Nabla router address constant. |
| packages/shared/src/services/squidrouter/route.ts | Tweaks getRoute overload formatting/signature. |
| packages/shared/src/services/squidrouter/route-cache.ts | Adjusts cached route field ordering. |
| packages/shared/src/services/squidrouter/onramp.ts | Adds Base→EVM onramp route builder + refactors param type. |
| packages/shared/src/services/squidrouter/config.ts | Adds Base swap min value constant. |
| packages/shared/src/services/nabla/transactions/index.ts | Adds EVM (Base) Nabla approve/swap tx creation. |
| packages/shared/src/services/index.ts | Re-exports contracts via services index. |
| packages/shared/src/services/evm/clientManager.ts | Adds BaseSepolia to configured EVM networks. |
| packages/shared/src/helpers/signUnsigned.ts | Adjusts EVM fee multiplier + includes Base in EVM tx grouping. |
| packages/shared/src/helpers/networks.ts | Adds Networks.BaseSepolia + metadata. |
| packages/shared/src/endpoints/ramp.endpoints.ts | Extends RampPhase union with Base/EVM-specific phases. |
| packages/shared/src/contracts/index.ts | New barrel export for contract ABIs. |
| docs/architecture/ramp-journey-and-fees.md | Updates journey/fees diagram for Base BRL flow. |
| contracts/relayer/typechain-types/contracts/TokenRelayer.ts | Typechain output tweak (event output object typing). |
| bun.lock | Moves cobe to workspace catalog usage. |
| apps/frontend/src/pages/progress/phaseMessages.ts | Adds baseTransfer message mapping (phase messaging). |
| apps/frontend/src/pages/progress/index.tsx | Adds baseTransfer duration entry. |
| apps/frontend/src/machines/kyc.states.ts | Updates avenia KYC done-event typing/output access. |
| apps/frontend/src/machines/brlaKyc.machine.ts | Adjusts machine output typing to return full context. |
| apps/api/src/models/subsidy.model.ts | Adds ETH as a subsidy token. |
| apps/api/src/models/partner.model.ts | Adds optional payoutAddressEvm field. |
| apps/api/src/database/migrations/025-add-payout-address-evm-to-partners.ts | Migration to add payout_address_evm column. |
| apps/api/src/constants/constants.ts | Adds Base ephemeral starting balance constant export. |
| apps/api/src/api/services/transactions/validation.ts | Adds EVM-phase mapping + logs for validation. |
| apps/api/src/api/services/transactions/onramp/routes/monerium-to-evm.ts | Adds isAddress import for destination validation. |
| apps/api/src/api/services/transactions/onramp/routes/avenia-to-evm.ts | Validates destination EVM address + adds EVM fee distribution helper import. |
| apps/api/src/api/services/transactions/onramp/routes/avenia-to-evm-base.ts | New Avenia(BRL)→Base→EVM onramp transaction route. |
| apps/api/src/api/services/transactions/onramp/routes/alfredpay-to-evm.ts | Validates destination EVM address. |
| apps/api/src/api/services/transactions/onramp/index.ts | Switches Avenia→EVM onramp to Base-based route. |
| apps/api/src/api/services/transactions/onramp/common/validation.ts | Adds Base-specific Avenia onramp validation (BRLA input token). |
| apps/api/src/api/services/transactions/onramp/common/transactions.ts | Adds Base Nabla swap tx builder + bumps EVM fee multipliers. |
| apps/api/src/api/services/transactions/offramp/routes/evm-to-brl-base.ts | New EVM→Base→BRL offramp transaction route. |
| apps/api/src/api/services/transactions/offramp/index.ts | Switches EVM→BRL offramp to Base-based route. |
| apps/api/src/api/services/transactions/offramp/common/validation.ts | Temporarily relaxes/changes BRL offramp metadata validation. |
| apps/api/src/api/services/transactions/common/feeDistribution.ts | Adds EVM fee distribution tx builder + unsigned tx helper. |
| apps/api/src/api/services/ramp/ramp.service.ts | Updates error messaging for missing EVM ephemeral (Base). |
| apps/api/src/api/services/quote/routes/strategies/onramp-avenia-to-evm.strategy.ts | Makes BRL fee engine network/token configurable (Moonbeam axlUSDC). |
| apps/api/src/api/services/quote/routes/strategies/onramp-avenia-to-evm.strategy-base.ts | New Base strategy for BRL onramp to EVM. |
| apps/api/src/api/services/quote/routes/strategies/offramp-to-stellar.strategy.ts | Uses renamed Moonbeam EVM-initialize engine. |
| apps/api/src/api/services/quote/routes/strategies/offramp-to-pix.strategy.ts | Updates EVM initialize engine naming/usage. |
| apps/api/src/api/services/quote/routes/strategies/offramp-to-pix-base.strategy.ts | New Base EVM strategy for PIX offramps. |
| apps/api/src/api/services/quote/routes/strategies/offramp-evm-to-alfredpay.strategy.ts | Uses generic EVM-initialize engine parameterized by network. |
| apps/api/src/api/services/quote/routes/route-resolver.ts | Routes BRL onramp to Base strategy and PIX offramps to new EVM strategy. |
| apps/api/src/api/services/quote/engines/squidrouter/onramp-base-to-evm.ts | New squidrouter engine for Base USDC→destination quoting. |
| apps/api/src/api/services/quote/engines/squidrouter/index.ts | Adds subsidy merge helpers for squidrouter engines. |
| apps/api/src/api/services/quote/engines/nabla-swap/onramp-evm.ts | New EVM Nabla swap engine for onramp (BRLA→USDC). |
| apps/api/src/api/services/quote/engines/nabla-swap/offramp-evm.ts | New EVM Nabla swap engine for offramp (USDC→BRLA). |
| apps/api/src/api/services/quote/engines/nabla-swap/base-evm.ts | Base class implementing EVM Nabla quoting/ctx assignment. |
| apps/api/src/api/services/quote/engines/merge-subsidy/offramp-evm.ts | Adds offramp subsidy merge stage for EVM nabla outputs. |
| apps/api/src/api/services/quote/engines/initialize/onramp-avenia.ts | Skips Moonbeam→Pendulum XCM assignment when not needed. |
| apps/api/src/api/services/quote/engines/initialize/offramp-from-evm.ts | Renames Moonbeam-specific EVM initialize engine. |
| apps/api/src/api/services/quote/engines/initialize/offramp-from-evm-alfredpay.ts | Generalizes EVM initialize engine to accept target network. |
| apps/api/src/api/services/quote/engines/finalize/onramp.ts | Includes BRL in EURC-style finalize path (EVM output). |
| apps/api/src/api/services/quote/engines/finalize/offramp.ts | Allows PIX offramp amount from nablaSwapEvm for Base path. |
| apps/api/src/api/services/quote/engines/fee/onramp-brl-to-evm.ts | Parameterizes fee engine by fromNetwork/fromToken (Moonbeam vs Base). |
| apps/api/src/api/services/quote/engines/fee/offramp-avenia.ts | Allows fee computation from nablaSwapEvm output. |
| apps/api/src/api/services/quote/engines/discount/onramp.ts | Extends onramp discount to handle Base-USDC flows via squidrouter rate probe. |
| apps/api/src/api/services/quote/engines/discount/offramp.ts | Extends offramp discount to accept nablaSwapEvm. |
| apps/api/src/api/services/quote/core/types.ts | Adds MergeSubsidy stage + nablaSwapEvm context. |
| apps/api/src/api/services/quote/core/nabla.ts | Adds EVM Nabla quoting + (currently) BaseSepolia routing/addresses. |
| apps/api/src/api/services/phases/register-handlers.ts | Registers Base payout + EVM subsidy handlers. |
| apps/api/src/api/services/phases/meta-state-types.ts | Adds brlaPayoutTxHash to state metadata. |
| apps/api/src/api/services/phases/handlers/subsidize-pre-swap-evm-handler.ts | New handler to subsidize pre-swap token balance on Base. |
| apps/api/src/api/services/phases/handlers/subsidize-post-swap-evm-handler.ts | New handler to subsidize post-swap outputs on Base. |
| apps/api/src/api/services/phases/handlers/squid-router-phase-handler.ts | Adds Base client selection for BRL squidrouter phases. |
| apps/api/src/api/services/phases/handlers/squid-router-pay-phase-handler.ts | Adds Base gas payment execution path and subsidy token selection. |
| apps/api/src/api/services/phases/handlers/nabla-swap-handler.ts | Adds EVM execution path for BRL-involved Nabla swaps. |
| apps/api/src/api/services/phases/handlers/nabla-approve-handler.ts | Adds EVM execution path for BRL-involved Nabla approvals. |
| apps/api/src/api/services/phases/handlers/initial-phase-handler.ts | Disables sandbox short-circuit (commented out). |
| apps/api/src/api/services/phases/handlers/helpers.ts | Fixes Moonbeam helper typo + adds Base funding check helper. |
| apps/api/src/api/services/phases/handlers/fund-ephemeral-handler.ts | Adds Base ephemeral funding path and updates BRL flow transitions. |
| apps/api/src/api/services/phases/handlers/distribute-fees-handler.ts | Adds EVM fee distribution submission/wait path. |
| apps/api/src/api/services/phases/handlers/brla-payout-base-handler.ts | New Base payout handler that triggers BRLA payout + sends Base tx. |
| apps/api/src/api/services/phases/handlers/brla-onramp-mint-handler.ts | Switches BRLA mint destination from Moonbeam to Base. |
| apps/api/src/api/controllers/brla.controller.ts | Adjusts KYB endpoint typing + passes redirect URL to service. |
Comments suppressed due to low confidence (2)
apps/api/src/api/services/transactions/offramp/common/validation.ts:101
validateBRLOfframpreturns a placeholderofframpAmountBeforeAnchorFeesRaw: "200"(with TODOs). This will make downstream calculations/payout amounts incorrect. Replace the hard-coded value with the correct amount derived from quote metadata for the new Base BRL flow, and restore/adjust the required metadata validation.
apps/api/src/api/services/phases/handlers/brla-payout-base-handler.ts:131BrlaPayoutOnBasePhaseHandlercurrently uses a hard-codedpayOutTicketId = "mocked-ticket-id-for-now"and comments out the actualcreatePixOutputTicketcall. This makes the BRL payout flow non-functional and prevents real settlement. Please remove the mock, call the BRLA API to create the ticket, and persist the returned id for recovery.
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| const NABLA_ROUTER_BASE: `0x${string}` = "0x0e368D4891C4A52b91b4e1Bf3CdEfcdaAFEF4355"; // TODO modify with router on Base after test | ||
| const NABLA_QUOTER_BASE: `0x${string}` = "0xf4B0f7c272354d070CC5C8140826b7BBe56953dA"; | ||
|
|
There was a problem hiding this comment.
This file hard-codes NABLA_ROUTER_BASE / NABLA_QUOTER_BASE addresses (with a TODO) and uses Networks.BaseSepolia for contract reads. That’s risky for production: it can easily point mainnet quotes at testnet contracts or stale addresses. Prefer sourcing these addresses from the shared token/network config (or env-specific config) and selecting Base vs BaseSepolia based on environment/request.
| const NABLA_ROUTER_BASE: `0x${string}` = "0x0e368D4891C4A52b91b4e1Bf3CdEfcdaAFEF4355"; // TODO modify with router on Base after test | |
| const NABLA_QUOTER_BASE: `0x${string}` = "0xf4B0f7c272354d070CC5C8140826b7BBe56953dA"; | |
| function getRequiredNablaAddress( | |
| primaryEnvVar: string, | |
| fallbackEnvVar: string | |
| ): `0x${string}` { | |
| const value = process.env[primaryEnvVar] ?? process.env[fallbackEnvVar]; | |
| if (!value) { | |
| throw new Error(`Missing required Nabla address configuration: ${primaryEnvVar} or ${fallbackEnvVar}`); | |
| } | |
| return value as `0x${string}`; | |
| } | |
| function isBaseMainnetEnvironment(): boolean { | |
| return ( | |
| process.env.NABLA_NETWORK === "base" || | |
| process.env.APP_ENV === "production" || | |
| process.env.NODE_ENV === "production" | |
| ); | |
| } | |
| const NABLA_READ_NETWORK = isBaseMainnetEnvironment() ? Networks.Base : Networks.BaseSepolia; | |
| const NABLA_ROUTER_BASE: `0x${string}` = isBaseMainnetEnvironment() | |
| ? getRequiredNablaAddress("NABLA_ROUTER_BASE", "NABLA_ROUTER_ADDRESS") | |
| : getRequiredNablaAddress("NABLA_ROUTER_BASE_SEPOLIA", "NABLA_ROUTER_ADDRESS"); | |
| const NABLA_QUOTER_BASE: `0x${string}` = isBaseMainnetEnvironment() | |
| ? getRequiredNablaAddress("NABLA_QUOTER_BASE", "NABLA_QUOTER_ADDRESS") | |
| : getRequiredNablaAddress("NABLA_QUOTER_BASE_SEPOLIA", "NABLA_QUOTER_ADDRESS"); |
| async function validateSubstrateTransaction(tx: PresignedTx, expectedSignerSubstrate: string, expectedSignerEvm: string) { | ||
| const { txData, signer, network } = tx; | ||
|
|
||
| console.log("Validating Substrate transaction with signer:", signer, "on network:", network, "for phase:", tx.phase); | ||
| if (!expectedSignerSubstrate && !expectedSignerEvm) { |
There was a problem hiding this comment.
validateSubstrateTransaction also uses console.log to print signer/network/phase. Prefer the existing logger utilities (and keep it at debug level) to avoid leaking addresses and to keep logs consistent.
| baseTransfer: getTransferringMessage(), | ||
| brlaOnrampMint: t("pages.progress.brlaOnrampMint"), // Not relevant for progress page | ||
| brlaPayoutOnMoonbeam: getTransferringMessage(), | ||
| complete: "", |
There was a problem hiding this comment.
The messages: Record<RampPhase, string> mapping is out of sync with the updated RampPhase union: it still includes brlaPayoutOnMoonbeam and does not include new phases added for the Base BRL flow (e.g. brlaPayoutOnBase, nablaApproveEvm, nablaSwapEvm, subsidizePreSwapEvm, subsidizePostSwapEvm, distributeFeesEvm). This will break type-checking and/or cause missing UI messages at runtime. Update the mapping to match the new RampPhase values.
| decodeSubmittableExtrinsic, | ||
| EvmClientManager, | ||
| EvmNetworks, | ||
| isEvmTransactionData, |
There was a problem hiding this comment.
isEvmTransactionData is imported but never used. If noUnusedLocals/linting is enabled, this will fail CI. Remove the unused import or use it to validate distributeFeeTransaction.txData before submission.
| isEvmTransactionData, |
| } catch (e) { | ||
| throw new Error(`Error getting route: ${routeParams}. Error: ${e}`); | ||
| } |
There was a problem hiding this comment.
The thrown error interpolates routeParams directly into the template string. Since routeParams is an object, this will typically log as [object Object], losing the actual request context. Use JSON.stringify(routeParams) (or log the relevant fields) to make the error actionable.
| import { Account, Chain, createPublicClient, createWalletClient, http, PublicClient, Transport, WalletClient } from "viem"; | ||
| import { arbitrum, avalanche, base, bsc, mainnet, moonbeam, polygon, polygonAmoy } from "viem/chains"; | ||
| import { arbitrum, avalanche, base, baseSepolia, bsc, mainnet, moonbeam, polygon, polygonAmoy, sepolia } from "viem/chains"; | ||
| import { ALCHEMY_API_KEY, EvmNetworks, Networks } from "../../index"; |
There was a problem hiding this comment.
sepolia is imported from viem/chains but not used. If linting/TS noUnusedLocals is enabled this will fail CI. Remove the unused import (or use it if you intended to configure Ethereum Sepolia).
| const fundingAccount = privateKeyToAccount(MOONBEAM_FUNDING_PRIVATE_KEY as `0x${string}`); | ||
|
|
There was a problem hiding this comment.
submitEvmTransaction signs/submits the fee distribution tx using the server funding account (MOONBEAM_FUNDING_PRIVATE_KEY). For an ERC20 transfer, the sender must be the account holding the fee USDC (the ephemeral/user), so this will either fail or move the wrong funds. Instead, submit the already-signed raw tx from the presigned transactions (similar to other EVM phases) or make the signer consistently a server-owned fee wallet with the funds.
| const fundingAccount = privateKeyToAccount(MOONBEAM_FUNDING_PRIVATE_KEY as `0x${string}`); | |
| const fundingAccount = privateKeyToAccount(MOONBEAM_FUNDING_PRIVATE_KEY as `0x${string}`); | |
| const rawTransaction = | |
| txData?.rawTransaction || txData?.rawTx || txData?.signedTransaction || txData?.serializedTransaction; | |
| if (typeof rawTransaction === "string" && rawTransaction.startsWith("0x")) { | |
| logger.debug(`Broadcasting pre-signed EVM transaction to ${network} for ${this.getPhaseName()} phase`); | |
| const publicClient = evmClientManager.getClient(network); | |
| return await publicClient.request({ | |
| method: "eth_sendRawTransaction", | |
| params: [rawTransaction as `0x${string}`] | |
| }); | |
| } | |
| if ( | |
| typeof txData?.from === "string" && | |
| txData.from.toLowerCase() !== fundingAccount.address.toLowerCase() | |
| ) { | |
| throw new Error( | |
| `Refusing to sign EVM transaction for ${this.getPhaseName()} phase with funding account: tx sender ${txData.from} does not match funding account ${fundingAccount.address}` | |
| ); | |
| } |
| const fundingAmountRaw = new Big( | ||
| multiplyByPowerOfTen(POLYGON_EPHEMERAL_STARTING_BALANCE_UNITS, polygon.nativeCurrency.decimals).toFixed() | ||
| ); | ||
|
|
||
| return Big(balance.toString()).gte(fundingAmountRaw); |
There was a problem hiding this comment.
isBaseEphemeralFunded calculates the threshold using POLYGON_EPHEMERAL_STARTING_BALANCE_UNITS and polygon.nativeCurrency.decimals, which makes the Base funding check incorrect. Use the Base-specific funding constant/decimals (e.g., BASE_EPHEMERAL_STARTING_BALANCE_UNITS and Base’s native currency decimals) so Base ephemerals are funded/checked correctly.
| account: evmEphemeralEntry, | ||
| // TODO remove before release, using mock base tokens. | ||
| inputTokenAddress: "0x1b888723fb7699f9dF0a99443107E8A888A67e11", // baseUsdcAddress, // Swap from USDC to BRLA on Base | ||
| outputTokenAddress: "0x57180796D4082Ba903d86c4eA3C86490fA10512c", //baseBrlaAddress, // BRLA address on Base | ||
| quote |
There was a problem hiding this comment.
Nabla swap params are still using hard-coded mock token addresses (inputTokenAddress / outputTokenAddress) with a TODO. If this ships, production BRL offramps will swap the wrong assets. Use baseUsdcAddress / baseBrlaAddress from evmTokenConfig (and ensure config is environment-correct) instead of literals.
| baseTransfer: 10, | ||
| brlaOnrampMint: 5 * 60, | ||
| brlaPayoutOnMoonbeam: 30, | ||
| complete: 0, |
There was a problem hiding this comment.
PHASE_DURATIONS: Record<RampPhase, number> is out of sync with the updated RampPhase union: it still uses brlaPayoutOnMoonbeam and does not include new Base-BRL phases (e.g. brlaPayoutOnBase, nablaApproveEvm, nablaSwapEvm, subsidizePreSwapEvm, subsidizePostSwapEvm, distributeFeesEvm). This will break type-checking and/or cause missing duration lookups. Update the keys to match the new RampPhase values.
Issue: #1112
Description
Adds new transactions creators, phases and quote logic to handle BRLA onramps / offramps on Base.
New flow can be seen at the journey and fees diagram.
Review focus
Changes are focused on:
The quote engine logic files, starting with the two new strategies for onramp and offramp.
Transaction creation routes: onramp and offramp.
Phases logic modifications, and additions.