Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
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
56 changes: 56 additions & 0 deletions .github/workflows/publish_npm_scoped_x402_openpayments.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
name: Publish @x402/openpayments package to NPM

on:
workflow_dispatch:

jobs:
publish-npm-x402-openpayments:
runs-on: ubuntu-latest
environment: ${{ github.ref == 'refs/heads/main' && 'npm' || '' }}
permissions:
contents: read
id-token: write
steps:
- uses: actions/checkout@v4

- name: Setup pnpm
uses: pnpm/action-setup@41ff72655975bd51cab0327fa583b6e92b6d3061
with:
version: 10.7.0

- uses: actions/setup-node@v4
with:
node-version: "24"
registry-url: "https://registry.npmjs.org"
cache: "pnpm"
cache-dependency-path: ./typescript

- name: Update npm for OIDC trusted publishing
run: npm install -g npm@latest

- name: Configure npm for trusted publishing
run: npm config delete always-auth 2>/dev/null || true

- name: Install and build
working-directory: ./typescript
run: |
pnpm install --frozen-lockfile
pnpm -r --filter=@x402/core --filter=@x402/openpayments run build

- name: Publish @x402/openpayments package
working-directory: ./typescript/packages/mechanisms/openpayments
run: |
# Get package information directly
PACKAGE_NAME=$(node -p "require('./package.json').name")
PACKAGE_VERSION=$(node -p "require('./package.json').version")

echo "Package: $PACKAGE_NAME@$PACKAGE_VERSION"

# Check if running on main branch
if [[ "${{ github.ref }}" == "refs/heads/main" ]]; then
echo "Publishing to NPM (main branch)"
pnpm publish --provenance --access public
else
echo "Dry run only (non-main branch: ${{ github.ref }})"
pnpm publish --dry-run --no-git-checks
fi
6 changes: 6 additions & 0 deletions examples/typescript/clients/advanced/.env-local
Original file line number Diff line number Diff line change
@@ -1,4 +1,10 @@
EVM_PRIVATE_KEY=
# ILP Open Payments (all required if using ILP)
ILP_CLIENT_WALLET_ADDRESS=
ILP_KEY_ID=
ILP_PRIVATE_KEY=
ILP_GRANT_TOKEN=
ILP_GRANT_TOKEN_MANAGE_URL= # optional: management URL for grant token rotation on 401
SVM_PRIVATE_KEY=
STELLAR_PRIVATE_KEY=
RESOURCE_SERVER_URL=http://localhost:4021
Expand Down
18 changes: 16 additions & 2 deletions examples/typescript/clients/advanced/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ const response = await fetchWithPayment("http://localhost:4021/weather");

