walletpair-sdk 1.0.0
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/LICENSE +21 -0
- package/README.md +415 -0
- package/dist/ble/framing.d.ts +23 -0
- package/dist/ble/framing.d.ts.map +1 -0
- package/dist/ble/framing.js +83 -0
- package/dist/ble/framing.js.map +1 -0
- package/dist/ble/index.d.ts +9 -0
- package/dist/ble/index.d.ts.map +1 -0
- package/dist/ble/index.js +9 -0
- package/dist/ble/index.js.map +1 -0
- package/dist/ble/web-ble-transport.d.ts +29 -0
- package/dist/ble/web-ble-transport.d.ts.map +1 -0
- package/dist/ble/web-ble-transport.js +93 -0
- package/dist/ble/web-ble-transport.js.map +1 -0
- package/dist/crypto.d.ts +102 -0
- package/dist/crypto.d.ts.map +1 -0
- package/dist/crypto.js +279 -0
- package/dist/crypto.js.map +1 -0
- package/dist/dapp-session.d.ts +106 -0
- package/dist/dapp-session.d.ts.map +1 -0
- package/dist/dapp-session.js +918 -0
- package/dist/dapp-session.js.map +1 -0
- package/dist/emitter.d.ts +16 -0
- package/dist/emitter.d.ts.map +1 -0
- package/dist/emitter.js +41 -0
- package/dist/emitter.js.map +1 -0
- package/dist/evm/eip1193.d.ts +83 -0
- package/dist/evm/eip1193.d.ts.map +1 -0
- package/dist/evm/eip1193.js +270 -0
- package/dist/evm/eip1193.js.map +1 -0
- package/dist/evm/index.d.ts +8 -0
- package/dist/evm/index.d.ts.map +1 -0
- package/dist/evm/index.js +8 -0
- package/dist/evm/index.js.map +1 -0
- package/dist/evm/wagmi.d.ts +118 -0
- package/dist/evm/wagmi.d.ts.map +1 -0
- package/dist/evm/wagmi.js +205 -0
- package/dist/evm/wagmi.js.map +1 -0
- package/dist/index.d.ts +22 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +24 -0
- package/dist/index.js.map +1 -0
- package/dist/types.d.ts +225 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +31 -0
- package/dist/types.js.map +1 -0
- package/dist/wallet-session.d.ts +107 -0
- package/dist/wallet-session.d.ts.map +1 -0
- package/dist/wallet-session.js +794 -0
- package/dist/wallet-session.js.map +1 -0
- package/dist/ws-transport.d.ts +29 -0
- package/dist/ws-transport.d.ts.map +1 -0
- package/dist/ws-transport.js +79 -0
- package/dist/ws-transport.js.map +1 -0
- package/package.json +55 -0
- package/src/__tests__/adversarial/crypto-attacks.test.ts +557 -0
- package/src/__tests__/adversarial/malicious-dapp.test.ts +505 -0
- package/src/__tests__/adversarial/malicious-relay.test.ts +528 -0
- package/src/__tests__/adversarial/malicious-wallet.test.ts +467 -0
- package/src/__tests__/spec-compliance/canonical-json.test.ts +227 -0
- package/src/__tests__/spec-compliance/crypto-vectors.test.ts +321 -0
- package/src/__tests__/spec-compliance/message-format.test.ts +356 -0
- package/src/__tests__/spec-compliance/sequence-numbers.test.ts +300 -0
- package/src/__tests__/spec-compliance/state-machine.test.ts +364 -0
- package/src/ble/framing.test.ts +196 -0
- package/src/ble/framing.ts +100 -0
- package/src/ble/index.ts +18 -0
- package/src/ble/web-ble-transport.test.ts +192 -0
- package/src/ble/web-ble-transport.ts +116 -0
- package/src/ble/web-bluetooth.d.ts +47 -0
- package/src/canonical-json.test.ts +612 -0
- package/src/crypto-directional.test.ts +263 -0
- package/src/crypto-hardening.test.ts +529 -0
- package/src/crypto.test.ts +635 -0
- package/src/crypto.ts +405 -0
- package/src/dapp-session.test.ts +647 -0
- package/src/dapp-session.ts +1004 -0
- package/src/emitter.test.ts +169 -0
- package/src/emitter.ts +45 -0
- package/src/evm/eip1193.test.ts +365 -0
- package/src/evm/eip1193.ts +346 -0
- package/src/evm/index.ts +19 -0
- package/src/evm/wagmi.test.ts +396 -0
- package/src/evm/wagmi.ts +321 -0
- package/src/index.ts +86 -0
- package/src/integration.test.ts +385 -0
- package/src/security.test.ts +430 -0
- package/src/sequence-validation.test.ts +1185 -0
- package/src/test-helpers.ts +216 -0
- package/src/types.test.ts +82 -0
- package/src/types.ts +305 -0
- package/src/wallet-session.test.ts +683 -0
- package/src/wallet-session.ts +922 -0
- package/src/ws-transport.test.ts +231 -0
- package/src/ws-transport.ts +92 -0
|
@@ -0,0 +1,216 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Shared test helpers — mock transport for unit testing sessions.
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import type { Capabilities, Transport, TransportState, ProtocolMessage, WalletMeta } from './types.js';
|
|
6
|
+
import {
|
|
7
|
+
b64urlDecode,
|
|
8
|
+
computeSharedSecret,
|
|
9
|
+
deriveJoinEncryptionKey,
|
|
10
|
+
deriveSessionKey,
|
|
11
|
+
sealJoin,
|
|
12
|
+
} from './crypto.js';
|
|
13
|
+
import type { X25519KeyPair } from './crypto.js';
|
|
14
|
+
|
|
15
|
+
export const DEFAULT_TEST_CAPABILITIES: Capabilities = {
|
|
16
|
+
methods: ['wallet_getAccounts', 'wallet_signMessage'],
|
|
17
|
+
events: ['accountsChanged', 'chainChanged'],
|
|
18
|
+
chains: ['eip155:1'],
|
|
19
|
+
};
|
|
20
|
+
|
|
21
|
+
/** Extract the JSON payload from a signed or unsigned snapshot string. */
|
|
22
|
+
export function parseSnapshot(signed: string): Record<string, unknown> {
|
|
23
|
+
// Signed format: "<64-hex-mac>.<json>"
|
|
24
|
+
if (signed.length > 65 && signed[64] === '.') {
|
|
25
|
+
return JSON.parse(signed.slice(65));
|
|
26
|
+
}
|
|
27
|
+
return JSON.parse(signed);
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
export const DEFAULT_TEST_WALLET_META: WalletMeta = {
|
|
31
|
+
name: 'Test Wallet',
|
|
32
|
+
description: 'Test wallet',
|
|
33
|
+
url: 'https://wallet.test',
|
|
34
|
+
icon: 'https://wallet.test/icon.png',
|
|
35
|
+
};
|
|
36
|
+
|
|
37
|
+
export function makeSealedJoin(
|
|
38
|
+
channelId: string,
|
|
39
|
+
dappPubKeyB64: string,
|
|
40
|
+
walletKp: X25519KeyPair,
|
|
41
|
+
capabilities: Capabilities = DEFAULT_TEST_CAPABILITIES,
|
|
42
|
+
meta: WalletMeta = DEFAULT_TEST_WALLET_META,
|
|
43
|
+
): string {
|
|
44
|
+
const dappPub = b64urlDecode(dappPubKeyB64);
|
|
45
|
+
const shared = computeSharedSecret(walletKp.privateKey, dappPub);
|
|
46
|
+
const rootKey = deriveSessionKey(shared, channelId);
|
|
47
|
+
const joinKey = deriveJoinEncryptionKey(rootKey, channelId);
|
|
48
|
+
const sealed = sealJoin(joinKey, channelId, capabilities, meta);
|
|
49
|
+
shared.fill(0);
|
|
50
|
+
rootKey.fill(0);
|
|
51
|
+
joinKey.fill(0);
|
|
52
|
+
return sealed;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
export function makeJoinBody(
|
|
56
|
+
channelId: string,
|
|
57
|
+
dappPubKeyB64: string,
|
|
58
|
+
walletKp: X25519KeyPair,
|
|
59
|
+
capabilities: Capabilities = DEFAULT_TEST_CAPABILITIES,
|
|
60
|
+
meta: WalletMeta = DEFAULT_TEST_WALLET_META,
|
|
61
|
+
): { sealed_join: string } {
|
|
62
|
+
return {
|
|
63
|
+
sealed_join: makeSealedJoin(channelId, dappPubKeyB64, walletKp, capabilities, meta),
|
|
64
|
+
};
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
/**
|
|
68
|
+
* In-memory transport for testing. Two MockTransports can be linked
|
|
69
|
+
* to simulate a relay (messages sent on one arrive on the other).
|
|
70
|
+
*/
|
|
71
|
+
export class MockTransport implements Transport {
|
|
72
|
+
state: TransportState = 'disconnected';
|
|
73
|
+
sent: ProtocolMessage[] = [];
|
|
74
|
+
|
|
75
|
+
private messageHandler: ((msg: ProtocolMessage) => void) | null = null;
|
|
76
|
+
private closeHandler: (() => void) | null = null;
|
|
77
|
+
private openHandler: (() => void) | null = null;
|
|
78
|
+
|
|
79
|
+
/** Link to the peer's transport. */
|
|
80
|
+
peer: MockTransport | null = null;
|
|
81
|
+
|
|
82
|
+
onMessage(handler: (msg: ProtocolMessage) => void): void { this.messageHandler = handler; }
|
|
83
|
+
onClose(handler: () => void): void { this.closeHandler = handler; }
|
|
84
|
+
onOpen(handler: () => void): void { this.openHandler = handler; }
|
|
85
|
+
|
|
86
|
+
async connect(): Promise<void> {
|
|
87
|
+
this.state = 'connected';
|
|
88
|
+
this.openHandler?.();
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
send(msg: ProtocolMessage): void {
|
|
92
|
+
this.sent.push(msg);
|
|
93
|
+
// Deliver to peer asynchronously (simulates relay)
|
|
94
|
+
if (this.peer) {
|
|
95
|
+
const peer = this.peer;
|
|
96
|
+
queueMicrotask(() => peer.receive(msg));
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
disconnect(): void {
|
|
101
|
+
this.state = 'disconnected';
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
/** Simulate receiving a message from the relay. */
|
|
105
|
+
receive(msg: ProtocolMessage): void {
|
|
106
|
+
this.messageHandler?.(msg);
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
/** Simulate transport close (disconnect from relay). */
|
|
110
|
+
simulateClose(): void {
|
|
111
|
+
this.state = 'disconnected';
|
|
112
|
+
this.closeHandler?.();
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
/**
|
|
117
|
+
* Create a pair of linked mock transports.
|
|
118
|
+
* Messages sent on dapp arrive on wallet and vice versa.
|
|
119
|
+
*/
|
|
120
|
+
export function createLinkedTransports(): { dapp: MockTransport; wallet: MockTransport } {
|
|
121
|
+
const dapp = new MockTransport();
|
|
122
|
+
const wallet = new MockTransport();
|
|
123
|
+
dapp.peer = wallet;
|
|
124
|
+
wallet.peer = dapp;
|
|
125
|
+
return { dapp, wallet };
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
/**
|
|
129
|
+
* Simulate the relay's role: when dApp sends "create", respond with "ready.waiting".
|
|
130
|
+
* When wallet sends "join", forward to dApp and respond with "ready.waiting".
|
|
131
|
+
* When dApp sends "accept", respond with "ready.connected" to both.
|
|
132
|
+
*/
|
|
133
|
+
export class MockRelay {
|
|
134
|
+
private dappTransport: MockTransport;
|
|
135
|
+
private walletTransport: MockTransport;
|
|
136
|
+
|
|
137
|
+
constructor(dapp: MockTransport, wallet: MockTransport) {
|
|
138
|
+
this.dappTransport = dapp;
|
|
139
|
+
this.walletTransport = wallet;
|
|
140
|
+
|
|
141
|
+
// Intercept sends and inject relay behavior
|
|
142
|
+
const origDappSend = dapp.send.bind(dapp);
|
|
143
|
+
dapp.send = (msg: ProtocolMessage) => {
|
|
144
|
+
origDappSend(msg);
|
|
145
|
+
this.handleDappMessage(msg);
|
|
146
|
+
};
|
|
147
|
+
|
|
148
|
+
const origWalletSend = wallet.send.bind(wallet);
|
|
149
|
+
wallet.send = (msg: ProtocolMessage) => {
|
|
150
|
+
origWalletSend(msg);
|
|
151
|
+
this.handleWalletMessage(msg);
|
|
152
|
+
};
|
|
153
|
+
|
|
154
|
+
// Don't forward to peer directly — relay controls message flow
|
|
155
|
+
dapp.peer = null;
|
|
156
|
+
wallet.peer = null;
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
private handleDappMessage(msg: ProtocolMessage): void {
|
|
160
|
+
if (msg.t === 'create') {
|
|
161
|
+
queueMicrotask(() => {
|
|
162
|
+
this.dappTransport.receive({
|
|
163
|
+
v: 1, t: 'ready', ch: msg.ch,
|
|
164
|
+
ts: Date.now(), from: '_adapter',
|
|
165
|
+
body: { state: 'waiting', reconnect: false, remote: null },
|
|
166
|
+
} as ProtocolMessage);
|
|
167
|
+
});
|
|
168
|
+
} else if (msg.t === 'accept') {
|
|
169
|
+
queueMicrotask(() => {
|
|
170
|
+
const target = (msg.body as any).target;
|
|
171
|
+
this.dappTransport.receive({
|
|
172
|
+
v: 1, t: 'ready', ch: msg.ch,
|
|
173
|
+
ts: Date.now(), from: '_adapter',
|
|
174
|
+
body: { state: 'connected', reconnect: false, remote: target },
|
|
175
|
+
} as ProtocolMessage);
|
|
176
|
+
this.walletTransport.receive({
|
|
177
|
+
v: 1, t: 'ready', ch: msg.ch,
|
|
178
|
+
ts: Date.now(), from: '_adapter',
|
|
179
|
+
body: { state: 'connected', reconnect: false, remote: msg.from },
|
|
180
|
+
} as ProtocolMessage);
|
|
181
|
+
});
|
|
182
|
+
} else if (msg.t === 'req') {
|
|
183
|
+
// Forward to wallet
|
|
184
|
+
queueMicrotask(() => this.walletTransport.receive(msg));
|
|
185
|
+
} else if (msg.t === 'ping') {
|
|
186
|
+
// Forward to wallet
|
|
187
|
+
queueMicrotask(() => this.walletTransport.receive(msg));
|
|
188
|
+
} else if (msg.t === 'close') {
|
|
189
|
+
queueMicrotask(() => this.walletTransport.receive(msg));
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
private handleWalletMessage(msg: ProtocolMessage): void {
|
|
194
|
+
if (msg.t === 'join') {
|
|
195
|
+
queueMicrotask(() => {
|
|
196
|
+
// Relay sends ready.waiting to wallet
|
|
197
|
+
this.walletTransport.receive({
|
|
198
|
+
v: 1, t: 'ready', ch: msg.ch,
|
|
199
|
+
ts: Date.now(), from: '_adapter',
|
|
200
|
+
body: { state: 'waiting', reconnect: false, remote: null },
|
|
201
|
+
} as ProtocolMessage);
|
|
202
|
+
// Relay forwards join to dApp
|
|
203
|
+
this.dappTransport.receive(msg);
|
|
204
|
+
});
|
|
205
|
+
} else if (msg.t === 'res') {
|
|
206
|
+
// Forward to dApp
|
|
207
|
+
queueMicrotask(() => this.dappTransport.receive(msg));
|
|
208
|
+
} else if (msg.t === 'evt') {
|
|
209
|
+
queueMicrotask(() => this.dappTransport.receive(msg));
|
|
210
|
+
} else if (msg.t === 'pong') {
|
|
211
|
+
queueMicrotask(() => this.dappTransport.receive(msg));
|
|
212
|
+
} else if (msg.t === 'close') {
|
|
213
|
+
queueMicrotask(() => this.dappTransport.receive(msg));
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
}
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
import { describe, it, expect } from 'vitest';
|
|
2
|
+
import {
|
|
3
|
+
parseChainId,
|
|
4
|
+
formatChainId,
|
|
5
|
+
evmChainId,
|
|
6
|
+
evmNumericChainId,
|
|
7
|
+
} from './types.js';
|
|
8
|
+
|
|
9
|
+
describe('parseChainId', () => {
|
|
10
|
+
it('parses eip155:1', () => {
|
|
11
|
+
const { namespace, reference } = parseChainId('eip155:1');
|
|
12
|
+
expect(namespace).toBe('eip155');
|
|
13
|
+
expect(reference).toBe('1');
|
|
14
|
+
});
|
|
15
|
+
|
|
16
|
+
it('parses solana:mainnet', () => {
|
|
17
|
+
const { namespace, reference } = parseChainId('solana:mainnet');
|
|
18
|
+
expect(namespace).toBe('solana');
|
|
19
|
+
expect(reference).toBe('mainnet');
|
|
20
|
+
});
|
|
21
|
+
|
|
22
|
+
it('parses cosmos:cosmoshub-4', () => {
|
|
23
|
+
const { namespace, reference } = parseChainId('cosmos:cosmoshub-4');
|
|
24
|
+
expect(namespace).toBe('cosmos');
|
|
25
|
+
expect(reference).toBe('cosmoshub-4');
|
|
26
|
+
});
|
|
27
|
+
|
|
28
|
+
it('throws on missing reference', () => {
|
|
29
|
+
expect(() => parseChainId('eip155')).toThrow('Invalid CAIP-2');
|
|
30
|
+
});
|
|
31
|
+
|
|
32
|
+
it('throws on empty string', () => {
|
|
33
|
+
expect(() => parseChainId('')).toThrow('Invalid CAIP-2');
|
|
34
|
+
});
|
|
35
|
+
|
|
36
|
+
it('throws on just a colon', () => {
|
|
37
|
+
expect(() => parseChainId(':')).toThrow('Invalid CAIP-2');
|
|
38
|
+
});
|
|
39
|
+
});
|
|
40
|
+
|
|
41
|
+
describe('formatChainId', () => {
|
|
42
|
+
it('formats eip155 + 1', () => {
|
|
43
|
+
expect(formatChainId('eip155', '1')).toBe('eip155:1');
|
|
44
|
+
});
|
|
45
|
+
|
|
46
|
+
it('formats solana + mainnet', () => {
|
|
47
|
+
expect(formatChainId('solana', 'mainnet')).toBe('solana:mainnet');
|
|
48
|
+
});
|
|
49
|
+
|
|
50
|
+
it('round-trips with parseChainId', () => {
|
|
51
|
+
const caip2 = 'eip155:137';
|
|
52
|
+
const { namespace, reference } = parseChainId(caip2);
|
|
53
|
+
expect(formatChainId(namespace, reference)).toBe(caip2);
|
|
54
|
+
});
|
|
55
|
+
});
|
|
56
|
+
|
|
57
|
+
describe('evmChainId', () => {
|
|
58
|
+
it('converts numeric chain ID to CAIP-2', () => {
|
|
59
|
+
expect(evmChainId(1)).toBe('eip155:1');
|
|
60
|
+
expect(evmChainId(137)).toBe('eip155:137');
|
|
61
|
+
expect(evmChainId(42161)).toBe('eip155:42161');
|
|
62
|
+
});
|
|
63
|
+
});
|
|
64
|
+
|
|
65
|
+
describe('evmNumericChainId', () => {
|
|
66
|
+
it('extracts numeric ID from eip155: prefix', () => {
|
|
67
|
+
expect(evmNumericChainId('eip155:1')).toBe(1);
|
|
68
|
+
expect(evmNumericChainId('eip155:137')).toBe(137);
|
|
69
|
+
expect(evmNumericChainId('eip155:42161')).toBe(42161);
|
|
70
|
+
});
|
|
71
|
+
|
|
72
|
+
it('returns null for non-eip155 chains', () => {
|
|
73
|
+
expect(evmNumericChainId('solana:mainnet')).toBeNull();
|
|
74
|
+
expect(evmNumericChainId('cosmos:cosmoshub-4')).toBeNull();
|
|
75
|
+
});
|
|
76
|
+
|
|
77
|
+
it('round-trips with evmChainId', () => {
|
|
78
|
+
for (const id of [1, 5, 10, 56, 137, 42161]) {
|
|
79
|
+
expect(evmNumericChainId(evmChainId(id))).toBe(id);
|
|
80
|
+
}
|
|
81
|
+
});
|
|
82
|
+
});
|
package/src/types.ts
ADDED
|
@@ -0,0 +1,305 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* WalletPair SDK — shared types and interfaces.
|
|
3
|
+
*
|
|
4
|
+
* Multi-chain ready: uses CAIP-2 chain IDs throughout (e.g. "eip155:1").
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
// ---------------------------------------------------------------------------
|
|
8
|
+
// Transport
|
|
9
|
+
// ---------------------------------------------------------------------------
|
|
10
|
+
|
|
11
|
+
export type TransportState = 'disconnected' | 'connecting' | 'connected'
|
|
12
|
+
|
|
13
|
+
export interface Transport {
|
|
14
|
+
readonly state: TransportState
|
|
15
|
+
send(msg: ProtocolMessage): void
|
|
16
|
+
connect(): Promise<void>
|
|
17
|
+
disconnect(): void
|
|
18
|
+
onMessage(handler: (msg: ProtocolMessage) => void): void
|
|
19
|
+
onClose(handler: () => void): void
|
|
20
|
+
onOpen(handler: () => void): void
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
// ---------------------------------------------------------------------------
|
|
24
|
+
// Protocol messages (wire format)
|
|
25
|
+
// ---------------------------------------------------------------------------
|
|
26
|
+
|
|
27
|
+
export interface ProtocolMessageBase {
|
|
28
|
+
v: 1
|
|
29
|
+
t: string
|
|
30
|
+
ch: string
|
|
31
|
+
ts: number
|
|
32
|
+
from: string
|
|
33
|
+
body: Record<string, unknown>
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
export interface CreateMessage extends ProtocolMessageBase {
|
|
37
|
+
t: 'create'
|
|
38
|
+
body: { meta: DAppMeta }
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
export interface JoinMessage extends ProtocolMessageBase {
|
|
42
|
+
t: 'join'
|
|
43
|
+
body: { sealed_join: string | null }
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
export interface AcceptMessage extends ProtocolMessageBase {
|
|
47
|
+
t: 'accept'
|
|
48
|
+
body: { target: string }
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
export interface ReadyMessage extends ProtocolMessageBase {
|
|
52
|
+
t: 'ready'
|
|
53
|
+
body: {
|
|
54
|
+
state: 'waiting' | 'connected'
|
|
55
|
+
role: 'dapp' | 'wallet'
|
|
56
|
+
self: string
|
|
57
|
+
remote: string | null
|
|
58
|
+
reconnect: boolean
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
export interface RequestMessage extends ProtocolMessageBase {
|
|
63
|
+
t: 'req'
|
|
64
|
+
body: { id: string; sealed: string }
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
export interface ResponseMessage extends ProtocolMessageBase {
|
|
68
|
+
t: 'res'
|
|
69
|
+
body: { id: string; sealed: string }
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
export interface EventMessage extends ProtocolMessageBase {
|
|
73
|
+
t: 'evt'
|
|
74
|
+
body: { id: string; sealed: string }
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
export interface PingMessage extends ProtocolMessageBase {
|
|
78
|
+
t: 'ping'
|
|
79
|
+
body: Record<string, never>
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
export interface PongMessage extends ProtocolMessageBase {
|
|
83
|
+
t: 'pong'
|
|
84
|
+
body: Record<string, never>
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
export interface CloseMessage extends ProtocolMessageBase {
|
|
88
|
+
t: 'close'
|
|
89
|
+
body: { reason: CloseReason }
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
export interface TerminateMessage extends ProtocolMessageBase {
|
|
93
|
+
t: 'terminate'
|
|
94
|
+
from: '_adapter'
|
|
95
|
+
body: { reason: CloseReason }
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
export type ProtocolMessage =
|
|
99
|
+
| CreateMessage
|
|
100
|
+
| JoinMessage
|
|
101
|
+
| AcceptMessage
|
|
102
|
+
| ReadyMessage
|
|
103
|
+
| RequestMessage
|
|
104
|
+
| ResponseMessage
|
|
105
|
+
| EventMessage
|
|
106
|
+
| PingMessage
|
|
107
|
+
| PongMessage
|
|
108
|
+
| CloseMessage
|
|
109
|
+
| TerminateMessage
|
|
110
|
+
|
|
111
|
+
export type CloseReason =
|
|
112
|
+
| 'normal'
|
|
113
|
+
| 'user_rejected'
|
|
114
|
+
| 'unsupported_capability'
|
|
115
|
+
| 'channel_not_found'
|
|
116
|
+
| 'channel_exists'
|
|
117
|
+
| 'already_connected'
|
|
118
|
+
| 'invalid_state'
|
|
119
|
+
| 'invalid_role'
|
|
120
|
+
| 'timeout'
|
|
121
|
+
| 'rate_limited'
|
|
122
|
+
| 'payload_too_large'
|
|
123
|
+
| 'protocol_error'
|
|
124
|
+
| 'unsupported_version'
|
|
125
|
+
| 'decryption_failed'
|
|
126
|
+
|
|
127
|
+
// ---------------------------------------------------------------------------
|
|
128
|
+
// Capabilities & metadata (multi-chain via CAIP-2)
|
|
129
|
+
// ---------------------------------------------------------------------------
|
|
130
|
+
|
|
131
|
+
export interface Capabilities {
|
|
132
|
+
/** Supported RPC methods (e.g. "wallet_getAccounts", "wallet_signMessage"). */
|
|
133
|
+
methods: string[]
|
|
134
|
+
/** Supported events (e.g. "accountsChanged", "chainChanged"). */
|
|
135
|
+
events: string[]
|
|
136
|
+
/** CAIP-2 chain IDs (e.g. "eip155:1", "solana:mainnet"). */
|
|
137
|
+
chains: string[]
|
|
138
|
+
/** Sub-protocol version map (e.g. { evm: 1 }). §8 */
|
|
139
|
+
version?: Record<string, number> | undefined
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
export interface DAppMeta {
|
|
143
|
+
name: string
|
|
144
|
+
description: string
|
|
145
|
+
url: string
|
|
146
|
+
icon: string
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
export interface WalletMeta {
|
|
150
|
+
name: string
|
|
151
|
+
description: string
|
|
152
|
+
url: string
|
|
153
|
+
icon: string
|
|
154
|
+
[key: string]: unknown
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
// ---------------------------------------------------------------------------
|
|
158
|
+
// Session phases
|
|
159
|
+
// ---------------------------------------------------------------------------
|
|
160
|
+
|
|
161
|
+
export type DAppPhase =
|
|
162
|
+
| 'idle'
|
|
163
|
+
| 'waiting'
|
|
164
|
+
| 'pending_accept'
|
|
165
|
+
| 'connected'
|
|
166
|
+
| 'disconnected'
|
|
167
|
+
| 'closed'
|
|
168
|
+
|
|
169
|
+
export type WalletPhase =
|
|
170
|
+
| 'idle'
|
|
171
|
+
| 'waiting'
|
|
172
|
+
| 'waiting_accept'
|
|
173
|
+
| 'connected'
|
|
174
|
+
| 'disconnected'
|
|
175
|
+
| 'closed'
|
|
176
|
+
|
|
177
|
+
// ---------------------------------------------------------------------------
|
|
178
|
+
// Session events
|
|
179
|
+
// ---------------------------------------------------------------------------
|
|
180
|
+
|
|
181
|
+
export interface DAppSessionEvents {
|
|
182
|
+
[key: string]: unknown
|
|
183
|
+
phase: DAppPhase
|
|
184
|
+
pairingUri: string
|
|
185
|
+
sessionFingerprint: string
|
|
186
|
+
walletJoined: { capabilities?: Capabilities | undefined; meta?: WalletMeta | undefined }
|
|
187
|
+
response: { id: string; ok: boolean; result: unknown }
|
|
188
|
+
event: { event: string; data: unknown }
|
|
189
|
+
error: Error
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
export interface WalletSessionEvents {
|
|
193
|
+
[key: string]: unknown
|
|
194
|
+
phase: WalletPhase
|
|
195
|
+
sessionFingerprint: string
|
|
196
|
+
request: { id: string; method: string; params: unknown }
|
|
197
|
+
error: Error
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
// ---------------------------------------------------------------------------
|
|
201
|
+
// Pairing URI
|
|
202
|
+
// ---------------------------------------------------------------------------
|
|
203
|
+
|
|
204
|
+
export interface PairingParams {
|
|
205
|
+
ch: string
|
|
206
|
+
pubkey: string
|
|
207
|
+
/** Relay URL. Undefined when using direct transport (BLE). */
|
|
208
|
+
relay?: string | undefined
|
|
209
|
+
name: string
|
|
210
|
+
/** DApp website URL. */
|
|
211
|
+
url: string
|
|
212
|
+
/** DApp icon URL. */
|
|
213
|
+
icon: string
|
|
214
|
+
/** Methods the dApp intends to call (§9.1). */
|
|
215
|
+
methods?: string[] | undefined
|
|
216
|
+
/** CAIP-2 chains the dApp intends to use (§9.1). */
|
|
217
|
+
chains?: string[] | undefined
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
// ---------------------------------------------------------------------------
|
|
221
|
+
// Request/response helpers
|
|
222
|
+
// ---------------------------------------------------------------------------
|
|
223
|
+
|
|
224
|
+
export interface PendingRequest {
|
|
225
|
+
id: string
|
|
226
|
+
method: string
|
|
227
|
+
resolve: (result: unknown) => void
|
|
228
|
+
reject: (error: Error) => void
|
|
229
|
+
timer?: ReturnType<typeof setTimeout>
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
export interface SessionPersistence {
|
|
233
|
+
/**
|
|
234
|
+
* Persist a complete serialized session snapshot. Implementations used for
|
|
235
|
+
* reconnect MUST make this durable before resolving; the SDK calls this
|
|
236
|
+
* before sending each encrypted message after advancing `sendSeq`.
|
|
237
|
+
*/
|
|
238
|
+
save(snapshot: string): void | Promise<void>
|
|
239
|
+
/** Load the most recent durable snapshot. */
|
|
240
|
+
load?(): string | null | Promise<string | null>
|
|
241
|
+
/** Remove the durable snapshot when the channel closes. */
|
|
242
|
+
clear?(): void | Promise<void>
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
// ---------------------------------------------------------------------------
|
|
246
|
+
// Session options
|
|
247
|
+
// ---------------------------------------------------------------------------
|
|
248
|
+
|
|
249
|
+
export interface DAppSessionOptions {
|
|
250
|
+
transport: Transport
|
|
251
|
+
/** DApp metadata (name, description, url, icon). Included in pairing URI and create message. */
|
|
252
|
+
meta: DAppMeta
|
|
253
|
+
/** Methods the dApp intends to call (included in pairing URI §9.1). */
|
|
254
|
+
methods?: string[] | undefined
|
|
255
|
+
/** CAIP-2 chains the dApp intends to use (included in pairing URI §9.1). */
|
|
256
|
+
chains?: string[] | undefined
|
|
257
|
+
/** Request timeout in ms (default 120_000). */
|
|
258
|
+
requestTimeout?: number | undefined
|
|
259
|
+
/** Session lifetime in ms (default 86_400_000 = 24h). §16 rule 16. */
|
|
260
|
+
sessionTtl?: number | undefined
|
|
261
|
+
/** Auto-accept known wallets on rejoin (default true). */
|
|
262
|
+
autoAccept?: boolean | undefined
|
|
263
|
+
/** Durable snapshot persistence for reconnect and write-ahead counters. */
|
|
264
|
+
persistence?: SessionPersistence | undefined
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
export interface WalletSessionOptions {
|
|
268
|
+
transport: Transport
|
|
269
|
+
/** Wallet capabilities to advertise. */
|
|
270
|
+
capabilities: Capabilities
|
|
271
|
+
/** Wallet metadata (name, description, url, icon). */
|
|
272
|
+
meta: WalletMeta
|
|
273
|
+
/** Session lifetime in ms (default 86_400_000 = 24h). §16 rule 16. */
|
|
274
|
+
sessionTtl?: number | undefined
|
|
275
|
+
/** Durable snapshot persistence for reconnect and write-ahead counters. */
|
|
276
|
+
persistence?: SessionPersistence | undefined
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
// ---------------------------------------------------------------------------
|
|
280
|
+
// Chain namespace helpers (for future multi-chain)
|
|
281
|
+
// ---------------------------------------------------------------------------
|
|
282
|
+
|
|
283
|
+
/** Parse CAIP-2 chain ID into namespace and reference. */
|
|
284
|
+
export function parseChainId(caip2: string): { namespace: string; reference: string } {
|
|
285
|
+
const [namespace, reference] = caip2.split(':')
|
|
286
|
+
if (!namespace || !reference) throw new Error(`Invalid CAIP-2 chain ID: ${caip2}`)
|
|
287
|
+
return { namespace, reference }
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
/** Build CAIP-2 chain ID from namespace and reference. */
|
|
291
|
+
export function formatChainId(namespace: string, reference: string): string {
|
|
292
|
+
return `${namespace}:${reference}`
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
/** Convert EVM numeric chain ID to CAIP-2. */
|
|
296
|
+
export function evmChainId(id: number): string {
|
|
297
|
+
return `eip155:${id}`
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
/** Extract EVM numeric chain ID from CAIP-2. Returns null if not eip155. */
|
|
301
|
+
export function evmNumericChainId(caip2: string): number | null {
|
|
302
|
+
const { namespace, reference } = parseChainId(caip2)
|
|
303
|
+
if (namespace !== 'eip155') return null
|
|
304
|
+
return Number.parseInt(reference, 10)
|
|
305
|
+
}
|