@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.
- package/README.md +604 -0
- package/dist/chunk-32OWUOZ3.js +308 -0
- package/dist/chunk-32OWUOZ3.js.map +1 -0
- package/dist/chunk-65HNHRJK.cjs +123 -0
- package/dist/chunk-65HNHRJK.cjs.map +1 -0
- package/dist/chunk-7KYDLL3B.js +480 -0
- package/dist/chunk-7KYDLL3B.js.map +1 -0
- package/dist/chunk-A6FLEJ7R.cjs +62 -0
- package/dist/chunk-A6FLEJ7R.cjs.map +1 -0
- package/dist/chunk-CUJK7ZTS.js +217 -0
- package/dist/chunk-CUJK7ZTS.js.map +1 -0
- package/dist/chunk-GI3BUPIH.cjs +236 -0
- package/dist/chunk-GI3BUPIH.cjs.map +1 -0
- package/dist/chunk-JXHV66Q4.js +106 -0
- package/dist/chunk-JXHV66Q4.js.map +1 -0
- package/dist/chunk-KNGZKGRS.cjs +552 -0
- package/dist/chunk-KNGZKGRS.cjs.map +1 -0
- package/dist/chunk-LELPCIE7.js +840 -0
- package/dist/chunk-LELPCIE7.js.map +1 -0
- package/dist/chunk-MCZG7QEM.cjs +310 -0
- package/dist/chunk-MCZG7QEM.cjs.map +1 -0
- package/dist/chunk-TCVKC227.js +56 -0
- package/dist/chunk-TCVKC227.js.map +1 -0
- package/dist/chunk-VXLUSU5B.cjs +856 -0
- package/dist/chunk-VXLUSU5B.cjs.map +1 -0
- package/dist/chunk-WCQVDF3K.js +12 -0
- package/dist/chunk-WCQVDF3K.js.map +1 -0
- package/dist/chunk-WGEGR3DF.cjs +15 -0
- package/dist/chunk-WGEGR3DF.cjs.map +1 -0
- package/dist/client-session-claim-3QF3noOr.d.ts +197 -0
- package/dist/client-session-claim-C4lUik3b.d.cts +197 -0
- package/dist/core-DMhuNfoz.d.cts +62 -0
- package/dist/core-DMhuNfoz.d.ts +62 -0
- package/dist/crypto/providers/noble-provider.cjs +14 -0
- package/dist/crypto/providers/noble-provider.cjs.map +1 -0
- package/dist/crypto/providers/noble-provider.d.cts +30 -0
- package/dist/crypto/providers/noble-provider.d.ts +30 -0
- package/dist/crypto/providers/noble-provider.js +5 -0
- package/dist/crypto/providers/noble-provider.js.map +1 -0
- package/dist/crypto/providers/node-provider.cjs +308 -0
- package/dist/crypto/providers/node-provider.cjs.map +1 -0
- package/dist/crypto/providers/node-provider.d.cts +32 -0
- package/dist/crypto/providers/node-provider.d.ts +32 -0
- package/dist/crypto/providers/node-provider.js +306 -0
- package/dist/crypto/providers/node-provider.js.map +1 -0
- package/dist/crypto/providers/quickcrypto-provider.cjs +339 -0
- package/dist/crypto/providers/quickcrypto-provider.cjs.map +1 -0
- package/dist/crypto/providers/quickcrypto-provider.d.cts +34 -0
- package/dist/crypto/providers/quickcrypto-provider.d.ts +34 -0
- package/dist/crypto/providers/quickcrypto-provider.js +337 -0
- package/dist/crypto/providers/quickcrypto-provider.js.map +1 -0
- package/dist/crypto/providers/webcrypto-provider.cjs +310 -0
- package/dist/crypto/providers/webcrypto-provider.cjs.map +1 -0
- package/dist/crypto/providers/webcrypto-provider.d.cts +30 -0
- package/dist/crypto/providers/webcrypto-provider.d.ts +30 -0
- package/dist/crypto/providers/webcrypto-provider.js +308 -0
- package/dist/crypto/providers/webcrypto-provider.js.map +1 -0
- package/dist/crypto-BUS06Qz-.d.cts +40 -0
- package/dist/crypto-BUS06Qz-.d.ts +40 -0
- package/dist/crypto-export.cjs +790 -0
- package/dist/crypto-export.cjs.map +1 -0
- package/dist/crypto-export.d.cts +257 -0
- package/dist/crypto-export.d.ts +257 -0
- package/dist/crypto-export.js +709 -0
- package/dist/crypto-export.js.map +1 -0
- package/dist/crypto-provider-deYoVIxi.d.cts +36 -0
- package/dist/crypto-provider-deYoVIxi.d.ts +36 -0
- package/dist/index.cjs +615 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +379 -0
- package/dist/index.d.ts +379 -0
- package/dist/index.js +504 -0
- package/dist/index.js.map +1 -0
- package/dist/schemas-export.cjs +294 -0
- package/dist/schemas-export.cjs.map +1 -0
- package/dist/schemas-export.d.cts +1598 -0
- package/dist/schemas-export.d.ts +1598 -0
- package/dist/schemas-export.js +5 -0
- package/dist/schemas-export.js.map +1 -0
- package/dist/siwe-export.cjs +237 -0
- package/dist/siwe-export.cjs.map +1 -0
- package/dist/siwe-export.d.cts +27 -0
- package/dist/siwe-export.d.ts +27 -0
- package/dist/siwe-export.js +228 -0
- package/dist/siwe-export.js.map +1 -0
- package/dist/testing.cjs +54 -0
- package/dist/testing.cjs.map +1 -0
- package/dist/testing.d.cts +20 -0
- package/dist/testing.d.ts +20 -0
- package/dist/testing.js +51 -0
- package/dist/testing.js.map +1 -0
- package/dist/validation-export.cjs +359 -0
- package/dist/validation-export.cjs.map +1 -0
- package/dist/validation-export.d.cts +3 -0
- package/dist/validation-export.d.ts +3 -0
- package/dist/validation-export.js +6 -0
- package/dist/validation-export.js.map +1 -0
- package/dist/validators-export.cjs +73 -0
- package/dist/validators-export.cjs.map +1 -0
- package/dist/validators-export.d.cts +37 -0
- package/dist/validators-export.d.ts +37 -0
- package/dist/validators-export.js +4 -0
- package/dist/validators-export.js.map +1 -0
- package/package.json +140 -0
- package/src/constants/index.ts +205 -0
- package/src/crypto/context.ts +228 -0
- package/src/crypto/diagnostics.ts +772 -0
- package/src/crypto/errors.ts +114 -0
- package/src/crypto/index.ts +89 -0
- package/src/crypto/payload-handler.ts +102 -0
- package/src/crypto/providers/compliance-provider.ts +579 -0
- package/src/crypto/providers/factory.ts +204 -0
- package/src/crypto/providers/index.ts +44 -0
- package/src/crypto/providers/noble-provider.ts +392 -0
- package/src/crypto/providers/node-provider.ts +433 -0
- package/src/crypto/providers/quickcrypto-provider.ts +483 -0
- package/src/crypto/providers/registry.ts +129 -0
- package/src/crypto/providers/webcrypto-provider.ts +364 -0
- package/src/crypto/session-security.ts +185 -0
- package/src/crypto/types.ts +93 -0
- package/src/crypto/utils.ts +190 -0
- package/src/crypto-export.ts +21 -0
- package/src/index.ts +38 -0
- package/src/schemas/auth.ts +60 -0
- package/src/schemas/client-messages.ts +57 -0
- package/src/schemas/core.ts +144 -0
- package/src/schemas/crypto.ts +65 -0
- package/src/schemas/discovery.ts +79 -0
- package/src/schemas/index.ts +239 -0
- package/src/schemas/relay-messages.ts +45 -0
- package/src/schemas/wallet-messages.ts +177 -0
- package/src/schemas-export.ts +23 -0
- package/src/siwe-export.ts +27 -0
- package/src/testing.ts +71 -0
- package/src/types/auth.ts +60 -0
- package/src/types/client-messages.ts +84 -0
- package/src/types/core.ts +131 -0
- package/src/types/crypto-provider.ts +264 -0
- package/src/types/crypto.ts +90 -0
- package/src/types/discovery.ts +50 -0
- package/src/types/errors.ts +87 -0
- package/src/types/index.ts +197 -0
- package/src/types/post-auth-operations.ts +363 -0
- package/src/types/providers.ts +72 -0
- package/src/types/relay-messages.ts +60 -0
- package/src/types/request-lifecycle.ts +161 -0
- package/src/types/signing-operations.ts +99 -0
- package/src/types/wallet-messages.ts +251 -0
- package/src/utils/client-session-claim.ts +188 -0
- package/src/utils/index.ts +54 -0
- package/src/utils/public-keys.ts +49 -0
- package/src/utils/siwe.ts +362 -0
- package/src/utils/url-decoding.ts +126 -0
- package/src/utils/url-encoding.ts +144 -0
- package/src/utils/wallet-session-claim.ts +188 -0
- package/src/validation-export.ts +32 -0
- package/src/validators/index.ts +222 -0
- 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
|
+
}
|