@layerzerolabs/common-canton 0.2.114 → 0.2.115

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,263 +0,0 @@
1
- #!/usr/bin/env tsx
2
- /// <reference types="node" />
3
- /**
4
- * Captures error scenarios (unhappy paths) for Canton API interactions.
5
- *
6
- * Usage:
7
- * tsx test/mocks/scripts/capture-unhappy-paths.ts
8
- *
9
- * Output:
10
- * test/mocks/captured-unhappy-paths.json
11
- */
12
-
13
- import type { ContractId } from '@daml/types';
14
- import * as path from 'path';
15
-
16
- import {
17
- MnemonicSignerAdapter,
18
- SignatureFormat,
19
- SignatureType,
20
- } from '@layerzerolabs/ver-signer-adapter';
21
- import { CantonClient } from '../../../src/client/index.js';
22
- // Import SDK test contracts
23
- import { packageId } from '../../../src/daml/sdk-test-0.0.1/lib/index.js';
24
- import { SimpleAsset } from '../../../src/daml/sdk-test-0.0.1/lib/SdkTest/module.js';
25
- import type { Command } from '../../../src/open-api/models/index.js';
26
- import { Configuration } from '../../../src/open-api/runtime.js';
27
- import { CantonSdk } from '../../../src/sdk/index.js';
28
- import type { ExternalSignature } from '../../../src/types.js';
29
- import {
30
- CANTON_URL,
31
- type CapturedInteraction,
32
- createCapturingFetch,
33
- createOperationContext,
34
- EXTERNAL_PARTY_DERIVATION_PATH,
35
- EXTERNAL_PARTY_ID,
36
- EXTERNAL_PARTY_JWT_TOKEN,
37
- EXTERNAL_PARTY_KEY_FINGERPRINT,
38
- EXTERNAL_PARTY_MNEMONIC,
39
- EXTERNAL_PARTY_USER_ID,
40
- INTERNAL_PARTY_ID,
41
- INTERNAL_PARTY_JWT_TOKEN,
42
- INTERNAL_PARTY_USER_ID,
43
- printScenarioSummary,
44
- saveInteractions,
45
- } from './capture-utils.js';
46
-
47
- // ─────────────────────────────────────────────────────────────────────────────
48
- // Helpers
49
- // ─────────────────────────────────────────────────────────────────────────────
50
-
51
- async function signWrongData(party: string): Promise<ExternalSignature> {
52
- const signer = new MnemonicSignerAdapter({
53
- mnemonicConfig: {
54
- getMnemonic: async () => ({
55
- mnemonic: EXTERNAL_PARTY_MNEMONIC,
56
- path: EXTERNAL_PARTY_DERIVATION_PATH,
57
- }),
58
- },
59
- });
60
- const { createHash } = await import('crypto');
61
- const wrongHash = createHash('sha256').update('this-is-wrong-data').digest();
62
-
63
- const derSignature = await signer.sign({
64
- data: wrongHash,
65
- signatureType: SignatureType.ECDSA,
66
- format: SignatureFormat.DER,
67
- });
68
-
69
- return {
70
- party,
71
- signature: Buffer.from(derSignature).toString('base64'),
72
- format: 'SIGNATURE_FORMAT_DER',
73
- signedBy: EXTERNAL_PARTY_KEY_FINGERPRINT,
74
- signingAlgorithmSpec: 'SIGNING_ALGORITHM_SPEC_EC_DSA_SHA_256',
75
- };
76
- }
77
-
78
- // ─────────────────────────────────────────────────────────────────────────────
79
- // Scenarios
80
- // ─────────────────────────────────────────────────────────────────────────────
81
-
82
- async function captureNonExistentContract(sdk: CantonSdk, setOp: (n: string) => void) {
83
- setOp('error_query_nonexistent_contract');
84
- const fakeId =
85
- '00' +
86
- '00'.repeat(65) +
87
- 'ca1212200000000000000000000000000000000000000000000000000000000000000000';
88
- try {
89
- await sdk.get(fakeId as ContractId<typeof SimpleAsset>, [INTERNAL_PARTY_ID]);
90
- } catch {
91
- /* expected */
92
- }
93
- }
94
-
95
- async function captureInvalidTemplateId(sdk: CantonSdk, setOp: (n: string) => void) {
96
- setOp('error_invalid_template_id');
97
- const cmd: Command = {
98
- createCommand: {
99
- templateId: 'invalid-package-id:SdkTest:SimpleAsset',
100
- createArguments: {
101
- owner: INTERNAL_PARTY_ID,
102
- value: '100',
103
- description: 'test',
104
- },
105
- },
106
- };
107
- try {
108
- await sdk.submit({ actAs: [INTERNAL_PARTY_ID] }, [cmd]);
109
- } catch {
110
- /* expected */
111
- }
112
- }
113
-
114
- async function captureDoubleArchive(sdk: CantonSdk, setOp: (n: string) => void) {
115
- // Setup: create contract
116
- setOp('double_archive_setup_create');
117
- const payload = {
118
- owner: INTERNAL_PARTY_ID,
119
- value: '100',
120
- description: 'double archive test',
121
- };
122
- const createCmd = sdk.buildCreate(SimpleAsset as any, payload, packageId);
123
- const createResult = await sdk.submit({ actAs: [INTERNAL_PARTY_ID] }, [createCmd]);
124
-
125
- setOp('double_archive_setup_wait');
126
- const createWait = await sdk.waitForCommand(createResult.commandId, {
127
- parties: [INTERNAL_PARTY_ID],
128
- beginExclusive: createResult.completionOffset,
129
- timeoutMs: 60_000,
130
- });
131
- if (createWait.status !== 'accepted') return;
132
-
133
- setOp('double_archive_setup_fetch_id');
134
- const update = await sdk.fetchTransaction(createWait.updateId, [INTERNAL_PARTY_ID]);
135
- const createdWrapper = (update.events ?? []).find((e) => 'CreatedEvent' in e);
136
- const contractId = (
137
- createdWrapper && 'CreatedEvent' in createdWrapper
138
- ? createdWrapper.CreatedEvent.contractId
139
- : undefined
140
- ) as ContractId<typeof SimpleAsset>;
141
-
142
- // First archive (success)
143
- setOp('double_archive_first_archive');
144
- const archiveCmd1 = sdk.buildExercise(SimpleAsset.Archive, contractId, {}, packageId);
145
- const archiveResult1 = await sdk.submit({ actAs: [INTERNAL_PARTY_ID] }, [archiveCmd1]);
146
-
147
- setOp('double_archive_first_wait');
148
- await sdk.waitForCommand(archiveResult1.commandId, {
149
- parties: [INTERNAL_PARTY_ID],
150
- beginExclusive: archiveResult1.completionOffset,
151
- timeoutMs: 60_000,
152
- });
153
-
154
- // Second archive (should fail)
155
- setOp('error_double_archive');
156
- const archiveCmd2 = sdk.buildExercise(SimpleAsset.Archive, contractId, {}, packageId);
157
- try {
158
- await sdk.submit({ actAs: [INTERNAL_PARTY_ID] }, [archiveCmd2]);
159
- } catch {
160
- /* expected */
161
- }
162
- }
163
-
164
- async function captureInvalidSignature(sdk: CantonSdk, setOp: (n: string) => void) {
165
- setOp('invalid_signature_prepare');
166
- const payload = {
167
- owner: EXTERNAL_PARTY_ID,
168
- value: '200',
169
- description: 'invalid signature test',
170
- };
171
- const createCmd = sdk.buildCreate(SimpleAsset, payload, packageId);
172
- const prepared = await sdk.prepareForSigning({ actAs: [EXTERNAL_PARTY_ID] }, [createCmd]);
173
-
174
- setOp('error_invalid_signature');
175
- const invalidSig = await signWrongData(EXTERNAL_PARTY_ID);
176
- try {
177
- await sdk.submitSigned(prepared, [invalidSig]);
178
- } catch {
179
- /* expected */
180
- }
181
- }
182
-
183
- async function captureCommandTimeout(sdk: CantonSdk, setOp: (n: string) => void) {
184
- setOp('error_command_timeout');
185
- try {
186
- await sdk.waitForCommand('fake-command-id', {
187
- parties: [INTERNAL_PARTY_ID],
188
- beginExclusive: 0,
189
- timeoutMs: 2_000,
190
- });
191
- } catch {
192
- /* expected */
193
- }
194
- }
195
-
196
- async function captureUnauthorizedParty(sdk: CantonSdk, setOp: (n: string) => void) {
197
- setOp('error_unauthorized_party');
198
- const fakeParty = 'fake-party::1220' + '00'.repeat(32);
199
- const payload = {
200
- owner: fakeParty,
201
- value: '100',
202
- description: 'unauthorized test',
203
- };
204
- const cmd = sdk.buildCreate(SimpleAsset as any, payload, packageId);
205
- try {
206
- await sdk.submit({ actAs: [fakeParty] }, [cmd]);
207
- } catch {
208
- /* expected */
209
- }
210
- }
211
-
212
- // ─────────────────────────────────────────────────────────────────────────────
213
- // Main
214
- // ─────────────────────────────────────────────────────────────────────────────
215
-
216
- async function main() {
217
- console.log('Capturing unhappy path requests...');
218
-
219
- const captured: CapturedInteraction[] = [];
220
-
221
- // Internal party scenarios
222
- const internalCtx = createOperationContext();
223
- const internalConfig = new Configuration({
224
- basePath: CANTON_URL,
225
- fetchApi: createCapturingFetch({
226
- captured,
227
- getOperation: internalCtx.getOperation,
228
- scenarioName: 'internal',
229
- }),
230
- headers: { Authorization: `Bearer ${INTERNAL_PARTY_JWT_TOKEN}` },
231
- });
232
- const internalSdk = new CantonSdk(new CantonClient(internalConfig, INTERNAL_PARTY_USER_ID));
233
-
234
- await captureNonExistentContract(internalSdk, internalCtx.setOperation);
235
- await captureInvalidTemplateId(internalSdk, internalCtx.setOperation);
236
- await captureDoubleArchive(internalSdk, internalCtx.setOperation);
237
- await captureCommandTimeout(internalSdk, internalCtx.setOperation);
238
- await captureUnauthorizedParty(internalSdk, internalCtx.setOperation);
239
-
240
- // External party scenarios
241
- const externalCtx = createOperationContext();
242
- const externalConfig = new Configuration({
243
- basePath: CANTON_URL,
244
- fetchApi: createCapturingFetch({
245
- captured,
246
- getOperation: externalCtx.getOperation,
247
- scenarioName: 'external',
248
- }),
249
- headers: { Authorization: `Bearer ${EXTERNAL_PARTY_JWT_TOKEN}` },
250
- });
251
- const externalSdk = new CantonSdk(new CantonClient(externalConfig, EXTERNAL_PARTY_USER_ID));
252
-
253
- await captureInvalidSignature(externalSdk, externalCtx.setOperation);
254
-
255
- const outputFile = path.join(import.meta.dirname, '..', 'captured-unhappy-paths.json');
256
- saveInteractions(outputFile, captured);
257
- printScenarioSummary(captured);
258
- }
259
-
260
- main().catch((err) => {
261
- console.error('Error:', err.message);
262
- process.exit(1);
263
- });
@@ -1,215 +0,0 @@
1
- /**
2
- * Shared utilities for capture scripts.
3
- */
4
-
5
- import * as fs from 'fs';
6
-
7
- // ─────────────────────────────────────────────────────────────────────────────
8
- // Configuration
9
- // ─────────────────────────────────────────────────────────────────────────────
10
-
11
- export const CANTON_URL = process.env.CANTON_URL ?? 'http://127.0.0.1:5013';
12
-
13
- // Internal party (keys managed by participant)
14
- export const INTERNAL_PARTY_USER_ID = process.env.INTERNAL_PARTY_USER_ID ?? '<INTERNAL_USER_ID>';
15
- export const INTERNAL_PARTY_JWT_TOKEN =
16
- process.env.INTERNAL_PARTY_JWT_TOKEN ?? '<INTERNAL_JWT_TOKEN>';
17
- export const INTERNAL_PARTY_ID = process.env.INTERNAL_PARTY_ID ?? '<INTERNAL_PARTY_ID>';
18
-
19
- // External party (keys managed off-chain, signed submissions)
20
- export const EXTERNAL_PARTY_USER_ID = process.env.EXTERNAL_PARTY_USER_ID ?? '<EXTERNAL_USER_ID>';
21
- export const EXTERNAL_PARTY_JWT_TOKEN =
22
- process.env.EXTERNAL_PARTY_JWT_TOKEN ?? '<EXTERNAL_JWT_TOKEN>';
23
- export const EXTERNAL_PARTY_ID = process.env.EXTERNAL_PARTY_ID ?? '<EXTERNAL_PARTY_ID>';
24
- export const EXTERNAL_PARTY_KEY_FINGERPRINT =
25
- process.env.EXTERNAL_PARTY_KEY_FINGERPRINT ?? '<EXTERNAL_KEY_FINGERPRINT>';
26
- export const EXTERNAL_PARTY_MNEMONIC = process.env.EXTERNAL_PARTY_MNEMONIC ?? '<EXTERNAL_MNEMONIC>';
27
- export const EXTERNAL_PARTY_DERIVATION_PATH =
28
- process.env.EXTERNAL_PARTY_DERIVATION_PATH ?? "m/44'/6767'/0'/0/0";
29
-
30
- // ─────────────────────────────────────────────────────────────────────────────
31
- // Types
32
- // ─────────────────────────────────────────────────────────────────────────────
33
-
34
- export interface CapturedInteraction {
35
- name: string;
36
- scenario?: string;
37
- timestamp: string;
38
- request: {
39
- method: string;
40
- url: string;
41
- path: string;
42
- headers: Record<string, string>;
43
- body: unknown;
44
- };
45
- response: {
46
- status: number;
47
- statusText: string;
48
- headers: Record<string, string>;
49
- body: unknown;
50
- };
51
- }
52
-
53
- // ─────────────────────────────────────────────────────────────────────────────
54
- // Endpoint Name Mapping
55
- // ─────────────────────────────────────────────────────────────────────────────
56
-
57
- const ENDPOINT_NAMES: Record<string, string> = {
58
- '/v2/state/connected-synchronizers': 'get_synchronizers',
59
- '/v2/state/ledger-end': 'get_ledger_end',
60
- '/v2/commands/async/submit': 'submit_command',
61
- '/v2/commands/completions': 'wait_completion',
62
- '/v2/updates/update-by-id': 'fetch_update',
63
- '/v2/events/events-by-contract-id': 'get_contract',
64
- '/v2/interactive-submission/prepare': 'prepare_signing',
65
- '/v2/interactive-submission/execute': 'submit_signed',
66
- };
67
-
68
- export function getEndpointName(path: string, method: string): string {
69
- return ENDPOINT_NAMES[path] ?? `${method.toLowerCase()}_${path.replace(/\//g, '_')}`;
70
- }
71
-
72
- // ─────────────────────────────────────────────────────────────────────────────
73
- // Operation Context
74
- // ─────────────────────────────────────────────────────────────────────────────
75
-
76
- export function createOperationContext() {
77
- let currentOperation = 'unknown';
78
-
79
- return {
80
- setOperation(name: string) {
81
- currentOperation = name;
82
- },
83
- getOperation() {
84
- return currentOperation;
85
- },
86
- };
87
- }
88
-
89
- // ─────────────────────────────────────────────────────────────────────────────
90
- // Capturing Fetch Wrapper
91
- // ─────────────────────────────────────────────────────────────────────────────
92
-
93
- export interface CapturingFetchOptions {
94
- captured: CapturedInteraction[];
95
- getOperation: () => string;
96
- scenarioName?: string;
97
- }
98
-
99
- export function createCapturingFetch(options: CapturingFetchOptions): typeof fetch {
100
- const { captured, getOperation, scenarioName } = options;
101
-
102
- return async (input: RequestInfo | URL, init?: RequestInit): Promise<Response> => {
103
- const url = typeof input === 'string' ? input : input.toString();
104
- const method = init?.method ?? 'GET';
105
- const path = new URL(url).pathname;
106
- const requestName = `${getOperation()}__${getEndpointName(path, method)}`;
107
-
108
- // Parse request body
109
- let requestBody: unknown = null;
110
- if (init?.body) {
111
- try {
112
- requestBody = JSON.parse(init.body as string);
113
- } catch {
114
- requestBody = init.body;
115
- }
116
- }
117
-
118
- // Extract request headers
119
- const requestHeaders: Record<string, string> = {};
120
- if (init?.headers) {
121
- if (init.headers instanceof Headers) {
122
- init.headers.forEach((value, key) => {
123
- requestHeaders[key] = value;
124
- });
125
- } else if (Array.isArray(init.headers)) {
126
- init.headers.forEach(([key, value]) => {
127
- requestHeaders[key] = value;
128
- });
129
- } else {
130
- for (const [key, value] of Object.entries(init.headers)) {
131
- requestHeaders[key] = value;
132
- }
133
- }
134
- }
135
-
136
- // Execute real fetch
137
- const response = await fetch(input, init);
138
-
139
- // Clone response to read body without consuming
140
- const cloned = response.clone();
141
- let responseBody: unknown;
142
- const contentType = response.headers.get('content-type') ?? '';
143
-
144
- if (contentType.includes('application/json')) {
145
- try {
146
- responseBody = await cloned.json();
147
- } catch {
148
- responseBody = await cloned.text();
149
- }
150
- } else {
151
- responseBody = await cloned.text();
152
- }
153
-
154
- // Extract response headers
155
- const responseHeaders: Record<string, string> = {};
156
- response.headers.forEach((value, key) => {
157
- responseHeaders[key] = value;
158
- });
159
-
160
- // Store captured interaction
161
- const interaction: CapturedInteraction = {
162
- name: requestName,
163
- timestamp: new Date().toISOString(),
164
- request: {
165
- method,
166
- url,
167
- path,
168
- headers: requestHeaders,
169
- body: requestBody,
170
- },
171
- response: {
172
- status: response.status,
173
- statusText: response.statusText,
174
- headers: responseHeaders,
175
- body: responseBody,
176
- },
177
- };
178
-
179
- if (scenarioName) {
180
- interaction.scenario = scenarioName;
181
- }
182
-
183
- captured.push(interaction);
184
-
185
- return response;
186
- };
187
- }
188
-
189
- // ─────────────────────────────────────────────────────────────────────────────
190
- // File Output
191
- // ─────────────────────────────────────────────────────────────────────────────
192
-
193
- export function saveInteractions(outputFile: string, captured: CapturedInteraction[]): void {
194
- fs.writeFileSync(outputFile, JSON.stringify(captured, null, 2));
195
- console.log(`\n✓ Saved ${captured.length} interactions to:`);
196
- console.log(` ${outputFile}`);
197
- }
198
-
199
- export function printEndpointSummary(captured: CapturedInteraction[]): void {
200
- const endpoints = new Set(captured.map((c) => `${c.request.method} ${c.request.path}`));
201
- console.log('\nCaptured endpoints:');
202
- endpoints.forEach((e) => console.log(` - ${e}`));
203
- }
204
-
205
- export function printScenarioSummary(captured: CapturedInteraction[]): void {
206
- const scenarios = new Map<string, number>();
207
- for (const interaction of captured) {
208
- const key = `${interaction.scenario}: ${interaction.response.status}`;
209
- scenarios.set(key, (scenarios.get(key) ?? 0) + 1);
210
- }
211
- console.log('\nCaptured scenarios:');
212
- scenarios.forEach((count, scenario) => {
213
- console.log(` - ${scenario} (${count} requests)`);
214
- });
215
- }