Skip to content
Open
Show file tree
Hide file tree
Changes from 8 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
13 changes: 10 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,7 @@ Use Filecoin Pin programmatically in your Node.js or browser applications. The l
Run a localhost IPFS Pinning Service API server that implements the [IPFS Pinning Service API specification](https://ipfs.github.io/pinning-services-api-spec/). This allows you to use standard IPFS tooling (like `ipfs pin remote`) while storing data on Filecoin.

- **Repository**: This repo (`filecoin-pin server` command in CLI)
- **Usage**: `PRIVATE_KEY=0x... npx filecoin-pin server`
- **Usage**: `PRIVATE_KEY=0x... npx filecoin-pin server` (or use session key auth — see [Configuration](#configuration))
- **Status**: Works and is tested, but hasn't received as many features as the CLI. If it would benefit your usecase, please comment on [tracking issue](https://github.com/filecoin-project/filecoin-pin/issues/46) so we can be better informed when it comes to prioritizing.

### Management Console GUI
Expand Down Expand Up @@ -139,7 +139,11 @@ npm install -g filecoin-pin
### Basic Usage

```bash
# 0. Set your PRIVATE_KEY environment variable or pass it via --private-key to each command.
# 0. Set up authentication (choose one):
# Private key: export PRIVATE_KEY=0x...
# (or pass --private-key <key> to each command)
# Session key: export WALLET_ADDRESS=0x... SESSION_KEY=0x...
# (or pass --wallet-address <addr> --session-key <key> to each command)

# 1. Configure payment permissions (one-time setup)
filecoin-pin payments setup --auto
Expand Down Expand Up @@ -221,7 +225,10 @@ When using `--network devnet`, Filecoin Pin reads connection details from a runn
* `-h`, `--help`: Display help information for each command
* `-V`, `--version`: Output the version number
* `-v`, `--verbose`: Verbose output
* `--private-key`: Ethereum-style (`0x`) private key, funded with USDFC (required)
* `--private-key`: Ethereum-style (`0x`) private key (wallet and signer), funded with USDFC
* `--wallet-address`: Session key mode: owner wallet address
* `--session-key`: Session key mode: scoped signing key registered to the wallet
* `--view-address`: Read-only address for querying storage status without signing
* `--network`: Filecoin network to use: `mainnet`, `calibration`, or `devnet` (default: `calibration`)
* `--rpc-url`: Filecoin RPC endpoint (overrides `--network` if specified)

Expand Down
8 changes: 8 additions & 0 deletions src/commands/server.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,8 @@ export const serverCommand = new Command('server')
.option('--car-storage <path>', 'path for CAR file storage', './cars')
.option('--database <path>', 'path to SQLite database', './pins.db')
.option('--private-key <key>', 'private key for Synapse (env: PRIVATE_KEY)')
.option('--wallet-address <address>', 'wallet address for session key auth (env: WALLET_ADDRESS)')
.option('--session-key <key>', 'session key for session key auth (env: SESSION_KEY)')
.option('--access-token <token>', 'bearer token required on all API requests except GET / (env: ACCESS_TOKEN)')

addNetworkOptions(serverCommand)
Expand All @@ -21,6 +23,12 @@ addNetworkOptions(serverCommand)
if (options.privateKey) {
process.env.PRIVATE_KEY = options.privateKey
}
if (options.walletAddress) {
process.env.WALLET_ADDRESS = options.walletAddress
}
if (options.sessionKey) {
process.env.SESSION_KEY = options.sessionKey
}
if (options.accessToken) {
process.env.ACCESS_TOKEN = options.accessToken
}
Expand Down
16 changes: 11 additions & 5 deletions src/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -29,10 +29,14 @@ function getDataDirectory(): string {
/**
* Create configuration from environment variables
*
* This demonstrates configuration best practices for Synapse SDK:
* - PRIVATE_KEY: Required for transaction signing (keep secure!)
* - RPC_URL: Filecoin network endpoint (mainnet or calibration) - takes precedence over NETWORK
* - NETWORK: Filecoin network name (mainnet or calibration) - used if RPC_URL not set
* Authentication (choose one):
* - PRIVATE_KEY: Standard private key auth
* - WALLET_ADDRESS + SESSION_KEY: Session key auth
* - VIEW_ADDRESS: Read-only mode, no signing (CLI only)
*
* Network:
* - RPC_URL: Filecoin RPC endpoint — takes precedence over NETWORK
* - NETWORK: Filecoin network name (mainnet, calibration, devnet) — used if RPC_URL not set
*/
export function createConfig(): Config {
const dataDir = getDataDirectory()
Expand All @@ -50,7 +54,9 @@ export function createConfig(): Config {
accessToken: process.env.ACCESS_TOKEN,

// Synapse SDK configuration
privateKey: process.env.PRIVATE_KEY, // Required: Ethereum-compatible private key
privateKey: process.env.PRIVATE_KEY,
walletAddress: process.env.WALLET_ADDRESS,
sessionKey: process.env.SESSION_KEY,
rpcUrl, // Determined from RPC_URL, NETWORK, or default to calibration
// Storage paths
databasePath: process.env.DATABASE_PATH ?? join(dataDir, 'pins.db'),
Expand Down
4 changes: 2 additions & 2 deletions src/core/payments/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@
*/

import { calibration, SIZE_CONSTANTS, type Synapse, TIME_CONSTANTS, TOKENS } from '@filoz/synapse-sdk'
import { formatUnits } from 'viem'
import { formatUnits, type Hash } from 'viem'
import { getClientAddress, isSessionKeyMode } from '../synapse/index.js'
import { assertPriceNonZero } from '../utils/validate-pricing.js'
import {
Expand Down Expand Up @@ -289,7 +289,7 @@ export async function depositUSDFC(
const amountMoreThanCurrentAllowance =
(await synapse.payments.allowance({ spender: synapse.chain.contracts.filecoinPay.address })) < amount

let txHash: `0x${string}`
let txHash: Hash

if (amountMoreThanCurrentAllowance || needsAllowanceUpdate) {
txHash = await synapse.payments.depositWithPermitAndApproveOperator({
Expand Down
11 changes: 6 additions & 5 deletions src/core/piece/remove-piece.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import type { Synapse } from '@filoz/synapse-sdk'
import type { StorageContext } from '@filoz/synapse-sdk/storage'
import type { Logger } from 'pino'
import type { Hash } from 'viem'
import { getErrorMessage } from '../utils/errors.js'
import type { ProgressEvent, ProgressEventHandler } from '../utils/types.js'

Expand All @@ -18,13 +19,13 @@ import type { ProgressEvent, ProgressEventHandler } from '../utils/types.js'
*/
export type RemovePieceProgressEvents =
| ProgressEvent<'remove-piece:submitting', { pieceCid: string; dataSetId: bigint }>
| ProgressEvent<'remove-piece:submitted', { pieceCid: string; dataSetId: bigint; txHash: `0x${string}` }>
| ProgressEvent<'remove-piece:confirming', { pieceCid: string; dataSetId: bigint; txHash: `0x${string}` }>
| ProgressEvent<'remove-piece:submitted', { pieceCid: string; dataSetId: bigint; txHash: Hash }>
| ProgressEvent<'remove-piece:confirming', { pieceCid: string; dataSetId: bigint; txHash: Hash }>
| ProgressEvent<
'remove-piece:confirmation-failed',
{ pieceCid: string; dataSetId: bigint; txHash: `0x${string}`; message: string }
{ pieceCid: string; dataSetId: bigint; txHash: Hash; message: string }
>
| ProgressEvent<'remove-piece:complete', { txHash: `0x${string}`; confirmed: boolean }>
| ProgressEvent<'remove-piece:complete', { txHash: Hash; confirmed: boolean }>

/**
* Number of block confirmations to wait for when waitForConfirmation=true
Expand Down Expand Up @@ -79,7 +80,7 @@ export async function removePiece(
pieceCid: string,
storageContext: StorageContext,
options: RemovePieceOptions
): Promise<`0x${string}`> {
): Promise<Hash> {
const { onProgress, waitForConfirmation } = options
const dataSetId = storageContext.dataSetId

Expand Down
42 changes: 34 additions & 8 deletions src/core/synapse/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,17 @@
SchedulePieceRemovalsPermission,
} from '@filoz/synapse-core/session-key'
import type { Logger } from 'pino'
import { type Account, custom, getAddress, type HttpTransport, http, type WebSocketTransport, webSocket } from 'viem'
import {
type Account,
type Address,
custom,
getAddress,
type Hex,
type HttpTransport,
http,
type WebSocketTransport,
webSocket,
} from 'viem'
import { privateKeyToAccount } from 'viem/accounts'
import { APPLICATION_SOURCE } from './constants.js'

Expand All @@ -36,6 +46,8 @@
port: number
host: string
privateKey: string | undefined
walletAddress: string | undefined
sessionKey: string | undefined
accessToken: string | undefined
rpcUrl: string
databasePath: string
Expand All @@ -61,22 +73,22 @@
* Standard authentication with private key
*/
export interface PrivateKeyConfig extends BaseSynapseConfig {
privateKey: `0x${string}`
privateKey: Hex
}

/**
* Session key authentication with owner address and session key private key
*/
export interface SessionKeyConfig extends BaseSynapseConfig {
walletAddress: `0x${string}`
sessionKey: `0x${string}`
walletAddress: Address
sessionKey: Hex
}

/**
* Read-only mode using an address (cannot sign transactions)
*/
export interface ReadOnlyConfig extends BaseSynapseConfig {
walletAddress: `0x${string}`
walletAddress: Address
readOnly: true
}

Expand Down Expand Up @@ -124,7 +136,7 @@
}

const PERMISSION_NAMES: Record<string, string> = {
[CreateDataSetPermission]: 'CreateDataSet',

Check failure on line 139 in src/core/synapse/index.ts

View workflow job for this annotation

GitHub Actions / test (lts/*, macos-latest)

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

Error: [vitest] No "CreateDataSetPermission" export is defined on the "@filoz/synapse-core/session-key" mock. Did you forget to return it from "vi.mock"? If you need to partially mock a module, you can use "importOriginal" helper inside: vi.mock(import("@filoz/synapse-core/session-key"), async (importOriginal) => { const actual = await importOriginal() return { ...actual, // your mocked methods } }) ❯ src/core/synapse/index.ts:139:4 ❯ src/test/unit/synapse-service.test.ts:4:1

Check failure on line 139 in src/core/synapse/index.ts

View workflow job for this annotation

GitHub Actions / test (lts/*, macos-latest)

[unit] src/test/unit/filecoin-pinning-server.unit.test.ts

Error: [vitest] No "CreateDataSetPermission" export is defined on the "@filoz/synapse-core/session-key" mock. Did you forget to return it from "vi.mock"? If you need to partially mock a module, you can use "importOriginal" helper inside: vi.mock(import("@filoz/synapse-core/session-key"), async (importOriginal) => { const actual = await importOriginal() return { ...actual, // your mocked methods } }) ❯ src/core/synapse/index.ts:139:4 ❯ src/filecoin-pinning-server.ts:6:1

Check failure on line 139 in src/core/synapse/index.ts

View workflow job for this annotation

GitHub Actions / test (current, ubuntu-latest)

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

Error: [vitest] No "CreateDataSetPermission" export is defined on the "@filoz/synapse-core/session-key" mock. Did you forget to return it from "vi.mock"? If you need to partially mock a module, you can use "importOriginal" helper inside: vi.mock(import("@filoz/synapse-core/session-key"), async (importOriginal) => { const actual = await importOriginal() return { ...actual, // your mocked methods } }) ❯ src/core/synapse/index.ts:139:4 ❯ src/test/unit/synapse-service.test.ts:4:1

Check failure on line 139 in src/core/synapse/index.ts

View workflow job for this annotation

GitHub Actions / test (current, ubuntu-latest)

[unit] src/test/unit/filecoin-pinning-server.unit.test.ts

Error: [vitest] No "CreateDataSetPermission" export is defined on the "@filoz/synapse-core/session-key" mock. Did you forget to return it from "vi.mock"? If you need to partially mock a module, you can use "importOriginal" helper inside: vi.mock(import("@filoz/synapse-core/session-key"), async (importOriginal) => { const actual = await importOriginal() return { ...actual, // your mocked methods } }) ❯ src/core/synapse/index.ts:139:4 ❯ src/filecoin-pinning-server.ts:6:1

Check failure on line 139 in src/core/synapse/index.ts

View workflow job for this annotation

GitHub Actions / test (current, windows-latest)

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

Error: [vitest] No "CreateDataSetPermission" export is defined on the "@filoz/synapse-core/session-key" mock. Did you forget to return it from "vi.mock"? If you need to partially mock a module, you can use "importOriginal" helper inside: vi.mock(import("@filoz/synapse-core/session-key"), async (importOriginal) => { const actual = await importOriginal() return { ...actual, // your mocked methods } }) ❯ src/core/synapse/index.ts:139:4 ❯ src/test/unit/synapse-service.test.ts:4:1

Check failure on line 139 in src/core/synapse/index.ts

View workflow job for this annotation

GitHub Actions / test (current, windows-latest)

[unit] src/test/unit/filecoin-pinning-server.unit.test.ts

Error: [vitest] No "CreateDataSetPermission" export is defined on the "@filoz/synapse-core/session-key" mock. Did you forget to return it from "vi.mock"? If you need to partially mock a module, you can use "importOriginal" helper inside: vi.mock(import("@filoz/synapse-core/session-key"), async (importOriginal) => { const actual = await importOriginal() return { ...actual, // your mocked methods } }) ❯ src/core/synapse/index.ts:139:4 ❯ src/filecoin-pinning-server.ts:6:1

Check failure on line 139 in src/core/synapse/index.ts

View workflow job for this annotation

GitHub Actions / test (lts/*, windows-latest)

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

Error: [vitest] No "CreateDataSetPermission" export is defined on the "@filoz/synapse-core/session-key" mock. Did you forget to return it from "vi.mock"? If you need to partially mock a module, you can use "importOriginal" helper inside: vi.mock(import("@filoz/synapse-core/session-key"), async (importOriginal) => { const actual = await importOriginal() return { ...actual, // your mocked methods } }) ❯ src/core/synapse/index.ts:139:4 ❯ src/test/unit/synapse-service.test.ts:4:1

Check failure on line 139 in src/core/synapse/index.ts

View workflow job for this annotation

GitHub Actions / test (lts/*, windows-latest)

[unit] src/test/unit/filecoin-pinning-server.unit.test.ts

Error: [vitest] No "CreateDataSetPermission" export is defined on the "@filoz/synapse-core/session-key" mock. Did you forget to return it from "vi.mock"? If you need to partially mock a module, you can use "importOriginal" helper inside: vi.mock(import("@filoz/synapse-core/session-key"), async (importOriginal) => { const actual = await importOriginal() return { ...actual, // your mocked methods } }) ❯ src/core/synapse/index.ts:139:4 ❯ src/filecoin-pinning-server.ts:6:1

Check failure on line 139 in src/core/synapse/index.ts

View workflow job for this annotation

GitHub Actions / test (current, macos-latest)

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

Error: [vitest] No "CreateDataSetPermission" export is defined on the "@filoz/synapse-core/session-key" mock. Did you forget to return it from "vi.mock"? If you need to partially mock a module, you can use "importOriginal" helper inside: vi.mock(import("@filoz/synapse-core/session-key"), async (importOriginal) => { const actual = await importOriginal() return { ...actual, // your mocked methods } }) ❯ src/core/synapse/index.ts:139:4 ❯ src/test/unit/synapse-service.test.ts:4:1

Check failure on line 139 in src/core/synapse/index.ts

View workflow job for this annotation

GitHub Actions / test (current, macos-latest)

[unit] src/test/unit/filecoin-pinning-server.unit.test.ts

Error: [vitest] No "CreateDataSetPermission" export is defined on the "@filoz/synapse-core/session-key" mock. Did you forget to return it from "vi.mock"? If you need to partially mock a module, you can use "importOriginal" helper inside: vi.mock(import("@filoz/synapse-core/session-key"), async (importOriginal) => { const actual = await importOriginal() return { ...actual, // your mocked methods } }) ❯ src/core/synapse/index.ts:139:4 ❯ src/filecoin-pinning-server.ts:6:1

Check failure on line 139 in src/core/synapse/index.ts

View workflow job for this annotation

GitHub Actions / test (lts/*, ubuntu-latest)

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

Error: [vitest] No "CreateDataSetPermission" export is defined on the "@filoz/synapse-core/session-key" mock. Did you forget to return it from "vi.mock"? If you need to partially mock a module, you can use "importOriginal" helper inside: vi.mock(import("@filoz/synapse-core/session-key"), async (importOriginal) => { const actual = await importOriginal() return { ...actual, // your mocked methods } }) ❯ src/core/synapse/index.ts:139:4 ❯ src/test/unit/synapse-service.test.ts:4:1

Check failure on line 139 in src/core/synapse/index.ts

View workflow job for this annotation

GitHub Actions / test (lts/*, ubuntu-latest)

[unit] src/test/unit/filecoin-pinning-server.unit.test.ts

Error: [vitest] No "CreateDataSetPermission" export is defined on the "@filoz/synapse-core/session-key" mock. Did you forget to return it from "vi.mock"? If you need to partially mock a module, you can use "importOriginal" helper inside: vi.mock(import("@filoz/synapse-core/session-key"), async (importOriginal) => { const actual = await importOriginal() return { ...actual, // your mocked methods } }) ❯ src/core/synapse/index.ts:139:4 ❯ src/filecoin-pinning-server.ts:6:1

Check failure on line 139 in src/core/synapse/index.ts

View workflow job for this annotation

GitHub Actions / test (current, macos-latest)

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

Error: [vitest] No "CreateDataSetPermission" export is defined on the "@filoz/synapse-core/session-key" mock. Did you forget to return it from "vi.mock"? If you need to partially mock a module, you can use "importOriginal" helper inside: vi.mock(import("@filoz/synapse-core/session-key"), async (importOriginal) => { const actual = await importOriginal() return { ...actual, // your mocked methods } }) ❯ src/core/synapse/index.ts:139:4 ❯ src/test/unit/synapse-service.test.ts:4:1

Check failure on line 139 in src/core/synapse/index.ts

View workflow job for this annotation

GitHub Actions / test (current, macos-latest)

[unit] src/test/unit/filecoin-pinning-server.unit.test.ts

Error: [vitest] No "CreateDataSetPermission" export is defined on the "@filoz/synapse-core/session-key" mock. Did you forget to return it from "vi.mock"? If you need to partially mock a module, you can use "importOriginal" helper inside: vi.mock(import("@filoz/synapse-core/session-key"), async (importOriginal) => { const actual = await importOriginal() return { ...actual, // your mocked methods } }) ❯ src/core/synapse/index.ts:139:4 ❯ src/filecoin-pinning-server.ts:6:1

Check failure on line 139 in src/core/synapse/index.ts

View workflow job for this annotation

GitHub Actions / test (current, windows-latest)

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

Error: [vitest] No "CreateDataSetPermission" export is defined on the "@filoz/synapse-core/session-key" mock. Did you forget to return it from "vi.mock"? If you need to partially mock a module, you can use "importOriginal" helper inside: vi.mock(import("@filoz/synapse-core/session-key"), async (importOriginal) => { const actual = await importOriginal() return { ...actual, // your mocked methods } }) ❯ src/core/synapse/index.ts:139:4 ❯ src/test/unit/synapse-service.test.ts:4:1

Check failure on line 139 in src/core/synapse/index.ts

View workflow job for this annotation

GitHub Actions / test (current, windows-latest)

[unit] src/test/unit/filecoin-pinning-server.unit.test.ts

Error: [vitest] No "CreateDataSetPermission" export is defined on the "@filoz/synapse-core/session-key" mock. Did you forget to return it from "vi.mock"? If you need to partially mock a module, you can use "importOriginal" helper inside: vi.mock(import("@filoz/synapse-core/session-key"), async (importOriginal) => { const actual = await importOriginal() return { ...actual, // your mocked methods } }) ❯ src/core/synapse/index.ts:139:4 ❯ src/filecoin-pinning-server.ts:6:1

Check failure on line 139 in src/core/synapse/index.ts

View workflow job for this annotation

GitHub Actions / test (current, ubuntu-latest)

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

Error: [vitest] No "CreateDataSetPermission" export is defined on the "@filoz/synapse-core/session-key" mock. Did you forget to return it from "vi.mock"? If you need to partially mock a module, you can use "importOriginal" helper inside: vi.mock(import("@filoz/synapse-core/session-key"), async (importOriginal) => { const actual = await importOriginal() return { ...actual, // your mocked methods } }) ❯ src/core/synapse/index.ts:139:4 ❯ src/test/unit/synapse-service.test.ts:4:1

Check failure on line 139 in src/core/synapse/index.ts

View workflow job for this annotation

GitHub Actions / test (current, ubuntu-latest)

[unit] src/test/unit/filecoin-pinning-server.unit.test.ts

Error: [vitest] No "CreateDataSetPermission" export is defined on the "@filoz/synapse-core/session-key" mock. Did you forget to return it from "vi.mock"? If you need to partially mock a module, you can use "importOriginal" helper inside: vi.mock(import("@filoz/synapse-core/session-key"), async (importOriginal) => { const actual = await importOriginal() return { ...actual, // your mocked methods } }) ❯ src/core/synapse/index.ts:139:4 ❯ src/filecoin-pinning-server.ts:6:1

Check failure on line 139 in src/core/synapse/index.ts

View workflow job for this annotation

GitHub Actions / test (lts/*, windows-latest)

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

Error: [vitest] No "CreateDataSetPermission" export is defined on the "@filoz/synapse-core/session-key" mock. Did you forget to return it from "vi.mock"? If you need to partially mock a module, you can use "importOriginal" helper inside: vi.mock(import("@filoz/synapse-core/session-key"), async (importOriginal) => { const actual = await importOriginal() return { ...actual, // your mocked methods } }) ❯ src/core/synapse/index.ts:139:4 ❯ src/test/unit/synapse-service.test.ts:4:1

Check failure on line 139 in src/core/synapse/index.ts

View workflow job for this annotation

GitHub Actions / test (lts/*, windows-latest)

[unit] src/test/unit/filecoin-pinning-server.unit.test.ts

Error: [vitest] No "CreateDataSetPermission" export is defined on the "@filoz/synapse-core/session-key" mock. Did you forget to return it from "vi.mock"? If you need to partially mock a module, you can use "importOriginal" helper inside: vi.mock(import("@filoz/synapse-core/session-key"), async (importOriginal) => { const actual = await importOriginal() return { ...actual, // your mocked methods } }) ❯ src/core/synapse/index.ts:139:4 ❯ src/filecoin-pinning-server.ts:6:1

Check failure on line 139 in src/core/synapse/index.ts

View workflow job for this annotation

GitHub Actions / test (lts/*, macos-latest)

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

Error: [vitest] No "CreateDataSetPermission" export is defined on the "@filoz/synapse-core/session-key" mock. Did you forget to return it from "vi.mock"? If you need to partially mock a module, you can use "importOriginal" helper inside: vi.mock(import("@filoz/synapse-core/session-key"), async (importOriginal) => { const actual = await importOriginal() return { ...actual, // your mocked methods } }) ❯ src/core/synapse/index.ts:139:4 ❯ src/test/unit/synapse-service.test.ts:4:1

Check failure on line 139 in src/core/synapse/index.ts

View workflow job for this annotation

GitHub Actions / test (lts/*, macos-latest)

[unit] src/test/unit/filecoin-pinning-server.unit.test.ts

Error: [vitest] No "CreateDataSetPermission" export is defined on the "@filoz/synapse-core/session-key" mock. Did you forget to return it from "vi.mock"? If you need to partially mock a module, you can use "importOriginal" helper inside: vi.mock(import("@filoz/synapse-core/session-key"), async (importOriginal) => { const actual = await importOriginal() return { ...actual, // your mocked methods } }) ❯ src/core/synapse/index.ts:139:4 ❯ src/filecoin-pinning-server.ts:6:1

Check failure on line 139 in src/core/synapse/index.ts

View workflow job for this annotation

GitHub Actions / test (lts/*, ubuntu-latest)

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

Error: [vitest] No "CreateDataSetPermission" export is defined on the "@filoz/synapse-core/session-key" mock. Did you forget to return it from "vi.mock"? If you need to partially mock a module, you can use "importOriginal" helper inside: vi.mock(import("@filoz/synapse-core/session-key"), async (importOriginal) => { const actual = await importOriginal() return { ...actual, // your mocked methods } }) ❯ src/core/synapse/index.ts:139:4 ❯ src/test/unit/synapse-service.test.ts:4:1

Check failure on line 139 in src/core/synapse/index.ts

View workflow job for this annotation

GitHub Actions / test (lts/*, ubuntu-latest)

[unit] src/test/unit/filecoin-pinning-server.unit.test.ts

Error: [vitest] No "CreateDataSetPermission" export is defined on the "@filoz/synapse-core/session-key" mock. Did you forget to return it from "vi.mock"? If you need to partially mock a module, you can use "importOriginal" helper inside: vi.mock(import("@filoz/synapse-core/session-key"), async (importOriginal) => { const actual = await importOriginal() return { ...actual, // your mocked methods } }) ❯ src/core/synapse/index.ts:139:4 ❯ src/filecoin-pinning-server.ts:6:1
[AddPiecesPermission]: 'AddPieces',
[SchedulePieceRemovalsPermission]: 'SchedulePieceRemovals',
}
Expand Down Expand Up @@ -165,7 +177,7 @@
const rpcUrl = config.rpcUrl ?? chain.rpcUrls.default.webSocket?.[0] ?? chain.rpcUrls.default.http[0]
const transport = rpcUrl ? createTransport(rpcUrl) : undefined

let account: Account | `0x${string}`
let account: Account | Address
let sessionKey: SessionKey<'Secp256k1'> | undefined

if (isReadOnlyConfig(config)) {
Expand All @@ -190,6 +202,20 @@
account = config.account
logger?.info({ event: 'synapse.init', mode: 'account' }, 'Initializing Synapse (pre-created account)')
} else {
const hasWallet = 'walletAddress' in config && config.walletAddress != null
const hasSessionKey = 'sessionKey' in config && config.sessionKey != null
if (hasWallet && !hasSessionKey) {
throw new Error(
'Session key authentication requires both --wallet-address and --session-key. ' +
'Missing: --session-key / SESSION_KEY.'
)
}
if (hasSessionKey && !hasWallet) {
throw new Error(
'Session key authentication requires both --wallet-address and --session-key. ' +
'Missing: --wallet-address / WALLET_ADDRESS.'
)
}
throw new Error(
'No authentication provided. Supply a private key (--private-key / PRIVATE_KEY), ' +
'wallet address (--wallet-address / WALLET_ADDRESS), or session key (--session-key / SESSION_KEY).'
Expand Down Expand Up @@ -233,9 +259,9 @@
* Handles both string addresses (read-only / session key mode) and
* full Account objects (private key mode).
*/
export function getClientAddress(synapse: Synapse): `0x${string}` {
export function getClientAddress(synapse: Synapse): Address {
const account = synapse.client.account
return (typeof account === 'string' ? account : account.address) as `0x${string}`
return (typeof account === 'string' ? account : account.address) as Address
}

/**
Expand Down
3 changes: 2 additions & 1 deletion src/core/upload/synapse.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,12 +10,13 @@ import { METADATA_KEYS, type PDPProvider } from '@filoz/synapse-sdk'
import type { StorageContext, StorageManagerUploadOptions } from '@filoz/synapse-sdk/storage'
import type { CID } from 'multiformats/cid'
import type { Logger } from 'pino'
import type { Hash } from 'viem'
import { APPLICATION_SOURCE } from '../synapse/constants.js'
import type { ProgressEvent, ProgressEventHandler } from '../utils/types.js'

export type UploadProgressEvents =
| ProgressEvent<'onStored', { providerId: bigint; pieceCid: PieceCID }>
| ProgressEvent<'onPiecesAdded', { txHash: `0x${string}`; providerId: bigint }>
| ProgressEvent<'onPiecesAdded', { txHash: Hash; providerId: bigint }>
| ProgressEvent<'onPiecesConfirmed', { dataSetId: bigint; providerId: bigint; pieceIds: bigint[] }>
| ProgressEvent<'onCopyComplete', { providerId: bigint; pieceCid: PieceCID }>
| ProgressEvent<'onCopyFailed', { providerId: bigint; pieceCid: PieceCID; error: Error }>
Expand Down
48 changes: 39 additions & 9 deletions src/filecoin-pinning-server.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
import fastify, { type FastifyInstance, type FastifyRequest } from 'fastify'
import { CID } from 'multiformats/cid'
import type { Logger } from 'pino'
import { isAddress, isHex } from 'viem'
import type { Config } from './core/synapse/index.js'
import { initializeSynapse, type PrivateKeyConfig } from './core/synapse/index.js'
import { initializeSynapse, type SynapseSetupConfig } from './core/synapse/index.js'
import { FilecoinPinStore, type PinOptions } from './filecoin-pin-store.js'
import type { ServiceInfo } from './server.js'

Expand All @@ -20,20 +21,49 @@ const DEFAULT_USER_INFO = {
name: 'Default User',
}

function buildSynapseConfig(config: Config): SynapseSetupConfig {
const base = { rpcUrl: config.rpcUrl }

if (config.walletAddress && config.sessionKey) {
if (!isAddress(config.walletAddress)) {
throw new Error('Wallet address must be an ethereum address')
}
if (!isHex(config.sessionKey)) {
throw new Error('Session key must be 0x-prefixed hexadecimal')
}
if (config.sessionKey.length !== 66) {
throw new Error('Session key must be 32 bytes')
}
return {
...base,
walletAddress: config.walletAddress,
sessionKey: config.sessionKey,
}
}

if (config.privateKey) {
if (!isHex(config.privateKey)) {
throw new Error('Private key must be 0x-prefixed hexadecimal')
}
if (config.privateKey.length !== 66) {
throw new Error('Private key must be 32 bytes')
}
return { ...base, privateKey: config.privateKey }
}

throw new Error(
'No authentication configured. Provide a private key (--private-key / PRIVATE_KEY) ' +
'or session key (--wallet-address + --session-key / WALLET_ADDRESS + SESSION_KEY).'
)
}

export async function createFilecoinPinningServer(
config: Config,
logger: Logger,
serviceInfo: ServiceInfo
): Promise<{ server: FastifyInstance; pinStore: FilecoinPinStore }> {
// Set up Synapse service
if (!config.privateKey) {
throw new Error('PRIVATE_KEY environment variable is required to start the pinning server')
}

const synapseConfig: PrivateKeyConfig = {
privateKey: config.privateKey as `0x${string}`,
rpcUrl: config.rpcUrl,
}
const synapseConfig = buildSynapseConfig(config)
const synapse = await initializeSynapse(synapseConfig, logger)

const filecoinPinStore = new FilecoinPinStore({
Expand Down
4 changes: 2 additions & 2 deletions src/payments/interactive.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
import { cancel, confirm, isCancel, password, text } from '@clack/prompts'
import { calibration, mainnet } from '@filoz/synapse-sdk'
import pc from 'picocolors'
import { parseUnits } from 'viem'
import { type Hex, parseUnits } from 'viem'
import {
calculateDepositCapacity,
checkAndSetAllowances,
Expand Down Expand Up @@ -84,7 +84,7 @@ export async function runInteractiveSetup(options: PaymentSetupOptions): Promise
const rpcUrl = options.rpcUrl || defaultRpcUrl

const config: PrivateKeyConfig = {
privateKey: privateKey as `0x${string}`,
privateKey: privateKey as Hex,
}
if (rpcUrl) {
config.rpcUrl = rpcUrl
Expand Down
9 changes: 5 additions & 4 deletions src/server.ts
Original file line number Diff line number Diff line change
Expand Up @@ -60,10 +60,11 @@ export async function startServer(): Promise<void> {
)

// Also print a user-friendly message to stderr for clarity
if (errorMessage.includes('PRIVATE_KEY')) {
console.error('\n❌ Error: PRIVATE_KEY environment variable is required')
console.error(' Please set your private key: export PRIVATE_KEY=0x...')
console.error(' Or run with: PRIVATE_KEY=0x... filecoin-pin server\n')
if (errorMessage.includes('No authentication')) {
console.error('\n❌ Error: Authentication is required to start the pinning server')
console.error(' Private key: --private-key <key> or PRIVATE_KEY=0x...')
console.error(' Session key: --wallet-address <addr> --session-key <key>')
console.error(' or WALLET_ADDRESS=0x... SESSION_KEY=0x...\n')
}

process.exit(1)
Expand Down
11 changes: 11 additions & 0 deletions src/test/mocks/synapse-core-session-key.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
import { type Mock, vi } from 'vitest'

/**
* Mock implementation of @filoz/synapse-core/session-key for testing
*
* fromSecp256k1 returns a minimal SessionKey-shaped object with a no-op
* syncExpirations so tests never hit the real network.
*/
export const fromSecp256k1: Mock = vi.fn(() => ({
syncExpirations: vi.fn().mockResolvedValue(undefined),
}))
Loading
Loading