Restore and unify API_REQUIREMENTS.md - #489
Conversation
Bugbot is paused — on-demand spend limit reachedBugbot 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. |
|
You have reached your Codex usage limits for code reviews. You can see your limits in the Codex usage dashboard. |
4a9cec8 to
f62893c
Compare
b671e36 to
474ef23
Compare
API_REQUIREMENTS.md
814abb0 to
2874454
Compare
Bugbot is paused — on-demand spend limit reachedBugbot 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. |
1b7be78 to
a444496
Compare
Bugbot is paused — on-demand spend limit reachedBugbot 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. |
204af1b to
840483f
Compare
Bugbot is paused — on-demand spend limit reachedBugbot 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. |
840483f to
dddb7a1
Compare
Bugbot is paused — on-demand spend limit reachedBugbot 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. |
|
Doc-only change, safe to merge without explicit review per our policy. |
CHANGELOG
Does this branch warrant an entry to the CHANGELOG?
Dependencies
none
Description
Doc-only. Makes native units the single amount convention in
docs/API_REQUIREMENTS.md, and restores six requirements thatff4cc3dweakened, 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
ef51604transcribes 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'sasSafeNumbertyping. 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
asSafeNumberfact 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 aboutStandardTxrather 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:
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:
978900000satoshis (9.789 BTC, correct) and1000000000000micro-units ($1M USDT, correct). The pair stayed coherent. The only defect was the trailing comment: it read0.009789 BTCagainst a value of 9.789 BTC.ff4cc3dresolved 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 sentenceff4cc3dadded.The value is restored and the comment corrected.
Display-unit limits also cost something in the plugins.
denominationToNativeis 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
ff4cc3dchainIddepositAddress/payoutAddress,depositTxid/payoutTxid,depositTokenId/payoutTokenId,depositEvmChainId/payoutEvmChainIdauthKeygenerated by Edge, cryptographically randomTwo of these change a live conformance verdict. SimpleSwap passes on EVM
chainIdand on reporting pagination only under theff4cc3dtext, and fails both against the source doc.The dropped chain identifier is an outright bug rather than a relaxation.
ff4cc3drewrote the reporting table aroundStandardTxfield names and lostsourceNetwork/destinationNetworkin the process, with no replacement row.StandardTxhas 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.
ff4cc3dreplaced 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.
ff4cc3dturned the return URI into one of three equal options, alongside apostMessageor 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
ff4cc3ddropped 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.mdThe plugin guide carried the same defect, in the block plugin authors copy. Step 6's limit sample was byte-for-byte the code
ff4cc3dhad 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 onenativeLimitand threw it as bothSwapBelowLimitErrorandSwapAboveLimitError, 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
denominationToNativeresult 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/floorso 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.mdstates 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.mdand.cursor/BUGBOT.mdAGENTS.mdnamed 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.mdunder Amount units and inBUGBOT.mdunderverify-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:
API_REQUIREMENTS.mdsection 4 gains a Rate types subsection. Where a provider offers more than one rate type, the API must expose which types a route supports, so the client selects one rather than attempting a type and inferring support from the failure.BUGBOT.mdgains three review rules:per-endpoint-auth,catalog-cache-expiry, andrate-type-fallback-scope.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
muston 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 a5xxfor 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
ceiland its above-limit withfloor, 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.shpassed 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
ff4cc3dMost 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
StandardTxstatus table, theinfoNeededrequirement, and the edge-core-js error class mapping all stay.