Skip to content

feat: model the realtime-funding refund destination as an external account - #783

Draft
ls-bolt[bot] wants to merge 2 commits into
mainfrom
07-31-plan-refund-destination-external-account
Draft

feat: model the realtime-funding refund destination as an external account#783
ls-bolt[bot] wants to merge 2 commits into
mainfrom
07-31-plan-refund-destination-external-account

Conversation

@ls-bolt

@ls-bolt ls-bolt Bot commented Jul 31, 2026

Copy link
Copy Markdown
Contributor

Summary

The Bitcoin L1 deposit-address work added a free-text refundAddress to the realtime-funding quote source, which was pulled before merge and deferred. This lands it properly: the refund target is now a reference to an existing external account rather than a raw address, so it goes through whitelisting (status), the trust/untrust confirm flow, and beneficiary verification instead of bypassing all three.

Three parts:

1. refundAccountId on RealtimeFundingQuoteSource — optional. A bare external-account ID string, matching the accountId idiom already used by AccountQuoteSource and AccountDestination. Must belong to customerId, be ACTIVE, and match the funding source on both currency and network — a USDC deposit can't refund to a USDT account on the same chain.

2. A documented fallback chain. Omitted → funds return to the customer's internal account for that asset. No internal account to fall back on → quote creation is rejected with a new REFUND_DESTINATION_REQUIRED 400 rather than accepting a deposit that can't be returned. That rejection was previously undocumented behavior; it's now real API surface, listed in both the Error400 code table and its enum.

3. BITCOIN_L1 external-account type. Every other crypto corridor (BASE_WALLET, ETHEREUM_WALLET, PLASMA_WALLET, POLYGON_WALLET, SOLANA_WALLET, SPARK_WALLET, TRON_WALLET) already has a member; BTC L1 did not, so a BTC L1 refund target was inexpressible. Adds the type plus BitcoinWalletExternalAccountInfo and its shared BitcoinWalletInfo address shape, wired into both the read and create external-account oneOfs.

The destination-side refundAddress is deliberately not included. A failed crypto payout refunds to an address Grid already knows, and AccountDestination also covers fiat accounts where a refund address is meaningless.

Changes

File Change
openapi/components/schemas/quotes/RealtimeFundingQuoteSource.yaml optional refundAccountId + fallback-chain docs
openapi/components/schemas/common/BitcoinWalletInfo.yaml new — shared BTC L1 address shape
openapi/components/schemas/external_accounts/BitcoinWalletExternalAccountInfo.yaml new — external-account oneOf member
openapi/components/schemas/external_accounts/ExternalAccountType.yaml BITCOIN_L1
.../ExternalAccountInfoOneOf.yaml, .../ExternalAccountCreateInfoOneOf.yaml member + discriminator mapping, both sides
openapi/components/schemas/errors/Error400.yaml REFUND_DESTINATION_REQUIRED in the code table and the enum
openapi/paths/quotes/quotes.yaml new cryptoFundingWithRefundAccount request example; 400 description notes the new code
.stainless/stainless.yml model entry + BaseExternalAccountInfo allOf[0] strip target for the new member
openapi.yaml, mintlify/openapi.yaml regenerated via make build

No info.version bump — every change is additive, so no new servers.url path.

Test plan

Spec-only repo, so lint plus structural verification of the generated bundle:

  • make lint-openapi — 0 errors. The two schema-properties-have-descriptions / -examples warnings on BitcoinWalletInfo.accountType are identical to the ones every sibling wallet schema already carries (EthereumWalletInfo, SparkWalletInfo), i.e. consistent with the established pattern.
  • make build run twice — bundles are byte-identical on re-run, so no generation drift.
  • Parsed the bundle and asserted: BitcoinWalletExternalAccountInfo is listed in both oneOfs with a BITCOIN_L1 mapping that resolves to a listed member; no discriminator mapping target in either oneOf is dangling; Error400's markdown table and enum have zero symmetric difference (no drift in either direction); refundAccountId is present and not required; refundAddress appears nowhere in either bundle.
  • Checked the Mintlify MDX for parallel enumerations that would need the same addition — the BASE_WALLET occurrences are single illustrative payloads and snippets/error-handling.mdx documents four hand-picked codes narratively rather than mirroring the enum, so no MDX change is needed.

