Skip to content

Add platform transaction fee config + platformFees fields to the spec#652

Open
jacklatourette wants to merge 1 commit into
mainfrom
platform-transaction-fees-spec
Open

Add platform transaction fee config + platformFees fields to the spec#652
jacklatourette wants to merge 1 commit into
mainfrom
platform-transaction-fees-spec

Conversation

@jacklatourette

Copy link
Copy Markdown
Contributor

Summary

The webdev sparkcore stack shipped platform transaction fees against a hand-wired vendored Grid API client (grid-api 0.0.82). The fields are live and testable there, but they do not exist in this spec repo — so the next SDK regen from upstream would silently delete integrator-facing fields. This PR adds them to the spec source (openapi/components/schemas/) and regenerates the bundles (openapi.yaml, mintlify/openapi.yaml) via npm run build:openapi.

1. New TransactionFeeConfig schema (backs lightsparkdev/webdev#29233)

openapi/components/schemas/config/TransactionFeeConfig.yaml + TransactionFeeEventType.yaml:

  • id (string, readOnly) — system-generated identifier
  • feeEventType (enum: CROSS_CURRENCY_TRANSACTION, TRANSFER_OUT_TRANSACTION; only the former is accepted by PATCH /config today, reads return the full set)
  • sourceCurrency (string) — ISO 4217 / crypto ticker code
  • variableFeeBps (integer, 0–10000)
  • fixedFee (CurrencyAmount) — must match sourceCurrency
  • isActive (boolean, readOnly)

Required: feeEventType, sourceCurrency, variableFeeBps, fixedFee. Rows are keyed by (feeEventType, sourceCurrency); deactivation is done by upserting the same key with zero fees.

2. transactionFeeConfigs on platform config (backs lightsparkdev/webdev#29233)

  • PlatformConfig.transactionFeeConfigs — read side; returns rows regardless of feeEventType so operator-created rows (e.g. Transfer Out) are visible
  • PlatformConfigUpdateRequest.transactionFeeConfigs — merge-by-key upsert; only CROSS_CURRENCY_TRANSACTION + USD accepted today, other values return 400; omit to leave configs unchanged

3. Platform fee surfacing on quotes and transactions (backs lightsparkdev/webdev#29402)

  • Quote.platformFeesIncluded (int64, min 0) — the portion of feesIncluded collected by the platform; already included in feesIncluded
  • OutgoingTransaction.platformFees (int64, min 0) — same, relative to fees

Descriptions match the vendored client/spec in webdev exactly, so a regen from this spec is a no-op against the hand-wired models.

Test plan

  • npm run lint (Redocly bundle + lint, Spectral --fail-severity=error) — exit 0. Only new finding is one non-failing schema-properties-have-descriptions warning on TransactionFeeConfig.fixedFee, an artifact of the $ref + sibling-description pattern already used throughout the spec (e.g. embeddedWalletConfig, sentAmount).
  • Verified the bundled diff is exactly the new fields plus Redocly relocating Currency/CurrencyAmount earlier (now referenced from the config section).
  • The webdev stack currently carries these fields in its vendored spec (grid-api 0.0.82), so the API surface is exercised end-to-end there before this merges.

🤖 Generated with Claude Code

- TransactionFeeConfig schema; transactionFeeConfigs on PlatformConfig + PlatformConfigUpdateRequest (backs webdev#29233)
- Quote.platformFeesIncluded + OutgoingTransaction.platformFees (backs webdev#29402)

These fields are live in the webdev vendored client; without this spec change the next regen deletes them.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@vercel

vercel Bot commented Jul 6, 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 Jul 6, 2026 10:35pm
grid-wallet-demo Ignored Ignored Jul 6, 2026 10:35pm

Request Review

@github-actions

github-actions Bot commented Jul 6, 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 transaction-fee-config parameter to config update method

csharp

feat(api): add transaction fee configs to config, platform fees to quotes/transactions

go

feat(api): add transaction fee configs to config, platform fees to quotes/transactions

kotlin

feat(api): add platform fee fields to quotes/transactions, transactionFeeConfigs to config

openapi

feat(api): add transaction fee config to platform, platform fees to quotes/transactions

php

feat(api): add transaction fee configs to config, platform fees to quotes/transactions

python

feat(api): add transaction_fee_configs to config, platform fees to quote/transaction

ruby

feat(api): add platform_fees to outgoing_transaction/quote, transaction_fee_configs to config

typescript

feat(api): add transactionFeeConfigs to config, platformFees/platformFeesIncluded to responses

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

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)
💡 Model/Recommended: We recommend you use a model for `#/components/schemas/TransactionFeeConfig`
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)
💡 Model/Recommended: We recommend you use a model for `#/components/schemas/TransactionFeeConfig`
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 ✅ (prev: build ⏭️) → lint ❗test ❗

go get github.com/stainless-sdks/grid-go@bb49eee5d8be62c278db18c15bfb8d6fb2d73413
New diagnostics (1 note)
💡 Model/Recommended: We recommend you use a model for `#/components/schemas/TransactionFeeConfig`
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)
💡 Model/Recommended: We recommend you use a model for `#/components/schemas/TransactionFeeConfig`
⚠️ grid-typescript 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 ✅

npm install https://pkg.stainless.com/s/grid-typescript/4098b057641a9df72ec5b81dc4766398c71c2806/dist.tar.gz
New diagnostics (1 note)
💡 Model/Recommended: We recommend you use a model for `#/components/schemas/TransactionFeeConfig`
⚠️ 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/39da9db50f83c91ce0fb398661d2da0d83473c4c/grid-0.0.1-py3-none-any.whl
New diagnostics (1 note)
💡 Model/Recommended: We recommend you use a model for `#/components/schemas/TransactionFeeConfig`
⚠️ grid-csharp studio · code · diff

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

New diagnostics (2 note)
💡 Model/Recommended: We recommend you use a model for `#/components/schemas/TransactionFeeConfig`
💡 Name/Renamed: 267 names were renamed due to language constraints, so fallback names will be used instead.
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)
💡 Model/Recommended: We recommend you use a model for `#/components/schemas/TransactionFeeConfig`
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)
💡 Model/Recommended: We recommend you use a model for `#/components/schemas/TransactionFeeConfig`

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-06 22:40:38 UTC

@greptile-apps

greptile-apps Bot commented Jul 6, 2026

Copy link
Copy Markdown
Contributor

Greptile Summary

This PR formalizes platform transaction fee fields into the Grid API spec, preventing a future SDK regeneration from silently deleting live integrator-facing fields that are already deployed in the vendored webdev client (grid-api 0.0.82).

  • Adds TransactionFeeConfig and TransactionFeeEventType schemas, wires transactionFeeConfigs onto both PlatformConfig (read) and PlatformConfigUpdateRequest (write), and exposes platformFeesIncluded on Quote and platformFees on OutgoingTransaction.
  • The two bundled files (openapi.yaml, mintlify/openapi.yaml) are regenerated from source and are the only difference from the source-file changes, with the Redocly bundler relocating Currency/CurrencyAmount definitions earlier due to the new references from the config section.

Confidence Score: 4/5

Safe to merge; changes are additive spec additions that match the already-deployed API surface in the vendored webdev client.

The schema additions are straightforward and match live API behavior. The two notable rough edges — readOnly fields (id, isActive) surfacing in the shared write schema, and platformFeesIncluded being optional despite the API always returning it — are spec-design concerns that could affect SDK generation quality but do not misrepresent the API contract in a breaking way.

TransactionFeeConfig.yaml (shared read/write schema with readOnly fields) and Quote.yaml (platformFeesIncluded missing from required) are worth a second look before the next SDK regeneration.

Important Files Changed

Filename Overview
openapi/components/schemas/config/TransactionFeeConfig.yaml New schema for platform transaction fee config; correctly uses readOnly for id/isActive, but the same schema is reused verbatim for both the read response and the write request body, leaving readOnly fields visible to SDK generators in the request context.
openapi/components/schemas/config/TransactionFeeEventType.yaml New enum with two values; description correctly documents the asymmetry between read (full set) and write (CROSS_CURRENCY_TRANSACTION only).
openapi/components/schemas/config/PlatformConfig.yaml Adds transactionFeeConfigs read-side array; placement before createdAt is consistent with existing field ordering.
openapi/components/schemas/config/PlatformConfigUpdateRequest.yaml Adds transactionFeeConfigs write-side array reusing TransactionFeeConfig; readOnly fields (id, isActive) are technically correct per OpenAPI 3.1 semantics but may appear in SDK-generated request types depending on the generator used.
openapi/components/schemas/quotes/Quote.yaml Adds platformFeesIncluded as an optional field; field is documented as always-present (returns 0 when no fee configured) but is not added to the required list, making it technically optional in the spec.
openapi/components/schemas/transactions/OutgoingTransaction.yaml Adds platformFees as an optional field consistent with the existing fees field (also optional); naming mirrors the parent field correctly.
openapi.yaml Generated bundle; diffs correctly reflect all source-file additions plus Redocly relocating Currency/CurrencyAmount definitions earlier.
mintlify/openapi.yaml Generated bundle identical in content to openapi.yaml; changes are a mirror of source schema additions.

