@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/cip0103-tests.d.ts +9 -6
- package/dist/cip0103-tests.js +209 -256
- package/dist/cip0103-tests.js.map +1 -1
- package/dist/index.d.ts +0 -7
- package/dist/index.js +573 -85
- package/dist/index.js.map +1 -1
- package/package.json +8 -5
- package/CHANGELOG.md +0 -168
- package/dist/cip0103-tests.d.ts.map +0 -1
- package/dist/index.d.ts.map +0 -1
- package/dist/loader.d.ts +0 -11
- package/dist/loader.d.ts.map +0 -1
- package/dist/loader.js +0 -65
- package/dist/loader.js.map +0 -1
- package/dist/report.d.ts +0 -39
- package/dist/report.d.ts.map +0 -1
- package/dist/report.js +0 -72
- package/dist/report.js.map +0 -1
- package/dist/tests.d.ts +0 -50
- package/dist/tests.d.ts.map +0 -1
- package/dist/tests.js +0 -233
- package/dist/tests.js.map +0 -1
- package/src/cip0103-tests.ts +0 -340
- package/src/index.ts +0 -116
- package/src/loader.ts +0 -78
- package/src/report.ts +0 -107
- package/src/tests.test.ts +0 -74
- package/src/tests.ts +0 -277
- package/tsconfig.esm.json +0 -10
- package/tsconfig.esm.tsbuildinfo +0 -1
- package/tsconfig.json +0 -14
- package/tsconfig.tsbuildinfo +0 -1
package/src/index.ts
DELETED
|
@@ -1,116 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
/**
|
|
3
|
-
* PartyLayer Conformance Runner CLI
|
|
4
|
-
*
|
|
5
|
-
* Runs adapter conformance tests and generates reports.
|
|
6
|
-
*/
|
|
7
|
-
|
|
8
|
-
import { program } from 'commander';
|
|
9
|
-
import { readFileSync, writeFileSync, existsSync } from 'fs';
|
|
10
|
-
import { loadAdapter } from './loader';
|
|
11
|
-
import { runConformanceTests, createMockContext } from './tests';
|
|
12
|
-
import { generateReport, formatReportText, type ConformanceReport } from './report';
|
|
13
|
-
|
|
14
|
-
program
|
|
15
|
-
.name('partylayer-conformance')
|
|
16
|
-
.description('Run conformance tests for PartyLayer wallet adapters')
|
|
17
|
-
.version('0.1.0');
|
|
18
|
-
|
|
19
|
-
program
|
|
20
|
-
.command('run')
|
|
21
|
-
.description('Run conformance tests for an adapter')
|
|
22
|
-
.requiredOption('--adapter <packageOrPath>', 'Adapter package name or path')
|
|
23
|
-
.option('--network <network>', 'Network to use', 'devnet')
|
|
24
|
-
.option('--registryUrl <url>', 'Registry URL (optional)')
|
|
25
|
-
.option('--output <path>', 'Output JSON report path', 'conformance-report.json')
|
|
26
|
-
.action(async (options) => {
|
|
27
|
-
try {
|
|
28
|
-
console.log(`Loading adapter: ${options.adapter}`);
|
|
29
|
-
const adapter = await loadAdapter(options.adapter);
|
|
30
|
-
|
|
31
|
-
console.log(`Running conformance tests for: ${adapter.name} (${adapter.walletId})`);
|
|
32
|
-
const context = createMockContext();
|
|
33
|
-
context.network = options.network as 'devnet' | 'testnet' | 'mainnet';
|
|
34
|
-
|
|
35
|
-
const startTime = Date.now();
|
|
36
|
-
const testResults = await runConformanceTests(adapter, context);
|
|
37
|
-
|
|
38
|
-
const report = generateReport(
|
|
39
|
-
{
|
|
40
|
-
walletId: adapter.walletId,
|
|
41
|
-
name: adapter.name,
|
|
42
|
-
},
|
|
43
|
-
testResults,
|
|
44
|
-
startTime
|
|
45
|
-
);
|
|
46
|
-
|
|
47
|
-
// Write JSON report
|
|
48
|
-
writeFileSync(options.output, JSON.stringify(report, null, 2));
|
|
49
|
-
console.log(`\nReport written to: ${options.output}`);
|
|
50
|
-
|
|
51
|
-
// Print human-readable summary
|
|
52
|
-
console.log('\n' + formatReportText(report));
|
|
53
|
-
|
|
54
|
-
// Exit with error code if tests failed
|
|
55
|
-
if (report.results.failed > 0) {
|
|
56
|
-
process.exit(1);
|
|
57
|
-
}
|
|
58
|
-
} catch (err) {
|
|
59
|
-
console.error('Error running conformance tests:', err);
|
|
60
|
-
process.exit(1);
|
|
61
|
-
}
|
|
62
|
-
});
|
|
63
|
-
|
|
64
|
-
program
|
|
65
|
-
.command('report')
|
|
66
|
-
.description('Format and display a conformance report')
|
|
67
|
-
.requiredOption('--input <path>', 'Input JSON report path')
|
|
68
|
-
.action((options) => {
|
|
69
|
-
try {
|
|
70
|
-
if (!existsSync(options.input)) {
|
|
71
|
-
console.error(`Report file not found: ${options.input}`);
|
|
72
|
-
process.exit(1);
|
|
73
|
-
}
|
|
74
|
-
|
|
75
|
-
const reportJson = readFileSync(options.input, 'utf-8');
|
|
76
|
-
const report = JSON.parse(reportJson) as ConformanceReport;
|
|
77
|
-
|
|
78
|
-
console.log(formatReportText(report));
|
|
79
|
-
|
|
80
|
-
if (report.results.failed > 0) {
|
|
81
|
-
process.exit(1);
|
|
82
|
-
}
|
|
83
|
-
} catch (err) {
|
|
84
|
-
console.error('Error reading report:', err);
|
|
85
|
-
process.exit(1);
|
|
86
|
-
}
|
|
87
|
-
});
|
|
88
|
-
|
|
89
|
-
program
|
|
90
|
-
.command('run-cip0103')
|
|
91
|
-
.description('Run CIP-0103 conformance tests against a Provider')
|
|
92
|
-
.option('--output <path>', 'Output JSON report path', 'cip0103-report.json')
|
|
93
|
-
.action(async (options) => {
|
|
94
|
-
try {
|
|
95
|
-
const { runCIP0103ConformanceTests, formatCIP0103Report } = await import('./cip0103-tests');
|
|
96
|
-
const { PartyLayerProvider } = await import('@partylayer/provider');
|
|
97
|
-
|
|
98
|
-
console.log('Running CIP-0103 conformance tests...');
|
|
99
|
-
|
|
100
|
-
const provider = new PartyLayerProvider();
|
|
101
|
-
const report = await runCIP0103ConformanceTests(provider);
|
|
102
|
-
|
|
103
|
-
writeFileSync(options.output, JSON.stringify(report, null, 2));
|
|
104
|
-
console.log(`\nReport written to: ${options.output}`);
|
|
105
|
-
console.log('\n' + formatCIP0103Report(report));
|
|
106
|
-
|
|
107
|
-
if (report.failed > 0) {
|
|
108
|
-
process.exit(1);
|
|
109
|
-
}
|
|
110
|
-
} catch (err) {
|
|
111
|
-
console.error('Error running CIP-0103 conformance tests:', err);
|
|
112
|
-
process.exit(1);
|
|
113
|
-
}
|
|
114
|
-
});
|
|
115
|
-
|
|
116
|
-
program.parse();
|
package/src/loader.ts
DELETED
|
@@ -1,78 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Adapter Module Loader
|
|
3
|
-
*
|
|
4
|
-
* Dynamically loads adapter modules (ESM/CJS safe)
|
|
5
|
-
*/
|
|
6
|
-
|
|
7
|
-
import { createRequire } from 'module';
|
|
8
|
-
import { resolve } from 'path';
|
|
9
|
-
import { pathToFileURL } from 'url';
|
|
10
|
-
|
|
11
|
-
import type { WalletAdapter } from '@partylayer/core';
|
|
12
|
-
|
|
13
|
-
// This package builds to ESM (type: module), where the `require` global is
|
|
14
|
-
// undefined — a bare `require(...)`/`require.resolve(...)` throws at runtime
|
|
15
|
-
// (and would hit esbuild's `__require` shim in bundled consumers). Use a real
|
|
16
|
-
// Node require created from this module's URL instead.
|
|
17
|
-
const nodeRequire = createRequire(import.meta.url);
|
|
18
|
-
|
|
19
|
-
/**
|
|
20
|
-
* Load adapter from package or path
|
|
21
|
-
*/
|
|
22
|
-
export async function loadAdapter(
|
|
23
|
-
packageNameOrPath: string
|
|
24
|
-
): Promise<WalletAdapter> {
|
|
25
|
-
let adapterPath: string;
|
|
26
|
-
|
|
27
|
-
// Check if it's a path
|
|
28
|
-
if (packageNameOrPath.startsWith('.') || packageNameOrPath.startsWith('/')) {
|
|
29
|
-
adapterPath = resolve(packageNameOrPath);
|
|
30
|
-
} else {
|
|
31
|
-
// Try to resolve as package
|
|
32
|
-
try {
|
|
33
|
-
adapterPath = nodeRequire.resolve(packageNameOrPath);
|
|
34
|
-
} catch {
|
|
35
|
-
throw new Error(`Cannot resolve adapter: ${packageNameOrPath}`);
|
|
36
|
-
}
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
// Try to load as ESM first
|
|
40
|
-
try {
|
|
41
|
-
const moduleUrl = pathToFileURL(adapterPath).href;
|
|
42
|
-
const module = await import(moduleUrl);
|
|
43
|
-
|
|
44
|
-
// Look for default export or named export
|
|
45
|
-
const adapter = module.default || module[Object.keys(module)[0]];
|
|
46
|
-
|
|
47
|
-
if (!adapter) {
|
|
48
|
-
throw new Error('No adapter export found');
|
|
49
|
-
}
|
|
50
|
-
|
|
51
|
-
// If it's a class, instantiate it
|
|
52
|
-
if (typeof adapter === 'function') {
|
|
53
|
-
return new adapter();
|
|
54
|
-
}
|
|
55
|
-
|
|
56
|
-
return adapter as WalletAdapter;
|
|
57
|
-
} catch (err) {
|
|
58
|
-
// Fallback to CJS
|
|
59
|
-
try {
|
|
60
|
-
const module = nodeRequire(adapterPath);
|
|
61
|
-
const adapter = module.default || module[Object.keys(module)[0]];
|
|
62
|
-
|
|
63
|
-
if (!adapter) {
|
|
64
|
-
throw new Error('No adapter export found');
|
|
65
|
-
}
|
|
66
|
-
|
|
67
|
-
if (typeof adapter === 'function') {
|
|
68
|
-
return new adapter();
|
|
69
|
-
}
|
|
70
|
-
|
|
71
|
-
return adapter as WalletAdapter;
|
|
72
|
-
} catch (cjsErr) {
|
|
73
|
-
throw new Error(
|
|
74
|
-
`Failed to load adapter: ${err instanceof Error ? err.message : String(err)}`
|
|
75
|
-
);
|
|
76
|
-
}
|
|
77
|
-
}
|
|
78
|
-
}
|
package/src/report.ts
DELETED
|
@@ -1,107 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Conformance Report Generation
|
|
3
|
-
*/
|
|
4
|
-
|
|
5
|
-
import type { TestResult } from './tests';
|
|
6
|
-
|
|
7
|
-
/**
|
|
8
|
-
* Conformance report
|
|
9
|
-
*/
|
|
10
|
-
export interface ConformanceReport {
|
|
11
|
-
adapter: {
|
|
12
|
-
walletId: string;
|
|
13
|
-
name: string;
|
|
14
|
-
version?: string;
|
|
15
|
-
};
|
|
16
|
-
environment: {
|
|
17
|
-
nodeVersion: string;
|
|
18
|
-
platform: string;
|
|
19
|
-
timestamp: string;
|
|
20
|
-
};
|
|
21
|
-
results: {
|
|
22
|
-
total: number;
|
|
23
|
-
passed: number;
|
|
24
|
-
failed: number;
|
|
25
|
-
duration: number;
|
|
26
|
-
};
|
|
27
|
-
tests: TestResult[];
|
|
28
|
-
}
|
|
29
|
-
|
|
30
|
-
/**
|
|
31
|
-
* Generate conformance report
|
|
32
|
-
*/
|
|
33
|
-
export function generateReport(
|
|
34
|
-
adapter: { walletId: string; name: string; version?: string },
|
|
35
|
-
tests: TestResult[],
|
|
36
|
-
startTime: number
|
|
37
|
-
): ConformanceReport {
|
|
38
|
-
const passed = tests.filter((t) => t.passed).length;
|
|
39
|
-
const failed = tests.filter((t) => !t.passed).length;
|
|
40
|
-
|
|
41
|
-
return {
|
|
42
|
-
adapter,
|
|
43
|
-
environment: {
|
|
44
|
-
nodeVersion: process.version,
|
|
45
|
-
platform: process.platform,
|
|
46
|
-
timestamp: new Date().toISOString(),
|
|
47
|
-
},
|
|
48
|
-
results: {
|
|
49
|
-
total: tests.length,
|
|
50
|
-
passed,
|
|
51
|
-
failed,
|
|
52
|
-
duration: Date.now() - startTime,
|
|
53
|
-
},
|
|
54
|
-
tests,
|
|
55
|
-
};
|
|
56
|
-
}
|
|
57
|
-
|
|
58
|
-
/**
|
|
59
|
-
* Format report as human-readable text
|
|
60
|
-
*/
|
|
61
|
-
export function formatReportText(report: ConformanceReport): string {
|
|
62
|
-
const lines: string[] = [];
|
|
63
|
-
|
|
64
|
-
lines.push('='.repeat(60));
|
|
65
|
-
lines.push('PartyLayer Adapter Conformance Report');
|
|
66
|
-
lines.push('='.repeat(60));
|
|
67
|
-
lines.push('');
|
|
68
|
-
lines.push(`Adapter: ${report.adapter.name} (${report.adapter.walletId})`);
|
|
69
|
-
if (report.adapter.version) {
|
|
70
|
-
lines.push(`Version: ${report.adapter.version}`);
|
|
71
|
-
}
|
|
72
|
-
lines.push(`Environment: Node ${report.environment.nodeVersion} on ${report.environment.platform}`);
|
|
73
|
-
lines.push(`Timestamp: ${report.environment.timestamp}`);
|
|
74
|
-
lines.push('');
|
|
75
|
-
lines.push('Summary:');
|
|
76
|
-
lines.push(` Total Tests: ${report.results.total}`);
|
|
77
|
-
lines.push(` Passed: ${report.results.passed}`);
|
|
78
|
-
lines.push(` Failed: ${report.results.failed}`);
|
|
79
|
-
lines.push(` Duration: ${report.results.duration}ms`);
|
|
80
|
-
lines.push('');
|
|
81
|
-
lines.push('Test Results:');
|
|
82
|
-
lines.push('-'.repeat(60));
|
|
83
|
-
|
|
84
|
-
report.tests.forEach((test, index) => {
|
|
85
|
-
const status = test.passed ? '✓' : '✗';
|
|
86
|
-
lines.push(`${index + 1}. ${status} ${test.name}`);
|
|
87
|
-
if (test.details) {
|
|
88
|
-
Object.entries(test.details).forEach(([key, value]) => {
|
|
89
|
-
lines.push(` ${key}: ${JSON.stringify(value)}`);
|
|
90
|
-
});
|
|
91
|
-
}
|
|
92
|
-
if (test.error) {
|
|
93
|
-
lines.push(` Error: ${test.error}`);
|
|
94
|
-
}
|
|
95
|
-
});
|
|
96
|
-
|
|
97
|
-
lines.push('-'.repeat(60));
|
|
98
|
-
lines.push('');
|
|
99
|
-
|
|
100
|
-
if (report.results.failed === 0) {
|
|
101
|
-
lines.push('✓ All tests passed!');
|
|
102
|
-
} else {
|
|
103
|
-
lines.push(`✗ ${report.results.failed} test(s) failed`);
|
|
104
|
-
}
|
|
105
|
-
|
|
106
|
-
return lines.join('\n');
|
|
107
|
-
}
|
package/src/tests.test.ts
DELETED
|
@@ -1,74 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Conformance: network-truthfulness contract.
|
|
3
|
-
*
|
|
4
|
-
* An adapter's connect() must surface the wallet's EFFECTIVE network in
|
|
5
|
-
* session.network, not merely echo the requested ctx.network.
|
|
6
|
-
*/
|
|
7
|
-
import { describe, it, expect } from 'vitest';
|
|
8
|
-
import {
|
|
9
|
-
toWalletId,
|
|
10
|
-
toPartyId,
|
|
11
|
-
type AdapterConnectResult,
|
|
12
|
-
type AdapterContext,
|
|
13
|
-
type AdapterDetectResult,
|
|
14
|
-
type CapabilityKey,
|
|
15
|
-
type WalletAdapter,
|
|
16
|
-
} from '@partylayer/core';
|
|
17
|
-
import { createMockContext, checkNetworkTruthfulness } from './tests';
|
|
18
|
-
|
|
19
|
-
/** Base fixture adapter; subclasses decide what session.network they report. */
|
|
20
|
-
abstract class FixtureAdapter implements WalletAdapter {
|
|
21
|
-
readonly walletId = toWalletId('fixture');
|
|
22
|
-
readonly name = 'Fixture';
|
|
23
|
-
getCapabilities(): CapabilityKey[] {
|
|
24
|
-
return ['connect'];
|
|
25
|
-
}
|
|
26
|
-
async detectInstalled(): Promise<AdapterDetectResult> {
|
|
27
|
-
return { installed: true };
|
|
28
|
-
}
|
|
29
|
-
abstract connect(ctx: AdapterContext): Promise<AdapterConnectResult>;
|
|
30
|
-
async disconnect(): Promise<void> {}
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
/** WRONG: echoes the requested ctx.network — fails the contract. */
|
|
34
|
-
class EchoAdapter extends FixtureAdapter {
|
|
35
|
-
async connect(ctx: AdapterContext): Promise<AdapterConnectResult> {
|
|
36
|
-
return {
|
|
37
|
-
partyId: toPartyId('party::echo'),
|
|
38
|
-
session: { walletId: this.walletId, network: ctx.network, createdAt: 0 },
|
|
39
|
-
capabilities: ['connect'],
|
|
40
|
-
};
|
|
41
|
-
}
|
|
42
|
-
}
|
|
43
|
-
|
|
44
|
-
/** RIGHT: reports the wallet's actual network — passes the contract. */
|
|
45
|
-
class TruthfulAdapter extends FixtureAdapter {
|
|
46
|
-
constructor(private readonly walletNetwork: string) {
|
|
47
|
-
super();
|
|
48
|
-
}
|
|
49
|
-
async connect(): Promise<AdapterConnectResult> {
|
|
50
|
-
return {
|
|
51
|
-
partyId: toPartyId('party::truth'),
|
|
52
|
-
session: { walletId: this.walletId, network: this.walletNetwork, createdAt: 0 },
|
|
53
|
-
capabilities: ['connect'],
|
|
54
|
-
};
|
|
55
|
-
}
|
|
56
|
-
}
|
|
57
|
-
|
|
58
|
-
describe('conformance: network-truthfulness contract', () => {
|
|
59
|
-
// The wallet is on mainnet; the dApp requested devnet.
|
|
60
|
-
const ctx = (): AdapterContext => ({ ...createMockContext(), network: 'devnet' });
|
|
61
|
-
const WALLET_NETWORK = 'canton:da-mainnet';
|
|
62
|
-
|
|
63
|
-
it('FAILS an adapter that echoes ctx.network', async () => {
|
|
64
|
-
const result = await checkNetworkTruthfulness(new EchoAdapter(), ctx(), WALLET_NETWORK);
|
|
65
|
-
expect(result.passed).toBe(false);
|
|
66
|
-
expect(result.error).toMatch(/echoed context\.network/);
|
|
67
|
-
});
|
|
68
|
-
|
|
69
|
-
it('PASSES an adapter that reports the wallet network', async () => {
|
|
70
|
-
const result = await checkNetworkTruthfulness(new TruthfulAdapter(WALLET_NETWORK), ctx(), WALLET_NETWORK);
|
|
71
|
-
expect(result.passed).toBe(true);
|
|
72
|
-
expect(result.details).toMatchObject({ expected: WALLET_NETWORK, got: WALLET_NETWORK });
|
|
73
|
-
});
|
|
74
|
-
});
|
package/src/tests.ts
DELETED
|
@@ -1,277 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Adapter Conformance Tests
|
|
3
|
-
*
|
|
4
|
-
* Standard contract tests that all adapters must pass.
|
|
5
|
-
*/
|
|
6
|
-
|
|
7
|
-
import type {
|
|
8
|
-
WalletAdapter,
|
|
9
|
-
AdapterContext,
|
|
10
|
-
CapabilityKey,
|
|
11
|
-
Session,
|
|
12
|
-
} from '@partylayer/core';
|
|
13
|
-
import {
|
|
14
|
-
CapabilityNotSupportedError,
|
|
15
|
-
} from '@partylayer/core';
|
|
16
|
-
|
|
17
|
-
/**
|
|
18
|
-
* Test result
|
|
19
|
-
*/
|
|
20
|
-
export interface TestResult {
|
|
21
|
-
name: string;
|
|
22
|
-
passed: boolean;
|
|
23
|
-
error?: string;
|
|
24
|
-
details?: Record<string, unknown>;
|
|
25
|
-
}
|
|
26
|
-
|
|
27
|
-
/**
|
|
28
|
-
* Conformance test suite
|
|
29
|
-
*/
|
|
30
|
-
export interface ConformanceSuite {
|
|
31
|
-
adapter: WalletAdapter;
|
|
32
|
-
context: AdapterContext;
|
|
33
|
-
results: TestResult[];
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
/**
|
|
37
|
-
* Create mock adapter context
|
|
38
|
-
*/
|
|
39
|
-
export function createMockContext(): AdapterContext {
|
|
40
|
-
return {
|
|
41
|
-
appName: 'Conformance Test',
|
|
42
|
-
origin: 'https://test.example.com',
|
|
43
|
-
network: 'devnet',
|
|
44
|
-
logger: {
|
|
45
|
-
debug: () => {},
|
|
46
|
-
info: () => {},
|
|
47
|
-
warn: () => {},
|
|
48
|
-
error: () => {},
|
|
49
|
-
},
|
|
50
|
-
telemetry: undefined,
|
|
51
|
-
registry: {
|
|
52
|
-
getWallet: async () => ({}),
|
|
53
|
-
},
|
|
54
|
-
crypto: {
|
|
55
|
-
encrypt: async (data: string) => data,
|
|
56
|
-
decrypt: async (data: string) => data,
|
|
57
|
-
generateKey: async () => 'test-key',
|
|
58
|
-
},
|
|
59
|
-
storage: {
|
|
60
|
-
get: async () => null,
|
|
61
|
-
set: async () => {},
|
|
62
|
-
remove: async () => {},
|
|
63
|
-
clear: async () => {},
|
|
64
|
-
},
|
|
65
|
-
timeout: async (ms: number) => {
|
|
66
|
-
await new Promise((resolve) => setTimeout(resolve, ms));
|
|
67
|
-
throw new Error('Timeout');
|
|
68
|
-
},
|
|
69
|
-
};
|
|
70
|
-
}
|
|
71
|
-
|
|
72
|
-
/**
|
|
73
|
-
* Run conformance tests
|
|
74
|
-
*/
|
|
75
|
-
export async function runConformanceTests(
|
|
76
|
-
adapter: WalletAdapter,
|
|
77
|
-
context: AdapterContext
|
|
78
|
-
): Promise<TestResult[]> {
|
|
79
|
-
const results: TestResult[] = [];
|
|
80
|
-
|
|
81
|
-
// Test 1: Adapter has required properties
|
|
82
|
-
results.push({
|
|
83
|
-
name: 'Adapter has walletId',
|
|
84
|
-
passed: typeof adapter.walletId === 'string' && adapter.walletId.length > 0,
|
|
85
|
-
details: { walletId: adapter.walletId },
|
|
86
|
-
});
|
|
87
|
-
|
|
88
|
-
results.push({
|
|
89
|
-
name: 'Adapter has name',
|
|
90
|
-
passed: typeof adapter.name === 'string' && adapter.name.length > 0,
|
|
91
|
-
details: { name: adapter.name },
|
|
92
|
-
});
|
|
93
|
-
|
|
94
|
-
// Test 2: getCapabilities returns valid array
|
|
95
|
-
let capabilities: CapabilityKey[];
|
|
96
|
-
try {
|
|
97
|
-
capabilities = adapter.getCapabilities();
|
|
98
|
-
results.push({
|
|
99
|
-
name: 'getCapabilities returns array',
|
|
100
|
-
passed: Array.isArray(capabilities),
|
|
101
|
-
details: { capabilities },
|
|
102
|
-
});
|
|
103
|
-
|
|
104
|
-
// Check for required capabilities
|
|
105
|
-
const hasConnect = capabilities.includes('connect');
|
|
106
|
-
results.push({
|
|
107
|
-
name: 'Capabilities include "connect"',
|
|
108
|
-
passed: hasConnect,
|
|
109
|
-
details: { capabilities },
|
|
110
|
-
});
|
|
111
|
-
} catch (err) {
|
|
112
|
-
results.push({
|
|
113
|
-
name: 'getCapabilities throws error',
|
|
114
|
-
passed: false,
|
|
115
|
-
error: err instanceof Error ? err.message : String(err),
|
|
116
|
-
});
|
|
117
|
-
return results; // Can't continue without capabilities
|
|
118
|
-
}
|
|
119
|
-
|
|
120
|
-
// Test 3: detectInstalled returns valid result
|
|
121
|
-
try {
|
|
122
|
-
const detect = await adapter.detectInstalled();
|
|
123
|
-
results.push({
|
|
124
|
-
name: 'detectInstalled returns valid result',
|
|
125
|
-
passed:
|
|
126
|
-
typeof detect === 'object' &&
|
|
127
|
-
typeof detect.installed === 'boolean' &&
|
|
128
|
-
(detect.installed || typeof detect.reason === 'string'),
|
|
129
|
-
details: { detect },
|
|
130
|
-
});
|
|
131
|
-
} catch (err) {
|
|
132
|
-
results.push({
|
|
133
|
-
name: 'detectInstalled throws error',
|
|
134
|
-
passed: false,
|
|
135
|
-
error: err instanceof Error ? err.message : String(err),
|
|
136
|
-
});
|
|
137
|
-
}
|
|
138
|
-
|
|
139
|
-
// Test 4: If detectInstalled returns false, connect should throw WalletNotInstalledError
|
|
140
|
-
// (This test requires mocking detectInstalled - skip for now or use mock adapter)
|
|
141
|
-
|
|
142
|
-
// Test 5: Capability checks
|
|
143
|
-
if (capabilities.includes('signMessage')) {
|
|
144
|
-
if (typeof adapter.signMessage !== 'function') {
|
|
145
|
-
results.push({
|
|
146
|
-
name: 'signMessage capability declared but method missing',
|
|
147
|
-
passed: false,
|
|
148
|
-
error: 'signMessage capability declared but method not implemented',
|
|
149
|
-
});
|
|
150
|
-
}
|
|
151
|
-
} else {
|
|
152
|
-
// If signMessage not in capabilities, calling it should throw CapabilityNotSupportedError
|
|
153
|
-
if (typeof adapter.signMessage === 'function') {
|
|
154
|
-
try {
|
|
155
|
-
await adapter.signMessage(context, {} as unknown as Session, { message: 'test' });
|
|
156
|
-
results.push({
|
|
157
|
-
name: 'signMessage throws CapabilityNotSupportedError when not supported',
|
|
158
|
-
passed: false,
|
|
159
|
-
error: 'signMessage did not throw error',
|
|
160
|
-
});
|
|
161
|
-
} catch (err) {
|
|
162
|
-
const passed = err instanceof CapabilityNotSupportedError;
|
|
163
|
-
results.push({
|
|
164
|
-
name: 'signMessage throws CapabilityNotSupportedError when not supported',
|
|
165
|
-
passed,
|
|
166
|
-
error: passed ? undefined : err instanceof Error ? err.message : String(err),
|
|
167
|
-
});
|
|
168
|
-
}
|
|
169
|
-
}
|
|
170
|
-
}
|
|
171
|
-
|
|
172
|
-
// Similar tests for signTransaction and submitTransaction
|
|
173
|
-
if (capabilities.includes('signTransaction')) {
|
|
174
|
-
if (typeof adapter.signTransaction !== 'function') {
|
|
175
|
-
results.push({
|
|
176
|
-
name: 'signTransaction capability declared but method missing',
|
|
177
|
-
passed: false,
|
|
178
|
-
error: 'signTransaction capability declared but method not implemented',
|
|
179
|
-
});
|
|
180
|
-
}
|
|
181
|
-
}
|
|
182
|
-
|
|
183
|
-
if (capabilities.includes('submitTransaction')) {
|
|
184
|
-
if (typeof adapter.submitTransaction !== 'function') {
|
|
185
|
-
results.push({
|
|
186
|
-
name: 'submitTransaction capability declared but method missing',
|
|
187
|
-
passed: false,
|
|
188
|
-
error: 'submitTransaction capability declared but method not implemented',
|
|
189
|
-
});
|
|
190
|
-
}
|
|
191
|
-
}
|
|
192
|
-
|
|
193
|
-
// Test 5b: restore capability <-> method symmetry.
|
|
194
|
-
//
|
|
195
|
-
// We had a real bug where 3 of 5 adapters implemented restore() but did
|
|
196
|
-
// not declare the 'restore' capability in getCapabilities(). The SDK
|
|
197
|
-
// checks method presence at runtime so session persistence still worked,
|
|
198
|
-
// but `wallet.capabilities.includes('restore')` returned false, which
|
|
199
|
-
// misleads docs consumers and anyone programmatically inspecting caps.
|
|
200
|
-
//
|
|
201
|
-
// Enforce both directions:
|
|
202
|
-
// - declared-but-missing: capability listed, no method → broken promise
|
|
203
|
-
// - implemented-but-undeclared: method exists, capability missing →
|
|
204
|
-
// silent drift that hides a feature from consumers
|
|
205
|
-
{
|
|
206
|
-
const hasRestoreMethod = typeof adapter.restore === 'function';
|
|
207
|
-
const declaresRestore = capabilities.includes('restore');
|
|
208
|
-
if (declaresRestore && !hasRestoreMethod) {
|
|
209
|
-
results.push({
|
|
210
|
-
name: 'restore capability declared but method missing',
|
|
211
|
-
passed: false,
|
|
212
|
-
error: `${adapter.name}: declares 'restore' capability but does not implement restore()`,
|
|
213
|
-
});
|
|
214
|
-
} else if (hasRestoreMethod && !declaresRestore) {
|
|
215
|
-
results.push({
|
|
216
|
-
name: 'restore method implemented but capability not declared',
|
|
217
|
-
passed: false,
|
|
218
|
-
error:
|
|
219
|
-
`${adapter.name}: implements restore() but getCapabilities() does not include 'restore'. ` +
|
|
220
|
-
`Add 'restore' to the returned array so consumers can discover the feature.`,
|
|
221
|
-
});
|
|
222
|
-
} else {
|
|
223
|
-
results.push({
|
|
224
|
-
name: 'restore capability / method symmetry',
|
|
225
|
-
passed: true,
|
|
226
|
-
details: { hasRestoreMethod, declaresRestore },
|
|
227
|
-
});
|
|
228
|
-
}
|
|
229
|
-
}
|
|
230
|
-
|
|
231
|
-
// Test 6: Error mapping
|
|
232
|
-
// Verify adapter uses mapUnknownErrorToPartyLayerError
|
|
233
|
-
// (This is a code inspection test - hard to test at runtime)
|
|
234
|
-
|
|
235
|
-
return results;
|
|
236
|
-
}
|
|
237
|
-
|
|
238
|
-
/**
|
|
239
|
-
* Network-truthfulness contract.
|
|
240
|
-
*
|
|
241
|
-
* An adapter's `connect()` MUST surface the wallet's EFFECTIVE network in
|
|
242
|
-
* `session.network` (so the SDK can detect a wallet/dApp network mismatch) —
|
|
243
|
-
* not merely echo the requested `context.network`.
|
|
244
|
-
*
|
|
245
|
-
* Drive it with a context whose `.network` differs from the network the wallet
|
|
246
|
-
* actually reports (`walletReportedNetwork`); a compliant adapter returns
|
|
247
|
-
* `session.network === walletReportedNetwork`. An adapter that echoes
|
|
248
|
-
* `context.network` fails the contract.
|
|
249
|
-
*
|
|
250
|
-
* Adapters that genuinely cannot read the wallet's network must NOT claim to
|
|
251
|
-
* (leave `session.network = context.network`) and must be recorded as
|
|
252
|
-
* "network-reported: no" in the support matrix — they are out of scope for this
|
|
253
|
-
* contract rather than silently passing it.
|
|
254
|
-
*/
|
|
255
|
-
export async function checkNetworkTruthfulness(
|
|
256
|
-
adapter: WalletAdapter,
|
|
257
|
-
context: AdapterContext,
|
|
258
|
-
walletReportedNetwork: string,
|
|
259
|
-
): Promise<TestResult> {
|
|
260
|
-
const name = 'connect() session.network is the wallet effective network';
|
|
261
|
-
try {
|
|
262
|
-
const result = await adapter.connect(context);
|
|
263
|
-
const got = result.session.network;
|
|
264
|
-
const passed = got === walletReportedNetwork;
|
|
265
|
-
return {
|
|
266
|
-
name,
|
|
267
|
-
passed,
|
|
268
|
-
error: passed
|
|
269
|
-
? undefined
|
|
270
|
-
: `expected session.network='${walletReportedNetwork}' (wallet-reported), got '${String(got)}'` +
|
|
271
|
-
(got === context.network ? ' — merely echoed context.network' : ''),
|
|
272
|
-
details: { expected: walletReportedNetwork, got, ctxNetwork: context.network },
|
|
273
|
-
};
|
|
274
|
-
} catch (err) {
|
|
275
|
-
return { name, passed: false, error: err instanceof Error ? err.message : String(err) };
|
|
276
|
-
}
|
|
277
|
-
}
|