# Guard purchase client v1

The public Node 24 ESM bundle is available at:

- `https://tollary.p-e.kr/sdk/guard-client-1.0.0.mjs`
- `https://tollary.p-e.kr/sdk/guard-client.manifest.json`

The versioned module is a single self-contained bundle. Its x402, EVM, fetch,
payment-identifier, viem, and Guard verification code is embedded; it has no
npm peer dependency. The manifest publishes byte length, SHA-256, and SRI for
the stable and versioned modules, plus this document's digest. Pin the
versioned URL and digest in production.

## What it does

`purchaseGuardProof(options)` joins one exact, bounded sequence:

1. validates the signed `{ transaction, mandate }` locally;
2. defaults payment to Base Sepolia and verifies the server's exact `$0.01`
   USDC x402 v2 challenge;
3. persists a payment identifier and recovery URL before the first request;
4. asks a caller-owned x402 account to sign only the payment authorization;
5. verifies `PAYMENT-RESPONSE`, the complete Guard v3 proof, its exact request
   binding, short validity window, and the pinned Ed25519 response attestation.

`inspectGuardPurchase(options)` performs steps 1 and 2 without paying. A
structurally blocked request throws `GuardStructuralBlockError` before fetch,
so malformed or policy-inconsistent transactions are never charged.

The module does not create a wallet, accept a private key or seed phrase, sign
the owner mandate, sign the guarded transaction, or broadcast it.

## Explicit signing roles

A production integration has three explicit roles:

- **mandate owner** — signs the EIP-712 policy outside the agent;
- **transaction sender** — may sign only after the verified Guard proof reaches
  the restricted signing boundary;
- **payment buyer** — pays the one-cent x402 fee through a caller-owned account.

The mandate owner must differ from the transaction sender. The purchase client
also rejects a payment buyer equal to the transaction sender so the sender's
underlying signer never enters the payment adapter. The payment buyer may equal
the mandate owner, although a dedicated low-balance testnet payer is recommended
operationally. `paymentAccount` is an async callback; it may return a viem
account, HSM adapter, or smart-wallet adapter exposing an address and
`signTypedData`. The client copies only those capabilities and never reads key
material.

## Base Sepolia integration

First create the exact owner-signed request with the separately published
Guarded viem SDK. Reserve the mandate nonce durably before signing it.

```js
import {
  purchaseGuardProof
} from './guard-client-1.0.0.mjs';

const purchase = await purchaseGuardProof({
  requestBody: guardRequest,       // exactly { transaction, mandate }
  expectedIssuer,                 // owner address pinned out of band
  publicDescriptor,               // Guard Ed25519 key pinned out of band

  // Caller-owned account lookup. No raw key is passed to this module.
  paymentAccount: async ({ role, network, endpoint }) => {
    if (role !== 'x402-payment-buyer') throw new Error('unexpected role');
    return isolatedTestnetPaymentAccount;
  },

  // Complete this durable write before allowing any fetch.
  onRecoveryReady: recovery => recoveryStore.put(recovery),

  // Omit network: Base Sepolia is the fail-safe default.
});

console.log({
  guardId: purchase.guard.guardId,
  decision: purchase.guard.decision,
  roles: purchase.roles
});
```

Never print, publish, or place `paymentIdentifier` or `recoveryUrl` in telemetry.
They are bearer recovery credentials and belong only in the protected recovery
store used by `onRecoveryReady`.

`publicDescriptor` and `expectedIssuer` are trust anchors. Obtain and pin them
through an independently authenticated configuration path; do not trust values
that arrive beside the response they are supposed to verify.

The returned proof is authorization evidence, not a broadcast. Pass it and its
`guardAttestationHeader` to `guardedSignAndBroadcast` in the Guarded viem SDK.
Keep every raw signing and broadcast route inaccessible to the agent.

## Recovery behavior

`onRecoveryReady` is mandatory. If it fails, the client makes no HTTP request.
If signing, networking, or the paid response fails later, `GuardClientError`
contains `paymentIdentifier` and `recoveryUrl`. Persist both before payment and
use the receipt endpoint to distinguish an unsettled attempt from a delivered
proof after a timeout.

## Mainnet is identity-gated

Mainnet is never inferred from endpoint, environment, or a truthy boolean. It
requires both an explicit network and the exact exported symbol:

```js
import {
  BASE_MAINNET,
  BASE_MAINNET_PAYMENT_EXPLICIT_OPT_IN,
  purchaseGuardProof
} from './guard-client-1.0.0.mjs';

await purchaseGuardProof({
  // all testnet options above,
  network: BASE_MAINNET,
  mainnetAuthorization: BASE_MAINNET_PAYMENT_EXPLICIT_OPT_IN
});
```

That call authorizes real USDC. Do not add it until the service's separate
Mainnet activation gate, wallet funding, operational approval, and customer
approval are complete. Importing a symbol with the same description does not
pass the identity check.
