@bananalink-sdk/protocol 1.2.7

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.
Files changed (158) hide show
  1. package/README.md +604 -0
  2. package/dist/chunk-32OWUOZ3.js +308 -0
  3. package/dist/chunk-32OWUOZ3.js.map +1 -0
  4. package/dist/chunk-65HNHRJK.cjs +123 -0
  5. package/dist/chunk-65HNHRJK.cjs.map +1 -0
  6. package/dist/chunk-7KYDLL3B.js +480 -0
  7. package/dist/chunk-7KYDLL3B.js.map +1 -0
  8. package/dist/chunk-A6FLEJ7R.cjs +62 -0
  9. package/dist/chunk-A6FLEJ7R.cjs.map +1 -0
  10. package/dist/chunk-CUJK7ZTS.js +217 -0
  11. package/dist/chunk-CUJK7ZTS.js.map +1 -0
  12. package/dist/chunk-GI3BUPIH.cjs +236 -0
  13. package/dist/chunk-GI3BUPIH.cjs.map +1 -0
  14. package/dist/chunk-JXHV66Q4.js +106 -0
  15. package/dist/chunk-JXHV66Q4.js.map +1 -0
  16. package/dist/chunk-KNGZKGRS.cjs +552 -0
  17. package/dist/chunk-KNGZKGRS.cjs.map +1 -0
  18. package/dist/chunk-LELPCIE7.js +840 -0
  19. package/dist/chunk-LELPCIE7.js.map +1 -0
  20. package/dist/chunk-MCZG7QEM.cjs +310 -0
  21. package/dist/chunk-MCZG7QEM.cjs.map +1 -0
  22. package/dist/chunk-TCVKC227.js +56 -0
  23. package/dist/chunk-TCVKC227.js.map +1 -0
  24. package/dist/chunk-VXLUSU5B.cjs +856 -0
  25. package/dist/chunk-VXLUSU5B.cjs.map +1 -0
  26. package/dist/chunk-WCQVDF3K.js +12 -0
  27. package/dist/chunk-WCQVDF3K.js.map +1 -0
  28. package/dist/chunk-WGEGR3DF.cjs +15 -0
  29. package/dist/chunk-WGEGR3DF.cjs.map +1 -0
  30. package/dist/client-session-claim-3QF3noOr.d.ts +197 -0
  31. package/dist/client-session-claim-C4lUik3b.d.cts +197 -0
  32. package/dist/core-DMhuNfoz.d.cts +62 -0
  33. package/dist/core-DMhuNfoz.d.ts +62 -0
  34. package/dist/crypto/providers/noble-provider.cjs +14 -0
  35. package/dist/crypto/providers/noble-provider.cjs.map +1 -0
  36. package/dist/crypto/providers/noble-provider.d.cts +30 -0
  37. package/dist/crypto/providers/noble-provider.d.ts +30 -0
  38. package/dist/crypto/providers/noble-provider.js +5 -0
  39. package/dist/crypto/providers/noble-provider.js.map +1 -0
  40. package/dist/crypto/providers/node-provider.cjs +308 -0
  41. package/dist/crypto/providers/node-provider.cjs.map +1 -0
  42. package/dist/crypto/providers/node-provider.d.cts +32 -0
  43. package/dist/crypto/providers/node-provider.d.ts +32 -0
  44. package/dist/crypto/providers/node-provider.js +306 -0
  45. package/dist/crypto/providers/node-provider.js.map +1 -0
  46. package/dist/crypto/providers/quickcrypto-provider.cjs +339 -0
  47. package/dist/crypto/providers/quickcrypto-provider.cjs.map +1 -0
  48. package/dist/crypto/providers/quickcrypto-provider.d.cts +34 -0
  49. package/dist/crypto/providers/quickcrypto-provider.d.ts +34 -0
  50. package/dist/crypto/providers/quickcrypto-provider.js +337 -0
  51. package/dist/crypto/providers/quickcrypto-provider.js.map +1 -0
  52. package/dist/crypto/providers/webcrypto-provider.cjs +310 -0
  53. package/dist/crypto/providers/webcrypto-provider.cjs.map +1 -0
  54. package/dist/crypto/providers/webcrypto-provider.d.cts +30 -0
  55. package/dist/crypto/providers/webcrypto-provider.d.ts +30 -0
  56. package/dist/crypto/providers/webcrypto-provider.js +308 -0
  57. package/dist/crypto/providers/webcrypto-provider.js.map +1 -0
  58. package/dist/crypto-BUS06Qz-.d.cts +40 -0
  59. package/dist/crypto-BUS06Qz-.d.ts +40 -0
  60. package/dist/crypto-export.cjs +790 -0
  61. package/dist/crypto-export.cjs.map +1 -0
  62. package/dist/crypto-export.d.cts +257 -0
  63. package/dist/crypto-export.d.ts +257 -0
  64. package/dist/crypto-export.js +709 -0
  65. package/dist/crypto-export.js.map +1 -0
  66. package/dist/crypto-provider-deYoVIxi.d.cts +36 -0
  67. package/dist/crypto-provider-deYoVIxi.d.ts +36 -0
  68. package/dist/index.cjs +615 -0
  69. package/dist/index.cjs.map +1 -0
  70. package/dist/index.d.cts +379 -0
  71. package/dist/index.d.ts +379 -0
  72. package/dist/index.js +504 -0
  73. package/dist/index.js.map +1 -0
  74. package/dist/schemas-export.cjs +294 -0
  75. package/dist/schemas-export.cjs.map +1 -0
  76. package/dist/schemas-export.d.cts +1598 -0
  77. package/dist/schemas-export.d.ts +1598 -0
  78. package/dist/schemas-export.js +5 -0
  79. package/dist/schemas-export.js.map +1 -0
  80. package/dist/siwe-export.cjs +237 -0
  81. package/dist/siwe-export.cjs.map +1 -0
  82. package/dist/siwe-export.d.cts +27 -0
  83. package/dist/siwe-export.d.ts +27 -0
  84. package/dist/siwe-export.js +228 -0
  85. package/dist/siwe-export.js.map +1 -0
  86. package/dist/testing.cjs +54 -0
  87. package/dist/testing.cjs.map +1 -0
  88. package/dist/testing.d.cts +20 -0
  89. package/dist/testing.d.ts +20 -0
  90. package/dist/testing.js +51 -0
  91. package/dist/testing.js.map +1 -0
  92. package/dist/validation-export.cjs +359 -0
  93. package/dist/validation-export.cjs.map +1 -0
  94. package/dist/validation-export.d.cts +3 -0
  95. package/dist/validation-export.d.ts +3 -0
  96. package/dist/validation-export.js +6 -0
  97. package/dist/validation-export.js.map +1 -0
  98. package/dist/validators-export.cjs +73 -0
  99. package/dist/validators-export.cjs.map +1 -0
  100. package/dist/validators-export.d.cts +37 -0
  101. package/dist/validators-export.d.ts +37 -0
  102. package/dist/validators-export.js +4 -0
  103. package/dist/validators-export.js.map +1 -0
  104. package/package.json +140 -0
  105. package/src/constants/index.ts +205 -0
  106. package/src/crypto/context.ts +228 -0
  107. package/src/crypto/diagnostics.ts +772 -0
  108. package/src/crypto/errors.ts +114 -0
  109. package/src/crypto/index.ts +89 -0
  110. package/src/crypto/payload-handler.ts +102 -0
  111. package/src/crypto/providers/compliance-provider.ts +579 -0
  112. package/src/crypto/providers/factory.ts +204 -0
  113. package/src/crypto/providers/index.ts +44 -0
  114. package/src/crypto/providers/noble-provider.ts +392 -0
  115. package/src/crypto/providers/node-provider.ts +433 -0
  116. package/src/crypto/providers/quickcrypto-provider.ts +483 -0
  117. package/src/crypto/providers/registry.ts +129 -0
  118. package/src/crypto/providers/webcrypto-provider.ts +364 -0
  119. package/src/crypto/session-security.ts +185 -0
  120. package/src/crypto/types.ts +93 -0
  121. package/src/crypto/utils.ts +190 -0
  122. package/src/crypto-export.ts +21 -0
  123. package/src/index.ts +38 -0
  124. package/src/schemas/auth.ts +60 -0
  125. package/src/schemas/client-messages.ts +57 -0
  126. package/src/schemas/core.ts +144 -0
  127. package/src/schemas/crypto.ts +65 -0
  128. package/src/schemas/discovery.ts +79 -0
  129. package/src/schemas/index.ts +239 -0
  130. package/src/schemas/relay-messages.ts +45 -0
  131. package/src/schemas/wallet-messages.ts +177 -0
  132. package/src/schemas-export.ts +23 -0
  133. package/src/siwe-export.ts +27 -0
  134. package/src/testing.ts +71 -0
  135. package/src/types/auth.ts +60 -0
  136. package/src/types/client-messages.ts +84 -0
  137. package/src/types/core.ts +131 -0
  138. package/src/types/crypto-provider.ts +264 -0
  139. package/src/types/crypto.ts +90 -0
  140. package/src/types/discovery.ts +50 -0
  141. package/src/types/errors.ts +87 -0
  142. package/src/types/index.ts +197 -0
  143. package/src/types/post-auth-operations.ts +363 -0
  144. package/src/types/providers.ts +72 -0
  145. package/src/types/relay-messages.ts +60 -0
  146. package/src/types/request-lifecycle.ts +161 -0
  147. package/src/types/signing-operations.ts +99 -0
  148. package/src/types/wallet-messages.ts +251 -0
  149. package/src/utils/client-session-claim.ts +188 -0
  150. package/src/utils/index.ts +54 -0
  151. package/src/utils/public-keys.ts +49 -0
  152. package/src/utils/siwe.ts +362 -0
  153. package/src/utils/url-decoding.ts +126 -0
  154. package/src/utils/url-encoding.ts +144 -0
  155. package/src/utils/wallet-session-claim.ts +188 -0
  156. package/src/validation-export.ts +32 -0
  157. package/src/validators/index.ts +222 -0
  158. package/src/validators-export.ts +8 -0
@@ -0,0 +1,161 @@
1
+ /**
2
+ * Request Lifecycle Types for BananaLink Protocol v2.0
3
+ *
4
+ * This module defines the foundational types for the post-authentication request lifecycle.
5
+ * All post-authentication operations (signing, transactions, etc.) inherit from these base types.
6
+ *
7
+ * Design Principles:
8
+ * - Base types are operation-agnostic (work for any post-auth operation)
9
+ * - Request/response correlation via requestId
10
+ * - Extensible metadata fields for future enhancements
11
+ * - Clear separation: lifecycle management vs operation-specific logic
12
+ */
13
+
14
+ /**
15
+ * Base request metadata shared by all post-authentication operations
16
+ * Provides extensibility for future request types
17
+ */
18
+ export interface BaseRequestMetadata {
19
+ /**
20
+ * Unique request identifier (UUID v4)
21
+ * Generated by dApp, used to match request with response
22
+ * MUST be unique within session scope
23
+ */
24
+ requestId: string;
25
+
26
+ /**
27
+ * Human-readable description of the request
28
+ * Displayed to user in wallet UI
29
+ * @example "Sign transaction to mint NFT"
30
+ * @example "Authenticate with dApp"
31
+ */
32
+ description?: string;
33
+
34
+ /**
35
+ * Request creation timestamp (Unix milliseconds)
36
+ * Used for request expiration and audit logging
37
+ */
38
+ timestamp: number;
39
+
40
+ /**
41
+ * Optional metadata for dApp-specific context
42
+ * Not interpreted by protocol, passed through to wallet
43
+ * Useful for analytics, debugging, and custom wallet UIs
44
+ * @example { "transactionType": "nft_mint", "collection": "0x..." }
45
+ */
46
+ metadata?: Record<string, unknown>;
47
+ }
48
+
49
+ /**
50
+ * Base response metadata for all fulfilled requests
51
+ */
52
+ export interface BaseResponseMetadata {
53
+ /**
54
+ * Request ID this response corresponds to
55
+ * MUST match the original request's requestId
56
+ * Used by dApp SDK to correlate request → response
57
+ */
58
+ requestId: string;
59
+
60
+ /**
61
+ * Response timestamp (Unix milliseconds)
62
+ */
63
+ timestamp: number;
64
+
65
+ /**
66
+ * Optional metadata from wallet
67
+ * Can include wallet-specific information
68
+ * @example { "signingMethod": "hardware_wallet", "device": "Ledger Nano S" }
69
+ */
70
+ metadata?: Record<string, unknown>;
71
+ }
72
+
73
+ /**
74
+ * Base rejection metadata for all rejected requests
75
+ */
76
+ export interface BaseRejectionMetadata {
77
+ /**
78
+ * Request ID being rejected
79
+ */
80
+ requestId: string;
81
+
82
+ /**
83
+ * Rejection reason code
84
+ * See RequestRejectionReason for standard codes
85
+ */
86
+ reason: RequestRejectionReason;
87
+
88
+ /**
89
+ * Human-readable rejection message
90
+ * Optional, for debugging/logging
91
+ * Should NOT be displayed to end users (use reason code for i18n)
92
+ */
93
+ message?: string;
94
+
95
+ /**
96
+ * Rejection timestamp (Unix milliseconds)
97
+ */
98
+ timestamp: number;
99
+
100
+ /**
101
+ * Optional rejection context
102
+ * Can include additional details about the rejection
103
+ * @example { "validationErrors": ["insufficient_balance"], "required": "0.1 ETH" }
104
+ */
105
+ details?: Record<string, unknown>;
106
+ }
107
+
108
+ /**
109
+ * Request rejection reasons (wallet-initiated)
110
+ *
111
+ * These are user-facing or validation-driven rejections from the wallet.
112
+ * Protocol-level errors (e.g., PENDING_REQUEST_EXISTS) use the error code system instead.
113
+ */
114
+ export type RequestRejectionReason =
115
+ | 'user_rejected' // User explicitly rejected in wallet UI
116
+ | 'request_expired' // Request expired before user action
117
+ | 'invalid_parameters' // Request parameters validation failed
118
+ | 'unsupported_operation' // Wallet doesn't support this operation
119
+ | 'insufficient_balance' // Insufficient funds for transaction
120
+ | 'security_warning' // Security scan flagged request
121
+ | 'wallet_locked' // Wallet locked during request
122
+ | 'network_mismatch' // Requested chain ID doesn't match wallet
123
+ | 'rate_limited'; // Too many requests from dApp
124
+
125
+ /**
126
+ * Request lifecycle states (relay-managed, not exposed to SDK)
127
+ *
128
+ * These states are maintained by the relay server for concurrency control.
129
+ * Documented here for implementation reference only.
130
+ *
131
+ * State Transitions:
132
+ * - created → pending (request delivered to wallet)
133
+ * - pending → fulfilled (wallet sends success response)
134
+ * - pending → rejected (wallet sends rejection)
135
+ * - pending → expired (request TTL exceeded)
136
+ * - pending → cancelled (dApp cancels request - future feature)
137
+ */
138
+ export type RequestState =
139
+ | 'pending' // Request delivered to wallet, awaiting user action
140
+ | 'fulfilled' // Request approved, response sent
141
+ | 'rejected' // Request rejected by wallet
142
+ | 'expired' // Request TTL exceeded (wallet timeout)
143
+ | 'cancelled'; // Request cancelled by dApp (future feature, not in v2.1)
144
+
145
+ /**
146
+ * Type guard to check if rejection reason is valid
147
+ */
148
+ export function isValidRejectionReason(reason: string): reason is RequestRejectionReason {
149
+ const validReasons: RequestRejectionReason[] = [
150
+ 'user_rejected',
151
+ 'request_expired',
152
+ 'invalid_parameters',
153
+ 'unsupported_operation',
154
+ 'insufficient_balance',
155
+ 'security_warning',
156
+ 'wallet_locked',
157
+ 'network_mismatch',
158
+ 'rate_limited',
159
+ ];
160
+ return validReasons.includes(reason as RequestRejectionReason);
161
+ }
@@ -0,0 +1,99 @@
1
+ /**
2
+ * Signing Operations Types for BananaLink Protocol v2.0
3
+ *
4
+ * This module defines abstract signing operation types that serve as a base
5
+ * for concrete signing methods (personal_sign, eth_signTypedData_v4).
6
+ *
7
+ * Design: All signing operations share common patterns:
8
+ * - Data to be signed (format varies by method)
9
+ * - Chain ID for signature context
10
+ * - Optional account specification
11
+ * - Signature result in hex format
12
+ */
13
+
14
+ import type { BaseRequestMetadata, BaseResponseMetadata, BaseRejectionMetadata } from './request-lifecycle';
15
+
16
+ /**
17
+ * Base signing request
18
+ * Extended by concrete signing operations (message signing, typed data signing)
19
+ */
20
+ export interface BaseSigningRequest extends BaseRequestMetadata {
21
+ /**
22
+ * The data to be signed
23
+ * Format depends on signing method:
24
+ * - personal_sign: UTF-8 string or hex-encoded bytes
25
+ * - eth_signTypedData_v4: EIP-712 typed data structure
26
+ */
27
+ data: unknown;
28
+
29
+ /**
30
+ * Chain ID for signature (EIP-155)
31
+ * Wallet MUST verify this matches current chain
32
+ * Used for signature domain separation
33
+ */
34
+ chainId: number;
35
+
36
+ /**
37
+ * Signing account address (optional)
38
+ * If specified, wallet MUST use this address
39
+ * If omitted, wallet uses currently selected account
40
+ * Format: 0x-prefixed hex string (42 characters)
41
+ */
42
+ account?: string;
43
+ }
44
+
45
+ /**
46
+ * Base signing response
47
+ * Extended by concrete signing response types
48
+ */
49
+ export interface BaseSigningResponse extends BaseResponseMetadata {
50
+ /**
51
+ * The signature produced by wallet
52
+ * Format: hex-encoded signature (0x-prefixed)
53
+ * Length depends on signature algorithm:
54
+ * - ECDSA (secp256k1): 65 bytes = 132 hex chars (+ 0x prefix)
55
+ */
56
+ signature: string;
57
+
58
+ /**
59
+ * Address that signed the data
60
+ * MUST match requested account if specified in request
61
+ * Format: 0x-prefixed hex string (42 characters)
62
+ */
63
+ signerAddress: string;
64
+ }
65
+
66
+ /**
67
+ * Signing request rejection
68
+ * Reuses base rejection metadata (type alias for clarity)
69
+ */
70
+ export type SigningRejection = BaseRejectionMetadata;
71
+
72
+ /**
73
+ * Type guard to validate signature format
74
+ */
75
+ export function isValidSignature(signature: string): boolean {
76
+ // Must be 0x-prefixed hex string
77
+ if (!signature.startsWith('0x')) {
78
+ return false;
79
+ }
80
+
81
+ // Must be valid hex (no invalid characters)
82
+ const hexPattern = /^0x[0-9a-fA-F]+$/;
83
+ if (!hexPattern.test(signature)) {
84
+ return false;
85
+ }
86
+
87
+ // ECDSA signatures are typically 65 bytes (130 hex chars + 0x)
88
+ // Allow some flexibility for different signature formats
89
+ const hexLength = signature.length - 2; // Remove 0x prefix
90
+ return hexLength >= 128 && hexLength <= 134; // 64-67 bytes
91
+ }
92
+
93
+ /**
94
+ * Type guard to validate Ethereum address format
95
+ */
96
+ export function isValidAddress(address: string): boolean {
97
+ // Must be 0x-prefixed hex string of exactly 40 hex chars (20 bytes)
98
+ return /^0x[0-9a-fA-F]{40}$/.test(address);
99
+ }
@@ -0,0 +1,251 @@
1
+ /**
2
+ * Wallet message types for BananaLink protocol v2.0
3
+ * These types define the communication between wallets and the relay server
4
+ */
5
+
6
+ import type { EncryptedPayload } from './discovery';
7
+ import type { DAppMetadata, SessionConfig } from './core';
8
+ import type { CloseSessionPayload } from './client-messages';
9
+ import type {
10
+ RequestFulfilledPayload,
11
+ RequestRejectedPayload,
12
+ } from './post-auth-operations';
13
+
14
+ /**
15
+ * Wallet session claim credential for claiming and reconnection
16
+ * NEVER forwarded to dApp - relay server only
17
+ */
18
+ export interface WalletSessionClaim {
19
+ /**
20
+ * Unique nonce for this session claim
21
+ * - Generated once by wallet when claiming session
22
+ * - Stored locally by wallet for reconnection
23
+ * - Used by relay to validate session ownership
24
+ * - Different for each session (no cross-session tracking)
25
+ */
26
+ sessionNonce: string;
27
+
28
+ /** Claim timestamp (used for replay attack prevention) */
29
+ timestamp: number;
30
+ }
31
+
32
+ /**
33
+ * Wallet message envelope structure
34
+ * All wallet-to-relay messages use this envelope
35
+ */
36
+ export interface WalletMessageEnvelope {
37
+ /** Session identifier */
38
+ sessionId: string;
39
+
40
+ /** Wallet session claim credential - ONLY visible to relay server */
41
+ walletSessionClaim: WalletSessionClaim;
42
+
43
+ /** Actual message payload */
44
+ payload: WalletMessagePayload;
45
+ }
46
+
47
+ /**
48
+ * Claim session payload
49
+ * Used to claim exclusive ownership of a session
50
+ */
51
+ export interface ClaimSessionPayload {
52
+ type: 'claim_session';
53
+ /** Wallet's public key for ECDH key exchange in format "algorithm:base64_public_key" (e.g., "AES-GCM:...") */
54
+ walletPublicKey: string;
55
+ }
56
+
57
+ /**
58
+ * Prefetch metadata payload
59
+ * Used to retrieve dApp metadata for user approval
60
+ */
61
+ export interface PrefetchMetadataPayload {
62
+ type: 'prefetch_metadata';
63
+ }
64
+
65
+ /**
66
+ * Connection rejected payload
67
+ * Notifies dApp that user rejected connection
68
+ */
69
+ export interface ConnectionRejectedPayload {
70
+ type: 'connection_rejected';
71
+ encrypted: EncryptedPayload;
72
+ }
73
+
74
+ /**
75
+ * Authenticate connection payload
76
+ * Completes authentication with signed message
77
+ */
78
+ export interface AuthenticateConnectionPayload {
79
+ type: 'authenticate_connection';
80
+ encrypted: EncryptedPayload;
81
+ }
82
+
83
+ /**
84
+ * Wallet reconnect payload
85
+ * Used to reconnect after network interruption
86
+ */
87
+ export interface WalletReconnectPayload {
88
+ type: 'wallet_reconnect';
89
+ }
90
+
91
+ /**
92
+ * Union type for all wallet message payloads
93
+ */
94
+ export type WalletMessagePayload =
95
+ // Connection flow payloads
96
+ | ClaimSessionPayload
97
+ | PrefetchMetadataPayload
98
+ | ConnectionRejectedPayload
99
+ | AuthenticateConnectionPayload
100
+ | WalletReconnectPayload
101
+ | CloseSessionPayload
102
+ // Post-authentication operation payloads (v2.1+)
103
+ | RequestFulfilledPayload
104
+ | RequestRejectedPayload;
105
+
106
+ /**
107
+ * Decrypted rejection data (visible only to dApp after decryption)
108
+ */
109
+ export interface RejectionData {
110
+ reason?: string;
111
+ timestamp: number;
112
+ }
113
+
114
+ /**
115
+ * Decrypted authentication data (visible only to dApp after decryption)
116
+ */
117
+ export interface AuthenticationData {
118
+ /** Wallet address */
119
+ address: string;
120
+
121
+ /** Signature of the SIWE message */
122
+ signature: string;
123
+
124
+ /** Full SIWE message that was signed */
125
+ message: string;
126
+
127
+ /** Chain ID (EIP-155) */
128
+ chainId: number;
129
+
130
+ /** Network identifier (e.g., "ethereum", "polygon") */
131
+ network?: string;
132
+
133
+ /** Signing algorithm (e.g., "ECDSA", "EdDSA") */
134
+ signingAlgo?: string;
135
+
136
+ /** Optional wallet metadata */
137
+ walletMetadata?: WalletMetadata;
138
+ }
139
+
140
+ /**
141
+ * Wallet metadata
142
+ */
143
+ export interface WalletMetadata {
144
+ /** Wallet name */
145
+ name: string;
146
+
147
+ /** Wallet version */
148
+ version?: string;
149
+
150
+ /** Wallet icon URL */
151
+ icon?: string;
152
+
153
+ /** Wallet description */
154
+ description?: string;
155
+
156
+ /** Wallet website URL */
157
+ url?: string;
158
+
159
+ /** Wallet provider type */
160
+ provider?: string;
161
+ }
162
+
163
+ /**
164
+ * Messages sent from relay to dApp (session claim stripped)
165
+ */
166
+
167
+ /**
168
+ * Wallet handshake message
169
+ * Initiates cryptographic handshake by sharing wallet's public key
170
+ * and notifying dApp that wallet is reviewing connection
171
+ */
172
+ export interface WalletHandshakeMessage {
173
+ type: 'wallet_handshake';
174
+ status: 'reviewing';
175
+ /** Wallet's public key in format "algorithm:base64_public_key" (e.g., "AES-GCM:...") */
176
+ walletPublicKey: string;
177
+ timestamp: number;
178
+ }
179
+
180
+ /**
181
+ * Connection rejected message
182
+ * Forwards rejection to dApp (encrypted)
183
+ */
184
+ export interface ConnectionRejectedMessage {
185
+ type: 'connection_rejected';
186
+ encrypted: EncryptedPayload;
187
+ }
188
+
189
+ /**
190
+ * Connection authenticated message
191
+ * Forwards authentication data to dApp (encrypted)
192
+ */
193
+ export interface ConnectionAuthenticatedMessage {
194
+ type: 'connection_authenticated';
195
+ encrypted: EncryptedPayload;
196
+ }
197
+
198
+ /**
199
+ * Prefetch metadata response
200
+ * Returns dApp metadata to wallet
201
+ */
202
+ export interface PrefetchMetadataResponse {
203
+ dappMetadata: DAppMetadata;
204
+ sessionConfig?: SessionConfig;
205
+ }
206
+
207
+ /**
208
+ * Union type for relay-to-dApp messages
209
+ */
210
+ export type RelayToDAppMessage =
211
+ | WalletHandshakeMessage
212
+ | ConnectionRejectedMessage
213
+ | ConnectionAuthenticatedMessage;
214
+
215
+ /**
216
+ * Type guards for wallet messages
217
+ */
218
+ export function isClaimSessionPayload(payload: WalletMessagePayload): payload is ClaimSessionPayload {
219
+ return payload?.type === 'claim_session';
220
+ }
221
+
222
+ export function isPrefetchMetadataPayload(payload: WalletMessagePayload): payload is PrefetchMetadataPayload {
223
+ return payload?.type === 'prefetch_metadata';
224
+ }
225
+
226
+ export function isConnectionRejectedPayload(payload: WalletMessagePayload): payload is ConnectionRejectedPayload {
227
+ return payload?.type === 'connection_rejected';
228
+ }
229
+
230
+ export function isAuthenticateConnectionPayload(payload: WalletMessagePayload): payload is AuthenticateConnectionPayload {
231
+ return payload?.type === 'authenticate_connection';
232
+ }
233
+
234
+ export function isWalletReconnectPayload(payload: WalletMessagePayload): payload is WalletReconnectPayload {
235
+ return payload?.type === 'wallet_reconnect';
236
+ }
237
+
238
+ /**
239
+ * Type guards for relay-to-dApp messages
240
+ */
241
+ export function isWalletHandshakeMessage(message: RelayToDAppMessage): message is WalletHandshakeMessage {
242
+ return message?.type === 'wallet_handshake';
243
+ }
244
+
245
+ export function isConnectionRejectedMessage(message: RelayToDAppMessage): message is ConnectionRejectedMessage {
246
+ return message?.type === 'connection_rejected';
247
+ }
248
+
249
+ export function isConnectionAuthenticatedMessage(message: RelayToDAppMessage): message is ConnectionAuthenticatedMessage {
250
+ return message?.type === 'connection_authenticated';
251
+ }
@@ -0,0 +1,188 @@
1
+ /**
2
+ * Client session claim utilities for BananaLink protocol v2.0
3
+ * Implements secure client session ownership and reconnection logic
4
+ */
5
+
6
+ import type { ClientSessionClaim, ClientMessageEnvelope } from '../types/client-messages';
7
+
8
+ /**
9
+ * Client session claim storage interface
10
+ * Implementations should provide secure storage for client session claims
11
+ */
12
+ export interface ClientSessionClaimStorage {
13
+ store(sessionId: string, claim: ClientSessionClaim): Promise<void>;
14
+ retrieve(sessionId: string): Promise<ClientSessionClaim | null>;
15
+ remove(sessionId: string): Promise<void>;
16
+ clear(): Promise<void>;
17
+ }
18
+
19
+ /**
20
+ * Client session claim manager for handling claim generation and validation
21
+ */
22
+ export class ClientSessionClaimManager {
23
+ constructor(private storage: ClientSessionClaimStorage) {}
24
+
25
+ /**
26
+ * Generate a new client session claim with provided nonce
27
+ * @param sessionId - Session identifier
28
+ * @param claimNonce - Pre-generated cryptographically secure nonce (use @bananalink-sdk/crypto)
29
+ * @returns Generated client session claim
30
+ */
31
+ async generateClaim(sessionId: string, claimNonce: string): Promise<ClientSessionClaim> {
32
+ const claim: ClientSessionClaim = {
33
+ claimNonce,
34
+ timestamp: Date.now(),
35
+ };
36
+
37
+ // Store the claim for future reconnection
38
+ await this.storage.store(sessionId, claim);
39
+
40
+ return claim;
41
+ }
42
+
43
+ /**
44
+ * Retrieve an existing client session claim
45
+ * @param sessionId - Session identifier
46
+ * @returns Stored client session claim or null if not found
47
+ */
48
+ async getClaim(sessionId: string): Promise<ClientSessionClaim | null> {
49
+ return this.storage.retrieve(sessionId);
50
+ }
51
+
52
+ /**
53
+ * Validate a client session claim for reconnection
54
+ * @param claim - Claim to validate
55
+ * @param storedClaim - Previously stored claim
56
+ * @returns Validation result
57
+ */
58
+ validateReconnectionClaim(
59
+ claim: ClientSessionClaim,
60
+ storedClaim: ClientSessionClaim
61
+ ): { valid: boolean; reason?: string } {
62
+ // Check nonce match
63
+ if (claim.claimNonce !== storedClaim.claimNonce) {
64
+ return {
65
+ valid: false,
66
+ reason: 'Client claim nonce mismatch - invalid reconnection attempt',
67
+ };
68
+ }
69
+
70
+ // Check timestamp validity (prevent very old reconnection attempts)
71
+ const MAX_RECONNECTION_AGE = 24 * 60 * 60 * 1000; // 24 hours
72
+ const claimAge = Date.now() - claim.timestamp;
73
+ if (claimAge > MAX_RECONNECTION_AGE) {
74
+ return {
75
+ valid: false,
76
+ reason: 'Client session claim too old - please create a new session',
77
+ };
78
+ }
79
+
80
+ return { valid: true };
81
+ }
82
+
83
+ /**
84
+ * Remove a client session claim (on session end)
85
+ * @param sessionId - Session identifier
86
+ */
87
+ async removeClaim(sessionId: string): Promise<void> {
88
+ await this.storage.remove(sessionId);
89
+ }
90
+
91
+ /**
92
+ * Clear all stored claims
93
+ */
94
+ async clearAllClaims(): Promise<void> {
95
+ await this.storage.clear();
96
+ }
97
+ }
98
+
99
+ // NOTE: Nonce generation moved to @bananalink-sdk/crypto package to avoid circular dependencies
100
+ // SDKs should use crypto.generateClientClaimNonce() before creating client session claims
101
+
102
+ /**
103
+ * Create a client message envelope with session claim
104
+ * @param sessionId - Session identifier
105
+ * @param claim - Client session claim
106
+ * @param payload - Message payload
107
+ * @returns Complete client message envelope
108
+ */
109
+ export function createClientMessageEnvelope(
110
+ sessionId: string,
111
+ claim: ClientSessionClaim,
112
+ payload: ClientMessageEnvelope['payload']
113
+ ): ClientMessageEnvelope {
114
+ return {
115
+ sessionId,
116
+ clientSessionClaim: claim,
117
+ payload,
118
+ };
119
+ }
120
+
121
+ /**
122
+ * Strip client session claim from client message for forwarding
123
+ * @param envelope - Original client message envelope
124
+ * @returns Message without client session claim
125
+ */
126
+ export function stripClientSessionClaim(
127
+ envelope: ClientMessageEnvelope
128
+ ): Omit<ClientMessageEnvelope, 'clientSessionClaim'> {
129
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars
130
+ const { clientSessionClaim, ...messageWithoutClaim } = envelope;
131
+ return messageWithoutClaim;
132
+ }
133
+
134
+ /**
135
+ * Validate client session claim timestamp for replay attack prevention
136
+ * @param timestamp - Claim timestamp
137
+ * @param maxAge - Maximum age in milliseconds (default: 30 seconds)
138
+ * @returns True if timestamp is within valid range
139
+ */
140
+ export function validateClientClaimTimestamp(
141
+ timestamp: number,
142
+ maxAge: number = 30 * 1000
143
+ ): boolean {
144
+ const now = Date.now();
145
+ const age = Math.abs(now - timestamp);
146
+ return age <= maxAge;
147
+ }
148
+
149
+ /**
150
+ * Validate client message envelope
151
+ * @param envelope - Message envelope to validate
152
+ * @returns Validation result
153
+ */
154
+ export function validateClientMessageEnvelope(
155
+ envelope: ClientMessageEnvelope
156
+ ): { valid: boolean; errors: string[] } {
157
+ const errors: string[] = [];
158
+
159
+ // Check required fields
160
+ if (!envelope.sessionId) {
161
+ errors.push('Missing session ID');
162
+ }
163
+
164
+ if (!envelope.clientSessionClaim) {
165
+ errors.push('Missing client session claim');
166
+ } else {
167
+ // Validate claim structure
168
+ if (!envelope.clientSessionClaim.claimNonce) {
169
+ errors.push('Missing claim nonce in client session claim');
170
+ }
171
+ if (!envelope.clientSessionClaim.timestamp) {
172
+ errors.push('Missing timestamp in client session claim');
173
+ } else if (!validateClientClaimTimestamp(envelope.clientSessionClaim.timestamp)) {
174
+ errors.push('Client session claim timestamp is too old or invalid');
175
+ }
176
+ }
177
+
178
+ if (!envelope.payload) {
179
+ errors.push('Missing message payload');
180
+ } else if (!envelope.payload.type) {
181
+ errors.push('Missing payload type');
182
+ }
183
+
184
+ return {
185
+ valid: errors.length === 0,
186
+ errors,
187
+ };
188
+ }