Skip to content
Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
34 changes: 25 additions & 9 deletions src/common/get-rpc-url.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,32 @@
import { calibration, mainnet } from '@filoz/synapse-sdk'
import { type Chain, calibration, mainnet } from '@filoz/synapse-sdk'
import type { CLIAuthOptions } from '../utils/cli-auth.js'

const NETWORK_CHAINS = {
mainnet,
calibration: calibration,
calibration,
} as const

type ConfiguredNetwork = keyof typeof NETWORK_CHAINS

function getConfiguredNetwork(options: CLIAuthOptions): ConfiguredNetwork | undefined {
const network = (options.mainnet === true ? 'mainnet' : options.network)?.toLowerCase().trim()
if (!network) {
return undefined
}
if (network !== 'mainnet' && network !== 'calibration') {
throw new Error(`Invalid network: "${network}". Must be "mainnet" or "calibration"`)
}
return network
}

export function getConfiguredChain(options: CLIAuthOptions): Chain | undefined {
const network = getConfiguredNetwork(options)
if (!network) {
return undefined
}
return NETWORK_CHAINS[network]
}

/**
* Get the RPC URL from the CLI options.
*
Expand All @@ -22,14 +43,9 @@ export function getRpcUrl(options: CLIAuthOptions): string {
return options.rpcUrl
}

// Try to use network flag
const network = options.network?.toLowerCase().trim()
const network = getConfiguredNetwork(options)
if (network) {
if (network !== 'mainnet' && network !== 'calibration') {
throw new Error(`Invalid network: "${network}". Must be "mainnet" or "calibration"`)
}
const chain = NETWORK_CHAINS[network]
const wsUrl = chain.rpcUrls.default.webSocket?.[0]
const wsUrl = NETWORK_CHAINS[network].rpcUrls.default.webSocket?.[0]
if (!wsUrl) {
throw new Error(`WebSocket RPC URL not available for network: "${network}"`)
}
Expand Down
16 changes: 13 additions & 3 deletions src/core/payments/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -222,6 +222,18 @@ export interface PaymentValidationResult {
helpMessage?: string
}

export function getUsdfcAcquisitionHelpMessage(isCalibnet: boolean): string {
if (isCalibnet) {
return 'Get test USDFC from: https://docs.secured.finance/usdfc-stablecoin/getting-started/getting-test-usdfc-on-testnet'
}

return [
'Bridge USDFC to Filecoin mainnet: https://app.usdfc.net/#/bridge',
'Or swap FIL -> USDFC on Sushi: https://www.sushi.com/filecoin/swap?token0=NATIVE&token1=0x80b98d3aa09ffff255c3ba4a241111ff1262f045',
'Minting guide: https://docs.secured.finance/usdfc-stablecoin/getting-started/minting-usdfc-step-by-step',
].join('\n ')
}

export function validatePaymentRequirements(
hasSufficientGas: boolean,
walletUsdfcBalance: bigint,
Expand All @@ -242,9 +254,7 @@ export function validatePaymentRequirements(
return {
isValid: false,
errorMessage: 'No USDFC tokens found',
helpMessage: isCalibnet
? 'Get test USDFC from: https://docs.secured.finance/usdfc-stablecoin/getting-started/getting-test-usdfc-on-testnet'
: 'Mint USDFC with FIL: https://docs.secured.finance/usdfc-stablecoin/getting-started/minting-usdfc-step-by-step',
helpMessage: getUsdfcAcquisitionHelpMessage(isCalibnet),
}
}

Expand Down
5 changes: 2 additions & 3 deletions src/payments/status.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ import {
FLOOR_PRICE_DAYS,
FLOOR_PRICE_PER_30_DAYS,
getPaymentStatus,
getUsdfcAcquisitionHelpMessage,
} from '../core/payments/index.js'
import { getClientAddress, initializeSynapse } from '../core/synapse/index.js'
import { formatFIL, formatUSDFC } from '../core/utils/format.js'
Expand Down Expand Up @@ -119,9 +120,7 @@ export async function showPaymentStatus(options: StatusOptions): Promise<void> {
log.line('')
log.line(`${pc.red('✗')} No USDFC tokens found`)
log.line('')
const helpMessage = filStatus.isCalibnet
? 'Get test USDFC from: https://docs.secured.finance/usdfc-stablecoin/getting-started/getting-test-usdfc-on-testnet'
: 'Mint USDFC with FIL: https://docs.secured.finance/usdfc-stablecoin/getting-started/minting-usdfc-step-by-step'
const helpMessage = getUsdfcAcquisitionHelpMessage(filStatus.isCalibnet)
log.line(` ${pc.cyan(helpMessage)}`)
log.flush()

Expand Down
40 changes: 34 additions & 6 deletions src/test/unit/config.test.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,43 @@
import { homedir, platform } from 'node:os'
import { join } from 'node:path'
import { calibration } from '@filoz/synapse-sdk'
import { describe, expect, it } from 'vitest'
import { afterEach, beforeEach, describe, expect, it } from 'vitest'
import { createConfig } from '../../config.js'

describe('Config', () => {
const originalEnv = {
host: process.env.HOST,
logLevel: process.env.LOG_LEVEL,
network: process.env.NETWORK,
port: process.env.PORT,
rpcUrl: process.env.RPC_URL,
}

beforeEach(() => {
delete process.env.HOST
delete process.env.LOG_LEVEL
delete process.env.NETWORK
delete process.env.PORT
delete process.env.RPC_URL
})

afterEach(() => {
if (originalEnv.host === undefined) delete process.env.HOST
else process.env.HOST = originalEnv.host

if (originalEnv.logLevel === undefined) delete process.env.LOG_LEVEL
else process.env.LOG_LEVEL = originalEnv.logLevel

if (originalEnv.network === undefined) delete process.env.NETWORK
else process.env.NETWORK = originalEnv.network

if (originalEnv.port === undefined) delete process.env.PORT
else process.env.PORT = originalEnv.port

if (originalEnv.rpcUrl === undefined) delete process.env.RPC_URL
else process.env.RPC_URL = originalEnv.rpcUrl
})

it('should create default config', () => {
const config = createConfig()

Expand Down Expand Up @@ -43,10 +76,5 @@ describe('Config', () => {
expect(config.port).toBe(8080)
expect(config.host).toBe('0.0.0.0')
expect(config.logLevel).toBe('debug')

// Clean up
delete process.env.PORT
delete process.env.HOST
delete process.env.LOG_LEVEL
})
})
4 changes: 4 additions & 0 deletions src/test/unit/get-rpc-url.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,10 @@ describe('getRpcUrl', () => {
expect(getRpcUrl({ network } satisfies CLIAuthOptions)).toBe(expected)
})

it('uses --mainnet shorthand when provided', () => {
expect(getRpcUrl({ mainnet: true })).toBe(mainnetWsUrl)
})

it('normalizes network casing and whitespace', () => {
expect(
getRpcUrl({
Expand Down
28 changes: 28 additions & 0 deletions src/test/unit/payment-validation.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
import { describe, expect, it } from 'vitest'
import { getUsdfcAcquisitionHelpMessage, validatePaymentRequirements } from '../../core/payments/index.js'

describe('getUsdfcAcquisitionHelpMessage', () => {
it('returns testnet USDFC docs on calibration', () => {
expect(getUsdfcAcquisitionHelpMessage(true)).toContain('getting-test-usdfc-on-testnet')
})

it('returns mainnet USDFC bridge docs on mainnet', () => {
const helpMessage = getUsdfcAcquisitionHelpMessage(false)

expect(helpMessage).toContain('https://app.usdfc.net/#/bridge')
expect(helpMessage).toContain(
'https://www.sushi.com/filecoin/swap?token0=NATIVE&token1=0x80b98d3aa09ffff255c3ba4a241111ff1262f045'
)
expect(helpMessage).toContain('minting-usdfc-step-by-step')
})
})

describe('validatePaymentRequirements', () => {
it('surfaces the mainnet USDFC docs when wallet balance is zero', () => {
const result = validatePaymentRequirements(true, 0n, false)

expect(result.isValid).toBe(false)
expect(result.errorMessage).toBe('No USDFC tokens found')
expect(result.helpMessage).toContain('https://app.usdfc.net/#/bridge')
})
})
9 changes: 7 additions & 2 deletions src/utils/cli-auth.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,8 @@
* and preparing them for use with the Synapse SDK.
*/

import type { Synapse } from '@filoz/synapse-sdk'
import { getRpcUrl } from '../common/get-rpc-url.js'
import type { Chain, Synapse } from '@filoz/synapse-sdk'
import { getConfiguredChain, getRpcUrl } from '../common/get-rpc-url.js'
import type { SynapseSetupConfig } from '../core/synapse/index.js'
import { initializeSynapse } from '../core/synapse/index.js'
import { createLogger } from '../logger.js'
Expand All @@ -26,6 +26,8 @@ export interface CLIAuthOptions {
viewAddress?: string | undefined
/** Filecoin network: mainnet or calibration */
network?: string | undefined
/** Commander shorthand for --network mainnet */
mainnet?: boolean | undefined
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
/** Commander shorthand for --network mainnet */
mainnet?: boolean | undefined

/** RPC endpoint URL (overrides network if specified) */
rpcUrl?: string | undefined
/** Optional provider ID overrides (comma-separated) */
Expand All @@ -52,6 +54,7 @@ export function parseCLIAuth(options: CLIAuthOptions): SynapseSetupConfig {
const sessionKey = options.sessionKey || process.env.SESSION_KEY
const viewAddress = options.viewAddress || process.env.VIEW_ADDRESS
const rpcUrl = getRpcUrl(options)
const chain = getConfiguredChain(options)

// Build config incrementally; initializeSynapse() validates the final shape
const config: {
Expand All @@ -60,6 +63,7 @@ export function parseCLIAuth(options: CLIAuthOptions): SynapseSetupConfig {
sessionKey?: string
readOnly?: boolean
rpcUrl?: string
chain?: Chain
} = {}

if (privateKey) config.privateKey = privateKey
Expand All @@ -71,6 +75,7 @@ export function parseCLIAuth(options: CLIAuthOptions): SynapseSetupConfig {
}
if (sessionKey) config.sessionKey = sessionKey
if (rpcUrl) config.rpcUrl = rpcUrl
if (chain) config.chain = chain
return config as SynapseSetupConfig
}

Expand Down