@partylayer/conformance-runner 0.2.8 → 0.2.10

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.
package/dist/tests.js DELETED
@@ -1,233 +0,0 @@
1
- /**
2
- * Adapter Conformance Tests
3
- *
4
- * Standard contract tests that all adapters must pass.
5
- */
6
- import { CapabilityNotSupportedError, } from '@partylayer/core';
7
- /**
8
- * Create mock adapter context
9
- */
10
- export function createMockContext() {
11
- return {
12
- appName: 'Conformance Test',
13
- origin: 'https://test.example.com',
14
- network: 'devnet',
15
- logger: {
16
- debug: () => { },
17
- info: () => { },
18
- warn: () => { },
19
- error: () => { },
20
- },
21
- telemetry: undefined,
22
- registry: {
23
- getWallet: async () => ({}),
24
- },
25
- crypto: {
26
- encrypt: async (data) => data,
27
- decrypt: async (data) => data,
28
- generateKey: async () => 'test-key',
29
- },
30
- storage: {
31
- get: async () => null,
32
- set: async () => { },
33
- remove: async () => { },
34
- clear: async () => { },
35
- },
36
- timeout: async (ms) => {
37
- await new Promise((resolve) => setTimeout(resolve, ms));
38
- throw new Error('Timeout');
39
- },
40
- };
41
- }
42
- /**
43
- * Run conformance tests
44
- */
45
- export async function runConformanceTests(adapter, context) {
46
- const results = [];
47
- // Test 1: Adapter has required properties
48
- results.push({
49
- name: 'Adapter has walletId',
50
- passed: typeof adapter.walletId === 'string' && adapter.walletId.length > 0,
51
- details: { walletId: adapter.walletId },
52
- });
53
- results.push({
54
- name: 'Adapter has name',
55
- passed: typeof adapter.name === 'string' && adapter.name.length > 0,
56
- details: { name: adapter.name },
57
- });
58
- // Test 2: getCapabilities returns valid array
59
- let capabilities;
60
- try {
61
- capabilities = adapter.getCapabilities();
62
- results.push({
63
- name: 'getCapabilities returns array',
64
- passed: Array.isArray(capabilities),
65
- details: { capabilities },
66
- });
67
- // Check for required capabilities
68
- const hasConnect = capabilities.includes('connect');
69
- results.push({
70
- name: 'Capabilities include "connect"',
71
- passed: hasConnect,
72
- details: { capabilities },
73
- });
74
- }
75
- catch (err) {
76
- results.push({
77
- name: 'getCapabilities throws error',
78
- passed: false,
79
- error: err instanceof Error ? err.message : String(err),
80
- });
81
- return results; // Can't continue without capabilities
82
- }
83
- // Test 3: detectInstalled returns valid result
84
- try {
85
- const detect = await adapter.detectInstalled();
86
- results.push({
87
- name: 'detectInstalled returns valid result',
88
- passed: typeof detect === 'object' &&
89
- typeof detect.installed === 'boolean' &&
90
- (detect.installed || typeof detect.reason === 'string'),
91
- details: { detect },
92
- });
93
- }
94
- catch (err) {
95
- results.push({
96
- name: 'detectInstalled throws error',
97
- passed: false,
98
- error: err instanceof Error ? err.message : String(err),
99
- });
100
- }
101
- // Test 4: If detectInstalled returns false, connect should throw WalletNotInstalledError
102
- // (This test requires mocking detectInstalled - skip for now or use mock adapter)
103
- // Test 5: Capability checks
104
- if (capabilities.includes('signMessage')) {
105
- if (typeof adapter.signMessage !== 'function') {
106
- results.push({
107
- name: 'signMessage capability declared but method missing',
108
- passed: false,
109
- error: 'signMessage capability declared but method not implemented',
110
- });
111
- }
112
- }
113
- else {
114
- // If signMessage not in capabilities, calling it should throw CapabilityNotSupportedError
115
- if (typeof adapter.signMessage === 'function') {
116
- try {
117
- await adapter.signMessage(context, {}, { message: 'test' });
118
- results.push({
119
- name: 'signMessage throws CapabilityNotSupportedError when not supported',
120
- passed: false,
121
- error: 'signMessage did not throw error',
122
- });
123
- }
124
- catch (err) {
125
- const passed = err instanceof CapabilityNotSupportedError;
126
- results.push({
127
- name: 'signMessage throws CapabilityNotSupportedError when not supported',
128
- passed,
129
- error: passed ? undefined : err instanceof Error ? err.message : String(err),
130
- });
131
- }
132
- }
133
- }
134
- // Similar tests for signTransaction and submitTransaction
135
- if (capabilities.includes('signTransaction')) {
136
- if (typeof adapter.signTransaction !== 'function') {
137
- results.push({
138
- name: 'signTransaction capability declared but method missing',
139
- passed: false,
140
- error: 'signTransaction capability declared but method not implemented',
141
- });
142
- }
143
- }
144
- if (capabilities.includes('submitTransaction')) {
145
- if (typeof adapter.submitTransaction !== 'function') {
146
- results.push({
147
- name: 'submitTransaction capability declared but method missing',
148
- passed: false,
149
- error: 'submitTransaction capability declared but method not implemented',
150
- });
151
- }
152
- }
153
- // Test 5b: restore capability <-> method symmetry.
154
- //
155
- // We had a real bug where 3 of 5 adapters implemented restore() but did
156
- // not declare the 'restore' capability in getCapabilities(). The SDK
157
- // checks method presence at runtime so session persistence still worked,
158
- // but `wallet.capabilities.includes('restore')` returned false, which
159
- // misleads docs consumers and anyone programmatically inspecting caps.
160
- //
161
- // Enforce both directions:
162
- // - declared-but-missing: capability listed, no method → broken promise
163
- // - implemented-but-undeclared: method exists, capability missing →
164
- // silent drift that hides a feature from consumers
165
- {
166
- const hasRestoreMethod = typeof adapter.restore === 'function';
167
- const declaresRestore = capabilities.includes('restore');
168
- if (declaresRestore && !hasRestoreMethod) {
169
- results.push({
170
- name: 'restore capability declared but method missing',
171
- passed: false,
172
- error: `${adapter.name}: declares 'restore' capability but does not implement restore()`,
173
- });
174
- }
175
- else if (hasRestoreMethod && !declaresRestore) {
176
- results.push({
177
- name: 'restore method implemented but capability not declared',
178
- passed: false,
179
- error: `${adapter.name}: implements restore() but getCapabilities() does not include 'restore'. ` +
180
- `Add 'restore' to the returned array so consumers can discover the feature.`,
181
- });
182
- }
183
- else {
184
- results.push({
185
- name: 'restore capability / method symmetry',
186
- passed: true,
187
- details: { hasRestoreMethod, declaresRestore },
188
- });
189
- }
190
- }
191
- // Test 6: Error mapping
192
- // Verify adapter uses mapUnknownErrorToPartyLayerError
193
- // (This is a code inspection test - hard to test at runtime)
194
- return results;
195
- }
196
- /**
197
- * Network-truthfulness contract.
198
- *
199
- * An adapter's `connect()` MUST surface the wallet's EFFECTIVE network in
200
- * `session.network` (so the SDK can detect a wallet/dApp network mismatch) —
201
- * not merely echo the requested `context.network`.
202
- *
203
- * Drive it with a context whose `.network` differs from the network the wallet
204
- * actually reports (`walletReportedNetwork`); a compliant adapter returns
205
- * `session.network === walletReportedNetwork`. An adapter that echoes
206
- * `context.network` fails the contract.
207
- *
208
- * Adapters that genuinely cannot read the wallet's network must NOT claim to
209
- * (leave `session.network = context.network`) and must be recorded as
210
- * "network-reported: no" in the support matrix — they are out of scope for this
211
- * contract rather than silently passing it.
212
- */
213
- export async function checkNetworkTruthfulness(adapter, context, walletReportedNetwork) {
214
- const name = 'connect() session.network is the wallet effective network';
215
- try {
216
- const result = await adapter.connect(context);
217
- const got = result.session.network;
218
- const passed = got === walletReportedNetwork;
219
- return {
220
- name,
221
- passed,
222
- error: passed
223
- ? undefined
224
- : `expected session.network='${walletReportedNetwork}' (wallet-reported), got '${String(got)}'` +
225
- (got === context.network ? ' — merely echoed context.network' : ''),
226
- details: { expected: walletReportedNetwork, got, ctxNetwork: context.network },
227
- };
228
- }
229
- catch (err) {
230
- return { name, passed: false, error: err instanceof Error ? err.message : String(err) };
231
- }
232
- }
233
- //# sourceMappingURL=tests.js.map
package/dist/tests.js.map DELETED
@@ -1 +0,0 @@
1
- {"version":3,"file":"tests.js","sourceRoot":"","sources":["../src/tests.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAQH,OAAO,EACL,2BAA2B,GAC5B,MAAM,kBAAkB,CAAC;AAqB1B;;GAEG;AACH,MAAM,UAAU,iBAAiB;IAC/B,OAAO;QACL,OAAO,EAAE,kBAAkB;QAC3B,MAAM,EAAE,0BAA0B;QAClC,OAAO,EAAE,QAAQ;QACjB,MAAM,EAAE;YACN,KAAK,EAAE,GAAG,EAAE,GAAE,CAAC;YACf,IAAI,EAAE,GAAG,EAAE,GAAE,CAAC;YACd,IAAI,EAAE,GAAG,EAAE,GAAE,CAAC;YACd,KAAK,EAAE,GAAG,EAAE,GAAE,CAAC;SAChB;QACD,SAAS,EAAE,SAAS;QACpB,QAAQ,EAAE;YACR,SAAS,EAAE,KAAK,IAAI,EAAE,CAAC,CAAC,EAAE,CAAC;SAC5B;QACD,MAAM,EAAE;YACN,OAAO,EAAE,KAAK,EAAE,IAAY,EAAE,EAAE,CAAC,IAAI;YACrC,OAAO,EAAE,KAAK,EAAE,IAAY,EAAE,EAAE,CAAC,IAAI;YACrC,WAAW,EAAE,KAAK,IAAI,EAAE,CAAC,UAAU;SACpC;QACD,OAAO,EAAE;YACP,GAAG,EAAE,KAAK,IAAI,EAAE,CAAC,IAAI;YACrB,GAAG,EAAE,KAAK,IAAI,EAAE,GAAE,CAAC;YACnB,MAAM,EAAE,KAAK,IAAI,EAAE,GAAE,CAAC;YACtB,KAAK,EAAE,KAAK,IAAI,EAAE,GAAE,CAAC;SACtB;QACD,OAAO,EAAE,KAAK,EAAE,EAAU,EAAE,EAAE;YAC5B,MAAM,IAAI,OAAO,CAAC,CAAC,OAAO,EAAE,EAAE,CAAC,UAAU,CAAC,OAAO,EAAE,EAAE,CAAC,CAAC,CAAC;YACxD,MAAM,IAAI,KAAK,CAAC,SAAS,CAAC,CAAC;QAC7B,CAAC;KACF,CAAC;AACJ,CAAC;AAED;;GAEG;AACH,MAAM,CAAC,KAAK,UAAU,mBAAmB,CACvC,OAAsB,EACtB,OAAuB;IAEvB,MAAM,OAAO,GAAiB,EAAE,CAAC;IAEjC,0CAA0C;IAC1C,OAAO,CAAC,IAAI,CAAC;QACX,IAAI,EAAE,sBAAsB;QAC5B,MAAM,EAAE,OAAO,OAAO,CAAC,QAAQ,KAAK,QAAQ,IAAI,OAAO,CAAC,QAAQ,CAAC,MAAM,GAAG,CAAC;QAC3E,OAAO,EAAE,EAAE,QAAQ,EAAE,OAAO,CAAC,QAAQ,EAAE;KACxC,CAAC,CAAC;IAEH,OAAO,CAAC,IAAI,CAAC;QACX,IAAI,EAAE,kBAAkB;QACxB,MAAM,EAAE,OAAO,OAAO,CAAC,IAAI,KAAK,QAAQ,IAAI,OAAO,CAAC,IAAI,CAAC,MAAM,GAAG,CAAC;QACnE,OAAO,EAAE,EAAE,IAAI,EAAE,OAAO,CAAC,IAAI,EAAE;KAChC,CAAC,CAAC;IAEH,8CAA8C;IAC9C,IAAI,YAA6B,CAAC;IAClC,IAAI,CAAC;QACH,YAAY,GAAG,OAAO,CAAC,eAAe,EAAE,CAAC;QACzC,OAAO,CAAC,IAAI,CAAC;YACX,IAAI,EAAE,+BAA+B;YACrC,MAAM,EAAE,KAAK,CAAC,OAAO,CAAC,YAAY,CAAC;YACnC,OAAO,EAAE,EAAE,YAAY,EAAE;SAC1B,CAAC,CAAC;QAEH,kCAAkC;QAClC,MAAM,UAAU,GAAG,YAAY,CAAC,QAAQ,CAAC,SAAS,CAAC,CAAC;QACpD,OAAO,CAAC,IAAI,CAAC;YACX,IAAI,EAAE,gCAAgC;YACtC,MAAM,EAAE,UAAU;YAClB,OAAO,EAAE,EAAE,YAAY,EAAE;SAC1B,CAAC,CAAC;IACL,CAAC;IAAC,OAAO,GAAG,EAAE,CAAC;QACb,OAAO,CAAC,IAAI,CAAC;YACX,IAAI,EAAE,8BAA8B;YACpC,MAAM,EAAE,KAAK;YACb,KAAK,EAAE,GAAG,YAAY,KAAK,CAAC,CAAC,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,GAAG,CAAC;SACxD,CAAC,CAAC;QACH,OAAO,OAAO,CAAC,CAAC,sCAAsC;IACxD,CAAC;IAED,+CAA+C;IAC/C,IAAI,CAAC;QACH,MAAM,MAAM,GAAG,MAAM,OAAO,CAAC,eAAe,EAAE,CAAC;QAC/C,OAAO,CAAC,IAAI,CAAC;YACX,IAAI,EAAE,sCAAsC;YAC5C,MAAM,EACJ,OAAO,MAAM,KAAK,QAAQ;gBAC1B,OAAO,MAAM,CAAC,SAAS,KAAK,SAAS;gBACrC,CAAC,MAAM,CAAC,SAAS,IAAI,OAAO,MAAM,CAAC,MAAM,KAAK,QAAQ,CAAC;YACzD,OAAO,EAAE,EAAE,MAAM,EAAE;SACpB,CAAC,CAAC;IACL,CAAC;IAAC,OAAO,GAAG,EAAE,CAAC;QACb,OAAO,CAAC,IAAI,CAAC;YACX,IAAI,EAAE,8BAA8B;YACpC,MAAM,EAAE,KAAK;YACb,KAAK,EAAE,GAAG,YAAY,KAAK,CAAC,CAAC,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,GAAG,CAAC;SACxD,CAAC,CAAC;IACL,CAAC;IAED,yFAAyF;IACzF,kFAAkF;IAElF,4BAA4B;IAC5B,IAAI,YAAY,CAAC,QAAQ,CAAC,aAAa,CAAC,EAAE,CAAC;QACzC,IAAI,OAAO,OAAO,CAAC,WAAW,KAAK,UAAU,EAAE,CAAC;YAC9C,OAAO,CAAC,IAAI,CAAC;gBACX,IAAI,EAAE,oDAAoD;gBAC1D,MAAM,EAAE,KAAK;gBACb,KAAK,EAAE,4DAA4D;aACpE,CAAC,CAAC;QACL,CAAC;IACH,CAAC;SAAM,CAAC;QACN,0FAA0F;QAC1F,IAAI,OAAO,OAAO,CAAC,WAAW,KAAK,UAAU,EAAE,CAAC;YAC9C,IAAI,CAAC;gBACH,MAAM,OAAO,CAAC,WAAW,CAAC,OAAO,EAAE,EAAwB,EAAE,EAAE,OAAO,EAAE,MAAM,EAAE,CAAC,CAAC;gBAClF,OAAO,CAAC,IAAI,CAAC;oBACX,IAAI,EAAE,mEAAmE;oBACzE,MAAM,EAAE,KAAK;oBACb,KAAK,EAAE,iCAAiC;iBACzC,CAAC,CAAC;YACL,CAAC;YAAC,OAAO,GAAG,EAAE,CAAC;gBACb,MAAM,MAAM,GAAG,GAAG,YAAY,2BAA2B,CAAC;gBAC1D,OAAO,CAAC,IAAI,CAAC;oBACX,IAAI,EAAE,mEAAmE;oBACzE,MAAM;oBACN,KAAK,EAAE,MAAM,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,GAAG,YAAY,KAAK,CAAC,CAAC,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,GAAG,CAAC;iBAC7E,CAAC,CAAC;YACL,CAAC;QACH,CAAC;IACH,CAAC;IAED,0DAA0D;IAC1D,IAAI,YAAY,CAAC,QAAQ,CAAC,iBAAiB,CAAC,EAAE,CAAC;QAC7C,IAAI,OAAO,OAAO,CAAC,eAAe,KAAK,UAAU,EAAE,CAAC;YAClD,OAAO,CAAC,IAAI,CAAC;gBACX,IAAI,EAAE,wDAAwD;gBAC9D,MAAM,EAAE,KAAK;gBACb,KAAK,EAAE,gEAAgE;aACxE,CAAC,CAAC;QACL,CAAC;IACH,CAAC;IAED,IAAI,YAAY,CAAC,QAAQ,CAAC,mBAAmB,CAAC,EAAE,CAAC;QAC/C,IAAI,OAAO,OAAO,CAAC,iBAAiB,KAAK,UAAU,EAAE,CAAC;YACpD,OAAO,CAAC,IAAI,CAAC;gBACX,IAAI,EAAE,0DAA0D;gBAChE,MAAM,EAAE,KAAK;gBACb,KAAK,EAAE,kEAAkE;aAC1E,CAAC,CAAC;QACL,CAAC;IACH,CAAC;IAED,mDAAmD;IACnD,EAAE;IACF,wEAAwE;IACxE,qEAAqE;IACrE,yEAAyE;IACzE,sEAAsE;IACtE,uEAAuE;IACvE,EAAE;IACF,2BAA2B;IAC3B,0EAA0E;IAC1E,sEAAsE;IACtE,uDAAuD;IACvD,CAAC;QACC,MAAM,gBAAgB,GAAG,OAAO,OAAO,CAAC,OAAO,KAAK,UAAU,CAAC;QAC/D,MAAM,eAAe,GAAG,YAAY,CAAC,QAAQ,CAAC,SAAS,CAAC,CAAC;QACzD,IAAI,eAAe,IAAI,CAAC,gBAAgB,EAAE,CAAC;YACzC,OAAO,CAAC,IAAI,CAAC;gBACX,IAAI,EAAE,gDAAgD;gBACtD,MAAM,EAAE,KAAK;gBACb,KAAK,EAAE,GAAG,OAAO,CAAC,IAAI,kEAAkE;aACzF,CAAC,CAAC;QACL,CAAC;aAAM,IAAI,gBAAgB,IAAI,CAAC,eAAe,EAAE,CAAC;YAChD,OAAO,CAAC,IAAI,CAAC;gBACX,IAAI,EAAE,wDAAwD;gBAC9D,MAAM,EAAE,KAAK;gBACb,KAAK,EACH,GAAG,OAAO,CAAC,IAAI,2EAA2E;oBAC1F,4EAA4E;aAC/E,CAAC,CAAC;QACL,CAAC;aAAM,CAAC;YACN,OAAO,CAAC,IAAI,CAAC;gBACX,IAAI,EAAE,sCAAsC;gBAC5C,MAAM,EAAE,IAAI;gBACZ,OAAO,EAAE,EAAE,gBAAgB,EAAE,eAAe,EAAE;aAC/C,CAAC,CAAC;QACL,CAAC;IACH,CAAC;IAED,wBAAwB;IACxB,uDAAuD;IACvD,6DAA6D;IAE7D,OAAO,OAAO,CAAC;AACjB,CAAC;AAED;;;;;;;;;;;;;;;;GAgBG;AACH,MAAM,CAAC,KAAK,UAAU,wBAAwB,CAC5C,OAAsB,EACtB,OAAuB,EACvB,qBAA6B;IAE7B,MAAM,IAAI,GAAG,2DAA2D,CAAC;IACzE,IAAI,CAAC;QACH,MAAM,MAAM,GAAG,MAAM,OAAO,CAAC,OAAO,CAAC,OAAO,CAAC,CAAC;QAC9C,MAAM,GAAG,GAAG,MAAM,CAAC,OAAO,CAAC,OAAO,CAAC;QACnC,MAAM,MAAM,GAAG,GAAG,KAAK,qBAAqB,CAAC;QAC7C,OAAO;YACL,IAAI;YACJ,MAAM;YACN,KAAK,EAAE,MAAM;gBACX,CAAC,CAAC,SAAS;gBACX,CAAC,CAAC,6BAA6B,qBAAqB,6BAA6B,MAAM,CAAC,GAAG,CAAC,GAAG;oBAC7F,CAAC,GAAG,KAAK,OAAO,CAAC,OAAO,CAAC,CAAC,CAAC,kCAAkC,CAAC,CAAC,CAAC,EAAE,CAAC;YACvE,OAAO,EAAE,EAAE,QAAQ,EAAE,qBAAqB,EAAE,GAAG,EAAE,UAAU,EAAE,OAAO,CAAC,OAAO,EAAE;SAC/E,CAAC;IACJ,CAAC;IAAC,OAAO,GAAG,EAAE,CAAC;QACb,OAAO,EAAE,IAAI,EAAE,MAAM,EAAE,KAAK,EAAE,KAAK,EAAE,GAAG,YAAY,KAAK,CAAC,CAAC,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,GAAG,CAAC,EAAE,CAAC;IAC1F,CAAC;AACH,CAAC"}
@@ -1,340 +0,0 @@
1
- /**
2
- * CIP-0103 Conformance Test Suite
3
- *
4
- * Validates that a Provider implementation conforms to CIP-0103:
5
- * - Interface shape (request, on, emit, removeListener)
6
- * - All 10 mandatory methods are handled
7
- * - Event subscription returns Provider for chaining
8
- * - Error responses use ProviderRpcError with numeric codes
9
- *
10
- * This suite can be run against:
11
- * - PartyLayerProvider (native path)
12
- * - createProviderBridge() (bridge path)
13
- * - Any third-party CIP-0103 Provider
14
- */
15
-
16
- import type { CIP0103Provider } from '@partylayer/core';
17
- import { CIP0103_MANDATORY_METHODS, CIP0103_EVENTS } from '@partylayer/core';
18
-
19
- // ─── Types ──────────────────────────────────────────────────────────────────
20
-
21
- export interface CIP0103TestResult {
22
- name: string;
23
- category: 'interface' | 'method' | 'event' | 'error' | 'lifecycle';
24
- passed: boolean;
25
- error?: string;
26
- details?: Record<string, unknown>;
27
- }
28
-
29
- export interface CIP0103ConformanceReport {
30
- total: number;
31
- passed: number;
32
- failed: number;
33
- results: CIP0103TestResult[];
34
- }
35
-
36
- // ─── Runner ─────────────────────────────────────────────────────────────────
37
-
38
- export async function runCIP0103ConformanceTests(
39
- provider: CIP0103Provider,
40
- ): Promise<CIP0103ConformanceReport> {
41
- const results: CIP0103TestResult[] = [];
42
-
43
- // ── Interface shape tests ─────────────────────────────────────────────
44
-
45
- results.push({
46
- name: 'Provider has request() method',
47
- category: 'interface',
48
- passed: typeof provider.request === 'function',
49
- });
50
-
51
- results.push({
52
- name: 'Provider has on() method',
53
- category: 'interface',
54
- passed: typeof provider.on === 'function',
55
- });
56
-
57
- results.push({
58
- name: 'Provider has emit() method',
59
- category: 'interface',
60
- passed: typeof provider.emit === 'function',
61
- });
62
-
63
- results.push({
64
- name: 'Provider has removeListener() method',
65
- category: 'interface',
66
- passed: typeof provider.removeListener === 'function',
67
- });
68
-
69
- // ── Method presence tests ─────────────────────────────────────────────
70
-
71
- for (const method of CIP0103_MANDATORY_METHODS) {
72
- try {
73
- await provider.request({ method, params: {} });
74
- results.push({
75
- name: `Method "${method}" is handled`,
76
- category: 'method',
77
- passed: true,
78
- });
79
- } catch (err: unknown) {
80
- // A valid ProviderRpcError means the method IS handled
81
- // (it just may not be authorized / connected yet)
82
- const isValidError = isNumericCodeError(err);
83
- results.push({
84
- name: `Method "${method}" is handled`,
85
- category: 'method',
86
- passed: isValidError,
87
- error: isValidError
88
- ? undefined
89
- : `Expected ProviderRpcError with numeric code, got: ${String(err)}`,
90
- details: isValidError
91
- ? { code: (err as { code: number }).code }
92
- : undefined,
93
- });
94
- }
95
- }
96
-
97
- // ── Event chaining tests ──────────────────────────────────────────────
98
-
99
- const noopHandler = () => {};
100
-
101
- const onResult = provider.on('statusChanged', noopHandler);
102
- results.push({
103
- name: 'on() returns Provider for chaining',
104
- category: 'event',
105
- passed: onResult === provider,
106
- error:
107
- onResult === provider
108
- ? undefined
109
- : 'on() did not return the Provider instance',
110
- });
111
-
112
- const removeResult = provider.removeListener('statusChanged', noopHandler);
113
- results.push({
114
- name: 'removeListener() returns Provider for chaining',
115
- category: 'event',
116
- passed: removeResult === provider,
117
- error:
118
- removeResult === provider
119
- ? undefined
120
- : 'removeListener() did not return the Provider instance',
121
- });
122
-
123
- // ── Error code validation ─────────────────────────────────────────────
124
-
125
- try {
126
- await provider.request({ method: '__cip0103_conformance_nonexistent__' });
127
- results.push({
128
- name: 'Unsupported method returns ProviderRpcError',
129
- category: 'error',
130
- passed: false,
131
- error: 'Expected error for unsupported method, got success',
132
- });
133
- } catch (err: unknown) {
134
- const hasNumericCode = isNumericCodeError(err);
135
- results.push({
136
- name: 'Unsupported method returns error with numeric code',
137
- category: 'error',
138
- passed: hasNumericCode,
139
- error: hasNumericCode
140
- ? undefined
141
- : `Expected numeric code, got: ${String(err)}`,
142
- details: hasNumericCode
143
- ? { code: (err as { code: number }).code }
144
- : undefined,
145
- });
146
-
147
- if (hasNumericCode) {
148
- const code = (err as { code: number }).code;
149
- // Should be 4200 (UNSUPPORTED_METHOD) or -32601 (METHOD_NOT_FOUND)
150
- const isCorrectCode = code === 4200 || code === -32601;
151
- results.push({
152
- name: 'Unsupported method uses code 4200 or -32601',
153
- category: 'error',
154
- passed: isCorrectCode,
155
- error: isCorrectCode
156
- ? undefined
157
- : `Expected code 4200 or -32601, got ${code}`,
158
- details: { code },
159
- });
160
- }
161
- }
162
-
163
- // ── emit() returns boolean ────────────────────────────────────────────
164
-
165
- const emitResult = provider.emit('__cip0103_conformance_test__');
166
- results.push({
167
- name: 'emit() returns boolean',
168
- category: 'interface',
169
- passed: typeof emitResult === 'boolean',
170
- error:
171
- typeof emitResult === 'boolean'
172
- ? undefined
173
- : `emit() returned ${typeof emitResult}`,
174
- });
175
-
176
- // ── Transaction lifecycle tests ─────────────────────────────────────
177
-
178
- {
179
- const txEvents: unknown[] = [];
180
- const txHandler = (...args: unknown[]) => txEvents.push(...args);
181
- provider.on(CIP0103_EVENTS.TX_CHANGED, txHandler);
182
-
183
- try {
184
- await provider.request({
185
- method: 'prepareExecute',
186
- params: { tx: { _conformance: true } },
187
- });
188
-
189
- // Check that 'pending' was emitted
190
- results.push({
191
- name: 'prepareExecute emits txChanged with "pending"',
192
- category: 'lifecycle',
193
- passed: txEvents.some(
194
- (e) => typeof e === 'object' && e !== null && (e as Record<string, unknown>).status === 'pending',
195
- ),
196
- error: txEvents.length === 0 ? 'No txChanged events emitted' : undefined,
197
- });
198
-
199
- // Check that 'signed' was emitted
200
- const signedEvent = txEvents.find(
201
- (e) => typeof e === 'object' && e !== null && (e as Record<string, unknown>).status === 'signed',
202
- ) as Record<string, unknown> | undefined;
203
- results.push({
204
- name: 'prepareExecute emits txChanged with "signed"',
205
- category: 'lifecycle',
206
- passed: !!signedEvent,
207
- });
208
-
209
- // Validate 'signed' payload shape
210
- if (signedEvent) {
211
- const payload = signedEvent.payload as Record<string, unknown> | undefined;
212
- results.push({
213
- name: 'signed event has payload.signature (string)',
214
- category: 'lifecycle',
215
- passed: typeof payload?.signature === 'string',
216
- });
217
- results.push({
218
- name: 'signed event has payload.signedBy (string)',
219
- category: 'lifecycle',
220
- passed: typeof payload?.signedBy === 'string',
221
- });
222
- results.push({
223
- name: 'signed event has payload.party (string)',
224
- category: 'lifecycle',
225
- passed: typeof payload?.party === 'string',
226
- });
227
- }
228
-
229
- // Check that a terminal state was reached
230
- const hasTerminal = txEvents.some(
231
- (e) =>
232
- typeof e === 'object' &&
233
- e !== null &&
234
- ((e as Record<string, unknown>).status === 'executed' ||
235
- (e as Record<string, unknown>).status === 'failed'),
236
- );
237
- results.push({
238
- name: 'prepareExecute reaches terminal tx state (executed or failed)',
239
- category: 'lifecycle',
240
- passed: hasTerminal,
241
- });
242
-
243
- // Validate 'executed' payload shape
244
- const executedEvent = txEvents.find(
245
- (e) => typeof e === 'object' && e !== null && (e as Record<string, unknown>).status === 'executed',
246
- ) as Record<string, unknown> | undefined;
247
- if (executedEvent) {
248
- const payload = executedEvent.payload as Record<string, unknown> | undefined;
249
- results.push({
250
- name: 'executed event has payload.updateId (string)',
251
- category: 'lifecycle',
252
- passed: typeof payload?.updateId === 'string',
253
- });
254
- results.push({
255
- name: 'executed event has payload.completionOffset (number)',
256
- category: 'lifecycle',
257
- passed: typeof payload?.completionOffset === 'number',
258
- });
259
- }
260
- } catch {
261
- // prepareExecute may throw — still check if events were emitted
262
- results.push({
263
- name: 'prepareExecute emits txChanged events (despite error)',
264
- category: 'lifecycle',
265
- passed: txEvents.length > 0,
266
- error: txEvents.length === 0 ? 'No txChanged events emitted before error' : undefined,
267
- });
268
- } finally {
269
- provider.removeListener(CIP0103_EVENTS.TX_CHANGED, txHandler);
270
- }
271
- }
272
-
273
- // ── ledgerApi handling test ────────────────────────────────────────
274
-
275
- try {
276
- await provider.request({
277
- method: 'ledgerApi',
278
- params: { requestMethod: 'GET', resource: '/v1/state/acs' },
279
- });
280
- results.push({
281
- name: 'ledgerApi returns result (supported)',
282
- category: 'method',
283
- passed: true,
284
- });
285
- } catch (err: unknown) {
286
- // ProviderRpcError with numeric code = method is recognized
287
- const isHandled = isNumericCodeError(err);
288
- results.push({
289
- name: 'ledgerApi is handled (returns result or ProviderRpcError)',
290
- category: 'method',
291
- passed: isHandled,
292
- error: isHandled ? undefined : `Unexpected error type: ${String(err)}`,
293
- });
294
- }
295
-
296
- // ── Summary ───────────────────────────────────────────────────────────
297
-
298
- const passed = results.filter((r) => r.passed).length;
299
- return {
300
- total: results.length,
301
- passed,
302
- failed: results.length - passed,
303
- results,
304
- };
305
- }
306
-
307
- // ─── Helpers ────────────────────────────────────────────────────────────────
308
-
309
- function isNumericCodeError(err: unknown): boolean {
310
- if (typeof err !== 'object' || err === null) return false;
311
- return typeof (err as Record<string, unknown>).code === 'number';
312
- }
313
-
314
- // ─── Formatting ─────────────────────────────────────────────────────────────
315
-
316
- export function formatCIP0103Report(report: CIP0103ConformanceReport): string {
317
- const lines: string[] = [
318
- '═══ CIP-0103 Conformance Report ═══',
319
- '',
320
- `Total: ${report.total} Passed: ${report.passed} Failed: ${report.failed}`,
321
- '',
322
- ];
323
-
324
- const categories = ['interface', 'method', 'event', 'error', 'lifecycle'] as const;
325
- for (const cat of categories) {
326
- const catResults = report.results.filter((r) => r.category === cat);
327
- if (catResults.length === 0) continue;
328
- lines.push(`── ${cat.toUpperCase()} ──`);
329
- for (const r of catResults) {
330
- const icon = r.passed ? '✓' : '✗';
331
- lines.push(` ${icon} ${r.name}`);
332
- if (r.error) {
333
- lines.push(` → ${r.error}`);
334
- }
335
- }
336
- lines.push('');
337
- }
338
-
339
- return lines.join('\n');
340
- }