Skip to content

Client Setup

This guide walks through creating an x402 client that authenticates and pays for requests routed through the Consensus network. The same four-step pattern works across ICP, SVM, and EVM — only the signer setup and scheme registration differ.

By the end you will have a working client that automatically handles the x402 payment flow: your code makes a normal fetch call and the client handles the 402 → sign → retry cycle transparently.

  • Node.js v18 or later
  • npm
  • A funded signing identity

To create an x402 client, first generate a network-compatible signer object, then ensure the associated account is funded.

Your ICP signing identity is generated from a PEM file. You can obtain this file from dfx, icp-cli, or by exporting from compatible wallets such as Plug Wallet. All three methods produce the same PEM artifact.


Using dfx or icp-cli

icp-cli is the command-line tool for ICP development and identity management. It serves as the replacement for dfx.

Export an existing dfx identity:

Terminal window
dfx identity export <identity-name> > identity.pem

Alternatively, using icp-cli:

Terminal window
icp identity export <identity-name> > identity.pem

Using Plug Wallet

Plug Wallet stores your ICP identity in the browser extension. To use it in server-side applications, export the private key as a PEM file.

Export steps:

  1. Click the menu icon (···) in the top-right corner
  2. Select Settings from the dropdown menu
  3. Click Export PEM to download your private key
Plug Wallet main interface
Figure 1: Plug Wallet home screen
Plug Wallet menu expanded showing Settings option
Figure 2: Access Settings from the menu (···)
Settings page with Export PEM button highlighted
Figure 3: Export PEM option in Settings

Terminal window
npm install @canister-software/x402-icp @x402/core @x402/fetch dotenv
PackagePurpose
@canister-software/x402-icpICP-specific signer and scheme registration
@x402/coreCore x402 client
@x402/fetchPayment-aware fetch wrapper
dotenvLoad environment variables from .env

Create a .env file in your project root:

Terminal window
PEM_PATH=./identity.pem
RESOURCE_SERVER_URL=https://facilitator.canister.software
ENDPOINT=/api/data
VariableDescription
PEM_PATHPath to your ICP identity PEM file
RESOURCE_SERVER_URLThe Consensus facilitator URL that routes your request
ENDPOINTThe API route you want to call through the network

Create a file client.js and add each step below.

import dotenv from 'dotenv'
dotenv.config()
import { pemToSigner } from '@canister-software/x402-icp/client'
const PEM_PATH = process.env.PEM_PATH || './identity.pem'
const signer = await pemToSigner(PEM_PATH)
console.log(`Signer principal: ${signer.principal}`)

pemToSigner reads your PEM file and returns a signer object. The principal logged on startup confirms the correct identity is loaded.

Create the x402 client and register the scheme

Section titled “Create the x402 client and register the scheme”
import { x402Client } from '@x402/core/client'
import { registerExactIcpScheme } from '@canister-software/x402-icp/client'
const client = new x402Client()
registerExactIcpScheme(client, { signer })

registerExactIcpScheme teaches the client how to respond to ICP-based payment challenges. When the server returns a 402 Payment Required, the client constructs and signs a valid ICP payment authorisation using your identity.

This step is identical for all networks.

import { wrapFetchWithPayment } from '@x402/fetch'
const fetchWithPayment = wrapFetchWithPayment(fetch, client)

wrapFetchWithPayment returns a drop-in replacement for the standard fetch function. Swap fetch for fetchWithPayment anywhere in your code and the x402 payment flow is handled transparently — no manual payment logic required.

This step is identical for all networks.

const SERVER_URL = process.env.RESOURCE_SERVER_URL || 'https://facilitator.canister.software'
const ENDPOINT = process.env.ENDPOINT || '/api/data'
const response = await fetchWithPayment(`${SERVER_URL}${ENDPOINT}`)
const body = await response.json()
console.log(JSON.stringify(body, null, 2))

When fetchWithPayment receives a 402 Payment Required it automatically:

  1. Reads the payment details from the response
  2. Signs the payment authorisation using your signer
  3. Retries the original request with the payment attached
  4. Returns the final response to your code

Your application sees only the successful response — the payment round-trip is invisible.

import dotenv from 'dotenv'
dotenv.config()
import { x402Client } from '@x402/core/client'
import { wrapFetchWithPayment } from '@x402/fetch'
import { registerExactIcpScheme, pemToSigner } from '@canister-software/x402-icp/client'
const PEM_PATH = process.env.PEM_PATH || './identity.pem'
const SERVER_URL = process.env.RESOURCE_SERVER_URL || 'https://facilitator.canister.software'
const ENDPOINT = process.env.ENDPOINT || '/api/data'
async function main() {
// 1. Create signer from PEM
const signer = await pemToSigner(PEM_PATH)
console.log(`Signer principal: ${signer.principal}`)
// 2. Create x402 client and register ICP scheme
const client = new x402Client()
registerExactIcpScheme(client, { signer })
// 3. Wrap fetch with payment handling
const fetchWithPayment = wrapFetchWithPayment(fetch, client)
// 4. Make the request — x402 handles 402 → sign → retry automatically
const response = await fetchWithPayment(`${SERVER_URL}${ENDPOINT}`)
const body = await response.json()
console.log(JSON.stringify(body, null, 2))
}
main()
Terminal window
node client.js
Signer principal: xxxxx-xxxxx-xxxxx-xxxxx-cai
{
// response from the endpoint
}