# How the dApp RPC Protocol Facilitates Communication with Nautilus Wallet

> Discover how the dApp RPC protocol in Nautilus Wallet builds secure JSON-RPC channels between dApps and the wallet for seamless blockchain interactions. Learn more today.

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

---

**Nautilus Wallet exposes a typed dApp RPC protocol built on the `webext-bridge` library, creating secure asynchronous JSON-RPC channels between injected page scripts, background workers, and UI components to process blockchain requests.**

The **dApp RPC protocol** implemented in the `nautls/nautilus-wallet` repository enables decentralized applications to interact with wallet functionality through a structured, type-safe messaging system. This architecture isolates external web pages from sensitive private keys while maintaining bidirectional communication flow across the browser extension's distinct execution contexts.

## Architecture Overview

The protocol operates across three isolated execution environments that collectively handle requests from web pages to the wallet's internal ledger.

### The Three-Layer Communication Stack

1. **Content-script side** – Injects the `ergoConnector` and `ergo` objects into the page context, serving as the dApp's entry point for blockchain operations.
2. **Background (UI) side** – Receives RPC calls from the content script, forwards them to the wallet UI, and manages response routing.
3. **UI components** – Vue.js components (such as [`BalanceView.vue`](https://github.com/nautls/nautilus-wallet/blob/main/BalanceView.vue)) that execute wallet operations including UTXO fetching and transaction signing.

### Namespace Isolation

All messages are scoped with a unique UUID defined as `RPC_NAMESPACE` in [`src/extension/connector/rpc/protocol.ts`](https://github.com/nautls/nautilus-wallet/blob/main/src/extension/connector/rpc/protocol.ts). This namespace isolation prevents message collisions when multiple wallet extensions are installed in the same browser instance.

## Core Protocol Components

The dApp RPC protocol relies on several TypeScript-defined structures that enforce type safety and ordered execution.

### External vs. Internal Request Types

In [`src/extension/connector/rpc/protocol.ts`](https://github.com/nautls/nautilus-wallet/blob/main/src/extension/connector/rpc/protocol.ts), the protocol distinguishes between two request origins:

- **ExternalRequest** – Declares operations originating from the web page (dApp), such as `GetBalance` or `SignTx`.
- **InternalRequest** – Declares operations originating from the wallet UI components.

Both request types share the same payload shape through the `WithPayload` utility type, ensuring consistent data structures across the bridge.

### Type Safety with ProtocolMap

The `ProtocolMap` interface in [`src/types/d.ts/webext-rpc.d.ts`](https://github.com/nautls/nautilus-wallet/blob/main/src/types/d.ts/webext-rpc.d.ts) links each request enum member to its specific request/response type pair. This TypeScript-only construct enables `webext-bridge` to generate fully type-safe RPC calls, preventing payload mismatches at compile time.

### Request Serialization via AsyncRequestQueue

The `AsyncRequestQueue` class in [`src/extension/connector/rpc/asyncRequestQueue.ts`](https://github.com/nautls/nautilus-wallet/blob/main/src/extension/connector/rpc/asyncRequestQueue.ts) guarantees ordered, single-threaded processing of UI-side requests. When the background script receives an `InternalRequest`, it enqueues an `AsyncRequest` object and resolves the associated promise only when the UI component explicitly signals completion through `success()` or `error()` callbacks.

## Message Flow and Execution

The dApp RPC protocol follows a strict four-step pipeline to process blockchain requests securely.

### Step 1: dApp to Content Script

Web applications interact with the injected API:

```typescript
// Example dApp code
await ergoConnector.nautilus.connect();
const utxos = await ergo.get_utxos({ target: "default" });
const signedTx = await ergo.sign_tx({ transaction: unsignedTx });

```

Each method call maps to an `ExternalRequest` enum value defined in [`protocol.ts`](https://github.com/nautls/nautilus-wallet/blob/main/protocol.ts) and transmits via `webext-bridge`.

### Step 2: Content Script to Background

The bridge routes the message to the background script, which converts the `ExternalRequest` into an `InternalRequest` with identical payload structure.

### Step 3: Background to UI Components

The background script enqueues the request via `queue.enqueue()` exposed in [`src/extension/connector/rpc/uiRpcHandlers.ts`](https://github.com/nautls/nautilus-wallet/blob/main/src/extension/connector/rpc/uiRpcHandlers.ts). UI components consume these requests through event listeners and execute wallet operations against the internal ledger.

### Step 4: Response to dApp

UI components resolve the queued request using the `success(data)` or `error(err)` helpers from [`protocol.ts`](https://github.com/nautls/nautilus-wallet/blob/main/protocol.ts). The result traverses back through the bridge layers, ultimately resolving the original promise in the web page's execution context.

## Implementation Examples

### Registering RPC Hooks

The background script initializes the protocol in [`src/extension/connector/rpc/uiRpcHandlers.ts`](https://github.com/nautls/nautilus-wallet/blob/main/src/extension/connector/rpc/uiRpcHandlers.ts):

```typescript
export const queue = new AsyncRequestQueue();

export function registerRpcHooks() {
  bridge.on(ExternalRequest.GetBalance, async (payload) => {
    const result = await queue.enqueue({ 
      type: InternalRequest.GetBalance, 
      payload 
    });
    return result;
  });
}

```

The `registerRpcHooks()` function is invoked during extension startup from [`src/extension/connector/main.ts`](https://github.com/nautls/nautilus-wallet/blob/main/src/extension/connector/main.ts), wiring the entire communication system.

### UI Component Request Handling

Vue components interact with the queue to process operations:

```typescript
// src/extension/connector/views/BalanceView.vue
import { queue } from "@/extension/connector/rpc/uiRpcHandlers";

queue.on(InternalRequest.GetBalance, async (msg) => {
  const balances = await ledger.getBalances();
  return success(balances);
});

```

This pattern ensures that sensitive wallet operations execute only within the trusted UI context while maintaining async communication with the untrusted page environment.

## Summary

- The **dApp RPC protocol** uses `webext-bridge` to establish typed JSON-RPC channels across browser extension contexts.
- **Namespace isolation** via `RPC_NAMESPACE` prevents cross-extension interference.
- **Dual request types** (`ExternalRequest` and `InternalRequest`) in [`protocol.ts`](https://github.com/nautls/nautilus-wallet/blob/main/protocol.ts) maintain security boundaries while sharing payload structures.
- **AsyncRequestQueue** serializes UI-side processing to prevent race conditions during user approvals.
- The entry point in [`src/extension/connector/main.ts`](https://github.com/nautls/nautilus-wallet/blob/main/src/extension/connector/main.ts) bootstraps the entire system by calling `registerRpcHooks()`.

## Frequently Asked Questions

### What library powers the dApp RPC protocol in Nautilus Wallet?

The protocol builds upon the `webext-bridge` library from Google Chrome Labs, which provides the underlying message transport mechanism between content scripts, background workers, and extension pages. This library handles serialization, routing, and promise-based async communication while the Nautilus-specific code in [`protocol.ts`](https://github.com/nautls/nautilus-wallet/blob/main/protocol.ts) defines the typed request/response contracts.

### How does Nautilus Wallet prevent cross-extension message collisions?

All RPC messages are prefixed with a unique namespace UUID (`RPC_NAMESPACE`) defined in [`src/extension/connector/rpc/protocol.ts`](https://github.com/nautls/nautilus-wallet/blob/main/src/extension/connector/rpc/protocol.ts). This namespace scoping ensures that messages intended for Nautilus Wallet are never intercepted or processed by other browser extensions that might also use `webext-bridge`, preventing both security vulnerabilities and logical errors.

### What is the difference between ExternalRequest and InternalRequest?

`ExternalRequest` enumerates operations that originate from untrusted web pages (dApps) calling the injected `ergo` object, while `InternalRequest` enumerates the same operations when forwarded to the trusted UI context. The background script in [`uiRpcHandlers.ts`](https://github.com/nautls/nautilus-wallet/blob/main/uiRpcHandlers.ts) acts as a translator, converting external calls into internal ones while preserving the payload structure through the `WithPayload` generic type.

### How are concurrent dApp requests sequenced to avoid race conditions?

The `AsyncRequestQueue` class in [`asyncRequestQueue.ts`](https://github.com/nautls/nautilus-wallet/blob/main/asyncRequestQueue.ts) implements a first-in-first-out queue that processes only one UI-side request at a time. When multiple dApps or rapid successive calls request wallet operations, the background script enqueues each as an `AsyncRequest` and awaits explicit resolution. This guarantees that user approval flows—such as transaction signing—complete sequentially rather than overlapping, preventing state corruption and user interface conflicts.