# Payment clients and discovery

[WURK skill](https://wurkapi.fun/skill.md) · [Commission work](commissioning.md) · [Recovery and support](recovery.md)

Read this guide when configuring a wallet payment client or discovering existing social services. Free account access and worker APIs use [account authentication](account.md#authentication-is-separate-from-payment). Funding a wallet is unnecessary for those free operations.

| Rail | Routes | Challenge | Paid retry | Receipt |
| --- | --- | --- | --- | --- |
| Solana/Base x402 v2 | `/solana/*`, `/base/*` | HTTP 402 with nonempty `accepts` and `PAYMENT-REQUIRED` | `PAYMENT-SIGNATURE` | `PAYMENT-RESPONSE` |

An x402 402 with empty `accepts` requests free SIWX authentication. It is not a payable quote. `X-PAYMENT` is not the x402 v2 payment header. Use the exact quoted asset, network, amount, recipient and metadata.

## Solana x402 client

The [first creator job](commissioning.md#first-job-human-onboarding-feedback) can use [checkout without registration](#checkout-without-registration). Basic keeps its payment-and-secret flow. Advanced USDC, Contest, store purchase and hire use a private checkout token for safe retries and status; account login is not part of payment. A generic automatic payment wrapper does not replace persisting the original payment or checking its outcome.
Use these compatible SDK versions for this example:

```bash
npm install @x402/core@2.28.0 @x402/svm@2.28.0 @solana/kit@6.10.0
```

Use an existing `@solana/kit` `TransactionSigner`. For a headless wallet, `createKeyPairSignerFromBytes(secretKeyBytes)` accepts decoded 64-byte secret-key bytes. Decode the wallet's actual storage format privately; do not pass a raw `@solana/web3.js` `Keypair` to `registerExactSvmScheme`.

```typescript
import { x402Client } from '@x402/core/client';
import { decodePaymentRequiredHeader, encodePaymentSignatureHeader } from '@x402/core/http';
import type { PaymentRequired } from '@x402/core/types';
import { registerExactSvmScheme } from '@x402/svm/exact/client';
import { toClientSvmSigner } from '@x402/svm';
import type { TransactionSigner } from '@solana/kit';

const solanaNetwork = 'solana:5eykt4UsFv8P8NJdTREpY1vzqKqZKvdp';
const solanaUsdc = 'EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v';

// Shared by Basic and Advanced: sign the complete approved requirements.
async function signSolanaQuote(signer: TransactionSigner, quote: PaymentRequired) {
  // SDK 2.28 defaults to a $1 cap. Bound this signer to the quote approved above.
  const client = new x402Client().setSpendControls({ allowedAssets: [{
    network: solanaNetwork, asset: solanaUsdc,
    maxAmountPerPayment: quote.accepts[0].amount,
  }] });
  registerExactSvmScheme(client, {
    signer: toClientSvmSigner(signer), networks: [solanaNetwork],
  });
  return encodePaymentSignatureHeader(await client.createPaymentPayload(quote));
}

export async function prepareBasicX402(
  signer: TransactionSigner,
  input: { description: string; winners: number; perUser: string },
  authorize: (quote: PaymentRequired) => Promise<void>,
) {
  const url = 'https://wurkapi.fun/solana/agenttohuman';
  const body = JSON.stringify(input);
  const initial = await fetch(url, {
    method: 'POST', redirect: 'error', body,
    headers: { 'Content-Type': 'application/json' },
  });
  if (initial.status !== 402) throw new Error(`Expected quote: HTTP ${initial.status}`);
  const header = initial.headers.get('PAYMENT-REQUIRED');
  if (!header) throw new Error('Missing x402 requirements');
  const quote = decodePaymentRequiredHeader(header);
  const selected = quote.accepts.find(item => item.scheme === 'exact'
    && item.network === solanaNetwork && item.asset === solanaUsdc);
  if (quote.x402Version !== 2 || !selected || quote.resource.url !== url) {
    throw new Error('Expected a payable Solana USDC quote for this request');
  }
  const requirements = { ...quote, accepts: [selected] };
  await authorize(requirements); // Enforce the user's intended task and spending budget.
  const signature = await signSolanaQuote(signer, requirements);
  return {
    url,
    init: {
      method: 'POST', redirect: 'error' as const, body,
      headers: {
        'Content-Type': 'application/json',
        'PAYMENT-SIGNATURE': signature,
      },
    },
  };
}
```

The `authorize` callback must reject any quote outside the already authorized task, recipient, network, asset or amount. Inspect `amount` in atomic units with integer arithmetic, including any extra metadata. Keep the selected requirement intact when signing.

Privately persist the returned `url` and `init` **before** submitting once with `fetch(prepared.url, prepared.init)`. Save the response's receipt, identifiers, secret and status URL privately. If submission times out or returns an uncertain result, retain this exact request and payment signature and use [recovery](recovery.md#recover-the-existing-operation). Calling the preparation function again can create a new payment; it is not a retry strategy.

For Base, install `@x402/evm@2.28.0` and use `registerExactEvmScheme` from `@x402/evm/exact/client` with your EVM signer, network `eip155:8453`, `/base/agenttohuman` and native Base USDC `0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913`. The same quote-inspection, persistence and recovery rules apply.

### Checkout without registration

Advanced USDC, Contest, store purchase and direct hire require no existing WURK account or API key. With the SDK imports and `signSolanaQuote` above, an Advanced Solana checkout can be prepared as follows. **Generate and save the token, exact body and idempotency key before calling this function.** Use `randomBytes(32).toString('base64url')` from `node:crypto` for the token. This function prepares a payment; it does not submit it.

```typescript
export async function prepareAdvancedCheckout(
  signer: TransactionSigner, body: string, token: string,
  authorize: (details: { body: string; quote: PaymentRequired; checkout: any }) => Promise<void>,
) {
  const url = 'https://wurkapi.fun/solana/agenttohumanadvanced';
  const headers = { 'Content-Type': 'application/json', 'X-Checkout-Token': token };
  const initial = await fetch(url, { method: 'POST', redirect: 'error', headers, body });
  if (initial.status !== 402) throw new Error('Read the original checkout status before preparing payment');
  const paymentHeader = initial.headers.get('PAYMENT-REQUIRED');
  if (!paymentHeader) throw new Error('Missing payment requirements');
  const quote = decodePaymentRequiredHeader(paymentHeader);
  const { checkout } = await initial.json();
  const selected = quote.accepts[0];
  if (quote.x402Version !== 2 || quote.accepts.length !== 1
    || selected?.scheme !== 'exact' || selected.network !== solanaNetwork
    || selected.asset !== solanaUsdc || checkout?.token !== token
    || checkout.status !== 'awaiting_payment' || checkout.family !== 'advanced'
    || !/^[0-9a-f-]{36}$/.test(checkout.id)
    || !/^[1-9]\d*$/.test(selected.amount)
    || selected.amount !== checkout.payment?.amountAtomic
    || selected.extra?.wurkGuestToken !== token
    || selected.extra?.memo !== `wurk-advanced:${checkout.id}`
    || quote.resource.url !== `${url}?checkoutId=${checkout.id}&reward_token=USDC`) {
    throw new Error('Unexpected checkout requirements');
  }
  const deadline = Date.parse(checkout.payment.initiateBefore);
  const checkTime = () => { if (!Number.isFinite(deadline) || Date.now() >= deadline) throw new Error('Payment window closed'); };
  checkTime();
  await authorize(structuredClone({ body, quote, checkout }));
  checkTime();
  const signature = await signSolanaQuote(signer, quote);
  checkTime();
  return { url, quote, checkout, init: { method: 'POST', redirect: 'error' as const, body,
    headers: { ...headers, 'PAYMENT-SIGNATURE': signature } } };
}
```

The authorization callback checks the user's actual brief and budget plus the exact amount, recipient, network, asset and metadata; it must reject unexpected terms. Privately persist the complete prepared result before submitting it once with `fetch(prepared.url, prepared.init)`. After a timeout, 202 or uncertain outcome, use `checkout.statusCheck` with the saved `X-Checkout-Token` and **without** a payment header. Do not rerun signing as automatic recovery.

Contest uses `wurk-contest:<checkout.id>`; store and hire use `wurk-store` and `wurk-hire`. Base uses native Base USDC and one EIP-3009 payment signature with the exact advertised `extra.wurkGuestNonce`; preserve `extra.wurkGuestToken` as well. These wire names are checkout-binding metadata, not a guest/account choice. The WURK SDK/CLI handles them. The stock `@x402/evm` 2.28.0 signer generates its own nonce and cannot pay these quotes unchanged; use a nonce-aware signer. Read the [checkout lifecycle](commissioning.md#x402-checkout-without-registration) for fields and recovery.

### Payment authentication

Use the same saved request and exact x402 payment requirements, without an API key or SIWX login. Modern Advanced-USDC, Contest, Store and hire reject `X-API-Key`, `SIGN-IN-WITH-X`, `Authorization` and `X-WURK-Auth` on creation/payment with `400 X402_PAYMENT_AUTH_FORBIDDEN`. The server derives account ownership from the verified payment wallet.

Status/recovery are separate read operations: use the saved checkout token or the supported account/wallet authentication. Preserve the original checkout and payment after uncertainty; changing the login method never authorizes a replacement transfer. Account, worker, profile and history APIs retain their own free authentication.

## Service discovery

Use the service-specific schema for social-order URLs, counts, supported requirements and limits. The challenge for the actual request determines the price. Social purchases and token boosts are separate from completing jobs as a worker.

| Discovery | URL |
| --- | --- |
| Public x402 resource list | [`.well-known/x402`](https://wurkapi.fun/.well-known/x402) |
| Combined paid operations | [`openapi.json`](https://wurkapi.fun/openapi.json) |
| Solana/Base x402 | [`openapi-x402.json`](https://wurkapi.fun/openapi-x402.json) |
| Solana social-growth catalog | [`openapi-x402-solana-social-growth.json`](https://wurkapi.fun/openapi-x402-solana-social-growth.json) |
| Solana human-feedback catalog | [`openapi-x402-solana-human-feedback.json`](https://wurkapi.fun/openapi-x402-solana-human-feedback.json) |
| Solana token-boost catalog | [`openapi-x402-solana-token-boosts.json`](https://wurkapi.fun/openapi-x402-solana-token-boosts.json) |
| Task-design playbook | [`best-practices.md`](https://wurkapi.fun/best-practices.md) |
| Skill metadata | [`skill.json`](https://wurkapi.fun/skill.json) |

The three focused Solana catalogs describe their respective service families. The combined catalog advertises canonical paid POST operations; additional documented aliases can exist. A payment catalog does not imply that account or worker APIs require payment.
