Skip to content

wallet-cli: Parameterise RpcHandler with struct-validated dispatch #8777

@sirtimid

Description

@sirtimid

Surfaced as a deferred follow-up from PR #8446.

Problem

`RpcHandler` (`packages/wallet-cli/src/daemon/types.ts`) is currently typed as `(params: Json) => Promise<Json | void>`. Every handler has to re-validate `params` from scratch — see the `call` handler in `daemon-entry.ts` doing `Array.isArray(params) && typeof params[0] === 'string'` before casting to `[string, ...Json[]]`. The next handler that needs structured params will copy-paste the same boilerplate.

The server-side `isJsonRpcRequest` check in `rpc-socket-server.ts` already validates the wire frame, but per-method param validation is still ad-hoc and the typed messenger surface is bypassed with an `as any` cast in the `call` handler.

Proposed direction

Parameterise the handler type over its params and result, and add a small `defineHandler` helper that owns the struct guard:

```ts
export type RpcHandler<TParams extends JsonRpcParams, TResult extends Json> =
(params: TParams) => Promise<TResult>;

export function defineHandler<TParams, TResult>(
paramsStruct: Struct<TParams>,
run: (params: TParams) => Promise<TResult>,
): RpcHandler<TParams & JsonRpcParams, TResult & Json> {
return async (params) => {
const validated = assertStruct(params, paramsStruct);
return run(validated);
};
}
```

Then rewrite each handler to declare its params struct (e.g. `getStatus` takes `undefined`, `call` takes a tuple of `[string, ...Json[]]`). The runtime guard runs once per request inside the server, and the handler body no longer needs the boilerplate.

Acceptance

  • All handlers in `daemon-entry.ts` go through the new helper.
  • The `as any` cast on `wallet.messenger.call` is replaced by a typed dispatcher whose params type comes from the struct.
  • `rpc-socket-server.ts`'s `handleRequest` validates params via the registered struct before calling the handler.
  • Tests updated; coverage stays at 100%.

Context

Metadata

Metadata

Assignees

No one assigned

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions