Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
27 commits
Select commit Hold shift + click to select a range
9c2d23e
feat(payments): add interactive Squid funding
snissn Jul 30, 2026
5772edb
fix(payments): seal Squid error redaction
snissn Jul 30, 2026
82d0858
fix(payments): disclose Squid fee commitment
snissn Jul 30, 2026
2f3b0f5
docs(payments): clarify Squid funding limits
snissn Jul 30, 2026
a9343da
test(payments): handle Windows marker modes
snissn Jul 30, 2026
960535f
refactor(payments): simplify Squid funding integration
snissn Jul 31, 2026
d51ef2c
fix(payments): pin reviewed Squid signing fix
snissn Jul 31, 2026
5b0563d
chore(payments): use squid-evm-funding v0.1.0
snissn Jul 31, 2026
7d82bf5
chore(deps): update squid-evm-funding repository
snissn Aug 6, 2026
1463199
refactor(payments): remove nested source floor ternary
snissn Aug 18, 2026
207a291
fix(payments): allow Filecoin USDFC FIL bootstrap
snissn Aug 18, 2026
5daad40
refactor(test): remove nested environment ternary
snissn Aug 18, 2026
c588bda
test(payments): use actual Squid funding module
snissn Aug 18, 2026
8586219
fix(payments): guard pending funding before planning
snissn Aug 18, 2026
a17ebe5
test(payments): assert pending marker skips confirmation
snissn Aug 18, 2026
047df3c
fix(payments): distinguish funding marker failure phases
snissn Aug 18, 2026
aca9bab
fix(payments): buffer Squid USDFC acquisition
snissn Aug 18, 2026
b210af7
fix(payments): restrict Squid acquisition to deposits
snissn Aug 18, 2026
8d4a2c8
fix(payments): add bounded Squid price headroom
snissn Aug 18, 2026
ce8d88d
chore(deps): sync lockfile with master
snissn Aug 18, 2026
d2ac315
chore(deps): use published Squid funding package
snissn Aug 19, 2026
aaeec19
fix(payments): provide default Squid integrator ID
snissn Aug 19, 2026
4dc8229
chore(deps): update Squid funding to 0.2.0
snissn Aug 20, 2026
75b906a
chore(deps): update Squid funding to 0.2.1
snissn Aug 20, 2026
87d73f8
chore(payments): update Squid funding to 0.2.2
snissn Aug 20, 2026
44ba287
chore: update Squid funding package
snissn Aug 22, 2026
b8659f3
fix: disclose Squid native route fees
snissn Aug 22, 2026
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
17 changes: 17 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -205,6 +205,20 @@ filecoin-pin data-set <dataset-id>
filecoin-pin add myfile.txt --network calibration
```

### Fund from another EVM chain

On Filecoin Mainnet, interactive `payments fund` can use [Squid](documentation/glossary.md#squid) when the owner wallet is short of FIL for gas or USDFC for the requested deposit. It supports Filecoin, Arbitrum, Ethereum, Base, Optimism, Polygon, Avalanche, and BNB Chain, and accepts `native`, a token address, or an unambiguous token symbol from Squid's current catalog.

```bash
filecoin-pin payments fund --days 30 \
--from-chain arbitrum \
--from-token USDC \
--max-source-amount 10 \
--source-rpc-url https://your-arbitrum-rpc.example
```

Filecoin Pin uses the public `filecoin-testing-94a4a25a-d40b-41cb-b148-e96098862` Squid integrator ID by default; `SQUID_INTEGRATOR_ID` can override it. The same private key must control the wallet on both chains. Filecoin Pin shows the planned source spend and asks for confirmation before signing. If the process is interrupted, the next attempt prints the path of a pending marker; verify both chains manually before deleting that file and retrying. Calibration, devnet, session keys, and non-interactive acquisition are not supported.

For detailed guides, see:
- **CLI**: [Complete CLI walkthrough](https://docs.filecoin.io/builder-cookbook/filecoin-pin/filecoin-pin-cli)
- **GitHub Action**: [CI/CD integration guide](https://docs.filecoin.io/builder-cookbook/filecoin-pin/github-action)
Expand Down Expand Up @@ -275,6 +289,9 @@ RPC_URL=wss://... # Filecoin RPC endpoint (overrides NETWORK if spe
# Mainnet: wss://wss.node.glif.io/apigw/lotus/rpc/v1
# Calibration: wss://wss.calibration.node.glif.io/apigw/lotus/rpc/v1

# Optional - interactive Mainnet funding through Squid
SQUID_INTEGRATOR_ID=... # Override the built-in public Squid integrator ID

# Optional for Pinning Server Daemon
ACCESS_TOKEN=... # Bearer token required on all API requests except GET /
ALLOW_NO_AUTH=true # Start without a token, serving all requests unauthenticated (not recommended)
Expand Down
3 changes: 3 additions & 0 deletions documentation/glossary.md
Original file line number Diff line number Diff line change
Expand Up @@ -237,6 +237,9 @@ Session keys require specific permissions (such as CREATE_DATA_SET and ADD_PIECE

Note that the filecoin-pin CLI's `--session-key` flag (and `SESSION_KEY` environment variable) expect the session key's **private key** — the `SESSION_KEY` value printed by `filecoin-pin session create` or `filecoin-pin session generate` — not the session address. The (public) session address is only used when authorizing or revoking: `filecoin-pin session authorize <session-address>` and `filecoin-pin session revoke <session-address>`.

## Squid

Squid is the cross-chain routing service [Filecoin Pin](#filecoin-pin) can use to acquire FIL or [USDFC](#usdfc) missing from a Filecoin Mainnet wallet during interactive funding. Supported tokens and available routes come from Squid at command time; see the [Squid API documentation](https://docs.squidrouter.com/api).


## Standard IPFS Tooling
Expand Down
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -152,6 +152,7 @@
"picocolors": "^1.1.1",
"pino": "^10.3.1",
"semver": "^7.7.4",
"@filecoin-project/squid-evm-funding": "^0.3.1",
"varint": "^6.0.0",
"viem": "^2.48.3"
},
Expand Down
16 changes: 16 additions & 0 deletions pnpm-lock.yaml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 2 additions & 0 deletions pnpm-workspace.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,10 @@ packages:

minimumReleaseAge: 10080
minimumReleaseAgeExclude:
- "@filecoin-project/squid-evm-funding"
- "@filoz/*"
onlyBuiltDependencies:
- esbuild
- node-datachannel
- playwright
- "@filecoin-project/squid-evm-funding"
3 changes: 2 additions & 1 deletion src/commands/payments.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import { runInteractiveSetup } from '../payments/interactive.js'
import { showPaymentStatus } from '../payments/status.js'
import type { FundOptions, PaymentSetupOptions } from '../payments/types.js'
import { runWithdraw } from '../payments/withdraw.js'
import { addAuthOptions } from '../utils/cli-options.js'
import { addAuthOptions, addFundingSourceOptions } from '../utils/cli-options.js'

export const paymentsCommand = new Command('payments').description(
'Manage storage payments (required before your first upload)'
Expand Down Expand Up @@ -71,6 +71,7 @@ const fundCommand = new Command('fund')
})

addAuthOptions(fundCommand)
addFundingSourceOptions(fundCommand)
paymentsCommand.addCommand(fundCommand)

// Withdraw command
Expand Down
70 changes: 62 additions & 8 deletions src/payments/fund.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,29 +5,34 @@
*/

import { confirm, isCancel } from '@clack/prompts'
import type { Synapse } from '@filoz/synapse-sdk'
import { NATIVE_TOKEN_ADDRESS } from '@filecoin-project/squid-evm-funding'
import { type Synapse, TIME_CONSTANTS } from '@filoz/synapse-sdk'
import pc from 'picocolors'
import { parseUnits } from 'viem'
import { formatUnits, parseUnits } from 'viem'
import { CliFatal, CliIncomplete, isCliFatal, isCliIncomplete, setIncompleteExitCode } from '../common/cli-errors.js'
import { MIN_RUNWAY_DAYS } from '../common/constants.js'
import { resolveIpfsIndexedMetadata } from '../core/metadata/index.js'
import {
calculateFilecoinPayFundingPlan,
checkUSDFCBalance,
clampDepositToLimit,
DEFAULT_LOCKUP_DAYS,
depositUSDFC,
executeFilecoinPayFunding,
getPaymentStatus,
MIN_FIL_FOR_GAS,
planFilecoinPayFunding,
toStorageRunwaySummary,
withdrawUSDFC,
} from '../core/payments/index.js'
import { initializeSynapse } from '../core/synapse/index.js'
import { getClientAddress, initializeSynapse } from '../core/synapse/index.js'
import { formatUSDFC } from '../core/utils/format.js'
import { formatRunwaySummary } from '../core/utils/index.js'
import { getCLILogger, parseCLIAuth } from '../utils/cli-auth.js'
import type { Spinner } from '../utils/cli-helpers.js'
import { cancel, createSpinner, intro, isInteractive, outro } from '../utils/cli-helpers.js'
import { isTTY, log } from '../utils/cli-logger.js'
import { acquirePaymentShortfalls, validateFundingSourceOptions } from './squid-funding.js'
import type { AutoFundOptions, FundingAdjustmentResult, FundOptions } from './types.js'

// Helper: confirm/warn or bail when target implies < lockup-days runway
Expand Down Expand Up @@ -245,12 +250,13 @@ export async function runFund(options: FundOptions): Promise<void> {

spinner.start('Connecting...')
try {
const sourceRequested = validateFundingSourceOptions(options)

// Parse and validate authentication
const authConfig = parseCLIAuth(options)

const logger = getCLILogger()
const synapse = await initializeSynapse(authConfig, logger)

spinner.stop(`${pc.green('✓')} Connected`)

const targetDays: number = hasDays ? Number(options.days) : 0
Expand All @@ -265,14 +271,62 @@ export async function runFund(options: FundOptions): Promise<void> {
throw new Error(`Invalid --amount '${options.amount}'`)
}

spinner.start('Calculating funding plan...')
const planResult = await planFilecoinPayFunding({
synapse,
const planOptions = {
targetRunwayDays: hasDays ? targetDays : undefined,
targetDeposit: hasAmount ? targetDeposit : undefined,
mode: options.mode ?? 'exact',
allowWithdraw: options.mode !== 'minimum',
})
} as const

spinner.start('Calculating funding plan...')
if (sourceRequested) {
const [status, accountSummary] = await Promise.all([
getPaymentStatus(synapse),
synapse.payments.accountSummary({}),
])
const preview = calculateFilecoinPayFundingPlan({ status, accountSummary, ...planOptions })
const filShortfall =
preview.delta > 0n && status.filBalance < MIN_FIL_FOR_GAS ? MIN_FIL_FOR_GAS - status.filBalance : 0n
const requiredWalletUsdfc =
preview.delta > 0n ? preview.delta + preview.current.spendRatePerEpoch * TIME_CONSTANTS.EPOCHS_PER_HOUR : 0n
const usdfcShortfall =
requiredWalletUsdfc > status.walletUsdfcBalance ? requiredWalletUsdfc - status.walletUsdfcBalance : 0n
if (filShortfall > 0n || usdfcShortfall > 0n) {
if (!isInteractive()) throw new Error('Squid source acquisition requires an interactive terminal')
await acquirePaymentShortfalls({
synapse,
owner: getClientAddress(synapse),
filShortfall,
usdfcShortfall,
requiredWalletUsdfc,
options,
confirm: async (summary) => {
const spend = summary.quotes.reduce((total, quote) => total + quote.sourceAmount, 0n)
const nativeRouteFee = summary.quotes
.flatMap((quote) => quote.costs)
.filter(
(cost) =>
cost.kind === 'fee' &&
cost.token.chainId === summary.source.chainId &&
cost.token.address?.toLowerCase() === NATIVE_TOKEN_ADDRESS
)
.reduce((total, cost) => total + cost.amount, 0n)
const targets = summary.quotes
.map(
(quote) =>
`${formatUnits(quote.requirement.amount, 18)} ${quote.requirement.id === 'filecoin-fil' ? 'FIL' : 'USDFC'}`
)
.join(' and ')
const proceed = await confirm({
message: `Spend ${formatUnits(spend, summary.source.decimals)} ${summary.source.symbol} from ${summary.sourceChainName} via Squid to receive ${targets} on Filecoin (source-token limit: ${formatUnits(summary.maxSourceAmount, summary.source.decimals)} ${summary.source.symbol}; estimated Squid route fee: ${formatUnits(nativeRouteFee, summary.nativeCurrency.decimals)} ${summary.nativeCurrency.symbol}; transaction-gas limit: ${formatUnits(summary.maxNativeFee, summary.nativeCurrency.decimals)} ${summary.nativeCurrency.symbol}; final network fees may vary)?`,
initialValue: false,
})
if (isCancel(proceed) || !proceed) throw new CliIncomplete('Source acquisition cancelled by user')
},
})
}
}
const planResult = await planFilecoinPayFunding({ synapse, ...planOptions })
const { plan } = planResult
spinner.stop(`${pc.green('✓')} Funding plan prepared`)

Expand Down
Loading