Skip to main content

Module Integration

About

This guide explains how to create and register a signer-side module in the Extra Wallet Signer codebase.

Signer modules:

  • encapsulate view logic, storage/state logic, or transaction signing,
  • are wired into the build system via a small module.config.ts file,
  • are exposed to the consumer layer through a generated loadSafeServiceMethod function.

If you’ve read the consumer-side module guide, think of signer modules as the simpler, logic-focused counterpart: no React components, only functions.

File Structure

Modules exposed to the consumer are organized by functional area:

  • src/services/safeView/ – View logic (e.g., form rendering, prompts).
  • src/services/safeStorage/ – State and storage logic (e.g., saving wallets, renaming portfolios).
  • src/services/safeSigner/ – Transaction signing logic.
  • src/services/core/ – Core logic not tied to a specific domain (e.g., service worker, theme setup).

Each of these directories has a modules/ subdirectory. The example structure:

src/services/safeView/
modules/
qrEncrypt/
submitQrEncryptForm.ts
showQrEncryptForm.ts
module.config.ts

You create a folder per logical module under modules/, place your action files there, and add a module.config.ts alongside them.

Module Structure

Each signer module directory typically contains:

1. Action Files (Functions)

Each file represents a single “action” or capability, for example:

  • submitQrEncryptForm.ts
  • showQrEncryptForm.ts
  • signTransaction.ts
  • addAccounts.ts

Each file should export its function as a named export:

export async function submitQrEncryptForm(/* args */) {
// ...
}

2. Module Config (module.config.ts)

A module.config.ts file must live next to your action files. It must export a named moduleConfig object:

import type { ModuleConfig } from "@/services/types/moduleConfig";

export const moduleConfig: ModuleConfig = {
doesSupportOfflineMode: true,
paths: ["./submitQrEncryptForm", "./showQrEncryptForm"],
names: ["submitQrEncryptForm", "showQrEncryptForm"],
hasExports: true,
// extraPaths: ['./workers/qrWorker'], // optional
};
One Config Per Folder

Each module folder should have one module.config.ts that lists all exported functions and their files in that folder.

Module Config Reference

Top-Level Properties

PropertyDescription
namesRequired. Array of exported function names from your module files. These names are used as keys when calling loadSafeServiceMethod(moduleName).
pathsRequired. Array of relative paths (from module.config.ts) to the files that contain these functions. Usually extensionless (e.g. './submitQrEncryptForm'); the build script resolves .ts.
doesSupportOfflineModeRequired. Whether this module can be used in offline mode. Set to true only if the logic is needed in offline mode.
hasExportsRequired. Controls how the module is loaded: true → functions are dynamically imported and re-exported via loadSafeServiceMethod; false → the module is auto-executed on import.
extraPathsOptional. Extra file paths (e.g., workers, helper scripts) that the build script cannot detect automatically but must be included in the bundle.
namespaths Alignment

Every names[i] must refer to a named export in one of the files listed in paths. If a name doesn’t exist in those files, the generated loader will fail at runtime.

Module Loading and loadSafeServiceMethod

The build system uses your moduleConfig to generate two kinds of loaders:

1. Auto-Executed Modules (hasExports: false)

These modules are imported for their side effects only (no exported functions needed). Example use cases: service worker setup, theme bootstrap, one-time initialization code.

Example output:

// src/services/safeService.ts:
import("./core/serviceWorker/setupServiceWorker");

For such modules:

  • hasExports is set to false.
  • The module is emitted and imported directly; no loadSafeServiceMethod integration is needed.

2. Function Modules (hasExports: true)

For modules with hasExports: true, the build script generates a typed dispatcher:

export async function loadSafeServiceMethod(moduleName: string) {
switch (moduleName) {
case "signTransaction": {
return import("./safeSigner/signTransaction");
}
case "addAccounts": {
return import("./safeStorage/modules/addAccounts/addAccounts");
}
case "createWallet": {
return import("./safeStorage/modules/createWallet/createWallet");
}
// ...
default: {
throw new Error(`Module ${moduleName} not found`);
}
}
}

The link between:

  • the string key (e.g. 'signTransaction'),
  • and the actual file ('./safeSigner/signTransaction')

is defined in your moduleConfig via names and paths.

Naming is Your Contract

The moduleName string is effectively a public API between consumer and signer. Changing or removing an entry in names without updating the consumer will break calls at runtime.

Checklist for Custom Signer Modules

Before you commit a new signer module, verify:

  • names is set:

    • Contains all function names you want to expose.
    • Each name matches a real named export in one of the module files.
  • paths is set:

    • Paths are relative to module.config.ts.
    • They point to the files that contain those exports.
  • doesSupportOfflineMode is set correctly:

    • true only if the module must be present offline.
    • false if it is a purely online module.
  • hasExports matches the intended behavior:

    • true for modules that export functions and are called via loadSafeServiceMethod.
    • false for modules that run once on import and don’t expose functions.
  • extraPaths includes any workers or dynamically referenced files that the build tool can’t see automatically.

  • ✅ Module is placed in the correct service area: safeView, safeStorage, safeSigner, or core.

Common Pitfalls

1. Mismatched Names and Exports

Symptom: ❌ Iframe encountered error: Error: Module X not found at loadSafeServiceMethod at runtime.

  • Check that names includes the exact function names.
  • Ensure those functions are named exports, not default exports.

2. Incorrect Paths

Symptom: Build fails or dynamic imports throw.

  • Confirm paths are relative to module.config.ts.
  • Don’t forget nested folders, e.g. './addAccounts/addAccounts'.

3. Wrong hasExports Setting

  • hasExports: false on a module that you expect to call via loadSafeServiceMethod → your function will never be reachable.
  • hasExports: true on a pure side-effect module → may cause unused code to be included but never executed.

Final Notes

  • Signer modules are discovered and wired up at build time.
  • Missing required fields or invalid configuration will prevent your module from being loaded or cause runtime errors when the consumer calls it.
  • Keep signer logic minimal and focused: perform sensitive operations inside signer modules and expose small, explicit entry points to the consumer.

For questions, issues, or contributions, please contact the core Extra Wallet team or open a PR in the signer repository.