# How Nautilus Wallet Implements Transaction Building and Fee Calculation

> Discover how Nautilus Wallet builds Ergo transactions using a plug-in architecture. Learn about native ERG and Babel fee calculation with dynamic liquidity selection.

- Repository: [Nautilus Team/nautilus-wallet](https://github.com/nautls/nautilus-wallet)
- Tags: internals
- Published: 2026-03-07

---

**Nautilus Wallet uses a layered, plug-in architecture in [`src/chains/ergo/transaction/builder.ts`](https://github.com/nautls/nautilus-wallet/blob/main/src/chains/ergo/transaction/builder.ts) to construct Ergo transactions, supporting both native ERG fees and token-based Babel fees through dynamic liquidity selection.**

The `nautls/nautilus-wallet` repository implements a sophisticated transaction building pipeline for the Ergo blockchain. This system handles everything from UTXO selection to dynamic fee calculation, including advanced token-based fee mechanisms. The architecture separates concerns between core builder logic, fee strategies, and high-level transaction factories.

## Core Transaction Builder Architecture

The foundation of transaction building resides in [`src/chains/ergo/transaction/builder.ts`](https://github.com/nautls/nautilus-wallet/blob/main/src/chains/ergo/transaction/builder.ts), which orchestrates the `TransactionBuilder` class from the Ergo SDK.

### Input Gathering and Context Setup

Every transaction begins by fetching the current blockchain context and available UTXOs. The `getContext()` helper retrieves inputs and the current block height, which initializes the builder:

```typescript
const { inputs, currentHeight } = await getContext();
const unsigned = new TransactionBuilder(currentHeight).from(inputs);

```

This establishes the temporal validity of the transaction and loads the wallet's unspent boxes as potential inputs.

### Output Construction and Change Handling

The builder constructs outputs using `OutputBuilder`, converting token amounts to their undecimalized integer representation:

```typescript
.to(
  new OutputBuilder(
    sendingNanoErgs.eq(0) && isBabelFee ? BigInt(MIN_BOX_VALUE) : sendingNanoErgs.toString(),
    recipientAddress
  ).addTokens(/* token list */)
)
.sendChangeTo(safeGetChangeAddress())

```

The `safeGetChangeAddress()` function derives a deterministic change address from the wallet's HD key pool, ensuring leftover ERG returns to a controlled address.

### Selection and Change Strategies

The `setSelectionAndChangeStrategy()` function configures UTXO selection algorithms based on wallet type. For **Ledger** devices, it enforces `isolateErgOnChange` and limits `maxTokensPerChangeBox` to 1 to comply with hardware constraints. Standard wallets use higher token limits and sort by creation height.

## Fee Calculation Strategies

The `setFee` function in [`builder.ts`](https://github.com/nautls/nautilus-wallet/blob/main/builder.ts) implements dual-path fee logic, handling both native ERG fees and **Babel fees** (token-based fees).

### Native ERG Fees

For standard ERG fees, the process is straightforward:

```typescript
let feeNanoErgs = undecimalize(fee.value, ERG_DECIMALS);
return builder.payFee(feeNanoErgs.toString());

```

The `undecimalize` helper from [`src/common/bigNumber.ts`](https://github.com/nautls/nautilus-wallet/blob/main/src/common/bigNumber.ts) converts human-readable ERG amounts to nanoErgs (the base unit).

### Babel Token Fees

Babel fees allow users to pay transaction fees in tokens rather than ERG. The implementation in `setFee` handles this complex conversion:

1. **Fetch liquidity**: `fetchBabelBoxes` queries the GraphQL endpoint via `graphQLService.getBoxes` to find boxes containing the fee token.
2. **Select optimal box**: `selectBestBabelBox` chooses a box with sufficient ERG balance and the best exchange rate.
3. **Calculate ERG equivalent**: `getNanoErgsPerTokenRate` extracts the conversion rate, and `feeNanoErgs = tokenUnits.multipliedBy(rate)`.

If no suitable Babel box exists, the system throws: *"There is not enough liquidity in the Babel Boxes for the selected fee asset in the selected price range."*

### Fee Conversion and Plugin Injection

When using Babel fees, the builder extends the transaction with `BabelSwapPlugin`:

```typescript
builder.extend(
  BabelSwapPlugin(fee.box, { tokenId: fee.tokenId, amount: tokenUnits.toString() })
);

```

This plugin ensures the network converts the token fee to ERG during transaction validation. The function also handles edge cases where the fee would consume the entire output, adjusting `sendingNanoErgs` to maintain the minimum box value (`MIN_BOX_VALUE`).

## High-Level Transaction Factories

The [`src/dapps/wallet-optimization/transactionFactory.ts`](https://github.com/nautls/nautilus-wallet/blob/main/src/dapps/wallet-optimization/transactionFactory.ts) file provides ergonomic wrappers for common operations. The `createP2PTransaction` function demonstrates the complete orchestration:

```typescript
export async function createP2PTransaction({
  recipientAddress,
  assets,
  fee,
  walletType
}: { recipientAddress: string; assets: TxAssetAmount[]; fee: FeeSettings; walletType: WalletType }) {
  const { inputs, currentHeight } = await getContext();
  const isBabelFee = fee.tokenId !== ERG_TOKEN_ID;
  const sendingNanoErgs = getSendingNanoErgs(assets);

  const unsigned = new TransactionBuilder(currentHeight)
    .from(inputs)
    .to(
      new OutputBuilder(
        sendingNanoErgs.eq(0) && isBabelFee ? BigInt(MIN_BOX_VALUE) : sendingNanoErgs.toString(),
        recipientAddress
      ).addTokens(/* token list */)
    )
    .sendChangeTo(safeGetChangeAddress());

  await setFee(unsigned, fee);
  setSelectionAndChangeStrategy(unsigned, walletType);

  return unsigned.build().toEIP12Object();
}

```

This factory handles peer-to-peer transfers, automatically managing Babel fee detection, minimum box value requirements, and wallet-specific selection strategies.

## Complete Transaction Flow Example

The following example demonstrates building a token transfer with a Babel fee using the high-level factory:

```typescript
import { createP2PTransaction } from '@/dapps/wallet-optimization/transactionFactory';
import { FeeSettings, WalletType } from '@/types/internal';
import { bn } from '@/common/bigNumber';
import { ERG_TOKEN_ID } from '@/constants/ergo';

// Define assets to send (e.g., 10 SIGMA tokens)
const assets = [
  {
    asset: { tokenId: '0f2c...abcd', metadata: { decimals: 2 } },
    amount: bn(10) // 10.00 tokens
  }
];

// Configure Babel fee (pay with the same token)
const fee: FeeSettings = {
  tokenId: '0f2c...abcd',
  value: bn(0.1), // 0.10 token fee
  nanoErgsPerToken: bn(0),
  assetInfo: { decimals: 2 }
};

// Build transaction
const unsignedTx = await createP2PTransaction({
  recipientAddress: '9hZ7...xyz',
  assets,
  fee,
  walletType: WalletType.Standard
});

// Result is EIP-12 compatible unsigned transaction
console.log(unsignedTx);

```

In this flow, `setFee` automatically fetches Babel boxes from [`src/chains/ergo/babelFees.ts`](https://github.com/nautls/nautilus-wallet/blob/main/src/chains/ergo/babelFees.ts), calculates the ERG equivalent using the liquidity pool rate, and injects the `BabelSwapPlugin` to handle on-chain conversion.

## Summary

- **Modular Architecture**: Transaction building is split between [`src/chains/ergo/transaction/builder.ts`](https://github.com/nautls/nautilus-wallet/blob/main/src/chains/ergo/transaction/builder.ts) (core logic) and [`src/dapps/wallet-optimization/transactionFactory.ts`](https://github.com/nautls/nautilus-wallet/blob/main/src/dapps/wallet-optimization/transactionFactory.ts) (high-level factories).
- **Dual Fee Support**: The `setFee` function handles both native ERG fees and **Babel token fees** by querying liquidity boxes and converting token amounts to ERG equivalents.
- **Hardware Wallet Compatibility**: `setSelectionAndChangeStrategy` enforces constraints for Ledger devices (single-token change boxes) while optimizing for standard wallets.
- **EIP-12 Standard**: All transactions are finalized using `.build().toEIP12Object()`, ensuring compatibility with Ergo's standard unsigned transaction format.

## Frequently Asked Questions

### How does Nautilus Wallet handle fee calculation for token transfers?

Nautilus Wallet calculates fees using the `setFee` function in [`src/chains/ergo/transaction/builder.ts`](https://github.com/nautls/nautilus-wallet/blob/main/src/chains/ergo/transaction/builder.ts). For standard transfers, it undecimalizes the ERG amount and calls `payFee`. For token transfers using Babel fees, it queries available liquidity boxes via `fetchBabelBoxes`, selects the optimal rate with `selectBestBabelBox`, and converts the token fee amount to its ERG equivalent using `getNanoErgsPerTokenRate`.

### What is the difference between standard and Ledger wallet transaction building?

The `setSelectionAndChangeStrategy` function configures the `TransactionBuilder` differently based on the `WalletType`. For **Ledger** devices, it sets `isolateErgOnChange` to true and limits `maxTokensPerChangeBox` to 1, ensuring hardware constraints are respected. **Standard** wallets use higher token limits per change box and sort inputs by creation height for optimal selection.

### How are Babel fees converted to ERG equivalents?

When a Babel fee is detected (token ID differs from `ERG_TOKEN_ID`), the system fetches Babel boxes containing that token from the GraphQL endpoint. It selects the box with the best exchange rate and sufficient ERG balance using `selectBestBabelBox`. The conversion applies the formula `feeNanoErgs = tokenUnits * getNanoErgsPerTokenRate(selectedBox)`, then extends the transaction with `BabelSwapPlugin` to execute the on-chain swap.

### Where is the transaction builder logic located in the Nautilus Wallet repository?

The core transaction building logic resides in [`src/chains/ergo/transaction/builder.ts`](https://github.com/nautls/nautilus-wallet/blob/main/src/chains/ergo/transaction/builder.ts), which contains the `setFee`, `safeGetChangeAddress`, and `setSelectionAndChangeStrategy` functions. Babel fee-specific utilities are in [`src/chains/ergo/babelFees.ts`](https://github.com/nautls/nautilus-wallet/blob/main/src/chains/ergo/babelFees.ts). High-level transaction factories that orchestrate the complete flow are located in [`src/dapps/wallet-optimization/transactionFactory.ts`](https://github.com/nautls/nautilus-wallet/blob/main/src/dapps/wallet-optimization/transactionFactory.ts).