feat(samples/kotlin): add USD → USDC wallet payout flow#593
Conversation
|
The latest updates on your projects. Learn more about Vercel for GitHub. 2 Skipped Deployments
|
|
Warning This pull request is not mergeable via GitHub because a downstack PR is open. Once all requirements are satisfied, merge this PR as a stack on Graphite.
This stack of pull requests is managed by Graphite. Learn more about stacking. |
Greptile SummaryThis PR adds a USD → USDC on-chain wallet payout flow, introducing a new
Confidence Score: 5/5Safe to merge — the changes are additive, follow established patterns, and introduce no regressions to existing flows. The new USDC payout flow is a clean, self-contained addition that mirrors the existing bank-payout flow. The backend crypto wallet branches are straightforward address-only builds with no beneficiary logic. Issues raised in prior review threads (platformAccountId regeneration, destCurrency dependency array, Ethereum import package) remain open but were already known; no new defects were introduced by this PR. No files require special attention beyond the items already tracked in open thread comments.
|
| Filename | Overview |
|---|---|
| samples/frontend/src/flows/UsdcPayoutFlow.tsx | New 4-step USDC payout wizard following the same structure as PayoutFlow; passes a constant destCurrency="USDC" to CreateQuote, avoiding the dep-array side-effect seen in PayoutFlow. Restart logic correctly resets externalAccountId/quoteId while preserving the customer. |
| samples/frontend/src/steps/CreateUsdcExternalAccount.tsx | New network-selector step with NETWORK_CONFIGS for five chains. platformAccountId is regenerated via Math.random() inside useEffect, so every network-switch wipes user edits (flagged in prior threads). Code is otherwise clean and consistent with sibling steps. |
| samples/frontend/src/steps/CreateQuote.tsx | Refactored to accept destCurrency prop instead of selectedCountry. destCurrency remains in the useEffect dependency array without being referenced inside the effect body (flagged in prior threads); the prop is display-only. |
| samples/kotlin/src/main/kotlin/com/grid/sample/routes/ExternalAccounts.kt | Five new wallet-type branches added to buildAccountInfo. Four use classes from com.lightspark.grid.models.customers.externalaccounts; EthereumWalletExternalAccountInfo is imported from the top-level com.lightspark.grid.models package with a divergent factory method name (flagged in prior threads). |
| samples/frontend/src/flows/PayoutFlow.tsx | Updated to pass destCurrency derived from COUNTRY_CONFIGS instead of selectedCountry directly; clean mechanical change. |
| samples/frontend/src/App.tsx | Registers the new usdc-payout flow key with metadata and renders UsdcPayoutFlow; straightforward wiring. |
| samples/frontend/src/components/Sidebar.tsx | Adds usdc-payout to FlowKey union type and FLOWS list; clean one-entry addition. |
Sequence Diagram
%%{init: {'theme': 'neutral'}}%%
sequenceDiagram
participant U as User (Browser)
participant FE as Frontend (UsdcPayoutFlow)
participant BE as Kotlin Backend
participant Grid as Grid API
U->>FE: Select network (Base/ETH/Polygon/Solana/Tron)
U->>FE: Submit "Create Customer"
FE->>BE: POST /api/customers
BE->>Grid: customers.create(...)
Grid-->>BE: "{ id: customerId }"
BE-->>FE: "{ id: customerId }"
U->>FE: Submit "Create USDC Wallet Account"
FE->>BE: "POST /api/customers/{customerId}/external-accounts"
Note over BE: buildAccountInfo("BASE_WALLET" | "ETHEREUM_WALLET" | ...)
BE->>Grid: "customers.externalAccounts.create(BaseWalletInfo | EthereumWalletExternalAccountInfo | ...)"
Grid-->>BE: "{ id: externalAccountId }"
BE-->>FE: "{ id: externalAccountId }"
U->>FE: "Submit "Create Quote" (destCurrency=USDC)"
FE->>BE: POST /api/quotes
BE->>Grid: "quotes.create(source=USD, destination=externalAccountId)"
Grid-->>BE: "{ id: quoteId }"
BE-->>FE: "{ id: quoteId }"
U->>FE: Submit "Simulate Funding"
FE->>BE: "POST /api/sandbox/fund/{quoteId}"
BE->>Grid: sandbox.fund(quoteId)
Grid-->>BE: funded
BE-->>FE: funded
%%{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 U as User (Browser)
participant FE as Frontend (UsdcPayoutFlow)
participant BE as Kotlin Backend
participant Grid as Grid API
U->>FE: Select network (Base/ETH/Polygon/Solana/Tron)
U->>FE: Submit "Create Customer"
FE->>BE: POST /api/customers
BE->>Grid: customers.create(...)
Grid-->>BE: "{ id: customerId }"
BE-->>FE: "{ id: customerId }"
U->>FE: Submit "Create USDC Wallet Account"
FE->>BE: "POST /api/customers/{customerId}/external-accounts"
Note over BE: buildAccountInfo("BASE_WALLET" | "ETHEREUM_WALLET" | ...)
BE->>Grid: "customers.externalAccounts.create(BaseWalletInfo | EthereumWalletExternalAccountInfo | ...)"
Grid-->>BE: "{ id: externalAccountId }"
BE-->>FE: "{ id: externalAccountId }"
U->>FE: "Submit "Create Quote" (destCurrency=USDC)"
FE->>BE: POST /api/quotes
BE->>Grid: "quotes.create(source=USD, destination=externalAccountId)"
Grid-->>BE: "{ id: quoteId }"
BE-->>FE: "{ id: quoteId }"
U->>FE: Submit "Simulate Funding"
FE->>BE: "POST /api/sandbox/fund/{quoteId}"
BE->>Grid: sandbox.fund(quoteId)
Grid-->>BE: funded
BE-->>FE: funded
Reviews (2): Last reviewed commit: "feat(samples/kotlin): add USD → USDC wal..." | Re-trigger Greptile
| useEffect(() => { | ||
| const config = NETWORK_CONFIGS[selectedNetwork] | ||
| setBody(JSON.stringify({ | ||
| customerId: customerId ?? "<customer-id>", | ||
| currency: "USDC", | ||
| platformAccountId: `acct_${Math.random().toString(36).slice(2, 10)}`, | ||
| accountInfo: { | ||
| accountType: config.accountType, | ||
| address: config.address, | ||
| }, | ||
| }, null, 2)) | ||
| }, [customerId, selectedNetwork]) |
There was a problem hiding this comment.
platformAccountId regenerates a new random value every time the network selector changes, because it is computed inside the useEffect. Any edit the user makes to the JSON body is wiped on each network switch — including the ID they may have noted or copied.
| useEffect(() => { | |
| const config = NETWORK_CONFIGS[selectedNetwork] | |
| setBody(JSON.stringify({ | |
| customerId: customerId ?? "<customer-id>", | |
| currency: "USDC", | |
| platformAccountId: `acct_${Math.random().toString(36).slice(2, 10)}`, | |
| accountInfo: { | |
| accountType: config.accountType, | |
| address: config.address, | |
| }, | |
| }, null, 2)) | |
| }, [customerId, selectedNetwork]) | |
| const stablePlatformAccountId = useRef(`acct_${Math.random().toString(36).slice(2, 10)}`) | |
| useEffect(() => { | |
| const config = NETWORK_CONFIGS[selectedNetwork] | |
| setBody(JSON.stringify({ | |
| customerId: customerId ?? "<customer-id>", | |
| currency: "USDC", | |
| platformAccountId: stablePlatformAccountId.current, | |
| accountInfo: { | |
| accountType: config.accountType, | |
| address: config.address, | |
| }, | |
| }, null, 2)) | |
| }, [customerId, selectedNetwork]) |
Prompt To Fix With AI
This is a comment left during a code review.
Path: samples/frontend/src/steps/CreateUsdcExternalAccount.tsx
Line: 62-73
Comment:
`platformAccountId` regenerates a new random value every time the network selector changes, because it is computed inside the `useEffect`. Any edit the user makes to the JSON body is wiped on each network switch — including the ID they may have noted or copied.
```suggestion
const stablePlatformAccountId = useRef(`acct_${Math.random().toString(36).slice(2, 10)}`)
useEffect(() => {
const config = NETWORK_CONFIGS[selectedNetwork]
setBody(JSON.stringify({
customerId: customerId ?? "<customer-id>",
currency: "USDC",
platformAccountId: stablePlatformAccountId.current,
accountInfo: {
accountType: config.accountType,
address: config.address,
},
}, null, 2))
}, [customerId, selectedNetwork])
```
How can I resolve this? If you propose a fix, please make it concise.| purposeOfPayment: "GIFT" | ||
| }, null, 2)) | ||
| }, [customerId, externalAccountId, sourceCurrency, selectedCountry]) | ||
| }, [customerId, externalAccountId, sourceCurrency, destCurrency]) |
There was a problem hiding this comment.
destCurrency is listed in the useEffect dependency array but is never referenced inside the effect body — it only appears in the JSX label on line 59. The effect will re-run whenever destCurrency changes (e.g., when the user picks a different country in PayoutFlow), but the regenerated body is identical to what was there before, silently discarding any edits the user made to the JSON.
| }, [customerId, externalAccountId, sourceCurrency, destCurrency]) | |
| }, [customerId, externalAccountId, sourceCurrency]) |
Prompt To Fix With AI
This is a comment left during a code review.
Path: samples/frontend/src/steps/CreateQuote.tsx
Line: 38
Comment:
`destCurrency` is listed in the `useEffect` dependency array but is never referenced inside the effect body — it only appears in the JSX label on line 59. The effect will re-run whenever `destCurrency` changes (e.g., when the user picks a different country in `PayoutFlow`), but the regenerated body is identical to what was there before, silently discarding any edits the user made to the JSON.
```suggestion
}, [customerId, externalAccountId, sourceCurrency])
```
How can I resolve this? If you propose a fix, please make it concise.Add a new "Send USDC to a Wallet" flow that funds an on-chain USDC payout with USD. Backend: handle crypto wallet external-account types (BASE/ETHEREUM/POLYGON/SOLANA/TRON_WALLET) in buildAccountInfo. Frontend: new CreateUsdcExternalAccount step (network selector) and UsdcPayoutFlow, wired into the sidebar. Generalize CreateQuote to take a destCurrency prop so it serves both the bank payout and USDC flows. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
a2991c4 to
7733983
Compare
a565834 to
4019a29
Compare

Add a new "Send USDC to a Wallet" flow that funds an on-chain USDC payout
with USD. Backend: handle crypto wallet external-account types
(BASE/ETHEREUM/POLYGON/SOLANA/TRON_WALLET) in buildAccountInfo. Frontend:
new CreateUsdcExternalAccount step (network selector) and UsdcPayoutFlow,
wired into the sidebar. Generalize CreateQuote to take a destCurrency prop
so it serves both the bank payout and USDC flows.
Co-Authored-By: Claude Opus 4.8 (1M context) noreply@anthropic.com