Skip to content

feat: add custody type and VASP link to external accounts - #829

Draft
shreyav wants to merge 3 commits into
mainfrom
shreyav/wallet-custody-type
Draft

feat: add custody type and VASP link to external accounts#829
shreyav wants to merge 3 commits into
mainfrom
shreyav/wallet-custody-type

Conversation

@shreyav

@shreyav shreyav commented Aug 13, 2026

Copy link
Copy Markdown
Contributor

Summary

Lets a platform declare that a crypto-wallet external account is held at an exchange rather than self-custodied — the missing counterparty dimension for Travel Rule support. ownershipType stays orthogonal: your own Kraken account is VASP_HOSTED + FIRST_PARTY; a friend's MetaMask is SELF_CUSTODY + THIRD_PARTY.

This is PR 3 of 3 for VASP counterparty support (Travel Rule). Merge after:

  1. feat: add VASP directory endpoint #827/vasps directory (provides the vaspName referenced here)
  2. feat: add beneficiary to crypto wallet external accounts #828beneficiary on crypto-wallet external accounts (the identity that COUNTERPARTY_USER_INFO_REQUIRED asks for)

Changes

  • New custodyType enum (SELF_CUSTODY | VASP_HOSTED) on ExternalAccountCreateRequest and ExternalAccount. Crypto wallets only; defaults to SELF_CUSTODY, so existing integrations are untouched.
  • vaspName — the canonical name from GET /vasps, mirroring how /discoveries results are passed back as bankName. Required when VASP_HOSTED, rejected otherwise.
  • New 400 COUNTERPARTY_USER_INFO_REQUIRED error code: the destination needs beneficiary info before a transfer can proceed (fix the account, retry).
  • Documented: ownership verification applies only to SELF_CUSTODY wallets; VASP_HOSTED accounts never require it.
  • Stainless model entry for CustodyType.

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:16 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 5:49pm
grid-wallet-demo Ignored Ignored Preview Aug 14, 2026 5:49pm

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

✱ Stainless preview builds for grid

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

cli

feat(api): add custody_type and vasp_id to agent/customer external accounts

go

feat(api): add custody_type and vasp_id fields to external accounts

kotlin

feat(api): add custodyType and vaspId fields to external accounts

openapi

feat(api): add custodyType/vaspId to external accounts, error code

php

feat(api): add custodyType and vaspID to external accounts

python

feat(api): add custody_type and vasp_id to external accounts

ruby

feat(api): add custody_type/vasp_id fields and CustodyType enum to external accounts

typescript

feat(api): add custodyType and vaspId fields 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 "note" diagnostic, but this did not represent a regression.
generate ✅build ✅lint ❗test ✅

npm install https://pkg.stainless.com/s/grid-typescript/66cf6df8728014876a00efd71c0115b00103127b/dist.tar.gz
grid-openapi studio · code · diff

Your SDK build had at least one "warning" diagnostic, but this did not represent a regression.
generate ⚠️

grid-ruby studio · code · diff

Your SDK build had at least one "note" diagnostic, but this did not represent a regression.
generate ✅build ✅lint ✅test ✅

grid-go studio · code · diff

Your SDK build had at least one "note" diagnostic, but this did not represent a regression.
generate ✅build ✅lint ❗test ❗

go get github.com/stainless-sdks/grid-go@86a18022556705a700fe35bc30b42ef43d69df6a
grid-kotlin studio · code · diff

Your SDK build had at least one "warning" diagnostic, but this did not represent a regression.
generate ⚠️build ✅lint ✅test ❗

grid-python studio · code · diff

Your SDK build had at least one "note" diagnostic, but this did not represent a regression.
generate ✅build ✅lint ❗test ❗

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

Your SDK build had at least one "note" diagnostic, but this did not represent a regression.
generate ✅lint ✅test ✅

grid-cli studio · code · diff

Your SDK build had at least one "warning" diagnostic, but this did not represent a regression.
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 17:54:49 UTC

# Conflicts:
#	mintlify/openapi.yaml
#	openapi.yaml
#	openapi/components/schemas/errors/Error400.yaml
@github-actions github-actions Bot removed the breaking-change Introduces a breaking change to the OpenAPI spec label Aug 14, 2026
shreyav added a commit that referenced this pull request Aug 14, 2026
## 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. #827 — `/vasps` directory
2. **This PR** — `beneficiary` on crypto-wallet external accounts
3. #829 — `custodyType`/`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](https://claude.com/claude-code)
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.

1 participant