Entity Relationship Diagram

%%{init: {'theme': 'neutral'}}%%
erDiagram
    PlatformConfig {
        string id
        string umaDomain
        string webhookEndpoint
        datetime createdAt
        datetime updatedAt
    }
    PlatformConfigUpdateRequest {
        string umaDomain
        string webhookEndpoint
    }
    TransactionFeeConfig {
        string id PK
        string feeEventType FK
        string sourceCurrency
        integer variableFeeBps
        boolean isActive
    }
    TransactionFeeEventType {
        string CROSS_CURRENCY_TRANSACTION
        string TRANSFER_OUT_TRANSACTION
    }
    CurrencyAmount {
        integer amount
        object currency
    }
    Quote {
        string id
        integer feesIncluded
        integer platformFeesIncluded
    }
    OutgoingTransaction {
        string id
        integer fees
        integer platformFees
    }

    PlatformConfig ||--o{ TransactionFeeConfig : "transactionFeeConfigs (read)"
    PlatformConfigUpdateRequest ||--o{ TransactionFeeConfig : "transactionFeeConfigs (write/upsert)"
    TransactionFeeConfig ||--|| TransactionFeeEventType : "feeEventType"
    TransactionFeeConfig ||--|| CurrencyAmount : "fixedFee"
    Quote }o--|| Quote : "platformFeesIncluded subset of feesIncluded"
    OutgoingTransaction }o--|| OutgoingTransaction : "platformFees subset of fees"
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"}}}%%
erDiagram
    PlatformConfig {
        string id
        string umaDomain
        string webhookEndpoint
        datetime createdAt
        datetime updatedAt
    }
    PlatformConfigUpdateRequest {
        string umaDomain
        string webhookEndpoint
    }
    TransactionFeeConfig {
        string id PK
        string feeEventType FK
        string sourceCurrency
        integer variableFeeBps
        boolean isActive
    }
    TransactionFeeEventType {
        string CROSS_CURRENCY_TRANSACTION
        string TRANSFER_OUT_TRANSACTION
    }
    CurrencyAmount {
        integer amount
        object currency
    }
    Quote {
        string id
        integer feesIncluded
        integer platformFeesIncluded
    }
    OutgoingTransaction {
        string id
        integer fees
        integer platformFees
    }

    PlatformConfig ||--o{ TransactionFeeConfig : "transactionFeeConfigs (read)"
    PlatformConfigUpdateRequest ||--o{ TransactionFeeConfig : "transactionFeeConfigs (write/upsert)"
    TransactionFeeConfig ||--|| TransactionFeeEventType : "feeEventType"
    TransactionFeeConfig ||--|| CurrencyAmount : "fixedFee"
    Quote }o--|| Quote : "platformFeesIncluded subset of feesIncluded"
    OutgoingTransaction }o--|| OutgoingTransaction : "platformFees subset of fees"
Loading
Prompt To Fix All With AI
Fix the following 2 code review issues. Work through them one at a time, proposing concise fixes.

---

### Issue 1 of 2
openapi/components/schemas/config/TransactionFeeConfig.yaml:30-46
**`readOnly` fields visible in write context via shared schema**

`TransactionFeeConfig` is referenced by both `PlatformConfig` (response) and `PlatformConfigUpdateRequest` (request body). The `id` and `isActive` fields are marked `readOnly: true`, which OpenAPI 3.1 semantics say should be excluded from requests — but many SDK generators (e.g. OpenAPI Generator Java/TypeScript) do not strip `readOnly` fields from generated request types for shared schemas; they only omit them from serialization, leaving the door open for generated clients to populate them and send them in the PATCH body. A common mitigation is a dedicated write schema (e.g. `TransactionFeeConfigInput`) that omits those two fields entirely, or using `allOf` composition with the read schema to override them out.

