Skip to content

Restore and unify API_REQUIREMENTS.md - #489

Merged
j0ntz merged 1 commit into
masterfrom
jon/api-requirements-unit-regression
Aug 25, 2026
Merged

Restore and unify API_REQUIREMENTS.md#489
j0ntz merged 1 commit into
masterfrom
jon/api-requirements-unit-regression

Conversation

@j0ntz

@j0ntz j0ntz commented Aug 20, 2026

Copy link
Copy Markdown
Contributor

CHANGELOG

Does this branch warrant an entry to the CHANGELOG?

  • Yes
  • No

Dependencies

none

Description

Doc-only. Makes native units the single amount convention in docs/API_REQUIREMENTS.md, and restores six requirements that ff4cc3d weakened, each of them weaker than both #442 and the original requirements doc this file was transcribed from.

Found while grading SimpleSwap against this doc for Asana 1217205278669378, which surfaced the units contradiction in section 3. Auditing the rest of the revision turned up the other five.

The reference point throughout is Paul's "2025 Exchange Provider API Requirements" doc (last modified 2025-12-19), which ef51604 transcribes almost verbatim.

One unit convention, stated once

The document had drifted into three conventions at once. Section 3 asked for display units and described itself as "the one place the native-unit preference does not apply". Section 6 asked for display units, citing StandardTx's asSafeNumber typing. Section 4 asked for native units.

All of it now reads the same way. Amount representation states outright that it governs every amount field and that no section carries a different convention, and sections 3, 4 and 6 use identical wording: native is a should, and a display-unit API must document that it is one.

The asSafeNumber fact moves to where it belongs. It constrains Edge's storage, not the provider's wire format, and the reporting plugin already converts, so it is now a note about StandardTx rather than an instruction to partners.

Worth stating plainly: the source doc has no unit convention anywhere. Its only amounts example sits inside the error-handling section, whose purpose is to show that every applicable error comes back in one response, so the display units in it are incidental to the point being made rather than a decision about units. Reading a convention out of an incidental example is exactly what drifted, twice. The native-unit preference was introduced by #442 and approved there; this PR makes it uniform rather than inventing it.

The 1000x error in section 3's example

The source doc's example is a BTC to USDT above-limit error:

"sourceAmountLimit": 9.789,        // BTC
"destinationAmountLimit": 1000000  // USDT

Those are not two unrelated numbers. 9.789 BTC and 1,000,000 USDT are the same $1M cap expressed in each asset, which is exactly what the section requires ("min/max amounts specified in both the source and destination asset").

#442 converted both to native units: 978900000 satoshis (9.789 BTC, correct) and
1000000000000 micro-units ($1M USDT, correct). The pair stayed coherent. The only defect was the trailing comment: it read 0.009789 BTC against a value of 9.789 BTC.

ff4cc3d resolved that mismatch by trusting the comment. It rewrote the source limit as the display string "0.009789", roughly $1,000, and left the destination at 1,000,000 USDT. The two sides of one limit now differ by 1000x, and the section that exists to require both sides illustrates it with a pair that cannot both be true. That mismatch is the entire basis for the display-unit sentence ff4cc3d added.

The value is restored and the comment corrected.

Display-unit limits also cost something in the plugins. denominationToNative is a plain multiply, so a display-unit limit carrying more decimals than the asset's denomination lands on a fractional native value, and Edge limits must be whole atomic units. Each plugin is then left picking a rounding direction for a bound it did not set. Native units are integers, so the whole class disappears.

The six restorations

# Requirement Source doc #442 ff4cc3d Now
1 EVM chainId must must should must
6 Paginated queries with start date, end date, count must must one of three optional approaches must
6 depositAddress/payoutAddress, depositTxid/payoutTxid, depositTokenId/payoutTokenId, depositEvmChainId/payoutEvmChainId must must Recommended must
6 Chain identifier per side must must absent from the table must
9 authKey generated by Edge, cryptographically random must must "a unique per-user identifier", provider-created sessions acceptable must
14 Widget accepts a return URI from Edge must must one of three equal alternatives must

Two of these change a live conformance verdict. SimpleSwap passes on EVM chainId and on reporting pagination only under the ff4cc3d text, and fails both against the source doc.

The dropped chain identifier is an outright bug rather than a relaxation. ff4cc3d rewrote the reporting table around StandardTx field names and lost sourceNetwork / destinationNetwork in the process, with no replacement row. StandardTx has the field (depositChainPluginId / payoutChainPluginId), so the table was simply missing it, and as written a provider could satisfy every listed field while leaving Edge unable to tell which chain a contract address belongs to.

