@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,239 @@
1
+ /**
2
+ * Zod validation schemas for BananaLink protocol types
3
+ * Provides runtime type validation for all protocol messages
4
+ */
5
+
6
+ import { z } from 'zod';
7
+ import { FRUITS, NETWORK_CONFIG } from '../constants';
8
+
9
+ // Base validation schemas
10
+ const ethereumAddressSchema = z.string().regex(/^0x[a-fA-F0-9]{40}$/, 'Invalid Ethereum address');
11
+ const iso8601Schema = z.string().datetime({ message: 'Invalid ISO 8601 datetime' });
12
+ const urlSchema = z.string().url('Invalid URL');
13
+ const domainSchema = z.string().min(1, 'Domain cannot be empty');
14
+ // Protocol v2.0: 32-byte nonces = 64 hex characters
15
+ const nonceSchema = z
16
+ .string()
17
+ .length(64, 'Nonce must be exactly 64 characters (32 bytes hex-encoded)')
18
+ .regex(/^[a-fA-F0-9]{64}$/, 'Nonce must be a valid 64-character hexadecimal string');
19
+
20
+ // SIWE base schema
21
+ export const siweFieldsSchema = z.object({
22
+ scheme: z.string().optional(),
23
+ domain: domainSchema,
24
+ address: ethereumAddressSchema,
25
+ statement: z.string().optional(),
26
+ uri: urlSchema,
27
+ version: z.literal('1'),
28
+ chainId: z.number().int().positive(),
29
+ nonce: nonceSchema,
30
+ issuedAt: iso8601Schema,
31
+ expirationTime: iso8601Schema.optional(),
32
+ notBefore: iso8601Schema.optional(),
33
+ requestId: z.string().optional(),
34
+ resources: z.array(urlSchema).optional(),
35
+ });
36
+
37
+ // DApp metadata schema
38
+ export const dAppMetadataSchema = z.object({
39
+ returnUrl: urlSchema.optional(),
40
+ });
41
+
42
+ // QR payload schema
43
+ export const qrPayloadSchema = z.object({
44
+ sessionId: z.string().min(1, 'Session ID cannot be empty'),
45
+ publicKey: z.string().min(1, 'Public key cannot be empty'),
46
+ providerId: z.string().optional(),
47
+ relayUrl: z.string().url().optional(),
48
+ });
49
+
50
+ // Encrypted payload schema
51
+ export const encryptedPayloadSchema = z.object({
52
+ iv: z.string().min(1, 'IV cannot be empty'),
53
+ ciphertext: z.string().min(1, 'Ciphertext cannot be empty'),
54
+ mac: z.string().min(1, 'MAC cannot be empty'),
55
+ });
56
+
57
+ // Relay message type schema
58
+ export const relayMessageTypeSchema = z.enum(['pub', 'sub', 'ack', 'error']);
59
+
60
+ // Relay message schema
61
+ export const relayMessageSchema = z.object({
62
+ topic: z.string().min(1, 'Topic cannot be empty'),
63
+ type: relayMessageTypeSchema,
64
+ payload: encryptedPayloadSchema.optional(),
65
+ id: z.string().min(1, 'Message ID cannot be empty'),
66
+ });
67
+
68
+ // Security policy schema
69
+ export const securityPolicySchema = z.object({
70
+ sessionTimeout: z.number().int().positive(),
71
+ requestTimeout: z.number().int().positive(),
72
+ maxConcurrentSessions: z.number().int().positive(),
73
+ requireOriginProof: z.boolean(),
74
+ allowedDomains: z.array(domainSchema).optional(),
75
+ blockedDomains: z.array(domainSchema).optional(),
76
+ });
77
+
78
+ // Origin proof schema
79
+ export const originProofSchema = z.object({
80
+ domain: domainSchema,
81
+ timestamp: z.number().int().positive(),
82
+ signature: z.string().min(1, 'Signature cannot be empty'),
83
+ });
84
+
85
+ // Session state schema (Protocol v2.0)
86
+ export const sessionStateSchema = z.enum([
87
+ 'created',
88
+ 'claimed',
89
+ 'authenticated',
90
+ 'active',
91
+ 'disconnected',
92
+ 'closed',
93
+ 'expired',
94
+ 'rejected',
95
+ ]);
96
+
97
+ // Session info schema
98
+ export const sessionInfoSchema = z.object({
99
+ sessionId: z.string().min(1, 'Session ID cannot be empty'),
100
+ state: sessionStateSchema,
101
+ address: ethereumAddressSchema.optional(),
102
+ metadata: dAppMetadataSchema,
103
+ createdAt: iso8601Schema,
104
+ lastActivity: iso8601Schema,
105
+ expiresAt: iso8601Schema.optional(),
106
+ });
107
+
108
+ // Display info schema
109
+ export const displayInfoSchema = z.object({
110
+ qrCode: z.string().min(1, 'QR code cannot be empty'),
111
+ frutiLink: z.string().regex(/^[🍌🍎🍊🍇🍓🍑🍒🍉🥝🍍🥭🥥🫐🍈🍋🥑]{8}$/u, 'Invalid FrutiLink format'),
112
+ deepLink: urlSchema,
113
+ });
114
+
115
+ // Error code schema (Protocol v2.0+)
116
+ export const errorCodeSchema = z.enum([
117
+ // Session errors
118
+ 'SESSION_EXPIRED',
119
+ 'SESSION_NOT_FOUND',
120
+ 'SESSION_ALREADY_CLAIMED',
121
+ 'INVALID_SESSION_CLAIM',
122
+ 'INVALID_CLIENT_CLAIM',
123
+ 'SESSION_TOKEN_EXPIRED',
124
+ 'SESSION_CLOSED',
125
+ // Encryption errors
126
+ 'ENCRYPTION_FAILED',
127
+ 'DECRYPTION_FAILED',
128
+ 'INVALID_SIGNATURE',
129
+ // Network errors
130
+ 'NETWORK_ERROR',
131
+ 'RELAY_ERROR',
132
+ 'MESSAGE_QUEUE_FULL',
133
+ 'RATE_LIMIT_EXCEEDED',
134
+ 'ORIGIN_MISMATCH',
135
+ // Request lifecycle errors (v2.1+)
136
+ 'REQUEST_PENDING',
137
+ 'REQUEST_NOT_FOUND',
138
+ 'REQUEST_EXPIRED',
139
+ 'REQUEST_INVALID',
140
+ 'REQUEST_TIMEOUT',
141
+ // Operation-specific errors (v2.1+)
142
+ 'SIGNING_FAILED',
143
+ 'SIGNING_REJECTED',
144
+ 'TRANSACTION_INVALID',
145
+ 'CHAIN_MISMATCH',
146
+ ]);
147
+
148
+ // BananaLink error schema
149
+ export const bananaLinkErrorSchema = z.object({
150
+ code: errorCodeSchema,
151
+ message: z.string().min(1, 'Error message cannot be empty'),
152
+ details: z.unknown().optional(),
153
+ });
154
+
155
+ // FrutiLink validation
156
+ export const frutiLinkSchema = z.string()
157
+ .length(8, 'FrutiLink must be exactly 8 characters')
158
+ .refine(
159
+ (value) => value.split('').every(char => FRUITS.includes(char as typeof FRUITS[number])),
160
+ 'FrutiLink contains invalid fruits'
161
+ );
162
+
163
+ // Supported chain ID validation
164
+ export const supportedChainIdSchema = z.number()
165
+ .int()
166
+ .refine(
167
+ (chainId) => NETWORK_CONFIG.SUPPORTED_CHAINS.includes(chainId as typeof NETWORK_CONFIG.SUPPORTED_CHAINS[number]),
168
+ 'Unsupported chain ID'
169
+ );
170
+
171
+ // Base64 string validation
172
+ export const base64Schema = z.string().regex(
173
+ /^[A-Za-z0-9+/]*={0,2}$/,
174
+ 'Invalid Base64 string'
175
+ );
176
+
177
+ // Session ID validation (UUID v4)
178
+ export const sessionIdSchema = z.string().regex(
179
+ /^[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/,
180
+ 'Invalid session ID format'
181
+ );
182
+
183
+ // Public key validation (base64 encoded)
184
+ export const publicKeySchema = z.string()
185
+ .min(1, 'Public key cannot be empty')
186
+ .refine(
187
+ (key) => {
188
+ try {
189
+ atob(key);
190
+ return true;
191
+ } catch {
192
+ return false;
193
+ }
194
+ },
195
+ 'Public key must be valid Base64'
196
+ );
197
+
198
+ // Validation helper functions (Protocol v2.0)
199
+ export const validateQRPayload = (payload: unknown) => qrPayloadSchema.parse(payload);
200
+ export const validateRelayMessage = (message: unknown) => relayMessageSchema.parse(message);
201
+ export const validateSessionInfo = (info: unknown) => sessionInfoSchema.parse(info);
202
+ export const validateFrutiLink = (frutiLink: unknown) => frutiLinkSchema.parse(frutiLink);
203
+
204
+ // Safe parsing functions (returns { success: boolean, data?: T, error?: ZodError })
205
+ export const safeValidateQRPayload = (payload: unknown) => qrPayloadSchema.safeParse(payload);
206
+ export const safeValidateRelayMessage = (message: unknown) => relayMessageSchema.safeParse(message);
207
+ export const safeValidateSessionInfo = (info: unknown) => sessionInfoSchema.safeParse(info);
208
+ export const safeValidateFrutiLink = (frutiLink: unknown) => frutiLinkSchema.safeParse(frutiLink);
209
+
210
+ // Type inference from schemas (Protocol v2.0)
211
+ export type SIWEFields = z.infer<typeof siweFieldsSchema>;
212
+ export type DAppMetadata = z.infer<typeof dAppMetadataSchema>;
213
+ export type QRPayload = z.infer<typeof qrPayloadSchema>;
214
+ export type EncryptedPayload = z.infer<typeof encryptedPayloadSchema>;
215
+ export type RelayMessage = z.infer<typeof relayMessageSchema>;
216
+ export type SecurityPolicy = z.infer<typeof securityPolicySchema>;
217
+ export type SessionInfo = z.infer<typeof sessionInfoSchema>;
218
+ export type DisplayInfo = z.infer<typeof displayInfoSchema>;
219
+
220
+ // Re-export core schemas (Protocol v2.0)
221
+ export {
222
+ dAppMetadataSchema as coreDAppMetadataSchema,
223
+ sessionConfigSchema,
224
+ sessionOptionsSchema,
225
+ sessionMetadataSchema,
226
+ createSessionRequestSchema,
227
+ createSessionResponseSchema,
228
+ clientSessionClaimSchema,
229
+ securityPolicySchema as coreSecurityPolicySchema,
230
+ } from './core';
231
+
232
+ // Re-export v2.0 wallet message schemas
233
+ export * from './wallet-messages';
234
+
235
+ // Re-export v2.0 client message schemas
236
+ export * from './client-messages';
237
+
238
+ // Re-export v2.0 relay message schemas
239
+ export * from './relay-messages';
@@ -0,0 +1,45 @@
1
+ /**
2
+ * Validation schemas for relay notification message types
3
+ */
4
+
5
+ import { z } from 'zod';
6
+
7
+ // Session closed acknowledgment schema
8
+ export const sessionClosedAckSchema = z.object({
9
+ type: z.literal('session_closed_ack'),
10
+ timestamp: z.number().int().positive(),
11
+ });
12
+
13
+ // Session closed notification schema
14
+ export const sessionClosedNotificationSchema = z.object({
15
+ type: z.literal('session_closed_notification'),
16
+ /** Which party closed the session */
17
+ closedBy: z.enum(['client', 'wallet']),
18
+ /** Optional reason for closure */
19
+ reason: z.string().optional(),
20
+ timestamp: z.number().int().positive(),
21
+ });
22
+
23
+ // Reconnected message schema
24
+ export const reconnectedMessageSchema = z.object({
25
+ type: z.literal('reconnected'),
26
+ /** Number of messages that were queued during disconnection */
27
+ queuedMessages: z.number().int().nonnegative(),
28
+ timestamp: z.number().int().positive(),
29
+ });
30
+
31
+ // Union of all relay notification messages
32
+ export const relayNotificationMessageSchema = z.discriminatedUnion('type', [
33
+ sessionClosedAckSchema,
34
+ sessionClosedNotificationSchema,
35
+ reconnectedMessageSchema,
36
+ ]);
37
+
38
+ // Validation helper functions
39
+ export const validateRelayNotificationMessage = (message: unknown) => {
40
+ return relayNotificationMessageSchema.parse(message);
41
+ };
42
+
43
+ export const safeValidateRelayNotificationMessage = (message: unknown) => {
44
+ return relayNotificationMessageSchema.safeParse(message);
45
+ };
@@ -0,0 +1,177 @@
1
+ /**
2
+ * Validation schemas for wallet message types
3
+ */
4
+
5
+ import { z } from 'zod';
6
+ import { dAppMetadataSchema, sessionConfigSchema } from './core';
7
+ import { encryptedPayloadSchema } from './discovery';
8
+
9
+ // Re-export encryptedPayloadSchema for convenience
10
+ export { encryptedPayloadSchema };
11
+
12
+ // Wallet session claim schema
13
+ export const walletSessionClaimSchema = z.object({
14
+ /** Unique nonce for this session claim (32 bytes hex-encoded = 64 characters) */
15
+ sessionNonce: z
16
+ .string()
17
+ .length(64, 'Session nonce must be exactly 64 characters (32 bytes hex-encoded)')
18
+ .regex(/^[0-9a-f]{64}$/, 'Session nonce must be lowercase hexadecimal'),
19
+ /** Claim timestamp */
20
+ timestamp: z.number().int().positive('Timestamp must be a positive integer'),
21
+ });
22
+
23
+ // Wallet message payload schemas
24
+ export const claimSessionPayloadSchema = z.object({
25
+ type: z.literal('claim_session'),
26
+ /** Wallet's public key for ECDH key exchange - format: "algorithm:key_data" (e.g., "AES-GCM:base64..." or "cleartext:none") */
27
+ walletPublicKey: z
28
+ .string()
29
+ .min(1, 'Wallet public key cannot be empty')
30
+ .regex(/^(AES-GCM:|cleartext:)/, 'Wallet public key must start with "AES-GCM:" or "cleartext:"'),
31
+ });
32
+
33
+ export const prefetchMetadataPayloadSchema = z.object({
34
+ type: z.literal('prefetch_metadata'),
35
+ });
36
+
37
+ export const connectionRejectedPayloadSchema = z.object({
38
+ type: z.literal('connection_rejected'),
39
+ encrypted: encryptedPayloadSchema,
40
+ });
41
+
42
+ export const authenticateConnectionPayloadSchema = z.object({
43
+ type: z.literal('authenticate_connection'),
44
+ encrypted: encryptedPayloadSchema,
45
+ });
46
+
47
+ export const walletReconnectPayloadSchema = z.object({
48
+ type: z.literal('wallet_reconnect'),
49
+ });
50
+
51
+ // Union of all wallet message payloads
52
+ export const walletMessagePayloadSchema = z.discriminatedUnion('type', [
53
+ claimSessionPayloadSchema,
54
+ prefetchMetadataPayloadSchema,
55
+ connectionRejectedPayloadSchema,
56
+ authenticateConnectionPayloadSchema,
57
+ walletReconnectPayloadSchema,
58
+ ]);
59
+
60
+ // Wallet message envelope schema
61
+ export const walletMessageEnvelopeSchema = z.object({
62
+ /** Session identifier */
63
+ sessionId: z.string().uuid('Session ID must be a valid UUID'),
64
+ /** Wallet session claim credential */
65
+ walletSessionClaim: walletSessionClaimSchema,
66
+ /** Message payload */
67
+ payload: walletMessagePayloadSchema,
68
+ });
69
+
70
+ // Decrypted data schemas
71
+ export const rejectionDataSchema = z.object({
72
+ reason: z.string().optional(),
73
+ timestamp: z.number().int().positive(),
74
+ });
75
+
76
+ export const walletMetadataSchema = z.object({
77
+ /** Wallet name */
78
+ name: z.string().min(1, 'Wallet name cannot be empty'),
79
+ /** Wallet version */
80
+ version: z.string().optional(),
81
+ /** Wallet icon URL */
82
+ icon: z.string().url('Invalid icon URL').optional(),
83
+ /** Wallet description */
84
+ description: z.string().optional(),
85
+ /** Wallet website URL */
86
+ url: z.string().url('Invalid wallet URL').optional(),
87
+ /** Wallet provider type */
88
+ provider: z.string().optional(),
89
+ });
90
+
91
+ export const authenticationDataSchema = z.object({
92
+ /** Wallet address */
93
+ address: z.string().regex(/^0x[a-fA-F0-9]{40}$/, 'Invalid Ethereum address'),
94
+ /** Signature of the SIWE message */
95
+ signature: z.string().regex(/^0x[a-fA-F0-9]+$/, 'Invalid signature format'),
96
+ /** Full SIWE message that was signed */
97
+ message: z.string().min(1, 'Message cannot be empty'),
98
+ /** Chain ID (EIP-155) */
99
+ chainId: z.number().int().positive('Chain ID must be a positive integer'),
100
+ /** Network identifier */
101
+ network: z.string().optional(),
102
+ /** Signing algorithm */
103
+ signingAlgo: z.string().optional(),
104
+ /** Optional wallet metadata */
105
+ walletMetadata: walletMetadataSchema.optional(),
106
+ });
107
+
108
+ // Relay-to-dApp message schemas
109
+ export const walletHandshakeMessageSchema = z.object({
110
+ type: z.literal('wallet_handshake'),
111
+ status: z.literal('reviewing'),
112
+ /** Wallet's public key in format "algorithm:base64_public_key" */
113
+ walletPublicKey: z.string(),
114
+ timestamp: z.number().int().positive(),
115
+ });
116
+
117
+ export const connectionRejectedMessageSchema = z.object({
118
+ type: z.literal('connection_rejected'),
119
+ encrypted: encryptedPayloadSchema,
120
+ });
121
+
122
+ export const connectionAuthenticatedMessageSchema = z.object({
123
+ type: z.literal('connection_authenticated'),
124
+ encrypted: encryptedPayloadSchema,
125
+ });
126
+
127
+ // Union of relay-to-dApp messages
128
+ export const relayToDAppMessageSchema = z.discriminatedUnion('type', [
129
+ walletHandshakeMessageSchema,
130
+ connectionRejectedMessageSchema,
131
+ connectionAuthenticatedMessageSchema,
132
+ ]);
133
+
134
+ // Prefetch metadata response schema
135
+ export const prefetchMetadataResponseSchema = z.object({
136
+ dappMetadata: dAppMetadataSchema,
137
+ sessionConfig: sessionConfigSchema.optional(),
138
+ });
139
+
140
+ // Validation helper functions
141
+ export const validateWalletMessage = (message: unknown) => {
142
+ return walletMessageEnvelopeSchema.parse(message);
143
+ };
144
+
145
+ export const safeValidateWalletMessage = (message: unknown) => {
146
+ return walletMessageEnvelopeSchema.safeParse(message);
147
+ };
148
+
149
+ export const validateRelayToDAppMessage = (message: unknown) => {
150
+ return relayToDAppMessageSchema.parse(message);
151
+ };
152
+
153
+ export const safeValidateRelayToDAppMessage = (message: unknown) => {
154
+ return relayToDAppMessageSchema.safeParse(message);
155
+ };
156
+
157
+ export const validateWalletSessionClaim = (claim: unknown) => {
158
+ return walletSessionClaimSchema.parse(claim);
159
+ };
160
+
161
+ export const safeValidateWalletSessionClaim = (claim: unknown) => {
162
+ return walletSessionClaimSchema.safeParse(claim);
163
+ };
164
+
165
+ /**
166
+ * Validate timestamp for replay attack prevention
167
+ * @param timestamp - Timestamp to validate
168
+ * @param maxAge - Maximum age in milliseconds (default: 5 minutes)
169
+ */
170
+ export function validateTimestamp(timestamp: number, maxAge: number = 5 * 60 * 1000): boolean {
171
+ const now = Date.now();
172
+ const age = Math.abs(now - timestamp);
173
+ return age <= maxAge;
174
+ }
175
+
176
+ // NOTE: Nonce generation moved to @bananalink-sdk/crypto package to avoid circular dependencies
177
+ // SDKs should use crypto.generateSessionNonce() before validating session claims
@@ -0,0 +1,23 @@
1
+ /**
2
+ * Zod validation schemas for BananaLink protocol
3
+ *
4
+ * This module exports all runtime validation schemas for the BananaLink protocol.
5
+ * Import from '@bananalink-sdk/protocol/schemas' when you need runtime validation.
6
+ *
7
+ * For TypeScript type definitions only, import from '@bananalink-sdk/protocol' instead.
8
+ *
9
+ * @example
10
+ * ```typescript
11
+ * // Runtime validation
12
+ * import { sessionStateSchema, validateMessage } from '@bananalink-sdk/protocol/schemas';
13
+ *
14
+ * // Type-only imports
15
+ * import type { SessionState, BananaLinkMessage } from '@bananalink-sdk/protocol';
16
+ * ```
17
+ *
18
+ * @packageDocumentation
19
+ */
20
+
21
+ // Re-export all schemas via the main schemas index
22
+ // (which already aggregates all schema modules)
23
+ export * from './schemas';
@@ -0,0 +1,27 @@
1
+ /**
2
+ * SIWE (Sign-In with Ethereum) utilities for BananaLink protocol
3
+ *
4
+ * This module exports ERC-4361 compliant message construction and validation utilities.
5
+ * Import from '@bananalink-sdk/protocol/siwe' when you need SIWE message handling.
6
+ *
7
+ * Most SDKs and applications won't need this module unless implementing custom
8
+ * authentication flows or wallet integration at the protocol level.
9
+ *
10
+ * @example
11
+ * ```typescript
12
+ * // SIWE message construction and validation
13
+ * import {
14
+ * constructSIWEMessage,
15
+ * parseSIWEMessage,
16
+ * validateSIWEMessage
17
+ * } from '@bananalink-sdk/protocol/siwe';
18
+ *
19
+ * // Type-only imports (available from main export)
20
+ * import type { SIWEFields, SIWEMessageOptions } from '@bananalink-sdk/protocol';
21
+ * ```
22
+ *
23
+ * @packageDocumentation
24
+ */
25
+
26
+ // Re-export all SIWE utilities
27
+ export * from './utils/siwe';
package/src/testing.ts ADDED
@@ -0,0 +1,71 @@
1
+ /**
2
+ * Testing utilities for @bananalink-sdk/protocol
3
+ *
4
+ * This module provides test utilities and mock implementations
5
+ * for testing BananaLink protocol integrations.
6
+ *
7
+ * @packageDocumentation
8
+ */
9
+
10
+ import type { WalletSessionClaim } from './types/wallet-messages';
11
+ import type { ClientSessionClaim } from './types/client-messages';
12
+
13
+ // Re-export storage interfaces
14
+ export type { WalletSessionClaimStorage } from './utils/wallet-session-claim';
15
+ export type { ClientSessionClaimStorage } from './utils/client-session-claim';
16
+
17
+ /**
18
+ * In-memory wallet session claim storage (for testing/development)
19
+ *
20
+ * WARNING: Do not use in production - claims should persist across app restarts
21
+ */
22
+ export class InMemoryWalletSessionClaimStorage {
23
+ private claims: Map<string, WalletSessionClaim> = new Map();
24
+
25
+ store(sessionId: string, claim: WalletSessionClaim): Promise<void> {
26
+ this.claims.set(sessionId, claim);
27
+ return Promise.resolve();
28
+ }
29
+
30
+ retrieve(sessionId: string): Promise<WalletSessionClaim | null> {
31
+ return Promise.resolve(this.claims.get(sessionId) || null);
32
+ }
33
+
34
+ remove(sessionId: string): Promise<void> {
35
+ this.claims.delete(sessionId);
36
+ return Promise.resolve();
37
+ }
38
+
39
+ clear(): Promise<void> {
40
+ this.claims.clear();
41
+ return Promise.resolve();
42
+ }
43
+ }
44
+
45
+ /**
46
+ * In-memory client session claim storage (for testing/development)
47
+ *
48
+ * WARNING: Do not use in production - claims should persist across page reloads
49
+ */
50
+ export class InMemoryClientSessionClaimStorage {
51
+ private claims: Map<string, ClientSessionClaim> = new Map();
52
+
53
+ store(sessionId: string, claim: ClientSessionClaim): Promise<void> {
54
+ this.claims.set(sessionId, claim);
55
+ return Promise.resolve();
56
+ }
57
+
58
+ retrieve(sessionId: string): Promise<ClientSessionClaim | null> {
59
+ return Promise.resolve(this.claims.get(sessionId) || null);
60
+ }
61
+
62
+ remove(sessionId: string): Promise<void> {
63
+ this.claims.delete(sessionId);
64
+ return Promise.resolve();
65
+ }
66
+
67
+ clear(): Promise<void> {
68
+ this.claims.clear();
69
+ return Promise.resolve();
70
+ }
71
+ }
@@ -0,0 +1,60 @@
1
+ /**
2
+ * Authentication types for the BananaLink protocol
3
+ */
4
+
5
+ // Import to ensure global type declarations are available
6
+ import './index';
7
+
8
+ // Session state
9
+ export type SessionState =
10
+ | 'created' // Session created by API Gateway
11
+ | 'claimed' // Wallet claimed session
12
+ | 'authenticated' // Wallet authenticated successfully
13
+ | 'active' // Both parties connected and communicating
14
+ | 'disconnected' // One or both parties temporarily disconnected
15
+ | 'closed' // Gracefully terminated via close_session
16
+ | 'expired' // TTL exceeded without activity
17
+ | 'rejected'; // Wallet explicitly rejected connection
18
+
19
+ // Session information
20
+ export interface SessionInfo {
21
+ /** Session identifier */
22
+ sessionId: string;
23
+ /** Current state */
24
+ state: SessionState;
25
+ /** Connected address (if any) */
26
+ address?: string;
27
+ /** DApp identifier */
28
+ dappId: string;
29
+ /** Essential dApp info */
30
+ dappInfo: Pick<DAppMetadata, 'name' | 'url'>;
31
+ /** Session-specific configuration */
32
+ sessionConfig?: SessionConfig;
33
+ /** Creation timestamp */
34
+ createdAt: string;
35
+ /** Last activity timestamp */
36
+ lastActivity: string;
37
+ /** Expiration timestamp */
38
+ expiresAt?: string;
39
+ }
40
+
41
+ // Session security configuration
42
+ export interface SessionSecurity {
43
+ /** Shared secret for encryption */
44
+ sharedSecret: CryptoKey;
45
+ /** Session identifier */
46
+ sessionId: string;
47
+ }
48
+
49
+ // Origin proof for domain verification
50
+ export interface OriginProof {
51
+ /** Domain asserting ownership */
52
+ domain: string;
53
+ /** Timestamp of the proof */
54
+ timestamp: number;
55
+ /** Signature proving domain ownership */
56
+ signature: string;
57
+ }
58
+
59
+ // Import types from core
60
+ import type { DAppMetadata, SessionConfig } from './core';