11import {
2+ ADD_PIECES_TYPEHASH ,
3+ CREATE_DATA_SET_TYPEHASH ,
24 METADATA_KEYS ,
35 type ProviderInfo ,
46 RPC_URLS ,
@@ -8,7 +10,9 @@ import {
810 Synapse ,
911 type SynapseOptions ,
1012} from '@filoz/synapse-sdk'
13+ import { type Provider as EthersProvider , JsonRpcProvider , Wallet , WebSocketProvider } from 'ethers'
1114import type { Logger } from 'pino'
15+ import { AddressOnlySigner } from './address-only-signer.js'
1216
1317const WEBSOCKET_REGEX = / ^ w s ( s ) ? : \/ \/ / i
1418
@@ -51,13 +55,22 @@ export interface Config {
5155
5256/**
5357 * Configuration for Synapse initialization
54- * Extends the main Config but makes privateKey required and rpcUrl optional
58+ *
59+ * Supports two authentication modes:
60+ * 1. Standard: privateKey only
61+ * 2. Session Key: walletAddress + sessionKey
5562 */
56- export interface SynapseSetupConfig extends Partial < Omit < Config , 'privateKey' | 'rpcUrl' > > {
57- /** Private key used for signing transactions. */
58- privateKey : string
63+ export interface SynapseSetupConfig {
64+ /** Private key for standard authentication (mutually exclusive with session key mode) */
65+ privateKey ?: string | undefined
66+ /** Wallet address for session key mode (requires sessionKey) */
67+ walletAddress ?: string | undefined
68+ /** Session key private key (requires walletAddress) */
69+ sessionKey ?: string | undefined
5970 /** RPC endpoint for the target Filecoin network. Defaults to calibration. */
6071 rpcUrl ?: string | undefined
72+ /** Optional override for WarmStorage contract address */
73+ warmStorageAddress ?: string | undefined
6174}
6275
6376/**
@@ -136,89 +149,122 @@ export function resetSynapseService(): void {
136149 activeProvider = null
137150}
138151
152+ /**
153+ * Validate authentication configuration
154+ */
155+ function validateAuthConfig ( config : SynapseSetupConfig ) : 'standard' | 'session-key' {
156+ const hasStandardAuth = config . privateKey != null
157+ const hasSessionKeyAuth = config . walletAddress != null && config . sessionKey != null
158+
159+ if ( ! hasStandardAuth && ! hasSessionKeyAuth ) {
160+ throw new Error ( 'Authentication required: provide either a privateKey or walletAddress + sessionKey' )
161+ }
162+
163+ if ( hasStandardAuth && hasSessionKeyAuth ) {
164+ throw new Error ( 'Conflicting authentication: provide either a privateKey or walletAddress + sessionKey, not both' )
165+ }
166+
167+ return hasStandardAuth ? 'standard' : 'session-key'
168+ }
169+
170+ /**
171+ * Create ethers provider for the given RPC URL
172+ */
173+ function createProvider ( rpcURL : string ) : EthersProvider {
174+ if ( WEBSOCKET_REGEX . test ( rpcURL ) ) {
175+ return new WebSocketProvider ( rpcURL )
176+ }
177+ return new JsonRpcProvider ( rpcURL )
178+ }
179+
180+ /**
181+ * Setup and verify session key, throws if expired
182+ */
183+ async function setupSessionKey ( synapse : Synapse , sessionWallet : Wallet , logger : Logger ) : Promise < void > {
184+ const sessionKey = synapse . createSessionKey ( sessionWallet )
185+
186+ // Verify permissions - fail fast if expired or expiring soon
187+ const expiries = await sessionKey . fetchExpiries ( [ CREATE_DATA_SET_TYPEHASH , ADD_PIECES_TYPEHASH ] )
188+ const now = Math . floor ( Date . now ( ) / 1000 )
189+ const bufferTime = 30 * 60 // 30 minutes in seconds
190+ const minValidTime = now + bufferTime
191+ const createExpiry = Number ( expiries [ CREATE_DATA_SET_TYPEHASH ] )
192+ const addExpiry = Number ( expiries [ ADD_PIECES_TYPEHASH ] )
193+
194+ if ( createExpiry <= minValidTime || addExpiry <= minValidTime ) {
195+ throw new Error (
196+ `Session key expired or expiring soon (requires 30+ minutes validity). CreateDataSet: ${ new Date ( createExpiry * 1000 ) . toISOString ( ) } , AddPieces: ${ new Date ( addExpiry * 1000 ) . toISOString ( ) } `
197+ )
198+ }
199+
200+ logger . info ( { event : 'synapse.session_key.verified' , createExpiry, addExpiry } , 'Session key verified' )
201+
202+ synapse . setSession ( sessionKey )
203+ logger . info ( { event : 'synapse.session_key.activated' } , 'Session key activated' )
204+ }
205+
139206/**
140207 * Initialize the Synapse SDK without creating storage context
141208 *
142- * This function initializes the Synapse SDK connection without creating
143- * a storage context. This method is primarily a wrapper for handling our
144- * custom configuration needs and adding detailed logging.
209+ * Supports two authentication modes:
210+ * - Standard: privateKey only
211+ * - Session Key: walletAddress + sessionKey
145212 *
146- * @param config - Application configuration with privateKey and RPC URL
213+ * @param config - Application configuration with authentication credentials
147214 * @param logger - Logger instance for detailed operation tracking
148215 * @returns Initialized Synapse instance
149216 */
150217export async function initializeSynapse ( config : SynapseSetupConfig , logger : Logger ) : Promise < Synapse > {
151218 try {
152- // Log the configuration status
153- logger . info (
154- {
155- hasPrivateKey : config . privateKey != null ,
156- rpcUrl : config . rpcUrl ,
157- } ,
158- 'Initializing Synapse'
159- )
219+ const authMode = validateAuthConfig ( config )
220+ const rpcURL = config . rpcUrl ?? RPC_URLS . calibration . websocket
160221
161- // IMPORTANT: Private key is required for transaction signing
162- // In production, this should come from secure environment variables, or a wallet integration
163- const privateKey = config . privateKey
164- if ( privateKey == null ) {
165- const error = new Error ( 'PRIVATE_KEY environment variable is required for Synapse integration' )
166- logger . error (
167- {
168- event : 'synapse.init.failed' ,
169- error : error . message ,
170- } ,
171- 'Synapse initialization failed: missing PRIVATE_KEY'
172- )
173- throw error
174- }
175-
176- logger . info ( { event : 'synapse.init' } , 'Initializing Synapse SDK' )
177-
178- // Configure Synapse with network settings
179- // Network options: 314 (mainnet) or 314159 (calibration testnet)
180- const synapseOptions : SynapseOptions = {
181- privateKey,
182- rpcURL : config . rpcUrl ?? RPC_URLS . calibration . websocket , // Default to calibration testnet
183- }
222+ logger . info ( { event : 'synapse.init' , authMode, rpcUrl : rpcURL } , 'Initializing Synapse SDK' )
184223
185- // Optional: Override the default Warm Storage contract address
186- // Useful for testing with custom deployments
187- if ( config . warmStorageAddress != null ) {
224+ const synapseOptions : SynapseOptions = { rpcURL }
225+ if ( config . warmStorageAddress ) {
188226 synapseOptions . warmStorageAddress = config . warmStorageAddress
189227 }
190228
191- const synapse = await Synapse . create ( synapseOptions )
192-
193- // Store reference to the provider for cleanup if it's a WebSocket provider
194- if ( synapseOptions . rpcURL && WEBSOCKET_REGEX . test ( synapseOptions . rpcURL ) ) {
229+ let synapse : Synapse
230+
231+ if ( authMode === 'session-key' ) {
232+ // Session key mode - validation guarantees these are defined
233+ const walletAddress = config . walletAddress
234+ const sessionKey = config . sessionKey
235+ if ( ! walletAddress || ! sessionKey ) {
236+ throw new Error ( 'Internal error: session key config validated but values missing' )
237+ }
238+
239+ // Create provider and signers for session key mode
240+ const provider = createProvider ( rpcURL )
241+ activeProvider = provider
242+
243+ const ownerSigner = new AddressOnlySigner ( walletAddress , provider )
244+ const sessionWallet = new Wallet ( sessionKey , provider )
245+
246+ // Initialize with owner signer, then activate session key
247+ synapse = await Synapse . create ( { ...synapseOptions , signer : ownerSigner } )
248+ await setupSessionKey ( synapse , sessionWallet , logger )
249+ } else {
250+ // Standard mode - validation guarantees privateKey is defined
251+ const privateKey = config . privateKey
252+ if ( ! privateKey ) {
253+ throw new Error ( 'Internal error: standard auth validated but privateKey missing' )
254+ }
255+
256+ synapse = await Synapse . create ( { ...synapseOptions , privateKey } )
195257 activeProvider = synapse . getProvider ( )
196258 }
197259
198- // Get network info for logging
199260 const network = synapse . getNetwork ( )
200- logger . info (
201- {
202- event : 'synapse.init' ,
203- network,
204- rpcUrl : synapseOptions . rpcURL ,
205- } ,
206- 'Synapse SDK initialized'
207- )
261+ logger . info ( { event : 'synapse.init.success' , network } , 'Synapse SDK initialized' )
208262
209- // Store instance for cleanup
210263 synapseInstance = synapse
211-
212264 return synapse
213265 } catch ( error ) {
214266 const errorMessage = error instanceof Error ? error . message : String ( error )
215- logger . error (
216- {
217- event : 'synapse.init.failed' ,
218- error : errorMessage ,
219- } ,
220- `Failed to initialize Synapse SDK: ${ errorMessage } `
221- )
267+ logger . error ( { event : 'synapse.init.failed' , error : errorMessage } , 'Failed to initialize Synapse SDK' )
222268 throw error
223269 }
224270}
0 commit comments