# Introduction

Snowbridge is a general-purpose, trustless, and decentralized bridge between Polkadot and Ethereum. It is part of the Polkadot SDK and exists as a system bridge for Polkadot. Ethereum contracts and other off-chain infrastructure can be found in our [Github repository](https://github.com/snowfork/snowbridge).

Key features that set Snowbridge apart from other bridges:

* Owned by the Polkadot community. We do not offer a token.
* Trustless, decentralised, no multisigs
* Integrated with Polkadot Hub and many parachains

The Snowbridge team continues to develop new features and enhance the bridge. For updates on Snowbridge, follow our [account on X](https://x.com/_snowbridge).

## Contact Us

To get in contact, please create a discussion thread [here](https://github.com/Snowfork/snowbridge/discussions).


# Integration Guides

How to integrate with Snowbridge V1 or V2.

### Snowbridge V2

Snowbridge V2 was just released, so if you are doing a new integration, we'd recommend you start with our V2 docs:

* [V2 SDK](/developers/snowbridge-v2/typescript-sdk)
* [V2 Parachain Integration](/developers/snowbridge-v2/parachain-integration)

### Snowbridge V1

We will keep Snowbridge V1 live until all our partners have moved their integration to use V2. Until then, here are our V1 docs:

* [V1 SDK](/developers/snowbridge-v1/typescript-sdk)
* [V1 Parachain Integration](/developers/snowbridge-v1/parachain-integration)

### Support

To get in touch, please log an [issue on our Github repo](https://github.com/Snowfork/snowbridge/issues). We will be in touch to set up a Telegram group, if necessary.

### Examples

We have a wide range of [example scripts](https://github.com/Snowfork/snowbridge/tree/main/web/packages/operations/src) demonstrating how to use the Snowbridge SDK.


# V2

Guides on integrating with Snowbridge V2.

## Features

Snowbridge V2 improves on Snowbridge V1, by adding new features such as contract execution in both directions of the bridge. This introduces a powerful composing engine that opens up new use cases for the bridge, such as integration with Ethereum L2.

Snowbridge V2 also significantly lowers fee, especially in the Polkadot -> Ethereum direction.

#### Integration Types

To integrate with Snowbridge V2, dApps or wallets can use our TypeScript SDK. Parachains who want to integrate directly with our bridge, should follow the V2 parachain integration guide.

* [SDK](/developers/snowbridge-v2/typescript-sdk)
* [Parachain Integration](/developers/snowbridge-v2/parachain-integration)


# SDK

A guide on using the SDK for integration.

The V2 SDK is very similar to the V1 SDK. It adds parameters such as `customXcm` for executing XCM on AssetHub or destination parachains, and fee-selection options such as `feeAsset` or `feeTokenLocation` depending on the route.

## Packages

Snowbridge V2 uses the same packages as the Snowbridge V1 SDK. The current stable SDK release series starts at **v1.0.0**.

### Example Scripts

We have a wide range of scripts using the Snowbridge SDK at <https://github.com/Snowfork/snowbridge/tree/main/web/packages/operations/src>, as examples of how to use the SDK and the bridge.

### Guides

Here are guides to integrate with Snowbridge V2:

* [Token transfer Ethereum -> Polkadot](/developers/snowbridge-v2/typescript-sdk/e2p)
* [Token transfer Polkadot -> Ethereum](/developers/snowbridge-v2/typescript-sdk/e2p-1)
* [Transact on AssetHub & Parachain](/developers/snowbridge-v2/typescript-sdk/transact-ah)
* [Transact on Ethereum & L2s](/developers/snowbridge-v2/typescript-sdk/transact-ethereum)
* [SDK Cases](/developers/snowbridge-v2/typescript-sdk/cases)


# Token transfer Ethereum -> Polkadot

A guide on using the Snowbridge TypeScript SDK for Ethereum to Polkadot transfers.

Uses the `@snowbridge/api` SDK.

## Setup

```typescript
import { createApi } from "@snowbridge/api"
import { EthersEthereumProvider } from "@snowbridge/provider-ethers"
import { polkadot_mainnet } from "@snowbridge/registry"

const {
    chains: { ethereum, assetHub },
} = polkadot_mainnet

const api = createApi({ info: polkadot_mainnet, ethereumProvider: new EthersEthereumProvider() })

const sender = api.sender(ethereum, assetHub)
```

## Build

```typescript
const transfer = await sender.build(
    "0x...", // source Ethereum account
    "5...", // beneficiary Polkadot account
    "0x0000000000000000000000000000000000000000", // Ether address
    15_000_000_000_000n, // amount: 0.000015 ETH
    {
        fee: {
            padFeeByPercentage: 33n,
        },
    },
)
```

The returned `transfer.tx` can then be submitted to the wallet by your application.


# Token transfer Polkadot -> Ethereum

A guide on using the Snowbridge TypeScript SDK for Polkadot to Ethereum transfers.

Uses the `@snowbridge/api` SDK.

## Setup

```typescript
import { createApi } from "@snowbridge/api"
import { EthersEthereumProvider } from "@snowbridge/provider-ethers"
import { polkadot_mainnet } from "@snowbridge/registry"

const {
    chains: { assetHub, ethereum },
} = polkadot_mainnet

const api = createApi({ info: polkadot_mainnet, ethereumProvider: new EthersEthereumProvider() })

const sender = api.sender(assetHub, ethereum)
```

## Build

```typescript
const transfer = await sender.build(
    "5...", // source Polkadot account
    "0x...", // beneficiary Ethereum account
    "0x0000000000000000000000000000000000000000", // Ether address
    15_000_000_000_000n, // amount: 0.000015 ETH
    {
        fee: {
            feeTokenLocation: { parents: 1, interior: "Here" }, // DOT location
            padFeeByPercentage: 33n,
            slippagePadPercentage: 20n,
        },
    },
)
```

The returned `transfer.tx` can then be submitted to the wallet by your application.


# Transact on AssetHub & Parachain

A guide on using the Snowbridge TypeScript SDK for integration.

Uses the `@snowbridge/api` SDK.

#### Transact On AssetHub

To specify XCM to be executed on AssetHub, define your XCM:

```typescript
const remarkCall = moonbeam.tx.system.remarkWithEvent(remarkMessage)
const callHex = remarkCall.method.toHex()

// Get weight info for the call
const paymentInfo = await remarkCall.paymentInfo(POLKADOT_ACCOUNT_PUBLIC)
const weight = paymentInfo.weight

const customXcm = [
   {
       transact: {
           originKind: "SovereignAccount",
           fallbackMaxWeight: {
               refTime: weight.refTime.toBigInt(),
               proofSize: weight.proofSize.toBigInt(),
           },
           call: {
               encoded: callHex,
           },
       },
   },
]
```

This XCM uses the `system.remarkWithEvent` extrinsic, wrapped in a `Transact` XCM instruction.

#### Transact Execution

To execute the XCM program on AssetHub, the SDK integration is identical to the token transfer steps, with the extra `customXcm` parameter:

```typescript
import { createApi } from "@snowbridge/api"
import { EthersEthereumProvider } from "@snowbridge/provider-ethers"
import { polkadot_mainnet } from "@snowbridge/registry"

const {
    chains: { ethereum, assetHub },
} = polkadot_mainnet
const api = createApi({ info: polkadot_mainnet, ethereumProvider: new EthersEthereumProvider() })

const sender = api.sender(ethereum, assetHub)

const transfer = await sender.build(
    "0x...", // source Ethereum account
    "5...", // beneficiary Polkadot account
    "0x0000000000000000000000000000000000000000", // Ether address
    15_000_000_000_000n, // amount: 0.000015 ETH
    {
        customXcm,
    },
)
```

The returned `transfer.tx` can then be submitted to the wallet by your application.

### Transact on Parachain

To transact on another parachain, like Hydration or NeuroWeb, use the same steps as above with concrete destructuring such as `const { chains: { ethereum, hydration } } = polkadot_mainnet`.

Your custom XCM program will be appended to the `InitiateTransfer` instruction that is built up in the SDK.

### Message Origin

It is important to note that the origin of the message on AssetHub or destination parachain is the original sender account on Ethereum, e.g.

```
{
    parents: 2,
    interior: {
        x2: [
            {
                GlobalConsensus: {
                    Ethereum: {
                        chainId: 1,
                    },
                },
            },
            {
                AccountKey20: {
                    key: "0xa84670..."
                }
            }
        ],
    },
}
```

The destination parachain should support XCM instruction `AliasOrigin` , and the destination parachain should be able to map the origin location into an account, e.g. using `HashedDescription`.


# Transact on Ethereum & L2s (WIP)

A guide on using the Snowbridge TypeScript SDK for integration.

Uses the `@snowbridge/api` SDK.

### Agent Setup

To execute arbitrary contracts on Ethereum and L2s, you need to create an agent for your calling parachain or user. More details can be found in the [Agents section](broken://pages/gZd0UprOH4eSA5EYNy2H#agent).

### SDK Usage

The SDK uses the same sender pattern as token transfers, with the Ethereum contract call passed through the build options.

```typescript
import { createApi } from "@snowbridge/api"
import { EthersEthereumProvider } from "@snowbridge/provider-ethers"
import { polkadot_mainnet } from "@snowbridge/registry"

const {
    chains: { assetHub, ethereum },
} = polkadot_mainnet
const api = createApi({ info: polkadot_mainnet, ethereumProvider: new EthersEthereumProvider() })
const TARGET_CONTRACT = "0x1111111111111111111111111111111111111111"
const TARGET_CALLDATA = "0x"

const sender = api.sender(assetHub, ethereum)

const transfer = await sender.build(
    "5...", // source Polkadot account
    "0x...", // beneficiary Ethereum account
    "0x0000000000000000000000000000000000000000", // Ether address
    15_000_000_000_000n, // amount: 0.000015 ETH
    {
        fee: {
            contractCall: {
                target: TARGET_CONTRACT,
                calldata: TARGET_CALLDATA,
                value: 0n,
                gas: 500_000n,
            },
        },
    },
)
```

The returned `transfer.tx` can then be submitted to the wallet by your application.

For current route coverage, see [SDK Cases](/developers/snowbridge-v2/typescript-sdk/cases).


# Create Agent

Guide to create an Agent on Ethereum.

To transact from Polkadot to Ethereum, you need to create an agent on Ethereum. An agent is similar to a sovereign account on Polkadot.

### Step 1: Get Agent ID

Use the create-agent helper to derive the `agentId` for your source parachain account:

```typescript
import { createApi } from "@snowbridge/api"
import { EthersEthereumProvider } from "@snowbridge/provider-ethers"
import { polkadot_mainnet } from "@snowbridge/registry"

const {
    chains: { assetHub },
} = polkadot_mainnet

const api = createApi({ info: polkadot_mainnet, ethereumProvider: new EthersEthereumProvider() })
const creator = api.createAgent()

const agentId = await creator.agentIdForAccount(
    assetHub.id,
    "5CXiZE6z6w78EuqGdmJao7PFnmArgoHJbHbjWPftW5otnBKs", // source account on the parachain
)
```

### Step 2: Create Agent

You have the option of creating an agent through the Snowbridge SDK, or by calling the contract directly.

#### SDK

The Snowbridge V2 SDK supports creating an Agent:

```typescript
const agentCreate = await creator.build(
    "0x...", // source Ethereum account submitting the create-agent transaction
    agentId,
)
```

The returned `agentCreate.tx` can then be submitted to the wallet by your application.

The full script is available at <https://github.com/Snowfork/snowbridge/blob/main/web/packages/operations/src/create_agent.ts>

#### Call Contract

You can call the `v2_createAgent` method directly on the Snowbridge gateway contract: <https://etherscan.io/address/0x27ca963c279c93801941e1eb8799c23f407d68e7#writeProxyContract>

Enter the ID from step 1 and click `Write`:

<figure><img src="/files/IQFKY6fRyUy2iJk7fEhq" alt=""><figcaption></figcaption></figure>


# SDK Cases

Route and example coverage for the Snowbridge V2 TypeScript SDK.

This page lists the route cases currently covered by the operations testcase matrix in [web/packages/operations/src/testcases/testAll.ts](https://github.com/Snowfork/snowbridge/blob/main/web/packages/operations/src/testcases/testAll.ts).

## Ethereum -> Polkadot

Examples covered:

* `ethereum:1 -> polkadot:1000` with `DOT`
* `ethereum:1 -> polkadot:2000` with `ETH`
* `ethereum:1 -> polkadot:2004` with `WETH`
* `ethereum:1 -> polkadot:2030` with `ETH`
* `ethereum:1 -> polkadot:2034` with `USDC`
* `ethereum:1 -> polkadot:2043` with `TRAC`
* `ethereum:1 -> polkadot:3369` with `MYTH`

## Polkadot -> Ethereum

Examples covered:

* `polkadot:1000 -> ethereum:1` with `DOT`
* `polkadot:2000 -> ethereum:1` with `ETH`
* `polkadot:2004 -> ethereum:1` with `WETH`
* `polkadot:2030 -> ethereum:1` with `ETH`
* `polkadot:2034 -> ethereum:1` with `USDC`
* `polkadot:2043 -> ethereum:1` with `TRAC`
* `polkadot:3369 -> ethereum:1` with `MYTH`
* `ethereum:1284 -> ethereum:1` with `WETH`

## L2 -> Polkadot

Examples covered:

* `ethereum_l2:10 -> polkadot:1000` with `ETH`
* `ethereum_l2:42161 -> polkadot:1000` with `WETH`
* `ethereum_l2:8453 -> polkadot:1000` with `USDC`

## Polkadot -> L2

Examples covered:

* `polkadot:1000 -> ethereum_l2:10` with `ETH`
* `polkadot:1000 -> ethereum_l2:42161` with `WETH`
* `polkadot:1000 -> ethereum_l2:8453` with `USDC`

## Inter-Parachain

Examples covered:

* `polkadot:1000 -> polkadot:2034` with `USDC`
* `polkadot:2034 -> polkadot:1000` with `USDC`

## Registration

Examples covered:

* Agent creation with `api.createAgent()`
* Token registration with `api.registerToken()`

## Common SDK Pattern

Most route cases follow the same pattern:

```typescript
const sender = api.sender(from, to)
const transfer = await sender.build(
    sourceAccount,
    beneficiaryAccount,
    tokenAddress,
    amount,
    options,
)
```

The returned `transfer.tx` can then be submitted to the wallet by your application.


# Parachain Integration

A guide for parachain integraters.

### V2 Protocol

Snowbridge V2 is a generalized messaging protocol, that supports token transfers (ERC-20s and Polkadot native assets), as well as arbitrary contract execution in both directions.

Snowbridge V2 protocol improves upon V1 with better fee handling, batching of messages, unordered message delivery and the ability to execute contract/extrinsics via generalized message passing. It builds on the [requirements for Snowbridge V1](/developers/snowbridge-v1/parachain-integration).

#### General Parachain Requirements

1. Must use at least Polkadot-SDK `stable2506`
2. Support XCMv5 and have `pallet-xcm` set up.
3. Support Ether as an asset. Your parachain must have a pallet that can store assets such as `orml-tokens` or `pallet-assets`.
4. Allow paying for execution with Ether.
5. XCM instruction `AliasOrigin` should be supported (check XCM weights are not set to MAX)

#### ERC20 Token Bridge (ENA)

1. Register the ERC20 token contract with the [Snowbridge Gateway.](broken://pages/pnzQlWeYRVy0L4FrWVoz#registering-tokens) This process will automatically set the ERC20 on Asset Hub as well.
2. Your parachain must support a pallet which can register assets such as `orml-tokens` or `pallet-xcm`. The ERC20 token must be registered with that pallet.
3. You can reach out to the Snowbridge team on Github to enable your token in our UI and SDK.

#### Polkadot Native Assets Token Bridge (PNA)

1. Your asset must first be registered on Asset Hub in the `ForeignAssets` Pallet.
2. Your asset must be able to `Teleport` to Asset Hub.
3. Once your asset can be sent successfully between Asset Hub and your chain you can then register the Asset on bridge hub via the `EthereumSystem` pallet `registerToken` extrinsic.
4. You can reach out to the Snowbridge team on github to enable your token in our UI and SDK.

#### Generalised Message Passing

In order to pass messages arbitrary message calls between Ethereum you require the following to be set up.

**Polkadot to Ethereum**

The origin that sends messages will need to an Agent created on Ethereum to act on that origins behalf on Ethereum. An agent is simply a contract on Ethereum that is associated with an origin on the Polkadot side. Only messages from that origin can dispatch messages to the Agent. [See more on agents.](broken://pages/gZd0UprOH4eSA5EYNy2H#agent)

1. Design your pallet or extrinsic carefully and choose the origin that will be used to dispatch to Ethereum.
2. Create an Agent with that origin on Bridge Hub.

**Ethereum to Polkadot**

All messages are routed through Asset Hub and your parachain will need to allow Asset Hub to alias origins from Ethereum.

1. Allow Asset Hub to alias origins. Example configuration: [People System Chain](https://github.com/polkadot-fellows/runtimes/blob/93d62ed/system-parachains/people/people-polkadot/src/xcm_config.rs#L230)
2. Allow system chains to alias accounts. Example configuration: [People System Chain](https://github.com/polkadot-fellows/runtimes/blob/93d62ed/system-parachains/people/people-polkadot/src/xcm_config.rs#L229)

### Using assets other than ETH or DOT for fees

Asset Hub contains a built-in DEX and since all Snowbridge messages are router through Asset Hub, there is a chance to swap DOT or ETH for any other fee asset.

1. The Asset must be registered on Asset Hub.
2. There must be a pool created with enough liquidity to make fee prices stable.
3. There must be monitoring of the pool in place to make sure its not drained of liquidity.


# V1

Guides on integrating with Snowbridge V1.

{% hint style="warning" %}
V1 is deprecated and considered a legacy API. Rather use our V2 APIs.
{% endhint %}

#### Integration Types

To integrate with Snowbridge V1, dApps or wallets can use our TypeScript SDK. Parachains who want to integrate directly with our bridge, should follow the V1 parachain integration guide.

* [V1 SDK](/developers/snowbridge-v1/typescript-sdk)
* [V1 Parachain Integration](/developers/snowbridge-v1/parachain-integration)


# SDK

A guide on using the Snowbridge TypeScript SDK for integration.

## Packages

The following packages are used in the Snowbridge SDK:

* [**@snowbridge/api**](https://www.npmjs.com/package/@snowbridge/api) This is the **main entry point** for developers integrating with Snowbridge. It provides all core interfaces and helper functions to initiate, validate, and send cross-chain transactions. It abstracts over the complexities of constructing and handling XCM messages, Ethereum transactions, and relayer coordination.
  * Use the [`toPolkadotV2`](https://github.com/Snowfork/snowbridge/blob/main/web/packages/api/src/toPolkadot_v2.ts) module for sending packages from Ethereum -> Polkadot.
  * Use the [`toEthereumV2`](https://github.com/Snowfork/snowbridge/blob/main/web/packages/api/src/toEthereum_v2.ts) module for sending packages from Polkadot -> Ethereum.
* [**@snowbridge/registry**](https://www.npmjs.com/package/@snowbridge/registry) This package contains the **asset and parachain registry** used by Snowbridge. It defines the list of supported tokens, parachains, and associated metadata (like contract addresses and decimals). It ensures your transfers use valid combinations of assets and destinations.
* [**@snowbridge/contract-types**](https://www.npmjs.com/package/@snowbridge/contract-types) Contains **TypeScript typings and contract ABIs** for the Ethereum contracts Snowbridge interacts with. Use this package to interact with contracts directly, or to extend SDK functionality.
* [**@snowbridge/contracts**](https://www.npmjs.com/package/@snowbridge/contracts) Provides deployed contract addresses and metadata for Snowbridge smart contracts on supported networks. This is useful when you need to interact with Snowbridge's **Ethereum-side contracts** directly.
* [**@snowbridge/base-types**](https://www.npmjs.com/package/@snowbridge/base-types) Defines **common data types** used throughout the SDK, such as asset representations, transfer objects, parachain locations, and more. These types are shared between `@snowbridge/api` and the registry.

### Example Scripts

We have a wide range of scripts using the Snowbridge SDK at <https://github.com/Snowfork/snowbridge/tree/main/web/packages/operations/src>, as examples of how to use the SDK and the bridge.

## Demos

The following examples show how to do an Ether transfer from Ethereum to Polkadot, and back. The full example code can be viewed by following the stated links.

### Ethereum to Polkadot

Full example: [send\_ether\_from\_eth\_to\_assethub.ts](https://github.com/Snowfork/snowbridge/blob/main/web/packages/operations/src/examples/send_ether_from_eth_to_assethub.ts)

Uses the `@snowbridge/api` [`toPolkadotV2`](https://github.com/Snowfork/snowbridge/blob/main/web/packages/api/src/toPolkadot_v2.ts) package to send the transaction.

#### Setup

This step prepares all required state and dependencies for the transfer operation. This includes loading the asset registry, initializing the context, which sets up the connections to Ethereum and Substrate-based networks and loading the user wallets for both Ethereum and Substrate chains.

```typescript
// Initialize polkadot-js 
crypto await cryptoWaitReady()
// Get the registry of parachains and assets.
const environment = "polkadot_mainnet"
const registry = assetRegistryFor(environment)
// Initialize the context which establishes and pool connections
const context = new Context(contextConfigFor(environment))

// Initialize ethereum wallet.
const ETHEREUM_ACCOUNT = new Wallet(
    process.env.ETHEREUM_KEY ?? "Your Key Goes Here",
    context.ethereum()
)
const ETHEREUM_ACCOUNT_PUBLIC = await ETHEREUM_ACCOUNT.getAddress()

// Initialize substrate wallet.
const polkadot_keyring = new Keyring({ type: "sr25519" })
const POLKADOT_ACCOUNT = polkadot_keyring.addFromUri(
    process.env.SUBSTRATE_KEY ?? "Your Key Goes Here"
)
const POLKADOT_ACCOUNT_PUBLIC = POLKADOT_ACCOUNT.address
```

#### Step 1: Get Delivery Fee

Use `getDeliveryFee()` to calculate how much the user must pay in order to deliver the message across chains. This includes relayer fees and any protocol-specific gas or weight costs. Displaying this to the user upfront ensures clarity and reduces failed transactions due to underpayment.

```javascript
// Select the token you want to send. In this case we use Ether. The registry 
// contains the list of tokens.
const TOKEN_CONTRACT = assetsV2.ETHER_TOKEN_ADDRESS
// Select the destination parachain. In this case it is Asset Hub.
const SOURCE_PARACHAIN = 1000
const fee = await toPolkadotV2.getDeliveryFee(
    context, // The context
    registry, // Asset registry
    TOKEN_CONTRACT, // The erc20 token contract address
    DESTINATION_PARACHAIN // Destination parachain
)
```

#### Step 2: Create Transfer

The `createTransfer()` function generates a transfer object, which includes source and destination accounts, the amount to send, the token being transferred and the precomputed delivery fee. This object contains all data necessary to execute the cross-chain transfer and is later used for validation and signing.

```javascript
const amount = 15_000_000_000_000n // 0.000015 ETH
const transfer = await toPolkadotV2.createTransfer(
    registry, // Asset registry
    ETHEREUM_ACCOUNT_PUBLIC, // Source account
    POLKADOT_ACCOUNT_PUBLIC, // Destination account
    TOKEN_CONTRACT, // The erc20 token contract address
    DESTINATION_PARACHAIN, // Destination parachain
    amount, // Transfer Amount
    fee // The delivery fee
)
```

#### Step 3: Validate Transfer

Although optional, `validateTransfer()` is strongly recommended. It performs local checks and dry-runs the transaction (when possible) to ensure:

* The sender has enough funds
* The asset is supported
* The constructed transaction will succeed on-chain

This step can save users from wasting gas or fees on transactions that would otherwise revert.

```javascript
const validation = await toPolkadotV2.validateTransfer(
    context, // The context
    transfer // The transfer tx
)

if (!validation.success) {
    console.error(validation.logs)
    throw Error(`validation has one of more errors.`)
}
```

#### Step 4: Send Transaction

Finally, the transaction is signed and submitted to the source chain. Use the `Wallet` instance to send the transaction.

```javascript
const response = await ETHEREUM_ACCOUNT.sendTransaction(transfer.tx)
const receipt = await response.wait(1)
if (!receipt) {
    throw Error(`Transaction ${response.hash} not included.`)
}
```

### Polkadot to Ethereum

Full example: [send\_ether\_from\_assethub\_to\_eth.ts](https://github.com/Snowfork/snowbridge/blob/main/web/packages/operations/src/examples/send_ether_from_assethub_to_eth.ts)

Uses the `@snowbridge/api` [`toEthereumV2`](https://github.com/Snowfork/snowbridge/blob/main/web/packages/api/src/toEthereum_v2.ts) module to send the transaction.

#### Setup

This step prepares all required state and dependencies for the transfer operation. This includes loading the asset registry, initializing the context, which sets up the connections to Ethereum and Substrate-based networks and loading the user wallets for both Ethereum and Substrate chains.

```typescript
// Initialize polkadot-js crypto
await cryptoWaitReady()
// Get the registry of parachains and assets.
const environment = "polkadot_mainnet"
const registry = assetRegistryFor(environment)

// Initialize the context which establishes and pool connections
const context = new Context(contextConfigFor(environment))
// Initialize ethereum wallet.
const ETHEREUM_ACCOUNT = new Wallet(
    process.env.ETHEREUM_KEY ?? "Your Key Goes Here",
    context.ethereum()
)
const ETHEREUM_ACCOUNT_PUBLIC = await ETHEREUM_ACCOUNT.getAddress()

// Initialize substrate wallet.
const polkadot_keyring = new Keyring({ type: "sr25519" })
const POLKADOT_ACCOUNT = polkadot_keyring.addFromUri(
    process.env.SUBSTRATE_KEY ?? "Your Key Goes Here"
)
const POLKADOT_ACCOUNT_PUBLIC = POLKADOT_ACCOUNT.address

// Select the token you want to send. In this case we use Ether. The registry contains the list of tokens.
const TOKEN_CONTRACT = assetsV2.ETHER_TOKEN_ADDRESS
// Select the destination parachain. In this case it is Asset Hub.
const SOURCE_PARACHAIN = 1000
```

#### Step 1: Get Delivery Fee

Use `getDeliveryFee()` to calculate how much the user must pay in order to deliver the message across chains. This includes relayer fees and any protocol-specific gas or weight costs. Displaying this to the user upfront ensures clarity and reduces failed transactions due to underpayment.

```javascript
const fee = await toEthereumV2.getDeliveryFee(
    context, // The context
    SOURCE_PARACHAIN, // Source parachain Id
    registry, // The asset registry
    TOKEN_CONTRACT // The token being transferred
)
```

#### Step 2: Create Transfer

The `createTransfer()` function generates a transfer object, which includes source and destination accounts, the amount to send, the token being transferred and the precomputed delivery fee. This object contains all data necessary to execute the cross-chain transfer and is later used for validation and signing.

```javascript
const amount = 15_000_000_000_000n // 0.000015 ETH
const transfer = await toEthereumV2.createTransfer(
    { sourceParaId: SOURCE_PARACHAIN, context }, // The context and source parachain
    registry, // The asset registry
    POLKADOT_ACCOUNT_PUBLIC, // The source account
    ETHEREUM_ACCOUNT_PUBLIC, // The destination account
    TOKEN_CONTRACT, // The transfer token
    amount, // The transfer amount
    fee // The fee
)
```

#### Step 3: Validate Transfer

Although optional, `validateTransfer()` is strongly recommended. It performs local checks and dry-runs the transaction (when possible) to ensure:

* The sender has enough funds
* The asset is supported
* The constructed transaction will succeed on-chain

This step can save users from wasting gas or fees on transactions that would otherwise revert.

```javascript
const validation = await toEthereumV2.validateTransfer(
    context, // The context
    transfer
)

if (!validation.success) {
    console.error(validation.logs)
    throw Error(`validation has one of more errors.`)
}

```

#### Step 4: Send Transaction

Finally, the transaction is signed and submitted to the source chain. Use the SDK helper `signAndSend()` which manages construction, signing, and submission of the extrinsic.

```javascript
const response = await toEthereumV2.signAndSend(
    context, // The context
    transfer,
    POLKADOT_ACCOUNT,
    { withSignedTransaction: true }
)
if (!response) {
    throw Error(`Transaction ${response} not included.`)
}
if (!response.messageId) {
    throw Error(
        `Transaction ${response} did not have a message id. Did your transaction revert?`
    )
}
```


# Parachain Integration

A guide for parachain integraters for Snowbridge V1.

### Snowbridge V1 Protocol

Snowbridge V1 is a token bridge which supports ERC20 assets and Polkadot assets.

#### General Parachain Requirements

1. An HRMP channel must be set up between Asset Hub and your parachain.
2. Your parachain must use at least Polkadot-SDK version `stable2409`
3. `pallet-xcm` with at least XCMv4 (XCMv5 preferable).
4. XCM `dryRun` runtime apis. [Asset Hub Parachain](https://github.com/polkadot-fellows/runtimes/blob/d6c5bd34c51ba7f670278f19d19e53e6db5a6b48/system-parachains/asset-hubs/asset-hub-polkadot/src/lib.rs#L1769)
5. XCM `feePayment` runtime apis. [Asset Hub Parachain](https://github.com/polkadot-fellows/runtimes/blob/d6c5bd34c51ba7f670278f19d19e53e6db5a6b48/system-parachains/asset-hubs/asset-hub-polkadot/src/lib.rs#L1741)
6. Your parachain must support a pallet which can register assets such as `orml-tokens` or `pallet-xcm`. DOT must be registered with that pallet.
7. Allow Asset Hub to be a reserve for DOT.
8. Accept DOT as payment for XCM execution.
9. Allow Asset Hub to be a reserve for bridged Assets. [Hydration Parachain](https://github.com/galacticcouncil/hydration-node/pull/784)

#### ERC20 Token Bridge (ENA)

1. Register the ERC20 token contract with the [Snowbridge Gateway.](broken://pages/pnzQlWeYRVy0L4FrWVoz#registering-tokens) This process will automatically set the ERC20 on Asset Hub as well.
2. Your parachain must support a pallet which can register assets such as `orml-tokens` or `pallet-xcm`. The ERC20 token must be registered with that pallet.
3. You can reach out to the Snowbridge team on github to enable your token in our UI and SDK.

#### Polkadot Native Assets Token Bridge (PNA)

1. Your asset must first be registered on Asset Hub in the `ForeignAssets` Pallet.
2. Your asset must be able to `Teleport` to Asset Hub.
3. Once your asset can be sent successfully between Asset Hub and your chain you can then register the Asset on bridge hub via the `EthereumSystem` pallet `registerToken` extrinsic.
4. You can reach out to the Snowbridge team on github to enable your token in our UI and SDK.


# Token Transfers

Sending ERC20 Tokens to Polkadot

The bridge currently supports sending ERC20 tokens from Ethereum to any Polkadot parachain, and back again.

The bridged tokens are minted in `ForeignAssets` pallet of the AssetHub parachain, and then transferred to the final destination using a [reserve transfer](https://wiki.polkadot.network/docs/learn-xcm-usecases#reserve-asset-transfer).

A token transfer can be initiated with a single transaction to our [Gateway](https://github.com/Snowfork/snowbridge/blob/main/contracts/src/interfaces/IGateway.sol) contract.

## How to send tokens to a parachain

Sending tokens is usually a single step for the user. However, a preliminary registration step is required for tokens which have not previously been bridged before.

### Token Sending

To send a previously registered token to a destination parachain, send this transaction to the Gateway:

```solidity
/// @dev Send ERC20 tokens to parachain `destinationChain` and deposit into account `destinationAddress`
function sendToken(address token, ParaID destinationChain, MultiAddress destinationAddress, uint128 destinationFee, uint128 amount)
    external
    payable;
```

This function will charge a fee in Ether that can be retrieved ahead of time by calling `quoteSendTokenFee`.


# V1 to V2 Upgrade Guide

## Snowbridge V1 to V2 Migration Guide

This guide is for parachain teams (e.g. Hydration) migrating their Snowbridge integration from V1 to V2. It covers both transfer directions: Ethereum to Polkadot and Polkadot to Ethereum.

### Prerequisites

Your parachain must support:

* **XCM v5**
* **XcmPaymentApi** runtime API (for fee estimation)
* **supportsV2** flag in the Snowbridge asset registry (Snowbridge team will add this)

For Polkadot-to-Ethereum V2 transfers, additionally:

* **Ether balance support**
* **AliasOrigin support**

***

### 1. Ethereum to Polkadot

V2 example script: <https://github.com/Snowfork/snowbridge/blob/main/web/packages/operations/src/transfer_to_polkadot_v2.ts>

#### V1: `sendToken`

In V1, the Ethereum Gateway exposes `sendToken` which takes the token address, destination parachain ID, beneficiary, and fee amount. The bridge handles all XCM construction internally.

#### V2: `v2_sendMessage`

In V2, the caller constructs an XCM program and passes it to the gateway along with asset and fee parameters.

```solidity
function v2_sendMessage(
    bytes calldata xcm,          // SCALE-encoded VersionedXcm (your XCM program)
    bytes[] calldata assets,     // Array of ABI-encoded asset specs
    bytes calldata claimer,      // SCALE-encoded claimer location
    uint128 executionFee,        // Ether for AssetHub execution
    uint128 relayerFee           // Ether for relayer incentive
) external payable;
```

* `msg.value` must be >= `executionFee + relayerFee`. Any surplus becomes the transfer value (deposited as Ether into the XCM holding register on AssetHub).

#### XCM Location Constants

All XCM examples below use these locations:

```typescript
// Ether location (zero-address ERC20 = native Ether)
const ETHER_LOCATION = {
    parents: 2,
    interior: { x1: [{ GlobalConsensus: { Ethereum: { chain_id: 1 } } }] }
}

// ERC20 token location
const ERC20_LOCATION = {
    parents: 2,
    interior: {
        X2: [
            { GlobalConsensus: { Ethereum: { chain_id: 1 } } },
            { AccountKey20: { key: TOKEN_ADDRESS } }
        ]
    }
}

// DOT location
const DOT_LOCATION = { parents: 1, interior: "Here" }
```

Note: `ETHER_LOCATION` and `BRIDGE_LOCATION` are the same value (the Ethereum consensus location). The Ether token address is `0x0000000000000000000000000000000000000000`.

#### How `v2_sendMessage` Works: Infrastructure vs User XCM

**Important:** The `xcm` parameter you pass to `v2_sendMessage` is **not** the full XCM program that executes on AssetHub. BridgeHub's inbound pallet **prepends** infrastructure instructions before appending your XCM.

**BridgeHub prepends (you do NOT construct these):**

```
1. DescendOrigin(PalletInstance(91))              -- Snowbridge inbound pallet origin
2. UniversalOrigin(Ethereum { chain_id })          -- Establishes Ethereum origin
3. ReserveAssetDeposited([ETH executionFee])       -- Brings execution fee into holding
4. SetHints { assetClaimer: claimer }              -- From your `claimer` param
5. PayFees { ETH executionFee }                    -- Pays AH execution fees
6. ReserveAssetDeposited([ETH surplus value])      -- Ether value (msg.value - fees), if any
7. ReserveAssetDeposited([ERC20 tokens])           -- From your `assets` param (or WithdrawAsset for PNA)
8. DescendOrigin(AccountKey20 { msg.sender })      -- Sets sender sub-origin
```

**Then your `xcm` parameter is appended.** So what you pass as `xcm` only needs to handle the assets that are already in the holding register.

#### Encoding the `assets` Parameter

ERC20 tokens are ABI-encoded for the `assets` parameter:

```typescript
import { AbiCoder } from "ethers"

// For ERC20 tokens (NativeTokenERC20, kind=0):
const encodedAsset = AbiCoder.defaultAbiCoder().encode(
    ["uint8", "address", "uint128"],
    [0, tokenAddress, amount],
)

// Wrap into bytes[] for the gateway
const assetsBytes = AbiCoder.defaultAbiCoder().encode(
    ["bytes[]"],
    [[encodedAsset]],
)
```

Ether is **not** encoded as an asset. Instead, it is sent as surplus `msg.value` beyond `executionFee + relayerFee`. The surplus automatically appears in the holding register on AssetHub.

Example of encoding in TypeScript: <https://github.com/Snowfork/snowbridge/blob/main/web/packages/api/src/transfers/toPolkadot/erc20ToParachain.ts#L225-L229>

#### Encoding the `claimer` Parameter

The claimer is a SCALE-encoded `StagingXcmV5Location` identifying who can reclaim trapped assets on AssetHub:

```typescript
import { toPolkadotSnowbridgeV2 } from "@snowbridge/api"

const claimerLocation = toPolkadotSnowbridgeV2.claimerFromBeneficiary(
    assetHub, // ApiPromise
    beneficiaryAddressHex,
)
const claimerBytes = toPolkadotSnowbridgeV2.claimerLocationToBytes(claimerLocation)
```

#### Message ID (Topic)

V2 generates a unique topic for each transfer using blake2:

```typescript
const topic = toPolkadotSnowbridgeV2.buildMessageId(
    destParaId, senderHex, tokenAddress,
    beneficiary, amount, accountNonce,
)
```

#### `xcm` Parameter: ERC20 to Parachain (Ether Destination Fee)

For transfers to your parachain paying destination fees in **Ether**:

```
v5: [
    // Forward token + ether fee to destination parachain
    { initiateTransfer: {
        destination: { parents: 1, interior: { x1: [{ parachain: DEST_PARA_ID }] } },
        remote_fees: {
            reserveDeposit: {
                definite: [{ id: ETHER_LOCATION, fun: { Fungible: remoteExecutionFee } }]
            }
        },
        preserveOrigin: false,
        assets: [{
            reserveDeposit: {
                definite: [{ id: ERC20_TOKEN_LOCATION, fun: { Fungible: tokenAmount } }]
            }
        }],
        // XCM that executes on the destination parachain:
        remoteXcm: [
            { refundSurplus: null },
            { depositAsset: {
                assets: { wild: { allCounted: 3 } },
                beneficiary: { parents: 0, interior: { x1: [BENEFICIARY_LOCATION] } }
            }},
            { setTopic: TOPIC }
        ]
    }},

    // Return any unused Ether fees on AssetHub to beneficiary
    { refundSurplus: null },
    { depositAsset: {
        assets: { wild: { allOf: { id: ETHER_LOCATION, fun: "Fungible" } } },
        beneficiary: { parents: 0, interior: { x1: [BENEFICIARY_LOCATION] } }
    }},
    { setTopic: TOPIC }
]
```

#### `xcm` Parameter: ERC20 to Parachain (DOT Destination Fee)

If your parachain prefers DOT as the destination fee asset, add `exchangeAsset` before `initiateTransfer` to swap Ether for DOT on AssetHub:

```
v5: [
    // Swap Ether for DOT on AssetHub (for destination fee)
    { exchangeAsset: {
        give: {
            definite: [{ id: ETHER_LOCATION, fun: { Fungible: remoteEtherFeeAmount } }]
        },
        want: [{ id: DOT_LOCATION, fun: { Fungible: remoteDotFeeAmount } }],
        maximal: true
    }},

    // Forward to destination with DOT as fee
    { initiateTransfer: {
        destination: { parents: 1, interior: { x1: [{ parachain: DEST_PARA_ID }] } },
        remote_fees: {
            reserveDeposit: {
                definite: [{ id: DOT_LOCATION, fun: { Fungible: remoteDotFeeAmount } }]
            }
        },
        preserveOrigin: false,
        assets: [{
            reserveDeposit: {
                definite: [{ id: ERC20_TOKEN_LOCATION, fun: { Fungible: tokenAmount } }]
            }
        }],
        remoteXcm: [
            { refundSurplus: null },
            { depositAsset: {
                assets: { wild: { allCounted: 3 } },
                beneficiary: { parents: 0, interior: { x1: [BENEFICIARY_LOCATION] } }
            }},
            { setTopic: TOPIC }
        ]
    }},

    // Return unused Ether to beneficiary on AssetHub
    { depositAsset: {
        assets: { wild: { allOf: { id: ETHER_LOCATION, fun: "Fungible" } } },
        beneficiary: { parents: 0, interior: { x1: [BENEFICIARY_LOCATION] } }
    }},
    { setTopic: TOPIC }
]
```

#### `xcm` Parameter: PNA to Parachain

For Polkadot Native Assets (like DOT), the `xcm` parameter structure is identical to the ERC20 versions above. The difference is only in the infrastructure part: BridgeHub uses `WithdrawAsset` instead of `ReserveAssetDeposited` for PNA tokens (since they are held in reserve on AssetHub). Your `xcm` parameter still uses `reserveDeposit` in `initiateTransfer.assets` because from the destination parachain's perspective, AssetHub is the reserve.

See `web/packages/api/src/xcmbuilders/toPolkadot/pnaToParachain.ts` for the exact implementation.

#### Full XCM on AssetHub (for reference)

For completeness, the full XCM that actually executes on AssetHub is the infrastructure prefix + your `xcm` appended. For an **ERC20 to Parachain with Ether fees**:

```
[BridgeHub prepends]
 1. DescendOrigin(PalletInstance(91))
 2. UniversalOrigin(Ethereum { chain_id })
 3. ReserveAssetDeposited([ETH executionFee])
 4. SetHints { assetClaimer: claimer }
 5. PayFees { ETH executionFee }
 6. ReserveAssetDeposited([ETH remaining value])
 7. ReserveAssetDeposited([ERC20 tokenAmount])
 8. DescendOrigin(AccountKey20 { msg.sender })

[Your xcm parameter]
 9. InitiateTransfer { destination, remote_fees, assets, remoteXcm }
10. RefundSurplus
11. DepositAsset { leftover ether -> beneficiary }
12. SetTopic
```

This full form is what the `buildAssetHubERC20ReceivedXcm` functions construct for dry-run validation purposes.

#### Ethereum to Polkadot Fees

The `DeliveryFee` returned by `getDeliveryFee` contains:

| Field                          | Description                                               |
| ------------------------------ | --------------------------------------------------------- |
| `assetHubDeliveryFeeEther`     | BridgeHub to AssetHub delivery fee (in Ether)             |
| `assetHubExecutionFeeEther`    | AssetHub XCM execution fee (in Ether)                     |
| `destinationDeliveryFeeEther`  | AssetHub to destination parachain delivery fee (in Ether) |
| `destinationExecutionFeeEther` | Destination execution fee (in Ether, if Ether fee path)   |
| `destinationExecutionFeeDOT`   | Destination execution fee (in DOT, if DOT fee path)       |
| `relayerFee`                   | Relayer incentive (in Ether)                              |
| `totalFeeInWei`                | Sum of all fees -- this is `msg.value` to send            |

The `totalFeeInWei` is split into the `v2_sendMessage` params as:

* `executionFee` = `assetHubExecutionFeeEther + destinationDeliveryFeeEther` (+ swap amount if DOT path)
* `relayerFee` = relayer incentive

All fees are padded by **33%** to account for weight estimation variance. Exchange rate swaps are padded by an additional **20%** slippage.

#### Using the TypeScript API (Ethereum to Polkadot)

```typescript
import {
    createApi,
    xcmBuilder,
} from "@snowbridge/api"
import { EthersEthereumProvider } from "@snowbridge/provider-ethers"
import { polkadot_mainnet } from "@snowbridge/registry"

const {
    chains: { ethereum, assetHub },
} = polkadot_mainnet
const api = createApi({ info: polkadot_mainnet, ethereumProvider: new EthersEthereumProvider() })

// 1. Create sender
const sender = api.sender(ethereum, assetHub)

// 2. Build the validated transfer
const transfer = await sender.build(
    ETHEREUM_SENDER,
    POLKADOT_BENEFICIARY,
    TOKEN_ADDRESS,
    amount,
    {
        fee: {
            feeAsset: { parents: 1, interior: "Here" }, // DOT location, omit for Ether fees
        },
    },
)
```

The returned `transfer.tx` can then be submitted to the wallet by your application.

***

### 2. Polkadot to Ethereum

V2 example script: <https://github.com/Snowfork/snowbridge/blob/main/web/packages/operations/src/transfer_to_ethereum_v2.ts>

#### V1: `transfer_assets_using_reserve_type_and_then`

In V1, transfers from Polkadot to Ethereum use the `polkadotXcm.transfer_assets_using_reserve_type_and_then` extrinsic. The runtime constructs the XCM for you based on the provided parameters.

#### V2: XCM Execute

In V2, the transfer is done via `polkadotXcm.execute` with a manually constructed XCM program. The XCM withdraws the token and fees, then uses `initiateTransfer` to route through AssetHub to the Ethereum bridge.

#### XCM Location Constants

```typescript
// DOT location
const DOT_LOCATION = { parents: 1, interior: "Here" }

// Bridge/Ethereum location (also the Ether location)
const BRIDGE_LOCATION = {
    parents: 2,
    interior: { x1: [{ GlobalConsensus: { Ethereum: { chain_id: ETH_CHAIN_ID } } }] }
}

// ERC20 token location
const ERC20_LOCATION = {
    parents: 2,
    interior: {
        X2: [
            { GlobalConsensus: { Ethereum: { chain_id: ETH_CHAIN_ID } } },
            { AccountKey20: { key: TOKEN_ADDRESS } }
        ]
    }
}
```

#### ERC20 from Parachain (with Ether on source chain)

When the source parachain holds Ether (e.g. via the Snowbridge Ether foreign asset), the XCM uses Ether directly for the Ethereum execution fee:

```
v5: [
    // 1. Withdraw all needed assets on the source parachain
    { withdrawAsset: [
        { id: DOT_LOCATION, fun: { Fungible: totalDOTFee } },
        { id: BRIDGE_LOCATION, fun: { Fungible: ethereumExecutionFee } },
        { id: ERC20_TOKEN_LOCATION, fun: { Fungible: tokenAmount } }
    ]},

    // 2. Pay local parachain execution fee in DOT
    { payFees: { asset: { id: DOT_LOCATION, fun: { Fungible: localDOTFee } } } },

    // 3. Error recovery on source chain: return assets to sender on failure
    { setAppendix: [
        { refundSurplus: null },
        { depositAsset: {
            assets: { wild: { allCounted: 3 } },
            beneficiary: { parents: 0, interior: { x1: [SENDER_LOCATION] } }
        }}
    ]},

    // 4. Forward everything to AssetHub
    { initiateTransfer: {
        destination: { parents: 1, interior: { x1: [{ parachain: ASSET_HUB_PARA_ID }] } },
        remote_fees: {
            reserveWithdraw: {
                definite: [{ id: DOT_LOCATION, fun: { Fungible: totalDOTFee - localDOTFee } }]
            }
        },
        preserveOrigin: true,
        assets: [
            // Ether for Ethereum execution fee
            { reserveWithdraw: {
                definite: [{ id: BRIDGE_LOCATION, fun: { Fungible: ethereumExecutionFee } }]
            }},
            // The ERC20 token
            { reserveWithdraw: {
                definite: [{ id: ERC20_TOKEN_LOCATION, fun: { Fungible: tokenAmount } }]
            }}
        ],
        // XCM to execute on AssetHub:
        remoteXcm: [
            // Error recovery on AssetHub
            { setAppendix: APPENDIX_INSTRUCTIONS },

            // Forward from AssetHub to Ethereum
            { initiateTransfer: {
                destination: BRIDGE_LOCATION,
                remote_fees: {
                    reserveWithdraw: {
                        definite: [{ id: BRIDGE_LOCATION, fun: { Fungible: ethereumExecutionFee } }]
                    }
                },
                preserveOrigin: true,
                assets: [{
                    reserveWithdraw: {
                        definite: [{ id: ERC20_TOKEN_LOCATION, fun: { Fungible: tokenAmount } }]
                    }
                }],
                remoteXcm: [
                    { depositAsset: {
                        assets: { wild: { allCounted: 3 } },
                        beneficiary: { parents: 0, interior: { x1: [{ AccountKey20: { key: ETH_BENEFICIARY } }] } }
                    }},
                    { setTopic: TOPIC }
                ]
            }},
            { setTopic: TOPIC }
        ]
    }},
    { setTopic: TOPIC }
]
```

#### ERC20 from Parachain (DOT-only fee, no Ether on source)

If your parachain does not hold Ether, you can pay the Ethereum execution fee in DOT by adding an `exchangeAsset` on AssetHub to swap DOT for Ether:

```
v5: [
    // 1. Withdraw DOT and the token (no Ether needed on source chain)
    { withdrawAsset: [
        { id: DOT_LOCATION, fun: { Fungible: totalDOTFee } },
        { id: ERC20_TOKEN_LOCATION, fun: { Fungible: tokenAmount } }
    ]},

    // 2. Pay local fee in DOT
    { payFees: { asset: { id: DOT_LOCATION, fun: { Fungible: localDOTFee } } } },

    // 3. Error recovery on source chain
    { setAppendix: [
        { refundSurplus: null },
        { depositAsset: {
            assets: { wild: { allCounted: 3 } },
            beneficiary: { parents: 0, interior: { x1: [SENDER_LOCATION] } }
        }}
    ]},

    // 4. Forward to AssetHub
    { initiateTransfer: {
        destination: { parents: 1, interior: { x1: [{ parachain: ASSET_HUB_PARA_ID }] } },
        remote_fees: {
            reserveWithdraw: {
                definite: [{
                    id: DOT_LOCATION,
                    fun: { Fungible: totalDOTFee - localDOTFee - ethereumExecutionFeeInDOT }
                }]
            }
        },
        preserveOrigin: true,
        assets: [
            // DOT that will be swapped to Ether on AssetHub
            { reserveWithdraw: {
                definite: [{ id: DOT_LOCATION, fun: { Fungible: ethereumExecutionFeeInDOT } }]
            }},
            // The ERC20 token
            { reserveWithdraw: {
                definite: [{ id: ERC20_TOKEN_LOCATION, fun: { Fungible: tokenAmount } }]
            }}
        ],
        // XCM to execute on AssetHub:
        remoteXcm: [
            // Error recovery on AssetHub
            { setAppendix: APPENDIX_INSTRUCTIONS },

            // Swap DOT for Ether on AssetHub
            { exchangeAsset: {
                give: { Wild: { AllOf: { id: DOT_LOCATION, fun: "Fungible" } } },
                want: [{ id: BRIDGE_LOCATION, fun: { Fungible: ethereumExecutionFee } }],
                maximal: false
            }},

            // Forward to Ethereum with Ether fee
            { initiateTransfer: {
                destination: BRIDGE_LOCATION,
                remote_fees: {
                    reserveWithdraw: {
                        definite: [{ id: BRIDGE_LOCATION, fun: { Fungible: ethereumExecutionFee } }]
                    }
                },
                preserveOrigin: true,
                assets: [{
                    reserveWithdraw: {
                        definite: [{ id: ERC20_TOKEN_LOCATION, fun: { Fungible: tokenAmount } }]
                    }
                }],
                remoteXcm: [
                    { depositAsset: {
                        assets: { wild: { allCounted: 3 } },
                        beneficiary: { parents: 0, interior: { x1: [{ AccountKey20: { key: ETH_BENEFICIARY } }] } }
                    }},
                    { setTopic: TOPIC }
                ]
            }},
            { setTopic: TOPIC }
        ]
    }},
    { setTopic: TOPIC }
]
```

#### PNA (Polkadot Native Asset) from Parachain

For PNA transfers, the differences from ERC20 are:

* The token uses `teleport` instead of `reserveWithdraw` when moving from source to AssetHub
* On AssetHub, the token uses `reserveDeposit` when forwarding to Ethereum (since the PNA is held in reserve on AssetHub, deposited to the bridge)

Both Ether-fee and DOT-fee variants are available. See `web/packages/api/src/xcmbuilders/toEthereum/pnaFromParachain.ts` and `pnaFromParachainWithDotAsFee.ts` for the exact implementations.

#### Error Recovery (Appendix Instructions)

V2 uses `setAppendix` for error recovery. The appendix XCM runs if any subsequent instruction fails. On AssetHub, the appendix typically:

1. Sets an `assetClaimer` hint (so the sender can reclaim trapped assets)
2. Refunds surplus fees
3. Deposits remaining assets back to the sender's parachain account

```typescript
// Built by buildAppendixInstructions() in xcmBuilder.ts
[
    { setHints: { hints: [{ assetClaimer: { location: claimerLocation } }] } },
    { refundSurplus: null },
    { depositAsset: {
        assets: { wild: { allCounted: 3 } },
        beneficiary: claimerLocation ?? {
            parents: 1,
            interior: { x2: [{ parachain: sourceParaId }, senderAccountLocation] }
        }
    }}
]
```

#### Polkadot to Ethereum Fees

The `DeliveryFee` returned by `getDeliveryFee` contains:

| Field                           | Description                                                                                |
| ------------------------------- | ------------------------------------------------------------------------------------------ |
| `localExecutionFeeDOT`          | Source parachain XCM execution fee                                                         |
| `localDeliveryFeeDOT`           | Source to AssetHub delivery fee                                                            |
| `assetHubExecutionFeeDOT`       | AssetHub XCM execution fee                                                                 |
| `bridgeHubDeliveryFeeDOT`       | AssetHub to BridgeHub delivery fee                                                         |
| `snowbridgeDeliveryFeeDOT`      | Snowbridge protocol fee (governance-set, read from `:BridgeHubEthereumBaseFeeV2:` storage) |
| `returnToSenderExecutionFeeDOT` | Error recovery XCM execution fee                                                           |
| `returnToSenderDeliveryFeeDOT`  | Error recovery delivery fee                                                                |
| `totalFeeInDot`                 | Sum of all DOT fees                                                                        |
| `ethereumExecutionFee`          | Ethereum gas cost (in Ether)                                                               |
| `ethereumExecutionFeeInNative`  | Ethereum cost converted to fee token (if DOT or native fee path)                           |

All fees are padded by **33%**. Exchange rate swaps are padded by an additional **20%** slippage.

#### Using the TypeScript API (Polkadot to Ethereum)

```typescript
import {
    createApi,
    xcmBuilder,
} from "@snowbridge/api"
import { EthersEthereumProvider } from "@snowbridge/provider-ethers"
import { polkadot_mainnet } from "@snowbridge/registry"

const {
    chains: { assetHub, ethereum },
} = polkadot_mainnet
const api = createApi({ info: polkadot_mainnet, ethereumProvider: new EthersEthereumProvider() })

// 1. Create sender
const sender = api.sender(assetHub, ethereum)

// 2. Build the validated transfer
const transfer = await sender.build(
    POLKADOT_SENDER,
    ETHEREUM_BENEFICIARY,
    TOKEN_ADDRESS,
    amount,
    {
        fee: {
            feeTokenLocation: { parents: 1, interior: "Here" }, // DOT location
        },
    },
)
```

The returned `transfer.tx` can then be submitted to the wallet by your application.

***

### Key Differences Summary

| Aspect                   | V1                                                        | V2                                                          |
| ------------------------ | --------------------------------------------------------- | ----------------------------------------------------------- |
| **Ethereum entry point** | `sendToken(token, paraId, beneficiary, fee)`              | `v2_sendMessage(xcm, assets, claimer, execFee, relayerFee)` |
| **Polkadot extrinsic**   | `polkadotXcm.transfer_assets_using_reserve_type_and_then` | `polkadotXcm.execute` with custom XCM                       |
| **XCM version**          | v4                                                        | v5                                                          |
| **Fee instruction**      | `buyExecution`                                            | `payFees`                                                   |
| **Transfer instruction** | `depositReserveAsset` / `initiateReserveWithdraw`         | `initiateTransfer`                                          |
| **AH fee asset (E2P)**   | DOT                                                       | Ether (with optional DOT for destination)                   |
| **Error recovery**       | Limited                                                   | `setAppendix` with claimer, refund, and deposit back        |
| **Custom XCM**           | Not supported                                             | `customXcm` parameter for extra instructions at destination |
| **Fee splitting (E2P)**  | Single fee                                                | Separate `executionFee` and `relayerFee`                    |

### Reference Files

* **E2P XCM builders**: <https://github.com/Snowfork/snowbridge/tree/main/web/packages/api/src/xcmbuilders/toPolkadot/>
* **P2E XCM builders**: <https://github.com/Snowfork/snowbridge/tree/main/web/packages/api/src/xcmbuilders/toEthereum/>
* **E2P transfer implementations**: <https://github.com/Snowfork/snowbridge/tree/main/web/packages/api/src/transfers/toPolkadot>
* **P2E transfer implementations**: <https://github.com/Snowfork/snowbridge/tree/main/web/packages/api/src/transfers/toEthereum>
* **Gateway V2 Solidity interface**: <https://github.com/Snowfork/snowbridge/blob/main/contracts/src/v2/IGateway.sol>


# Bug Bounty

We have several bug bounty programs live on [HackenProof](https://hackenproof.com/).

* [Snowbridge On Chain code](https://hackenproof.com/programs/snowbridge-on-chain-code)
* [Snowbridge User Interface](https://hackenproof.com/programs/snowbridge-web)


# Audits

Snowbridge continually commissions incremental audits of the protocol implementation.

Our main auditor, [Oak Security](https://oaksecurity.io/), uses a [blinded](https://oaksecurity.io/security-audits) approach, which emphasises redundancy, wherein multiple auditors work independently and only reveal their findings in a final consensus meeting.

## CommonPrefix

#### 2023-11-17

Audit of BEEFY light client sampling protocol

{% file src="/files/8CqlVMGpFJiqjg2QRMbX" %}

## Oak Security

Auditor-hosted document archive: <https://github.com/oak-security/audit-reports/tree/main/Snowbridge>

#### 2025-05-29

Audit of new Snowbridge V2 functionality

{% file src="/files/wBznXfcaqaiTh6fDn6kC" %}

#### 2025-01-07

Incremental audit

{% file src="/files/sOl4epNso8ag7BiMHJGm" %}

#### 2024-08-16

Incremental audit

{% file src="/files/y3I4LgimbSOOVrQsrCSs" %}

#### 2024-08-10

Incremental audit

{% file src="/files/1oD3TI57QMcJ7Etfn4oT" %}

#### 2024-05-06

Incremental audit

{% file src="/files/3OJL1zqlicc4EKtQTsAY" %}

#### 2024-05-24

Finalized audit for the initial v1.1 codebase

{% file src="/files/CUR90J7AK9KjEWbSMhkK" %}

#### 2024-02-12

Auditing Team: [Oak Security](https://oaksecurity.io/)

{% file src="/files/VnhWks9GZexQVCjMxGAW" %}


# Overview

Snowbridge provides a secure point-to-point bridge between Ethereum and the Polkadot Hub.

Users on Ethereum interact with our Gateway contract to either send tokens or generalised messages. After having received them, the Polkadot Hub can execute them locally, or in turn relay them to other parachains on Polkadot.

<figure><img src="/files/O82HrMemGDXoZ9Xeh3rq" alt=""><figcaption></figcaption></figure>


# Verification

Message verification is delegated to trustless and decentralised on-chain light clients. We have developed our own light clients for Polkadot (BEEFY) and Ethereum (PoS) with support from the W3F and the Ethereum community.


# Ethereum

We have implemented a Proof-of-Stake (PoS) light client for the Beacon chain. This client deprecates the older PoW light client we developed in 2020.

The beacon client tracks the beacon chain, the new Ethereum chain that replaced the Ethereum's Proof-of-Work consensus method around on 15 September 2022, called the Merge. The work we have done consists of the following parts:

* Beacon Client pallet
  * Force checkpoint
  * Submit (finalized header & sync committee update)
  * Submit execution header
  * Message verification
* Beacon Relayer
  * Sends data from a beacon node to the beacon client

## Concepts

### Before the Merge: Execution Layer

Before the Merge, the Ethereum chain as we know it existed in isolation in the sense that consensus was determined by the same chain, using Proof-of-Work (POW).

<figure><img src="/files/pWqAbzBuoVGXCRZN2rtt" alt=""><figcaption><p>Ethereum Chain before the Merge</p></figcaption></figure>

### After the Merge: Consensus Layer

After the Merge, the Beacon chain became the sole manner in which consensus is tracked on Ethereum. The Beacon chain is a separate chain that was launched on 1 December 2020 and has been running independently since then. On 15 September 2022, the original Ethereum chain's POW consensus method was disabled and the chain switched over to the Beacon chain for consensus. The original Ethereum chain is now often referred to as the Execution Layer and the Beacon chain as the Consensus Layer.

<figure><img src="/files/llLlNMXIhw3foNtW6chr" alt=""><figcaption><p>Ethereum Chains after the Merge</p></figcaption></figure>

### **Snowbridge Beacon Client**

The Snowbridge beacon client is based on the [Altair Sync Protocol](https://github.com/ethereum/consensus-specs/blob/dev/specs/altair/light-client/sync-protocol.md) (often referred to as ALC - Altair Light Client). Although there has been [some criticism of the protocol](https://prestwich.substack.com/p/altair) and its security, the ALC protocol remains the best explored light client to track the Beacon chain with reasonable security. If you are interested in additional reading about the sync committee's security, please read [our analysis on the Polkadot Forum](https://forum.polkadot.network/t/snowforks-analysis-of-sync-committee-security/2712/8).

#### **Beacon Headers & Execution Headers**

The Snowbridge light client to track Ethereum consensus is implemented as an on-chain Beacon client, on the parachain. It is implemented as a Substrate pallet and the code can be found on Github under the [`ethereum-beacon-client` pallet](https://github.com/Snowfork/snowbridge/blob/main/parachain/pallets/ethereum-beacon-client/src/lib.rs).

The beacon client tracks finalized beacon blocks. The Beacon chain introduced finality to the chain (more on this later). Since it is vital that transfer messages are included in the canonical chain (and not in blocks that go through a re-org), the beacon client only tracks blocks that are ancestors of finalized beacon blocks.

In the diagram below, the purple blocks are examples of those stored in the beacon client. Only finalized beacon blocks are stored as checkpoints. Not all finalized beacon blocks need to be stored and skipping a finalized block is allowed, since these finalized blocks are merely used as checkpoints to indicate that all ancestors of such a block will be seen as finalized as well.

Beacon blocks and execution headers are linked through the `ExecutionPayload` field in a Beacon block. To verify messages, we are particularly interested in the `receiptsRoot` hash, which is used to verify the Ethereum message receipt containing the details about the transfer. For this reason, we store all the execution headers that are ancestors of a finalized beacon header.

<figure><img src="/files/E2mZrm0qUOgdaHe765Oi" alt=""><figcaption><p>Snowbridge storage (items in purple are stored on-chain)</p></figcaption></figure>

#### Sync Committees

Additionally, the beacon client also syncs sync committees. Sync committees are a subset of randomly chosen validators to sign blocks for a sync committee period (256 epochs, around 27 hours).

<figure><img src="/files/EWmhvjA24t3vJktZFghJ" alt=""><figcaption></figcaption></figure>

### Proofs

The Beacon client checks the following proofs before storing beacon headers and execution headers:

* Merkle proof of the beacon state root to verify if the supposedly finalized header is finalized
* BLS signature verification to assert that the sync committee signed the block attesting to the finalized header
* Ancestry proofs to verify that the imported execution header is indeed a valid ancestor of a finalized header (also merkle proofs).

Additionally, the sync committee and next sync committee is also verified using Merkle proofs, to verify if those sync committees are part of the beacon state.

## Beacon Client Operations

### **Force checkpoint**

This operation can only be executed by the root origin (on pallet initialization or by governance) and serves a starting point for syncing blocks.

The `force_checkpoint` payload contain:

* A beacon header (validated manually to ensure it is on the correct chain).
* The current sync committee plus a merkle proof branch to verify the sync committee.
* The validators root (the merkle root of all the validators that were present at genesis time - this is used to determine the correct chain).
* The block roots merkle root (the merkle root of the `blocks_root` field in the beacon state of the beacon header - used for ancestry proofs using the beacon header in this payload) plus the merkle branch roots to proof the blocks root merkle root against the beacon header state root.

### **Submit**

After the checkpoint has been validated, the beacon relayer periodically sends updates. These updates contain finalized headers and optionally, the next sync committee.

The `submit` update contains:

* An attested header: A recent header attesting to the finalized header in the update. This header is not finalized, but its `state_root` field is used to prove the `finalized_header` field in the same update. This header isn't stored (because we are not interested in headers that are not finalized), but only used for proofs.
* A sync aggregate: The signing information concerning the attested header (the sync committee signature and voting information regarding the attested header, to see if we can trust it)
* The signature slot: The slot at which the sync committee signature for the attested header can be found. This is typically `attested_header.slot + 1`, unless the next slot is a skipped slot, in which case it will be `attested_header.slot + 2`, and so forth until a block at the slot is present (some slots contain no blocks and is called a missed block slot)
* The next sync committee update (optional): If the next sync committee is known and has not be stored in the beacon light client, the relayer will send it. The sync committee subset of validators change every \~27 hours. The sync committee is verified using a Merkle proof and then stored in storage.
* The finalized header and its merkle proof: This serves as a checkpoint to know which execution headers can safely be imported which being in danger of a reorg. The finalized block root header is stored along with the slot number and block roots root.
* The block roots root and its proof, similar to the force checkpoint update.

### **Execution header updates**

Once there are more than 2 beacon finalized headers, all the execution headers between the two finalized beacon headers are backfilled. The execution header lives on the Ethereum execution layer (historically just the Ethereum chain). The execution header looks almost the same as it used to in the Ethereum PoW world. Each beacon header contains an ExecutionPayload header which is on the execution layer. A compacted version of the execution header is stored in storage in order to use the `receipts_root` field for message verification.

The `submit_execution_header` update contains:

* A header: The beacon header containing an execution header.
* An ancestry proof: The merkle proof branch to the block\_root in the beacon state pointing to this header, plus the finalized header root used to proof this ancestor block.
* The execution header of this beacon header.
* The merkle proof to prove that this execution header is in fact contained in the header provided.

### **Message verification**

The light client is also responsible for verifying incoming Ethereum events. It does so using transaction receipt proofs which prove that a particular transaction to a particular Ethereum smart contract was in fact valid, was included in the chain, and did emit some event. It accepts and processes a proof, verifies it and then returns the set of Ethereum events that were emitted by the proven transaction receipt.

## Implementation

Pallets:

* [ethereum-beacon-client](https://github.com/Snowfork/snowbridge/tree/main/parachain/pallets/ethereum-beacon-client)


# Polkadot

We use Polkadot’s [BEEFY](https://eprint.iacr.org/2025/057.pdf) protocol to implement an efficient light client that only needs to verify a very small subset of relay chain validator signatures. BEEFY is live on Rococo, and is awaiting deployment on Kusama and Polkadot.

Fundamentally, the BEEFY light client allows the bridge to prove that a specified parachain header was finalized by the relay chain.

We want a bridge design that is light enough to deploy on Ethereum. It will be too expensive to verify signatures from say 1000 validators of the Polkadot relay chain on Ethereum, so we basically have two choices: verify all signatures in succinct proofs or only verify a few signatures. We settled for a design that tries to make the latter cryptoeconomically secure.

The ideal security to aim for is for an attack to be as expensive as the smaller market cap of DOT and ETH. Unfortunately, we can only slash the bond of the few validators whose signatures are verified, so any attack attempt is necessarily much cheaper than the whole market cap. However, we can aim to make an attack very expensive in expectation by making sure that an attack succeeds with low probability and that failed attacks still cost the attackers.

## Update Protocol

The light client needs to be frequently updated with new BEEFY commitments by an untrusted permissionless set of relayers.

BEEFY commitments are signed by relay chain validators. The light client needs to verify these signatures before accepting commitments.

In collaboration with W3F, we have designed a protocol where the light client needs to only verify $$N$$ signatures samples from randomly chosen validators​. The choice of $$N$$ is done dynamically based on a few variables and is described [here](#signature-sampling).

In the EVM there is no cryptographically secure source of randomness. Instead, we make our update protocol crypto-economically secure through an interactive update protocol. In this protocol, a candidate commitment is verified over 3 transactions. At a high level it works like this:

1. `submitInitial` - In the first transaction, the relayer submits the commitment, a randomly selected validator signature, and an initial bitfield claiming which validators have signed the commitment.
2. The relayer must then wait [MAX\_SEED\_LOOKAHEAD](https://eth2book.info/bellatrix/part3/config/preset/#max_seed_lookahead) blocks.
3. `commitPrevRandao` - The relayer submits a second transaction to reveal and commit to a random seed, derived from Ethereum's [RANDAO](https://eips.ethereum.org/EIPS/eip-4399).
4. The relayer requests from the light client a bitfield with $$N$$randomly chosen validators sampled from the initial bitfield.​
5. `submitFinal` - The relayer sends a third and final transaction with signatures for all the validators specified in the final bitfield
6. The light client verifies all validator signatures in the third transaction to ensure:
   1. The provided validators are in the current validator set
   2. The provided validators are in the final bitfield
   3. The provided validators have signed the beefy commitment
7. If the third transaction succeeds then the payload inside the BEEFY commitment is applied

## Signature Sampling

The choice $$N$$ is described by the [formal analysis of signature sampling from W3F](https://eprint.iacr.org/2025/057.pdf). It consists of the following variables.

$$
N = \lceil log\_2(R \* V \* \frac{1}{S} \*(75+E)\*172.8)\rceil + 1 + 2 \lceil log\_2(C) \rceil
$$

1. $$V$$ - Validator set length.
2. $$C$$ - The number of times a validator's signature was previously used for `submitInitial` calls within a session. There is no limit to how many times `submitInitial` can be called except for its gas cost. This allows an adversary to spam this transaction in order to gain influence over the RANDAO provided they can pay for gas. The light client will track how many times a validator signature is used when calling `submitInitial` in a session and increase the number of validator signatures required to be verified when finalizing the commitment. This will make finalizing the commitment cost more gas and would require more validators to back dishonest claims and be slashed by the BEEFY protocol.
3. $$E$$ - RANDAO commit expiry. The number of blocks a relayer has to commit to a random seed based on RANDAO.
4. The ratio of the total supply of DOT to the minimum amount slashable. These are done using two heuristic variables.
   1. $$R$$ - The ratio of total stake per validator.
   2. $$S$$ - A slash rate which is the percentage of a validator's stake that can be slashed.
5. Constant $$75$$ is the number of slots that an adversary can use to influence RANDAO. See formal analysis for more details.
6. Constant $$172.8$$ is the expected number of choices an adversary has to influence the RANDAO based on Markov chain analysis by W3F. See formal analysis for more details.

From the list above 1 and 2 are known in the light client and can be calculated on-chain. Variables 3, 4.1, and 4.2 are not known by the light client and are instead calculated off-chain and set as a minimum number of required signatures during the initialization of the light client. This minimum is immutable for the life time of the light client.

## Slashing of BEEFY validators who produce equivocations

The BEEFY protocol includes a mechanism to slash validators who commit fraud. For example, a subset of active validators could maliciously sign a fraudulent commitment, and collude with a relayer to submit it to our light client. However, we have an equivocation fisherman that can detect such activity and submit [proof-of-equivocation](https://docs.rs/pallet-beefy/latest/pallet_beefy/struct.EquivocationOffence.html) back to the Polkadot relay chain, whereupon these validators will be slashed 50% of their stake.

## Message Verification

On our parachain, outbound channels periodically emit message commitment hashes which are inserted into the parachain header as a digest item. These commitment hashes are produced by hashing a set of messages submitted by end users.

To verify these commitment hashes, the light client side needs the following information

1. The full message bundle
2. Partial parachain header
3. A merkle leaf proof for the parachain header containing the commitment hash for (1)
4. An MMR leaf proof for the MMR leaf containing the merkle root for the merkle tree in (2)

Working backwards, if the BEEFY light client successfully verifies a parachain header, then the commitment hash within that header is also valid, and the messages mapping to that commitment hash can be safely dispatched.

## Implementation

Solidity Contracts:

* [BeefyClient.sol](https://github.com/Snowfork/snowbridge/blob/main/contracts/src/BeefyClient.sol)
* [Verification.sol](https://github.com/Snowfork/snowbridge/blob/main/contracts/src/Verification.sol)


# Operational Costs

To remain operational, the BEEFY light client must be updated with new BEEFY commitments. These commitments are emitted periodically by the relay chain, roughly every minute. A mandatory commitment is emitted at the start of every validator [session](https://wiki.polkadot.network/docs/maintain-polkadot-parameters#periods-of-common-actions-and-attributes) and must be provided to the light client.

It will be prohibitively expensive to submit updates every minute. So we envision that the rate of updates will be dynamic and influenced by user demand. Assuming current gas prices, the cost of operating the BEEFY client should be between $200,000 and $1,000,000 per year. For detailed calculations, see our [Cost Predictions](https://docs.google.com/spreadsheets/d/1QtxNtG4GE1IUaH204QFO6lObyAqLV9WCbmSYEopU18Q/edit?usp=sharing).

Our current implementation is not very optimized, as we have focused foremost on correctness and readability. However, we have identified several easy optimizations which can reduce the cost by at least 20% or more.


# Governance

As a system bridge for Polkadot, it is exclusively governed by Polkadot's [OpenGov](https://polkadot.com/opengov/) governance model.

This promotes decentralisation in the following ways:

* No power is vested in centralised collectives or multisig accounts
* Snowfork and its employees have no control over the bridge and its locked-up collateral
* Anyone can participate in governance and vote on proposals.

## Cross-chain Governance

Our bridge has contracts on the Ethereum side, and these contracts need to be able to evolve along with the parachain side. Cross-chain governance will control both configuration and code upgrades on the Ethereum side.

As a prime example, Polkadot and BEEFY consensus algorithms will change, and so we need to make sure the Ethereum side of the bridge remains compatible. Otherwise locked up collateral will not be redeemable.

Smart contract upgrades and configuration changes are triggered by Polkadot governance through the use of cross-chain messaging secured by the bridge itself.

## Upgrades

The Polkadot side of our bridge is easily upgradable using forkless runtime upgrades. On the Ethereum side, it is more complicated, since smart contracts are immutable.

The gateway contract on Ethereum consists of a proxy and an implementation contract. Polkadot governance can send a cross-chain message to the Gateway, instructing it to upgrade to a new implementation contract.


# Relayers

{% hint style="success" %}
Relayers are **permissionless and trustless**. This means that anyone can operate a relayer for channels they are interested in.
{% endhint %}

A relay is a piece of software running offchain that watches two blockchains and relays messages across them. The implementation of the relayer in our bridge is not part of the core protocol, as it is offchain and so is untrusted. Of course, some relayer still needs to be running in order for the bridge to function, but it only needs to conform to the protocol defined by on-chain requirements.

We provide relayer software that will be run by incentivized relayers to keep the bridge active, but the design and implementation of the relayer are not relevant for understanding the trustless bridge protocol.

## Polkadot->Ethereum

### BEEFY relay

Relays signed BEEFY commitments and proofs from a Polkadot relay chain to the BEEFY light client contract on Ethereum.

### Message relay

Relays message commitments and proofs from BridgeHub to inbound channel contracts on Ethereum

## Ethereum->Polkadot

### Header relay

Relays the following objects to the Ethereum light client pallet on the BridgeHub parachain:

* Beacon chain headers
* Execution chain headers
* Sync Committees

### Message relay

Relays messages emitted by outbound channel contracts on Ethereum.


# Infrastructure

## Contracts

| Contract    | Address                                                                                                               |
| ----------- | --------------------------------------------------------------------------------------------------------------------- |
| BeefyClient | [0x6ed05baa904df3de117ecfa638d4cb84e1b8a00c](https://etherscan.io/address/0x6ed05baa904df3de117ecfa638d4cb84e1b8a00c) |
| Gateway     | [0x27ca963c279c93801941e1eb8799c23f407d68e7](https://etherscan.io/address/0x27ca963c279c93801941e1eb8799c23f407d68e7) |

## Accounts

### Consensus Relayers

<table><thead><tr><th width="184">Name</th><th width="118">Network</th><th>Address</th></tr></thead><tbody><tr><td>BEEFY Relay</td><td>Ethereum</td><td><a href="https://etherscan.io/address/0xb8124b07467e46de73eb5c73a7b1e03863f18062">0xb8124b07467e46de73eb5c73a7b1e03863f18062</a></td></tr><tr><td>Beacon Relay</td><td>BridgeHub</td><td><a href="https://bridgehub-polkadot.subscan.io/account/16DWunYRv2q29SMxqgrPGhob5az332hhLggSj2Rysk3g1rvk">16DWunYRv2q29SMxqgrPGhob5az332hhLggSj2Rysk3g1rvk</a></td></tr></tbody></table>

### Message Relayers

<table><thead><tr><th width="262">Name</th><th>Network</th><th>Address</th></tr></thead><tbody><tr><td>Governance Relay</td><td>Ethereum</td><td><a href="https://etherscan.io/address/0x0f51678Ac675C1abf2BeC1DAC9cA701cFcfFF5E2">0x0f51678Ac675C1abf2BeC1DAC9cA701cFcfFF5E2</a></td></tr><tr><td>AssetHub Parachain Relay</td><td>Ethereum</td><td><a href="https://etherscan.io/address/0x1F1819C3C68F9533adbB8E51C8E8428a818D693E">0x1F1819C3C68F9533adbB8E51C8E8428a818D693E</a></td></tr><tr><td>AssetHub Ethereum Relay</td><td>BridgeHub</td><td><a href="https://bridgehub-polkadot.subscan.io/account/13Dbqvh6nLCRckyfsBr8wEJzxbi34KELwdYQFKKchN4NedGh">13Dbqvh6nLCRckyfsBr8wEJzxbi34KELwdYQFKKchN4NedGh</a></td></tr></tbody></table>

### Sovereign Accounts

| Name               | Network   | Address                                                                                                                                            |
| ------------------ | --------- | -------------------------------------------------------------------------------------------------------------------------------------------------- |
| AssetHub Sovereign | Ethereum  | [0xd803472c47a87D7B63E888DE53f03B4191B846a8](https://etherscan.io/address/0xd803472c47a87d7b63e888de53f03b4191b846a8)                              |
| AssetHub Sovereign | BridgeHub | [13cKp89SgdtqUngo2WiEijPrQWdHFhzYZLf2TJePKRvExk7o](https://bridgehub-polkadot.subscan.io/account/13cKp89SgdtqUngo2WiEijPrQWdHFhzYZLf2TJePKRvExk7o) |
|                    |           |                                                                                                                                                    |


# Processes for keeping track of dependency changes

Methods to keep up to date with Snowbridge dependency updates.

## Polkadot Changes

The [polkadot-sdk](https://github.com/paritytech/polkadot-sdk) is ultimately the place where any changes in the following components will be made. Snowbridge has several dependencies on components in the [polkadot-sdk](https://github.com/paritytech/polkadot-sdk):

* Polkadot's relay chain
* Bridge Hub parachain
* Substrate
* Cumulus

### Releases

The best way to get notifications about any changes in the polkadot-sdk, is to watch releases. See the last section on how to turn on notifications for [polkadot-sdk releases](https://github.com/paritytech/polkadot-sdk/releases).

### Polkadot-SDK tests

Snowbridge has a set of tests that test common bridge functionality, like register and sending a token from Ethereum to Polkadot, and to send the token back from Polkadot to Ethereum. These tests run on the polkadot-sdk CI, and so any incompatibility stemming from changes made in the polkadot-sdk will be caught by failing tests. These tests use the Rococo bridge hub and asset hub runtimes. It tests runtime configurations and Snowbridge pallets. The developer making the incompatible or breaking changes is responsible for making the fix as well. Noteworthy tests are:

* [Snowbridge emulated tests](https://github.com/paritytech/polkadot-sdk/blob/master/cumulus/parachains/integration-tests/emulated/tests/bridges/bridge-hub-rococo/src/tests/snowbridge.rs)
* [Snowbridge runtime tests](https://github.com/paritytech/polkadot-sdk/blob/master/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-rococo/tests/snowbridge.rs)

### Smoke Tests

Apart from unit and emulated tests in the polkadot-sdk, Snowbridge also has a set of smoke tests, which uses a local testnet with a local Polkadot relay chain, asset hub and bridge hub parachains and Ethereum nodes. These smoke tests ultimately catch changes in the polkadot-sdk that may not have been caught by the polkadot-sdk tests.

These tests are run when a PR is merged into the polkadot-sdk repo - *<mark style="color:purple;">TODO we need to set this up</mark>*.

### Updating Snowfork Polkadot-SDK Fork

Since the Snowbridge team primarily works on a [fork of the polkadot-sdk](https://github.com/Snowfork/polkadot-sdk), the fork periodically needs to be updated from the [original repository](https://github.com/paritytech/polkadot-sdk). Pulling the latest code from paritytech/polkadot-sdk:master into snowfork/polkadot-sdk:snowbridge (the “snowbridge” branch is like our “main” branch) is another way to become aware of any changes. This update should be done bi-monthly, at the very least.

## Ethereum Changes

### Light Client Protocol

Ethereum consensus protocol changes are tracked on <https://github.com/ethereum/consensus-specs>. Similarly to the polkadot-sdk, the release page should be watched for releases. Apart from this, on the main readme page of the Ethereum consensus repo page, any change stating “Light client sync protocol changes” should be followed:

### Ethereum Network Updates

Ethereum network updates are available at <https://blog.ethereum.org/category/protocol>. At the bottom of the page is an email subscribe form signup. Sign up to get email notifications about protocol changes.

### Lodestar

Snowbridge uses Lodestar as consensus node to relay headers to the on-chain light client. Lodestar should be kept up to date to support the latest Ethereum fork. Lodestar’s releases can be followed here:

[https://github.com/ChainSafe/lodestar/releases/](https://github.com/ChainSafe/lodestar/releases/tag/v1.15.0)

The release notes often contain the relevancy for a certain update, e.g. for v1.15.0: “This update is recommended to all users of Lodestar and mandatory for those running Sepolia and Holesky testnets. This release is also ready for the Gnosis Chain Chiado fork.”

## Enable Notifications for Releases on Github

To get notifications for Github repository releases, go to the Github repository, click on Watch -> Custom -> Releases.

Recommended repository releases to watch:

* [ethereum/consensus-specs](https://github.com/ethereum/consensus-specs)
* [ChainSafe/lodestar](https://github.com/ChainSafe/lodestar/releases/tag/v1.15.0)
* [paritytech/polkadot-sdk](https://github.com/paritytech/polkadot-sdk)

<figure><img src="https://lh7-us.googleusercontent.com/MsdyEhct1vKgHCgXWiFDvLg5DJ7CFPjSg52LpNNpATjmf0tzubFAI3Ti6nsAP2N5Rr8TdKlOnpmohObMXO9FJB6FFtSB2mqJ-Xdytq_BFxyTltpCxjex1PPJ793bXEqMbH7j5MlcjcB2zO1LAy_x2FI" alt=""><figcaption><p>Enable release notifications on Github</p></figcaption></figure>

## Update Notifications Checklist

* [ ] [ethereum/consensus-specs](https://github.com/ethereum/consensus-specs) Github releases
* [ ] [ChainSafe/lodestar](https://github.com/ChainSafe/lodestar/releases/tag/v1.15.0) Github releases
* [ ] [paritytech/polkadot-sdk](https://github.com/paritytech/polkadot-sdk) Github releases
* [ ] [Ethereum network update email notifications](https://blog.ethereum.org/category/protocol)


# Contributing to Snowbridge

Processes for making changes to the Snowbridge pallets and runtimes for BridgeHub and AssetHub

## Writing New Code

Any new code will be added to the Snowfork repositories:

* <https://github.com/snowfork/snowbridge> - For adding/modifying contracts, off-chain relayer code, test net setup scripts and smoke tests.
* <https://github.com/Snowfork/polkadot-sdk> - For parachain and pallet changes.

## Merging New Code

#### Internal Snowfork Review

For both repositories mentioned above, pull requests (PR) should be made to the respective main branches. The Snowfork team members will review. Once the PR has been reviewed by 1 of more team members and all Github Actions pass, the pull request should be merged.

#### Parity Review

For any changes made to the [Snowfork/polkadot-sdk](https://github.com/Snowfork/polkadot-sdk), these changes should be contributed back to the original repository, [paritytech/polkadot-sdk](https://github.com/paritytech/polkadot-sdk).

To create an upstream pull request, do the following steps:

1. Check out the <https://github.com/Snowfork/polkadot-sdk> repository
2. Switch to the branch you would like to contribute upstream
3. Run \`./bridges/snowbridge/scripts/contribute-upstream.sh my-changes\`, where \`my-changes\` is the name of the new branch that will be created with your changes. The reason why this script creates a new branch is because we replaced Parity’s CI with our own, and so we need to clean up the changes that we have made to contribute the code back upstream. A new branch is created so it does not affect our CI and local development processes, but cleans the code so not to make irrelevant changes in the upstream PR.
4. Open the pull request on [paritytech/polkadot-sdk](https://github.com/paritytech/polkadot-sdk).
5. If the change is a minor change that doesn’t require release notes or greater awareness in Parity, ask on the PR in a comment for label R0-silent to be added to the PR. If the change is a larger change that requires awareness, add a file called \`pr\_xxx.prdoc\` in the \`prdoc\` directory, where xxx is the PR number. Describe the changes in the prdoc file (look at examples in that directory - it is fairly straightforward).
6. Usually, the Parity bridges team will review the PR within a day or two, without needing to prompt. For urgent reviews, post the link to the PR in the [Builders <> Snowfork Matrix Room](https://matrix.to/#/!gxqZwOyvhLstCgPJHO:matrix.parity.io?via=matrix.parity.io\&via=parity.io\&via=matrix.org) Builders <> Snowfork Matrix Room, asking for reviews.
7. If the change needs to be deployed to Rococo immediately (outside a regular release cycle), also update the relevant runtime spec version. This is typically the [BridgeHub](https://github.com/Snowfork/polkadot-sdk/blob/snowbridge/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-rococo/src/lib.rs#L206) or AssetHub runtime spec version. This spec version will automatically be incremented by Parity for release cycles.

### Crate Updates on crates.io

As part of the paritytech/polkadot-sdk release cycle, crates are published on crates.io. No extra action is required from the snowfork team to publish the Snowbridge crates. The crates are published by [parity-crate-owner](https://crates.io/users/parity-crate-owner).

### Auditing

Snowbridge pallets should be audited before releasing to Kusama and Polkadot. Audits should ideally be anticipated at least a month or two in advance, so that auditors can be engaged and booked in time. Since the overall codebase was audited, incremental audits will typically run for a week or less, with a week or two to address the findings, if necessary.

Audit fixes are usually done on a branch, so as not to interfere with other new features being built and to allow the auditors to easily verify fixes.

### Rococo Runtime Upgrade & Deployment Processes

Rococo deployments are done after the polkadot-sdk release. If out-of-cycle deployments need to be done, they can be arranged in [Chain Infrastructure: Rococo DevOps](https://matrix.to/#/!DiRwwDQntOGihlVwNO:parity.io?via=parity.io\&via=web3.foundation\&via=matrix.org).

### Polkadot

#### Runtime Upgrade

Once a new version of the polkadot-sdk is released, the polkadot-sdk crates should be updated in a PR to the fellowship runtimes repository. An example of such a PR is [Upgrade to latest polkadot-sdk@1.5 release #137](https://github.com/polkadot-fellows/runtimes/pull/137). Parity usually handles this and will push the release forward from the Fellows runtime PR to the execution of the upgrade on Polkadot.

#### Deployment

To deploy the change, extrinisc `parachainSystem.authorizeUpgrade` is called.

#### Voting

A proposal to upgrade the runtime is created and can be viewed on Polkassembly (e.g. <https://kusama.polkassembly.io/referendum/244>)

#### Execution

Once the referendum receives enough votes, `parachainSystem.enactAuthorizedUpgrade` can be executed to enact the upgrade.

The above steps are handled by Parity devs.


# Governance and Operational Processes

## Introduction

The purpose of this document is to outline the governance structure and operational processes for Snowbridge. We aim to ensure that members of the fellowship understand and are comfortable with the proposed model, in case of an emergency, for the interventions to be whitelisted by the fellowship.

## Cross-chain Governance

Snowbridge is a common-good project, and its governance, including both configuration and code upgrades on the Ethereum side, will be exclusively managed by Polkadot's cross-chain governance system, secured by the bridge itself. This governance structure promotes decentralisation by:

1. Ensuring no power is vested in centralised collectives or multisig accounts.
2. Preventing Snowfork and its employees from having any control over the bridge or its locked-up collateral.
3. Allowing anyone, from regular users to elected members of the Polkadot fellowship, to participate in governance.

Polkadot's governance will oversee and trigger smart contract upgrades and configuration changes through cross-chain messaging, ensuring that the Ethereum side remains compatible with changes in Polkadot and BEEFY consensus algorithms.

## Governance API

The following calls are essential controls to maintain and operate the bridge effectively, and they must be initiated by the root origin via a suitable governance track, such as a whitelisted caller

* [upgrade](https://github.com/Snowfork/snowbridge/blob/c2142e41b5a2cbd3749a5fd8f22a95abf2b923d9/parachain/pallets/system/src/lib.rs#L304) - Upgrade the gateway contract
* [set\_operating\_mode](https://github.com/Snowfork/snowbridge/blob/c2142e41b5a2cbd3749a5fd8f22a95abf2b923d9/parachain/pallets/system/src/lib.rs#L332) - Set the operating mode of the gateway contract
* [set\_pricing\_parameters](https://github.com/Snowfork/snowbridge/blob/c2142e41b5a2cbd3749a5fd8f22a95abf2b923d9/parachain/pallets/system/src/lib.rs#L349) - Set fee/reward parameters

## Non-emergency Upgrades

We expect to need non-emergency governance calls once every few months as we improve the bridge and add new functionality. Fast ratification won't be as important for these calls, and they will be audited with public code to ensure transparency and security.

## Emergency Situations

Emergency response (halt-bridge and emergency-upgrade procedures) is documented in [Emergency Procedures](/resources/emergency-procedures). On-call operators should read that page directly.

## Fallback governance

The Polkadot side of our bridge can be easily upgraded using forkless runtime upgrades. The process is more complex on the Ethereum side. The gateway contract on Ethereum consists of a proxy and an implementation contract. Polkadot governance can send a cross-chain message to the gateway, instructing it to upgrade to a new implementation contract.

For any emergencies that can be handled via Polkadot governance, the team aims to use a **Whitelisted Caller Track to fix any bugs**. This will allow the bridge to be updated in a speedy manner with the authorisation of Polkadot Fellowship (as both support and approval thresholds are lower than Root track) - we aim for the Fellowship members to ratify the use of Whitelisted Caller track for any emergency situation with Snowbridge: always taking into account an analysis on a case-by-case basis linked to each submission.

On the Ethereum side, the design intentionally avoids fallback / backdoor governance mechanisms to maintain the bridge's integrity and security. Although there are early-stage ideas for fallback governance that don't involve backdoors, they are not likely to be implemented short term.


# Emergency Procedures

On-call runbook. Halt mechanics are at the top so they're easy to reach under pressure. For routine governance, see [Governance and Operational Processes](/resources/governance-and-operational-processes).

Halt is technically reversible but the halt referendum on Polkassembly is public. See [Decision authority](#decision-authority) for when solo action vs team confirmation applies.

## Producing the preimage and submission links

The governance page at [app.snowbridge.network/governance](https://app.snowbridge.network/governance) is the single source of truth during an incident. Select the halt scope (see [Halt scopes reference](#halt-scopes-reference)) and the page emits both the preimage **and** the two ready-to-submit papi.how links. Take the links straight to [Submitting](#submitting).

**Fallback (UI down only)**: call `buildHaltBridgePreimage` then `buildHaltBridgeSubmissionUrls` from `@snowbridge/api`. Produces the same preimage + URLs the UI shows.

## Halt scopes reference

Pick the narrowest scope that covers the failure mode. Governance page form fields:

* **All** Every component. Default if nothing else selected.
* **Ethereum client** Halts `EthereumBeaconClient::submit` and short-circuits `Verifier::verify` for all BridgeHub consumers. Stops V1 + V2 inbound `submit` and `outbound-queue-v2::submit_delivery_receipt`. Use for beacon-light-client or sync-committee compromise. `force_checkpoint` stays available (root-only) for recovery.
* **Inbound queue** Both V1 + V2 inbound pallets on BridgeHub.
* **Inbound queue V1** V1 inbound only.
* **Inbound queue V2** V2 inbound only.
* **Outbound queue** V1 outbound on BridgeHub **and** AssetHub system-frontend (short-circuits `PausableExporter` for V1 + V2 at XcmRouter). V2 has no local outbound halt, so system-frontend is the primary V2 outbound lever.
* **System frontend** AssetHub system-frontend only. Blocks V1 + V2 P→E at `PausableExporter` (`SendError::NotApplicable`). V1 BridgeHub outbound keeps draining in-flight messages.
* **Gateway** Sends `Command::SetOperatingMode(Halted)` to the Ethereum Gateway via both V1 + V2 system pallets. Delivery is relayer-dependent, so schedule **before** local outbound halts.
* **Gateway V2** V2-only Gateway halt. Blocks `v2_sendMessage` and `v2_registerToken` once delivered. Pair with **Inbound queue V2** + **AssetHub max fee V2** for a V2-only pause.
* **AssetHub max fee** Sets `BridgeHubEthereumBaseFee` + `BridgeHubEthereumBaseFeeV2` to `u128::MAX`. Fee deterrent, not a router halt.
* **AssetHub max fee V2** V2-only variant. Writes only `BridgeHubEthereumBaseFeeV2`. Only V2-isolated P→E lever.

| Failure mode                                    | Scopes                                                          |
| ----------------------------------------------- | --------------------------------------------------------------- |
| Beacon light client / sync committee compromise | **Ethereum client**                                             |
| Ethereum Gateway compromise                     | **Gateway** + **AssetHub max fee**                              |
| Inbound-queue bug (one version)                 | **Inbound queue V1** or **Inbound queue V2**                    |
| Outbound-queue / system-frontend bug            | **Outbound queue**                                              |
| V2 P→E only (V1 keeps flowing)                  | **AssetHub max fee V2** (fee deterrent only)                    |
| Full V2 pause                                   | **Gateway V2** + **Inbound queue V2** + **AssetHub max fee V2** |
| Full P→E halt (V1 + V2)                         | **Outbound queue** or **System frontend**                       |
| Uncertain                                       | **All**                                                         |

When uncertain: **All**. To block both directions immediately: **Gateway** + **AssetHub max fee**.

## Submitting

Submission goes through OpenGov's **Whitelisted Caller** track, which requires the Polkadot Fellowship to whitelist the call first. From the governance page's result panel, two papi.how links handle this end-to-end:

1. **Asset Hub batch** Click **Open**. Notes the preimage and opens the public Whitelisted Caller referendum on Asset Hub. Anyone on the team can submit. Sign in papi.how.
2. **Fellowship whitelist** Click **Copy** and share the link in the Polkadot Fellowship Element channel (see [Comms](#comms-during-an-incident)). Must be submitted by a Fellow of rank 3 or higher. Bottleneck of the flow.

Enactment defaults to `After(10)` blocks (matches opengov-cli's default).

**Wall-clock: hours, not minutes.** Run Polkadot Fellowship escalation in parallel with the Asset Hub submission.

### Fallback: opengov-cli

If the governance page is unreachable, construct the same submission locally with [opengov-cli](https://github.com/joepetrowski/opengov-cli) and the preimage bytes (which the SDK fallback in [Producing](#producing-the-preimage-and-submission-links) can still generate offline):

{% code overflow="wrap" %}

```
opengov-cli submit-referendum \
    --proposal 0x<preimage-bytes> \
    --network polkadot \
    --track whitelistedcaller \
    --output AppsUiLink
```

{% endcode %}

Emits the same two papi.how URLs the UI shows. Use only when the UI is down; the UI is the single source of truth the team drives from during an incident.

## Verifying the halt

After the call executes, query each affected chain's `OperatingMode` storage (expected: `Halted`).

| Halt scope                                          | Chain     | Storage                                                                                                              |
| --------------------------------------------------- | --------- | -------------------------------------------------------------------------------------------------------------------- |
| **Ethereum client**                                 | BridgeHub | `ethereumBeaconClient.operatingMode`                                                                                 |
| **Inbound queue V1**                                | BridgeHub | `ethereumInboundQueue.operatingMode`                                                                                 |
| **Inbound queue V2**                                | BridgeHub | `ethereumInboundQueueV2.operatingMode`                                                                               |
| **Outbound queue** (BridgeHub)                      | BridgeHub | `ethereumOutboundQueue.operatingMode`                                                                                |
| **Outbound queue** / **System frontend** (AssetHub) | AssetHub  | `systemFrontend.operatingMode`                                                                                       |
| **Gateway** (BridgeHub side)                        | BridgeHub | `ethereumSystem.operatingMode`, `ethereumSystemV2.operatingMode`                                                     |
| **Gateway** (Ethereum contract)                     | Ethereum  | `Gateway.operatingMode() == Halted`. **Relayer-dependent**: watch for `SetOperatingMode` event before confirming.    |
| **AssetHub max fee**                                | AssetHub  | `bridgeHubEthereumBaseFee` + `bridgeHubEthereumBaseFeeV2` == `u128::MAX` (`340282366920938463463374607431768211455`) |
| **AssetHub max fee V2**                             | AssetHub  | `bridgeHubEthereumBaseFeeV2` == `u128::MAX`                                                                          |

Polkadot-side halt is the firm guarantee. If Gateway isn't halted yet (no relayer delivery), `sendToken`/`sendMessage` on Ethereum still accept calls but nothing downstream processes them.

## Detection

Triggers for the incident flow:

* **Funds drained or unexpectedly moved**. Highest priority. Halt first, investigate after.
* **Bug bounty report** (HackenProof or direct), verified by a team member as a valid exploit with working PoC.

When in doubt: post in Slack, treat as incident until ruled out.

## Decision authority

| Action                          | Threshold                                                                                 |
| ------------------------------- | ----------------------------------------------------------------------------------------- |
| Solo halt                       | 1 member. **Only for visible exploit / funds being drained.**                             |
| Confirmed halt                  | 2 members agree. Default for bug bounty, anomalies, "I don't understand what I'm seeing." |
| Escalate to Polkadot Fellowship | 2 members agree. Same conversation as confirmed halt in practice.                         |
| Public comms                    | Full team. **Only after fix is deployed and bridge is resuming.**                         |
| Emergency upgrade               | Coordinated with Polkadot Fellowship. Code is exploit-sensitive.                          |

A halt referendum on Polkassembly is public, so solo authority is reserved for cases where the incident is already public (funds moving). Otherwise discuss in Slack first.

## Comms during an incident

Each step assumes the previous one has happened.

1. **Slack** `#snowbridge-security` Post the signal (link to explorer, alert, bounty report). Non-visible signals: wait for at least one teammate to confirm before halting. Visible exploit: skip ahead.
2. **Halt** See [Producing](#producing-the-preimage-and-submission-links) + [Submitting](#submitting). For visible exploits, run in parallel with steps 3 and 4.
3. **Internal confirmation** 2+ members agree it's a real incident. Retroactive for solo-halt cases.
4. **Element with Polkadot Fellowship** New room, invite Adrian, Bastian, Oliver. Fellowship coordination happens here.
5. **Integrators** Telegram. Hydration first, then others. Tell them what's halted + expected resume timing.
6. **No public comms** (Twitter/X, forum, blog, public Discord) until fix is deployed and resume is in flight.

## Resuming the bridge

Same flow as halting: the governance page emits the resume preimage and the two submission links. Select scopes matching what was halted, then proceed via [Submitting](#submitting).

Fallback: `buildResumeBridgePreimage` + `buildResumeBridgeSubmissionUrls` in `@snowbridge/api`.

Before submitting, confirm:

* Fix is deployed and verified in production.
* Full team has signed off.
* Monitoring is back to baseline, no fresh anomalies.
* AssetHub fee values being restored to pre-incident values. Resume writes `BridgeHubEthereumBaseFee` and `BridgeHubEthereumBaseFeeV2` back to known good defaults (currently `14_929_540_998` for V1, `1_000_000_000` for V2). Double-check these match what was live before.

Public comms can begin once resume executes and the bridge is processing again.

## Emergency Upgrade

For cases a halt alone can't contain (e.g. critical pallet logic bug):

* **Halt first anyway.** Buys time to develop the upgrade without pressure. Skip only if halting is itself harmful.
* **Restrict the code.** Upgrade code for an unpatched vulnerability is itself exploit material. Private branch, limited reviewers (team + necessary Polkadot Fellowship contacts). Don't publicise until executed on-chain.
* **Coordinate the pathway with Polkadot Fellowship.** Whitelisted Caller for runtime, multi-sig for contracts. Use the Element channel.
* **Resume** only after the upgrade is verified live ([Resuming the bridge](#resuming-the-bridge)).

## Post-mortem

Within 48h of resume:

* **Owner** Whoever drove the incident (defaults to whoever halted first).
* **Format** Google Doc, shared with team + Polkadot Fellowship contacts from the Element channel.
* **Contents** Timeline (timestamps), root cause, halt scope + reason, what worked, what didn't, action items with owners and dates.
* **Action items** Track in the team issue tracker, not the doc itself.

Also write one for false-positive halts. Tuning detection signals to reduce false positives is itself useful output.


# General Governance Updates

Snowbridge has several governance APIs that can only be executed using a democratic process via Polkadot OpenGov.

These APIs include:

* Updating the Gateway contract on Ethereum
* Updating pricing parameters for fee calculations

These APIs are available on the `EthereumSystem` pallet on BridgeHub. We have also developed a [tool](https://github.com/Snowfork/snowbridge/tree/main/control) for generating calls to these APIs.

## Steps for initiating a governance update

As an example, we will show how to upgrade the Gateway contract on Ethereum.

### **Generate the preimage**

Deploy the new gateway contract, and then generate a preimage for calling `EthereumSystem.upgrade`

```bash
snowbridge-preimage --format binary upgrade PARAMS > preimage.bin
 
```

### Test the update in chopsticks

The `snowbridge-preimage` tool will also generate a helper script `chopsticks-execute-upgrade.js` to execute the update in simulated chopsticks environment.

1. Run chopsticks and fork Polkadot, AssetHub, and BridgeHub, using these [configs](https://github.com/Snowfork/snowbridge/tree/main/control/chopsticks)

```
chopsticks xcm -r polkadot.yml -p polkadot-asset-hub.yml -p polkadot-bridge-hub.yml
```

2. Once the chopsticks environment has been initialized, connect to BridgeHub in Polkadot-JS, and execute the contents of `chopsticks-execute-upgrade.js` in the Polkadot-JS Javascript console.

A more [complicated](/resources/test-runtime-upgrades) testing scenario would involve having to upgrade BridgeHub with new code, and then calling a governance API.

### OpenGov

The next step involves submitting the proposal to the Whitelisted Caller track in OpenGov.

This actually involves two referendums:

* A referendum on the Collectives chain where the technical fellowship vote to whitelist the preimage.
* A public referendum on Polkadot where the general public vote to execute the whitelisted preimage.

We use the tool [opengov-cli](https://github.com/joepetrowski/opengov-cli) to generate the various calls required to setup these referendums.

```
opengov-cli submit-referendum --proposal preimage.hex --network polkadot --track whitelisted-caller --after 100 --output-len-limit 100 --output AppsUiLink
```


# Test Runtime Upgrades

How to test upgrades depending on a runtime upgrade not yet executed.

## Overview

A scenario that frequently occurs is that we need to test a Snowbridge-related runtime upgrade that depends on a system parachain upgrade. Runtime upgrades for system parachains can take up to four weeks to execute. If we wait for the system parachain upgrade to complete first before initiating the Snowbridge upgrades, release cycles could take months.

Therefore, it is useful to be able to test system parachain upgrades that have not yet executed and then apply Snowbridge upgrades to ensure everything works.

## Steps

In the following scenario, we will simulate execution of the 1.2.0 upgrade: <https://github.com/polkadot-fellows/runtimes/releases/tag/v1.2.0>.

1. Install [opengov-cli](https://github.com/joepetrowski/opengov-cli)
2. Build the preimage for the upgrade:

<pre class="language-sh"><code class="lang-sh"><strong>opengov-cli build-upgrade --network polkadot --relay-version 1.2.0 --filename preimage.hex
</strong></code></pre>

3. Convert the preimage from hex to binary

```sh
cd upgrade-polkadot-1.2.0 
xxd -r -p preimage.hex > preimage.bin
```

4. Determine the size of the of preimage, save as `PREIMAGE_SIZE`

On Linux:

```sh
$ stat -c%s preimage.bin
1567371
$ export PREIMAGE_SIZE=1567371
```

On Mac:

```sh
$ stat -f%z preimage.bin
1567371
$ export PREIMAGE_SIZE=1567371
```

5. Compute blake2-256 hash of preimage, save as PREIMAGE\_HASH

```sh
$ b2sum -l 256 preimage.bin | awk '{print "0x"$1}'
0x15165c85152568b7f523e374ce1a5172f2aa148721d5dae0441f86c201c1a77b4
$ export PREIMAGE_HASH=0x15165c85152568b7f523e374ce1a5172f2aa148721d5dae0441f86c201c1a77b4
```

6. Prepend compact-encoded length prefix to preimage, and convert back to hex, save as PREIMAGE\_WITH\_LENGTH\_PREFIX:

```rust
use codec::Encode;
use std::fs::File;

fn main() {
    let mut file = File::open("preimage.bin")?;
    let mut buf: Vec<u8> = Vec::new();
    file.read_to_end(&mut buf)?;
    let bytes_encoded = buf.encode();
    println!("0x{}", hex::encode(bytes_encoded));
}
```

7. Create a chopsticks configuration file for the Polkadot relay chain, substituting the values generated previously:

`polkadot.yml`

```yaml
endpoint: wss://polkadot-rpc.dwellir.com
mock-signature-host: true
block: ${env.POLKADOT_BLOCK_NUMBER}
db: ./polkadot.sqlite

import-storage:
  System:
    Account:
      - - - 5GrwvaEF5zXb26Fz9rcQpDWS57CtERHpNehXCPcNoHGKutQY
        - providers: 1
          data:
            free: '10000000000000000000'
  ParasDisputes:
    $removePrefix: ['disputes'] # those can makes block building super slow
  Preimage:
    {
      PreimageFor:
        [[[[PREIMAGE_HASH, PREIMAGE_SIZE]], PREIMAGE_WITH_LENGTH_PREFIX]],
      StatusFor:
        [[[PREIMAGE_HASH], { Requested: { count: 1, len: PREIMAGE_SIZE } }]],
    }
```

8. Use these Chopstics config files for AssetHub and BridgeHub

`polkadot-asset-hub.yml`

```yaml
endpoint: wss://statemint-rpc.dwellir.com
mock-signature-host: true
block: ${env.POLKADOT_ASSET_HUB_BLOCK_NUMBER}
db: ./assethub.sqlite

import-storage:
  System:
    Account:
      - - - 5GrwvaEF5zXb26Fz9rcQpDWS57CtERHpNehXCPcNoHGKutQY
        - providers: 1
          data:
            free: 1000000000000000
```

`polkadot-bridge-hub.yml`

```yaml
endpoint: wss://polkadot-bridge-hub-rpc.dwellir.com
mock-signature-host: true
block: ${env.POLKADOT_BRIDGEHUB_BLOCK_NUMBER}
db: ./bridgehub.sqlite

import-storage:
  System:
    Account:
      - - - 5GrwvaEF5zXb26Fz9rcQpDWS57CtERHpNehXCPcNoHGKutQY
        - providers: 1
          data:
            free: 1000000000000000
```

9. Run Chopsticks

```sh
yarn start xcm -r polkadot.yml -p polkadot-asset-hub.yml -p polkadot-bridge-hub.yml
```

A verification step that can be performed to see if the preimage has been added successfully is to check the `preimage` storage in the chain state. The authorized preimage should be in the list of added preimages.

10. Execute the upgrade on the relay chain using Polkadot-JS:

```rust
const number = (await api.rpc.chain.getHeader()).number.toNumber()

await api.rpc('dev_setStorage', {
  Scheduler: {
    Agenda: [
      [
        [number + 1],
        [
          {
            call: {
              Lookup: {
                hash: PREIMAGE_HASH,
                len: PREIMAGE_SIZE,
              },
            },
            origin: {
              system: 'Root',
            },
          },
        ],
      ],
    ],
  },
})

await api.rpc('dev_newBlock', { count: 1 })
```

11. Advance a few blocks on the relay chain

```rust
await api.rpc('dev_newBlock', { count: 2 })
```

12. Advance by one block on bridgehub (not sure if necessary, need to experiment)

```rust
await api.rpc('dev_newBlock', { count: 1 })
```

13. Now that the upgrade has been authorized on BridgeHub, we can execute the upgrade by calling parachainSystem.enactAuthorizedUpgrade, passing the parachain WASM blob previously generated by opengov-cli:

<figure><img src="/files/INWqHKN7dFo8dDaY1M7S" alt=""><figcaption></figcaption></figure>

14. Advance a few blocks on both bridgehub AND the relay chain

```rust
await api.rpc('dev_newBlock', { count: 1 })
```

15. The parachain should now be upgraded.

## Caveats

Some polkadot API endpoints aggressively timeout connections, causing Chopsticks to die: Comment

```sh
API-WS: disconnected from wss://polkadot-rpc.dwellir.com: 1006:: Abnormal Closure
```

The usual remedy is to restart chopsticks and pray the API connections don't die again.


# Run Relayers

Steps to set up your own Snowbridge message relayers.

This guide explains how to run Snowbridge relayers using Docker Compose.

## Overview

Snowbridge relayers are off-chain agents that facilitate message passing between Ethereum and Polkadot. Running a relayer helps decentralize the bridge and you can earn rewards for successfully relaying messages.

### Which Relayers Should I Run?

For new operators, we recommend starting with:

| Relayer        | Direction                          |
| -------------- | ---------------------------------- |
| `parachain-v2` | Polkadot → Ethereum, Snowbridge V2 |
| `parachain`    | Polkadot → Ethereum, Snowbridge V1 |
| `ethereum-v2`  | Ethereum → Polkadot, Snowbridge V2 |
| `ethereum`     | Ethereum → Polkadot, Snowbridge V1 |

**Note:** The `beefy` and `beacon` relayers are consensus relayers that are expensive to operate (high gas costs) and are run exclusively by the Snowfork team. Individual operators do not need to run these.

### Hardware Requirements

Minimum recommended specifications:

* **CPU:** 2 cores (dedicated, avoid burstable instances)
* **RAM:** 4 GB
* **Storage:** 20 GB SSD
* **Network:** Stable internet connection with low latency

## Prerequisites

* Docker and Docker Compose installed
* Private keys for signing transactions (Ethereum and/or Substrate)
* RPC endpoints for:
  * Ethereum execution layer (WebSocket)
  * Ethereum beacon chain (HTTP)
  * Polkadot relay chain (WebSocket)
  * BridgeHub parachain (WebSocket)
  * AssetHub parachain (WebSocket, for ethereum relay gas estimation)

## Quick Start

1. **Download the Docker Compose file and environment template for your network:**

   ```bash
   mkdir snowbridge && cd snowbridge

   # Docker Compose file
   curl -O https://raw.githubusercontent.com/Snowfork/snowbridge/main/relayer/docker-compose.yml

   # For mainnet (Polkadot + Ethereum)
   curl -o .env https://raw.githubusercontent.com/Snowfork/snowbridge/main/relayer/.env.mainnet.example
   ```
2. **Configure your .env file with:**
   * RPC endpoints
   * Private key references (see [Private Keys](#private-keys) section)
   * (Mainnet only) Chainalysis API key for OFAC compliance
3. **Start the relayers:**

   ```bash
   docker compose up -d
   ```

To start all services including consensus relayers (Snowfork only):

```bash
docker compose --profile consensus up -d
```

## Architecture

The Docker Compose setup runs the following relayer services:

| Service                | Description                                      | Keys Required | Profile   |
| ---------------------- | ------------------------------------------------ | ------------- | --------- |
| `beacon-state-service` | Caches beacon state proofs                       | None          | default   |
| `beacon`               | Relays Ethereum beacon headers to Polkadot       | Substrate     | consensus |
| `ethereum-v2`          | Relays Ethereum messages to Polkadot (v2)        | Substrate     | default   |
| `ethereum`             | Relays Ethereum messages to Polkadot (v1)        | Substrate     | default   |
| `parachain-v2`         | Relays Polkadot messages to Ethereum (v2)        | Ethereum      | default   |
| `parachain`            | Relays Polkadot messages to Ethereum (v1)        | Ethereum      | default   |
| `primary-governance`   | Relays primary governance messages to Ethereum   | Ethereum      | default   |
| `secondary-governance` | Relays secondary governance messages to Ethereum | Ethereum      | default   |
| `reward`               | Processes relayer rewards                        | Substrate     | default   |
| `beefy`                | Relays BEEFY commitments to Ethereum             | Ethereum      | consensus |
| `beefy-on-demand`      | On-demand BEEFY relay                            | Ethereum      | consensus |

**Note:** Services in the `consensus` profile require `--profile` consensus to start.

### Service Dependencies

```
beacon-state-service (starts first, health checked)
    ├── beacon (consensus profile)
    ├── ethereum-v2
    ├── ethereum
    └── reward

parachain-v2 (independent)
parachain (independent)
primary-governance (independent)
secondary-governance (independent)
beefy (independent, consensus profile)
beefy-on-demand (independent, consensus profile)
```

## Configuration

### Environment Files

Each network has a pre-configured environment file:

| Network | File                   | Ethereum         | Polkadot |
| ------- | ---------------------- | ---------------- | -------- |
| Mainnet | `.env.mainnet.example` | Ethereum Mainnet | Polkadot |
| Paseo   | `.env.paseo.example`   | Sepolia          | Paseo    |
| Westend | `.env.westend.example` | Sepolia          | Westend  |

### Private Keys

For production deployments, use AWS Secrets Manager:

```bash
# Pattern: {environment}/{relay-name}
BEACON_RELAY_SUBSTRATE_KEY_ID=snowbridge/beacon-relay
EXECUTION_RELAY_SUBSTRATE_KEY_ID=snowbridge/asset-hub-ethereum-relay-v2
BEEFY_RELAY_ETHEREUM_KEY_ID=snowbridge/beefy-relay
BEEFY_ON_DEMAND_RELAY_ETHEREUM_KEY_ID=snowbridge/beefy-on-demand-relay
PARACHAIN_V1_RELAY_ETHEREUM_KEY_ID=snowbridge/asset-hub-parachain-relay
PARACHAIN_RELAY_ETHEREUM_KEY_ID=snowbridge/asset-hub-parachain-relay-v2
REWARD_RELAY_SUBSTRATE_KEY_ID=snowbridge/asset-hub-parachain-relay-v2-delivery-proof
EXECUTION_V1_RELAY_SUBSTRATE_KEY_ID=snowbridge/asset-hub-ethereum-relay
PRIMARY_GOVERNANCE_RELAY_ETHEREUM_KEY_ID=prod/governance-relay
SECONDARY_GOVERNANCE_RELAY_ETHEREUM_KEY_ID=prod/governance-relay
```

Create secrets in AWS Secrets Manager containing the raw private key strings. Requires AWS credentials configured in your `.env` file.

### Endpoint Configuration

All endpoints are configured via environment variables:

| Variable             | Description                              |
| -------------------- | ---------------------------------------- |
| `ETHEREUM_ENDPOINT`  | Ethereum execution layer RPC (WebSocket) |
| `BEACON_ENDPOINT`    | Ethereum beacon chain HTTP endpoint      |
| `POLKADOT_ENDPOINT`  | Polkadot relay chain RPC (WebSocket)     |
| `BRIDGEHUB_ENDPOINT` | BridgeHub parachain RPC (WebSocket)      |
| `ASSETHUB_ENDPOINT`  | AssetHub parachain RPC (WebSocket)       |
| `FLASHBOTS_ENDPOINT` | Flashbots RPC for private transactions   |

### OFAC Compliance

The execution and parachain relays support OFAC compliance checking via Chainalysis.

* **Mainnet**: Enabled by default, requires `CHAINALYSIS_API_KEY`
* **Testnets**: Disabled by default

### Fund Relayer Accounts

The Ethereum and Polkadot BridgeHub accounts should be funded with at least $10 each.

## Operations

### View logs

```bash
# All services
docker compose logs -f

# Specific service
docker compose logs -f parachain-v2
```

### Stop relayers

```bash
docker compose down
```

### Restart a specific relayer

```bash
docker compose restart ethereum-v2
```

### Check health

```bash
# Beacon state service health
curl http://localhost:8080/health

# Check container status
docker compose ps
```

### Upgrade

To upgrade to a newer relayer version:

```bash
# Pull the latest image
docker compose pull

# Restart with the new image
docker compose up -d
```

Or specify a specific version via the `IMAGE_TAG` environment variable in your `.env` file. The example `.env` files are pre-configured with the correct image tag (`latest`).

## Volumes

The setup creates persistent volumes for:

* `beacon-state-data` — Beacon state service cache and persistence
* `beacon-data` — Beacon relay local datastore

To reset state:

```bash
docker compose down -v
```

## Rewards

Relayers earn rewards for successfully delivering messages:

* **Polkadot → Ethereum** (`parachain-v2`): Rewards are paid in ETH on Ethereum
* **Ethereum → Polkadot** (`ethereum-v2`): Rewards are paid in DOT on AssetHub

To claim rewards, configure the `REWARD_ADDRESS` environment variable with your reward destination address.

The `reward` relayer service automatically claims accumulated rewards periodically.

## Monitoring

### CloudWatch Logging (AWS)

If running on AWS EC2, logs are automatically sent to CloudWatch when configured:

1. Set `AWS_REGION`, `AWS_ACCESS_KEY_ID`, and `AWS_SECRET_ACCESS_KEY` in your `.env` file
2. Logs will appear in CloudWatch under `snowbridge/{environment}/`

### Local Logging

```bash
# Follow all logs
docker compose logs -f

# Follow specific service
docker compose logs -f parachain-v2

# View last 100 lines
docker compose logs --tail 100 ethereum-v2
```

### Health Checks

```bash
# Beacon state service health
curl http://localhost:8080/health

# Check container status
docker compose ps
```

## Troubleshooting

### Beacon state service not healthy

Check the logs:

```bash
docker compose logs beacon-state-service
```

Common issues:

* Beacon endpoint not reachable
* Incorrect fork versions (check your .env matches the network)

### Relayer failing to submit transactions

* Check private key is correctly configured
* If using AWS Secrets Manager, verify AWS credentials in `.env`
* Check endpoint connectivity

### Gas estimation failures (ethereum relay)

* Verify AssetHub and BridgeHub endpoints are correct

### Relayer not picking up messages

* Ensure your endpoints are synced and not lagging
* Check logs for connectivity issues

## Getting Help

* Telegram: [Snowbridge Relayer Group](https://t.me/+I8Iel-Eaxcw3NjU0)
* GitHub Issues: <https://github.com/Snowfork/snowbridge/issues>


# Local Development Guide

Set up a development environment and run the end to end test stack.

### System Requirements

* Ubuntu 22.04 LTS (Ubuntu 20.04 LTS should also work)

### Development Tools

* Utilities (`jq`, `direnv`, `sponge`, `gcc`, `g++`, `build-essential`)

```bash
sudo apt install jq direnv moreutils gcc g++ build-essential
```

Install hooks for `direnv`. Change this if you are using a different shell.

```bash
direnv hook bash >> .bashrc
source .bashrc
```

* Install <https://github.com/nvm-sh/nvm>

```bash
curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.39.2/install.sh | bash
```

* Install Node

```bash
cd core && nvm use
```

* Install pnpm (<https://pnpm.io/>)

```bash
corepack enable
corepack prepare pnpm@7.14.2 --activate
```

* Rust (<https://docs.substrate.io/install/linux/>)

```bash
sudo apt install -y git clang curl libssl-dev llvm libudev-dev make protobuf-compiler

curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh
source ~/.cargo/env

rustup default stable
rustup update
rustup update nightly
rustup target add wasm32-unknown-unknown --toolchain nightly
```

* Typos (<https://crates.io/crates/typos-cli#install>)

```bash
cargo install typos-cli
```

* Golang (<https://go.dev/doc/install>)

```bash
curl -LO https://go.dev/dl/go1.19.3.linux-amd64.tar.gz

sudo rm -rf /usr/local/go
sudo tar -C /usr/local -xzf go1.19.3.linux-amd64.tar.gz

# Add to ~/.profile to persist
export PATH=$PATH:/usr/local/go/bin:$HOME/go/bin
```

* Mage and Revive (<https://magefile.org/>, <https://github.com/mgechev/revive#installation>)

```bash
go install github.com/magefile/mage@latest
go install github.com/mgechev/revive@master
```

* Geth (<https://geth.ethereum.org/docs/install-and-build/installing-geth>)

```bash
go install github.com/ethereum/go-ethereum/cmd/geth@latest
```

### Setup

This guide uses the root of the `$HOME/` folder for all source code.

1. Clone the <https://github.com/paritytech/polkadot> repo.

   ```bash
   git clone -n https://github.com/paritytech/polkadot.git
   cd polkadot
   git checkout v0.9.30
   cargo build --release
   ```
2. Clone the <https://github.com/Snowfork/snowbridge> repo.

   ```bash
   git clone https://github.com/Snowfork/snowbridge.git
   ```
3. `yarn` install dependencies.

   ```bash
   cd snowbridge
   (cd core && pnpm install)
   ```
4. Edit `.envrc` and `direnv allow`

   In the `web/packages/test` subfolder of the `snowbridge` repo copy the envrc-example.

   ```bash
   cp .envrc-example .envrc
   ```

   Modify the `POLKADOT_BIN` variable in `.envrc` to point to the `polkadot` binary. If you have checked out all source code to the `$HOME` folder you can use the relative path below: `export POLKADOT_BIN=../../../../polkadot/target/release/polkadot`

   Allow the variables to be automatically loaded by `direnv`

   ```bash
   direnv allow
   ```

   In the `contracts` subfolder of the `snowbridge` repo copy the envrc-example. Here we do not need to edit the `.envrc` as defaults are set.

   ```bash
   cp .envrc-example .envrc
   direnv allow
   ```

### Running the E2E stack

1. Start up the local E2E test stack

In a separate terminal change directory to the `web/packages/test` subfolder of the `snowbridge` repo. Run `start-services.sh` script to start the bridge.

```bash
scripts/start-services.sh
```

This script will:

1. Launch a local ethereum node (Geth & Lodestar)
2. Deploy contracts
3. Build and start the Snowbridge parachain
4. Configure the bridge
5. Start the relayers.

When this is complete `Testnet has been initialized` will be printed to the terminal. The bridge will continue to run until cancelled by `Ctrl+C` to kill the `start-services.sh` script.

1. Bootstrap the bridge.

   The bridge requires a certain amount of funds (SnowDOT and SnowETH) in order for Incentivized channels to be used. The bootstrap process are the first two test cases and needs to be run before other tests will pass.

   In the `web/packages/test` subfolder of the `snowbridge` repo run the bootstrap tests:

   ```bash
   pnpm test:integration test/bootstrap.js
   ```
2. Run any single test or all tests.

   To run all tests:

   ```bash
   pnpm test:integration
   ```

   To run a single test:

   ```bash
   pnpm test:integration --grep 'should transfer ETH from Substrate to Ethereum \(incentivized channel\)'
   ```

## Inspecting the E2E environment

1. Ethereum

   The ethereum data directory is `/tmp/snowbridge/geth`.

   The ethereum log file is `/tmp/snowbridge/geth.log`.

   The Lodestar log file is `/tmp/snowbridge/lodestar.log`.
2. Relaychain

   The relay chain log files are in the `web/packages/test` subdirectory of the `snowbridge` repo. `alice.log`, `bob.log`, `charlie.log`

   The relay chain can be accessed via the polkadot.js web using the following url:

   [https://polkadot.js.org/apps/?rpc=ws%3A%2F%127.0.0.1%3A9944#/explorer](https://polkadot.js.org/apps/?rpc=ws%3A%2F%2Flocalhost%3A9944#/explorer)
3. Parachain

   The Snowbridge parachain log files are in the `web/packages/test` subdirectory of the `snowbridge` repo. `11144.log`, `11155.log`

   The Snowbridge parachain can be accessed via the polkadot.js web using the following url:

   [https://polkadot.js.org/apps/?rpc=ws%3A%2F%127.0.0.1%3A11144#/explorer](https://polkadot.js.org/apps/?rpc=ws%3A%2F%2Flocalhost%3A11144#/explorer)
4. Test Parachain

   The third-party test parachain log files are in the `web/packages/test` subdirectory of the `snowbridge` repo. `13144.log`, `13155.log`

   The Snowbridge Test parachain can be accessed via the polkadot.js web using the following url:

   [https://polkadot.js.org/apps/?rpc=ws%3A%2F%127.0.0.1%3A13144#/explorer](https://polkadot.js.org/apps/?rpc=ws%3A%2F%2Flocalhost%3A13144#/explorer)
5. Relayers

The relayers log files can be found in the `web/packages/test` subdirectory of the `snowbridge` repo.

* `beacon-relay.log`
* `parachain-relay.log`
* `beefy-relay.log`

The`start-services.sh` script will automatically restart the relayer processes if they exit and print to the terminal. Seeing a relayer restart constantly is a sign that something might be wrong with your environment. Grepping the relayer logs will help pin point the issue.