Notes for the reviewer

  • This spec leads the server. refundAccountId and REFUND_DESTINATION_REQUIRED are described here before they're honored end-to-end, the same way BITCOIN_L1 shipped ahead of the server previously. Flagged and approved before implementation, but worth a second look if you'd rather it land alongside the backend change.
  • breaking-change label, as expected. oasdiff reports 9 errors / 530 warnings. All 9 errors are the same response-property-one-of-added for BitcoinWalletExternalAccountInfo across the three external-account read/create endpoint families; the 530 warnings are response-property-enum-value-added for BITCOIN_L1 and REFUND_DESTINATION_REQUIRED. Additive with no removals — adding to ExternalAccountType propagates to accountType on every member inheriting BaseExternalAccountInfo, so the list is wide but shallow.
  • One new Stainless diagnostic — a 💡 note, Schema/IsAmbiguous: Missing type for schema, reported once per SDK. It's the composed allOf-only wrapper having no type of its own; all seven existing wallet members have exactly the same shape, so this matches the established pattern rather than deviating from it. Adding type: object to just the new one would make it the odd member out; worth a separate sweep across all eight if the SDK team wants it silenced.
  • The lint/test stages in the Stainless preview are pre-existing. grid-go, grid-python, and grid-cli show the identical failure pattern on already-merged PRs (e.g. Add USD account bank, account type and wire routing fields #778), and the preview check still concludes green here. Not a regression from this change.
  • The Mintlify preview won't show the new field. mintlify/docs.json pins the API reference to Stainless's hosted published spec, so the rendered reference only picks this up after merge and republish.
  • The refund target being caller-supplied at all is the one genuinely open design question. This models it as optional-with-fallback, which supports both "caller specifies" and "server derives from the transaction" without committing the API to either.

@vercel

vercel Bot commented Jul 31, 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 Jul 31, 2026 8:51pm
grid-wallet-demo Ignored Ignored Preview Jul 31, 2026 8:51pm

Request Review

@ls-bolt ls-bolt Bot added the bolt label Jul 31, 2026

Copy link
Copy Markdown

This stack of pull requests is managed by Graphite. Learn more about stacking.

…count

Adds an optional `refundAccountId` to `RealtimeFundingQuoteSource`, referencing
an existing external account rather than a free-text address, so the refund
target goes through whitelisting (`status`), the trust/untrust confirm flow, and
beneficiary verification instead of bypassing them.

Documents the fallback chain: omitted falls back to the customer's internal
account for that asset, and a customer with no internal account to fall back on
gets a new `REFUND_DESTINATION_REQUIRED` 400 rather than a deposit that cannot
be returned.

Bitcoin L1 was the only crypto corridor with no `ExternalAccountType` member, so
a BTC L1 refund target was inexpressible. Adds `BITCOIN_L1` plus
`BitcoinWalletExternalAccountInfo` and its shared `BitcoinWalletInfo` address
shape, wired into both external-account oneOfs.

Co-Authored-By: jklein24 <jklein24@users.noreply.github.com>
Co-Authored-By: peter.rojs <peter.rojs@lightspark.com>
@ls-bolt ls-bolt Bot changed the title plan: refund destination as a source external account feat: model the realtime-funding refund destination as an external account Jul 31, 2026
@github-actions github-actions Bot added the breaking-change Introduces a breaking change to the OpenAPI spec label Jul 31, 2026
@github-actions

Copy link
Copy Markdown
Contributor

⚠️ Breaking OpenAPI changes detected

oasdiff reports 9 error / 530 warning changes to openapi.yaml.
This PR will need approval from an API reviewer before merge.

Errors (9)

  • GET /agents/me/external-accounts — added #/components/schemas/BitcoinWalletExternalAccountInfo to the data/items/allOf[subschema #1]/accountInfo response property oneOf list for the response status 200 [response-property-one-of-added].
  • POST /agents/me/external-accounts — added #/components/schemas/BitcoinWalletExternalAccountInfo to the allOf[subschema #1]/accountInfo response property oneOf list for the response status 201 [response-property-one-of-added].
  • GET /agents/me/external-accounts/{externalAccountId} — added #/components/schemas/BitcoinWalletExternalAccountInfo to the allOf[subschema #1]/accountInfo response property oneOf list for the response status 200 [response-property-one-of-added].
  • GET /customers/external-accounts — added #/components/schemas/BitcoinWalletExternalAccountInfo to the data/items/allOf[subschema #1]/accountInfo response property oneOf list for the response status 200 [response-property-one-of-added].
  • POST /customers/external-accounts — added #/components/schemas/BitcoinWalletExternalAccountInfo to the allOf[subschema #1]/accountInfo response property oneOf list for the response status 201 [response-property-one-of-added].
  • GET /customers/external-accounts/{externalAccountId} — added #/components/schemas/BitcoinWalletExternalAccountInfo to the allOf[subschema #1]/accountInfo response property oneOf list for the response status 200 [response-property-one-of-added].
  • GET /platform/external-accounts — added #/components/schemas/BitcoinWalletExternalAccountInfo to the data/items/allOf[subschema #1]/accountInfo response property oneOf list for the response status 200 [response-property-one-of-added].
  • POST /platform/external-accounts — added #/components/schemas/BitcoinWalletExternalAccountInfo to the allOf[subschema #1]/accountInfo response property oneOf list for the response status 201 [response-property-one-of-added].
  • GET /platform/external-accounts/{externalAccountId} — added #/components/schemas/BitcoinWalletExternalAccountInfo to the allOf[subschema #1]/accountInfo response property oneOf list for the response status 200 [response-property-one-of-added].

Warnings (530)

Show sample
  • GET /agents — added the new REFUND_DESTINATION_REQUIRED enum value to the code response property for the response status 400 [response-property-enum-value-added]. Adding new enum values to response could be unexpected for clients, use x-extensible-enum instead.
  • POST /agents — added the new REFUND_DESTINATION_REQUIRED enum value to the code response property for the response status 400 [response-property-enum-value-added]. Adding new enum values to response could be unexpected for clients, use x-extensible-enum instead.
  • GET /agents/approvals — added the new REFUND_DESTINATION_REQUIRED enum value to the code response property for the response status 400 [response-property-enum-value-added]. Adding new enum values to response could be unexpected for clients, use x-extensible-enum instead.
  • POST /agents/device-codes/{code}/redeem — added the new REFUND_DESTINATION_REQUIRED enum value to the code response property for the response status 400 [response-property-enum-value-added]. Adding new enum values to response could be unexpected for clients, use x-extensible-enum instead.
  • GET /agents/me/external-accounts — added the new BITCOIN_L1 enum value to the data/items/allOf[subschema #1]/accountInfo/oneOf[subschema #10: GBP Account]/allOf[#/components/schemas/BaseExternalAccountInfo]/accountType response property for the response status 200 [response-property-enum-value-added]. Adding new enum values to response could be unexpected for clients, use x-extensible-enum instead.
  • GET /agents/me/external-accounts — added the new BITCOIN_L1 enum value to the data/items/allOf[subschema #1]/accountInfo/oneOf[subschema #11: GHS Account]/allOf[#/components/schemas/BaseExternalAccountInfo]/accountType response property for the response status 200 [response-property-enum-value-added]. Adding new enum values to response could be unexpected for clients, use x-extensible-enum instead.
  • GET /agents/me/external-accounts — added the new BITCOIN_L1 enum value to the data/items/allOf[subschema #1]/accountInfo/oneOf[subschema #12: GTQ Account]/allOf[#/components/schemas/BaseExternalAccountInfo]/accountType response property for the response status 200 [response-property-enum-value-added]. Adding new enum values to response could be unexpected for clients, use x-extensible-enum instead.
  • GET /agents/me/external-accounts — added the new BITCOIN_L1 enum value to the data/items/allOf[subschema #1]/accountInfo/oneOf[subschema #13: HKD Account]/allOf[#/components/schemas/BaseExternalAccountInfo]/accountType response property for the response status 200 [response-property-enum-value-added]. Adding new enum values to response could be unexpected for clients, use x-extensible-enum instead.
  • GET /agents/me/external-accounts — added the new BITCOIN_L1 enum value to the data/items/allOf[subschema #1]/accountInfo/oneOf[subschema #14: HTG Account]/allOf[#/components/schemas/BaseExternalAccountInfo]/accountType response property for the response status 200 [response-property-enum-value-added]. Adding new enum values to response could be unexpected for clients, use x-extensible-enum instead.
  • GET /agents/me/external-accounts — added the new BITCOIN_L1 enum value to the data/items/allOf[subschema #1]/accountInfo/oneOf[subschema #15: IDR Account]/allOf[#/components/schemas/BaseExternalAccountInfo]/accountType response property for the response status 200 [response-property-enum-value-added]. Adding new enum values to response could be unexpected for clients, use x-extensible-enum instead.
  • GET /agents/me/external-accounts — added the new BITCOIN_L1 enum value to the data/items/allOf[subschema #1]/accountInfo/oneOf[subschema #16: INR Account]/allOf[#/components/schemas/BaseExternalAccountInfo]/accountType response property for the response status 200 [response-property-enum-value-added]. Adding new enum values to response could be unexpected for clients, use x-extensible-enum instead.
  • GET /agents/me/external-accounts — added the new BITCOIN_L1 enum value to the data/items/allOf[subschema #1]/accountInfo/oneOf[subschema #17: JMD Account]/allOf[#/components/schemas/BaseExternalAccountInfo]/accountType response property for the response status 200 [response-property-enum-value-added]. Adding new enum values to response could be unexpected for clients, use x-extensible-enum instead.
  • GET /agents/me/external-accounts — added the new BITCOIN_L1 enum value to the data/items/allOf[subschema #1]/accountInfo/oneOf[subschema #18: KES Account]/allOf[#/components/schemas/BaseExternalAccountInfo]/accountType response property for the response status 200 [response-property-enum-value-added]. Adding new enum values to response could be unexpected for clients, use x-extensible-enum instead.
  • GET /agents/me/external-accounts — added the new BITCOIN_L1 enum value to the data/items/allOf[subschema #1]/accountInfo/oneOf[subschema #19: MWK Account]/allOf[#/components/schemas/BaseExternalAccountInfo]/accountType response property for the response status 200 [response-property-enum-value-added]. Adding new enum values to response could be unexpected for clients, use x-extensible-enum instead.
  • GET /agents/me/external-accounts — added the new BITCOIN_L1 enum value to the data/items/allOf[subschema #1]/accountInfo/oneOf[subschema #1: AED Account]/allOf[#/components/schemas/BaseExternalAccountInfo]/accountType response property for the response status 200 [response-property-enum-value-added]. Adding new enum values to response could be unexpected for clients, use x-extensible-enum instead.
  • GET /agents/me/external-accounts — added the new BITCOIN_L1 enum value to the data/items/allOf[subschema #1]/accountInfo/oneOf[subschema #20: MXN Account]/allOf[#/components/schemas/BaseExternalAccountInfo]/accountType response property for the response status 200 [response-property-enum-value-added]. Adding new enum values to response could be unexpected for clients, use x-extensible-enum instead.
  • GET /agents/me/external-accounts — added the new BITCOIN_L1 enum value to the data/items/allOf[subschema #1]/accountInfo/oneOf[subschema #21: MYR Account]/allOf[#/components/schemas/BaseExternalAccountInfo]/accountType response property for the response status 200 [response-property-enum-value-added]. Adding new enum values to response could be unexpected for clients, use x-extensible-enum instead.
  • GET /agents/me/external-accounts — added the new BITCOIN_L1 enum value to the data/items/allOf[subschema #1]/accountInfo/oneOf[subschema #22: NGN Account]/allOf[#/components/schemas/BaseExternalAccountInfo]/accountType response property for the response status 200 [response-property-enum-value-added]. Adding new enum values to response could be unexpected for clients, use x-extensible-enum instead.
  • GET /agents/me/external-accounts — added the new BITCOIN_L1 enum value to the data/items/allOf[subschema #1]/accountInfo/oneOf[subschema #23: PHP Account]/allOf[#/components/schemas/BaseExternalAccountInfo]/accountType response property for the response status 200 [response-property-enum-value-added]. Adding new enum values to response could be unexpected for clients, use x-extensible-enum instead.
  • GET /agents/me/external-accounts — added the new BITCOIN_L1 enum value to the data/items/allOf[subschema #1]/accountInfo/oneOf[subschema #24: PKR Account]/allOf[#/components/schemas/BaseExternalAccountInfo]/accountType response property for the response status 200 [response-property-enum-value-added]. Adding new enum values to response could be unexpected for clients, use x-extensible-enum instead.
  • GET /agents/me/external-accounts — added the new BITCOIN_L1 enum value to the data/items/allOf[subschema #1]/accountInfo/oneOf[subschema #25: RWF Account]/allOf[#/components/schemas/BaseExternalAccountInfo]/accountType response property for the response status 200 [response-property-enum-value-added]. Adding new enum values to response could be unexpected for clients, use x-extensible-enum instead.
  • GET /agents/me/external-accounts — added the new BITCOIN_L1 enum value to the data/items/allOf[subschema #1]/accountInfo/oneOf[subschema #26: SGD Account]/allOf[#/components/schemas/BaseExternalAccountInfo]/accountType response property for the response status 200 [response-property-enum-value-added]. Adding new enum values to response could be unexpected for clients, use x-extensible-enum instead.
  • GET /agents/me/external-accounts — added the new BITCOIN_L1 enum value to the data/items/allOf[subschema #1]/accountInfo/oneOf[subschema #27: SLV Account]/allOf[#/components/schemas/BaseExternalAccountInfo]/accountType response property for the response status 200 [response-property-enum-value-added]. Adding new enum values to response could be unexpected for clients, use x-extensible-enum instead.
  • GET /agents/me/external-accounts — added the new BITCOIN_L1 enum value to the data/items/allOf[subschema #1]/accountInfo/oneOf[subschema #28: THB Account]/allOf[#/components/schemas/BaseExternalAccountInfo]/accountType response property for the response status 200 [response-property-enum-value-added]. Adding new enum values to response could be unexpected for clients, use x-extensible-enum instead.
  • GET /agents/me/external-accounts — added the new BITCOIN_L1 enum value to the data/items/allOf[subschema #1]/accountInfo/oneOf[subschema #29: TZS Account]/allOf[#/components/schemas/BaseExternalAccountInfo]/accountType response property for the response status 200 [response-property-enum-value-added]. Adding new enum values to response could be unexpected for clients, use x-extensible-enum instead.
  • …and 505 more warnings.

Detected by oasdiff. Full report: job summary or the oasdiff-report artifact.

@github-actions

github-actions Bot commented Jul 31, 2026

Copy link
Copy Markdown
Contributor

✱ Stainless preview builds for grid

This PR will update the grid SDKs with the following commit messages.

cli

chore(internal): regenerate SDK with no functional changes

go

chore(internal): regenerate SDK with no functional changes

kotlin

feat(api): add BitcoinWalletInfo model to external accounts

openapi

feat(api): add refundAccountId to quotes, BITCOIN_L1 wallet type and error code

php

chore(internal): regenerate SDK with no functional changes

python

feat(api): add BitcoinWalletInfo to customers

ruby

feat(api): add BitcoinWalletInfo model to customers

typescript

feat(api): add BitcoinWalletInfo type to external-accounts

Edit this comment to update them. They will appear in their respective SDK's changelogs.

grid-typescript studio · code · diff

Your SDK build had at least one new note diagnostic, which is a regression from the base state.
generate ✅build ✅lint ❗test ✅

npm install https://pkg.stainless.com/s/grid-typescript/9813008c38c56238fcf27226b2ffe3be91b5f165/dist.tar.gz
New diagnostics (1 note)
💡 Schema/IsAmbiguous: Missing type for schema
grid-openapi studio · code · diff

Your SDK build had at least one new note diagnostic, which is a regression from the base state.
generate ⚠️

New diagnostics (1 note)
💡 Schema/IsAmbiguous: Missing type for schema
grid-ruby studio · code · diff

Your SDK build had at least one new note diagnostic, which is a regression from the base state.
generate ✅build ✅ (prev: build ⏭️) → lint ✅test ✅

New diagnostics (1 note)
💡 Schema/IsAmbiguous: Missing type for schema
grid-go studio · code · diff

Your SDK build had at least one new note diagnostic, which is a regression from the base state.
generate ✅build ⏭️lint ❗test ❗

go get github.com/stainless-sdks/grid-go@55eaced9a52c46526ce17fc7c358fbc83657e611
New diagnostics (1 note)
💡 Schema/IsAmbiguous: Missing type for schema
grid-kotlin studio · code · diff

Your SDK build had at least one new note diagnostic, which is a regression from the base state.
generate ⚠️build ✅ (prev: build ⏭️) → lint ✅ (prev: lint ⏭️) → test ❗

New diagnostics (1 note)
💡 Schema/IsAmbiguous: Missing type for schema
⚠️ grid-python studio · code · diff

Your SDK build had a failure in the lint CI job, which is a regression from the base state.
generate ✅build ✅ (prev: build ⏭️) → lint ❗ (prev: lint ⏭️) → test ❗

pip install https://pkg.stainless.com/s/grid-python/1263f13187e7ea11886ce186a763924c4b354706/grid-0.0.1-py3-none-any.whl
New diagnostics (1 note)
💡 Schema/IsAmbiguous: Missing type for schema
grid-php studio · code · diff

Your SDK build had at least one new note diagnostic, which is a regression from the base state.
generate ✅lint ✅test ✅

New diagnostics (1 note)
💡 Schema/IsAmbiguous: Missing type for schema
grid-cli studio · code · diff

Your SDK build had at least one new note diagnostic, which is a regression from the base state.
generate ⚠️build ❗lint ❗test ❗

New diagnostics (1 note)
💡 Schema/IsAmbiguous: Missing type for schema

This comment is auto-generated by GitHub Actions and is automatically kept up to date as you push.
If you push custom code to the preview branch, re-run this workflow to update the comment.
Last updated: 2026-07-31 20:56:46 UTC

@mintlify

mintlify Bot commented Jul 31, 2026

Copy link
Copy Markdown
Contributor

Preview deployment for your docs. Learn more about Mintlify Previews.

Project Status Preview Updated (UTC)
Grid 🟢 Ready View Preview Jul 31, 2026, 8:54 PM

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

Labels

bolt breaking-change Introduces a breaking change to the OpenAPI spec

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant