Make EVM destination funding fee-aware - #1312
Conversation
✅ Deploy Preview for vortex-sandbox ready!
To edit notification comments on pull requests, go to your Netlify project configuration. |
There was a problem hiding this comment.
🟡 Changes recommended
Same-chain reserve overlap, uncapped direct-flow funding, and incomplete Base Sepolia signing remain unresolved.
Once you've addressed the issues Copilot identified, you can request another Copilot review.
This review doesn't count toward merge requirements. Sign up for the private preview to control whether Copilot approvals count.
Pull request overview
Makes EVM destination funding fee-aware across quoting, registration, signing, and execution.
Changes:
- Prices destination gas and persists a registration-time fee envelope.
- Funds only the signed payout liability shortfall.
- Adds configuration, documentation, translations, and test coverage.
File summaries
| File | Description |
|---|---|
packages/shared/src/helpers/signUnsigned.ts |
Centralizes the signing fee multiplier. |
packages/shared/src/endpoints/quote.endpoints.ts |
Adds the high-network-fee error. |
docs/security-spec/03-ramp-engine/ephemeral-accounts.md |
Documents dynamic destination funding. |
apps/frontend/src/translations/pt.json |
Adds Portuguese error text. |
apps/frontend/src/translations/en.json |
Adds English error text. |
apps/frontend/src/stores/quote/useQuoteStore.ts |
Maps the new friendly error. |
apps/api/src/tests/quote-pricing.golden.test.ts |
Updates fee-pricing goldens. |
apps/api/src/tests/corridors/brl-onramp-crosschain.scenario.test.ts |
Expands destination pricing scenarios. |
apps/api/src/test-utils/fake-world/fake-prices.ts |
Adds native-token test prices. |
apps/api/src/constants/constants.ts |
Separates Moonbeam source reserves. |
apps/api/src/config/vars.ts |
Adds destination gas configuration. |
apps/api/src/config/vars.test.ts |
Tests configuration validation. |
apps/api/src/api/services/phases/blocks/phases/subsidize-pre/simulation.ts |
Deducts destination network fees. |
apps/api/src/api/services/phases/blocks/phases/fund-ephemeral/execution.ts |
Implements dynamic shortfall funding. |
apps/api/src/api/services/phases/blocks/phases/final-settlement-subsidy/execution.ts |
Uses signed gas liability for native settlement. |
apps/api/src/api/services/phases/blocks/phases/destination-transfer/transactions.ts |
Validates prepared fees against quotes. |
apps/api/src/api/services/phases/blocks/core/types.ts |
Extends phase gas context. |
apps/api/src/api/services/phases/blocks/core/metadata.ts |
Defines persisted fee envelopes. |
apps/api/src/api/services/phases/blocks/core/flow.ts |
Controls and persists gas pricing. |
apps/api/src/api/services/phases/blocks/core/fees.ts |
Includes destination execution costs. |
apps/api/src/api/services/phases/blocks/core/evm-transactions.ts |
Defers fee multiplication to signing. |
apps/api/src/api/services/phases/blocks/core/evm-destination-gas.ts |
Implements destination gas policy. |
apps/api/src/api/services/phases/blocks/core/evm-destination-gas.test.ts |
Tests gas pricing and envelopes. |
apps/api/src/api/services/phases/blocks/core/destination-funding.ts |
Replaces static funding amounts. |
apps/api/src/api/services/phases/blocks/core/destination-funding.test.ts |
Tests liability and shortfalls. |
apps/api/src/api/services/phases/blocks/__tests__/fund-ephemeral-user-hashes.test.ts |
Tests non-Ethereum funding liability. |
apps/api/src/api/services/phases/blocks/__tests__/brl-onramp-base-same-chain.flow.test.ts |
Updates same-chain fixtures. |
apps/api/src/api/services/phases/blocks/__tests__/brl-onramp-base-cross-chain.transactions.test.ts |
Verifies single fee multiplication. |
apps/api/src/api/services/phases/blocks/__tests__/brl-onramp-base-cross-chain.flow.test.ts |
Updates cross-chain fixtures. |
apps/api/src/api/services/phases/blocks/__tests__/alfredpay-onramp-direct.flow.test.ts |
Updates direct-flow fixtures. |
apps/api/src/api/services/phases/blocks/__tests__/alfredpay-onramp-cross-chain.flow.test.ts |
Updates cross-chain fixtures. |
apps/api/.env.example |
Documents gas-policy settings. |
Review details
- Files reviewed: 32/32 changed files
- Comments generated: 3
- Review effort level: Balanced
We're testing this review assessment. Please use 👍 or 👎 to tell us if it's correct.
✅ Deploy Preview for vortexfi ready!
To edit notification comments on pull requests, go to your Netlify project configuration. |
✅ Deploy Preview for vrtx-dashboard canceled.
|
…destination-funding
…destination-funding
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 54 out of 54 changed files in this pull request and generated 2 comments.
Suppressed comments (1)
apps/api/src/api/services/phases/blocks/phases/fund-ephemeral/execution.ts:397
- This preflight also executes before the destination v2 operation can replay a confirmed response. After a confirmed send followed by a balance-poll timeout, a still-lagging balance plus newly elevated gas/Base L1 fees pauses the ramp here even though no transaction needs to be sent again. Check the persisted operation first and only enforce the live envelope for a genuinely new broadcast.
const fees = await destinationClient.estimateFeesPerGas();
await assertEvmTreasuryFundingFeeWithinQuote(destinationGasQuote, destinationNetwork, fees.maxFeePerGas);
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 56 out of 56 changed files in this pull request and generated 1 comment.
Suppressed comments (1)
apps/api/src/config/vars.ts:114
Numberaccepts non-decimal JavaScript forms such as0x10, so startup accepts that ceiling, butnew Big(config.evmDestinationGas.maxExecutionFeeUsd)later rejects it and all dynamic quotes fail at runtime. Validate the stored string as decimal syntax here so invalid configuration fails during startup.
function readPositiveDecimalEnv(name: string, defaultValue: string): string {
const rawValue = process.env[name] ?? defaultValue;
const value = Number(rawValue.trim());
if (!Number.isFinite(value) || value <= 0 || rawValue.trim() === "") {
throw new Error(`${name} must be a positive number`);
}
return rawValue.trim();
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 56 out of 56 changed files in this pull request and generated no new comments.
Suppressed comments (1)
apps/api/src/api/services/ramp/ramp.service.ts:944
- The preflight trusts the network embedded in persisted JSON without binding it to the quote destination. If this envelope is malformed or manually edited to name another EVM chain, this check can pass on that chain,
flow.registercan create its durable provider ticket, and only the later destination-transfer preparation rejects the mismatch. Fail closed on the network mismatch here so no provider side effect occurs first.
if (metadata.globals.evmDestinationGas) {
// Run the same persisted-envelope guard before provider registration can
// create an independently durable ticket. prepareTxs keeps its exact check.
await preflightEvmDestinationFeeWithinQuote(metadata.globals.evmDestinationGas);
Summary
networkFeeUsd, converted from each chain's native token and protected by a configurable USD ceilingNetworkFeesTooHigh503 through quote, registration, and durable ramp error pathsScope
The policy covers all configured EVM destinations: Ethereum, Arbitrum, Base, BSC, Avalanche, Moonbeam, Polygon, Base Sepolia, and Polygon Amoy. ETH-, POL/MATIC-, BNB-, AVAX-, and GLMR-denominated gas is converted to USD through the existing price feed.
Source-chain reserve constants remain static and continue to cover the upstream phase plan. For cross-chain payouts, the signed destination liability is funded separately. When source and destination are the same EVM network, the signed payout liability is added to the fixed source reserve so upstream phases cannot consume it. Exact provider-token direct payouts remain source-reserved because those topologies have no fee-distribution phase.
Funding and fee policy
Funding-program v2 persists:
maximumFeePerGasArbitrum gas limits include the NodeInterface L1 poster-gas component plus the configured quote margin. Base-family pricing persists conservative
getL1FeeUpperBoundmaxima; the payout reserve uses that accepted maximum and does not perform a late oracle call after bridge delivery.Dynamic financial operations use v2 attempt classes and bind their request to the stable target balance rather than a live shortfall. This makes a confirmed funding send replayable after a balance-poll timeout or changed Base fee without repeating or permanently conflicting with the operation.
Quotes without
globals.evmDestinationGascontinue through the historical static-funding amounts and operation identities. This compatibility path remains until all such quotes and ramps have expired or completed.Required rollout
EVM_DYNAMIC_DESTINATION_FUNDING_ENABLEDdefaults tofalsedeliberately.EVM_DYNAMIC_DESTINATION_FUNDING_ENABLED=false. Those replicas can execute both legacy and v2 ramps, but still produce only legacy-compatible quotes.EVM_DYNAMIC_DESTINATION_FUNDING_ENABLED=trueto start producing v2 quotes.Do not enable v2 quote production during the first rolling deployment. The two-phase activation prevents an old worker from consuming new executor metadata and falling back to the old static treasury amounts.
Review note
#1311 is merged. This PR targets
stagingdirectly and includes the compatibility updates needed for staging's sequential EVM fee-distribution flow.Verification
bun lint:fixbun run typecheck