diff --git a/docs/astro.config.mjs b/docs/astro.config.mjs
index 05885f51..22fae8d4 100644
--- a/docs/astro.config.mjs
+++ b/docs/astro.config.mjs
@@ -150,6 +150,15 @@ export default defineConfig({
},
],
},
+ {
+ label: 'Storage Provider Guides',
+ collapsed: true,
+ items: [
+ {
+ autogenerate: { directory: 'storage-provider-guides', collapsed: true },
+ },
+ ],
+ },
{
label: 'CookBooks',
collapsed: true,
diff --git a/docs/src/content/docs/index.mdx b/docs/src/content/docs/index.mdx
index 43963e1f..a6af9e34 100644
--- a/docs/src/content/docs/index.mdx
+++ b/docs/src/content/docs/index.mdx
@@ -34,12 +34,12 @@ Ready to integrate Filecoin Onchain Cloud into your application?
Get up and running in minutes with our step-by-step tutorial. [Start Building →](/getting-started/)
-
- Understand the architecture and key protocols behind FOC. [Learn More →](/core-concepts/architecture/)
-
Comprehensive guides for storage, payments, and monitoring. [Explore Guides →](/developer-guides/synapse/)
+
+ Run a PDP node, register with Warm Storage, and supply storage to the network. [Become a Provider →](/storage-provider-guides/)
+
diff --git a/docs/src/content/docs/storage-provider-guides/advanced/_meta.yml b/docs/src/content/docs/storage-provider-guides/advanced/_meta.yml
new file mode 100644
index 00000000..3edb6e8e
--- /dev/null
+++ b/docs/src/content/docs/storage-provider-guides/advanced/_meta.yml
@@ -0,0 +1,3 @@
+label: Advanced
+collapsed: true
+order: 4
diff --git a/docs/src/content/docs/storage-provider-guides/advanced/lxd-container-setup.mdx b/docs/src/content/docs/storage-provider-guides/advanced/lxd-container-setup.mdx
new file mode 100644
index 00000000..b152fe50
--- /dev/null
+++ b/docs/src/content/docs/storage-provider-guides/advanced/lxd-container-setup.mdx
@@ -0,0 +1,276 @@
+---
+title: LXD Container Setup
+description: Set up LXD containers with ZFS storage, static IPs, and SSH access to isolate Filecoin PDP deployments on a single host.
+sidebar:
+ order: 2
+---
+
+import { Steps } from '@astrojs/starlight/components';
+
+This guide sets up LXD containers with SSH access for Filecoin PDP deployments. Containers let you run several isolated nodes on one host, each with its own static IP and storage mounts.
+
+This config comes from one specific environment. Change hostnames, IPs, storage paths, and other settings to match your system.
+
+## Prerequisites
+
+- Root or sudo access
+- 32 GB+ RAM recommended
+- 100 GB+ disk space
+
+## Install and Initialize LXD
+
+
+
+1. **Install LXD** and add your user to the `lxd` group:
+
+ ```bash
+ sudo snap install lxd
+ sudo usermod -aG lxd $USER
+ newgrp lxd
+ lxd --version
+ ```
+
+2. **Initialize LXD:**
+
+ ```bash
+ lxd init
+ ```
+
+ Use these values when prompted:
+
+ | Prompt | Value |
+ | --- | --- |
+ | Clustering | no |
+ | Storage pool | yes |
+ | Storage backend | zfs |
+ | Create new ZFS pool | yes |
+ | Use existing block device | no |
+ | Size | 200GiB (or more) |
+ | MAAS server | no |
+ | Network bridge | yes |
+ | Bridge name | lxdbr0 |
+ | IPv4 / IPv6 | auto for both |
+ | LXD over network | no |
+ | Auto-update images | yes |
+
+ Verify the storage pool:
+
+ ```bash
+ lxc storage list
+ zfs list
+ ```
+
+
+
+## Create and Launch a Container
+
+
+
+1. **Create a container profile.** Profiles define network and storage settings. Create one per container with a unique IP:
+
+ ```bash
+ lxc profile create mycontainer-1
+ lxc profile edit mycontainer-1
+ ```
+
+ Paste this config, changing the IP for each container:
+
+ ```yaml
+ name: mycontainer-1
+ description: Container with static IP
+ config:
+ user.network-config: |
+ version: 2
+ renderer: networkd
+ ethernets:
+ eth0:
+ dhcp4: false
+ addresses:
+ - 192.168.1.100/24
+ gateway4: 192.168.1.1
+ nameservers:
+ addresses:
+ - 192.168.1.1
+ - 8.8.8.8
+ devices:
+ eth0:
+ name: eth0
+ nictype: bridged
+ parent: lxdbr0
+ type: nic
+ root:
+ path: /
+ pool: default
+ type: disk
+ ```
+
+2. **Launch the container** and check its status:
+
+ ```bash
+ lxc launch ubuntu:22.04 mycontainer-1 -p mycontainer-1
+ lxc list
+ ```
+
+
+
+## Set Up SSH Access
+
+
+
+1. **Install OpenSSH** in the container:
+
+ ```bash
+ lxc exec mycontainer-1 -- bash
+ apt update
+ apt install -y openssh-server
+ systemctl enable ssh
+ systemctl start ssh
+ exit
+ ```
+
+2. **Add your SSH key** and test the connection:
+
+ ```bash
+ lxc exec mycontainer-1 -- mkdir -p /root/.ssh
+ lxc file push ~/.ssh/id_rsa.pub mycontainer-1/root/.ssh/authorized_keys
+ lxc exec mycontainer-1 -- chmod 700 /root/.ssh
+ lxc exec mycontainer-1 -- chmod 600 /root/.ssh/authorized_keys
+ ssh root@192.168.1.100
+ ```
+
+3. **Create a non-root user (recommended):**
+
+ ```bash
+ lxc exec mycontainer-1 -- bash
+ adduser myuser
+ usermod -aG sudo myuser
+ mkdir -p /home/myuser/.ssh
+ cp /root/.ssh/authorized_keys /home/myuser/.ssh/
+ chown -R myuser:myuser /home/myuser/.ssh
+ exit
+ ssh myuser@192.168.1.100
+ ```
+
+
+
+## Add Storage Mounts
+
+Mount host directories into the container for sealing and long-term storage:
+
+```bash
+sudo mkdir -p /data/mycontainer-1/storage
+lxc config device add mycontainer-1 data-storage disk source=/data/mycontainer-1/storage path=/mnt/storage
+lxc exec mycontainer-1 -- df -h
+```
+
+Add multiple mounts the same way:
+
+```bash
+lxc config device add mycontainer-1 sealing disk source=/nvme-storage/mycontainer-1 path=/sealing
+lxc config device add mycontainer-1 long-term disk source=/network-storage/mycontainer-1 path=/storage
+```
+
+## Set Resource Limits (Optional)
+
+```bash
+lxc config set mycontainer-1 limits.memory 32GiB
+lxc config set mycontainer-1 limits.cpu 8
+lxc info mycontainer-1
+```
+
+## Management Commands
+
+```bash
+# Container control
+lxc start mycontainer-1
+lxc stop mycontainer-1
+lxc restart mycontainer-1
+lxc delete mycontainer-1 --force
+
+# Access
+lxc exec mycontainer-1 -- bash
+ssh myuser@192.168.1.100
+
+# Snapshots
+lxc snapshot mycontainer-1 backup-2024
+lxc restore mycontainer-1 backup-2024
+
+# Info
+lxc list
+lxc info mycontainer-1
+lxc config show mycontainer-1
+```
+
+## Create Additional Containers
+
+Copy the profile, change the IP, and launch:
+
+```bash
+lxc profile copy mycontainer-1 mycontainer-2
+lxc profile edit mycontainer-2 # change IP to 192.168.1.101
+lxc launch ubuntu:22.04 mycontainer-2 -p mycontainer-2
+lxc exec mycontainer-2 -- apt update
+lxc exec mycontainer-2 -- apt install -y openssh-server
+lxc file push ~/.ssh/id_rsa.pub mycontainer-2/root/.ssh/authorized_keys
+lxc exec mycontainer-2 -- chmod 700 /root/.ssh
+lxc exec mycontainer-2 -- chmod 600 /root/.ssh/authorized_keys
+sudo mkdir -p /data/mycontainer-2/storage
+lxc config device add mycontainer-2 data-storage disk source=/data/mycontainer-2/storage path=/mnt/storage
+```
+
+## Networking Options
+
+- **Managed bridge (default, lxdbr0).** Containers are NAT'd behind the host. Simple, with automatic DHCP, but not directly reachable externally without port forwarding. Configured during `lxd init`.
+- **Physical network bridge.** Containers receive IP addresses on your physical network. Set this in the profile:
+
+ ```yaml
+ devices:
+ eth0:
+ nictype: bridged
+ parent: br0
+ type: nic
+ ```
+
+- **SR-IOV (advanced).** Near-native performance with hardware isolation using dedicated NIC virtual functions. Requires an SR-IOV-capable NIC and kernel module configuration.
+
+## Troubleshooting
+
+```bash
+# Container won't start
+lxc info mycontainer-1 --show-log
+
+# Network issues
+lxc exec mycontainer-1 -- cloud-init status
+lxc exec mycontainer-1 -- ip addr
+ping 192.168.1.100
+
+# SSH not working
+lxc exec mycontainer-1 -- systemctl status ssh
+lxc exec mycontainer-1 -- ls -la /root/.ssh/
+
+# Storage issues
+zpool status
+lxc storage info default
+```
+
+## Complete Setup Example
+
+```bash
+lxc profile create prod-1
+lxc profile edit prod-1
+sudo mkdir -p /data/prod-1/{sealing,storage}
+lxc launch ubuntu:22.04 prod-1 -p prod-1
+lxc exec prod-1 -- apt update && apt install -y openssh-server
+lxc exec prod-1 -- mkdir -p /root/.ssh
+lxc file push ~/.ssh/id_rsa.pub prod-1/root/.ssh/authorized_keys
+lxc exec prod-1 -- chmod 700 /root/.ssh
+lxc exec prod-1 -- chmod 600 /root/.ssh/authorized_keys
+lxc config device add prod-1 sealing disk source=/data/prod-1/sealing path=/sealing
+lxc config device add prod-1 storage disk source=/data/prod-1/storage path=/storage
+lxc config set prod-1 limits.memory 64GiB
+lxc config set prod-1 limits.cpu 16
+lxc snapshot prod-1 initial-setup
+ssh root@192.168.1.100
+```
+
+You now have LXD containers with ZFS storage, static IP networking, SSH access, persistent storage mounts, and resource management.
diff --git a/docs/src/content/docs/storage-provider-guides/advanced/nginx-reverse-proxy.mdx b/docs/src/content/docs/storage-provider-guides/advanced/nginx-reverse-proxy.mdx
new file mode 100644
index 00000000..a97d3aa7
--- /dev/null
+++ b/docs/src/content/docs/storage-provider-guides/advanced/nginx-reverse-proxy.mdx
@@ -0,0 +1,212 @@
+---
+title: Nginx Reverse Proxy Setup
+description: Put Nginx in front of a Curio PDP node to terminate TLS with Let's Encrypt certificates while Curio serves plain HTTP on the LAN.
+sidebar:
+ order: 1
+---
+
+import { Steps } from '@astrojs/starlight/components';
+
+This guide sets up Nginx on Ubuntu 22.04 to provide HTTPS access to a Curio PDP node using Let's Encrypt certificates. Nginx terminates TLS on the public side and forwards plain HTTP to Curio over the LAN, so Curio does not manage certificates itself.
+
+The example serves `calib.ezpdpz.net` from a Curio instance at `192.168.1.160`. Substitute your own hostnames, internal IP addresses, and paths throughout.
+
+## Architecture
+
+- Nginx handles HTTPS on port 443 (public-facing) and terminates TLS.
+- Curio runs on an internal IP, serving HTTP on port 443 with `DelegateTLS` enabled.
+- Traffic between Nginx and Curio is unencrypted HTTP over the LAN.
+
+## Prerequisites
+
+- Root or sudo access
+- One or more domain names pointing at your server's public IP
+- A Curio PDP node running on the internal network
+- Ports 80 and 443 open in the firewall
+
+## Setup
+
+
+
+1. **Install Nginx** and start it:
+
+ ```bash
+ sudo apt update
+ sudo apt install nginx
+ nginx -v
+ sudo systemctl start nginx
+ sudo systemctl enable nginx
+ ```
+
+2. **Install Certbot** for Let's Encrypt certificates:
+
+ ```bash
+ sudo apt install certbot python3-certbot-nginx
+ certbot --version
+ ```
+
+3. **Configure a virtual host.** Create a config file for your domain. Replace `calib.ezpdpz.net` with your domain:
+
+ ```bash
+ sudo nano /etc/nginx/sites-available/calib.ezpdpz.net
+ ```
+
+ Start with a minimal config so Certbot can validate the domain:
+
+ ```nginx
+ server {
+ listen 80;
+ server_name calib.ezpdpz.net;
+
+ location / {
+ return 200 "Server is ready for certbot";
+ }
+ }
+ ```
+
+ Enable the site, test the config, and reload:
+
+ ```bash
+ sudo ln -s /etc/nginx/sites-available/calib.ezpdpz.net /etc/nginx/sites-enabled/
+ sudo nginx -t
+ sudo systemctl reload nginx
+ ```
+
+4. **Obtain the SSL certificate** with the Nginx plugin:
+
+ ```bash
+ sudo certbot --nginx -d calib.ezpdpz.net
+ ```
+
+ Follow the prompts: enter your email, agree to the terms, and choose to redirect HTTP to HTTPS. Certbot obtains the certificate, wires it into Nginx, and sets up automatic renewal.
+
+5. **Configure the reverse proxy.** Edit the site config again:
+
+ ```bash
+ sudo nano /etc/nginx/sites-available/calib.ezpdpz.net
+ ```
+
+ Replace its contents with the following. Substitute `YOUR_DOMAIN` and `YOUR_CURIO_IP`:
+
+ ```nginx
+ # HTTP server - redirect to HTTPS
+ server {
+ listen 80;
+ server_name YOUR_DOMAIN;
+
+ # Let's Encrypt challenge location
+ location /.well-known/acme-challenge/ {
+ root /var/www/html;
+ }
+
+ # Redirect everything else to HTTPS
+ location / {
+ return 301 https://$server_name$request_uri;
+ }
+ }
+
+ # HTTPS server - proxy to Curio
+ server {
+ listen 443 ssl;
+ server_name YOUR_DOMAIN;
+
+ # Let's Encrypt certificates
+ ssl_session_cache shared:SSL:100m;
+ ssl_certificate /etc/letsencrypt/live/YOUR_DOMAIN/fullchain.pem;
+ ssl_certificate_key /etc/letsencrypt/live/YOUR_DOMAIN/privkey.pem;
+ include /etc/letsencrypt/options-ssl-nginx.conf;
+ ssl_dhparam /etc/letsencrypt/ssl-dhparams.pem;
+
+ # Logging
+ access_log /var/log/nginx/YOUR_DOMAIN.access.log;
+ error_log /var/log/nginx/YOUR_DOMAIN.error.log;
+
+ # Large file upload/download settings
+ client_max_body_size 0;
+ client_body_timeout 600s;
+ send_timeout 600s;
+ proxy_request_buffering off;
+ proxy_buffering off;
+ gzip off;
+
+ # Proxy everything to Curio (HTTP with DelegateTLS)
+ location / {
+ proxy_pass http://YOUR_CURIO_IP:443;
+ proxy_http_version 1.1;
+ proxy_socket_keepalive on;
+ proxy_set_header Connection "";
+ proxy_set_header Host $host;
+ proxy_set_header X-Real-IP $remote_addr;
+ proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
+ proxy_set_header X-Forwarded-Proto $scheme;
+ proxy_connect_timeout 600s;
+ proxy_send_timeout 600s;
+ proxy_read_timeout 600s;
+ }
+ }
+ ```
+
+ Test and reload:
+
+ ```bash
+ sudo nginx -t
+ sudo systemctl reload nginx
+ ```
+
+6. **Configure Curio for DelegateTLS.** On the Curio machine, set `DelegateTLS = true` in the HTTP section of your PDP configuration layer. This tells Curio to serve HTTP on port 443 and let Nginx handle TLS termination. Restart Curio after the change.
+
+7. **Test the setup.**
+
+ ```bash
+ curl -I https://calib.ezpdpz.net
+ openssl s_client -connect calib.ezpdpz.net:443 -servername calib.ezpdpz.net
+ ```
+
+ You should see a response from Curio through Nginx, and a valid certificate chain.
+
+
+
+## Configuration Breakdown
+
+### HTTP Server Block (Port 80)
+
+- `location /.well-known/acme-challenge/` lets Certbot renew certificates.
+- `location /` redirects all other HTTP traffic to HTTPS.
+
+### HTTPS Server Block (Port 443)
+
+- `ssl_session_cache shared:SSL:100m` caches roughly 400K SSL sessions to avoid "could not allocate new session" errors under load.
+- `ssl_certificate` and `ssl_certificate_key` point to the Let's Encrypt certificate and key.
+- `include /etc/letsencrypt/options-ssl-nginx.conf` applies Certbot's SSL settings, and `ssl_dhparam` supplies Diffie-Hellman parameters.
+
+### Large File Settings (Critical for PDP)
+
+- `client_max_body_size 0` removes the upload size limit.
+- `client_body_timeout 600s` and `send_timeout 600s` allow up to 10 minutes for slow uploads and responses.
+- `proxy_request_buffering off` and `proxy_buffering off` stream data straight through, reducing memory use.
+- `gzip off` skips compression of binary PDP data, which does not compress.
+
+### Proxy Settings
+
+- `proxy_pass http://YOUR_CURIO_IP:443` forwards to Curio over HTTP. Curio handles TLS internally only when not delegating, so with DelegateTLS this stays HTTP.
+- `proxy_http_version 1.1` with `proxy_socket_keepalive on` and `proxy_set_header Connection ""` keeps upstream connections alive during large transfers.
+- The `proxy_set_header` directives preserve client information, and the `proxy_*_timeout 600s` values allow long-running transfers.
+
+## Monitoring
+
+```bash
+sudo tail -f /var/log/nginx/calib.ezpdpz.net.access.log
+sudo tail -f /var/log/nginx/calib.ezpdpz.net.error.log
+sudo systemctl status nginx
+```
+
+## Troubleshooting
+
+- **Certificate renewal fails.** Ensure port 80 is reachable from the internet, the `/.well-known/acme-challenge/` location is present in the HTTP block, and `/var/www/html` exists.
+- **Proxy connection fails.** Verify Curio is running on the internal IP, has `DelegateTLS = true`, and is listening on port 443. Check network connectivity between the Nginx and Curio machines.
+- **502 Bad Gateway.** Curio is down or not responding, the `proxy_pass` IP is wrong, or Curio is not in DelegateTLS mode.
+- **Config test fails.** Run `sudo nginx -t` to see the specific syntax error.
+
+## Summary
+
+Nginx handles all SSL complexity, including automatic certificate renewal and centralized certificate management, while Curio focuses on PDP operations. The setup supports unlimited upload sizes, forwards client IPs to the backend, redirects HTTP to HTTPS, and serves multiple domains, one per Curio instance.
diff --git a/docs/src/content/docs/storage-provider-guides/index.mdx b/docs/src/content/docs/storage-provider-guides/index.mdx
new file mode 100644
index 00000000..86544468
--- /dev/null
+++ b/docs/src/content/docs/storage-provider-guides/index.mdx
@@ -0,0 +1,53 @@
+---
+title: Overview
+description: Run a storage provider on Filecoin Onchain Cloud. Deploy a PDP node, register with Warm Storage, and withdraw your earnings.
+sidebar:
+ order: 1
+ label: Overview
+---
+
+import { Card, CardGrid } from "@astrojs/starlight/components";
+
+These guides are for operators who want to **supply storage** to Filecoin Onchain Cloud, rather than build applications on top of it. You run a Proof of Data Possession (PDP) node, register it with the [Filecoin Warm Storage Service](/core-concepts/fwss-overview/) (FWSS), and earn USDFC as clients store data with you.
+
+A storage provider node is a [Curio](https://docs.curiostorage.org/) deployment backed by a [Lotus](https://lotus.filecoin.io/) chain node and a [YugabyteDB](https://docs.yugabyte.com/) database. PDP is the proof mechanism: your node periodically proves to the PDPVerifier contract that it still holds each client's data in a retrievable state. Once registered, your node appears in the [provider directory](https://www.filecoin.services/providers) and the SDK can select it for client uploads.
+
+:::note[Terminology]
+In the Filecoin Onchain Cloud context, *storage provider*, *PDP provider*, and *FWSS provider* refer to the same role: an operator running a PDP-enabled node registered with Warm Storage. These guides use **PDP node** for the running stack and **storage provider** for the operator.
+:::
+
+## Guides
+
+
+
+ Deploy the full Lotus, YugabyteDB, and Curio stack and register with Warm Storage. Covers both Calibration and Mainnet. [Start →](/storage-provider-guides/install-and-run-a-pdp-node/)
+
+
+ Withdraw your available USDFC balance from Filecoin Pay and swap it to FIL, using either filpay-cli or Foundry. [Read →](/storage-provider-guides/withdraw-from-filecoin-pay/)
+
+
+ Production hardening: an Nginx reverse proxy for TLS termination and LXD containers for isolating nodes. [Explore →](/storage-provider-guides/advanced/nginx-reverse-proxy/)
+
+
+
+## Before You Start
+
+You need a Linux host that meets the PDP hardware requirements and a public HTTPS endpoint.
+
+| Resource | Minimum |
+| --- | --- |
+| RAM | 32 GiB |
+| CPU | 8 cores |
+| Fast storage (NVMe/SSD) | 1 TiB |
+| Long-term storage (HDD) | 10 TiB |
+| GPU | Not required |
+| Connectivity | Public HTTPS endpoint (domain) with HTTP/2 |
+
+The install guide is written for **Ubuntu 22.04**. On other distributions, adapt the package installation steps to your platform.
+
+## Get Help
+
+- [#fil-pdp](https://filecoinproject.slack.com/archives/C0717TGU7V2) on [Filecoin Slack](https://filecoin.io/slack) for PDP and FWSS questions
+- [#fil-curio-help](https://filecoinproject.slack.com/archives/C06LF5YP8S3) for Curio
+- [#fil-lotus-help](https://filecoinproject.slack.com/archives/CPFTWMY7N) for Lotus
+- [filecoin.services](https://www.filecoin.services) for the provider directory and Warm Storage overview
diff --git a/docs/src/content/docs/storage-provider-guides/install-and-run-a-pdp-node.mdx b/docs/src/content/docs/storage-provider-guides/install-and-run-a-pdp-node.mdx
new file mode 100644
index 00000000..7e3a178e
--- /dev/null
+++ b/docs/src/content/docs/storage-provider-guides/install-and-run-a-pdp-node.mdx
@@ -0,0 +1,428 @@
+---
+title: Install & Run a PDP Node
+description: Deploy a Filecoin Warm Storage Service stack with Lotus, YugabyteDB, and Curio, then register your PDP node on Calibration or Mainnet.
+sidebar:
+ order: 2
+---
+
+import { Tabs, TabItem, Steps } from '@astrojs/starlight/components';
+
+This guide deploys a full Filecoin Warm Storage Service (FWSS) stack and registers a Proof of Data Possession (PDP) node. By the end you will have:
+
+- Lotus syncing with the Filecoin chain and managing wallets
+- YugabyteDB storing deal and piece metadata
+- Curio coordinating sealing, PDP proofs, and HTTP serving
+- A PDP node registered with the Warm Storage contract and serving clients
+
+The steps are identical for both networks. Where a command or value differs, use the **Calibration** / **Mainnet** switch. Selecting a network once applies your choice to every switch on the page.
+
+:::note[Written for Ubuntu 22.04]
+This guide targets Ubuntu 22.04 and a user with sudo privileges. On other distributions, adapt the package installation commands to your platform.
+:::
+
+## Prerequisites
+
+| Resource | Minimum |
+| --- | --- |
+| RAM | 32 GiB |
+| CPU | 8 cores |
+| Fast storage (NVMe/SSD) | 1 TiB |
+| Long-term storage (HDD) | 10 TiB |
+| GPU | Not required |
+| Connectivity | Public HTTPS endpoint (domain) with HTTP/2 |
+
+## Install System Packages
+
+Prepare the system with the build dependencies for the stack.
+
+```bash
+sudo apt update && sudo apt upgrade -y && sudo apt install -y \
+ mesa-opencl-icd ocl-icd-opencl-dev gcc git jq pkg-config curl clang \
+ build-essential hwloc libhwloc-dev libarchive-dev wget ntp python-is-python3 aria2
+```
+
+### Install Go 1.23.7
+
+```bash
+sudo rm -rf /usr/local/go
+wget https://go.dev/dl/go1.23.7.linux-amd64.tar.gz
+sudo tar -C /usr/local -xzf go1.23.7.linux-amd64.tar.gz
+echo 'export PATH=$PATH:/usr/local/go/bin' >> ~/.bashrc
+source ~/.bashrc
+go version
+```
+
+You should see `go version go1.23.7 linux/amd64`.
+
+### Install Rust
+
+```bash
+curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh
+```
+
+When prompted, choose option `1) Proceed with standard installation` (the default, just press Enter). Then load Rust into your shell:
+
+```bash
+source $HOME/.cargo/env
+rustc --version
+```
+
+You should see a version such as `rustc 1.86.0 (05f9846f8 2025-03-31)`.
+
+### Add Go and Rust to the Secure Sudo Path
+
+```bash
+sudo tee /etc/sudoers.d/dev-paths <
+
+1. **Build the Lotus daemon.** Clone the repository and check out the latest release:
+
+ ```bash
+ git clone https://github.com/filecoin-project/lotus.git
+ cd lotus
+ git checkout $(curl -s https://api.github.com/repos/filecoin-project/lotus/releases/latest | jq -r .tag_name)
+ ```
+
+ Build and install for your network:
+
+
+
+ ```bash
+ make clean && make GOFLAGS="-tags=calibnet" lotus
+ sudo make install-daemon
+ lotus --version
+ ```
+
+ You should see a version such as `lotus version 1.34.1+calibnet`.
+
+
+ ```bash
+ make clean lotus
+ sudo make install-daemon
+ lotus --version
+ ```
+
+ You should see a version such as `lotus version 1.34.1+mainnet+git.710b4ac66`.
+
+
+
+2. **Import a snapshot and start the daemon.** Download a recent chain snapshot:
+
+
+
+ ```bash
+ aria2c -x5 -o snapshot.car.zst https://forest-archive.chainsafe.dev/latest/calibnet/
+ ```
+
+
+ ```bash
+ aria2c -x5 -o snapshot.car.zst https://forest-archive.chainsafe.dev/latest/mainnet/
+ ```
+
+ Mainnet snapshots are larger than Calibration and take longer to import.
+
+
+
+ Import it and start the daemon in the background:
+
+ ```bash
+ lotus daemon --import-snapshot snapshot.car.zst --remove-existing-chain --halt-after-import
+ nohup lotus daemon > ~/lotus.log 2>&1 &
+ ```
+
+ :::tip[Ethereum RPC errors]
+ If you hit errors related to `EnableEthRPC` or `EnableIndexer`, enable both and restart Lotus:
+
+ ```bash
+ sed -i 's/^\( *\)#*EnableEthRPC = .*/\1EnableEthRPC = true/; s/^\( *\)#*EnableIndexer = .*/\1EnableIndexer = true/' ~/.lotus/config.toml
+ ```
+
+ :::
+
+3. **Monitor sync progress.** Wait for the node to sync, then watch the logs:
+
+ ```bash
+ lotus sync wait
+ lotus sync wait --watch
+ tail -f ~/lotus.log
+ ```
+
+
+
+## Run YugabyteDB
+
+Curio uses YugabyteDB to store metadata about deals, sealing operations, and PDP submissions. See the [YugabyteDB quick start](https://docs.yugabyte.com/preview/tutorials/quick-start/linux/) for reference.
+
+
+
+1. **Raise the open-file limit.** YugabyteDB needs a high `ulimit`. Persist the new limits across reboots:
+
+ ```bash
+ echo "$(whoami) soft nofile 1048576" | sudo tee -a /etc/security/limits.conf
+ echo "$(whoami) hard nofile 1048576" | sudo tee -a /etc/security/limits.conf
+ ```
+
+ Apply the limit to the current shell and verify it:
+
+ ```bash
+ ulimit -n 1048576
+ ulimit -n
+ ```
+
+ This should output `1048576`.
+
+2. **Install YugabyteDB.**
+
+ ```bash
+ wget https://software.yugabyte.com/releases/2.25.1.0/yugabyte-2.25.1.0-b381-linux-x86_64.tar.gz
+ tar xvfz yugabyte-2.25.1.0-b381-linux-x86_64.tar.gz
+ cd yugabyte-2.25.1.0
+ ./bin/post_install.sh
+ ```
+
+3. **Start the database.**
+
+ ```bash
+ ./bin/yugabyted start \
+ --advertise_address 127.0.0.1 \
+ --master_flags rpc_bind_addresses=127.0.0.1 \
+ --tserver_flags rpc_bind_addresses=127.0.0.1
+ ```
+
+ :::caution[Locale errors on first start]
+ If you see locale-related errors, generate the UTF-8 locale and retry: `sudo locale-gen en_US.UTF-8`.
+ :::
+
+ Visit `http://127.0.0.1:15433` to confirm the install. The YugabyteDB web UI displays the dashboard when the service is healthy. You can also check the cluster from the CLI:
+
+ ```bash
+ ./bin/yugabyted status
+ ```
+
+
+
+## Install and Configure Curio
+
+Curio is the core PDP client. It coordinates sealing, talks to Lotus, and submits PDP proofs. See the [Curio documentation](https://docs.curiostorage.org/) for reference.
+
+
+
+1. **Increase the UDP buffer size.** Curio needs a larger UDP buffer. Set it now and persist it:
+
+ ```bash
+ sudo sysctl -w net.core.rmem_max=2097152
+ sudo sysctl -w net.core.rmem_default=2097152
+ echo 'net.core.rmem_max=2097152' | sudo tee -a /etc/sysctl.conf
+ echo 'net.core.rmem_default=2097152' | sudo tee -a /etc/sysctl.conf
+ ```
+
+2. **Build Curio.** Clone the repository and switch to the PDP branch:
+
+ ```bash
+ git clone https://github.com/filecoin-project/curio.git
+ cd curio
+ git checkout pdpv0
+ ```
+
+ Build for your network. This step takes a few minutes.
+
+
+
+ ```bash
+ make clean calibnet
+ ```
+
+
+ ```bash
+ make clean build
+ ```
+
+
+
+ Install the compiled binary into `/usr/local/bin` and verify it:
+
+ ```bash
+ sudo make install
+ curio --version
+ ```
+
+ The version string includes your network, for example `curio version 1.27.0+calibnet+git_...` on Calibration or `+mainnet` on Mainnet.
+
+3. **Run the guided setup.** Curio ships an interactive setup utility:
+
+ ```bash
+ curio guided-setup
+ ```
+
+ Work through the prompts:
+
+ 1. **Installation type.** Select **Setup non-Storage Provider cluster**. This is the correct choice for a PDP node: it provisions a Curio cluster without the sealing pipeline of a traditional Filecoin storage provider.
+ 2. **YugabyteDB connection.** With the defaults from this guide, use host `127.0.0.1`, port `5433`, username `yugabyte`, password `yugabyte`, database `yugabyte`. Confirm these with `./bin/yugabyted status` from the YugabyteDB directory. Choose **Continue to connect and update schema** to let Curio create its tables.
+ 3. **Telemetry.** Choose whether to share telemetry with the Curio team, then continue.
+ 4. **Save configuration.** Pick a location for the database configuration file. A common default is `/home/your-username/curio.env`.
+
+4. **Launch the Curio web GUI.**
+
+ ```bash
+ curio run --layers=gui
+ ```
+
+ Open `http://127.0.0.1:4701` to reach the interface.
+
+
+
+## Enable Proof of Data Possession
+
+This section turns on Proof of Data Possession (PDP) for your node and prepares it to serve clients. Keep Curio running with the GUI layer (`curio run --layers=gui`) while you work through the GUI steps.
+
+
+
+1. **Attach storage locations.** Point Curio at your fast (sealing) and long-term (store) paths:
+
+ ```bash
+ curio cli storage attach --init --seal /fast-storage/path
+ curio cli storage attach --init --store /long-term-storage/path
+ ```
+
+ Your fast-storage path should be high-performance media such as NVMe or SSD.
+
+2. **Add a PDP configuration layer.** In the Curio GUI, open the **Configurations** page and create a new layer named `pdp`. Under **Subsystems**, enable:
+
+ - `EnableParkPiece`
+ - `EnablePDP`
+ - `EnableCommP`
+ - `EnableMoveStorage`
+ - `NoUnsealedDecode`
+
+ In the **HTTP** section, set:
+
+ - `Enable`: `true`
+ - `DomainName`: your domain, for example `pdp.mydomain.com`
+ - `ListenAddress`: `0.0.0.0:443`
+
+ :::tip[TLS certificates]
+ Point your domain's A record at your server's public IP so Let's Encrypt can issue a certificate. To terminate TLS with Nginx instead of Curio, see [Nginx Reverse Proxy Setup](/storage-provider-guides/advanced/nginx-reverse-proxy/).
+ :::
+
+3. **Import your wallet.** Create a new delegated FIL wallet, which Curio uses as the PDP owner address:
+
+ ```bash
+ lotus wallet new delegated
+ ```
+
+
+
+ ```bash
+ # Example output:
+ t410fuo4dghaeiqzokiqnxruzdr6e3cjktnxprrc56bi
+ ```
+
+
+ ```bash
+ # Example output:
+ f410fgleqyjv4u3wtsro6tmu3utqz2obrjvqyf7gtffq
+ ```
+
+
+
+ List your wallets at any time with `lotus wallet list`. Export and convert the private key to hex:
+
+ ```bash
+ lotus wallet export | xxd -r -p | jq -r '.PrivateKey' | base64 -d | xxd -p -c 32
+ ```
+
+ In the Curio GUI, open the **PDP** page. In the **Owner Address** section select **Import Key**, paste the hex key into the **Private Key (Hex)** field, and select **Import Key** again. Your `0x` address, the delegated Ethereum address derived from the wallet, is added to the Owner Address section.
+
+ :::danger[Protect your key material]
+ Never expose or store the private key in plain text without protection.
+ :::
+
+ Fund the `0x` wallet so PDP operations are not interrupted:
+
+
+
+ Send **5 tFIL** to your `0x` wallet. Get testnet FIL from the [Calibration faucet](https://docs.filecoin.io/smart-contracts/developing-contracts/get-test-tokens).
+
+
+ Send **10 FIL** to your `0x` wallet. This covers the 5 FIL storage provider creation fee plus headroom for initial operation.
+
+
+
+4. **Restart and verify.** Restart Curio with both layers:
+
+ ```bash
+ curio run --layers=gui,pdp
+ ```
+
+ :::caution[Cannot bind to port 443]
+ If Curio cannot bind to port 443, grant the binary the capability and restart:
+
+ ```bash
+ sudo setcap 'cap_net_bind_service=+ep' /usr/local/bin/curio
+ ```
+
+ :::
+
+ Browse to your PDP node's domain. You should see `Hello, World! -Curio` in the browser.
+
+
+
+## Register with Warm Storage
+
+Register your node with the Filecoin Warm Storage Service so clients can discover and use it. In the Curio GUI, open the **PDP** page and find the **Filecoin Service Registry** section.
+
+
+
+1. **Update details.** Select **Update Details**, enter a **Name** and a short **Description** for your provider node, then select **Update** to submit them to the FWSS contract. You can review other providers' names and descriptions at [filecoin.services/warmstorage](https://www.filecoin.services/warmstorage).
+
+2. **Update the PDP offering.** Select **Update PDP Offering** and set:
+
+ - **Minimum Piece Size (Bytes)**: `1048576`
+ - **Maximum Piece Size (Bytes)**: `1073741824`
+ - **Minimum Proving Period (Epochs)**: `30`
+ - **Location**: your node location in the format `C=US;ST=California;L=San Francisco`
+ - **Storage Price** (per TiB per day in USDFC):
+
+
+ `0.833`
+ `0.0833`
+
+
+ Then add two capabilities:
+
+ - `capacityTib`: your available capacity in TiB
+ - `serviceStatus`: advertises whether the node is in production or testing.
+
+
+ Set `serviceStatus` to `prod`.
+ Set `serviceStatus` to `testing`.
+
+
+ Select **Update PDP** to submit your offering to the FWSS contract.
+
+
+
+## You Are Done
+
+Your PDP-enabled storage provider stack is now:
+
+- Syncing with the Filecoin network via Lotus
+- Recording deal and piece metadata in YugabyteDB
+- Running Curio for sealing and coordination
+- Proving data possession through PDP
+- Registered with the Warm Storage contract
+
+## Next Steps
+
+- [Withdraw funds from Filecoin Pay](/storage-provider-guides/withdraw-from-filecoin-pay/) once clients start paying for storage
+- [Nginx Reverse Proxy Setup](/storage-provider-guides/advanced/nginx-reverse-proxy/) to terminate TLS in front of Curio
+- [LXD Container Setup](/storage-provider-guides/advanced/lxd-container-setup/) to isolate nodes on one host
+- Explore tools and resources at [filecoin.services](https://www.filecoin.services), and join [#fil-pdp](https://filecoinproject.slack.com/archives/C0717TGU7V2) on [Filecoin Slack](https://filecoin.io/slack)
diff --git a/docs/src/content/docs/storage-provider-guides/withdraw-from-filecoin-pay.mdx b/docs/src/content/docs/storage-provider-guides/withdraw-from-filecoin-pay.mdx
new file mode 100644
index 00000000..f3218955
--- /dev/null
+++ b/docs/src/content/docs/storage-provider-guides/withdraw-from-filecoin-pay.mdx
@@ -0,0 +1,221 @@
+---
+title: Withdraw from Filecoin Pay
+description: Withdraw your available USDFC balance from Filecoin Pay and swap it to FIL, using either filpay-cli or Foundry.
+sidebar:
+ order: 3
+---
+
+import { Tabs, TabItem, Steps } from '@astrojs/starlight/components';
+
+As clients store data with you, payments accrue in [Filecoin Pay](/core-concepts/filecoin-pay-overview/). This guide shows how to withdraw your available USDFC balance to your wallet and swap it to FIL.
+
+Two tools can do this. **filpay-cli** is recommended: it is built on the [Synapse SDK](/developer-guides/synapse/) and handles pending nonces correctly even while your PDP nodes are actively sending transactions. **Foundry (`cast`)** works for reference, but has a known nonce bug on Filecoin that causes frequent failures on busy nodes.
+
+:::caution[Only the available balance is withdrawable]
+Your account reports a total balance and an available balance. Only the available amount can be withdrawn. The rest is locked in active payment rails until those rails settle.
+:::
+
+## Contract Addresses (Mainnet)
+
+This guide covers Mainnet withdrawals. The addresses below, the default `rpc.ankr.com/filecoin` endpoint, and the SushiSwap swap all target Mainnet. The same commands work on Calibration with the Calibration contract addresses and an RPC such as `https://api.calibration.node.glif.io/rpc/v1`.
+
+| Contract | Address |
+| --- | --- |
+| Payments | `0x23b1e018F08BB982348b15a86ee926eEBf7F4DAa` |
+| USDFC Token | `0x80B98d3aa09ffff255c3ba4A241111Ff1262F045` |
+
+## Withdraw
+
+
+
+
+### Prerequisites
+
+- [Node.js](https://nodejs.org/en) 18+
+- Your wallet private key
+- Some FIL for gas
+- filpay-cli installed:
+
+ ```bash
+ npm install -g filpay-cli
+ ```
+
+
+
+1. **Check your available balance.** Replace `` with your address:
+
+ ```bash
+ filpay balance --account
+ ```
+
+ For wallet balance and more detail, add `--detailed`:
+
+ ```bash
+ filpay balance --account --detailed
+ ```
+
+ The command returns four values:
+
+ | Field | Description |
+ | --- | --- |
+ | `Current Funds` | Total funds in your Filecoin Pay account |
+ | `Available` | Withdrawable amount, what you can take out now |
+ | `Lockup Rate` | Amount locked in active payment rails |
+ | `Wallet Balance` | USDFC in your wallet (shown with `--detailed`) |
+
+2. **Withdraw funds** to your wallet:
+
+ ```bash
+ filpay withdraw --key
+ ```
+
+ Examples:
+
+ ```bash
+ filpay withdraw 10 --key $PRIVATE_KEY # withdraw 10 USDFC
+ filpay withdraw 0.5 --key $PRIVATE_KEY # withdraw 0.5 USDFC
+ filpay withdraw 10 --to 0xRecipientAddress --key $PRIVATE_KEY # to another address
+ ```
+
+ :::tip[Keep your key out of shell history]
+ Store the key in an environment variable rather than passing it inline:
+
+ ```bash
+ export FILPAY_KEY="your_private_key_here"
+ filpay withdraw 10 --key $FILPAY_KEY
+ ```
+
+ :::
+
+ The default RPC is `https://rpc.ankr.com/filecoin`. Use a different endpoint with `--rpc`:
+
+ ```bash
+ filpay withdraw 10 --key $PRIVATE_KEY --rpc https://api.node.glif.io/rpc/v1
+ ```
+
+
+
+### Manage Payment Rails
+
+filpay-cli also covers wallet and rail operations:
+
+```bash
+filpay wallet-balance --account # wallet USDFC balance
+filpay wallet-balance --key $PRIVATE_KEY # include contract balance
+filpay rails list --key $PRIVATE_KEY # list your payment rails
+filpay rails info --key $PRIVATE_KEY # detail for one rail
+filpay settle 0xPayerAddress --key $PRIVATE_KEY # settle a specific payer
+filpay rails settle-all --key $PRIVATE_KEY --yes # settle all rails
+filpay settlement-preview 0xPayerAddress --key $PRIVATE_KEY # preview before settling
+filpay deposit 100 --key $PRIVATE_KEY # deposit USDFC into Filecoin Pay
+filpay balance --account --json # JSON output for scripting
+```
+
+### Troubleshooting
+
+- **"Insufficient funds".** You are withdrawing more than your available balance. Re-check it with `filpay balance`.
+- **"Nonce too low".** Unlike `cast`, filpay-cli handles nonces correctly even with active PDP nodes. Wait a moment and retry.
+- **Transaction reverts.** Ensure your wallet has enough FIL for gas.
+- **Balance shows 0.** Funds may still be locked in active payment rails. If this looks wrong, contact the FOC team.
+- **"filpay: not found".** Install it globally with `npm install -g filpay-cli`, or run it with `npx filpay-cli balance --account `.
+
+### Resources
+
+- npm package: [filpay-cli](https://www.npmjs.com/package/filpay-cli)
+- Source: [FilOzone/filpay-cli](https://github.com/FilOzone/filpay-cli)
+- Contract: [FilOzone/filecoin-pay](https://github.com/FilOzone/filecoin-pay)
+
+
+
+
+:::caution[Known nonce issue with cast]
+Foundry's `cast send` uses the `latest` nonce instead of `pending`, which causes frequent "nonce too low" errors while your PDP nodes are sending transactions. Prefer the filpay-cli tab, which handles pending nonces reliably. The `cast` commands below remain valid for reference.
+:::
+
+### Prerequisites
+
+- [Foundry](https://book.getfoundry.sh/getting-started/installation) installed (provides `cast`)
+- Your wallet private key, or a hardware wallet (Ledger or Trezor)
+- Some FIL for gas
+
+
+
+1. **Check your available balance.** Replace `` with your address:
+
+ ```bash
+ cast call 0x23b1e018F08BB982348b15a86ee926eEBf7F4DAa \
+ "getAccountInfoIfSettled(address,address)(uint256,uint256,uint256,uint256)" \
+ 0x80B98d3aa09ffff255c3ba4A241111Ff1262F045 \
+ \
+ --rpc-url https://rpc.ankr.com/filecoin
+ ```
+
+ The command returns four values:
+
+ | Position | Field | Description |
+ | --- | --- | --- |
+ | 1 | `fundedUntilEpoch` | Epoch until which your account is funded |
+ | 2 | `currentFunds` | Total funds in your account |
+ | 3 | `availableFunds` | Withdrawable amount (in wei) |
+ | 4 | `currentLockupRate` | Rate locked in active payment rails |
+
+ Only the third value, `availableFunds`, can be withdrawn. USDFC uses 18 decimals, so divide by `10^18` to convert wei to USDFC. To print the available balance directly in USDFC:
+
+ ```bash
+ cast call 0x23b1e018F08BB982348b15a86ee926eEBf7F4DAa \
+ "getAccountInfoIfSettled(address,address)(uint256,uint256,uint256,uint256)" \
+ 0x80B98d3aa09ffff255c3ba4A241111Ff1262F045 \
+ \
+ --rpc-url https://rpc.ankr.com/filecoin | \
+ awk 'NR==3 {printf "Available: %.6f USDFC\n", $1/1e18}'
+ ```
+
+2. **Withdraw funds.** Replace `` with the `availableFunds` value from step 1:
+
+ ```bash
+ cast send 0x23b1e018F08BB982348b15a86ee926eEBf7F4DAa \
+ "withdraw(address,uint256)" \
+ 0x80B98d3aa09ffff255c3ba4A241111Ff1262F045 \
+ \
+ --rpc-url https://rpc.ankr.com/filecoin \
+ --private-key
+ ```
+
+ For example, to withdraw 10 USDFC use `10000000000000000000`, and for 100 USDFC use `100000000000000000000`.
+
+ Using a hardware wallet, swap `--private-key` for `--ledger` or `--trezor`:
+
+ ```bash
+ cast send 0x23b1e018F08BB982348b15a86ee926eEBf7F4DAa \
+ "withdraw(address,uint256)" \
+ 0x80B98d3aa09ffff255c3ba4A241111Ff1262F045 \
+ \
+ --rpc-url https://rpc.ankr.com/filecoin \
+ --ledger
+ ```
+
+
+
+### Quick Reference: USDFC to Wei
+
+| USDFC | Wei |
+| --- | --- |
+| 1 | 1,000,000,000,000,000,000 |
+| 10 | 10,000,000,000,000,000,000 |
+| 100 | 100,000,000,000,000,000,000 |
+| 1,000 | 1,000,000,000,000,000,000,000 |
+
+You can also compute it directly: `cast to-wei 50` returns `50000000000000000000`.
+
+### Troubleshooting
+
+- **"Insufficient funds".** You are withdrawing more than your available balance. Re-check step 1.
+- **Transaction reverts.** Ensure your wallet has enough FIL for gas.
+- **Balance shows 0.** Funds may still be locked in active payment rails. If this looks wrong, contact the FOC team.
+
+
+
+
+## Swap USDFC to FIL
+
+After withdrawing, the USDFC is in your wallet. To swap it to FIL, open [SushiSwap](https://www.sushi.com/filecoin/swap?token0=0x80b98d3aa09ffff255c3ba4a241111ff1262f045&token1=NATIVE), connect your wallet, enter the amount of USDFC to swap, then review and confirm the transaction.