Skip to content

feat: add beneficiary to crypto wallet external accounts - #828

Merged
shreyav merged 5 commits into
mainfrom
shreyav/wallet-beneficiary
Aug 14, 2026
Merged

feat: add beneficiary to crypto wallet external accounts#828
shreyav merged 5 commits into
mainfrom
shreyav/wallet-beneficiary

Conversation

@shreyav

@shreyav shreyav commented Aug 13, 2026

Copy link
Copy Markdown
Contributor

Summary

Crypto-wallet external accounts were the only account family without a beneficiary. This PR adds one, following the fiat pattern — an INDIVIDUAL/BUSINESS oneOf discriminated by beneficiaryType.

The beneficiary identifies who owns the wallet — the counterparty identity needed for Travel Rule data, independent of custody.

This is PR 2 of 3 for VASP counterparty support (Travel Rule):

  1. feat: add VASP directory endpoint #827/vasps directory
  2. This PRbeneficiary on crypto-wallet external accounts
  3. feat: add custody type and VASP link to external accounts #829custodyType/vaspName on external accounts

Field requirements: exactly what is transmitted

The individual variant is a new WalletIndividualBeneficiary with only fullName + countryOfResidence (both required) — the exact set transmitted as Travel Rule counterparty info. The generic IndividualBeneficiary couldn't be reused because it requires birthDate/nationality, which are never transmitted for wallets and would force platforms to collect a third party's date of birth (OpenAPI composition can't relax required). Optional PII fields were deliberately omitted: adding optional fields later is non-breaking, while accepting-but-ignoring PII invites needless collection. The BUSINESS variant reuses the existing BusinessBeneficiary (legalName required).

Semantics (one deliberate divergence from fiat)

  • Fiat accounts require beneficiary. For wallets it is optional for FIRST_PARTY — when omitted, the customer's verified identity is used, so the dominant own-wallet case sends nothing extra.
  • Required for THIRD_PARTY wallets on platforms subject to counterparty requirements (e.g., EU Travel Rule and similar regimes) — enforced at runtime with 400 INVALID_INPUT, not in the schema, since the requirement is platform-dependent.

Changes

  • New WalletBeneficiaryFields fragment (the beneficiary property) composed into all seven wallet variants: BASE_WALLET, ETHEREUM_WALLET, POLYGON_WALLET, PLASMA_WALLET, SOLANA_WALLET, SPARK_WALLET, TRON_WALLET
  • New WalletBeneficiaryOneOf — the named individual/business union (matches the *OneOf house convention)
  • New WalletIndividualBeneficiary schema (fullName + countryOfResidence)
  • Stainless model entries for all three

Out of scope

  • LIGHTNING external accounts — Travel Rule counterparty identity for Lightning flows in-band (payment-level), not via a stored account beneficiary. Flagging in case reviewers feel otherwise.

Testing

make build bundles cleanly; redocly lint and spectral lint match the pre-existing baseline on main exactly (no new findings).

🤖 Generated with Claude Code

@mintlify

mintlify Bot commented Aug 13, 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 Aug 13, 2026, 9:14 PM

@vercel

vercel Bot commented Aug 13, 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 Aug 14, 2026 9:58pm
grid-wallet-demo Ignored Ignored Preview Aug 14, 2026 9:58pm

Request Review

@github-actions github-actions Bot added the breaking-change Introduces a breaking change to the OpenAPI spec label Aug 13, 2026
@github-actions

github-actions Bot commented Aug 13, 2026

Copy link
Copy Markdown
Contributor

⚠️ Breaking OpenAPI changes detected

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

Errors (28)

  • POST /agents/me/external-accounts — added #/components/schemas/WalletBeneficiaryFields to the allOf[subschema #1]/accountInfo/oneOf[subschema #39: Base Wallet]/ request property allOf list [request-property-all-of-added].
  • POST /agents/me/external-accounts — added #/components/schemas/WalletBeneficiaryFields to the allOf[subschema #1]/accountInfo/oneOf[subschema #40: Ethereum L1 Wallet]/ request property allOf list [request-property-all-of-added].
  • POST /agents/me/external-accounts — added #/components/schemas/WalletBeneficiaryFields to the allOf[subschema #1]/accountInfo/oneOf[subschema #42: Polygon Wallet]/ request property allOf list [request-property-all-of-added].
  • POST /agents/me/external-accounts — added #/components/schemas/WalletBeneficiaryFields to the allOf[subschema #1]/accountInfo/oneOf[subschema #43: Plasma Wallet]/ request property allOf list [request-property-all-of-added].
  • POST /agents/me/external-accounts — added #/components/schemas/WalletBeneficiaryFields to the allOf[subschema #1]/accountInfo/oneOf[subschema #44: Solana Wallet]/ request property allOf list [request-property-all-of-added].
  • POST /agents/me/external-accounts — added #/components/schemas/WalletBeneficiaryFields to the allOf[subschema #1]/accountInfo/oneOf[subschema #45: Spark Wallet]/ request property allOf list [request-property-all-of-added].
  • POST /agents/me/external-accounts — added #/components/schemas/WalletBeneficiaryFields to the allOf[subschema #1]/accountInfo/oneOf[subschema #46: Tron Wallet]/ request property allOf list [request-property-all-of-added].
  • POST /customers/external-accounts — added #/components/schemas/WalletBeneficiaryFields to the allOf[subschema #1]/accountInfo/oneOf[subschema #39: Base Wallet]/ request property allOf list [request-property-all-of-added].
  • POST /customers/external-accounts — added #/components/schemas/WalletBeneficiaryFields to the allOf[subschema #1]/accountInfo/oneOf[subschema #40: Ethereum L1 Wallet]/ request property allOf list [request-property-all-of-added].
  • POST /customers/external-accounts — added #/components/schemas/WalletBeneficiaryFields to the allOf[subschema #1]/accountInfo/oneOf[subschema #42: Polygon Wallet]/ request property allOf list [request-property-all-of-added].
  • POST /customers/external-accounts — added #/components/schemas/WalletBeneficiaryFields to the allOf[subschema #1]/accountInfo/oneOf[subschema #43: Plasma Wallet]/ request property allOf list [request-property-all-of-added].
  • POST /customers/external-accounts — added #/components/schemas/WalletBeneficiaryFields to the allOf[subschema #1]/accountInfo/oneOf[subschema #44: Solana Wallet]/ request property allOf list [request-property-all-of-added].
  • POST /customers/external-accounts — added #/components/schemas/WalletBeneficiaryFields to the allOf[subschema #1]/accountInfo/oneOf[subschema #45: Spark Wallet]/ request property allOf list [request-property-all-of-added].
  • POST /customers/external-accounts — added #/components/schemas/WalletBeneficiaryFields to the allOf[subschema #1]/accountInfo/oneOf[subschema #46: Tron Wallet]/ request property allOf list [request-property-all-of-added].
  • POST /platform/external-accounts — added #/components/schemas/WalletBeneficiaryFields to the accountInfo/oneOf[subschema #39: Base Wallet]/ request property allOf list [request-property-all-of-added].
  • POST /platform/external-accounts — added #/components/schemas/WalletBeneficiaryFields to the accountInfo/oneOf[subschema #40: Ethereum L1 Wallet]/ request property allOf list [request-property-all-of-added].
  • POST /platform/external-accounts — added #/components/schemas/WalletBeneficiaryFields to the accountInfo/oneOf[subschema #42: Polygon Wallet]/ request property allOf list [request-property-all-of-added].
  • POST /platform/external-accounts — added #/components/schemas/WalletBeneficiaryFields to the accountInfo/oneOf[subschema #43: Plasma Wallet]/ request property allOf list [request-property-all-of-added].
  • POST /platform/external-accounts — added #/components/schemas/WalletBeneficiaryFields to the accountInfo/oneOf[subschema #44: Solana Wallet]/ request property allOf list [request-property-all-of-added].
  • POST /platform/external-accounts — added #/components/schemas/WalletBeneficiaryFields to the accountInfo/oneOf[subschema #45: Spark Wallet]/ request property allOf list [request-property-all-of-added].
  • POST /platform/external-accounts — added #/components/schemas/WalletBeneficiaryFields to the accountInfo/oneOf[subschema #46: Tron Wallet]/ request property allOf list [request-property-all-of-added].
  • POST webhook:external-account-status — added #/components/schemas/WalletBeneficiaryFields to the allOf[subschema #2]/data/allOf[subschema #1]/accountInfo/oneOf[subschema #38: Base Wallet]/ request property allOf list [request-property-all-of-added].
  • POST webhook:external-account-status — added #/components/schemas/WalletBeneficiaryFields to the allOf[subschema #2]/data/allOf[subschema #1]/accountInfo/oneOf[subschema #39: Ethereum L1 Wallet]/ request property allOf list [request-property-all-of-added].
  • POST webhook:external-account-status — added #/components/schemas/WalletBeneficiaryFields to the allOf[subschema #2]/data/allOf[subschema #1]/accountInfo/oneOf[subschema #41: Polygon Wallet]/ request property allOf list [request-property-all-of-added].
  • POST webhook:external-account-status — added #/components/schemas/WalletBeneficiaryFields to the allOf[subschema #2]/data/allOf[subschema #1]/accountInfo/oneOf[subschema #42: Plasma Wallet]/ request property allOf list [request-property-all-of-added].
  • POST webhook:external-account-status — added #/components/schemas/WalletBeneficiaryFields to the allOf[subschema #2]/data/allOf[subschema #1]/accountInfo/oneOf[subschema #43: Solana Wallet]/ request property allOf list [request-property-all-of-added].
  • POST webhook:external-account-status — added #/components/schemas/WalletBeneficiaryFields to the allOf[subschema #2]/data/allOf[subschema #1]/accountInfo/oneOf[subschema #44: Spark Wallet]/ request property allOf list [request-property-all-of-added].
  • POST webhook:external-account-status — added #/components/schemas/WalletBeneficiaryFields to the allOf[subschema #2]/data/allOf[subschema #1]/accountInfo/oneOf[subschema #45: Tron Wallet]/ request property allOf list [request-property-all-of-added].

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

@github-actions

github-actions Bot commented Aug 13, 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 WalletBeneficiaryFields model to external accounts

openapi

feat(api): add beneficiary field to wallet external account types

php

feat(api): add wallet beneficiary models to external_accounts

python

feat(api): add WalletBeneficiaryFields type to customers

ruby

feat(api): add WalletBeneficiaryFields to customers

typescript

feat(api): add WalletBeneficiaryFields type to external-accounts
⚠️ grid-openapi studio · code

Your SDK build had at least one "warning" diagnostic.
generate ⚠️

grid-ruby studio · code

Your SDK build had at least one "note" diagnostic.
generate ✅build ✅lint ✅test ✅

⚠️ grid-go studio · code

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

go get github.com/stainless-sdks/grid-go@6fee2f0167e887dab13324db153b5ea338b8d9e1
⚠️ grid-kotlin studio · code

Your SDK build had a failure in the test CI job, which is a regression from the base state.
generate ⚠️build ✅lint ✅test ❗

grid-typescript studio · conflict

Your SDK build had at least one note diagnostic.

⚠️ grid-python studio · code

Your SDK build had a failure in the lint CI job, which is a regression from the base state.
generate ✅build ✅lint ❗test ❗

pip install https://pkg.stainless.com/s/grid-python/3e5364e83a056b1b4172e9ede9ec7481df98f281/grid-0.0.1-py3-none-any.whl
grid-php studio · code

Your SDK build had at least one "note" diagnostic.
generate ✅lint ✅test ✅

⚠️ grid-cli studio · code

Your SDK build had a failure in the test CI job, which is a regression from the base state.
generate ⚠️build ⏭️lint ⏭️test ❗


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-08-14 23:29:11 UTC

# Conflicts:
#	.stainless/stainless.yml
…iaries

Matches what is actually transmitted for Travel Rule counterparty info
(name + country of residence) instead of the generic beneficiary's
birthDate/nationality requirements, which are not transmitted and would
force platforms to collect a third party's date of birth.
@greptile-apps

greptile-apps Bot commented Aug 14, 2026

Copy link
Copy Markdown
Contributor

Greptile Summary

The PR adds an optional, discriminated individual-or-business beneficiary to all seven non-Lightning crypto-wallet external-account variants and registers the new models for SDK generation.

  • Adds a wallet-specific individual beneficiary requiring full name and country of residence.
  • Composes the shared beneficiary field into Base, Ethereum, Polygon, Plasma, Solana, Spark, and Tron wallet schemas.
  • Regenerates the root and Mintlify OpenAPI bundles and exposes the new models through Stainless.

Confidence Score: 5/5

The PR appears safe to merge with no concrete blocking or independently actionable non-blocking issues identified.

The create-request unions reach all seven changed wallet schemas, the discriminator mappings resolve consistently in the generated bundles, and the intentionally optional and excluded cases match the documented scope.

Important Files Changed

Filename Overview
openapi/components/schemas/common/WalletIndividualBeneficiary.yaml Defines the wallet-specific individual beneficiary with the intended required identity fields and optional supporting details.
openapi/components/schemas/external_accounts/WalletBeneficiaryOneOf.yaml Adds an internally consistent discriminated union whose mappings resolve correctly in the generated bundles.
openapi/components/schemas/external_accounts/WalletBeneficiaryFields.yaml Defines the deliberately optional beneficiary property and documents the platform-dependent third-party requirement.
.stainless/stainless.yml Registers all three new wallet beneficiary schemas as generated external-account SDK models.
openapi.yaml The generated root bundle consistently includes the new schemas and all seven wallet compositions.
mintlify/openapi.yaml The generated Mintlify bundle remains synchronized with the root OpenAPI bundle.

Flowchart

%%{init: {'theme': 'neutral'}}%%
flowchart LR
  C[External-account create request] --> U[ExternalAccountCreateInfoOneOf]
  U --> W[Wallet external-account variant]
  W --> F[WalletBeneficiaryFields]
  F --> B{beneficiaryType}
  B -->|INDIVIDUAL| I[WalletIndividualBeneficiary]
  B -->|BUSINESS| O[BusinessBeneficiary]
  W --> G[Bundled OpenAPI]
  G --> S[Stainless-generated SDK models]
Loading

Reviews (1): Last reviewed commit: "Name the wallet beneficiary union Wallet..." | Re-trigger Greptile

Comment thread openapi/components/schemas/common/WalletIndividualBeneficiary.yaml Outdated
propertyName: beneficiaryType
mapping:
INDIVIDUAL: ../common/WalletIndividualBeneficiary.yaml
BUSINESS: ../common/BusinessBeneficiary.yaml

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.

does this mean there'll be a business beneficiary as well?

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.

Er I mean is there a individual beneficiary we can resuse

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.

Huh i guess not I see a bunch of different types of beneficiaries depending on the country but not a generic one.

Do you think this one will be generic or is it only for wallets?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

yeah we went through the exercise of splitting up beneficiaries for individuals based on region reqs but we havent done that for business yet -- so reusing the existing business one and added a new one just for wallets w the minimum requirements

Comment thread openapi/components/schemas/external_accounts/WalletBeneficiaryFields.yaml Outdated
Drops the never-transmitted optional PII (birthDate, nationality, email,
phoneNumber, address) from WalletIndividualBeneficiary — optional fields
can be added back without breaking; collected-but-unused PII cannot be
removed as easily. Documents the 400 INVALID_INPUT on missing required
beneficiary.
@shreyav
shreyav merged commit 3ad6a07 into main Aug 14, 2026
11 checks passed
@shreyav
shreyav deleted the shreyav/wallet-beneficiary branch August 14, 2026 23:22
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

breaking-change Introduces a breaking change to the OpenAPI spec

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants