feat(console): rewire treasury to org-scoped backend#65
Conversation
Treasury screen + store read model now use the real org-scoped endpoints: GET /orgs/:id/treasury (managed address, registered, per-token encrypted balances), POST /orgs/:id/treasury/deposit (Add funds — minor-unit USDC + fresh idempotency key, reflects submitted/confirmed), and GET /orgs/:id/treasury/deposits (recent deposits). Removed the no-backend Stellar-era actions (public balance, receive, send-public, prove-*). Dashboard + Pay updated for the new shape; demo stubs reseeded. Epic #58 step D. Closes #64.
📝 WalkthroughWalkthroughThe console treasury flow now uses org-scoped treasury views, managed encrypted balances, direct deposits, and deposit history. Legacy public-balance, funding, and proof operations are removed from the API, demo implementation, store, and Treasury screen. ChangesOrg-scoped treasury flow
Estimated code review effort: 4 (Complex) | ~60 minutes Sequence Diagram(s)sequenceDiagram
participant TreasuryScreen
participant ConsoleApi
participant OrgTreasuryEndpoints
TreasuryScreen->>ConsoleApi: Load treasury and deposits
ConsoleApi->>OrgTreasuryEndpoints: GET org-scoped treasury data
OrgTreasuryEndpoints-->>ConsoleApi: Treasury view and deposit history
ConsoleApi-->>TreasuryScreen: Render balances and deposits
TreasuryScreen->>ConsoleApi: Submit deposit with idempotency key
ConsoleApi->>OrgTreasuryEndpoints: POST treasury deposit
OrgTreasuryEndpoints-->>ConsoleApi: Deposit result and transaction hash
ConsoleApi-->>TreasuryScreen: Refresh treasury and show result
Possibly related PRs
🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
✨ Finishing Touches📝 Generate docstrings
🧪 Generate unit tests (beta)
Comment |
|
|
||
| const fromName = accounts.find((a) => a.id === fromAccountId)?.name ?? ""; | ||
| const fromBalance = treasury?.accounts.find((a) => a.account.id === fromAccountId)?.balance?.amount; | ||
| const fromBalance: string | undefined = undefined; |
There was a problem hiding this comment.
When an account is selected, this value is always undefined, so the Pay form now renders Balance: Private for every source account. Users lose the decoded balance check that previously warned them before submitting an overdrawn payment, and the failure only appears after the backend rejects or cannot settle the payment.
Prompt To Fix With AI
This is a comment left during a code review.
Path: apps/console/src/screens/Pay.tsx
Line: 50
Comment:
**Source Balance Always Hidden**
When an account is selected, this value is always `undefined`, so the Pay form now renders `Balance: Private` for every source account. Users lose the decoded balance check that previously warned them before submitting an overdrawn payment, and the failure only appears after the backend rejects or cannot settle the payment.
How can I resolve this? If you propose a fix, please make it concise.|
Greptile (Pay source balance always undefined): acknowledged, deferred to step B by design. The old |
There was a problem hiding this comment.
Actionable comments posted: 5
🧹 Nitpick comments (4)
apps/console/src/demo/data.ts (1)
436-438: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick winSource chain-scoped
tokenIds from@benzo/configinstead of hardcoding.The
"avalanche-fuji:usdc"/"avalanche-fuji:eurc"identifiers (andsourceChain: "avalanche-fuji"in the seeded deposits) embed the chain directly. Per the project convention these chain/token definitions should come from@benzo/configwith aTODO(@benzo/config)fallback until that package is published, so demo and real paths stay in sync when the chain changes.As per coding guidelines: "Use chain definitions and addresses from
@benzo/config(keep theTODO(@benzo/config)vendor fallback until that package is published)."🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@apps/console/src/demo/data.ts` around lines 436 - 438, Replace hardcoded Avalanche Fuji token IDs and seeded deposit sourceChain values in the demo data with the corresponding chain-scoped definitions from `@benzo/config`, retaining the TODO(`@benzo/config`) vendor fallback until the package is available. Update the balances entries and seeded deposits consistently so demo and production paths share the same chain configuration.Source: Coding guidelines
apps/console/src/demo/api.ts (1)
159-159: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick winHardcoded
tokenId/sourceChainshould come from@benzo/config.
tokenId: avalanche-fuji:${body.token}(andsourceChain: "avalanche-fuji"on Line 154) embed the chain. Same convention as flagged indata.ts— prefer@benzo/configchain/token definitions with theTODO(@benzo/config)fallback.As per coding guidelines: "Use chain definitions and addresses from
@benzo/config(keep theTODO(@benzo/config)vendor fallback until that package is published)."🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@apps/console/src/demo/api.ts` at line 159, Replace the hardcoded "avalanche-fuji" values in the demo API’s sourceChain and tokenId construction with the corresponding chain and token definitions from `@benzo/config`, preserving the TODO(`@benzo/config`) vendor fallback until the package is available. Update the affected handler around the returned object and sourceChain assignment consistently.Source: Coding guidelines
apps/console/src/screens/Pay.tsx (1)
50-50: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value
fromBalanceis now a permanent no-op; the "Balance" UI branch is dead code.Since
fromBalanceis hardcoded toundefinedand never reassigned, thefromBalance != null ? <Amount .../> : <span>Private</span>branch at Line 169 will always take theelsepath. This matches the PR intent (no numeric treasury balance shown), but leaving the always-false condition and unusedstring | undefinedtype annotation as-is obscures that this is an intentional placeholder rather than a bug. Consider collapsing to the literal "Private" render and adding a short// TODO:note tying it to the follow-up work for per-account balances (or the CCTP funding seam) so future readers don't mistake it for leftover dead code.♻️ Optional simplification
- const fromBalance: string | undefined = undefined; + // TODO(treasury): wire up real per-account balance once available; until then, balances are always private.- {fromBalance != null ? <Amount minor={fromBalance} className="font-medium text-fg" /> : <span className="text-fg">Private</span>} + <span className="text-fg">Private</span>Also applies to: 165-172
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@apps/console/src/screens/Pay.tsx` at line 50, Replace the unused `fromBalance` variable and its always-false conditional in the payment balance rendering with the literal “Private” output. Add a brief TODO comment documenting the intended follow-up for per-account balances or the CCTP funding seam, so the placeholder is clearly intentional.apps/console/src/screens/Dashboard.test.tsx (1)
25-30: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low valueDuplicate treasury fixture literal.
The same detailed
treasuryobject (address/custody/registered/consented/custodyConsent/balances) is repeated verbatim inbeforeEachand again in the "collapses setup into a compact banner" test. Extracting a sharedmakeTreasury()/constant fixture would remove the duplication and make future contract changes (e.g. toTreasuryView) a one-line update instead of a multi-site edit.Also applies to: 59-66
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@apps/console/src/screens/Dashboard.test.tsx` around lines 25 - 30, The detailed treasury fixture is duplicated between beforeEach and the “collapses setup into a compact banner” test. Extract the shared address, custody, consent, and balances data into a makeTreasury() helper or constant, then reuse it in both locations so TreasuryView contract changes require updating only one fixture.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@apps/console/src/demo/api.ts`:
- Around line 161-168: Handle an unknown before cursor explicitly in
treasuryDeposits: when query.before is provided and findIndex returns -1, return
an empty deposits array with no nextCursor; otherwise use index + 1 for the
start position, while retaining 0 when before is unset. Remove the unnecessary
Math.max call and preserve normal pagination for valid cursors.
In `@apps/console/src/screens/Treasury.tsx`:
- Line 65: Change the initial value of the amount state in the Treasury
component from "1000" to an empty string so the money-movement form starts with
no prefilled deposit amount.
- Around line 118-129: Persist the deposit idempotency key across retries
instead of generating it inside each addFunds submission. Add a useRef near the
other state in the Treasury component, initialize it with
randomIdempotencyKey(), reuse its value in api.depositToTreasury, and clear or
replace the ref only after a successful deposit; retain the same key when the
request fails or times out.
- Around line 83-103: Prevent stale deposit requests from updating state after
an organization switch. In loadDeposits and its useEffect, add a request-scoped
cancellation flag or generation ref tied to activeOrgId, and guard every
setDeposits, setDepositsError, and setDepositsLoading call—including success,
error, and finally paths—so only the latest request can update state.
- Around line 384-388: Update the KV component’s value prop to use an imported
ReactNode type from "react" instead of React.ReactNode, matching the typing
pattern used by other console components and avoiding reliance on the React UMD
global.
---
Nitpick comments:
In `@apps/console/src/demo/api.ts`:
- Line 159: Replace the hardcoded "avalanche-fuji" values in the demo API’s
sourceChain and tokenId construction with the corresponding chain and token
definitions from `@benzo/config`, preserving the TODO(`@benzo/config`) vendor
fallback until the package is available. Update the affected handler around the
returned object and sourceChain assignment consistently.
In `@apps/console/src/demo/data.ts`:
- Around line 436-438: Replace hardcoded Avalanche Fuji token IDs and seeded
deposit sourceChain values in the demo data with the corresponding chain-scoped
definitions from `@benzo/config`, retaining the TODO(`@benzo/config`) vendor
fallback until the package is available. Update the balances entries and seeded
deposits consistently so demo and production paths share the same chain
configuration.
In `@apps/console/src/screens/Dashboard.test.tsx`:
- Around line 25-30: The detailed treasury fixture is duplicated between
beforeEach and the “collapses setup into a compact banner” test. Extract the
shared address, custody, consent, and balances data into a makeTreasury() helper
or constant, then reuse it in both locations so TreasuryView contract changes
require updating only one fixture.
In `@apps/console/src/screens/Pay.tsx`:
- Line 50: Replace the unused `fromBalance` variable and its always-false
conditional in the payment balance rendering with the literal “Private” output.
Add a brief TODO comment documenting the intended follow-up for per-account
balances or the CCTP funding seam, so the placeholder is clearly intentional.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: defaults
Review profile: CHILL
Plan: Pro Plus
Run ID: 89bf2a86-6a4c-4e20-a5c4-4b2bb1004118
📒 Files selected for processing (12)
apps/console/src/demo/api.tsapps/console/src/demo/data.tsapps/console/src/lib/api.test.tsapps/console/src/lib/api.tsapps/console/src/lib/store.test.tsxapps/console/src/lib/store.tsxapps/console/src/screens/Dashboard.test.tsxapps/console/src/screens/Dashboard.tsxapps/console/src/screens/Pay.tsxapps/console/src/screens/Treasury.test.tsxapps/console/src/screens/Treasury.tsxpackages/types/src/api.ts
| treasuryDeposits: async (_orgId: string, query: { limit?: number; before?: string } = {}): Promise<TreasuryDepositsResponse> => { | ||
| await delay(READ); | ||
| const start = query.before ? db.treasuryDeposits.findIndex((d) => d.id === query.before) + 1 : 0; | ||
| const from = Math.max(0, start); | ||
| const limit = query.limit ?? 20; | ||
| const deposits = db.treasuryDeposits.slice(from, from + limit); | ||
| const next = db.treasuryDeposits[from + limit]?.id; | ||
| return { deposits: clone(deposits), nextCursor: next }; |
There was a problem hiding this comment.
🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win
Unknown before cursor silently returns the first page.
When query.before is provided but no matching deposit exists, findIndex(...) returns -1, so start becomes 0 and the method returns the first page (with a nextCursor) rather than an empty result. A stale/invalid cursor then re-serves page 1, which can loop a "load more" flow. Math.max(0, start) is also dead once before is unset (0) or found (idx+1 ≥ 1).
🐛 Proposed fix
- const start = query.before ? db.treasuryDeposits.findIndex((d) => d.id === query.before) + 1 : 0;
- const from = Math.max(0, start);
- const limit = query.limit ?? 20;
+ const limit = query.limit ?? 20;
+ if (query.before) {
+ const idx = db.treasuryDeposits.findIndex((d) => d.id === query.before);
+ if (idx === -1) return { deposits: [], nextCursor: undefined };
+ }
+ const from = query.before
+ ? db.treasuryDeposits.findIndex((d) => d.id === query.before) + 1
+ : 0;
const deposits = db.treasuryDeposits.slice(from, from + limit);
const next = db.treasuryDeposits[from + limit]?.id;
return { deposits: clone(deposits), nextCursor: next };📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| treasuryDeposits: async (_orgId: string, query: { limit?: number; before?: string } = {}): Promise<TreasuryDepositsResponse> => { | |
| await delay(READ); | |
| const start = query.before ? db.treasuryDeposits.findIndex((d) => d.id === query.before) + 1 : 0; | |
| const from = Math.max(0, start); | |
| const limit = query.limit ?? 20; | |
| const deposits = db.treasuryDeposits.slice(from, from + limit); | |
| const next = db.treasuryDeposits[from + limit]?.id; | |
| return { deposits: clone(deposits), nextCursor: next }; | |
| treasuryDeposits: async (_orgId: string, query: { limit?: number; before?: string } = {}): Promise<TreasuryDepositsResponse> => { | |
| await delay(READ); | |
| const limit = query.limit ?? 20; | |
| if (query.before) { | |
| const idx = db.treasuryDeposits.findIndex((d) => d.id === query.before); | |
| if (idx === -1) return { deposits: [], nextCursor: undefined }; | |
| } | |
| const from = query.before | |
| ? db.treasuryDeposits.findIndex((d) => d.id === query.before) + 1 | |
| : 0; | |
| const deposits = db.treasuryDeposits.slice(from, from + limit); | |
| const next = db.treasuryDeposits[from + limit]?.id; | |
| return { deposits: clone(deposits), nextCursor: next }; |
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@apps/console/src/demo/api.ts` around lines 161 - 168, Handle an unknown
before cursor explicitly in treasuryDeposits: when query.before is provided and
findIndex returns -1, return an empty deposits array with no nextCursor;
otherwise use index + 1 for the start position, while retaining 0 when before is
unset. Remove the unnecessary Math.max call and preserve normal pagination for
valid cursors.
| const activeOrgId = activeOrg?.id; | ||
| const canAddFunds = canManageTreasury(session?.role); | ||
|
|
||
| const [amount, setAmount] = useState("1000"); |
There was a problem hiding this comment.
🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win
Money-movement form defaults to a prefilled 1000 USDC amount.
Prefilling a nonzero, sizable amount on a deposit form increases the risk of an accidental large deposit if a user clicks "Add funds" without adjusting it first. Consider defaulting to an empty string.
🔧 Proposed fix
- const [amount, setAmount] = useState("1000");
+ const [amount, setAmount] = useState("");📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| const [amount, setAmount] = useState("1000"); | |
| const [amount, setAmount] = useState(""); |
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@apps/console/src/screens/Treasury.tsx` at line 65, Change the initial value
of the amount state in the Treasury component from "1000" to an empty string so
the money-movement form starts with no prefilled deposit amount.
| const loadDeposits = useCallback(async () => { | ||
| if (!activeOrgId) { | ||
| setDeposits([]); | ||
| setDepositsError(null); | ||
| return; | ||
| } | ||
| // Refresh OUTSIDE the try: a settled shield must not be flipped to failed by a | ||
| // transient refresh error. | ||
| if (confirmed) { | ||
| await Promise.all([refresh(), loadPublic()]).catch(() => {}); | ||
| } | ||
| } | ||
|
|
||
| // ---- Send to a wallet (real public on-chain USDC payment) ----------------- | ||
| const [sendTo, setSendTo] = useState(""); | ||
| const [sendAmt, setSendAmt] = useState(""); | ||
| const [confirmSend, setConfirmSend] = useState(false); | ||
| const [busySend, setBusySend] = useState(false); | ||
| const [sendResult, setSendResult] = useState<{ onChain: boolean; txHash?: string; error?: string } | null>(null); | ||
| const addrLooksValid = /^0x[a-fA-F0-9]{40}$/.test(sendTo.trim()); | ||
|
|
||
| async function sendPublic(): Promise<boolean> { | ||
| setBusySend(true); | ||
| setSendResult(null); | ||
| setDepositsLoading(true); | ||
| try { | ||
| const r = await api.treasurySendPublic(sendTo.trim(), sendAmt); | ||
| if (r.onChain) { | ||
| setSendResult({ onChain: true, txHash: r.txHash }); | ||
| toast({ title: `Sent ${sendAmt} USDC to a wallet`, tone: "success" }); | ||
| setSendTo(""); | ||
| setSendAmt(""); | ||
| await loadPublic(); | ||
| return true; | ||
| } | ||
| setSendResult({ onChain: false, error: r.error }); | ||
| toast({ title: r.error ?? "Couldn't send", tone: "danger" }); | ||
| return false; | ||
| const response = await api.treasuryDeposits(activeOrgId, { limit: DEPOSIT_LIMIT }); | ||
| setDeposits(response.deposits); | ||
| setDepositsError(null); | ||
| } catch (e) { | ||
| toast({ title: friendlyError(e), tone: "danger" }); | ||
| setSendResult({ onChain: false, error: friendlyError(e) }); | ||
| return false; | ||
| setDepositsError(friendlyError(e, "Couldn't load treasury deposits.")); | ||
| } finally { | ||
| setBusySend(false); | ||
| setDepositsLoading(false); | ||
| } | ||
| } | ||
| }, [activeOrgId]); | ||
|
|
||
| // ---- Receive (address + QR) ---------------------------------------------- | ||
| const [receiveOpen, setReceiveOpen] = useState(false); | ||
| const [recv, setRecv] = useState<{ address: string; asset: string; issuer: string; live: boolean } | null>(null); | ||
| const [recvLoading, setRecvLoading] = useState(false); | ||
| useEffect(() => { | ||
| void loadDeposits(); | ||
| }, [loadDeposits]); |
There was a problem hiding this comment.
🩺 Stability & Availability | 🟡 Minor | ⚡ Quick win
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
set -euo pipefail
# Map the target file and nearby relevant files.
ast-grep outline apps/console/src/screens/Treasury.tsx --view expanded || true
ast-grep outline apps/console/src/screens/store.tsx --view expanded || true
# Read the relevant ranges with line numbers.
echo "---- Treasury.tsx ----"
sed -n '1,220p' apps/console/src/screens/Treasury.tsx | cat -n
echo "---- store.tsx ----"
sed -n '1,260p' apps/console/src/screens/store.tsx | cat -n
# Find any cancellation / request guard patterns in the screen directory.
rg -n "cancelled|requestOrgIdRef|useRef\\(|activeOrgId" apps/console/src/screens -g '*.tsx'Repository: Miny-Labs/benzo-console
Length of output: 11029
🏁 Script executed:
#!/bin/bash
set -euo pipefail
# Locate the console store implementation and any cancelled-flag refresh pattern.
fd -a 'store.tsx' apps/console/src
fd -a 'store.ts' apps/console/src
fd -a 'store.*' apps/console/src
rg -n "cancelled|refresh\\s*=|useConsole|activeOrgId" apps/console/src -g '*.ts' -g '*.tsx'
# Read the most likely store file if found.
for f in $(fd -a 'store.tsx' apps/console/src; fd -a 'store.ts' apps/console/src); do
echo "---- $f ----"
sed -n '1,260p' "$f" | cat -n
doneRepository: Miny-Labs/benzo-console
Length of output: 28921
Guard deposit-history requests on org switch.
loadDeposits can still write an older org’s response after activeOrgId changes. Add a request-scoped ref or cancel flag so setDeposits, setDepositsError, and setDepositsLoading only update for the latest org.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@apps/console/src/screens/Treasury.tsx` around lines 83 - 103, Prevent stale
deposit requests from updating state after an organization switch. In
loadDeposits and its useEffect, add a request-scoped cancellation flag or
generation ref tied to activeOrgId, and guard every setDeposits,
setDepositsError, and setDepositsLoading call—including success, error, and
finally paths—so only the latest request can update state.
| setBusyDeposit(true); | ||
| setDepositResult(null); | ||
| try { | ||
| const result = await api.depositToTreasury(activeOrgId, { | ||
| amount: minor, | ||
| token: FUNDING_TOKEN, | ||
| idempotencyKey: randomIdempotencyKey(), | ||
| }); | ||
| setDepositResult(result); | ||
| toast({ title: `${depositStatusLabel(result.status)} - ${formatMoney(result.amount, TOKEN_META[result.token].decimals, TOKEN_META[result.token].symbol)}`, tone: "success" }); | ||
| setAmount(""); | ||
| await Promise.allSettled([refresh(), loadDeposits()]); |
There was a problem hiding this comment.
🗄️ Data Integrity & Integration | 🟠 Major | ⚡ Quick win
Idempotency key is regenerated on every submit, defeating retry protection.
randomIdempotencyKey() is called fresh inside addFunds() each time it runs. If a deposit request succeeds server-side but the client never sees the response (timeout/dropped connection), a user retry generates a brand-new key, so the backend has no way to recognize it as the same logical deposit — risking a duplicate on-chain deposit. Idempotency keys are only useful if they persist across retries of the same logical operation.
🔧 Proposed fix: persist the key until success
+ const pendingIdempotencyKey = useRef<string | null>(null);
+
async function addFunds() {
if (!activeOrgId) return;
let minor = "0";
try {
minor = usdcToMinor(amount);
} catch {
setDepositResult(null);
return;
}
if (!MINOR_UNITS.test(minor)) {
setDepositResult(null);
return;
}
setBusyDeposit(true);
setDepositResult(null);
try {
+ if (!pendingIdempotencyKey.current) pendingIdempotencyKey.current = randomIdempotencyKey();
const result = await api.depositToTreasury(activeOrgId, {
amount: minor,
token: FUNDING_TOKEN,
- idempotencyKey: randomIdempotencyKey(),
+ idempotencyKey: pendingIdempotencyKey.current,
});
setDepositResult(result);
+ pendingIdempotencyKey.current = null;
toast(...);(requires adding useRef to the react import)
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@apps/console/src/screens/Treasury.tsx` around lines 118 - 129, Persist the
deposit idempotency key across retries instead of generating it inside each
addFunds submission. Add a useRef near the other state in the Treasury
component, initialize it with randomIdempotencyKey(), reuse its value in
api.depositToTreasury, and clear or replace the ref only after a successful
deposit; retain the same key when the request fails or times out.
| function KV({ label, value }: { label: string; value: React.ReactNode }) { | ||
| return ( | ||
| <div className="space-y-2"> | ||
| <div className="flex items-center gap-2 font-semibold text-white"> | ||
| <ShieldCheck size={15} /> {result.headline} | ||
| </div> | ||
| {result.ref?.root ? ( | ||
| <motion.div | ||
| className="rounded-lg border border-white/10 bg-white/[0.04] px-3 py-2" | ||
| style={{ transformPerspective: 640 }} | ||
| initial={reduce ? false : { rotateX: 90, opacity: 0 }} | ||
| animate={{ rotateX: 0, opacity: 1 }} | ||
| transition={{ duration: 0.5, ease: EASE }} | ||
| data-testid="prove-merkle-root" | ||
| > | ||
| <div className="text-[11px] uppercase tracking-wide text-white/45">Merkle root</div> | ||
| <div className="break-all font-mono text-[12px] text-white/80">{result.ref.root}</div> | ||
| </motion.div> | ||
| ) : null} | ||
| <div className="flex items-center justify-between gap-2 text-white/60"> | ||
| <span>Verified on-chain. Anyone can re-check it.</span> | ||
| {result.ref ? <OnChainDetail refData={result.ref} /> : null} | ||
| </div> | ||
| <div className="flex items-center justify-between gap-4 py-2"> | ||
| <dt className="text-muted">{label}</dt> | ||
| <dd className="font-medium text-fg">{value}</dd> |
There was a problem hiding this comment.
🎯 Functional Correctness | 🟠 Major | ⚡ Quick win
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
fd tsconfig.json --exec cat {}
rg -n 'allowUmdGlobalAccess' .
rg -n 'React\.' apps/console/src --type=ts --type=tsx -g '!**/node_modules/**'Repository: Miny-Labs/benzo-console
Length of output: 3560
🏁 Script executed:
#!/bin/bash
set -euo pipefail
printf '\n== apps/console/tsconfig.json ==\n'
cat -n apps/console/tsconfig.json
printf '\n== apps/console/src/screens/Treasury.tsx (relevant excerpt) ==\n'
sed -n '360,410p' apps/console/src/screens/Treasury.tsx
printf '\n== ReactNode usage in apps/console/src ==\n'
rg -n 'React\.ReactNode|type ReactNode|import .*ReactNode' apps/console/src
printf '\n== tsconfig chain search for allowUmdGlobalAccess ==\n'
rg -n 'allowUmdGlobalAccess' .Repository: Miny-Labs/benzo-console
Length of output: 4695
🏁 Script executed:
#!/bin/bash
set -euo pipefail
printf '\n== package manifests mentioning react/typescript ==\n'
rg -n '"(`@types/react`|react|typescript)"' package.json apps/console/package.json packages/*/package.json -g '!**/node_modules/**'
printf '\n== local TypeScript probe ==\n'
tmpdir="$(mktemp -d)"
cat > "$tmpdir/tsconfig.json" <<'JSON'
{
"compilerOptions": {
"target": "ES2022",
"module": "ESNext",
"moduleResolution": "Bundler",
"jsx": "react-jsx",
"strict": true,
"skipLibCheck": true,
"noEmit": true
},
"include": ["index.tsx"]
}
JSON
cat > "$tmpdir/index.tsx" <<'TSX'
function KV({ value }: { value: React.ReactNode }) {
return value;
}
export {};
TSX
if command -v tsc >/dev/null 2>&1; then
(cd "$tmpdir" && tsc -p tsconfig.json) || true
else
echo "tsc not available"
fiRepository: Miny-Labs/benzo-console
Length of output: 1347
Import ReactNode here instead of using React.ReactNode
apps/console/tsconfig.json doesn’t enable allowUmdGlobalAccess, so React.ReactNode won’t resolve in this module. Use a type import from "react" like the other console components.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@apps/console/src/screens/Treasury.tsx` around lines 384 - 388, Update the KV
component’s value prop to use an imported ReactNode type from "react" instead of
React.ReactNode, matching the typing pattern used by other console components
and avoiding reliance on the React UMD global.
Epic: #58 · Step D (Treasury) · Closes #64
Rewires the Treasury screen + store read model off the demo-era (Stellar-legacy) endpoints onto the real org-scoped backend. Builds on auth (#60) and onboarding (#63).
What changed
lib/api.ts—orgTreasury(orgId)→GET /orgs/:id/treasury;depositToTreasury(orgId, {amount, token, idempotencyKey})→POST /orgs/:id/treasury/deposit;treasuryDeposits(orgId, {limit?, before?})→GET /orgs/:id/treasury/deposits. Removed the no-backend fns/paths (treasury,treasuryPublicBalance,treasuryReceive,treasurySendPublic,fundTreasury,proveBalance/Total/Solvency) — zero dead references remain.lib/store.tsx— thetreasuryread model is org-scoped:api.orgTreasury(activeOrg.id), fetched only when there's an active org; independent-read resilience preserved. (+ newstore.test.tsx.)screens/Treasury.tsx— real managed treasury: address (copy),registeredbadge, per-token encrypted balances; Add funds →depositToTreasury(USDC → minor units + freshidempotencyKey, reflects submitted/confirmed + tx); Recent deposits fromtreasuryDeposits. Dropped the no-backend actions. Trimmed ~892 → ~391 lines.Dashboard.tsx/Pay.tsx— updated for the new treasury shape (they read it).packages/types— treasury view + deposit + deposits-history types matching the backend.VITE_DEMO_MODE=1renders a populated Treasury.CCTP funding (
/orgs/:id/treasury/fund-intent) is intentionally left as aTODO(cctp)seam.Verification (all green)
typecheck·test(72) ·build·VITE_DEMO_MODE=1 build·biome lint .Summary by CodeRabbit
New Features
Bug Fixes
Greptile Summary
This PR moves Treasury onto the org-scoped backend. The main changes are:
Confidence Score: 5/5
The changed flow looks safe to merge after a small cleanup to Pay balance display.
apps/console/src/screens/Pay.tsx
Important Files Changed
Sequence Diagram
%%{init: {'theme': 'neutral'}}%% sequenceDiagram participant UI as Treasury screen participant Store as Console store participant API as lib/api.ts participant Backend as Org treasury backend Store->>API: orgTreasury(activeOrg.id) API->>Backend: GET /orgs/:id/treasury Backend-->>API: TreasuryView API-->>Store: Managed treasury state Store-->>UI: Address and balances UI->>API: "treasuryDeposits(orgId, { limit })" API->>Backend: GET /orgs/:id/treasury/deposits Backend-->>API: Deposit history API-->>UI: Recent deposits UI->>API: depositToTreasury(orgId, amount, token, idempotencyKey) API->>Backend: POST /orgs/:id/treasury/deposit Backend-->>API: Submitted or confirmed deposit API-->>UI: Deposit result UI->>Store: refresh()%%{init: {'theme': 'base', 'themeVariables': {"darkMode": true, "background": "#0d1117", "primaryColor": "#21262d", "primaryTextColor": "#e6edf3", "primaryBorderColor": "#8b949e", "lineColor": "#8b949e", "textColor": "#e6edf3", "edgeLabelBackground": "#161b22", "actorBkg": "#21262d", "actorBorder": "#8b949e", "actorTextColor": "#e6edf3", "actorLineColor": "#8b949e", "signalColor": "#8b949e", "signalTextColor": "#e6edf3", "noteBkgColor": "#373320", "noteBorderColor": "#d4a72c", "noteTextColor": "#f0e6c0", "labelBoxBkgColor": "#21262d", "labelBoxBorderColor": "#8b949e", "labelTextColor": "#e6edf3", "loopTextColor": "#e6edf3", "activationBkgColor": "#30363d", "activationBorderColor": "#8b949e"}}}%% sequenceDiagram participant UI as Treasury screen participant Store as Console store participant API as lib/api.ts participant Backend as Org treasury backend Store->>API: orgTreasury(activeOrg.id) API->>Backend: GET /orgs/:id/treasury Backend-->>API: TreasuryView API-->>Store: Managed treasury state Store-->>UI: Address and balances UI->>API: "treasuryDeposits(orgId, { limit })" API->>Backend: GET /orgs/:id/treasury/deposits Backend-->>API: Deposit history API-->>UI: Recent deposits UI->>API: depositToTreasury(orgId, amount, token, idempotencyKey) API->>Backend: POST /orgs/:id/treasury/deposit Backend-->>API: Submitted or confirmed deposit API-->>UI: Deposit result UI->>Store: refresh()Prompt To Fix All With AI
Reviews (1): Last reviewed commit: "feat(console): rewire treasury to org-sc..." | Re-trigger Greptile