Section 1's restoration also brings back the reason the source doc gives, which went out with the obligation: numeric chain ids let a newly listed EVM work the day it is added, with no new entry in the plugin's mapping file. ff4cc3d replaced that with "avoids ambiguity with provider-specific EVM network names", a weaker claim supporting a weaker requirement.

Section 14 is the narrowest of the six: it sits under "Additional Requirements for Fiat On/Off Ramp Providers", so it binds fiat ramps only and no swap provider ever reaches it. ff4cc3d turned the return URI into one of three equal options, alongside a postMessage or navigation to a known path. Those are not equivalent. Any step taking a card payment, Apple Pay or Google Pay has to run in the system browser, both because those payment methods are unavailable to an embedded WebView and because an embedded WebView is a surface the host app can inject JavaScript into, which no card processor accepts for cardholder data entry. Edge cannot observe that page, so a redirect to a URI it registered is the only route back. The section keeps the observable signals, scoped to the non-payment steps Edge does display in its own WebView (bank linking, sell flows), and says plainly that a widget offering only those cannot host a payment step.

Examples

Every example now carries native amounts annotated with the display equivalent in a trailing comment, matching how the source doc and #442 wrote theirs. The asset and chain comments ff4cc3d dropped from the section 1 examples are back for the same reason.

One addition that is not a restoration: native amounts are now specified as JSON strings. A high-decimal asset in native units runs past the IEEE-754 safe integer range (1 ETH is 1e18 wei, well beyond 2^53), so a universal native-unit rule is unusable as JSON numbers. Section 3's example already used strings; this states why.

Section 3's counter-example ("Amount is below the minimum of 0.0001 BTC") keeps its display amount, with a line noting that the defect there is the shape and not the units, so it does not get "fixed" into a native amount later.

CREATING_AN_EXCHANGE_PLUGIN.md

The plugin guide carried the same defect, in the block plugin authors copy. Step 6's limit sample was byte-for-byte the code ff4cc3d had put in section 3, and it broke three ways at once: it converted unconditionally, so a provider that follows the new section 3 gets multiplied twice and shows a limit inflated by 10^decimals; it did not round; and it derived one nativeLimit and threw it as both SwapBelowLimitError and SwapAboveLimitError, so no single rounding direction could have been correct for it.

The second of those predates this branch. That sample failed the guide's own pre-PR checklist, printed 230 lines below it, which already required every denominationToNative result to be rounded and minimums to round up. Two plugins now on unmerged branches carry a hand-retrofitted version of the same fix.

The sample now selects the side, rounds a minimum up and a maximum down, and says in a comment that the unit convention has to be confirmed against a live response. Step 5 gains the same caveat, and both samples import ceil / floor so they compile as written.

The fix deliberately keeps the conversion as the main path rather than flipping to native. Most providers a plugin author integrates today still send display units, so a sample that assumed compliance would be wrong for the common case in the same way the current one is wrong for the compliant case.

The two documents have different jobs. API_REQUIREMENTS.md states what Edge asks a partner to send, so it can name one convention. The plugin guide states what plugin code does with what actually arrives, so it cannot. Each had drifted into the other's job: the guide hardcoded a convention, and section 3 had taken the plugin's conversion behavior and turned it into a demand on partners.

AGENTS.md and .cursor/BUGBOT.md

AGENTS.md named this doc as reading for evaluating a provider or arguing a gap back to them. It is now pre-implementation reading too, in the docs list and at the top of "Starting a new provider", where the useful form of the point is that a gap you are about to work around may be the provider's to fix rather than yours.

Both files also record what the doc is not. It states what Edge asks partners for, not what any given provider sends, and most existing integrations predate the request and send decimals. So it settles nothing in a units argument about a specific provider, where a live response still does. That guard goes in AGENTS.md under Amount units and in BUGBOT.md under verify-amount-units, since both already carry the rule that a documented unit is not evidence.

Nothing else in either file needed changing. The plugin-side rounding rules (native-amounts-are-integers, limit-rounding-direction) stay where they are; they describe what a plugin does with a limit, which is not a partner requirement and was the reason I kept that detail out of section 3.

Folded in from #485

#485 drops its two documentation commits, and their content lands here instead. Neither hunk overlaps anything this PR already changed, so nothing had to be reconciled:

The rate-types requirement is an addition, not a restoration, in the same category as the JSON-strings guidance above. Neither the source doc nor #442 mentions rate types, and it creates a new must on partners. The reasoning behind it: a provider that publishes rate types per asset but not per route leaves the client attempting a type and reading the failure, and a 5xx for a route the provider cannot fix is indistinguishable from an outage.

The StealthEX plugin on that branch confirms the Step 6 fix rather than motivating it. It already rounds its below-limit with ceil and its above-limit with floor, which is what the corrected sample now shows and what the old one did not.

Prose pass

Both documents get a pass against the house writing rules, which neither had had. 16 em dashes are replaced with commas, colons or sentence breaks, including one inside a code comment that a fence hid from the linter. Gone with them: two "Additionally" openers, two "What matters is that" forward references, three count-announcement openers, "load-bearing", and a run of seven consecutive bullets that each carried mid-prose bold, so none of it landed. Headings move to sentence case in both files, and the plugin guide's opening line stops restating its own title. Its table of contents was also missing the pre-PR checklist.

The judgment-tier rules are the ones that mattered here. no-slop-lint.sh passed both files before most of the findings above, since it cannot see title case, bold density, a heading restated by the line under it, or an em dash inside a code fence. It returns clean on all four files now, and every in-document anchor still resolves after the heading changes.

What this keeps from ff4cc3d

Most of it. No sections were dropped, the bolded must count went 22 to 27, and sections 2, 5 and 7 got materially stricter and clearer. Everything here is a targeted revert of six items plus the units unification; the integration detail, the StandardTx status table, the infoNeeded requirement, and the edge-core-js error class mapping all stay.

@cursor

cursor Bot commented Aug 20, 2026

Copy link
Copy Markdown

Bugbot is paused — on-demand spend limit reached

Bugbot uses usage-based billing for this team and has hit its on-demand spend limit.

A team admin can raise the spend limit in the Cursor dashboard, or wait for the next billing cycle to continue.

@chatgpt-codex-connector

Copy link
Copy Markdown

You have reached your Codex usage limits for code reviews. You can see your limits in the Codex usage dashboard.

@j0ntz
j0ntz force-pushed the jon/api-requirements-unit-regression branch 2 times, most recently from 4a9cec8 to f62893c Compare August 20, 2026 22:24
@j0ntz j0ntz changed the title Restore native units for limit error amounts Restore API requirements weakened without review in ff4cc3d Aug 20, 2026
@j0ntz
j0ntz force-pushed the jon/api-requirements-unit-regression branch from b671e36 to 474ef23 Compare August 20, 2026 22:35
@j0ntz j0ntz changed the title Restore API requirements weakened without review in ff4cc3d Restore and unify API_REQUIREMENTS.md Aug 20, 2026
@j0ntz
j0ntz force-pushed the jon/api-requirements-unit-regression branch 4 times, most recently from 814abb0 to 2874454 Compare August 21, 2026 17:23
@cursor

cursor Bot commented Aug 21, 2026

Copy link
Copy Markdown

Bugbot is paused — on-demand spend limit reached

Bugbot uses usage-based billing for this team and has hit its on-demand spend limit.

A team admin can raise the spend limit in the Cursor dashboard, or wait for the next billing cycle to continue.

@j0ntz
j0ntz force-pushed the jon/api-requirements-unit-regression branch 2 times, most recently from 1b7be78 to a444496 Compare August 21, 2026 17:51
@cursor

cursor Bot commented Aug 21, 2026

Copy link
Copy Markdown

Bugbot is paused — on-demand spend limit reached

Bugbot uses usage-based billing for this team and has hit its on-demand spend limit.

A team admin can raise the spend limit in the Cursor dashboard, or wait for the next billing cycle to continue.

@j0ntz
j0ntz force-pushed the jon/api-requirements-unit-regression branch 4 times, most recently from 204af1b to 840483f Compare August 21, 2026 18:27
@cursor

cursor Bot commented Aug 21, 2026

Copy link
Copy Markdown

Bugbot is paused — on-demand spend limit reached

Bugbot uses usage-based billing for this team and has hit its on-demand spend limit.

A team admin can raise the spend limit in the Cursor dashboard, or wait for the next billing cycle to continue.

Native units everywhere. Restores six requirements weakened in ff4cc3d.
Fixes the plugin guide's limit sample. Folds in #485's docs.
@j0ntz
j0ntz force-pushed the jon/api-requirements-unit-regression branch from 840483f to dddb7a1 Compare August 21, 2026 18:30
@cursor

cursor Bot commented Aug 21, 2026

Copy link
Copy Markdown

Bugbot is paused — on-demand spend limit reached

Bugbot uses usage-based billing for this team and has hit its on-demand spend limit.

A team admin can raise the spend limit in the Cursor dashboard, or wait for the next billing cycle to continue.

@j0ntz j0ntz mentioned this pull request Aug 21, 2026
2 tasks
@j0ntz

j0ntz commented Aug 25, 2026

Copy link
Copy Markdown
Contributor Author

Doc-only change, safe to merge without explicit review per our policy.

@j0ntz
j0ntz merged commit 72e56e9 into master Aug 25, 2026
3 checks passed
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