WDK logoWDK documentation

Worklet Bundler Configuration

Configure wdk.config.js and generate Bare worklet bundles with @tetherto/wdk-worklet-bundler

This page shows how to install @tetherto/wdk-worklet-bundler and its Pear runtime, shape wdk.config.js, and generate a Bare worklet bundle for your WDK modules.

Install the packages

Install the bundler as a development dependency and Pear Worklet as a runtime dependency in the host project:

Install The Bundler And Runtime
npm install @tetherto/pear-wrk-wdk@1.0.0-beta.13
npm install --save-dev @tetherto/wdk-worklet-bundler@1.0.0-beta.12

Worklet Bundler beta.12 generates entrypoints that import Pear Worklet. Use Pear Worklet beta.13 for JSON-RPC generic-module calls and events, and for the diagnostic export used by options.handleLeakCheck. The bundler checks whether the runtime package is installed; it does not enforce this version pairing.

Create wdk.config.js

Use init to create a starter config, or write the file yourself:

Initialize A Starter Config
npx wdk-worklet-bundler init

The published config surface accepts networks, optional protocols, generic modules, dynamic-call allowlists, transport, preloadModules, output, and options:

Example wdk.config.js
module.exports = {
  networks: {
    ethereum: {
      package: '@tetherto/wdk-wallet-evm-erc-4337'
    },
    bitcoin: {
      package: '@tetherto/wdk-wallet-btc'
    }
  },

  protocols: {
    moonpay: {
      package: '@tetherto/wdk-protocol-fiat-moonpay'
    }
  },

  modules: {
    preferences: {
      package: '@your-org/wdk-module-preferences',
      factory: 'createModule',
      events: ['changed']
    }
  },

  allowedMethods: {
    ethereum: {
      methods: ['getAddress', 'getBalance'],
      protocols: {
        fiat: {
          moonpay: { methods: [] }
        }
      }
    }
  },

  allowedModuleMethods: {
    preferences: {
      methods: ['getTheme', 'setTheme']
    }
  },

  transport: 'hrpc',

  preloadModules: [
    'spark-frost-bare-addon'
  ],

  output: {
    bundle: './.wdk-bundle/wdk-worklet.bundle.js'
  },

  options: {
    targets: ['ios-arm64', 'android-arm64'],
    convertEsmToCjs: true
  }
}

This example enables ESM-to-CJS conversion because its targets include iOS. Leave conversion disabled only when every target engine can load ES modules, such as an Android V8-only build.

Required fields

networks

networks is required. Each key is a logical network name, and each value must provide a package string for the wallet module to bundle.

The loader also accepts local paths that resolve relative to the config file’s directory:

Use A Local Wallet Package
module.exports = {
  networks: {
    local_dev: {
      package: './local-packages/my-custom-wallet'
    }
  }
}

Optional fields

protocols (optional)

Use protocols when the worklet should preload WDK protocol packages alongside wallet modules.

allowedMethods (optional)

Use allowedMethods to restrict dynamic callMethod() dispatch on generated HRPC and JSON-RPC worklets. Account methods are keyed by the logical networks name. Protocol methods are nested under that network by protocol type and configured protocol name:

Restrict Wallet And Protocol Calls
module.exports = {
  networks: {
    ethereum: { package: '@tetherto/wdk-wallet-evm' }
  },
  protocols: {
    aave: { package: '@tetherto/wdk-protocol-lending-aave-evm' }
  },
  allowedMethods: {
    ethereum: {
      methods: ['getAddress', 'getBalance', 'sendTransaction'],
      protocols: {
        lending: {
          aave: { methods: ['supply', 'withdraw'] }
        }
      }
    }
  }
}

Restrictions are opt-in per exact surface:

  • Omitting allowedMethods, a network, protocols, a protocol type, a protocol name, or its methods field leaves that surface unrestricted.
  • methods: [] denies every dynamic call on the exact account or protocol surface where it appears.
  • A protocol restriction does not inherit the network account methods list. Add the nested protocol entry explicitly.
  • A denied call fails with METHOD_NOT_ALLOWED before the target method runs.

This configuration is not default-deny. List every wallet and protocol surface exposed to an untrusted host, and use an explicit empty list when that surface should accept no dynamic calls.

allowedModuleMethods (optional)

Use allowedModuleMethods to restrict callModule() dispatch on both HRPC and JSON-RPC. Keys must match the names under modules:

Restrict Generic Module Calls
module.exports = {
  modules: {
    preferences: {
      package: '@your-org/wdk-module-preferences'
    }
  },
  allowedModuleMethods: {
    preferences: {
      methods: ['getTheme', 'setTheme']
    }
  }
}

Omitting a module or its methods field leaves that module unrestricted. Set methods: [] to deny every dynamic call on that module. Both generated transports receive this map.

modules (optional)

Use modules for named generic packages that expose a module factory. Each entry supports:

  • package (string, required): Package name or local path.
  • factory (string, optional): Named factory export. When omitted, the package's callable default export is used.
  • events (string[], optional): Event names forwarded from the worklet to the host.

The factory receives { seed, config, capabilities, emit } and can return the module instance or a promise for it. A module that needs the seed must consume it synchronously rather than retain it. The build-time name, such as preferences, must match the key in the host's runtime module config.

Beta.12 generates these modules for both transports. With Pear Worklet beta.13, JSON-RPC initialization constructs them from the encrypted seed pair and matching runtime modules config. Module calls retain JSON-string args; results and event payloads are decoded JSON values. See the Pear Worklet JSON-RPC examples.

transport (optional)

Choose hrpc or jsonrpc. HRPC is the default and is the transport used by React Native Core. JSON-RPC produces a length-prefixed bundle for a native host and enables addon linking by default. In beta.12, ESM-to-CJS conversion is an independent, explicit option for both transports.

preloadModules (optional)

Use preloadModules for native addons or other modules that must be required before the generated worklet starts.

output (optional)

Supported output fields are:

  • bundle: Bundle path. Defaults to ./.wdk-bundle/wdk-worklet.bundle.js for HRPC and ./.wdk-bundle/wdk-worklet.bundle for JSON-RPC.
  • types: Declared in the beta.12 config type, but the generator does not honor a custom value and always writes declarations to ./.wdk/index.d.ts.
  • addons.ios, addons.macos, addons.android: Platform addon directories. Defaults are ./ios-addons, ./mac-addons, and ./android-addons.
  • addonsYml: BareKit Swift dependency file. Defaults to ./ios-addons/addons.yml and is generated when iOS addons are linked.

options (optional)

The published config type supports these build options:

  • minify (boolean): Declared in the beta.12 config type but not read by the bundle path. ESM-to-CJS conversion minifies independently of this field.
  • sourceMaps (boolean): Declared in the beta.12 config type but not read by the bundle path, so it does not produce source maps.
  • targets (string[]): Override the default Bare build hosts. The shipped defaults cover iOS arm64 and simulator targets plus Android arm, arm64, ia32, and x64 hosts.
  • linkAddons (boolean): Link native addons with bare-link. Defaults to true for JSON-RPC and false for HRPC.
  • platforms (('ios' | 'macos' | 'android')[]): Addon platforms. Defaults to all three when addon linking is active.
  • swiftTarget (string): Xcode target written to addons.yml. Defaults to app.
  • handleLeakCheck (number | true, HRPC only): Opt in to repeated handle snapshots while suspended. true uses Pear Worklet beta.13's default 1000 ms interval; a positive number sets the interval in milliseconds. Omit the field to disable it. false, zero, negative numbers, and strings fail config validation.
  • convertEsmToCjs (boolean): Convert the bundle for a Bare port whose JavaScript engine cannot load ES modules, such as JSC or QuickJS. Defaults to false for both transports. Leave it off only when every target uses an ESM-capable engine such as V8.

Configure suspend diagnostics

Set options.handleLeakCheck to inspect handles that remain active after an HRPC worklet suspends:

Sample Handles During Suspend
module.exports = {
  networks: {
    ethereum: { package: '@tetherto/wdk-wallet-evm' }
  },
  transport: 'hrpc',
  options: {
    handleLeakCheck: 5000
  }
}

This emits registerHandleLeakCheck({ tickIntervalMs: 5000 }). The diagnostic logs immediately on suspend and every five seconds until idle or resume. It uses an unreferenced timer and console.warn independently of LOG_LEVEL; it does not close handles or replace module lifecycle handling.

If the installed Pear Worklet package does not expose @tetherto/pear-wrk-wdk/diagnostics/handle-leak-check, generation warns and omits the diagnostic. If the export exists but its optional bare-walk-handles dependency or Bare lifecycle events are unavailable, the runtime helper does nothing.

Beta.12 emits this diagnostic only for HRPC. Setting options.handleLeakCheck in a JSON-RPC config passes validation but does not emit diagnostic registration. Custom JSON-RPC entrypoints can register the Pear Worklet helper themselves.

Configure ESM-to-CJS conversion

Enable conversion in wdk.config.js when any target engine cannot load ESM:

Enable ESM-to-CJS Conversion
module.exports = {
  networks: {
    ethereum: { package: '@tetherto/wdk-wallet-evm' }
  },
  options: {
    convertEsmToCjs: true
  }
}

In the normal generate or generateBundle() path, beta.12 uses one flag for both build-time conversion and the generated runtime loader:

  • After bare-pack, it converts .js, .mjs, and .cjs files to CommonJS, lowers dynamic import(), removes "type": "module" from bundled package manifests, and rebuilds bundle offsets with bare-bundle.
  • It preserves raw bundles and the default UTF-8 CommonJS, ESM, or JSON string wrappers produced by supported bare-pack output suffixes. Wrappers created with a non-UTF-8 --encoding, such as Base64, are not supported.
  • It validates the rewritten file map, offsets, lengths, CommonJS syntax, entrypoint, package manifests, and absence of dynamic imports before generation succeeds.
  • The generated HRPC or JSON-RPC entrypoint routes .mjs files through the CommonJS loader only when conversion is enabled.

There is no beta.12 CLI flag that overrides this setting. Set options.convertEsmToCjs in the config used for the build.

Configure JSON-RPC and native addons

Use JSON-RPC when a native host will implement Pear Worklet's length-prefixed JSON-RPC protocol:

JSON-RPC wdk.config.js
module.exports = {
  networks: {
    ethereum: {
      package: '@tetherto/wdk-wallet-evm'
    }
  },
  transport: 'jsonrpc',
  output: {
    addons: {
      ios: './ios-addons',
      macos: './mac-addons',
      android: './android-addons'
    },
    addonsYml: './ios-addons/addons.yml'
  },
  options: {
    platforms: ['ios', 'android'],
    swiftTarget: 'app',
    convertEsmToCjs: true
  }
}

JSON-RPC generation defaults to a bundle without a .js suffix and enables addon linking. The example opts into ESM-to-CJS conversion because its targets include iOS; omit that option only when every target engine can load ESM. Addon linking includes bare-posix with the other required Bare modules. See the Pear Worklet API reference for framing and supported methods.

Validate dependencies

Use validate to check the config and dependency resolution before you generate the bundle:

Validate wdk.config.js
npx wdk-worklet-bundler validate

Handle peer dependencies

Worklet Bundler beta.12 separates required peers from peers explicitly marked optional through peerDependenciesMeta:

  • During bundle generation, missing required peers are offered for installation. In a non-interactive environment without --install, the CLI prints the exact manual install command and continues; bare-pack can still fail if the bundle needs that peer. The peer scan is skipped by --source-only.
  • Missing optional peers are not prompted for or installed. By default, the bundler passes each one to bare-pack --defer, so an unused optional feature does not block the build.
  • Optional peers detected anywhere in the scanned package tree are not deferred. Beta.12 matches package names across root, nested, scoped, and symlinked package trees; install a peer in an ancestor node_modules visible to the package that imports it, because the same package name elsewhere in the tree can suppress deferral without making that import resolvable. A peer that is required anywhere else in the scanned dependency tree is treated as required and is not deferred.

Use --no-defer-optional-peers when the app uses an optional feature and you want a missing peer to fail during the build:

Require Optional Peers At Build Time
npx wdk-worklet-bundler generate --no-defer-optional-peers

For example, a bundle can build while a missing @ledgerhq/ledger-bitcoin peer is deferred, then fail at runtime if the app uses the corresponding Ledger feature. Install the peer explicitly, or use --no-defer-optional-peers to surface the missing import during bundling.

This behavior is a per-run setting. wdk.config.js has no deferOptionalPeers field. Programmatic callers can set GenerateBundleOptions.deferOptionalPeers to false; omitting it or setting it to true keeps the default deferral behavior.

If a core configured dependency is missing, a non-interactive generate run that will invoke bare-pack exits with status 1 and tells you to install it manually or rerun with --install. A --source-only run can still emit the generated source files.

Generate the bundle

Use generate to build the worklet artifact:

Generate The Worklet Bundle
npx wdk-worklet-bundler generate --install

generate --install can auto-install missing configured wallet, protocol, generic, preload, and Pear Worklet runtime dependencies after the package manager is detected from the project root. In beta.12, the Pear Worklet runtime is included for both HRPC and JSON-RPC, so the generated entrypoint's runtime dependency is surfaced during validation and installation instead of failing later in bare-pack.

Use --source-only when you want the generated .wdk/wdk-worklet.generated.js entrypoint and related artifacts without running bare-pack. This mode also skips bundle conversion. If options.convertEsmToCjs is true and you run bare-pack yourself, convert the packed bundle with convertBundleEsmToCjs() before deployment; the source-only entrypoint already contains the matching .mjs loader patch. Keep the same conversion setting when generating the entrypoint and converting the bundle.

The generate command also accepts --transport hrpc|jsonrpc, --link-addons, --skip-link-addons, --platforms ios,macos,android, and --no-defer-optional-peers. Set transport in wdk.config.js when you rely on its transport-specific default bundle path. In beta.12, --transport changes the generated transport after output paths have already been resolved, so it does not switch .js HRPC output to the extensionless JSON-RPC default, or vice versa. If you use the flag, set output.bundle explicitly to the intended path.

--skip-generation reuses an existing generated entrypoint. If options.convertEsmToCjs changed since that entrypoint was created, regenerate without --skip-generation before building so the runtime .mjs loader matches the bundle conversion setting.

Suspend and resume behavior

Generated HRPC and JSON-RPC entrypoints forward Bare suspend and resume events to the configured generic modules' optional lifecycle methods. Pear Worklet closes modules during full disposal or reinitialization; targeted wallet disposal leaves them running.

HRPC entrypoints additionally suspend and resume both the bare-http1 and bare-https global agents and log Bare suspend, resume, and idle events. JSON-RPC beta.12 does not include that HTTP-agent handling. These lifecycle hooks require no diagnostic flag; options.handleLeakCheck only adds HRPC handle reporting.

Troubleshooting

  • If loadConfig() cannot find a config file, run npx wdk-worklet-bundler init or pass --config \<path\>.
  • If validate or generate reports missing modules, use generate --install or inspect the install command from the exported helper APIs in the API Reference.
  • When bare-pack reports an unresolved package subpath, beta.12 reduces scoped or unscoped subpaths to the installable package root and prints an install command for the package manager detected from the project root. It prints no install hint when the reported specifier is not an installable package, such as a relative or absolute path, private import, URL or builtin-prefixed specifier, or bare scope.
  • If you need to inspect generated source before bundling, use generate --source-only and review the files in .wdk/. For a CommonJS-only target, pack and convert that source as described above before running it.

Need Help?

On this page