WDK logoWDK documentation

Pear Worklet WDK Configuration

Configure Pear Worklet HRPC and JSON-RPC contexts, WDK payloads, and generic modules

This page explains how to build the worklet context, shape the worklet config payload, initialize WDK, call generic modules, choose a transport, and inspect suspend delays.

Worklet Context

You can bind the shipped RPC handlers to your Bare worklet using registerRpcHandlers():

Register RPC Handlers
require('bare-node-runtime/global')

const { registerRpcHandlers } = require('@tetherto/pear-wrk-wdk/worklet')
const wdkModule = require('@tetherto/wdk', { with: { imports: 'bare-node-runtime/imports' } })
const { createModule: createPreferencesModule } = require('@your-org/wdk-module-preferences')

const WDK = wdkModule.default || wdkModule.WDK || wdkModule
const walletCache = {}

function loadWalletManager(network) {
  if (walletCache[network]) return walletCache[network]

  let walletModule
  if (network === 'ethereum') {
    walletModule = require('@tetherto/wdk-wallet-evm', { with: { imports: 'bare-node-runtime/imports' } })
  }
  if (network === 'spark') {
    walletModule = require('@tetherto/wdk-wallet-spark', { with: { imports: 'bare-node-runtime/imports' } })
  }

  if (walletModule) walletCache[network] = walletModule.default || walletModule
  return walletCache[network] || null
}

const walletManagers = new Proxy({}, {
  get: (_, network) => loadWalletManager(network),
  has: (_, network) => ['ethereum', 'spark'].includes(network)
})

const context = {
  wdk: null,
  WDK,
  walletManagers,
  protocolManagers: {},
  moduleManagers: {
    preferences: {
      createModule: createPreferencesModule,
      events: ['changed']
    }
  },
  allowedMethods: {
    ethereum: {
      methods: ['getAddress', 'getBalance', 'sendTransaction']
    }
  },
  allowedModuleMethods: {
    preferences: {
      methods: ['getTheme', 'setTheme']
    }
  },
  capabilities: {},
  wdkLoadError: null
}

module.exports = (rpc) => {
  registerRpcHandlers(rpc, context)

  Bare.on('suspend', async () => {
    await context.moduleRuntime?.suspendAll()
  })

  Bare.on('resume', async () => {
    await context.moduleRuntime?.resumeAll()
  })
}

Required Context Fields

  • wdk: The current WDK instance. Set this to null before the first initialization.
  • WDK: The WDK constructor used to create the seeded instance.
  • walletManagers: A map from blockchain name to wallet manager implementation.
  • protocolManagers: A map from protocol name to protocol manager implementation.
  • wdkLoadError: Any startup error captured while loading WDK. Use null when there is no load failure.

For generic modules on either transport, moduleManagers optionally maps module names to { createModule, events? }. The factory receives { seed, config, capabilities, emit } and can return an instance or a promise. capabilities is an optional host-supplied object and is empty by default. The runtime manages moduleRuntime and moduleInstances; do not initialize those fields yourself. Manual integrations must forward Bare suspend and resume events as shown if module instances should receive those lifecycle calls. See Worklet Bundler lifecycle behavior for generated entrypoints.

Restrict Dynamic Methods

callMethod() and callModule() dispatch method names supplied by the host. Add allowlists to RpcContext when that host should not reach every method on the resolved object.

Wallet and Protocol Methods

allowedMethods is keyed by network. A network's direct methods array applies to its wallet account. Protocol restrictions are nested by protocol type and protocol name:

Restrict Dynamic Wallet And Protocol Calls
const context = {
  // Other required context fields...
  allowedMethods: {
    ethereum: {
      methods: ['getAddress', 'getBalance', 'sendTransaction'],
      protocols: {
        lending: {
          aave: {
            methods: ['supply', 'withdraw']
          }
        }
      }
    }
  }
}

This map applies to the shared HRPC and JSON-RPC callMethod() handler. Restrictions are opt-in per surface:

  • Omitting the map, a network, protocols, a protocol type, a protocol name, or methods leaves that exact surface unrestricted.
  • An explicit methods: [] denies every call on that exact account or protocol surface.
  • Protocol calls use their nested list rather than falling back to the account list.
  • A denied method fails before account or protocol dispatch with METHOD_NOT_ALLOWED.

Generic Module Methods

