Skip to main content

Overview

Filecoin Pay V1 enables ERC20 token payment flows through payment rails - automated payment channels between payers and recipients. It supports:
  • Continuous rate-based payments (per epoch)
  • Deposits and withdrawals
  • Operator approvals (delegate rail creation)
  • Settlement with validation callbacks
  • One-time transfers

Payment Rails

A payment rail is a persistent payment channel:
struct Rail {
    uint256 id;
    address client;        // Payer
    address payee;         // Recipient
    address operator;      // Service contract (optional)
    uint256 rate;          // Amount per epoch
    uint256 startEpoch;
    uint256 lastSettledEpoch;
    uint256 endEpoch;      // 0 = active, >0 = terminated
}

Architecture

Account Management

Deposit

import * as Pay from '@filoz/synapse-core/pay'
import { createWalletClient, http } from 'viem'
import { calibration } from '@filoz/synapse-core/chains'

const client = createWalletClient({
  chain: calibration,
  transport: http(),
  account,
})

const hash = await Pay.deposit(client, {
  amount: 100000000000000000000n, // 100 tokens (18 decimals)
  to: account.address, // Optional, defaults to sender
})

await client.waitForTransactionReceipt({ hash })

Deposit with Permit (ERC-2612)

const hash = await Pay.depositWithPermit(client, {
  amount: 100000000000000000000n,
  deadline: BigInt(Math.floor(Date.now() / 1000) + 3600), // 1 hour
})

Deposit and Approve Operator

Combine deposit + operator approval in one transaction:
const hash = await Pay.depositAndApprove(client, {
  amount: 100000000000000000000n,
  operator: calibration.contracts.fwss.address,
  rateAllowance: 10000000000000000000n,    // 10 tokens per epoch
  lockupAllowance: 1000000000000000000000n, // 1000 tokens lockup
})

Withdraw

const hash = await Pay.withdraw(client, {
  amount: 50000000000000000000n, // 50 tokens
})

Check Balance

const account = await Pay.accounts(client, {
  address: account.address,
})

console.log('Available:', account.availableFunds)
console.log('Total deposited:', account.totalDeposited)
console.log('Total withdrawn:', account.totalWithdrawn)
console.log('Total paid:', account.totalPaid)
console.log('Total received:', account.totalReceived)

Operator Approvals

Operators (like FWSS) can create rails on behalf of clients:

Approve Operator

const hash = await Pay.setOperatorApproval(client, {
  operator: calibration.contracts.fwss.address,
  approve: true,
  rateAllowance: 10000000000000000000n,    // Max 10 tokens/epoch
  lockupAllowance: 1000000000000000000000n, // Max 1000 tokens lockup
  maxLockupPeriod: 2880n,                    // Max 30 days (in epochs)
})

Revoke Operator

const hash = await Pay.setOperatorApproval(client, {
  operator: calibration.contracts.fwss.address,
  approve: false,
})

Check Approval Status

const approval = await Pay.operatorApprovals(client, {
  address: account.address,
  operator: calibration.contracts.fwss.address,
})

console.log('Approved:', approval.isApproved)
console.log('Rate allowance:', approval.rateAllowance)
console.log('Lockup allowance:', approval.lockupAllowance)
console.log('Max lockup period:', approval.maxLockupPeriod)
console.log('Rate used:', approval.rateUsage)
console.log('Lockup used:', approval.lockupUsage)

Rail Management

Get Rail Info

const rail = await Pay.getRail(client, { 
  railId: 42n 
})

console.log('Client:', rail.client)
console.log('Payee:', rail.payee)
console.log('Operator:', rail.operator)
console.log('Rate per epoch:', rail.rate)
console.log('Start:', rail.startEpoch)
console.log('Last settled:', rail.lastSettledEpoch)
console.log('End:', rail.endEpoch) // 0 = active

Query Rails

// Get all rails where address is payer
const { results: payerRails } = await Pay.getRailsForPayerAndToken(client, {
  payer: account.address,
})

// Get all rails where address is payee
const { results: payeeRails } = await Pay.getRailsForPayeeAndToken(client, {
  payee: account.address,
})

for (const rail of payerRails) {
  console.log(`Rail ${rail.railId}:`)
  console.log(`  To: ${rail.payee}`)
  console.log(`  Rate: ${rail.rate}/epoch`)
  console.log(`  Active: ${rail.active}`)
}

Settlement

Settle Rail

const hash = await Pay.settleRail(client, {
  railId: 42n,
  untilEpoch: 1000n, // Optional, defaults to current epoch
})

Get Settlement Amounts (Read-Only)

import { simulateContract } from 'viem/actions'

const { result } = await simulateContract(
  client,
  Pay.settleRailCall({
    chain: calibration,
    railId: 42n,
    untilEpoch: 1000n,
  })
)

const [
  totalSettledAmount,
  totalNetPayeeAmount,
  totalOperatorCommission,
  totalNetworkFee,
  finalSettledEpoch,
  note,
] = result

console.log('Total settled:', totalSettledAmount)
console.log('Net payee:', totalNetPayeeAmount)
console.log('Operator fee:', totalOperatorCommission)
console.log('Network fee:', totalNetworkFee)

Emergency Settlement (Terminated Rails)

const hash = await Pay.settleTerminatedRailWithoutValidation(client, {
  railId: 42n,
})
Emergency settlement bypasses validator callbacks. Use only for terminated rails after max settlement epoch has passed.

Validation Callbacks

Service contracts can validate settlements:
interface ISettlementValidator {
    function beforeSettle(
        uint256 railId,
        uint256 untilEpoch,
        uint256 amount
    ) external returns (bool approved, uint256 approvedAmount, string memory note);
}
FWSS implements this to verify storage proofs before settlement.

Events

Deposit

event Deposited(
    address indexed account,
    uint256 amount
);

RailCreated

event RailCreated(
    uint256 indexed railId,
    address indexed client,
    address indexed payee,
    uint256 rate
);

RailSettled

event RailSettled(
    uint256 indexed railId,
    uint256 amount,
    uint256 settledEpoch
);

Listen for Events

import { watchContractEvent } from 'viem/actions'

const unwatch = watchContractEvent(client, {
  address: calibration.contracts.filecoinPay.address,
  abi: calibration.contracts.filecoinPay.abi,
  eventName: 'RailSettled',
  args: {
    railId: 42n,
  },
  onLogs: (logs) => {
    for (const log of logs) {
      console.log('Rail settled:')
      console.log('  Amount:', log.args.amount)
      console.log('  Epoch:', log.args.settledEpoch)
    }
  },
})

Fee Structure

// Settlement includes fees
totalSettledAmount = baseAmount + operatorCommission + networkFee
totalNetPayeeAmount = baseAmount

// Fees are configurable by contract owner
// Example:
// Operator commission: 5%
// Network fee: 1%

Best Practices

Use Operator Approvals

Let services manage rails with operator approvals

Monitor Balance

Keep sufficient balance for continuous payments

Settle Regularly

Settle rails to free up locked funds

Use Permits

Use permit functions to reduce transaction count

Specification

Filecoin Pay README

Read the full payment rails specification

Source Code

Filecoin Pay

View the FilecoinPay contract source

Next Steps

FWSS Contract

Learn how FWSS uses payment rails

Payment Management

Use payments in your app

Build docs developers (and LLMs) love