Skip to content

feat(samples/kotlin): add USD → USDC wallet payout flow#593

Open
pengying wants to merge 1 commit into
06-16-fix_samples_kotlin_send_customer_email_for_usdb_embedded_walletsfrom
06-16-feat_samples_kotlin_add_usd_usdc_wallet_payout_flow
Open

feat(samples/kotlin): add USD → USDC wallet payout flow#593
pengying wants to merge 1 commit into
06-16-fix_samples_kotlin_send_customer_email_for_usdb_embedded_walletsfrom
06-16-feat_samples_kotlin_add_usd_usdc_wallet_payout_flow

Conversation

@pengying

Copy link
Copy Markdown
Contributor

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

@vercel

vercel Bot commented Jun 16, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

2 Skipped Deployments
Project Deployment Actions Updated (UTC)
grid-flow-builder Ignored Ignored Preview Jun 18, 2026 6:13pm
grid-wallet-demo Ignored Ignored Preview Jun 18, 2026 6:13pm

Request Review

pengying commented Jun 16, 2026

Copy link
Copy Markdown
Contributor Author

@pengying pengying marked this pull request as ready for review June 16, 2026 22:59
@greptile-apps

greptile-apps Bot commented Jun 16, 2026

Copy link
Copy Markdown
Contributor

Greptile Summary

This PR adds a USD → USDC on-chain wallet payout flow, introducing a new UsdcPayoutFlow with a CreateUsdcExternalAccount step that lets users pick a blockchain network (Base/Ethereum/Polygon/Solana/Tron) and creates the corresponding external account. The Kotlin backend is extended to handle all five crypto wallet account types, and CreateQuote is generalized to accept a destCurrency prop so it serves both the existing bank payout and new USDC flows.

  • New UsdcPayoutFlow: Wires together CreateCustomerCreateUsdcExternalAccountCreateQuote (fixed destCurrency="USDC") → SandboxFund, with a "Start New Payment" restart button that reuses the customer but resets the external account and quote.
  • Kotlin buildAccountInfo: Five new when branches handle BASE_WALLET, ETHEREUM_WALLET, POLYGON_WALLET, SOLANA_WALLET, and TRON_WALLET account types, each requiring only an on-chain address (no beneficiary).

Confidence Score: 5/5

Safe 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.

Important Files Changed

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
Loading
%%{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
Loading

Reviews (2): Last reviewed commit: "feat(samples/kotlin): add USD → USDC wal..." | Re-trigger Greptile

Comment on lines +62 to +73
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])

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 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.

Suggested change
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])

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 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.

Suggested change
}, [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>
@pengying pengying force-pushed the 06-16-feat_samples_kotlin_add_usd_usdc_wallet_payout_flow branch from a2991c4 to 7733983 Compare June 18, 2026 18:13
@pengying pengying force-pushed the 06-16-fix_samples_kotlin_send_customer_email_for_usdb_embedded_wallets branch from a565834 to 4019a29 Compare June 18, 2026 18:13

@restamp-bot restamp-bot Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

7733983 is a pure rebase onto 4019a29. Approving based on @jklein24's previous approval of a2991c4.

@restamp-bot restamp-bot Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

7733983 is a pure rebase onto 4019a29. Approving based on @jklein24's previous approval of a2991c4.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants