From 626d3bc8aedae1667cd602059271d7380bee4818 Mon Sep 17 00:00:00 2001 From: Allen Date: Sat, 9 May 2026 13:19:06 -0400 Subject: [PATCH] Fix JsCommands schema stub in openrpc-dapp-api.json MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The components.schemas.JsCommands entry is still the placeholder introduced in #40 ("Api spec placeholders", 2025-06-05): "JsCommands": { "title": "JsCommands", "type": "object", "description": "Structure representing JS commands for transaction execution", "additionalProperties": true } This stub has never been filled in, even though PRs #904, #1259, #1339, and #1609 have all touched JsPrepareSubmissionRequest. It contradicts four other parts of this same repo: 1. Canton Ledger API — api-specs/ledger-api/3.4.12/openapi.yaml lines 4736-4751 define JsCommands.commands as a required, non-empty type: array whose items are $ref Command. 2. SDK docs — docs/dapp-building/dapp-sdk/usage.md:205 shows { commands: [ { CreateCommand: {...} } ] }. 3. Canonical example — examples/ping/src/commands/createPingCommand.ts:9 builds commands: [ { CreateCommand: {...} } ]. 4. Upstream runtime — wallet-gateway/remote/src/dapp-api/controller.ts:243 accesses params.commands?.[0], only valid if commands is an array. This patch replaces the stub with: - Four named variant schemas (CreateCommand, ExerciseCommand, CreateAndExerciseCommand, ExerciseByKeyCommand), each a one-key object with opaque inner shape (additionalProperties: true, description defers to Canton Ledger API). - A Command oneOf tagged-union referencing the four variants. - JsCommands redefined as an array of Command, minItems: 1. The oneOf discriminated union path is what generated clients need to emit a clean z.union([...]) with openapi-zod-client (rather than the superRefine fallback the default json-schema-to-zod path produces). Severity: blocks CIP-0103 interop for any wallet that generates its JSON-RPC validators from the published OpenRPC spec. Downstream fix verified in 0xsend/canton-monorepo#2810 (Send Canton Wallet webext), with a CIP-0103 conformance Playwright spec driving the upstream examples/ping dApp against a patched wallet. Staging fork: 0xsend/wallet-gateway (private, for pre-upstream review). This commit targets the detached pin f30e464c to match the canton-monorepo work-dir reference. Sealed-by-rl-review: a3261c182921581712ca124f0018bfcd45c04f48 (diff-range, 6 commits) Signed-off-by: Allen --- Squashed commits --- 5c24ca75 Fix JsCommands schema stub in openrpc-dapp-api.json dcf81def fix(review-fix-up): address major-1 schema titles 513ce47c fix(review-fix-up): address major-2 generated commands dbf68f5d fix(review-fix-up): address major-1: refresh dapp clients a1dcb80d fix(review-fix-up): address major-1: sync dapp openrpc schemas a3261c18 fix(review-fix-up): address major-2: accept command arrays --- api-specs/openrpc-dapp-api.json | 83 ++++++++++++++++++- .../src/index.ts | 56 ++++++++++++- core/wallet-dapp-rpc-client/src/index.ts | 56 ++++++++++++- core/wallet-dapp-rpc-client/src/openrpc.json | 83 ++++++++++++++++++- sdk/dapp-sdk/src/dapp-api/rpc-gen/typings.ts | 56 ++++++++++++- .../extension/src/dapp-api/rpc-gen/typings.ts | 56 ++++++++++++- .../remote/src/dapp-api/rpc-gen/typings.ts | 56 ++++++++++++- wallet-gateway/remote/src/utils.ts | 3 +- 8 files changed, 432 insertions(+), 17 deletions(-) diff --git a/api-specs/openrpc-dapp-api.json b/api-specs/openrpc-dapp-api.json index e8a7afdcd..9fe3ac96c 100644 --- a/api-specs/openrpc-dapp-api.json +++ b/api-specs/openrpc-dapp-api.json @@ -324,11 +324,88 @@ }, "required": ["createdEventBlob"] }, + "CreateCommand": { + "title": "CreateCommand", + "type": "object", + "additionalProperties": false, + "required": ["CreateCommand"], + "properties": { + "CreateCommand": { + "title": "CreateCommandPayload", + "type": "object", + "additionalProperties": true, + "description": "Inner shape is defined by the Canton Ledger API CreateCommand schema; do not re-specify here." + } + } + }, + "ExerciseCommand": { + "title": "ExerciseCommand", + "type": "object", + "additionalProperties": false, + "required": ["ExerciseCommand"], + "properties": { + "ExerciseCommand": { + "title": "ExerciseCommandPayload", + "type": "object", + "additionalProperties": true, + "description": "Inner shape is defined by the Canton Ledger API ExerciseCommand schema; do not re-specify here." + } + } + }, + "CreateAndExerciseCommand": { + "title": "CreateAndExerciseCommand", + "type": "object", + "additionalProperties": false, + "required": ["CreateAndExerciseCommand"], + "properties": { + "CreateAndExerciseCommand": { + "title": "CreateAndExerciseCommandPayload", + "type": "object", + "additionalProperties": true, + "description": "Inner shape is defined by the Canton Ledger API CreateAndExerciseCommand schema; do not re-specify here." + } + } + }, + "ExerciseByKeyCommand": { + "title": "ExerciseByKeyCommand", + "type": "object", + "additionalProperties": false, + "required": ["ExerciseByKeyCommand"], + "properties": { + "ExerciseByKeyCommand": { + "title": "ExerciseByKeyCommandPayload", + "type": "object", + "additionalProperties": true, + "description": "Inner shape is defined by the Canton Ledger API ExerciseByKeyCommand schema; do not re-specify here." + } + } + }, + "Command": { + "title": "Command", + "description": "A Daml command atom. Mirror of the Canton Ledger API Command union; inner shapes are intentionally opaque so the dApp layer never drifts from the Ledger API contract.", + "oneOf": [ + { + "$ref": "api-specs/openrpc-dapp-api.json#/components/schemas/CreateCommand" + }, + { + "$ref": "api-specs/openrpc-dapp-api.json#/components/schemas/ExerciseCommand" + }, + { + "$ref": "api-specs/openrpc-dapp-api.json#/components/schemas/CreateAndExerciseCommand" + }, + { + "$ref": "api-specs/openrpc-dapp-api.json#/components/schemas/ExerciseByKeyCommand" + } + ] + }, "JsCommands": { "title": "JsCommands", - "type": "object", - "description": "Structure representing JS commands for transaction execution", - "additionalProperties": true + "type": "array", + "minItems": 1, + "description": "Non-empty array of Daml command atoms to submit atomically as a single transaction.", + "items": { + "$ref": "api-specs/openrpc-dapp-api.json#/components/schemas/Command" + } }, "JsPrepareSubmissionResponse": { "title": "JsPrepareSubmissionResponse", diff --git a/core/wallet-dapp-remote-rpc-client/src/index.ts b/core/wallet-dapp-remote-rpc-client/src/index.ts index 9188e7e0d..1151d82e2 100644 --- a/core/wallet-dapp-remote-rpc-client/src/index.ts +++ b/core/wallet-dapp-remote-rpc-client/src/index.ts @@ -14,12 +14,64 @@ export type CommandId = string type AlwaysTrue = any /** * - * Structure representing JS commands for transaction execution + * Inner shape is defined by the Canton Ledger API CreateCommand schema; do not re-specify here. * */ -export interface JsCommands { +export interface CreateCommandPayload { [key: string]: any } +export interface CreateCommand { + CreateCommand: CreateCommandPayload +} +/** + * + * Inner shape is defined by the Canton Ledger API ExerciseCommand schema; do not re-specify here. + * + */ +export interface ExerciseCommandPayload { + [key: string]: any +} +export interface ExerciseCommand { + ExerciseCommand: ExerciseCommandPayload +} +/** + * + * Inner shape is defined by the Canton Ledger API CreateAndExerciseCommand schema; do not re-specify here. + * + */ +export interface CreateAndExerciseCommandPayload { + [key: string]: any +} +export interface CreateAndExerciseCommand { + CreateAndExerciseCommand: CreateAndExerciseCommandPayload +} +/** + * + * Inner shape is defined by the Canton Ledger API ExerciseByKeyCommand schema; do not re-specify here. + * + */ +export interface ExerciseByKeyCommandPayload { + [key: string]: any +} +export interface ExerciseByKeyCommand { + ExerciseByKeyCommand: ExerciseByKeyCommandPayload +} +/** + * + * A Daml command atom. Mirror of the Canton Ledger API Command union; inner shapes are intentionally opaque so the dApp layer never drifts from the Ledger API contract. + * + */ +export type Command = + | CreateCommand + | ExerciseCommand + | CreateAndExerciseCommand + | ExerciseByKeyCommand +/** + * + * Non-empty array of Daml command atoms to submit atomically as a single transaction. + * + */ +export type JsCommands = Command[] /** * * The party that signed the transaction. diff --git a/core/wallet-dapp-rpc-client/src/index.ts b/core/wallet-dapp-rpc-client/src/index.ts index c41bd3567..083dc363e 100644 --- a/core/wallet-dapp-rpc-client/src/index.ts +++ b/core/wallet-dapp-rpc-client/src/index.ts @@ -14,12 +14,64 @@ export type CommandId = string type AlwaysTrue = any /** * - * Structure representing JS commands for transaction execution + * Inner shape is defined by the Canton Ledger API CreateCommand schema; do not re-specify here. * */ -export interface JsCommands { +export interface CreateCommandPayload { [key: string]: any } +export interface CreateCommand { + CreateCommand: CreateCommandPayload +} +/** + * + * Inner shape is defined by the Canton Ledger API ExerciseCommand schema; do not re-specify here. + * + */ +export interface ExerciseCommandPayload { + [key: string]: any +} +export interface ExerciseCommand { + ExerciseCommand: ExerciseCommandPayload +} +/** + * + * Inner shape is defined by the Canton Ledger API CreateAndExerciseCommand schema; do not re-specify here. + * + */ +export interface CreateAndExerciseCommandPayload { + [key: string]: any +} +export interface CreateAndExerciseCommand { + CreateAndExerciseCommand: CreateAndExerciseCommandPayload +} +/** + * + * Inner shape is defined by the Canton Ledger API ExerciseByKeyCommand schema; do not re-specify here. + * + */ +export interface ExerciseByKeyCommandPayload { + [key: string]: any +} +export interface ExerciseByKeyCommand { + ExerciseByKeyCommand: ExerciseByKeyCommandPayload +} +/** + * + * A Daml command atom. Mirror of the Canton Ledger API Command union; inner shapes are intentionally opaque so the dApp layer never drifts from the Ledger API contract. + * + */ +export type Command = + | CreateCommand + | ExerciseCommand + | CreateAndExerciseCommand + | ExerciseByKeyCommand +/** + * + * Non-empty array of Daml command atoms to submit atomically as a single transaction. + * + */ +export type JsCommands = Command[] /** * * The party that signed the transaction. diff --git a/core/wallet-dapp-rpc-client/src/openrpc.json b/core/wallet-dapp-rpc-client/src/openrpc.json index ea99511e6..3baa4b3d9 100644 --- a/core/wallet-dapp-rpc-client/src/openrpc.json +++ b/core/wallet-dapp-rpc-client/src/openrpc.json @@ -324,11 +324,88 @@ }, "required": ["createdEventBlob"] }, + "CreateCommand": { + "title": "CreateCommand", + "type": "object", + "additionalProperties": false, + "required": ["CreateCommand"], + "properties": { + "CreateCommand": { + "title": "CreateCommandPayload", + "type": "object", + "additionalProperties": true, + "description": "Inner shape is defined by the Canton Ledger API CreateCommand schema; do not re-specify here." + } + } + }, + "ExerciseCommand": { + "title": "ExerciseCommand", + "type": "object", + "additionalProperties": false, + "required": ["ExerciseCommand"], + "properties": { + "ExerciseCommand": { + "title": "ExerciseCommandPayload", + "type": "object", + "additionalProperties": true, + "description": "Inner shape is defined by the Canton Ledger API ExerciseCommand schema; do not re-specify here." + } + } + }, + "CreateAndExerciseCommand": { + "title": "CreateAndExerciseCommand", + "type": "object", + "additionalProperties": false, + "required": ["CreateAndExerciseCommand"], + "properties": { + "CreateAndExerciseCommand": { + "title": "CreateAndExerciseCommandPayload", + "type": "object", + "additionalProperties": true, + "description": "Inner shape is defined by the Canton Ledger API CreateAndExerciseCommand schema; do not re-specify here." + } + } + }, + "ExerciseByKeyCommand": { + "title": "ExerciseByKeyCommand", + "type": "object", + "additionalProperties": false, + "required": ["ExerciseByKeyCommand"], + "properties": { + "ExerciseByKeyCommand": { + "title": "ExerciseByKeyCommandPayload", + "type": "object", + "additionalProperties": true, + "description": "Inner shape is defined by the Canton Ledger API ExerciseByKeyCommand schema; do not re-specify here." + } + } + }, + "Command": { + "title": "Command", + "description": "A Daml command atom. Mirror of the Canton Ledger API Command union; inner shapes are intentionally opaque so the dApp layer never drifts from the Ledger API contract.", + "oneOf": [ + { + "$ref": "api-specs/openrpc-dapp-api.json#/components/schemas/CreateCommand" + }, + { + "$ref": "api-specs/openrpc-dapp-api.json#/components/schemas/ExerciseCommand" + }, + { + "$ref": "api-specs/openrpc-dapp-api.json#/components/schemas/CreateAndExerciseCommand" + }, + { + "$ref": "api-specs/openrpc-dapp-api.json#/components/schemas/ExerciseByKeyCommand" + } + ] + }, "JsCommands": { "title": "JsCommands", - "type": "object", - "description": "Structure representing JS commands for transaction execution", - "additionalProperties": true + "type": "array", + "minItems": 1, + "description": "Non-empty array of Daml command atoms to submit atomically as a single transaction.", + "items": { + "$ref": "api-specs/openrpc-dapp-api.json#/components/schemas/Command" + } }, "JsPrepareSubmissionResponse": { "title": "JsPrepareSubmissionResponse", diff --git a/sdk/dapp-sdk/src/dapp-api/rpc-gen/typings.ts b/sdk/dapp-sdk/src/dapp-api/rpc-gen/typings.ts index ed9dbc910..2e8196007 100644 --- a/sdk/dapp-sdk/src/dapp-api/rpc-gen/typings.ts +++ b/sdk/dapp-sdk/src/dapp-api/rpc-gen/typings.ts @@ -13,12 +13,64 @@ export type CommandId = string type AlwaysTrue = any /** * - * Structure representing JS commands for transaction execution + * Inner shape is defined by the Canton Ledger API CreateCommand schema; do not re-specify here. * */ -export interface JsCommands { +export interface CreateCommandPayload { [key: string]: any } +export interface CreateCommand { + CreateCommand: CreateCommandPayload +} +/** + * + * Inner shape is defined by the Canton Ledger API ExerciseCommand schema; do not re-specify here. + * + */ +export interface ExerciseCommandPayload { + [key: string]: any +} +export interface ExerciseCommand { + ExerciseCommand: ExerciseCommandPayload +} +/** + * + * Inner shape is defined by the Canton Ledger API CreateAndExerciseCommand schema; do not re-specify here. + * + */ +export interface CreateAndExerciseCommandPayload { + [key: string]: any +} +export interface CreateAndExerciseCommand { + CreateAndExerciseCommand: CreateAndExerciseCommandPayload +} +/** + * + * Inner shape is defined by the Canton Ledger API ExerciseByKeyCommand schema; do not re-specify here. + * + */ +export interface ExerciseByKeyCommandPayload { + [key: string]: any +} +export interface ExerciseByKeyCommand { + ExerciseByKeyCommand: ExerciseByKeyCommandPayload +} +/** + * + * A Daml command atom. Mirror of the Canton Ledger API Command union; inner shapes are intentionally opaque so the dApp layer never drifts from the Ledger API contract. + * + */ +export type Command = + | CreateCommand + | ExerciseCommand + | CreateAndExerciseCommand + | ExerciseByKeyCommand +/** + * + * Non-empty array of Daml command atoms to submit atomically as a single transaction. + * + */ +export type JsCommands = Command[] /** * * The party that signed the transaction. diff --git a/wallet-gateway/extension/src/dapp-api/rpc-gen/typings.ts b/wallet-gateway/extension/src/dapp-api/rpc-gen/typings.ts index ed9dbc910..2e8196007 100644 --- a/wallet-gateway/extension/src/dapp-api/rpc-gen/typings.ts +++ b/wallet-gateway/extension/src/dapp-api/rpc-gen/typings.ts @@ -13,12 +13,64 @@ export type CommandId = string type AlwaysTrue = any /** * - * Structure representing JS commands for transaction execution + * Inner shape is defined by the Canton Ledger API CreateCommand schema; do not re-specify here. * */ -export interface JsCommands { +export interface CreateCommandPayload { [key: string]: any } +export interface CreateCommand { + CreateCommand: CreateCommandPayload +} +/** + * + * Inner shape is defined by the Canton Ledger API ExerciseCommand schema; do not re-specify here. + * + */ +export interface ExerciseCommandPayload { + [key: string]: any +} +export interface ExerciseCommand { + ExerciseCommand: ExerciseCommandPayload +} +/** + * + * Inner shape is defined by the Canton Ledger API CreateAndExerciseCommand schema; do not re-specify here. + * + */ +export interface CreateAndExerciseCommandPayload { + [key: string]: any +} +export interface CreateAndExerciseCommand { + CreateAndExerciseCommand: CreateAndExerciseCommandPayload +} +/** + * + * Inner shape is defined by the Canton Ledger API ExerciseByKeyCommand schema; do not re-specify here. + * + */ +export interface ExerciseByKeyCommandPayload { + [key: string]: any +} +export interface ExerciseByKeyCommand { + ExerciseByKeyCommand: ExerciseByKeyCommandPayload +} +/** + * + * A Daml command atom. Mirror of the Canton Ledger API Command union; inner shapes are intentionally opaque so the dApp layer never drifts from the Ledger API contract. + * + */ +export type Command = + | CreateCommand + | ExerciseCommand + | CreateAndExerciseCommand + | ExerciseByKeyCommand +/** + * + * Non-empty array of Daml command atoms to submit atomically as a single transaction. + * + */ +export type JsCommands = Command[] /** * * The party that signed the transaction. diff --git a/wallet-gateway/remote/src/dapp-api/rpc-gen/typings.ts b/wallet-gateway/remote/src/dapp-api/rpc-gen/typings.ts index 5e77ce966..30f70ae4d 100644 --- a/wallet-gateway/remote/src/dapp-api/rpc-gen/typings.ts +++ b/wallet-gateway/remote/src/dapp-api/rpc-gen/typings.ts @@ -13,12 +13,64 @@ export type CommandId = string type AlwaysTrue = any /** * - * Structure representing JS commands for transaction execution + * Inner shape is defined by the Canton Ledger API CreateCommand schema; do not re-specify here. * */ -export interface JsCommands { +export interface CreateCommandPayload { [key: string]: any } +export interface CreateCommand { + CreateCommand: CreateCommandPayload +} +/** + * + * Inner shape is defined by the Canton Ledger API ExerciseCommand schema; do not re-specify here. + * + */ +export interface ExerciseCommandPayload { + [key: string]: any +} +export interface ExerciseCommand { + ExerciseCommand: ExerciseCommandPayload +} +/** + * + * Inner shape is defined by the Canton Ledger API CreateAndExerciseCommand schema; do not re-specify here. + * + */ +export interface CreateAndExerciseCommandPayload { + [key: string]: any +} +export interface CreateAndExerciseCommand { + CreateAndExerciseCommand: CreateAndExerciseCommandPayload +} +/** + * + * Inner shape is defined by the Canton Ledger API ExerciseByKeyCommand schema; do not re-specify here. + * + */ +export interface ExerciseByKeyCommandPayload { + [key: string]: any +} +export interface ExerciseByKeyCommand { + ExerciseByKeyCommand: ExerciseByKeyCommandPayload +} +/** + * + * A Daml command atom. Mirror of the Canton Ledger API Command union; inner shapes are intentionally opaque so the dApp layer never drifts from the Ledger API contract. + * + */ +export type Command = + | CreateCommand + | ExerciseCommand + | CreateAndExerciseCommand + | ExerciseByKeyCommand +/** + * + * Non-empty array of Daml command atoms to submit atomically as a single transaction. + * + */ +export type JsCommands = Command[] /** * * The party that signed the transaction. diff --git a/wallet-gateway/remote/src/utils.ts b/wallet-gateway/remote/src/utils.ts index f020a318f..f26f72c54 100644 --- a/wallet-gateway/remote/src/utils.ts +++ b/wallet-gateway/remote/src/utils.ts @@ -5,6 +5,7 @@ import { v4 } from 'uuid' import { LedgerClient, Types } from '@canton-network/core-ledger-client' import type { DisclosedContracts, + JsCommands, PackageIdSelectionPreference, } from './dapp-api/rpc-gen/typings.js' @@ -33,7 +34,7 @@ export async function networkStatus( export interface PrepareParams { commandId?: string - commands?: { [k: string]: unknown } + commands?: JsCommands actAs?: string[] readAs?: string[] disclosedContracts?: DisclosedContracts