@sip-protocol/sdk 0.1.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.
@@ -0,0 +1,364 @@
1
+ /**
2
+ * Ethereum Wallet Types
3
+ *
4
+ * Type definitions for Ethereum wallet integration.
5
+ * Follows EIP-1193 (Provider API) and EIP-712 (Typed Data Signing).
6
+ */
7
+
8
+ import type { HexString } from '@sip-protocol/types'
9
+
10
+ // ============================================================================
11
+ // EIP-1193 Provider Types
12
+ // ============================================================================
13
+
14
+ /**
15
+ * EIP-1193 Provider interface
16
+ * Standard interface for Ethereum providers (MetaMask, etc.)
17
+ */
18
+ export interface EIP1193Provider {
19
+ /** Make an Ethereum JSON-RPC request */
20
+ request<T = unknown>(args: EIP1193RequestArguments): Promise<T>
21
+ /** Event emitter for provider events */
22
+ on(event: string, handler: (...args: unknown[]) => void): void
23
+ removeListener(event: string, handler: (...args: unknown[]) => void): void
24
+ /** Provider is MetaMask */
25
+ isMetaMask?: boolean
26
+ /** Provider is Coinbase Wallet */
27
+ isCoinbaseWallet?: boolean
28
+ /** Selected address (may be undefined before connection) */
29
+ selectedAddress?: string | null
30
+ /** Chain ID in hex format */
31
+ chainId?: string
32
+ /** Whether provider is connected */
33
+ isConnected?(): boolean
34
+ }
35
+
36
+ /**
37
+ * EIP-1193 Request arguments
38
+ */
39
+ export interface EIP1193RequestArguments {
40
+ method: string
41
+ params?: unknown[] | Record<string, unknown>
42
+ }
43
+
44
+ /**
45
+ * EIP-1193 Provider events
46
+ */
47
+ export type EIP1193Event =
48
+ | 'connect'
49
+ | 'disconnect'
50
+ | 'chainChanged'
51
+ | 'accountsChanged'
52
+ | 'message'
53
+
54
+ /**
55
+ * EIP-1193 Connect info
56
+ */
57
+ export interface EIP1193ConnectInfo {
58
+ chainId: string
59
+ }
60
+
61
+ /**
62
+ * EIP-1193 Provider RPC error
63
+ */
64
+ export interface EIP1193ProviderRpcError extends Error {
65
+ code: number
66
+ data?: unknown
67
+ }
68
+
69
+ // ============================================================================
70
+ // EIP-712 Typed Data Types
71
+ // ============================================================================
72
+
73
+ /**
74
+ * EIP-712 Typed data domain
75
+ */
76
+ export interface EIP712Domain {
77
+ name?: string
78
+ version?: string
79
+ chainId?: number
80
+ verifyingContract?: string
81
+ salt?: string
82
+ }
83
+
84
+ /**
85
+ * EIP-712 Type definition
86
+ */
87
+ export interface EIP712TypeDefinition {
88
+ name: string
89
+ type: string
90
+ }
91
+
92
+ /**
93
+ * EIP-712 Types object
94
+ */
95
+ export interface EIP712Types {
96
+ EIP712Domain?: EIP712TypeDefinition[]
97
+ [key: string]: EIP712TypeDefinition[] | undefined
98
+ }
99
+
100
+ /**
101
+ * EIP-712 Typed data for signing
102
+ */
103
+ export interface EIP712TypedData {
104
+ domain: EIP712Domain
105
+ types: EIP712Types
106
+ primaryType: string
107
+ message: Record<string, unknown>
108
+ }
109
+
110
+ // ============================================================================
111
+ // Transaction Types
112
+ // ============================================================================
113
+
114
+ /**
115
+ * Ethereum transaction request
116
+ */
117
+ export interface EthereumTransactionRequest {
118
+ /** Sender address */
119
+ from?: string
120
+ /** Recipient address */
121
+ to?: string
122
+ /** Value in wei (hex) */
123
+ value?: string
124
+ /** Transaction data (hex) */
125
+ data?: string
126
+ /** Gas limit (hex) */
127
+ gas?: string
128
+ /** Gas price (hex) - legacy */
129
+ gasPrice?: string
130
+ /** Max fee per gas (hex) - EIP-1559 */
131
+ maxFeePerGas?: string
132
+ /** Max priority fee per gas (hex) - EIP-1559 */
133
+ maxPriorityFeePerGas?: string
134
+ /** Nonce (hex) */
135
+ nonce?: string
136
+ /** Chain ID */
137
+ chainId?: number
138
+ }
139
+
140
+ /**
141
+ * Ethereum transaction receipt
142
+ */
143
+ export interface EthereumTransactionReceipt {
144
+ /** Transaction hash */
145
+ transactionHash: string
146
+ /** Block number */
147
+ blockNumber: string
148
+ /** Block hash */
149
+ blockHash: string
150
+ /** Sender address */
151
+ from: string
152
+ /** Recipient address */
153
+ to: string | null
154
+ /** Gas used */
155
+ gasUsed: string
156
+ /** Effective gas price */
157
+ effectiveGasPrice: string
158
+ /** Status (1 = success, 0 = failure) */
159
+ status: string
160
+ /** Contract address (if deployment) */
161
+ contractAddress: string | null
162
+ }
163
+
164
+ // ============================================================================
165
+ // Token Types (ERC-20)
166
+ // ============================================================================
167
+
168
+ /**
169
+ * Token metadata for wallet_watchAsset
170
+ */
171
+ export interface EthereumTokenMetadata {
172
+ /** Token contract address */
173
+ address: string
174
+ /** Token symbol */
175
+ symbol: string
176
+ /** Token decimals */
177
+ decimals: number
178
+ /** Token image URL (optional) */
179
+ image?: string
180
+ }
181
+
182
+ // ============================================================================
183
+ // Chain Types
184
+ // ============================================================================
185
+
186
+ /**
187
+ * Common Ethereum chain IDs
188
+ */
189
+ export const EthereumChainId = {
190
+ MAINNET: 1,
191
+ GOERLI: 5,
192
+ SEPOLIA: 11155111,
193
+ POLYGON: 137,
194
+ POLYGON_MUMBAI: 80001,
195
+ ARBITRUM: 42161,
196
+ ARBITRUM_GOERLI: 421613,
197
+ OPTIMISM: 10,
198
+ OPTIMISM_GOERLI: 420,
199
+ BASE: 8453,
200
+ BASE_GOERLI: 84531,
201
+ LOCALHOST: 1337,
202
+ } as const
203
+
204
+ export type EthereumChainIdType = (typeof EthereumChainId)[keyof typeof EthereumChainId]
205
+
206
+ /**
207
+ * Chain metadata for wallet_addEthereumChain
208
+ */
209
+ export interface EthereumChainMetadata {
210
+ chainId: string // hex
211
+ chainName: string
212
+ nativeCurrency: {
213
+ name: string
214
+ symbol: string
215
+ decimals: number
216
+ }
217
+ rpcUrls: string[]
218
+ blockExplorerUrls?: string[]
219
+ iconUrls?: string[]
220
+ }
221
+
222
+ /**
223
+ * Ethereum wallet name/type
224
+ */
225
+ export type EthereumWalletName = 'metamask' | 'coinbase' | 'walletconnect' | 'generic'
226
+
227
+ // ============================================================================
228
+ // Adapter Configuration
229
+ // ============================================================================
230
+
231
+ /**
232
+ * Ethereum adapter configuration
233
+ */
234
+ export interface EthereumAdapterConfig {
235
+ /** Wallet to connect to */
236
+ wallet?: EthereumWalletName
237
+ /** Target chain ID */
238
+ chainId?: number
239
+ /** RPC endpoint URL (for balance queries) */
240
+ rpcEndpoint?: string
241
+ /** Custom provider (for testing) */
242
+ provider?: EIP1193Provider
243
+ }
244
+
245
+ // ============================================================================
246
+ // Provider Detection & Utilities
247
+ // ============================================================================
248
+
249
+ /**
250
+ * Get the injected Ethereum provider
251
+ */
252
+ export function getEthereumProvider(
253
+ wallet: EthereumWalletName = 'metamask'
254
+ ): EIP1193Provider | undefined {
255
+ if (typeof window === 'undefined') return undefined
256
+
257
+ const win = window as unknown as {
258
+ ethereum?: EIP1193Provider & {
259
+ providers?: EIP1193Provider[]
260
+ }
261
+ coinbaseWalletExtension?: EIP1193Provider
262
+ }
263
+
264
+ // Handle multiple injected providers
265
+ if (win.ethereum?.providers?.length) {
266
+ switch (wallet) {
267
+ case 'metamask':
268
+ return win.ethereum.providers.find((p) => p.isMetaMask)
269
+ case 'coinbase':
270
+ return win.ethereum.providers.find((p) => p.isCoinbaseWallet)
271
+ default:
272
+ return win.ethereum.providers[0]
273
+ }
274
+ }
275
+
276
+ switch (wallet) {
277
+ case 'metamask':
278
+ return win.ethereum?.isMetaMask ? win.ethereum : undefined
279
+ case 'coinbase':
280
+ return win.coinbaseWalletExtension ?? (win.ethereum?.isCoinbaseWallet ? win.ethereum : undefined)
281
+ case 'generic':
282
+ default:
283
+ return win.ethereum
284
+ }
285
+ }
286
+
287
+ /**
288
+ * Detect which Ethereum wallets are installed
289
+ */
290
+ export function detectEthereumWallets(): EthereumWalletName[] {
291
+ if (typeof window === 'undefined') return []
292
+
293
+ const detected: EthereumWalletName[] = []
294
+ const win = window as unknown as {
295
+ ethereum?: EIP1193Provider & { providers?: EIP1193Provider[] }
296
+ coinbaseWalletExtension?: EIP1193Provider
297
+ }
298
+
299
+ if (win.ethereum?.providers?.length) {
300
+ if (win.ethereum.providers.some((p) => p.isMetaMask)) detected.push('metamask')
301
+ if (win.ethereum.providers.some((p) => p.isCoinbaseWallet)) detected.push('coinbase')
302
+ } else {
303
+ if (win.ethereum?.isMetaMask) detected.push('metamask')
304
+ if (win.ethereum?.isCoinbaseWallet || win.coinbaseWalletExtension) detected.push('coinbase')
305
+ }
306
+
307
+ if (win.ethereum && detected.length === 0) {
308
+ detected.push('generic')
309
+ }
310
+
311
+ return detected
312
+ }
313
+
314
+ /**
315
+ * Convert number to hex string with 0x prefix
316
+ */
317
+ export function toHex(value: number | bigint): HexString {
318
+ return `0x${value.toString(16)}` as HexString
319
+ }
320
+
321
+ /**
322
+ * Convert hex string to bigint
323
+ */
324
+ export function fromHex(hex: string): bigint {
325
+ return BigInt(hex)
326
+ }
327
+
328
+ /**
329
+ * Convert hex string to number
330
+ */
331
+ export function hexToNumber(hex: string): number {
332
+ return Number(BigInt(hex))
333
+ }
334
+
335
+ /**
336
+ * Pad address to checksum format
337
+ */
338
+ export function normalizeAddress(address: string): HexString {
339
+ return address.toLowerCase() as HexString
340
+ }
341
+
342
+ /**
343
+ * Get default RPC endpoint for chain
344
+ */
345
+ export function getDefaultRpcEndpoint(chainId: number): string {
346
+ switch (chainId) {
347
+ case EthereumChainId.MAINNET:
348
+ return 'https://eth.llamarpc.com'
349
+ case EthereumChainId.GOERLI:
350
+ return 'https://rpc.ankr.com/eth_goerli'
351
+ case EthereumChainId.SEPOLIA:
352
+ return 'https://rpc.sepolia.org'
353
+ case EthereumChainId.POLYGON:
354
+ return 'https://polygon-rpc.com'
355
+ case EthereumChainId.ARBITRUM:
356
+ return 'https://arb1.arbitrum.io/rpc'
357
+ case EthereumChainId.OPTIMISM:
358
+ return 'https://mainnet.optimism.io'
359
+ case EthereumChainId.BASE:
360
+ return 'https://mainnet.base.org'
361
+ default:
362
+ return 'http://localhost:8545'
363
+ }
364
+ }
@@ -0,0 +1,116 @@
1
+ /**
2
+ * Wallet Module
3
+ *
4
+ * Chain-agnostic wallet adapter interface for SIP Protocol.
5
+ */
6
+
7
+ // Base adapter class
8
+ export { BaseWalletAdapter, MockWalletAdapter } from './base-adapter'
9
+
10
+ // Wallet errors
11
+ export { WalletError, notConnectedError, featureNotSupportedError } from './errors'
12
+
13
+ // Registry
14
+ export {
15
+ walletRegistry,
16
+ registerWallet,
17
+ createWalletFactory,
18
+ isPrivateWalletAdapter,
19
+ } from './registry'
20
+
21
+ // Solana adapter
22
+ export {
23
+ SolanaWalletAdapter,
24
+ createSolanaAdapter,
25
+ MockSolanaAdapter,
26
+ createMockSolanaAdapter,
27
+ createMockSolanaProvider,
28
+ createMockSolanaConnection,
29
+ getSolanaProvider,
30
+ detectSolanaWallets,
31
+ solanaPublicKeyToHex,
32
+ base58ToHex,
33
+ } from './solana'
34
+
35
+ export type {
36
+ SolanaPublicKey,
37
+ SolanaTransaction,
38
+ SolanaVersionedTransaction,
39
+ SolanaWalletProvider,
40
+ SolanaWalletName,
41
+ SolanaCluster,
42
+ SolanaAdapterConfig,
43
+ SolanaConnection,
44
+ SolanaSendOptions,
45
+ SolanaUnsignedTransaction,
46
+ SolanaSignature,
47
+ MockSolanaAdapterConfig,
48
+ } from './solana'
49
+
50
+ // Ethereum adapter
51
+ export {
52
+ EthereumWalletAdapter,
53
+ createEthereumAdapter,
54
+ MockEthereumAdapter,
55
+ createMockEthereumAdapter,
56
+ createMockEthereumProvider,
57
+ getEthereumProvider,
58
+ detectEthereumWallets,
59
+ toHex,
60
+ fromHex,
61
+ hexToNumber,
62
+ normalizeAddress,
63
+ getDefaultRpcEndpoint,
64
+ EthereumChainId,
65
+ } from './ethereum'
66
+
67
+ export type {
68
+ EIP1193Provider,
69
+ EIP1193RequestArguments,
70
+ EIP1193Event,
71
+ EIP1193ConnectInfo,
72
+ EIP1193ProviderRpcError,
73
+ EIP712Domain,
74
+ EIP712TypeDefinition,
75
+ EIP712Types,
76
+ EIP712TypedData,
77
+ EthereumTransactionRequest,
78
+ EthereumTransactionReceipt,
79
+ EthereumTokenMetadata,
80
+ EthereumChainMetadata,
81
+ EthereumWalletName,
82
+ EthereumAdapterConfig,
83
+ EthereumChainIdType,
84
+ MockEthereumAdapterConfig,
85
+ } from './ethereum'
86
+
87
+ // Re-export types from types package for convenience
88
+ export type {
89
+ // Core types
90
+ WalletConnectionState,
91
+ Signature,
92
+ UnsignedTransaction,
93
+ SignedTransaction,
94
+ TransactionReceipt,
95
+ // Events
96
+ WalletEventType,
97
+ WalletEvent,
98
+ WalletEventHandler,
99
+ WalletConnectEvent,
100
+ WalletDisconnectEvent,
101
+ WalletAccountChangedEvent,
102
+ WalletChainChangedEvent,
103
+ WalletErrorEvent,
104
+ // Adapter interfaces
105
+ WalletAdapter,
106
+ PrivateWalletAdapter,
107
+ // Privacy types
108
+ WalletShieldedSendParams,
109
+ WalletShieldedSendResult,
110
+ // Registry types
111
+ WalletInfo,
112
+ WalletAdapterFactory,
113
+ WalletRegistryEntry,
114
+ } from '@sip-protocol/types'
115
+
116
+ export { WalletErrorCode } from '@sip-protocol/types'
@@ -0,0 +1,207 @@
1
+ /**
2
+ * Wallet Registry
3
+ *
4
+ * Manages wallet adapter registration and discovery.
5
+ * Allows applications to register and detect available wallets.
6
+ */
7
+
8
+ import type {
9
+ ChainId,
10
+ WalletAdapter,
11
+ PrivateWalletAdapter,
12
+ WalletInfo,
13
+ WalletRegistryEntry,
14
+ WalletAdapterFactory,
15
+ } from '@sip-protocol/types'
16
+
17
+ /**
18
+ * Global wallet registry
19
+ *
20
+ * Manages available wallet adapters and provides discovery functionality.
21
+ *
22
+ * @example
23
+ * ```typescript
24
+ * // Register a wallet adapter
25
+ * walletRegistry.register({
26
+ * info: {
27
+ * id: 'phantom',
28
+ * name: 'Phantom',
29
+ * chains: ['solana'],
30
+ * supportsPrivacy: false,
31
+ * },
32
+ * factory: () => new PhantomWalletAdapter(),
33
+ * detect: () => typeof window !== 'undefined' && 'phantom' in window,
34
+ * })
35
+ *
36
+ * // Get available wallets for a chain
37
+ * const solanaWallets = walletRegistry.getAvailableWallets('solana')
38
+ *
39
+ * // Create a wallet adapter
40
+ * const wallet = walletRegistry.create('phantom')
41
+ * ```
42
+ */
43
+ class WalletRegistry {
44
+ private entries: Map<string, WalletRegistryEntry> = new Map()
45
+
46
+ /**
47
+ * Register a wallet adapter
48
+ *
49
+ * @param entry - The wallet registry entry
50
+ */
51
+ register(entry: WalletRegistryEntry): void {
52
+ this.entries.set(entry.info.id, entry)
53
+ }
54
+
55
+ /**
56
+ * Unregister a wallet adapter
57
+ *
58
+ * @param id - The wallet identifier
59
+ */
60
+ unregister(id: string): void {
61
+ this.entries.delete(id)
62
+ }
63
+
64
+ /**
65
+ * Get wallet info by ID
66
+ *
67
+ * @param id - The wallet identifier
68
+ * @returns The wallet info or undefined
69
+ */
70
+ getInfo(id: string): WalletInfo | undefined {
71
+ return this.entries.get(id)?.info
72
+ }
73
+
74
+ /**
75
+ * Get all registered wallet infos
76
+ *
77
+ * @returns Array of all wallet infos
78
+ */
79
+ getAllWallets(): WalletInfo[] {
80
+ return Array.from(this.entries.values()).map((entry) => entry.info)
81
+ }
82
+
83
+ /**
84
+ * Get wallets that support a specific chain
85
+ *
86
+ * @param chain - The chain to filter by
87
+ * @returns Array of wallet infos that support the chain
88
+ */
89
+ getWalletsForChain(chain: ChainId): WalletInfo[] {
90
+ return Array.from(this.entries.values())
91
+ .filter((entry) => entry.info.chains.includes(chain))
92
+ .map((entry) => entry.info)
93
+ }
94
+
95
+ /**
96
+ * Get available (detected) wallets
97
+ *
98
+ * @param chain - Optional chain to filter by
99
+ * @returns Array of wallet infos for detected wallets
100
+ */
101
+ getAvailableWallets(chain?: ChainId): WalletInfo[] {
102
+ return Array.from(this.entries.values())
103
+ .filter((entry) => {
104
+ // Check if detected
105
+ if (!entry.detect()) return false
106
+ // Check chain if specified
107
+ if (chain && !entry.info.chains.includes(chain)) return false
108
+ return true
109
+ })
110
+ .map((entry) => entry.info)
111
+ }
112
+
113
+ /**
114
+ * Get wallets that support privacy features
115
+ *
116
+ * @param chain - Optional chain to filter by
117
+ * @returns Array of wallet infos with privacy support
118
+ */
119
+ getPrivacyWallets(chain?: ChainId): WalletInfo[] {
120
+ return Array.from(this.entries.values())
121
+ .filter((entry) => {
122
+ if (!entry.info.supportsPrivacy) return false
123
+ if (chain && !entry.info.chains.includes(chain)) return false
124
+ return true
125
+ })
126
+ .map((entry) => entry.info)
127
+ }
128
+
129
+ /**
130
+ * Check if a wallet is available (detected)
131
+ *
132
+ * @param id - The wallet identifier
133
+ * @returns True if wallet is detected
134
+ */
135
+ isAvailable(id: string): boolean {
136
+ const entry = this.entries.get(id)
137
+ return entry ? entry.detect() : false
138
+ }
139
+
140
+ /**
141
+ * Create a wallet adapter instance
142
+ *
143
+ * @param id - The wallet identifier
144
+ * @returns A new wallet adapter instance
145
+ * @throws If wallet is not registered
146
+ */
147
+ create(id: string): WalletAdapter | PrivateWalletAdapter {
148
+ const entry = this.entries.get(id)
149
+ if (!entry) {
150
+ throw new Error(`Wallet '${id}' is not registered`)
151
+ }
152
+ return entry.factory()
153
+ }
154
+
155
+ /**
156
+ * Create and connect a wallet adapter
157
+ *
158
+ * @param id - The wallet identifier
159
+ * @returns A connected wallet adapter
160
+ * @throws If wallet is not registered or connection fails
161
+ */
162
+ async connect(id: string): Promise<WalletAdapter | PrivateWalletAdapter> {
163
+ const wallet = this.create(id)
164
+ await wallet.connect()
165
+ return wallet
166
+ }
167
+
168
+ /**
169
+ * Clear all registered wallets
170
+ */
171
+ clear(): void {
172
+ this.entries.clear()
173
+ }
174
+ }
175
+
176
+ /**
177
+ * Global wallet registry instance
178
+ */
179
+ export const walletRegistry = new WalletRegistry()
180
+
181
+ /**
182
+ * Helper to register a wallet adapter
183
+ */
184
+ export function registerWallet(entry: WalletRegistryEntry): void {
185
+ walletRegistry.register(entry)
186
+ }
187
+
188
+ /**
189
+ * Helper to create a wallet adapter factory
190
+ */
191
+ export function createWalletFactory<T extends WalletAdapter | PrivateWalletAdapter>(
192
+ AdapterClass: new () => T
193
+ ): WalletAdapterFactory {
194
+ return () => new AdapterClass()
195
+ }
196
+
197
+ /**
198
+ * Check if an adapter is a PrivateWalletAdapter
199
+ */
200
+ export function isPrivateWalletAdapter(
201
+ adapter: WalletAdapter | PrivateWalletAdapter
202
+ ): adapter is PrivateWalletAdapter {
203
+ return (
204
+ 'supportsStealthAddresses' in adapter &&
205
+ typeof adapter.supportsStealthAddresses === 'function'
206
+ )
207
+ }