# xgate wallet client

A Node 24 client and wallet broker for bounded Anonymous inference over Tempo MPP
session v2, x402 v2 Base batch settlement, and x402 v2 Solana exact payments.
Network, recipient, payment authority and budget are explicit fixed configuration;
the client never switches rails or schemes after an uncertain outcome.

The client serializes once, reviews a quote, reserves its full ceiling in SQLite,
signs without broadcasting, persists the credential, and submits exactly one paid
POST. It verifies protected settlement metadata plus canonical finalized channel
funding, recipient credit/payment and unused-funding return before delivering a result.
Solana instead verifies the fixed transfer and sponsor-paid native fee; it has no
funded hold or partial capture.
No prompts or inference responses are stored by this client. Private payment
capabilities and signed credentials are encrypted with the retained journal key.

A disconnected or ambiguous purchase keeps its reservation and blocks fresh
purchases. `reconcile` performs protected GETs and independent chain reads only.
It never resubmits inference or retrieves a lost result. Even a completed purchase
cannot be run again under the same invocation ID. A deliberate new user turn has a
new identity. Do not delete a journal or change its policy to clear an unknown.

Runnable single-request examples: [Base x402, Solana x402 and Tempo MPP](examples/README.md).

## Install

The release package is `@lucid-agents/xgate-client`:

```sh
npm install -g @lucid-agents/xgate-client@0.1.7
xgate-wallet --config /absolute/private/wallet.json
```

For an application using the broker API, install it as a dependency instead:

```sh
npm install @lucid-agents/xgate-client@0.1.7
```

```js
import { BrokerClient } from "@lucid-agents/xgate-client/broker";
```

The package contains runnable `examples/` and compiled Node client code. Never put
wallet keys into the Pi extension or a public package. Repository build instructions
below also work before a release is available.

## Build and configure

From the repository root:

```sh
pnpm --filter @lucid-agents/xgate-client build
node packages/xgate-client/dist/serve.js --config /absolute/private/wallet.json
```

The service binds only `127.0.0.1`. Use a dedicated operating-system account or an
isolated host with an authenticated TLS proxy when Pi has shell access. Running
both processes as the same user does **not** isolate the wallet key from Pi's
filesystem tools. The broker token permits inference only within the service's
fixed policy; it is not a generic signing API.

Create an owner-only directory and config (0700 directory / 0600 files). Supply an
existing funded EVM/Tempo wallet key as 32-byte hex text (with optional `0x`), or a
Solana keypair JSON array of 64 bytes (seed followed by matching public key), plus a
separate retained 32-byte **raw**
journal encryption key, and a broker token file containing 32 random bytes encoded
as 64 lowercase hex characters. The client does not generate or rotate wallet keys.
Keep the journal and encryption key together in private backups.

Example config, with every placeholder replaced by the operator:

```json
{
  "port": 4021,
  "policy": {
    "origin": "https://devnet-api.xgate.run",
    "network": "tempo-moderato",
    "payer": "<wallet address matching walletKeyFile>",
    "recipient": "<verified staging router recipient>",
    "models": ["claude-haiku-4-5-20251001", "gpt-5-mini"],
    "maxPurchaseAtomic": "200000",
    "maxTotalAtomic": "1000000",
    "maxPurchases": 20
  },
  "rpcUrl": "<approved HTTPS RPC for this network>",
  "walletKeyFile": "/absolute/private/wallet.hex",
  "journalKeyFile": "/absolute/private/journal.key",
  "tokenFile": "/absolute/private/broker-token",
  "journalFile": "/absolute/private/journal.sqlite"
}
```

Amounts are six-decimal atomic units. `maxTotalAtomic` bounds **cumulative submitted
funding authorizations**, not just eventual charges. Returned remainders do not
replenish it. Only cancellation proven to precede any paid submission releases that
unused authorization. Request slots remain consumed. Unknown purchases retain the
full ceiling. The ledger binds its policy, payer and encryption key across restarts
and serializes reservation across processes sharing that ledger. Use one ledger and
service authority for each wallet/network budget, not independent ledgers per Pi
session. Do not create a new ledger to evade a budget or unresolved purchase.

## x402 configuration

Keep the same broker configuration fields shown above. Select one of these
`policy.network` values; the payment profile is fixed by that choice:

| Network | Protocol/profile | Asset | Additional policy fields |
| --- | --- | --- | --- |
| `base-mainnet`, `base-sepolia` | x402 v2 `batch-settlement`, EIP-3009 | Native USDC / test USDC | `receiverAuthorizer`, `confirmation` |
| `solana-mainnet`, `solana-devnet` | x402 v2 `exact`, sponsored SPL transfer | Native USDC / test USDC | `feePayer` or `feePayers` |
| `tempo-mainnet`, `tempo-moderato` | MPP session v2 | Stargate USDC.e / pathUSD | None |

The Base receiver authorizer and Solana fee payer are operator-reviewed addresses,
not the buyer key or a value silently trusted from a new challenge. Pin them for the
selected router/network; changed challenge authority is rejected before signing.
For a facilitator with rotating sponsors, configure `feePayers` as an explicit
reviewed allowlist of one to eight distinct Solana addresses instead of `feePayer`.
Do not set both. Discovery never adds addresses automatically. Each signed purchase
and its recovery still use the single sponsor selected in the original quote.
Existing single-sponsor policies keep their original journal identity; changing an
established policy is not an automatic budget migration.

