Developers
Confidentiality you can program.
Encrypted balances, claimable transfers, view grants and policy-bound agents on Solana, behind one SDK: @privatly/sdk.
The SDK described here is in development; today the app uses these interfaces internally. The REST routes below are live and documented as they run.
Start
Quickstart
Install the SDK, wrap a privacy provider for the connected wallet, unlock and register once.
1Install
npm i @privatly/sdk2Create a client
The client wraps a PrivacyProvider. The live provider is Umbra encrypted token accounts (Arcium). Keys are derived from one wallet signature; the SDK never sees a seed phrase or private key.
import { createPrivacyClient, httpQuoter } from "@privatly/sdk";
// provider: a PrivacyProvider bound to the connected wallet account
// (Umbra on Solana mainnet). Signing stays inside the wallet.
const client = createPrivacyClient({
provider,
quote: httpQuoter("https://privatly.xyz"),
});
await client.unlock(); // one signature derives this wallet's private keys
await client.register(); // once per wallet: creates the encrypted accountCore
Balances and transfers
Shield into an encrypted balance, send receiver-claimable transfers, claim what was sent to you. Deposits and withdrawals between public wallets and encrypted balances stay visible on-chain.
01
Quote
The server returns the fee for the action and amount, with a short expiry.
02
Prove and sign
A Groth16 proof is generated in the browser; the wallet signs the transaction.
03
Finalize
Arcium's encrypted computation updates the balance; the callback is observed.
// Public wallet to encrypted balance. The deposit is visible on-chain.
await client.shield("USDC", 250_000_000n); // 250 USDC in base units
// Receiver-claimable transfer from the encrypted balance.
const { result, quote } = await client.send({
symbol: "USDC",
amount: 100_000_000n,
recipient,
source: "private",
amountUsd: 100, // from your price source, used for the fee quote only
});
quote.privacyExecutionFee; // show before signing; the SDK does not collect it
result.signatures; // signed by the user's wallet| Field | Type | Description |
|---|---|---|
| signatures | string[] | Signed by the user's wallet. The server verifies these when an execution is reported. |
| relatedSignatures | string[] | Not signed by the user: encrypted computation callbacks and relayer transactions. |
| callback | "finalized" | "pending" | "failed" | "none" | Whether the encrypted computation finished. failed means staged funds may need recovery. |
Claim on the receiving side
// Recipient side: find transfers sent to this wallet, then claim them.
const pending = await client.scanClaimable();
const { claimedIds, failed } = await client.claim(pending);Autonomy
Agents
An agent is a server-held key plus a Squads smart account. You sign the account and its daily spending limits; the runtime executes structured objectives within them.
- 1.Create the agent. The server generates its key and stores it encrypted at rest (AES-256-GCM).
- 2.Sign the Squads smart account creation and the per-token daily spending limits. Those limits are the hard cap.
- 3.Fund the vault with what the agent may spend. Vault balances are public on-chain.
- 4.Add objectives: a TWAP swap through Jupiter or a recurring payment.
- 5.On every run the server evaluates your policy first. Above the approval threshold, you sign the vault transaction yourself.
Target SDK shape
In the app this runs on the /api/agents routes with your session. previewPolicy exists today and runs the same evaluation the server does; the agent handle is the interface we are building toward.
import { previewPolicy, type PrivateAgent } from "@privatly/sdk";
// Same evaluation the server runs before every agent execution.
const decision = previewPolicy(policy, {
amountUsd: 120,
asset: "USDC",
category: "payments",
spentTodayUsd: 300,
});
// Target shape, backed today by /api/agents/** in the app.
const agent: PrivateAgent = await agents.get(agentId);
await agent.addObjective({
kind: "recurring_payment",
asset: "USDC",
amount: 120,
recipient: vendor,
intervalDays: 7,
maxRuns: 12,
});Runs are picked up by a scheduled job on the server. A client-side policy check is a preview; the server decides, and the on-chain limit caps.
Disclosure
View keys
A view key is an on-chain compliance grant (Umbra compliance grants (X25519)). It lets a named viewer decrypt your activity. It is revocable at any time.
// Let an auditor decrypt this wallet's activity. Revocable on-chain.
const { signature, record } = await client.grantViewAccess({
granteeAddress: auditorWallet,
granteeX25519: auditorX25519PublicKey, // 32 bytes
});
// Keep record.nonce: revoking needs it.
await client.revokeViewAccess(record);| Field | Type | Description |
|---|---|---|
| granteeAddress | string | Wallet authorized to request re-encryption. |
| granteeX25519 | Uint8Array | The viewer's X25519 public key, 32 bytes. |
| granterX25519 | Uint8Array | Your master viewing public key. |
| nonce | bigint | Identifies the grant on-chain. Required to revoke. |
Grants have no on-chain expiry. An expiry date in the app is a reminder to revoke, not an enforced deadline. Agents cannot create grants.
REST
API
These routes run the app today. They use a Sign in with Solana session cookie, so they are built for the browser. A key-authenticated public API is part of the SDK work.
| Route | Auth | Description |
|---|---|---|
| Session | ||
| GET/api/auth/nonce | None | Issues a sign-in nonce, bound to an httpOnly cookie for 10 minutes. |
| POST/api/auth/verify | None | Verifies a Sign in with Solana signature and starts a 7 day session. |
| GET/api/auth/session | None | Current session wallet and admin flag, or null. |
| POST/api/auth/logout | None | Ends the session. |
| Pricing | ||
| POST/api/quote | None | Authoritative fee quote. Plan tier from the session; optional x-api-key is metered. |
| GET/api/prices | None | USD prices for supported tokens from the Jupiter Price API. |
| Swaps and executions | ||
| GET, POST/api/swap/quote | Session | Jupiter ExactIn quote with the platform fee for the session wallet's plan. |
| POST/api/swap/build | Session | Builds the Jupiter swap transaction for the session wallet to sign. |
| POST/api/executions | Session | Reports a confirmed execution. Signatures are verified on-chain, the quote settles into the ledger. |
| GET/api/activity | Session | Activity of the session wallet. Optional ?agent=<id>. |
| Disclosure and plans | ||
| GET, POST/api/view-keys | Session | View grant metadata. The grant itself is created on-chain first and verified here. |
| POST/api/view-keys/revoke | Session | Marks a grant revoked after the on-chain revocation is verified. |
| GET, POST/api/subscriptions | Session | Current plan; activate a plan from a verified USDC payment to the treasury. |
| Messaging | ||
| GET, POST/api/messaging-keys | Session | Publish your X25519 public key; look up another wallet's key. |
| GET, POST/api/messages/conversations | Session | List conversations; create or return one with a peer. |
| GET, POST/api/messages/{conversationId} | Member | Read and send sealed messages. The server sees ciphertext only. |
| POST/api/messages/{conversationId}/read | Member | Read marker. |
| Agents | ||
| GET, POST/api/agents | Session | List agents; create one (server-generated key, encrypted at rest). |
| GET, PATCH, DELETE/api/agents/{id} | Owner | Detail with live on-chain state; pause, resume, edit policy; delete when empty. |
| POST/api/agents/{id}/setup | Owner | Squads smart account lifecycle: create, spending limits, fund, withdraw. |
| GET, POST, PATCH/api/agents/{id}/objectives | Owner | Structured objectives: TWAP swap or recurring payment. |
| GET/api/agents/{id}/executions | Owner | Execution history. |
| GET, POST/api/agents/{id}/approvals | Owner | Approve (owner-signed vault transaction) or decline a pending run. |
| GET/api/vaults | Session | Agent vaults with live balances and spending limits. |
| Integrators | ||
| GET, POST/api/integrators | Session | Your integrator record, usage and payouts; register one per wallet. |
| POST/api/integrators/keys | Session | Create an API key. The full key is returned once. |
| DELETE/api/integrators/keys/{id} | Session | Revoke an API key. |
Errors
Every error is JSON: { "error": string }. Unexpected failures return 500 with no internal details.
| Field | Type | Description |
|---|---|---|
| 400 | status | Invalid JSON or malformed parameter |
| 401 | status | Sign in required, expired sign-in, or invalid API key |
| 403 | status | Wallet does not match the session, or plan limit reached |
| 404 | status | Not found, or not yours |
| 409 | status | Conflict: already applied, outdated quote, or limit reached |
| 422 | status | Schema validation failed. Body includes zod issues |
| 502 | status | Upstream (Jupiter, RPC) unavailable |
Sign in with Solana
// 1. Nonce (sets an httpOnly cookie scoped to /api/auth)
GET /api/auth/nonce
200 { "nonce": "9f2c4a..." }
// 2. The wallet signs a Sign in with Solana message containing that nonce,
// the current host as domain, and an issuedAt within the last 10 minutes.
// 3. Verify
POST /api/auth/verify
{
"address": "<base58 wallet>",
"signedMessage": "<base64 message bytes>",
"signature": "<base64 ed25519 signature>"
}
200 { "wallet": "<base58 wallet>" } // sets privatly_session, 7 daysPOST /api/quote
Accepts an action, a USD amount and an asset, never a fee. The plan tier comes from the session. Signed-in requests get a persisted quote id that /api/executions settles once. An x-api-key header, when present, must be valid and is counted for that integrator.
curl -X POST https://privatly.xyz/api/quote \
-H "Content-Type: application/json" \
-H "x-api-key: $API_KEY" \
-d '{"action":"PRIVATE_TRANSFER","amountUsd":1000,"asset":"USDC"}'{
"action": "PRIVATE_TRANSFER", // PRIVATE_SWAP | PRIVATE_TRANSFER | AGENT_EXECUTION
// PRIVATE_REBALANCE | ENCRYPTED_MESSAGE | VIEW_KEY
"amountUsd": 1000, // 0 to 1,000,000,000
"asset": "USDC" // 1 to 64 characters
}| Field | Type | Description |
|---|---|---|
| privacyExecutionFee | micro-USD | Protocol fee plus relay cost. The single fee line users see. |
| networkCost | micro-USD | Estimated Solana fee. Paid by the wallet to the network. |
| totalFee | micro-USD | privacyExecutionFee + networkCost. |
| discounts | AppliedDiscount[] | Plan or volume discounts, each with a reason. A fee floor applies. |
| expiresAt | epoch ms | Quotes are short lived. Request a new one after expiry. |
| id | uuid? | Present for signed-in requests. Reference it when reporting the execution. |
{
"quote": {
"action": "PRIVATE_TRANSFER",
"protocolFee": {
"$big": "800000"
},
"providerCost": {
"$big": "0"
},
"relayerCost": {
"$big": "0"
},
"networkCost": {
"$big": "2000"
},
"integratorShare": {
"$big": "0"
},
"privacyExecutionFee": {
"$big": "800000"
},
"totalFee": {
"$big": "802000"
},
"baseProtocolFee": {
"$big": "800000"
},
"discounts": [],
"ruleSnapshot": {
"model": "bps_with_cap",
"bps": 8,
"minimumUsd": 0.05,
"maximumUsd": 25
},
"quotedAt": 1791329918362,
"expiresAt": 1791329948362,
"source": "server"
},
"tier": "free"
}GET /api/prices
GET /api/prices
200 {
"prices": { "SOL": <usd>, "USDC": <usd>, "USDT": <usd>, "JitoSOL": <usd> }, // missing when unpriced
"at": <epoch ms>
}Swaps
The server decides the fee and the treasury account again when building, so a tampered quote is refused.
GET /api/swap/quote?inputMint=<mint>&outputMint=<mint>&amount=<base units>&slippageBps=50
200 {
"quote": { /* Jupiter quote response, platformFee included when collectable */ },
"fee": { "bps": 15, "mint": "<mint the fee is taken in>" } // bps 0 when not collectable
}
POST /api/swap/build
{ "userPublicKey": "<session wallet>", "quoteResponse": { /* quote from above */ } }
200 { "transaction": "<base64 versioned transaction>", "lastValidBlockHeight": <number | null> }
409 { "error": "Quote is outdated. Refresh and try again." }POST /api/executions
POST /api/executions
{
"kind": "private_transfer", // private_swap | private_transfer | shield | unshield | claim
// view_key_created | view_key_revoked | agent_setup | subscription
"title": "Sent USDC",
"signatures": ["<signature>"], // 1 to 8, each verified on-chain as signed by the session wallet
"quoteId": "<uuid from /api/quote>", // optional: settles that quote into the revenue ledger once
"publicFields": [{ "label": "Asset", "value": "USDC" }],
"privatePayload": "<ciphertext>" // optional, encrypted on the client
}
200 { "id": "<activity id>" }
422 { "error": "Transaction not signed by this wallet (5Kd1x2ab…)" }POST /api/subscriptions
The wallet first sends the plan price in USDC to the treasury. The server checks the transfer on-chain: signer, destination, amount, age, and that it was never used before.
POST /api/subscriptions
{ "tier": "pro", "signature": "<USDC transfer to the treasury, signed by the session wallet>" }
200 { "tier": "pro", "expiresAt": "2026-11-04T12:00:00.000Z" }
409 { "error": "This payment was already applied" }Program
Integrator program
Wallets, apps and agent platforms that route executions through their integration earn a share of the protocol fee. Register and issue API keys in the dashboard today.
Swap fee: 0.15% of volume. Integrator share: 3 bps, never more than half the protocol fee. Minimum payout $50.
| Attribute | Integrator | Affiliate (planned) |
|---|---|---|
| What they do | Embed the product in a wallet, app or agent platform | Send users to the product |
| Share | 3 of 15 bps on attributed swaps (20% of the fee) | 10% of fees from referred users |
| Duration | For as long as executions route through the integration | 180 days per referred user |
| Attribution | Verified only: API keys, hashed server side | Referral link, time bounded |
Status: integrator records, API keys and request metering are live. Attributing settled executions to a key ships with the public API; until then attributed revenue is zero. Client-supplied attribution is always ignored.
Create a key
POST /api/integrators/keys
{ "label": "Production" }
201 {
"key": "privatly_live_…", // shown once; only a SHA-256 hash is stored
"record": { "id": "<uuid>", "label": "Production", "prefix": "privatly_live_ab12cd",
"createdAt": "…", "lastUsedAt": null, "revokedAt": null }
}