Skip to content

Commit 1d5988a

Browse files
authored
fix: add signer support to initializeSynapse (#172)
1 parent 107a023 commit 1d5988a

File tree

3 files changed

+109
-47
lines changed

3 files changed

+109
-47
lines changed

src/core/synapse/index.ts

Lines changed: 95 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ import {
1010
Synapse,
1111
type SynapseOptions,
1212
} from '@filoz/synapse-sdk'
13-
import { type Provider as EthersProvider, JsonRpcProvider, Wallet, WebSocketProvider } from 'ethers'
13+
import { type Provider as EthersProvider, JsonRpcProvider, type Signer, Wallet, WebSocketProvider } from 'ethers'
1414
import type { Logger } from 'pino'
1515
import { ADDRESS_ONLY_SIGNER_SYMBOL, AddressOnlySigner } from './address-only-signer.js'
1616

@@ -54,26 +54,50 @@ export interface Config {
5454
}
5555

5656
/**
57-
* Configuration for Synapse initialization
58-
*
59-
* Supports two authentication modes:
60-
* 1. Standard: privateKey only
61-
* 2. Session Key: walletAddress + sessionKey
57+
* Common options for all Synapse configurations
6258
*/
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
59+
interface BaseSynapseConfig {
7060
/** RPC endpoint for the target Filecoin network. Defaults to calibration. */
7161
rpcUrl?: string | undefined
7262
/** Optional override for WarmStorage contract address */
7363
warmStorageAddress?: string | undefined
7464
withCDN?: boolean | undefined
7565
}
7666

67+
/**
68+
* Standard authentication with private key
69+
*/
70+
export interface PrivateKeyConfig extends BaseSynapseConfig {
71+
privateKey: string
72+
}
73+
74+
/**
75+
* Session key authentication with wallet address and session key
76+
*/
77+
export interface SessionKeyConfig extends BaseSynapseConfig {
78+
walletAddress: string
79+
sessionKey: string
80+
}
81+
82+
/**
83+
* Signer-based authentication with ethers Signer
84+
*/
85+
export interface SignerConfig extends BaseSynapseConfig {
86+
signer: Signer
87+
/** Target Filecoin network (required for signer mode to determine default RPC) */
88+
network: 'mainnet' | 'calibration'
89+
}
90+
91+
/**
92+
* Configuration for Synapse initialization
93+
*
94+
* Supports three authentication modes:
95+
* 1. Standard: privateKey only
96+
* 2. Session Key: walletAddress + sessionKey
97+
* 3. Signer: ethers Signer instance
98+
*/
99+
export type SynapseSetupConfig = PrivateKeyConfig | SessionKeyConfig | SignerConfig
100+
77101
/**
78102
* Structured service object containing the fully initialized Synapse SDK and
79103
* its storage context
@@ -189,22 +213,44 @@ export function isSessionKeyMode(synapse: Synapse): boolean {
189213
}
190214
}
191215

216+
/**
217+
* Type guards for authentication configuration
218+
*/
219+
function isPrivateKeyConfig(config: Partial<SynapseSetupConfig>): config is PrivateKeyConfig {
220+
return 'privateKey' in config && config.privateKey != null
221+
}
222+
223+
function isSessionKeyConfig(config: Partial<SynapseSetupConfig>): config is SessionKeyConfig {
224+
return (
225+
'walletAddress' in config && 'sessionKey' in config && config.walletAddress != null && config.sessionKey != null
226+
)
227+
}
228+
229+
function isSignerConfig(config: Partial<SynapseSetupConfig>): config is SignerConfig {
230+
return 'signer' in config && config.signer != null
231+
}
232+
192233
/**
193234
* Validate authentication configuration
194235
*/
195-
function validateAuthConfig(config: SynapseSetupConfig): 'standard' | 'session-key' {
196-
const hasStandardAuth = config.privateKey != null
197-
const hasSessionKeyAuth = config.walletAddress != null && config.sessionKey != null
236+
function validateAuthConfig(config: Partial<SynapseSetupConfig>): 'standard' | 'session-key' | 'signer' {
237+
const hasPrivateKey = isPrivateKeyConfig(config)
238+
const hasSessionKey = isSessionKeyConfig(config)
239+
const hasSigner = isSignerConfig(config)
240+
241+
const authCount = [hasPrivateKey, hasSessionKey, hasSigner].filter(Boolean).length
198242

199-
if (!hasStandardAuth && !hasSessionKeyAuth) {
200-
throw new Error('Authentication required: provide either a privateKey or walletAddress + sessionKey')
243+
if (authCount === 0) {
244+
throw new Error('Authentication required: provide either privateKey, walletAddress + sessionKey, or signer')
201245
}
202246

203-
if (hasStandardAuth && hasSessionKeyAuth) {
204-
throw new Error('Conflicting authentication: provide either a privateKey or walletAddress + sessionKey, not both')
247+
if (authCount > 1) {
248+
throw new Error('Conflicting authentication: provide only one of privateKey, walletAddress + sessionKey, or signer')
205249
}
206250

207-
return hasStandardAuth ? 'standard' : 'session-key'
251+
if (hasPrivateKey) return 'standard'
252+
if (hasSessionKey) return 'session-key'
253+
return 'signer'
208254
}
209255

210256
/**
@@ -246,18 +292,26 @@ async function setupSessionKey(synapse: Synapse, sessionWallet: Wallet, logger:
246292
/**
247293
* Initialize the Synapse SDK without creating storage context
248294
*
249-
* Supports two authentication modes:
295+
* Supports three authentication modes:
250296
* - Standard: privateKey only
251297
* - Session Key: walletAddress + sessionKey
298+
* - Signer: ethers Signer instance
252299
*
253300
* @param config - Application configuration with authentication credentials
254301
* @param logger - Logger instance for detailed operation tracking
255302
* @returns Initialized Synapse instance
256303
*/
257-
export async function initializeSynapse(config: SynapseSetupConfig, logger: Logger): Promise<Synapse> {
304+
export async function initializeSynapse(config: Partial<SynapseSetupConfig>, logger: Logger): Promise<Synapse> {
258305
try {
259306
const authMode = validateAuthConfig(config)
260-
const rpcURL = config.rpcUrl ?? RPC_URLS.calibration.websocket
307+
308+
// Determine RPC URL based on auth mode
309+
let rpcURL: string
310+
if (isSignerConfig(config)) {
311+
rpcURL = config.rpcUrl ?? RPC_URLS[config.network].websocket
312+
} else {
313+
rpcURL = config.rpcUrl ?? RPC_URLS.calibration.websocket
314+
}
261315

262316
logger.info({ event: 'synapse.init', authMode, rpcUrl: rpcURL }, 'Initializing Synapse SDK')
263317

@@ -275,31 +329,36 @@ export async function initializeSynapse(config: SynapseSetupConfig, logger: Logg
275329
let synapse: Synapse
276330

277331
if (authMode === 'session-key') {
278-
// Session key mode - validation guarantees these are defined
279-
const walletAddress = config.walletAddress
280-
const sessionKey = config.sessionKey
281-
if (!walletAddress || !sessionKey) {
282-
throw new Error('Internal error: session key config validated but values missing')
332+
// Session key mode - type guard ensures these are defined
333+
if (!isSessionKeyConfig(config)) {
334+
throw new Error('Internal error: session key mode but config type mismatch')
283335
}
284336

285337
// Create provider and signers for session key mode
286338
const provider = createProvider(rpcURL)
287339
activeProvider = provider
288340

289-
const ownerSigner = new AddressOnlySigner(walletAddress, provider)
290-
const sessionWallet = new Wallet(sessionKey, provider)
341+
const ownerSigner = new AddressOnlySigner(config.walletAddress, provider)
342+
const sessionWallet = new Wallet(config.sessionKey, provider)
291343

292344
// Initialize with owner signer, then activate session key
293345
synapse = await Synapse.create({ ...synapseOptions, signer: ownerSigner })
294346
await setupSessionKey(synapse, sessionWallet, logger)
347+
} else if (authMode === 'signer') {
348+
// Signer mode - type guard ensures signer is defined
349+
if (!isSignerConfig(config)) {
350+
throw new Error('Internal error: signer mode but config type mismatch')
351+
}
352+
353+
synapse = await Synapse.create({ ...synapseOptions, signer: config.signer })
354+
activeProvider = synapse.getProvider()
295355
} else {
296-
// Standard mode - validation guarantees privateKey is defined
297-
const privateKey = config.privateKey
298-
if (!privateKey) {
299-
throw new Error('Internal error: standard auth validated but privateKey missing')
356+
// Private key mode - type guard ensures privateKey is defined
357+
if (!isPrivateKeyConfig(config)) {
358+
throw new Error('Internal error: private key mode but config type mismatch')
300359
}
301360

302-
synapse = await Synapse.create({ ...synapseOptions, privateKey })
361+
synapse = await Synapse.create({ ...synapseOptions, privateKey: config.privateKey })
303362
activeProvider = synapse.getProvider()
304363
}
305364

src/test/unit/synapse-service.test.ts

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -42,9 +42,12 @@ describe('synapse-service', () => {
4242

4343
describe('setupSynapse', () => {
4444
it('should throw error when no authentication is provided', async () => {
45-
config.privateKey = undefined
45+
// Create an invalid config with no authentication
46+
const invalidConfig = {
47+
rpcUrl: 'wss://wss.calibration.node.glif.io/apigw/lotus/rpc/v1',
48+
} as any
4649

47-
await expect(setupSynapse(config, logger)).rejects.toThrow('Authentication required')
50+
await expect(setupSynapse(invalidConfig, logger)).rejects.toThrow('Authentication required')
4851
})
4952

5053
it('should initialize Synapse when private key is configured', async () => {

src/utils/cli-auth.ts

Lines changed: 9 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -40,22 +40,22 @@ export interface CLIAuthOptions {
4040
* @param options - CLI authentication options
4141
* @returns Synapse setup config (validation happens in initializeSynapse)
4242
*/
43-
export function parseCLIAuth(options: CLIAuthOptions): SynapseSetupConfig {
43+
export function parseCLIAuth(options: CLIAuthOptions): Partial<SynapseSetupConfig> {
4444
// Read from CLI options or environment variables
4545
const privateKey = options.privateKey || process.env.PRIVATE_KEY
4646
const walletAddress = options.walletAddress || process.env.WALLET_ADDRESS
4747
const sessionKey = options.sessionKey || process.env.SESSION_KEY
4848
const rpcUrl = options.rpcUrl || process.env.RPC_URL
4949
const warmStorageAddress = options.warmStorageAddress || process.env.WARM_STORAGE_ADDRESS
5050

51-
// Build config - validation happens in initializeSynapse()
52-
const config: SynapseSetupConfig = {
53-
privateKey,
54-
walletAddress,
55-
sessionKey,
56-
rpcUrl,
57-
warmStorageAddress,
58-
}
51+
// Build config - only include defined values, validation happens in initializeSynapse()
52+
const config: any = {}
53+
54+
if (privateKey) config.privateKey = privateKey
55+
if (walletAddress) config.walletAddress = walletAddress
56+
if (sessionKey) config.sessionKey = sessionKey
57+
if (rpcUrl) config.rpcUrl = rpcUrl
58+
if (warmStorageAddress) config.warmStorageAddress = warmStorageAddress
5959

6060
return config
6161
}

0 commit comments

Comments
 (0)