Base confirmation must explicitly be `l2-confirmed`, `safe`, or `finalized`.
`l2-confirmed` checks the current canonical L2 chain, not Ethereum finality. Stronger
confirmation can leave an otherwise settled purchase pending until a later
`reconcile`; do not create a replacement purchase while waiting.

For example, the Base policy additions are:

```json
{
  "network": "base-sepolia",
  "receiverAuthorizer": "<reviewed Base receiver authorizer>",
  "confirmation": "safe"
}
```

For Solana, use:

```json
{
  "network": "solana-devnet",
  "feePayer": "<reviewed Solana facilitator fee payer>"
}
```

These are partial examples to merge into the full policy, not complete configs.
Use the corresponding HTTPS RPC, payer, recipient and private key file. Solana
addresses retain their case. The buyer and recipient token accounts must already
exist; this adapter never creates accounts, requests faucet funds, broadcasts
transactions, or signs token approvals.

Base opens a fresh channel per purchase with the pinned SDK's minimum deposit of
**three times the quote ceiling**. Both `maxPurchaseAtomic` and `maxTotalAtomic`
limit that full deposit, not merely the eventual charge. The adapter verifies the
original channel's canonical funding, remaining receiver credit and returned
remainder. Receiver credit is not a claim of an immediate treasury wallet payout.
Readback is bounded to 2,048 blocks from the saved opening baseline; unavailable or
later-than-bounded evidence remains unresolved.

Solana exact authorizes and charges the full fixed quote on successful inference;
it does not charge only measured usage and has no partial return. Recovery matches
the original message and signatures, exact USDC account changes and sponsored fees.
A zero-charge report requires finalized block height beyond the saved authorization
lifetime, an invalid blockhash and complete bounded source-account history. Pruned,
missing or over-64-transaction history keeps the reservation unresolved. This uses
[finalized address history](https://solana.com/docs/rpc/http/getsignaturesforaddress)
and the node's [available ledger bound](https://solana.com/docs/rpc/http/getfirstavailableblock),
not wallet-wide balance differences as proof of no payment.

Existing Tempo configs and encrypted journals retain their original policy identity.
Never repoint an existing journal to a different network, budget, signer or authority.
Use a separately authorized budget for each funding authority, preserve previous
journals, and resolve their unknown purchases. No automatic protocol fallback exists.
Base `exact`/`upto`, Permit2, auth-capture and non-USDC assets are outside this client.

## Broker interface

Every endpoint requires `Authorization: Bearer <broker token>`. Never expose the
loopback service through an unauthenticated proxy. Browser Origin requests are
refused and no cross-origin access is enabled.

- `GET /models`: operator-allowed live catalog entries.
- `GET /status`: authorized, reserved and charged totals, plus pending invocation IDs.
- `GET /receipts`: up to 50 recent content-free purchase records.
- `POST /purchase`: `{id, route, body}`; `body` is the exact serialized native request.
- `POST /reconcile`: `{id}`; settle the original record using metadata/chain reads.

`id` is a 64-character hex invocation identity, stable across retry/restart. Reusing
it with changed request bytes is an error. Native headers, status capabilities and
signed payment credentials never appear in broker responses. A financial record
marked complete does not imply the caller received the original inference body.

The TypeScript API exports `XgateClient`, `Ledger`, `TempoPayment` and `BrokerClient`.
`PaymentTransport.prepare` is a signing-only seam: implementations must never
broadcast. The Tempo implementation enforces this with a read-only RPC allowlist.
No generic HTTP or provider retries are performed. OpenAI requests require a fresh
`openai-anonymous-tools-v2` qualification; existing v1 approvals are insufficient.

## Validation

`pnpm --filter @lucid-agents/xgate-client test` builds the actual package and runs fixture tests
for concurrency, exact bytes, budget exhaustion, signing failure, response loss,
restart reconciliation, canonical channel effects and late recovery. Fixtures do
not prove live payment conformance. See the repository's Pi integration delivery
record for any separately authorized signed staging evidence.

The x402 adapters pin `@x402/core`, `@x402/evm`, and `@x402/svm` at 2.25.0, with
Solana Kit 5.5.1. Tests execute those SDKs with deterministic public fixture keys and
read-only RPC fixtures for both mainnet and testnet identities. They cover wrong
chains/recipients, receipt tampering, expiry, retained uncertainty, refunds, exact
amounts, sponsored fees and recovery without a paid POST. A read-only staging quote
also confirmed the router's advertised Base batch/Solana exact shapes on 9 October
2026 Australia/Sydney. Separately authorized signed staging qualification on that
same date exercised installed npm tarballs through the actual Pi CLI, live providers,
and testnet payments. See the [staging evidence and limitations](../../docs/validation/pi-client-staging-20261009.md).
Production client qualification remains separate. These checks did not publish npm
packages or deploy services.

## License

MIT. See [LICENSE](LICENSE).
