Skip to content
Merged
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
2 changes: 1 addition & 1 deletion docs/api-reference/cli/other-commands.md
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@ Interactive prompts:

Flags:
- `--api-key` — Provide API key non-interactively
- `--environment` — Set environment (sandbox, live)
- `--environment` — Set environment (sandbox, live, staging_sandbox, staging_live)
- `-i, --interactive` — Interactive mode (default: true)

### View Configuration
Expand Down
5 changes: 1 addition & 4 deletions docs/api-reference/cli/plans.md
Original file line number Diff line number Diff line change
Expand Up @@ -242,12 +242,9 @@ The CLI provides helper commands to build price configuration objects:
# Free (no payment required)
nvm plans get-free-price-config

# Fiat price (USD, default)
# Fiat price
nvm plans get-fiat-price-config --amount 1000 --receiver "0x123..."

# Fiat price (EUR)
nvm plans get-fiat-price-config --amount 2900 --receiver "0x123..." --currency EUR

# Crypto price
nvm plans get-crypto-price-config --amount 1000 --receiver "0x123..."

Expand Down
8 changes: 4 additions & 4 deletions docs/api-reference/openclaw-plugin/commands.md
Original file line number Diff line number Diff line change
Expand Up @@ -79,7 +79,7 @@ Most users will not need to call this tool directly — `nevermined_queryAgent`
| `agentId` | string | No | Config `agentId` | The agent ID |
| `paymentType` | string | No | Config `paymentType` or `"crypto"` | `"crypto"` (nvm:erc4337 scheme) or `"fiat"` (nvm:card-delegation scheme) |
| `paymentMethodId` | string | No | Auto-selects first enrolled card | Stripe payment method ID (`pm_...`). Only used for fiat. |
| `spendingLimitCents` | number | No | Config `defaultSpendingLimitCents` or `1000` | Max fiat spend in cents; currency matches your enrolled card (e.g., USD or EUR cents). Only used for fiat. |
| `spendingLimitCents` | number | No | Config `defaultSpendingLimitCents` or `1000` | Max spend in cents. Only used for fiat. |
| `delegationDurationSecs` | number | No | Config `defaultDelegationDurationSecs` or `3600` | Delegation duration in seconds. Only used for fiat. |

**Example prompt:**
Expand Down Expand Up @@ -115,7 +115,7 @@ Use this for plans priced in native tokens (ETH, MATIC) or ERC-20 tokens (USDC).

### Purchase a Fiat Plan — `nevermined_orderFiatPlan`

Purchase a payment plan using fiat currency (USD or EUR). Instead of executing an on-chain transaction, this tool returns a Stripe checkout URL where you complete the payment in a browser. Once the payment is confirmed, credits are added to your account.
Purchase a payment plan using fiat currency (USD). Instead of executing an on-chain transaction, this tool returns a Stripe checkout URL where you complete the payment in a browser. Once the payment is confirmed, credits are added to your account.

Use this for plans that have been created with `pricingType: fiat`. For crypto-priced plans, use `nevermined_orderPlan` instead.

Expand Down Expand Up @@ -238,7 +238,7 @@ After registration, the returned `agentId` and `planId` should be saved in your

Create a standalone payment plan without associating it with an agent. This is useful when you want to manage plans separately from agents, or when a single plan should grant access to multiple agents.

The plan can be priced in three ways: `"fiat"` sets the price in USD or EUR cents (e.g. `"100"` = $1.00 or €1.00) and subscribers pay via Stripe, `"erc20"` sets the price in an ERC-20 token's smallest unit (e.g. `"1000000"` = 1 USDC or 1 EURC) and requires a `tokenAddress`, and `"crypto"` (the default) sets the price in the blockchain's native token.
The plan can be priced in three ways: `"fiat"` sets the price in USD cents (e.g. `"100"` = $1.00) and subscribers pay via Stripe, `"erc20"` sets the price in an ERC-20 token's smallest unit (e.g. `"1000000"` = 1 USDC) and requires a `tokenAddress`, and `"crypto"` (the default) sets the price in the blockchain's native token.

