Skip to main content
Make sure you have created a Privy application and recorded the APP_ID and APP_SECRET from the Privy dashboard.

1. Install dependencies

npm install @privy-io/node x402-fetch dotenv

2. Configure the Privy client

import { PrivyClient, APIError, PrivyAPIError } from '@privy-io/node';
import { wrapFetchWithPayment } from 'x402-fetch';
import dotenv from 'dotenv';

dotenv.config();

const privy = new PrivyClient({
  appId: process.env.PRIVY_APP_ID!,
  appSecret: process.env.PRIVY_APP_SECRET!,
});

3. Create or reuse an embedded wallet

let walletId: string;

try {
  const wallet = await privy.wallets().create({ chain_type: 'ethereum' });
  walletId = wallet.id;
} catch (error) {
  if (error instanceof APIError || error instanceof PrivyAPIError) {
    console.error(error.status, error.name, error.message);
    throw error;
  }
  throw error;
}
Fund the wallet on the network you plan to use for x402 (Base Sepolia in this example).

4. Wrap fetch with Privy’s signing helpers

const fetchWithPayment = wrapFetchWithPayment(fetch, {
  async signMessage(message: string) {
    const signed = await privy.wallets().ethereum().signMessage(walletId, { message });
    return signed.signature;
  },
});
The wrapper expects a signer object; we forward messages to Privy’s signMessage endpoint.

5. Trigger a Horizon job

const baseUrl = process.env.HORIZON_BASE_URL ?? 'https://api.horizon.new/v1';

const response = await fetchWithPayment(`${baseUrl}/generate/images`, {
  method: 'POST',
  headers: { 'Content-Type': 'application/json' },
  body: JSON.stringify({
    prompt: 'Render a neon skyline with floating gardens.',
    metadata: { campaign: 'launch-creative' },
  }),
});

const job = await response.json();
console.log(job.jobId, job.result?.artifacts);

6. Poll status or subscribe to webhooks

const status = await fetchWithPayment(`${baseUrl}/jobs/${job.jobId}`).then((res) => res.json());

if (status.state === 'succeeded') {
  console.log('Artifacts:', status.result.artifacts);
}

Common troubleshooting

  • APIError — Ensure the Privy SDK can reach the API and that your credentials are valid.
  • 402 loops — Confirm the wallet is funded and that the initial request body is replayed exactly; x402 rejects mutated payloads.
  • Chain mismatch — When targeting production, swap Base Sepolia for the correct caip2 network ID before settlement.