# Transaction Signing and Submission in Nautilus Wallet: A Complete Technical Guide

> Explore transaction signing and submission in Nautilus Wallet. Learn how dApps interact with the Ergo blockchain through our technical guide.

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

---

**Nautilus Wallet orchestrates transaction signing and submission through a multi-layered architecture where dApps invoke `window.ergoConnector.nautilus` methods that flow through injected scripts, background processes, and cryptographic provers before broadcasting via GraphQL to the Ergo blockchain.**

Nautilus Wallet is an open-source browser extension for the Ergo blockchain that provides secure transaction signing and submission capabilities for decentralized applications. According to the nautls/nautilus-wallet source code, the wallet implements a sophisticated message-passing architecture that isolates private keys within the extension background while enabling seamless dApp integration. This technical guide examines the complete pipeline from API invocation to blockchain confirmation, referencing specific implementation files and method signatures.

## The Injected API: dApp Entry Points

Decentralized applications interact with Nautilus Wallet through the injected API exposed in [`src/extension/content-scripts/injected.ts`](https://github.com/nautls/nautilus-wallet/blob/main/src/extension/content-scripts/injected.ts). The `NautilusErgoApi` class provides two primary methods for transaction handling.

**`sign_tx(transaction)`** accepts an **EIP-12 unsigned transaction** and forwards it to the extension background via `ExternalRequest.SignTx`. This method triggers the cryptographic signing flow where user confirmation is required.

**`submit_tx(transaction)`** accepts a **signed transaction** and forwards it via `ExternalRequest.SubmitTransaction` for network broadcasting. This separation allows dApps to either submit immediately after signing or handle the signed transaction separately.

```typescript
// src/extension/content-scripts/injected.ts
async sign_tx(transaction: EIP12UnsignedTransaction) {
  return handle(await sendMessage(ExternalRequest.SignTx, { transaction }, CONTENT_SCRIPT));
}

async submit_tx(transaction: SignedTransaction) {
  return handle(
    await sendMessage(ExternalRequest.SubmitTransaction, { transaction }, CONTENT_SCRIPT)
  );
}

```

## Message Routing: Bridging Content Scripts and Background

The content script in [`src/extension/content-scripts/contentScript.ts`](https://github.com/nautls/nautilus-wallet/blob/main/src/extension/content-scripts/contentScript.ts) acts as a secure bridge between the dApp's execution context and the privileged extension background. It receives **external requests** from the injected API and repackages them as **internal requests** using the `InternalRequest` enum.

This architectural pattern ensures that malicious web pages cannot directly access wallet internals. The content script validates message origins before forwarding:

```typescript
// src/extension/content-scripts/contentScript.ts
onMessage(ExternalRequest.SignTx, async ({ data }) => {
  return await sendMessage(InternalRequest.SignTx, { payload, ...data }, BACKGROUND);
});

onMessage(ExternalRequest.SubmitTransaction, async ({ data }) => {
  return await sendMessage(InternalRequest.SubmitTransaction, { payload, ...data }, BACKGROUND);
});

```

## Background Processing: Authorization and UI Coordination

The background script at [`src/extension/background/background.ts`](https://github.com/nautls/nautilus-wallet/blob/main/src/extension/background/background.ts) handles privileged operations using `onMessageAuth` wrappers that verify authentication state before processing.

### Transaction Signing Flow

When `InternalRequest.SignTx` arrives, the background opens a secure UI window via `openWindow()` where users review transaction details. The request enters the `AsyncRequestQueue` system, creating a promise that resolves only after user confirmation or rejection:

```typescript
// src/extension/background/background.ts
onMessageAuth(InternalRequest.SignTx, async (msg) => {
  if (!msg.data.transaction) return invalidRequest("Invalid params.");
  return await openWindow(InternalRequest.SignTx, msg.data, msg.sender.tabId);
});

```

### Transaction Submission Flow

For `InternalRequest.SubmitTransaction`, the background immediately invokes the GraphQL service without UI interaction, assuming the transaction is already signed. The handler includes error mapping to standardized `TxSendErrorCode` values:

```typescript
// src/extension/background/background.ts
onMessageAuth(InternalRequest.SubmitTransaction, async (msg, walletId) => {
  if (!msg.data.transaction) return invalidRequest("Invalid params.");
  try {
    const response = await graphQLService.submitTransaction(msg.data.transaction, walletId);
    return success(response.transactionId);
  } catch (e) {
    return error(TxSendErrorCode.Refused, (e as Error).message);
  }
});

```

## Cryptographic Signing: The Prover Architecture

The actual cryptographic operations occur in [`src/chains/ergo/transaction/prover.ts`](https://github.com/nautls/nautilus-wallet/blob/main/src/chains/ergo/transaction/prover.ts). The **Prover** class abstracts both software-based signing and hardware wallet (Ledger) interactions.

### Software Wallet Signing

For software wallets, the Prover derives secret keys from the wallet's seed and signs transactions in-memory using the `wallet.sign_transaction` method from the Ergo WASM bindings.

### Hardware Wallet Signing

For Ledger devices, the Prover instantiates an `ErgoLedgerApp`, gathers input boxes and witnesses, and requests cryptographic proofs from the connected hardware device without exposing private keys to the browser context.

The unified interface exposes `signTransaction()` which returns a EIP-12 compatible signed transaction object:

```typescript
// src/chains/ergo/transaction/prover.ts
async signTransaction(unsignedTx: EIP12UnsignedTransaction): Promise<SignedTransaction> {
  const { tx, inputs, dataInputs } = this.#parseUnsignedTx(unsignedTx);
  const signed = await this.#signTx(tx, inputs, dataInputs);
  return signed.to_js_eip12();
}

```

## Network Broadcasting: GraphQL Service and Retry Logic

The `graphQLService` in [`src/chains/ergo/services/graphQlService.ts`](https://github.com/nautls/nautilus-wallet/blob/main/src/chains/ergo/services/graphQlService.ts) handles communication with Ergo nodes via GraphQL endpoints. The `submitTransaction` method implements resilience patterns for blockchain interaction.

**Exponential Backoff**: If submission fails because inputs remain in the local UTXO set (indicating a race condition or unconfirmed parent transaction), the service automatically retries with exponential backoff.

**UTXO Synchronization**: Upon successful broadcast, the service updates the local UTXO database via `utxosDbService.addFromTx()` to reflect spent and created boxes:

```typescript
// src/chains/ergo/services/graphQlService.ts
override async submitTransaction(
  signedTransaction: SignedTransaction,
  walletId?: number
): Promise<TransactionEvaluationSuccess> {
  const result = await super.submitTransaction(signedTransaction);
  if (!result.success) throw new Error(result.message);
  else if (walletId) utxosDbService.addFromTx(signedTransaction, walletId);
  return result;
}

```

## Practical Implementation: Complete dApp Integration

Below is a production-ready example demonstrating the full signing and submission flow using the Fleet SDK and Nautilus Wallet:

```typescript
import { TransactionBuilder } from '@fleet-sdk/common';

// 1. Build an unsigned EIP-12 transaction
const unsigned = new TransactionBuilder(currentHeight)
  .addOutput({ address: receiver, value: 1_000_000n })
  .build()
  .toEIP12Object();

// 2. Request cryptographic signing (opens Nautilus confirmation window)
const signed = await window.ergoConnector.nautilus.sign_tx(unsigned);

// 3. Broadcast to the Ergo network
const txId = await window.ergoConnector.nautilus.submit_tx(signed);
console.log('Transaction submitted, id:', txId);

```

This implementation automatically routes through [`src/extension/content-scripts/injected.ts`](https://github.com/nautls/nautilus-wallet/blob/main/src/extension/content-scripts/injected.ts) → [`contentScript.ts`](https://github.com/nautls/nautilus-wallet/blob/main/contentScript.ts) → [`background.ts`](https://github.com/nautls/nautilus-wallet/blob/main/background.ts) → [`prover.ts`](https://github.com/nautls/nautilus-wallet/blob/main/prover.ts) → [`graphQlService.ts`](https://github.com/nautls/nautilus-wallet/blob/main/graphQlService.ts) as detailed above.

## Summary

- **Nautilus Wallet** isolates cryptographic operations from dApps through a three-layer message passing architecture: injected API, content script bridge, and privileged background context.
- **Signing requests** (`sign_tx`) trigger user confirmation windows managed by `AsyncRequestQueue` in the background script before the **Prover** class generates cryptographic proofs.
- **Submission requests** (`submit_tx`) bypass UI confirmation and route directly to `graphQLService.submitTransaction`, which implements exponential backoff retry logic for UTXO conflicts.
- **Hardware wallets** are supported through the `ErgoLedgerApp` abstraction within the Prover, ensuring private keys never enter the browser context.
- Source files [`src/extension/content-scripts/injected.ts`](https://github.com/nautls/nautilus-wallet/blob/main/src/extension/content-scripts/injected.ts), [`src/extension/background/background.ts`](https://github.com/nautls/nautilus-wallet/blob/main/src/extension/background/background.ts), and [`src/chains/ergo/transaction/prover.ts`](https://github.com/nautls/nautilus-wallet/blob/main/src/chains/ergo/transaction/prover.ts) define the critical integration points for developers building Ergo dApps.

## Frequently Asked Questions

### How does Nautilus Wallet protect private keys during transaction signing?

Private keys remain confined to the extension background context or Ledger hardware device. The `Prover` class in [`src/chains/ergo/transaction/prover.ts`](https://github.com/nautls/nautilus-wallet/blob/main/src/chains/ergo/transaction/prover.ts) either uses WASM-bound wallet objects for software signing or communicates with Ledger via the `ErgoLedgerApp` interface. dApps receive only the final signed transaction object, never accessing key material.

### What happens if transaction submission fails due to network issues?

The `graphQLService.submitTransaction` method in [`src/chains/ergo/services/graphQlService.ts`](https://github.com/nautls/nautilus-wallet/blob/main/src/chains/ergo/services/graphQlService.ts) implements retry logic with exponential backoff when inputs remain in the local UTXO set. If the Ergo node rejects