### Issue 2 of 2
openapi/components/schemas/quotes/Quote.yaml:90-99
**`platformFeesIncluded` not added to `required` despite always-present semantics**

The description states the field returns `0` when no applicable fee is configured, implying the API always emits it. `feesIncluded` — the parent field this is a subset of — is already in the `required` list. Keeping `platformFeesIncluded` optional means generated clients will model it as nullable/absent, and response-validation code won't catch a server regression that accidentally drops it. If the field is guaranteed in all responses (including those predating this PR), it should be added to `required`; if it may be absent for legacy quotes, that should be noted in the description.

Reviews (1): Last reviewed commit: "Add platform transaction fee config + pl..." | Re-trigger Greptile

Comment on lines +30 to +46
fixedFee:
$ref: ../common/CurrencyAmount.yaml
description: >-
Fixed fee charged per transaction, in the smallest unit of
`sourceCurrency`. `fixedFee.currency.code` must match `sourceCurrency`.
isActive:
type: boolean
description: >-
Whether this row is currently active. Reads return only active rows
unless explicitly filtered otherwise.
readOnly: true
example: true
required:
- feeEventType
- sourceCurrency
- variableFeeBps
- fixedFee

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 readOnly fields visible in write context via shared schema

TransactionFeeConfig is referenced by both PlatformConfig (response) and PlatformConfigUpdateRequest (request body). The id and isActive fields are marked readOnly: true, which OpenAPI 3.1 semantics say should be excluded from requests — but many SDK generators (e.g. OpenAPI Generator Java/TypeScript) do not strip readOnly fields from generated request types for shared schemas; they only omit them from serialization, leaving the door open for generated clients to populate them and send them in the PATCH body. A common mitigation is a dedicated write schema (e.g. TransactionFeeConfigInput) that omits those two fields entirely, or using allOf composition with the read schema to override them out.

Prompt To Fix With AI
This is a comment left during a code review.
Path: openapi/components/schemas/config/TransactionFeeConfig.yaml
Line: 30-46

Comment:
**`readOnly` fields visible in write context via shared schema**

`TransactionFeeConfig` is referenced by both `PlatformConfig` (response) and `PlatformConfigUpdateRequest` (request body). The `id` and `isActive` fields are marked `readOnly: true`, which OpenAPI 3.1 semantics say should be excluded from requests — but many SDK generators (e.g. OpenAPI Generator Java/TypeScript) do not strip `readOnly` fields from generated request types for shared schemas; they only omit them from serialization, leaving the door open for generated clients to populate them and send them in the PATCH body. A common mitigation is a dedicated write schema (e.g. `TransactionFeeConfigInput`) that omits those two fields entirely, or using `allOf` composition with the read schema to override them out.

How can I resolve this? If you propose a fix, please make it concise.

Comment on lines +90 to +99
platformFeesIncluded:
type: integer
format: int64
description: >-
The portion of `feesIncluded` collected by the platform
(platform-configured transaction fees), in the smallest unit of the
sending currency. 0 when the platform has no applicable fee configured.
Already included in `feesIncluded`.
minimum: 0
example: 5

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 platformFeesIncluded not added to required despite always-present semantics

The description states the field returns 0 when no applicable fee is configured, implying the API always emits it. feesIncluded — the parent field this is a subset of — is already in the required list. Keeping platformFeesIncluded optional means generated clients will model it as nullable/absent, and response-validation code won't catch a server regression that accidentally drops it. If the field is guaranteed in all responses (including those predating this PR), it should be added to required; if it may be absent for legacy quotes, that should be noted in the description.

Prompt To Fix With AI
This is a comment left during a code review.
Path: openapi/components/schemas/quotes/Quote.yaml
Line: 90-99

Comment:
**`platformFeesIncluded` not added to `required` despite always-present semantics**

The description states the field returns `0` when no applicable fee is configured, implying the API always emits it. `feesIncluded` — the parent field this is a subset of — is already in the `required` list. Keeping `platformFeesIncluded` optional means generated clients will model it as nullable/absent, and response-validation code won't catch a server regression that accidentally drops it. If the field is guaranteed in all responses (including those predating this PR), it should be added to `required`; if it may be absent for legacy quotes, that should be noted in the description.

How can I resolve this? If you propose a fix, please make it concise.

Note: If this suggestion doesn't match your team's coding style, reply to this and let me know. I'll remember it for next time!

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