| Parameter | Type | Required | Description |
|-----------|------|----------|-------------|
Expand All @@ -247,7 +247,7 @@ The plan can be priced in three ways: `"fiat"` sets the price in USD or EUR cent
| `priceAmount` | string | **Yes** | Price in cents for fiat (e.g. "100" = $1.00), in token smallest unit for crypto |
| `receiver` | string | **Yes** | Receiver wallet address (0x...) |
| `creditsAmount` | number | **Yes** | Number of credits in the plan |
| `pricingType` | string | No | `"fiat"` for Stripe (USD or EUR), `"erc20"` for ERC20 tokens, `"crypto"` for native token (default) |
| `pricingType` | string | No | `"fiat"` for Stripe/USD, `"erc20"` for ERC20 tokens, `"crypto"` for native token (default) |
| `accessLimit` | string | No | `"credits"` (default) or `"time"` |
| `tokenAddress` | string | No | ERC20 token contract address. Required when pricingType is "erc20". |

Expand Down
34 changes: 31 additions & 3 deletions docs/api-reference/typescript/payment-plans.md
Original file line number Diff line number Diff line change
Expand Up @@ -68,23 +68,51 @@ const priceConfig = payments.plans.getCryptoPriceConfig(

```typescript
// ERC20 token pricing (USDC, DAI, etc.)
const ERC20_ADDRESS = '0x036CbD53842c5426634e7929541eC2318f3dCF7e' // USDC on Base
const USDC_ADDRESS = '0x036CbD53842c5426634e7929541eC2318f3dCF7e' // USDC on Base Sepolia

const priceConfig = payments.plans.getERC20PriceConfig(
20n, // Amount in token's smallest unit
ERC20_ADDRESS, // Token contract address
USDC_ADDRESS, // Token contract address
builderAddress // Receiver address
)
```

### EURC Token Payment (Euro Stablecoin)

```typescript
// EURC pricing (Euro stablecoin on Base, 6 decimals)
const eurcPriceConfig = payments.plans.getEURCPriceConfig(
29_000_000n, // €29.00 in smallest unit (6 decimals)
builderAddress // Receiver address
)

// Or use a custom EURC address (e.g., testnet)
import { EURC_TOKEN_ADDRESS_TESTNET } from '@nevermined-io/payments'

const testnetEurcConfig = payments.plans.getEURCPriceConfig(
29_000_000n,
builderAddress,
EURC_TOKEN_ADDRESS_TESTNET
)
```

### Fiat Payment (Stripe)

```typescript
// Fiat pricing (USD via Stripe)
// Fiat pricing in USD (default)
const priceConfig = payments.plans.getFiatPriceConfig(
1000n, // Amount in cents ($10.00)
builderAddress
)

// Fiat pricing in EUR
import { Currency } from '@nevermined-io/payments'

const eurPriceConfig = payments.plans.getFiatPriceConfig(
2900n, // Amount in euro cents (€29.00)
builderAddress,
Currency.EUR
)
```

### Free Plans
Expand Down
163 changes: 160 additions & 3 deletions docs/api-reference/typescript/x402.md
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,7 @@ For fiat plans using `nvm:card-delegation`, pass `X402TokenOptions` with a `Card
```typescript
import { X402TokenOptions, CardDelegationConfig } from '@nevermined-io/payments'

// USD card delegation
const tokenOptions: X402TokenOptions = {
scheme: 'nvm:card-delegation',
delegationConfig: {
Expand All @@ -90,6 +91,19 @@ const tokenOptions: X402TokenOptions = {
}
}

// EUR card delegation
const eurTokenOptions: X402TokenOptions = {
scheme: 'nvm:card-delegation',
delegationConfig: {
providerPaymentMethodId: 'pm_1AbCdEfGhIjKlM',
spendingLimitCents: 10000, // €100.00 (in euro cents)
durationSecs: 2592000, // 30 days
currency: 'eur',
maxTransactions: 100
}
}

// USD card delegation token
const { accessToken } = await subscriberPayments.x402.getX402AccessToken(
planId,
agentId,
Expand All @@ -98,8 +112,81 @@ const { accessToken } = await subscriberPayments.x402.getX402AccessToken(
undefined, // expiration
tokenOptions
)

// EUR card delegation token
const { accessToken: eurAccessToken } = await subscriberPayments.x402.getX402AccessToken(
planId,
agentId,
undefined,
undefined,
undefined,
eurTokenOptions
)
```

### Reusing Existing Delegations

Instead of creating a new delegation on every token request, you can reuse an existing delegation by passing its `delegationId`. This is useful when running multiple agents that should share a single spending budget:

```typescript
const tokenOptions: X402TokenOptions = {
scheme: 'nvm:card-delegation',
delegationConfig: {
delegationId: 'a1b2c3d4-e5f6-7890-abcd-ef1234567890',
}
}

const { accessToken } = await subscriberPayments.x402.getX402AccessToken(
planId, agentId, undefined, undefined, undefined, tokenOptions
)
```

When `delegationId` is provided, the backend verifies that the delegation is active and that the requesting API key has access, then returns its existing token without creating a new delegation.

### Specifying a Card

You can target a specific enrolled card using `cardId`. The backend will look for an active delegation on that card or create a new one:

```typescript
const tokenOptions: X402TokenOptions = {
scheme: 'nvm:card-delegation',
delegationConfig: {
cardId: 'a1b2c3d4-e5f6-7890-abcd-ef1234567890',
spendingLimitCents: 5000, // Only used if a new delegation is created
durationSecs: 604800,
}
}
```

### Auto-Selection

When neither `cardId` nor `delegationId` is specified (and `providerPaymentMethodId` is omitted), the backend automatically selects the best card and delegation for the requesting API key. It finds cards accessible to the API key, looks for active delegations with remaining budget, and reuses one if available — otherwise creates a new delegation:

```typescript
const tokenOptions: X402TokenOptions = {
scheme: 'nvm:card-delegation',
delegationConfig: {
spendingLimitCents: 10000,
durationSecs: 2592000,
}
}
```

### CardDelegationConfig Reference

| Field | Type | Required | Description |
|-------|------|----------|-------------|
| `delegationId` | `string` | No | Existing delegation UUID to reuse |
| `cardId` | `string` | No | Payment method UUID to target |
| `providerPaymentMethodId` | `string` | No | Stripe payment method ID (e.g., `pm_...`) |
| `spendingLimitCents` | `number` | No* | Max spending in cents (for new delegations) |
| `durationSecs` | `number` | No* | Duration in seconds (for new delegations) |
| `currency` | `string` | No | Currency code, defaults to `usd` |
| `merchantAccountId` | `string` | No | Stripe Connect account ID |
| `maxTransactions` | `number` | No | Max transactions allowed |

\* Required when creating a new delegation. Ignored when reusing an existing one via `delegationId`.

### Auto Scheme Resolution

Use `resolveScheme()` to auto-detect the correct scheme from plan metadata:
Expand All @@ -117,21 +204,87 @@ const scheme = await resolveScheme(payments, planId, 'nvm:card-delegation')

### DelegationAPI

List enrolled payment methods for card delegation:
Manage payment methods and delegations for card delegation:

```typescript
import { DelegationAPI, PaymentMethodSummary } from '@nevermined-io/payments'

const delegationApi = DelegationAPI.getInstance(payments.options)

// List enrolled payment methods
const methods: PaymentMethodSummary[] = await delegationApi.listPaymentMethods()

for (const method of methods) {
console.log(`${method.brand} ****${method.last4} (expires ${method.expMonth}/${method.expYear})`)
// e.g., "visa ****4242 (expires 12/2027)"
if (method.allowedApiKeyIds) {
console.log(` Restricted to API keys: ${method.allowedApiKeyIds.join(', ')}`)
}
}
```

#### Update Payment Method

Restrict a card to specific NVM API Keys so only designated agents can use it:

```typescript
await delegationApi.updatePaymentMethod('pm-uuid-here', {
alias: 'Production Card',
allowedApiKeyIds: ['sk-agent-1', 'sk-agent-2'], // Only these API keys can use this card
})

// Remove restrictions (any API key can use the card)
await delegationApi.updatePaymentMethod('pm-uuid-here', {
allowedApiKeyIds: null,
})
```

#### List Delegations

Retrieve all delegations for the authenticated user:

```typescript
const { delegations, totalResults } = await delegationApi.listDelegations()

console.log(`Total delegations: ${totalResults}`)
for (const d of delegations) {
console.log(`${d.delegationId} - ${d.status} (${d.remainingBudgetCents} cents remaining)`)
if (d.apiKeyId) {
console.log(` Restricted to API key: ${d.apiKeyId}`)
}
}
```

`PaymentMethodSummary` fields:
#### DelegationListResponse Fields

`listDelegations()` resolves to a `DelegationListResponse` object:

| Field | Type | Description |
|-------|------|-------------|
| `delegations` | `DelegationSummary[]` | Array of delegation records |
| `totalResults` | `number` | Total number of delegations |
| `page` | `number` | Current page number |
| `offset` | `number` | Offset into the full result set |

#### DelegationSummary Fields

Each item in the `delegations` array is a `DelegationSummary`:

| Field | Type | Description |
|-------|------|-------------|
| `delegationId` | `string` | Unique delegation identifier |
| `provider` | `string` | Payment provider (e.g., `stripe`) |
| `providerPaymentMethodId` | `string` | Provider-side payment method ID |
| `status` | `string` | Delegation status (`Active`, `Revoked`, etc.) |
| `spendingLimitCents` | `string` | Maximum spendable amount in cents |
| `amountSpentCents` | `string` | Amount already spent in cents |
| `remainingBudgetCents` | `string` | Remaining budget in cents |
| `currency` | `string` | Currency code (e.g., `usd`) |
| `transactionCount` | `number` | Number of transactions made |
| `expiresAt` | `string` | Expiry timestamp (ISO 8601) |
| `createdAt` | `string` | Creation timestamp (ISO 8601) |
| `apiKeyId` | `string \| null` | API key this delegation is restricted to, or `null` if unrestricted |

#### PaymentMethodSummary Fields

| Field | Type | Description |
|-------|------|-------------|
Expand All @@ -140,6 +293,7 @@ for (const method of methods) {
| `last4` | `string` | Last 4 digits of the card number |
| `expMonth` | `number` | Card expiration month |
| `expYear` | `number` | Card expiration year |
| `allowedApiKeyIds` | `string[] \| null` | API keys allowed to use this card (`null` = unrestricted) |

## X402 Access Token Structure

Expand Down Expand Up @@ -564,6 +718,8 @@ When `scheme` is set to `'nvm:card-delegation'`, the `network` is automatically
5. **Log Transactions**: Record settlement transaction hashes
6. **Handle Errors**: Provide clear error messages in 402 responses
7. **Token Reuse**: Subscribers can reuse tokens for multiple requests
8. **Restrict Cards to API Keys**: When running multiple agents, restrict each card to specific NVM API Keys using `allowedApiKeyIds` to prevent unauthorized spending
9. **Reuse Delegations**: Pass `delegationId` to reuse existing delegations instead of creating new ones on each request — this avoids delegation sprawl and keeps spending consolidated

## Related Documentation

Expand All @@ -576,5 +732,6 @@ When `scheme` is set to `'nvm:card-delegation'`, the `network` is automatically

**Source References**:
- `src/x402/token.ts` (getX402AccessToken)
- `src/x402/delegation-api.ts` (DelegationAPI: listPaymentMethods, listDelegations, updatePaymentMethod)
- `src/x402/facilitator-api.ts` (verifyPermissions, settlePermissions, buildPaymentRequired)
- `tests/e2e/test_x402_e2e.test.ts` (complete X402 flow)