allowedModuleMethods is keyed by the moduleManagers name and applies to callModule() on both HRPC and JSON-RPC:

Restrict Dynamic Module Calls
const context = {
  // Other required context fields...
  allowedModuleMethods: {
    preferences: {
      methods: ['getTheme', 'setTheme']
    }
  }
}

Omitting a module or its methods field leaves that module unrestricted. Set methods: [] to deny every dynamic call on it.

These maps are not default-deny. List every dynamic surface exposed to an untrusted host. The beta.13 runtime reports denied calls with METHOD_NOT_ALLOWED, but the published error-code declaration omits that new member; handle the literal runtime code until the declaration is corrected.

Worklet Config Payload

Both initializeWDK() and resetWdkWallets() expect config to be a JSON string. The decoded object must contain at least one entry under networks.

Worklet Config JSON
const workletConfig = {
  networks: {
    ethereum: {
      blockchain: 'ethereum',
      config: {
        provider: 'https://rpc.ankr.com/eth_sepolia'
      }
    }
  },
  protocols: {
    moonpay: {
      blockchain: 'ethereum',
      protocolName: 'moonpay',
      config: {
        environment: 'sandbox'
      }
    }
  },
  modules: {
    preferences: {
      storagePath: '/app-data/preferences'
    }
  }
}

Payload Rules

  • networks is required and must contain at least one network entry.
  • Each network entry must include blockchain and an object config.
  • protocols is optional during initialization.
  • modules is optional and contains runtime config for named generic modules. Each key must match a moduleManagers key in the worklet context and the corresponding build-time Worklet Bundler module name.
  • resetWdkWallets() reads only the networks portion of the decoded config.

JSON-RPC generic modules require Pear Worklet beta.13. Supply the same moduleManagers, allowedModuleMethods, and runtime modules config used by HRPC. If generating the entrypoint, use Worklet Bundler beta.12 with Pear Worklet beta.13.

Initialize WDK

You can create and register the WDK instance inside the worklet using initializeWDK():

Initialize WDK
const { HRPC } = require('@tetherto/pear-wrk-wdk')

const hrpc = new HRPC(ipcStream)

await hrpc.initializeWDK({
  encryptionKey: secrets.encryptionKey,
  encryptedSeed: secrets.encryptedSeedBuffer,
  config: JSON.stringify(workletConfig)
})

Initialization Rules

  • Pass both encryptionKey and encryptedSeed, or omit both together.
  • On first initialization, the worklet must receive an encrypted seed pair so it can create context.wdk.
  • If context.wdk already exists, a later initializeWDK() call disposes the existing instance and closes its generic modules before re-registering wallets and protocols from the new config.
  • In beta.13, generic modules on either transport are constructed only when that initializeWDK() request includes both encryptionKey and encryptedSeed. A seedless reinitialization closes existing module instances but does not rebuild them, even when config.modules is present. Supply the seed pair on every initialization that must construct or reconstruct modules.
  • Module close() is called during full disposal or reinitialization. Targeted blockchain disposal leaves generic modules running. Optional suspend() and resume() methods run only when the host forwards Bare lifecycle events; the manual context above does so.

Reset Selected Wallets

You can selectively dispose and re-register wallet modules using resetWdkWallets():

Reset Selected Wallet Modules
await hrpc.resetWdkWallets({
  config: JSON.stringify({
    networks: {
      ethereum: {
        blockchain: 'ethereum',
        config: {
          provider: 'https://rpc.ankr.com/eth_sepolia'
        }
      }
    }
  })
})

Reset Rules

  • resetWdkWallets() requires an existing initialized context.wdk.
  • The handler calls wdk.dispose(targetChains) with the blockchains extracted from config.networks.
  • Only wallets listed in the request networks object are re-registered.
  • The reset flow does not re-register protocols.
  • The reset flow does not close or reconstruct generic modules; existing module instances keep running.

Call Wallet and Protocol Methods

You can execute wallet account methods through callMethod():

Call a Wallet Method
const result = await hrpc.callMethod({
  methodName: 'getAddress',
  network: 'ethereum',
  accountIndex: 0
})

