Skip to content

Dry run on Ethereum#1711

Open
yrong wants to merge 38 commits into
mainfrom
ron/dry-run-on-ethereum
Open

Dry run on Ethereum#1711
yrong wants to merge 38 commits into
mainfrom
ron/dry-run-on-ethereum

Conversation

@yrong
Copy link
Copy Markdown
Contributor

@yrong yrong commented Feb 11, 2026

Dry run on Ethereum

Validates Snowbridge V2 transfers end-to-end by simulating the resulting Gateway.v2_dispatch against a forked Ethereum node, surfacing reverts, CommandFailed events, and L1Adapter DepositCallInvoked / DepositCallFailed outcomes before the user signs anything.

For Polkadot → Ethereum (L1 and L2) transfers, after the source / AH / BridgeHub Polkadot dry-runs succeed, the SDK now:

  1. Builds a synthetic v2_dispatch(commands, agentID, nonce) tx whose commands mirror what the relayer would submit to mainnet (UnlockNativeToken + optional CallContract).
  2. Sends it to a forked Ethereum node (FORKED_PROVIDER_URL) using impersonation of the gateway proxy as from.
  3. Parses the resulting receipt for the gateway's CommandFailed event and the L1 Adapter's DepositCallInvoked / DepositCallFailed events, surfacing failures via the existing ValidationLog mechanism.

Fix fee estimation for parachain→L2 V2 transfers

The SDK's fee estimation XCM used legacy BuyExecution + InitiateReserveWithdraw, but the Polkadot SDK executor actually inserts PayFees + InitiateTransfer + AliasOrigin into the forwarded XCM. These V5 instructions have significantly higher benchmarked weights (remote_fees handling, asset reanchoring, XCMP queuing), causing queryWeightToAssetFee to underestimate fees.

  • New buildResultXcmAssetHubERC20TransferFromParachainV2: builds the actual forwarded XCM shape (8 instructions: WithdrawAsset×3, PayFees, AliasOrigin, SetAppendix, InitiateTransfer, SetTopic), matching the executor's output exactly.
  • supportsV2 gating: the V2 function is only used for parachains with features.supportsV2: true (currently Hydration 2034).

Parachain→L2 transfer support

  • New ERC20FromParachain transfer class for parachain→L2 routes.
  • Registry additions: Hydration (2034) routes to Base (8453), Optimism (10), Arbitrum (42161) for ETH, USDC, and ERC20 tokens.
  • erc20ToL2.tserc20FromAH.ts rename for consistency.
  • buildRegistry.ts updated to generate routes for v2-enabled parachains (v2_parachains config).
  • Fix fee estimation for parachain→L2 V2 transfers.

Production test

https://app.snowbridge.network/activity#0x7128106c3cf6c505bc38ce4c58bfa05fded5a73bda54f9b634757627186705be

@yrong yrong marked this pull request as ready for review February 13, 2026 07:31
Base automatically changed from alistair/l2 to main February 14, 2026 04:27
@alistair-singh
Copy link
Copy Markdown
Contributor

Not sure we can use tenderly in our API, Is it free an available for public use?

Copy link
Copy Markdown
Contributor

@alistair-singh alistair-singh left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

+1

@yrong
Copy link
Copy Markdown
Contributor Author

yrong commented Feb 27, 2026

Not sure we can use tenderly in our API, Is it free an available for public use?

Just create a virtual testnet and expose a public endpoint, which can be incrementally synced with the parent (actual) network. I assume the fee is already covered by our team subscription.

@alistair-singh
Copy link
Copy Markdown
Contributor

alistair-singh commented Mar 3, 2026

Not sure we can use tenderly in our API, Is it free an available for public use?

Just create a virtual testnet and expose a public endpoint, which can be incrementally synced with the parent (actual) network. I assume the fee is already covered by our team subscription.

I think we can just use eth_Call or eth_estimateGas. Estimate gas will even allow us to set gas appropriately.

@yrong
Copy link
Copy Markdown
Contributor Author

yrong commented Apr 1, 2026

