Add mobile vault screen for deposit, withdraw, and escrow management - #521
Add mobile vault screen for deposit, withdraw, and escrow management#521BigManly4 wants to merge 1 commit into
Conversation
Port the vault sub-account flow from the Next.js wallet (frontend/wallet/app/vault/page.tsx and frontend/wallet/lib/vault.ts) to Expo/React Native, following the conventions already established by the bulk-payout and swap mobile ports. - frontend/mobile/lib/vault.ts: typed vault domain logic ported from the wallet lib (amount/delay parsing and formatting, address validation, withdrawal status/countdown helpers). Chain submission and reads are behind injectable VaultSubmit/VaultFetch parameters, matching the executeBulkPayout(rows, submitBatch) pattern already used in lib/bulkPayout.ts, since mobile has no passkey/session signing infrastructure ported yet. - frontend/mobile/lib/escrow.ts: claimable-balance escrow helpers (createEscrow, claimEscrow, reclaimEscrow, buildEscrowClaimants). The wallet's version is a one-line re-export of sdk/src/claimableBalance.ts; that re-export does not typecheck from frontend/mobile because the mobile TS program only resolves @stellar/stellar-sdk from frontend/mobile/node_modules and there is no hoisted root node_modules, so this file mirrors that implementation locally using the @stellar/stellar-sdk dependency mobile already has. - frontend/mobile/app/vault.tsx: React Native screen covering vault creation/attach, balance display, deposit, and the full withdrawal lifecycle (queue with delay, live countdown, execute once ready, cancel while pending). Styled to match the existing dark-theme mobile screens. Signing/submission and vault reads go through stub functions declared at the top of the screen (stubSubmitVaultTx, stubFetchVaultDetails) so the flow is exercisable today and can be swapped for real Soroban RPC calls once mobile signing lands. Verified: npm run typecheck is clean, and npx expo export --platform web bundles /vault as a static route with no errors. Not verified: an interactive simulator/device render.
|
@BigManly4 is attempting to deploy a commit to the miracle656's projects Team on Vercel. A member of the Team first needs to authorize it. |
|
@BigManly4 Great news! 🎉 Based on an automated assessment of this PR, the linked Wave issue(s) no longer count against your application limits. You can now already apply to more issues while waiting for a review of this PR. Keep up the great work! 🚀 |
Miracle656
left a comment
There was a problem hiding this comment.
The domain layer here is genuinely well done. lib/vault.ts splits cleanly into pure, testable helpers — xlmToStroops, stroopsToXlm, formatCountdown, withdrawalStatus, isWithdrawalReady — and the injectable VaultSubmit / VaultFetch seams are the right pattern for a port with no signing infra yet. It matches how lib/sep24.ts and lib/bulkPayout.ts are structured, which is what I want.
The doc comment in escrow.ts about claimable-balance semantics is also exactly the kind of thing worth writing down:
A Stellar claimable balance is consumed by the first successful claim. Once either party claims, the balance is gone — the other party's subsequent claim will be rejected by the Stellar network.
Two blockers though.
🚨 1. The screen is fully stubbed, and the UI doesn't say so
Every operation goes through a stub:
const stubSubmitVaultTx: VaultSubmit = async (call) => {
return { hash: `stub-${call.method}-${Date.now().toString(36)}` }
}
...
const address = `C-stub-${Date.now().toString(36)}`Nothing is deployed, nothing is submitted, and the vault details are fabricated. That's a defensible staging decision on its own — but I searched the screen for any user-facing indication and there is none. No demo banner, no disabled state, no "not yet functional" copy. The word "stub" appears only in variable names.
So the user flow is: open Vault → enter an amount → tap Deposit → see a success state with transaction hash stub-deposit-mf3k2j → believe funds moved into a time-locked vault.
In a wallet, showing a fabricated success for a financial operation is the most damaging version of this bug. Someone will believe they've locked funds they haven't.
Either of these is fine:
- Gate it. Render the screen behind a dev flag (
process.env.EXPO_PUBLIC_VAULT_DEMO === '1'), so it isn't reachable in a normal build. - Label it. A persistent, unmissable banner — "Demo mode — no transactions are submitted" — plus replacing the fake hash with something that can't be mistaken for a real one.
I'd prefer the first. This is the same class of issue I flagged on #515, where the NFT gallery shows fabricated holdings, so it's worth stating the general rule: never render fabricated financial state to a user without an unmistakable marker.
🚨 2. lib/escrow.ts is dead code
183 lines, and app/vault.tsx references it zero times. It isn't imported anywhere in the PR.
It's also structurally different from vault.ts — it takes raw Keypair objects (senderKeypair, claimantKeypair) and calls server.loadAccount() directly, rather than using an injectable signer seam. So it can't be wired into the screen as-is without first deciding where those keypairs come from, and that decision is exactly the one still open on #512 (which currently pulls a secret key out of unencrypted AsyncStorage — please don't follow that pattern here).
Please either wire it up in this PR or split it into its own, once the signing story is settled. Merging 183 lines of unreferenced key-handling code isn't something I want to do on the way past.
Smaller
buildClaimLink hardcodes a domain:
return `https://app.veil.xyz/claim/${balanceId}`Worth knowing there are currently three different app identities proposed across open PRs. Yours matches #508's (app.veil.xyz) — noting it as a data point. I'm settling the canonical value and will confirm; don't change it yet, but it should come from shared config rather than a literal once it's fixed.
Collision with #507 — the navigation shell adds a vault route stub. Yours supersedes it; just rebase once #507 lands.
Tests — xlmToStroops / stroopsToXlm / formatCountdown / withdrawalStatus are pure and are the obvious first tests once a runner exists (#524/#525 add jest.config.js). Rounding on the stroops conversion in particular is worth pinning down.
The domain modelling is the hard part and you've done it well. Gate the demo state and resolve the escrow file and this gets much closer.
|
Thanks — The blocking problemBoth IO ends are stubs: const stubSubmitVaultTx: VaultSubmit = async (call) => {
await new Promise((resolve) => setTimeout(resolve, 400));
return { hash: `stub-${call.method}-${Date.now().toString(36)}` }
};and the screen renders whatever comes back as a confirmation: const result = await operation();
const hash = typeof result === 'string' ? result : result.hash;
setNotice(`Transaction submitted: ${hash.slice(0, 16)}...`);So a user taps Deposit, waits 400 ms, and is told "Transaction submitted: stub-deposit-mf…". No transaction was built, signed or submitted, and no funds moved. The comments in the source are honest about this, but the comments aren't what the user sees — the UI is, and the UI says it worked. For a screen that moves money that's a correctness problem rather than a rough edge, and it's the same class of issue as #515 (closed for showing NFTs a wallet doesn't hold). A placeholder that fails loudly would be fine; one that succeeds falsely isn't. Against #477's acceptance — vault balances render; deposit/withdraw sign + submit — the balances come from This is more buildable than the comments suggestThe stub comment says mobile "does not yet have the passkey/session signing infrastructure". That was true when you opened this, but has since changed:
So the two stubs can be replaced with real implementations rather than deferred. What I'd keep
If you'd like to resubmit: branch off current |
Summary
Ports the vault sub-account feature from the Next.js web wallet to the Expo mobile app, following the conventions already established by the bulk-payout and swap mobile ports in this repo.
What was ported
How signing is stubbed, and why
Mobile does not yet have the passkey/session-based signing infrastructure the web wallet uses (frontend/wallet/lib/passkeyAuth, sessionStorage-backed signer keys). Rather than inventing a signing flow, the actual chain submission and reads are behind injectable async parameters — VaultSubmit and VaultFetch in lib/vault.ts — the same style as executeBulkPayout(rows, submitBatch) in the existing lib/bulkPayout.ts. The screen supplies stubSubmitVaultTx and stubFetchVaultDetails today so the full deposit/withdraw/queue/execute/cancel flow is exercisable end to end in the UI, and a real Soroban RPC-backed implementation (mirroring frontend/wallet/lib/vault.ts's submitOperation/simulateCall) can be swapped in later without touching the screen or the typed domain logic.
What was verified
closes #477