Call Method Notes

  • args is optional and must be a JSON string when provided.
  • options is optional and must be a JSON string when provided.
  • When args decodes to an array, the handler spreads the values as positional method arguments.
  • When args decodes to an object or primitive, the handler passes it as a single argument.
  • Set options.protocolType to swap, swidge, bridge, lending, or fiat to call a protocol wrapper. Every protocol call requires a non-empty options.protocolName.
  • When context.allowedMethods contains the target account or protocol surface, methodName must appear in that exact surface's methods array.
  • In beta.13, a missing wallet or protocol method fails with BAD_REQUEST. The removed options.defaultValue field no longer supplies a fallback; catch unsupported-method errors in the host.

Call Generic Module Methods

On an HRPC worklet configured with matching moduleManagers and runtime modules, call a module method by name:

Call A Generic Module
const response = await hrpc.callModule({
  module: 'preferences',
  method: 'getTheme',
  args: JSON.stringify([])
})

const theme = response.result ? JSON.parse(response.result) : undefined

args is an optional JSON string. Arrays are spread into positional arguments; a non-array value is passed as one argument. Promise results are awaited, .toArray() results are materialized, and Uint8Array values are normalized to hex before the response is serialized.

When context.allowedModuleMethods contains the target module, method must appear in its methods array. A denied call returns METHOD_NOT_ALLOWED before module instance lookup or dispatch.

Subscribe to events declared by the module manager:

Receive A Module Event
hrpc.onModuleEvent(({ module, event, payload }) => {
  if (module === 'preferences' && event === 'changed') {
    const value = payload ? JSON.parse(payload) : undefined
    console.log('Preferences changed:', value)
  }
})

JSON-RPC Transport

Native hosts can register the separate framed JSON-RPC server entrypoint:

Register JSON-RPC Handlers
const { registerJsonRpcHandlers } = require('@tetherto/pear-wrk-wdk/jsonrpc')

module.exports = (ipc) => {
  registerJsonRpcHandlers(ipc, context)
}

Messages are UTF-8 JSON-RPC 2.0 objects prefixed by a four-byte unsigned big-endian payload length. Requests require an ID, and IDs must be unique while a request is in flight. The package exports no JSON-RPC host/client helper; the native host must implement framing and correlation.

JSON-RPC beta.13 supports generic-module initialization, callModule, allowedModuleMethods, and moduleEvent notifications alongside wallet and protocol operations. resetWdkWallets remains HRPC-only. See the API reference for all methods and response shapes.

Send a module call as a framed JSON-RPC request; args remains a JSON string:

Call A Module Over JSON-RPC
{"jsonrpc":"2.0","id":1,"method":"callModule","params":{"module":"preferences","method":"getTheme","args":"[]"}}

For a module that returns 'dark', the response contains the decoded value inside result.result:

JSON-RPC Module Result
{"jsonrpc":"2.0","id":1,"result":{"result":"dark"}}

A declared module event arrives as a notification without an id; params.payload is already decoded:

JSON-RPC Module Event
{"jsonrpc":"2.0","method":"moduleEvent","params":{"module":"preferences","event":"changed","payload":{"theme":"dark"}}}

Manual JSON-RPC entrypoints must forward Bare suspend and resume events to context.moduleRuntime as shown in the HRPC context example. Registering JSON-RPC handlers alone does not install those lifecycle listeners.

Beta.13 omits JSON-RPC parameters and results from its INFO request/response logs and moves wallet-call arguments to DEBUG. DEBUG can still expose sensitive arguments, and module errors or application logs are not generally redacted. Keep production logging at its default ERROR level and avoid sensitive values in custom logs and error messages.

Inspect Suspend Delays

Register the optional registerHandleLeakCheck() helper once in a Bare worklet entrypoint:

Inspect Bare Handles During Suspend
const { registerHandleLeakCheck } = require('@tetherto/pear-wrk-wdk/diagnostics/handle-leak-check')

registerHandleLeakCheck({ tickIntervalMs: 1000 })

The helper logs a handle snapshot immediately on suspend, then repeats every tickIntervalMs milliseconds until idle or resume. The default interval is 1000 ms. Its timer is unreferenced so the diagnostic itself does not keep the event loop active. It reports handles; it does not close them or suspend modules.

Provide a positive interval; the beta.13 helper passes the value to the timer without validating it. Registration is a no-op if the optional bare-walk-handles dependency or Bare lifecycle events are unavailable. Diagnostic output uses console.warn regardless of LOG_LEVEL, so register it only when you need handle diagnostics.


Need Help?

On this page