What you can build
Plinth is a USDC lending pool on Solana backed by stock tokens. Around it sit three tools for software: a paid data API, a payment facilitator for other services, and a JavaScript SDK with a CLI. Payments use x402, the HTTP 402 payment standard: a request without payment gets the price, and a signed USDC transfer for that price gets the response.
- 01Paid data
Pool conditions, loan quotes, borrower reports, and loans at risk, for a few cents in USDC per request.
- 02Facilitator
Take x402 payments for your own API without running a wallet server or holding SOL. (not enabled on this site yet)
- 03SDK and CLI
Quote, borrow, repay, deposit, and pay x402 endpoints from an agent or a script.
Buy pool data over x402
- 01
Ask
Call an endpoint without paying. It answers
402 Payment Requiredwith the price in thePAYMENT-REQUIREDheader (x402 v2); the body repeats it in the v1 format. The requirement names the server's fee payer inextra.feePayer. - 02
Sign
Build a USDC
TransferCheckedfor exactly that price to the payee's token account, with the server as fee payer, and sign it. Signing sends nothing by itself. - 03
Pay and receive
Send the request again with the signed transaction in
PAYMENT-SIGNATURE(orX-PAYMENTfor v1). The server checks it, adds its fee-payer signature, sends it, and answers with the data and the settlement inPAYMENT-RESPONSE(X-PAYMENT-RESPONSEfor v1). The USDC goes straight to the payee; the server only pays the network fee.
GET /api/x402/pool
0.01 USDC. Pool conditions: funds, idle USDC, utilization, safety fund, limits, and every listed stock with its price and loan limits.
GET /api/x402/quote
0.01 USDC. Loan quote: fee, total to repay, liquidation price, and the largest loan allowed for a stock, collateral, amount, and term. Parameters: asset, collateral, amount, days, borrower (optional).
GET /api/x402/borrower/{address}
0.03 USDC. Borrower report: repayment record, debt now, open loans with health, and full loan history.
GET /api/x402/risk
0.05 USDC. Open loans near or past their liquidation threshold or due date, with health and collateral.
The free catalog at GET /api/x402 lists every resource, its price, the network, the USDC mint, the fee payer, and where payments go. You are never charged for a request that fails: bad parameters answer 400, an unknown borrower 404, and an unreadable chain 503, all before a payment is asked for.
If settling takes more than a few seconds, the server answers 402 with "pending": true and a Retry-After header. Send the same signed payment again after that many seconds: once it has settled, the data is served for it without a second charge. A signed payment is only used for the endpoint it was sent to.
With the official x402 client
Any standard x402 v2 client for Solana can pay, including the official @x402/fetch with @x402/svm. Allow USDC on this network in its spend controls, with a cap per payment:
import { wrapFetchWithPaymentFromConfig } from "@x402/fetch";
import { ExactSvmScheme } from "@x402/svm/exact/client";
// signer: a @solana/kit TransactionSigner (e.g. a keypair loaded from a file; never hard-code a key)
const pay = wrapFetchWithPaymentFromConfig(fetch, {
schemes: [{ network: "solana:*", client: new ExactSvmScheme(signer) }],
spendControls: { allowedAssets: [{ network: "solana:EtWTRABZaYq6iMfeYKouRu166VU2xqa1", asset: "7k8RSp2TLduCfVJKyMsjMWHCoc3cYQQJrJm6pF44B5c7", maxAmountPerPayment: "100000" }] }, // at most 0.1 USDC per call
});
const res = await pay("https://www.plinthcity.com/api/x402/pool");Pay any x402 endpoint, and borrow the gap
The Plinth CLI and SDK include an x402 payer. It pays any x402 endpoint in USDC on this network, including other sites. If the agent's USDC balance is short, it can borrow the difference from this pool against a stock token the agent holds (at least the pool's minimum loan, locked with a buffer above the loan limit), then pay, and keep the rest for the next calls. The agent needs a little SOL only to borrow and repay; x402 payments themselves are paid for by the server's fee payer.
npx plinth x402-catalog --url https://www.plinthcity.com
npx plinth pay --url https://www.plinthcity.com/api/x402/pool --max-price 0.05
npx plinth pay --url https://www.plinthcity.com/api/x402/pool --max-price 0.05 --max-borrow 5 --asset NVDAx --days 7 --yesWithout --yes the command only previews: the price, your balance, and any loan it would take. --max-price and --max-borrow are hard limits. A loan taken to pay follows every normal rule: a fixed fee, a due date, and liquidation of all its collateral if it is not repaid. x402 never opens a loan on its own, never repays, and never gives Plinth control of the agent's wallet: every payment and every loan is signed by the agent.
Take x402 payments for your own API (not enabled on this site yet)
A facilitator checks each payment, adds its fee-payer signature, and sends the transfer for you. With Plinth's, you run no wallet server and hold no SOL: the USDC moves from the payer straight to your wallet, and the facilitator pays the network fee. It never signs a transaction in which its own key would move funds.
- 01
Register
On the Merchants page, connect the Solana wallet that should be paid and sign one message. You receive an API key, shown once.
- 02
Connect your server
Point your x402 server's facilitator at
https://www.plinthcity.com/api/facilitatorwith the key in anAuthorization: Bearerheader. Its fee payer is listed atGET /supported. - 03
Get paid
Each settle is free up to a daily number; after that a small fixed fee comes from a prepaid USDC balance you top up from the same wallet. A settle that is refused or fails costs nothing.
import { HTTPFacilitatorClient, x402ResourceServer } from "@x402/core/server";
import { ExactSvmScheme } from "@x402/svm/exact/server";
const facilitator = new HTTPFacilitatorClient({
url: "https://www.plinthcity.com/api/facilitator",
createAuthHeaders: async () => ({
verify: { Authorization: `Bearer ${process.env.FACILITATOR_KEY}` },
settle: { Authorization: `Bearer ${process.env.FACILITATOR_KEY}` },
supported: {},
}),
});
const server = new x402ResourceServer(facilitator).register("solana:EtWTRABZaYq6iMfeYKouRu166VU2xqa1", new ExactSvmScheme());
// price in USDC atomic units (6 decimals): 20000 = 0.02 USDC; extra.feePayer comes from GET /supported
const requirements = await server.buildPaymentRequirements({
scheme: "exact", network: "solana:EtWTRABZaYq6iMfeYKouRu166VU2xqa1", payTo: YOUR_WALLET,
price: { amount: "20000", asset: "7k8RSp2TLduCfVJKyMsjMWHCoc3cYQQJrJm6pF44B5c7" },
});POST /verify, POST /settle
The standard x402 facilitator calls. Your key only settles payments to your own wallet.
GET /supported
Networks, x402 versions, and the fee payer that signs as the transaction's fee payer.
GET /health
The current terms: free settles, the fee, daily limits, the smallest payment, and the fee payer's SOL.
GET /merchants, /settlements, /discovery/resources
Public lists of merchants, recent settlements, and paid resources seen on this facilitator.
New merchants are listed as unverified until the site owner checks them, and a merchant can be suspended for abuse. The facilitator has had an internal review and tests against malicious payments only, not an independent audit.
Drive the pool from code
The JavaScript SDK (@solana/kit) and the plinth CLI cover every pool action: read the market, quote, borrow, add collateral, repay, deposit, withdraw, liquidate, and pay x402 endpoints. Every action that moves money has a preview first, and the CLI sends nothing without --yes.
Install
npm install @plinth/sdk # JavaScript SDK (Node.js 20+); Solana API at @plinth/sdk/solana
npx plinth --help # the CLI, no install neededThe Solana Devnet deployment ships with the SDK, so reading needs only SOLANA_CLUSTER (and optionally SOLANA_RPC_URL): try npx plinth doctor or npx plinth pool. To send transactions, set SOLANA_KEYPAIR to a keypair file (solana-keygen format), and ALLOW_MAINNET=true on mainnet.
Borrow and repay
import { LendingClient, loadSolanaDeployment, loadKeypairSigner, parseUsd, parseToken } from "@plinth/sdk/solana";
const signer = await loadKeypairSigner(process.env.SOLANA_KEYPAIR); // a keypair file; never hard-code a key
const pool = new LendingClient({ rpcUrl: "https://api.devnet.solana.com", deployment: loadSolanaDeployment("devnet"), signer });
const q = await pool.quote({ asset: "NVDAx", collateral: parseToken("2", 6), amount: parseUsd("50"), days: 7, borrower: signer.address });
if (q.reasons.length === 0) {
const { loanId } = await pool.borrow({ asset: "NVDAx", collateral: parseToken("2", 6), amount: parseUsd("50"), days: 7 });
const loan = await pool.loan(loanId); // health, due date, whether it can be liquidated
await pool.repay(loanId);
}
// a new address starts with a 3 USDC limit that grows with loans held 7+ days and repaid in fullPay x402, borrowing only if needed
import { X402Payer, parseUsd } from "@plinth/sdk/solana";
const payer = new X402Payer({ client: pool, maxPrice: parseUsd("0.05"), maxBorrow: parseUsd("5"), collateral: "NVDAx", days: 7 });
const preview = await payer.preview(url); // price, balance, any loan it would take; nothing is signed
const { response, paid, borrowed, loanId } = await payer.pay(url);Main methods: poolStats, assets, quote, borrowLimit, borrow, loan, loans, addCollateral, repay, previewDeposit/deposit, positionsOf, previewWithdraw/withdraw, previewLiquidate/liquidate, and record for an address's repayment history.
Before an agent spends or borrows
Cap the price per request and the loan size, restrict which collateral an agent may use, keep a buffer above the loan limit, and keep enough USDC to repay. If prices or pool data cannot be verified, stop opening loans and ask for a human review. A loan can be liquidated after a price drop, or once the due date and its one-day grace period have passed; the liquidator takes all of the collateral.
The pool program has not been audited and can still be upgraded by its upgrade authority. Read the risks before using real funds.