- Node.js v20+ (install via [nvm](https://github.com/nvm-sh/nvm))
- pnpm v10 (install via [pnpm.io/installation](https://pnpm.io/installation))
- Valid EVM, SVM, and/or Stellar private keys for making payments
- Valid EVM, ILP Open Payments, SVM, and/or Stellar private keys for making payments (each network is optional)
- A running x402 server (see [server examples](../../servers/))
- Familiarity with the [basic fetch client](../fetch/)

Expand All @@ -41,6 +41,12 @@ and fill required environment variables:
- `EVM_PRIVATE_KEY` - Ethereum private key for EVM payments
- `SVM_PRIVATE_KEY` - Solana private key for SVM payments
- `STELLAR_PRIVATE_KEY` - Stellar secret key (starts with `S`) for signing Stellar payments
- **Open Payments (ILP)** — set all of the following to enable `ilp:*`:
- `ILP_CLIENT_WALLET_ADDRESS` — client Open Payments wallet address URL
- `ILP_KEY_ID` — key id (`kid`) for the client’s key
- `ILP_PRIVATE_KEY` — base64-encoded Ed25519 private key
- `ILP_GRANT_TOKEN` — pre-approved grant access token for outgoing payments
- `ILP_GRANT_TOKEN_MANAGE_URL` — (optional) management URL for grant token rotation on 401

2. Install and build all packages from the typescript examples root:

Expand All @@ -66,6 +72,12 @@ Stellar accounts need to be created and funded with both XLM and USDC. Instructi
2. Add USDC trustline (required to transact USDC): go to [Fund Account](https://lab.stellar.org/account/fund) ➡️ Paste your `Public Key` ➡️ Add USDC Trustline ➡️ paste your `Secret key` ➡️ Sign transaction ➡️ Add Trustline.
3. Get testnet USDC from [Circle Faucet](https://faucet.circle.com/) (select Stellar network).

#### Open Payments (ILP)

The client uses `@x402/openpayments` with network pattern `ilp:*` and `ilp:openpayments` requirements from the server. You need a funded Open Payments wallet, a registered signing key, and an **outgoing-payment grant** issued by your auth server.

For testing, create a free account at [Interledger Test Wallet](https://wallet.interledger-test.dev/).

## Available Examples

Each example demonstrates a specific advanced pattern:
Expand Down Expand Up @@ -178,7 +190,7 @@ import { ExactSvmScheme } from "@x402/svm/exact/client";
import { ExactStellarScheme } from "@x402/stellar/exact/client";

// Define network preference order (most preferred first)
const networkPreferences = ["eip155:", "solana:", "stellar:"];
const networkPreferences = ["eip155:", "ilp:", "solana:", "stellar:"];

const preferredNetworkSelector = (
_x402Version: number,
Expand All @@ -202,6 +214,8 @@ const fetchWithPayment = wrapFetchWithPayment(fetch, client);
const response = await fetchWithPayment("http://localhost:4021/weather");
```

Include `ilp:` in `networkPreferences` when using the all-networks Open Payments setup.

**Use case:**

- Prefer payments on specific chains
Expand Down
28 changes: 25 additions & 3 deletions examples/typescript/clients/advanced/all_networks.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,13 +5,14 @@
* optional chain configuration via environment variables.
*
* New chain support should be added here in alphabetic order by network prefix
* (e.g., "eip155" before "solana" before "stellar").
* (e.g., "eip155" before "ilp" before "solana" before "stellar").
*/

import { config } from "dotenv";
import { x402Client, wrapFetchWithPayment, x402HTTPClient } from "@x402/fetch";
import { ExactEvmScheme } from "@x402/evm/exact/client";
import { UptoEvmScheme } from "@x402/evm/upto/client";
import { ExactOpenPaymentsScheme } from "@x402/openpayments/exact/client";
import { ExactSvmScheme } from "@x402/svm/exact/client";
import { ExactStellarScheme } from "@x402/stellar/exact/client";
import { createEd25519Signer } from "@x402/stellar";
Expand All @@ -23,6 +24,11 @@ config();

// Configuration - optional per network
const evmPrivateKey = process.env.EVM_PRIVATE_KEY as `0x${string}` | undefined;
const ilpClientWalletAddress = process.env.ILP_CLIENT_WALLET_ADDRESS as string | undefined;
const ilpKeyId = process.env.ILP_KEY_ID as string | undefined;
const ilpPrivateKey = process.env.ILP_PRIVATE_KEY as string | undefined; // base64-encoded Ed25519
const ilpGrantToken = process.env.ILP_GRANT_TOKEN as string | undefined;
const ilpGrantTokenManageUrl = process.env.ILP_GRANT_TOKEN_MANAGE_URL as string | undefined; // optional: enables token rotation on 401
const svmPrivateKey = process.env.SVM_PRIVATE_KEY as string | undefined;
const stellarPrivateKey = process.env.STELLAR_PRIVATE_KEY as string | undefined;
const baseURL = process.env.RESOURCE_SERVER_URL || "http://localhost:4021";
Expand All @@ -35,9 +41,10 @@ const url = `${baseURL}${endpointPath}`;
*/
async function main(): Promise<void> {
// Validate at least one private key is provided
if (!evmPrivateKey && !svmPrivateKey && !stellarPrivateKey) {
const hasIlpConfig = ilpClientWalletAddress && ilpKeyId && ilpPrivateKey && ilpGrantToken;
if (!evmPrivateKey && !hasIlpConfig && !svmPrivateKey && !stellarPrivateKey) {
console.error(
"❌ At least one of EVM_PRIVATE_KEY, SVM_PRIVATE_KEY, or STELLAR_PRIVATE_KEY is required",
"❌ At least one of EVM_PRIVATE_KEY, ILP ENVs, SVM_PRIVATE_KEY, or STELLAR_PRIVATE_KEY is required",
);
process.exit(1);
}
Expand All @@ -53,6 +60,21 @@ async function main(): Promise<void> {
console.log(`Initialized EVM account: ${evmSigner.address}`);
}

// Register ILP Open Payments scheme if credentials are provided
if (hasIlpConfig) {
client.register(
"ilp:*",
new ExactOpenPaymentsScheme({
clientWalletAddress: ilpClientWalletAddress!,
keyId: ilpKeyId!,
privateKey: ilpPrivateKey!,
grantToken: ilpGrantToken!,
grantTokenManageUrl: ilpGrantTokenManageUrl,
}),
);
console.log(`Initialized ILP account: ${ilpClientWalletAddress}`);
}

// Register SVM scheme if private key is provided
if (svmPrivateKey) {
const svmSigner = await createKeyPairSignerFromBytes(base58.decode(svmPrivateKey));
Expand Down
1 change: 1 addition & 0 deletions examples/typescript/clients/advanced/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
"@x402/evm": "workspace:*",
"@x402/svm": "workspace:*",
"@x402/stellar": "workspace:*",
"@x402/openpayments": "workspace:*",
"@x402/fetch": "workspace:*",
"dotenv": "^16.4.7",
"viem": "^2.39.0",
Expand Down
5 changes: 4 additions & 1 deletion examples/typescript/facilitator/.env-local
Original file line number Diff line number Diff line change
@@ -1,4 +1,7 @@
PORT=
EVM_PRIVATE_KEY=
STELLAR_PRIVATE_KEY=
SVM_PRIVATE_KEY=
SVM_PRIVATE_KEY=
ILP_KEY_ID=
ILP_PRIVATE_KEY=
ILP_WALLET_ADDRESS=
8 changes: 8 additions & 0 deletions examples/typescript/facilitator/advanced/.env-local
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
EVM_PRIVATE_KEY=
# ILP Open Payments (all required if using ILP)
ILP_KEY_ID=
ILP_PRIVATE_KEY=
ILP_WALLET_ADDRESS=
SVM_PRIVATE_KEY=
STELLAR_PRIVATE_KEY=
PORT=4022
3 changes: 3 additions & 0 deletions examples/typescript/facilitator/advanced/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ Express.js facilitator service demonstrating advanced x402 patterns including al
- EVM private key with Base Sepolia ETH for transaction fees
- SVM private key with Solana Devnet SOL for transaction fees
- Stellar private key with testnet XLM for transaction fees (fund via [Stellar Laboratory](https://lab.stellar.org/account/create) ➡️ Generate keypair ➡️ Fund account with Friendbot)
- Open Payments credentials (`ILP_KEY_ID`, `ILP_PRIVATE_KEY`, `ILP_WALLET_ADDRESS`) — create a free wallet at [Interledger Test Wallet](https://wallet.interledger-test.dev/) and register an Ed25519 signing key

## Setup

Expand All @@ -23,6 +24,7 @@ and fill required environment variables:
- `EVM_PRIVATE_KEY` - Ethereum private key
- `SVM_PRIVATE_KEY` - Solana private key
- `STELLAR_PRIVATE_KEY` - Stellar secret key (starts with `S`)
- **Open Payments** (all required together): `ILP_KEY_ID`, `ILP_PRIVATE_KEY` (base64), `ILP_WALLET_ADDRESS` (facilitator wallet address URL)
- `PORT` - Server port (optional, defaults to 4022)

2. Install and build all packages from the typescript examples root:
Expand Down Expand Up @@ -242,3 +244,4 @@ Networks use [CAIP-2](https://github.com/ChainAgnostic/CAIPs/blob/main/CAIPs/cai
- `solana:5eykt4UsFv8P8NJdTREpY1vzqKqZKvdp` — Solana Mainnet
- `stellar:testnet` — Stellar Testnet
- `stellar:pubnet` — Stellar Mainnet
- `ilp:openpayments` — ILP Open Payments
25 changes: 22 additions & 3 deletions examples/typescript/facilitator/advanced/all_networks.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
* optional chain configuration via environment variables.
*
* New chain support should be added here in alphabetic order by network prefix
* (e.g., "eip155" before "solana" before "stellar").
* (e.g., "eip155" before "ilp" before "solana" before "stellar").
*/

import { base58 } from "@scure/base";
Expand All @@ -19,6 +19,7 @@ import {
} from "@x402/core/types";
import { toFacilitatorEvmSigner } from "@x402/evm";
import { ExactEvmScheme } from "@x402/evm/exact/facilitator";
import { ExactOpenPaymentsScheme } from "@x402/openpayments/exact/facilitator";
import { toFacilitatorSvmSigner } from "@x402/svm";
import { ExactSvmScheme } from "@x402/svm/exact/facilitator";
import { createEd25519Signer } from "@x402/stellar";
Expand All @@ -36,19 +37,24 @@ const PORT = process.env.PORT || "4022";

// Configuration - optional per network
const evmPrivateKey = process.env.EVM_PRIVATE_KEY as `0x${string}` | undefined;
const ilpKeyId = process.env.ILP_KEY_ID as string | undefined;
const ilpPrivateKey = process.env.ILP_PRIVATE_KEY as string | undefined; // base64-encoded Ed25519
const ilpWalletAddress = process.env.ILP_WALLET_ADDRESS as string | undefined;
const svmPrivateKey = process.env.SVM_PRIVATE_KEY as string | undefined;
const stellarPrivateKey = process.env.STELLAR_PRIVATE_KEY as string | undefined;

// Validate at least one private key is provided
if (!evmPrivateKey && !svmPrivateKey && !stellarPrivateKey) {
const hasIlpConfig = ilpKeyId && ilpPrivateKey && ilpWalletAddress;
if (!evmPrivateKey && !hasIlpConfig && !svmPrivateKey && !stellarPrivateKey) {
console.error(
"❌ At least one of EVM_PRIVATE_KEY, SVM_PRIVATE_KEY, or STELLAR_PRIVATE_KEY is required",
"❌ At least one of EVM_PRIVATE_KEY, ILP ENVs, SVM_PRIVATE_KEY, or STELLAR_PRIVATE_KEY is required",
);
process.exit(1);
}

// Network configuration
const EVM_NETWORK = "eip155:84532"; // Base Sepolia
const ILP_NETWORK = "ilp:openpayments"; // Open Payments (Interledger)
const SVM_NETWORK = "solana:EtWTRABZaYq6iMfeYKouRu166VU2xqa1"; // Solana Devnet
const STELLAR_NETWORK = "stellar:testnet"; // Stellar Testnet

Expand Down Expand Up @@ -129,6 +135,19 @@ if (evmPrivateKey) {
);
}

// Register ILP Open Payments scheme if credentials are provided
if (hasIlpConfig) {
console.info(`ILP Facilitator wallet: ${ilpWalletAddress}`);
facilitator.register(
ILP_NETWORK,
new ExactOpenPaymentsScheme({
keyId: ilpKeyId!,
privateKey: ilpPrivateKey!,
walletAddress: ilpWalletAddress!,
}),
);
}

// Register SVM scheme if private key is provided
if (svmPrivateKey) {
const svmAccount = await createKeyPairSignerFromBytes(
Expand Down
1 change: 1 addition & 0 deletions examples/typescript/facilitator/advanced/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@
"@x402/extensions": "workspace:*",
"@x402/svm": "workspace:*",
"@x402/stellar": "workspace:*",
"@x402/openpayments": "workspace:*",
"dotenv": "^16.4.5",
"express": "^4.19.2",
"viem": "^2.21.54"
Expand Down
Loading
Loading