> ## Documentation Index
> Fetch the complete documentation index at: https://bridge.near.tools/llms.txt
> Use this file to discover all available pages before exploring further.

# Manual Finalization

> Finalize transfers yourself without relayer fees

When you set `fee: 0n` and `nativeFee: 0n`, relayers won't process your transfer. You finalize it yourself on the destination chain.

**When to use this:**

* Cost savings on high-volume transfers
* Running your own relayer infrastructure
* Specific timing or ordering requirements

## The Process

The steps depend on your source chain:

**From NEAR:**

1. Initiate transfer (`ft_transfer_call`)
2. Call `signTransfer` to trigger MPC signing
3. Parse the `SignTransferEvent` from logs
4. Submit finalization on destination chain

**From EVM/Solana:**

1. Initiate transfer on source chain
2. Wait for proof to become available (Merkle proof or Wormhole VAA)
3. Submit finalization on NEAR

The proof type and wait time depends on the source:

| Source                | Proof Type    | Wait Time   |
| --------------------- | ------------- | ----------- |
| NEAR                  | MPC Signature | \~1-5 min   |
| Ethereum              | Merkle Proof  | \~15-20 min |
| L2s (Base, Arb, etc.) | Wormhole VAA  | \~1 min     |
| Solana                | Wormhole VAA  | \~15 sec    |

## When is Finalization Ready?

Poll the API for the `Signed` status:

```typescript theme={null}
import { BridgeAPI } from "@omni-bridge/core"

const api = new BridgeAPI("mainnet")

const statuses = await api.getTransferStatus({ transactionHash: hash })

if (statuses.includes("Signed")) {
  // Ready to finalize!
}
```

For Ethereum → NEAR specifically, you need to wait for the light client to sync the block (check `Initialized` status and wait \~15-20 min).

## From NEAR to EVM

NEAR → EVM transfers require three steps: initiate, sign, then finalize.

### Step 1: Initiate the Transfer

```typescript theme={null}
import { createBridge } from "@omni-bridge/core"
import { createNearBuilder, toNearKitTransaction } from "@omni-bridge/near"

const bridge = createBridge({ network: "mainnet" })
const nearBuilder = createNearBuilder({ network: "mainnet" })

const signerId = "alice.near"

// Validate and build the transfer
const validated = await bridge.validateTransfer({
  token: "near:wrap.near",
  amount: 1_000_000_000_000_000_000_000_000n,
  sender: `near:${signerId}`,
  recipient: "eth:0x...",
  fee: 0n,
  nativeFee: 0n,
})

const initTx = nearBuilder.buildTransfer(validated, signerId)
const initResult = await toNearKitTransaction(near, initTx).send()
```

### Step 2: Sign the Transfer

After the transfer is initialized, you must call `signTransfer` to trigger MPC signing:

```typescript theme={null}
// Extract the transfer ID from the InitTransferEvent in the logs
const initEvent = parseInitTransferEvent(initResult)

const signTx = nearBuilder.buildSignTransfer(
  {
    origin_chain: "Near",
    origin_nonce: initEvent.nonce,
  },
  signerId
)

const signResult = await toNearKitTransaction(near, signTx).send()
```

### Step 3: Finalize on EVM

Parse the `SignTransferEvent` from the sign transaction logs and finalize on EVM:

```typescript theme={null}
import { createEvmBuilder } from "@omni-bridge/evm"
import { ChainKind } from "@omni-bridge/core"
import { MPCSignature } from "@omni-bridge/near"

const evm = createEvmBuilder({ network: "mainnet", chain: ChainKind.Eth })

// Parse SignTransferEvent from signResult logs
const signEvent = parseSignTransferEvent(signResult)

// Convert signature for EVM (adds 27 to recovery ID)
const signature = MPCSignature.fromSignTransferEvent(signEvent).toBytes(true)

const tx = evm.buildFinalization(signEvent.message_payload, signature)
await walletClient.sendTransaction(tx)
```

<Note>
  The MPC signature needs format conversion for EVM — use `MPCSignature.toBytes(true)` to add 27 to the recovery ID. For Solana, use `toBytes(false)`.
</Note>

## From EVM to NEAR

### Ethereum (Merkle Proof)

Ethereum uses the NEAR light client for verification:

```typescript theme={null}
import { createNearBuilder, toNearKitTransaction } from "@omni-bridge/near"
import { getEvmProof, ProofKind } from "@omni-bridge/evm"

const nearBuilder = createNearBuilder({ network: "mainnet" })

// Wait for light client to sync (~15-20 min after source tx confirms)

// Generate Merkle proof
const proof = await getEvmProof(txHash, ChainKind.Eth)

// Serialize for NEAR
const proverArgs = nearBuilder.serializeEvmProofArgs({
  proof_kind: ProofKind.InitTransfer,
  proof,
})

// Build and send finalization
const tx = nearBuilder.buildFinalization(ChainKind.Eth, proverArgs, signerId)
await toNearKitTransaction(near, tx).send()
```

### L2s (Wormhole VAA)

Base, Arbitrum, Polygon, and BNB use Wormhole:

```typescript theme={null}
import { getWormholeVaa } from "@omni-bridge/core"

// Wait for Wormhole guardians to sign (~1 min)
const vaa = await getWormholeVaa(txSignature, "Mainnet")

// Serialize for NEAR
const proverArgs = nearBuilder.serializeWormholeProofArgs({
  proof_kind: ProofKind.InitTransfer,
  vaa,
})

// Finalize
const tx = nearBuilder.buildFinalization(ChainKind.Base, proverArgs, signerId)
await toNearKitTransaction(near, tx).send()
```

## From Solana to NEAR

Solana also uses Wormhole VAAs:

```typescript theme={null}
import { getWormholeVaa } from "@omni-bridge/core"

const vaa = await getWormholeVaa(solanaSignature, "Mainnet")

const proverArgs = nearBuilder.serializeWormholeProofArgs({
  proof_kind: ProofKind.InitTransfer,
  vaa,
})

const tx = nearBuilder.buildFinalization(ChainKind.Sol, proverArgs, signerId)
await toNearKitTransaction(near, tx).send()
```

## Finalization Builders

Each destination has a `buildFinalization` method:

| Destination | Method                                                       |
| ----------- | ------------------------------------------------------------ |
| EVM         | `evmBuilder.buildFinalization(payload, signature)`           |
| NEAR        | `nearBuilder.buildFinalization(chain, proverArgs, signerId)` |
| Solana      | `solanaBuilder.buildFinalization(payload, signature, payer)` |

## Working Examples

For complete, runnable examples:

* **NEAR → EVM**: [`e2e/near-to-eth.test.ts`](https://github.com/near-one/bridge-sdk-js/blob/main/e2e/near-to-eth.test.ts)
* **EVM → NEAR**: [`e2e/eth-to-near.test.ts`](https://github.com/near-one/bridge-sdk-js/blob/main/e2e/eth-to-near.test.ts)

## Consider Using Relayers Instead

Manual finalization adds complexity. Use [relayer fees](/guides/fees) if:

* You want fire-and-forget simplicity
* The fee cost is acceptable
* You don't need precise timing control
