Skip to main content

Documentation Index

Fetch the complete documentation index at: https://mintlify.com/Koniverse/SubWallet-Extension/llms.txt

Use this file to discover all available pages before exploring further.

Overview

The messaging system enables communication between the extension’s UI layer and background service. It uses Chrome’s runtime messaging API with a port-based connection that supports both one-time requests and long-lived subscriptions.

Core Architecture

Message Types

Messages are strongly typed using TypeScript discriminated unions:
export type MessageTypes = keyof RequestSignatures;

export type MessageTypesWithSubscriptions = keyof SubscriptionMessageTypes;
export type MessageTypesWithNoSubscriptions = Exclude<MessageTypes, keyof SubscriptionMessageTypes>;
export type MessageTypesWithNullRequest = NullKeys<RequestTypes>;

Message Structure

id
string
required
Unique identifier generated for each message
message
MessageType
required
The message type identifier (e.g., 'pri(accounts.create.suriV2)')
request
RequestTypes[TMessageType]
required
The request payload specific to the message type
origin
'page' | 'extension' | string
required
Origin of the message sender

Core Functions

sendMessage

Send a message to the background service and receive a response.
export function sendMessage<TMessageType extends MessageTypes>(
  message: TMessageType,
  request?: RequestTypes[TMessageType],
  subscriber?: (data: unknown) => void
): Promise<ResponseTypes[TMessageType]>
message
MessageType
required
The message type identifier
request
RequestTypes[TMessageType]
Request payload (optional for null-request messages)
subscriber
function
Callback function for subscription-based messages
Returns: Promise<ResponseTypes[TMessageType]>

Example: Simple Request

import { sendMessage } from '@subwallet/extension-koni-ui/messaging/base';

// Ping the background service
const response = await sendMessage('pri(ping)', null);

Example: Request with Payload

// Create account with seed phrase
const result = await sendMessage('pri(accounts.create.suriV2)', {
  name: 'My Account',
  password: 'secure-password',
  suri: 'seed phrase words...',
  types: ['sr25519']
});

Example: Subscription

// Subscribe to notifications
await sendMessage(
  'pri(notifications.subscribe)',
  null,
  (notifications) => {
    console.log('Received notifications:', notifications);
  }
);

subscribeMessage

Create a subscription that starts immediately and returns an unsubscribe function.
export function subscribeMessage<TMessageType extends MessageTypesWithSubscriptions>(
  message: TMessageType,
  request: RequestTypes[TMessageType],
  callback: (data: ResponseTypes[TMessageType]) => void,
  subscriber: (data: SubscriptionMessageTypes[TMessageType]) => void
): {
  promise: Promise<ResponseTypes[TMessageType]>,
  unsub: () => void
}
message
MessageType
required
The subscription message type
request
RequestTypes[TMessageType]
required
Request payload
callback
function
required
Called once when subscription is established
subscriber
function
required
Called for each subscription update
Returns: Object with promise and unsub function

Example

import { subscribeMessage } from '@subwallet/extension-koni-ui/messaging/base';

const { promise, unsub } = subscribeMessage(
  'pri(transaction.history.subscribe)',
  { address: '0x...', chain: 'polkadot' },
  (initial) => console.log('Initial data:', initial),
  (update) => console.log('Update:', update)
);

// Later, unsubscribe
unsub();

lazySubscribeMessage

Create a subscription that can be started manually.
export function lazySubscribeMessage<TMessageType extends MessageTypesWithSubscriptions>(
  message: TMessageType,
  request: RequestTypes[TMessageType],
  callback: (data: ResponseTypes[TMessageType]) => void,
  subscriber: (data: SubscriptionMessageTypes[TMessageType]) => void
): {
  promise: Promise<ResponseTypes[TMessageType]>,
  start: () => void,
  unsub: () => void
}
Returns: Object with promise, start, and unsub functions

Example

const { promise, start, unsub } = lazySubscribeMessage(
  'pri(price.subscribeCurrentTokenPrice)',
  'bitcoin-price-id',
  (initial) => console.log('Initial price:', initial),
  (update) => console.log('Price update:', update)
);

// Start subscription when needed
start();

// Stop when done
unsub();

Message Categories

Account Messages

// Create account from seed/private key
await sendMessage('pri(accounts.create.suriV2)', request);

// Export account as JSON
await sendMessage('pri(accounts.export.json)', { address, password });

// Save current account
await sendMessage('pri(accounts.saveCurrentProxy)', { address });

Settings Messages

// Subscribe to settings changes
await sendMessage('pri(settings.subscribe)', data, callback);

// Save theme preference
await sendMessage('pri(settings.saveTheme)', 'dark');

// Toggle balance visibility
await sendMessage('pri(settings.changeBalancesVisibility)', null);

Transaction Messages

// Get transaction details
await sendMessage('pri(transactions.getOne)', { hash, chain });

// Subscribe to transaction history
await sendMessage(
  'pri(transaction.history.subscribe)',
  { address, chain },
  callback
);

Price & Chart Messages

// Subscribe to current token price
await sendMessage('pri(price.subscribeCurrentTokenPrice)', priceId, callback);

// Get historical price data
await sendMessage('pri(price.getHistory)', { priceId, timeframe });

// Check if chart is available
await sendMessage('pri(price.checkCoinGeckoPriceSupport)', priceId);

Port Connection

The messaging system maintains a persistent connection to the background service:
const PORT_EXTENSION = 'subwallet-extension';

let port: chrome.runtime.Port;
port = chrome.runtime.connect({ name: PORT_EXTENSION });

Auto-Reconnection

The port automatically reconnects if the connection is lost:
port.onDisconnect.addListener(() => {
  const err = chrome.runtime.lastError;
  if (err) {
    console.warn(`${err.message}, Reconnecting to the port.`);
    setTimeout(onConnectPort, 1000);
  }
});

Message Handler

Internal handler structure for managing pending requests:
interface Handler {
  resolve: (data: any) => void;
  reject: (error: Error) => void;
  subscriber?: (data: any) => void;
}

type Handlers = Record<string, Handler>;
Handlers are stored by message ID and automatically cleaned up after one-time requests.

Error Handling

port.onMessage.addListener((data) => {
  const handler = handlers[data.id];
  
  if (!handler) {
    console.error(`Unknown response: ${JSON.stringify(data)}`);
    return;
  }
  
  if (data.error) {
    handler.reject(new Error(data.error));
  } else {
    handler.resolve(data.response);
  }
});

Best Practices

  1. Use typed messages: Always use the predefined message type constants to ensure type safety
  2. Handle errors: Wrap sendMessage calls in try-catch blocks
try {
  const result = await sendMessage('pri(accounts.create.suriV2)', request);
} catch (error) {
  console.error('Account creation failed:', error);
}
  1. Cleanup subscriptions: Always call unsub() when subscriptions are no longer needed
useEffect(() => {
  const { unsub } = subscribeMessage(...);
  return () => unsub();
}, []);
  1. Batch related operations: Combine related requests to reduce message overhead

Common Patterns

Request-Response Pattern

// One-time request
const data = await sendMessage('pri(someMessage)', request);

Subscribe-Update Pattern

// Long-lived subscription
const { unsub } = subscribeMessage(
  'pri(someMessage.subscribe)',
  request,
  (initial) => setState(initial),
  (update) => setState(update)
);

Lazy Initialization Pattern

// Create subscription but start later
const { start, unsub } = lazySubscribeMessage(...);

// Start when component mounts
useEffect(() => {
  start();
  return () => unsub();
}, []);

Build docs developers (and LLMs) love