Skip to content

fix(payments): reject lossy mint conversions and enforce exact u64 amounts - #2453

Open
multipletwigs wants to merge 11 commits into
mainfrom
bashtwigs/ape-355-399-token-2022-ui-amounts
Open

multipletwigs wants to merge 11 commits into
mainfrom
bashtwigs/ape-355-399-token-2022-ui-amounts

Conversation

@multipletwigs

@multipletwigs multipletwigs commented Oct 20, 2026 •

Copy link
Copy Markdown
Collaborator

Payments now reject interest-bearing, scaled-UI and non-transferable mints instead of converting a requested amount through floating-point rounding or truncation (APE-355, APE-399). Ordinary amounts use Kit strict decimal fixed-points and remain bigint through instruction construction.

  • parseTokenAmount enforces exact precision and the full unsigned 64-bit range; excess precision or overflow returns 400 through the payment converter. Values above 2^53 stay exact.
  • One mint read supplies the owner program, actual decimals and amount converter. Custom mints must exist, decode as initialized mints and belong to an SPL token program. Cluster-specific, verified catalogue facts retain their existing shortcut.
  • Single sends, ramp-deposit funding/builds, batches, pay-link builds and recurring activation/update/collection use the same rules. Cancelling an existing recurring payment remains available.
  • Transfer-fee policy is deliberately deferred: USDG and PYUSD carry TransferFeeConfig. This PR preserves their existing support and does not claim to solve fee-adjusted recipient amounts.
  • Payment-request creation precision and exact settlement comparison are handled in stacked fix(payments): validate and settle exact payment amounts (APE-853) #2473. Finality remains in fix(payments): wait for finalized payment requests (APE-570) #2480.

RPC budget (mint/source reads only): a custom SPL send or batch goes from 3 calls to 2; a catalogue SPL send goes from 2 to 1. Unsupported interest/scaled mints are rejected after one mint read, with no clock read. Recurring builds reuse mint facts for the ATA lookup rather than fetching token supply separately.

Verification: 213-test initial payment/batch/recurring/subscription suite, final 147 recurring tests after mint-read cleanup, 18 mint-admission tests, 15 pay-link tests, 6 ramp-funding tests and 25 shared amount tests; API and shared-package typechecks; Biome; value-movement boundary check. The strengthened live catalogue check initially exposed transfer-fee extensions on USDG/PYUSD; its final policy checks interest, scale and non-transferability and passes all 22 catalogue mints.

Compatibility: interest-bearing, scaled-UI and non-transferable mints now return 400 on new payment builds. No schema migration or new dependency.

@vercel

vercel Bot commented Oct 20, 2026 •

Copy link
Copy Markdown

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

Project Deployment Actions Updated
sdp-docs Ready Ready Preview Oct 21, 2026 6:11pm UTC
sdp-web Ready Ready Preview Oct 21, 2026 6:11pm UTC

Request Review

@linear

linear Bot commented Oct 20, 2026

Copy link
Copy Markdown

APE-355

@greptile-apps

greptile-apps Bot commented Oct 20, 2026 •

Copy link
Copy Markdown
Contributor

RetriggerView in GreptileConfidence Score: 5/5

[Critical impact] The PR appears safe to merge with the verified fixes in stacked PR #2473.

Summary

Payments now reject interest-bearing, scaled-UI, and non-transferable mints. Amounts stay exact through conversion and instruction construction.

  • Payment builds use exact amounts and reject unsupported token mints.
  • Recurring payments use the same exact amounts and supported mints as other sends.
  • Canceling a recurring payment stays available after its token is paused.

Reviews (7) · Last reviewed commit: "Merge remote-tracking branch 'origin/mai..." · Reviewed by Greptile

Comment thread apps/sdp-api/src/services/payments/recurring-payments/shared.ts Outdated
Comment thread apps/sdp-api/src/services/payments/token-amount.ts Outdated
Comment thread apps/sdp-api/src/services/payments/token-amount.test.ts Outdated
Comment thread apps/sdp-api/src/services/payments/token-amount.test.ts Outdated
@github-actions

github-actions Bot commented Oct 21, 2026 •

Copy link
Copy Markdown
Contributor

React Doctor skipped this pull request — it changed no React files.

Reviewed by React Doctor for commit ab3d729.

…est or scale

Single transfers, batches and payment requests convert through one
loadPaymentAmountConverter per mint, so TransferChecked debits the requested
UI amount on InterestBearing and ScaledUiAmount mints (APE-355, APE-399).
Recurring payments and subscription plans refuse those mints, since their
per-period cap is fixed in base units. A batch recipient that converts to zero
base units is refused.
Cancel is an exit: run the start-admission token check only on resume, so a
schedule on a paused, archived, or scaled/interest-bearing issued token can
still be stopped.
…the cluster RPC from scope

The UI-amount mint read answers a well-known mint declared on the open
scope's cluster from the catalogue, skipping the RPC read; check:well-known-mints
now also pins that no catalogue mint carries an interest-bearing or scaled UI
amount extension. The converter and static-decimals read take the cluster RPC
from scope instead of a caller-passed client.
@multipletwigs multipletwigs changed the title fix(payments): convert Token-2022 UI amounts through the mint's interest or scale fix(payments): reject lossy mint conversions and enforce exact u64 amounts Oct 21, 2026
Comment thread apps/sdp-api/src/services/payments/recurring-payments/collection.ts
@multipletwigs

Copy link
Copy Markdown
Collaborator Author

@greptileai please review again!

G1de0n
G1de0n previously approved these changes Oct 21, 2026

@G1de0n G1de0n left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Approving, refusing these mints fail-closed is a safer call than float/Clock-dependent conversion, and the exact u64 parse looks right. One thing to settle before merge: SDP issuance itself offers interestBearing / scaledUiAmount / nonTransferable on the stablecoin templates and interestBearing on securities (SUPPORT_MATRIX.md:44-50), so tokens we issue can't be paid after this, and an active recurring payment on one would 400 every cycle. Worth a prod query for issued tokens with those extensions that have active recurring payments or subscriptions, and either confirm it's zero or decide what happens to them. Smaller:

  • check:well-known-mints is the guard for the catalogue shortcut in loadPaymentMint, but no workflow runs it. Worth wiring into CI on well-known-tokens.ts changes.
  • decodeMint with token-2022 0.16.0 is a closed union, so a mint with a newer extension now 400s where getTokenSupply used to work. Fine if accepted.
  • Stale "static scaling" comment and a redundant re-parse in transfer-batches/resolve.ts:146-155.

@G1de0n G1de0n left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Adding to the issuance point: the public docs present scaledUiAmount as part of the tokenized-security template (issuance-token-types.mdx:12, and the regulated-stablecoin tutorial :149 lists it under "Required extensions"), while definitions.ts only requires permanentDelegate + pausable. So anyone following the tutorial issues a security token that this PR then refuses for transfers, batches, pay-links and recurring, and nothing tells them at issuance time. Probably worth either fixing the docs or warning at token creation that these extensions aren't payable through SDP.

Small test gaps: /pay/:token/tx has no case for the extension refusal, and the NonTransferable refusal isn't covered in token-amount.test.ts.

This branch was successfully deployed

2 active deployments
Preview – sdp-web — ab3d7290 Deployed Oct 21, 2026 by vercel[bot]
Preview – sdp-docs — ab3d7290 Deployed Oct 21, 2026 by vercel[bot]
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