Not sure we can use tenderly in our API, Is it free an available for public use?

Just create a virtual testnet and expose a public endpoint, which can be incrementally synced with the parent (actual) network. I assume the fee is already covered by our team subscription.

I think we can just use eth_Call or eth_estimateGas. Estimate gas will even allow us to set gas appropriately.

It depends on performing an actual dry run to obtain the receipt logs with failure details (indicating which command failed).

@claravanstaden
Copy link
Copy Markdown
Contributor

@yrong do you want to merge this still?

@yrong
Copy link
Copy Markdown
Contributor Author

yrong commented May 25, 2026

@yrong do you want to merge this still?

Yeah, I’ve resolved the conflicts and still want to merge it. I think it should be valuable for L2 transfers.

That said, I’m still planning to set up a local Anvil node and expose it for dry-running, since we canceled the Tenderly subscription to reduce costs, mentioned in #1711 (comment)

Comment thread web/packages/api/src/index.ts Outdated
import type { ToEthereumTransferResult, ToPolkadotTransferResult } from "./history"

type IGatewayProxy = {
getFunction(name: "v2_dispatch"): {
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This should exist on gatewayV2: IGatewayV2, if not you should add it there instead of creating a new interface.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Comment thread web/packages/api/src/index.ts Outdated
Comment on lines +1318 to +1324
const forkedProviderApiKey =
process.env.FORKED_PROVIDER_API_KEY ||
process.env.NEXT_PUBLIC_FORKED_PROVIDER_API_KEY
const forkedProvider = context.ethereumProvider.createProvider(
process.env.FORKED_PROVIDER_URL ||
process.env.NEXT_PUBLIC_FORKED_PROVIDER_URL ||
"https://fork-mainnet.snowbridge.network",
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This needs to be passed in through the registry or config like the subsquid url.

Copy link
Copy Markdown
Contributor Author

@yrong yrong Jun 2, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

69a18c3

I assume the API key is a sensitive credential and shouldn't be stored in the registry. Unlike Subsquid, which is effectively read-only, the dry-run node exposes state-mutating methods such as set_storage and set_block_time, so we should protect access to it with an API key.

logs.push({
kind: ValidationKind.Error,
reason: ValidationReason.FeeEstimationError,
message: ethereumDryRunError,
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

These error messages are displayed to the user. I rather you console.error the full error message and return a user friendly message here.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

const forkedProviderApiKey =
process.env.FORKED_PROVIDER_API_KEY ||
process.env.NEXT_PUBLIC_FORKED_PROVIDER_API_KEY
const forkedProvider = context.ethereumProvider.createProvider(
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Instead of creating a provider for each transfer can we cache this in the context with all the other connections?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Comment on lines +1267 to +1269
const forkedProviderApiKey =
process.env.FORKED_PROVIDER_API_KEY ||
process.env.NEXT_PUBLIC_FORKED_PROVIDER_API_KEY
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This needs to be provided via the environment config, not env vars. Just like how we get the rpc node url and key.

Copy link
Copy Markdown
Contributor Author

@yrong yrong Jun 2, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

const forkedProviderApiKey =
process.env.FORKED_PROVIDER_API_KEY ||
process.env.NEXT_PUBLIC_FORKED_PROVIDER_API_KEY
const forkedProvider = context.ethereumProvider.createProvider(
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This anvil provider should be cached and kept on the context.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This seems to be a duplicate of the comment at #1711 (comment), and has already been addressed.

yrong and others added 4 commits June 2, 2026 12:02
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
@yrong yrong requested a review from alistair-singh June 2, 2026 04:51
yrong and others added 6 commits June 2, 2026 14:22
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
@yrong
Copy link
Copy Markdown
Contributor Author

yrong commented Jun 2, 2026

During the review, AI identified another issue: the L2 bridge fee shown in the fee breakdown should not be denominated in ETH, but in the transfer asset itself.

b2867b0

@alistair-singh Could you take a look and let me know if that makes sense, or whether it would have any impact on how the UI displays fees?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants