Recovery opportunity
Display the returned action and fee/net estimate. An estimate is not recovered funds. Keep execution controls off while your access is analysis-only.
Partner API · Analysis preview
Your backend sends a wallet address. The API returns supported recovery opportunities, protected accounts and fee estimates.
01 / Connect
You need an activated API key and a wallet address. Request access if you have not been onboarded. Choose either integration below.
Send JSON to https://unclaimedsol.com/api/v1/check-wallet. Replace the placeholders with your server-side key, wallet address and a saved idempotency key.
POST /api/v1/check-wallet HTTP/1.1
Host: unclaimedsol.com
Authorization: Bearer <server-only-api-key>
Content-Type: application/json
Accept: application/json
Idempotency-Key: <saved-key-for-this-request>
{
"wallet": "<wallet-base58-address>",
"mode": "safe",
"limit": 20,
"includeAirdrops": false
}
Save the exact body and key before sending. Validate responses against the pinned contract. The SDK version header is optional.
npm install --save-exact @unclaimedsol/wallet-sdk@0.1.0-preview.5
Load UNCLAIMED_API_ORIGIN=https://unclaimedsol.com and UNCLAIMED_API_KEY through your normal backend secret loader. Do not add /api/v1 to the SDK origin. The SDK does not load .env automatically.
Save this as analysis.ts. Your application persists the request body and key before calling it.
import {
createUnclaimedClient,
nextWalletPage,
summarizeOpportunities,
} from "@unclaimedsol/wallet-sdk";
const baseUrl = process.env.UNCLAIMED_API_ORIGIN;
const apiKey = process.env.UNCLAIMED_API_KEY;
if (!baseUrl || !apiKey) throw new Error("Server API configuration is missing.");
const client = createUnclaimedClient({ baseUrl, apiKey });
// Your app persists the exact input and key BEFORE calling this function.
export async function analyzeWallet(wallet: string, idempotencyKey: string) {
const input = { wallet, mode: "safe", limit: 20, includeAirdrops: false } as const;
const page = await client.checkWallet(input, { idempotencyKey });
return {
items: page.data.items,
summary: summarizeOpportunities(page.data.items),
nextInput: nextWalletPage(input, page),
};
}
Preview.5 accepts protected stale_price results. Earlier SDKs that reject this state must be upgraded. Full SDK reference ↗
02 / Display
A successful 200 response contains one page in data.items. It can include protected accounts alongside recoverable opportunities.
Display the returned action and fee/net estimate. An estimate is not recovered funds. Keep execution controls off while your access is analysis-only.
Show its protection reason. Never enable selection or count recovery value for action: "none" or protected items.
null means unavailable, not zero.summarizeOpportunities(allLoadedItems); do not add page summaries together.A positive token account has a reference price that is too old to use safely. It stays protected, with stale_price in both classification reason and protection arrays, action none, and execution unsupported. Accept this valid response state; do not treat it as zero value or “no price.”
Independently verified empty accounts may still appear. Stale SOL references leave USD estimates null. Failed required account or protection lookups still return 503 incomplete. Direct clients must enforce the full contract, including these constraints.
When data.pagination.hasMore is true, copy data.pagination.nextCursor to the next request's cursor. Keep the wallet and settings unchanged, and save a new key for that page. Do not paginate automatically.
For SDK users, nextWalletPage(input, page) prepares the next input or returns null when done:
// Use the saved first request and key.
const first = await analyzeWallet(wallet, savedFirstKey);
const allLoadedItems = [...first.items];
if (first.nextInput) {
// Persist first.nextInput and a NEW page key before calling.
const nextPage = await client.checkWallet(first.nextInput, {
idempotencyKey: savedPageKey,
});
allLoadedItems.push(...nextPage.data.items);
}
const totals = summarizeOpportunities(allLoadedItems);
03 / Retry safely
Persist each exact request body and its key before sending. The same body and key replay the stored result; they do not refresh the wallet.
| What happened? | What to do |
|---|---|
Timeout, lost/malformed response, request_in_progress or platform_unavailable | Retry the same body and key. The outcome may already exist. |
Parsed, retryable terminal failure such as 503 incomplete or platform_failure_recorded | Use a new saved key for a deliberate new attempt. Honor retryAfterMs. |
snapshot_expired, or a deliberate fresh wallet scan | Omit the old cursor and use a new saved key. |
| Nonretryable refusal or invalid input | Correct the cause before trying again. |
After a confirmed failed analysis and a server correction, start fresh without the old cursor. After an unknown outcome, reconcile with the original request first. Never add automatic retries or pagination.
Pace requests at least 31 seconds apart for the current invited rollout, with one request in flight. Honor retryAfterMs and your assigned limits.
Confirm your assigned limits before live use. The current invited rollout allows 2 requests/minute, one in flight, 100 provider stages per customer per UTC day, and a shared 300-stage pool.
A new first page reserves 3 stages; each subsequent page reserves 1. Failed or uncertain reservations remain spent. When exhausted, stop fresh attempts and wait for the next UTC day or contact us. Stored replays use no new provider stages while your key is active, but request-rate limits still apply.
The preview supports up to 400 token accounts per wallet and 200 per page. Start with 20 per page. Unsupported or unresolved required lookups can still produce incomplete.
This local fixture demonstrates analysis and pagination with synthetic responses. No API key, wallet connection or provider calls are needed.
npm install --save-exact --ignore-scripts @unclaimedsol/wallet-sdk@0.1.0-preview.5
node node_modules/@unclaimedsol/wallet-sdk/examples/reference/server.mjs
Open http://127.0.0.1:4317; stop with Ctrl + C. The example uses in-memory analysis sessions and a private journal for optional execution. Public hosting needs authentication, durable shared state and concurrency controls.
Request partner access or use your existing partner channel. Share your application, intended analysis flow and expected volume. Receive your activated key privately; never include it in an agent prompt.
Free or invoiced pilot terms are agreed per partner. Metering does not prove payment collection. Recovery service fees and Solana network costs are separate when execution is enabled.
Start with fixture checks, then agree a bounded live test of analysis, user-requested pagination, exact replay and error handling. Previous owner test allowances cannot be reused. Independent partner acceptance remains pending.
Execution is separately admitted. The pilot builds one direct transaction for 1–10 explicitly selected empty, non-native classic SPL accounts. Token-2022, collectibles, positive-balance burns, Excess SOL, swaps and gasless are outside this execution scope. Broader analysis does not make those items executable.
createPartnerConsent on your server; the wallet signs its exact UTF-8 message. Attach the canonical base64 signature to the helper’s proof as partnerAttribution. Declining message signing omits that field and preserves ordinary recovery without partner credit or website cashback.client.build(input, session.token, { idempotencyKey }). Retain the accepted receipt before showing fresh amounts, service fee, network estimate and funding. A null receipt means nothing was built. Explain that the existing instruction can permanently burn tokens deposited after validation.lastValidBlockHeight. Validate the unchanged message, payer and all required signatures. Save exact signed bytes, signature and transaction ID before your backend submits. A failed read, expiry or unknown send requires reconciliation, with no automatic rebuild or resend.client.recordExecution({ wallet, transactions }, receipt.token), using the original transaction ID and known signature. Record retries retain the same receipt and data; they take no idempotency key. Reload saved work after interruption. Never change signed work to not_signed.The SDK never signs or submits. Keep API, receipt/session and provider credentials on the server. The loopback reference is single-process; public hosting needs application authentication, durable shared state and concurrency controls. Do not expose its local /pilot route as a hosted service.
| State | Display and action |
|---|---|
| Recovery pending or unknown | Retain exact work and reconcile. No recovered-value claim. |
| Recovery verified; credit pending or held | Show verified recovery separately. Do not label the credit as applied or paid. |
| Credit applied | Credited earnings are 20% of verified eligible service fees, floored once per immutable batch. This is not payout or a share of principal. |
| Attribution not requested or not eligible | No partner earnings; retain the recovery outcome separately. |
The existing manual payout minimum is 0.1 SOL. Build estimates, metering and credited earnings do not establish payment collection, payout or profitable partner economics.
held field although separately bound canonical audit evidence proves published credit applied once. The later engine correction fixes new first responses; saved terminal responses were not rewritten. Do not interpret arbitrary held receipts as applied. Inconsistent historical metadata needs evidence-based reconciliation.Begin with the fixture and record setup time and friction. Before live work, agree the tester, exact application revision, key/enrollment, supported wallet/account scope, attribution choice, finite UTC window, provider/API/submission/signing limits, fee ceiling and shutdown owner. Prior owner-test allowances cannot be reused. Preserve receipt recording when execution is switched off.
Record who actually operated the integration and distinguish completed, failed and unresolved attempts. Verified recovery, credit, actual network cost and provider cost need separate evidence. One independent test does not establish recurring adoption or a paying customer. Public naming, quotes and results require separate permission.
Wallet API credentials are separate from existing integrations:
Contract: preview.5 analysis.yaml · SDK reference · Agent integration brief