Stats and Wallet - 2DAICommunity/2dai-cloud-sdk GitHub Wiki

Stats & Wallet

Two read-only namespaces: client.stats (account analytics — scope read) and client.finance (wallet data — the opt-in finance scope, except where noted). Neither can change anything: no generation, no organising, and — by construction — no money movement.

client.stats

The numbers behind the dashboard's home page, from code. All three methods need only the read scope.

stats.overview()

Storage, counters, streak, smart-collection counts and per-tool recents:

const o = await client.stats.overview();

o.storage;        // { usedBytes, quotaBytes, imageBytes, videoBytes, uploadBytes, trashBytes }
o.counters;       // { totalCreations, weekCreations, weekDelta, privateFolders,
                  //   sharedFolders, favoriteFolders, trashCreations }
o.streak.days;    // consecutive days with at least one creation
o.smartSummaries; // [{ id: 'faces', count }, …] — the smart-collection lenses
o.toolsRecent;    // { [toolKind]: { total, recentIds: [creationId, …] } }
o.cache;          // { computedAt, ageSeconds, stale }

overview (and top) are served from a server-side cache rather than recomputed per call — that's what makes them cheap enough to poll. The cache block tells you how fresh the numbers are: stale: true means the cache had aged out and a background refresh has been kicked — the response you got is the previous snapshot, so re-fetch in a bit for fresher numbers.

stats.generations({ days })

Generation volume and spend over a window — days is 30 (default) or 90, anything else is a 400:

const g = await client.stats.generations({ days: 30 });

g.totals;    // { count, costUsd }
g.byDay;     // [{ day, count, costUsd }, …]
g.byTool;    // [{ toolKind, count, costUsd }, …]
g.bySource;  // { machine: { count, costUsd }, studio: { count, costUsd } }

bySource splits your volume between machine (SDK, MCP, raw REST — work made through keys) and studio (the web app) — the quickest way to see what your automation costs versus your hands-on work. Unlike overview, this is a live aggregate; the server memoizes it for ~60 s per window, so tight loops read the memo rather than hammering the aggregation.

stats.top()

What you actually use — most-used references, styles and keywords:

const t = await client.stats.top();

t.topRefs;      // [{ referenceCreationId, useCount, suggestedTool, latestUsedAt,
                //    suggestedConfig: { quality, style, motionStyle, requestedDuration,
                //                       isEnhanced, frameInterpolation } }, …]
t.topStyles;    // [{ style, count }, …]
t.topKeywords;  // [{ tag, count }, …]
t.cache;        // same freshness contract as overview

suggestedConfig is the configuration the studio would pre-fill for that reference — your own most-common settings when using it — so an agent can re-run "the usual" without hardcoding anything:

const fav = t.topRefs[0];
await client.generate.imageWithRefs({
  tool: (fav.suggestedTool as any) ?? 'character-ref',
  prompt: 'in a winter forest',
  refCreationIds: [fav.referenceCreationId],
  quality: fav.suggestedConfig.quality ?? undefined,
});

client.finance

Read-only wallet data, named 1:1 with the finance key scope. The scope is opt-in and never pre-selected when minting a key; a key without it gets 403 MISSING_SCOPE with requiredScope: 'finance' on every method below except the last two. And it is read-only in the strongest sense: deposits, withdrawals, swaps and locks simply don't exist on the API-key surface, so no key — whatever its scopes — can move funds. See Keys & Scopes.

finance.wallet()

The balance snapshot:

const w = await client.finance.wallet();
w.tokens;             // $2DAI token balance (withdrawable, via the dashboard)
w.creditUsd;          // USD credit — what generations burn
w.tier;               // effective tier id, 'tier0'..'tier5'
w.pendingWithdrawal;  // a withdrawal is mid-flight
w.pendingSwap;        // a swap is mid-flight
w.lockActive;         // a staking lock is running

finance.tier()

Your effective tier and the four legs it is the max of — useful when you want to know why you're the tier you are:

const t = await client.finance.tier();
t.effective;             // e.g. 'tier3'
t.legs;                  // { db, watermark, holdWatermark, dynamic } — each a tier id or null

db is an explicit assignment, watermark comes from your staking lock, holdWatermark is the 24-hour hold that keeps your tier stable across a re-lock or swap, and dynamic is what your live wallet value qualifies for.

finance.lock()

The staking-lock lifecycle:

const l = await client.finance.lock();
// { kind: 'none' }
// { kind: 'active',        lock: {…}, remainingMs }
// { kind: 'expiring-soon', lock: {…}, remainingMs }   // under 24h left (or ended, pending the sweep)

finance.transactions({ limit?, page? })

Deposit / withdrawal / swap history, newest first. page is 0-based (the one offset in the SDK that is — it mirrors the wallet dashboard) and limit clamps to 5..50 (default 20):

const tx = await client.finance.transactions({ limit: 20, page: 0 });
tx.transactions;  // [{ id, type, status, asset: '2DAI'|'USD', amount, hash, date,
                  //    processedDate, counterparty, reason, needsAdminReview, secondaryUsd }, …]
tx.hasMore;       // fetch page + 1 for older rows

amount is a magnitude — the direction comes from type. secondaryUsd is set on swap rows only: the USD credited alongside the debited tokens.

finance.balanceHistory({ limit? }) / finance.creditHistory({ limit? })

Chart points over a window of limit days (7..365, default 90) — token balance for the first, USD credit for the second:

const h = await client.finance.creditHistory({ limit: 30 });
h.currentCredit;  // (balanceHistory: currentBalance)
h.points;         // [{ date, balance, delta, type }, …] — balance is AFTER the event

finance.creditSources({ days? })

Where credit came from — and went — over days (1..365, default 30):

const s = await client.finance.creditSources({ days: 30 });
s.accruedUsd;   // earned (e.g. staking accrual)
s.bonusesUsd;   // bonuses
s.swappedUsd;   // swapped in from tokens
s.spentUsd;     // burned on generations

finance.tokenPrice() — scope read

The cached $2DAI/USD quote. Machine calls never trigger a live price refresh — you get the platform's cache, with its age:

const p = await client.finance.tokenPrice();
p.usdPrice;   // the quote
p.asOf;       // when it was computed (or null)
p.staleMs;    // how old the cache is (or null)

When no cache exists yet the server answers 503 PRICE_UNAVAILABLE — retry later rather than treating it as a zero price.

finance.tiers() — scope read

The public tier catalogue — names, USD lock-value floors and per-tier settings, the same data the pricing page shows:

const tiers = await client.finance.tiers();
// [{ key: 'tier1', name: 'Explorer', valueUSD: 300, icon?, color?, settings? }, …]

Putting it together

A morning digest that never risks a cent:

const [o, g, w] = await Promise.all([
  client.stats.overview(),
  client.stats.generations({ days: 30 }),
  client.finance.wallet(),          // needs the finance scope
]);

console.log(
  `${o.counters.totalCreations} creations (+${o.counters.weekCreations} this week), ` +
  `streak ${o.streak.days}d — 30d spend $${g.totals.costUsd.toFixed(2)} ` +
  `(machine ${g.bySource.machine.count} / studio ${g.bySource.studio.count}) — ` +
  `credit $${w.creditUsd.toFixed(2)}`
);