Repository navigation
fix(trades): convert sell amounts to stroops explicitly (issue #292) - #426
Merged
dark-sarge merged 4 commits intoOct 1, 2026
Merged
Conversation
…sell path The sell form sent the raw naira figure and services/stellar.ts multiplied by 1_000_000 on the way to the escrow contract, so the conversion was implicit and a future call site could easily scale the amount a second time. Add `toStroops`/`fromStroops`/`asStroops` to packages/shared, have the sell form convert once, and require a positive integer already in stroops at the API. The service forwards that integer to the contract untouched, while the naira the seller quoted is still what lands in the naira-denominated platform ledger. 🤖 Generated with Codebuff Co-Authored-By: Codebuff <noreply@codebuff.com>
|
@CodingBabe-1 Great news! 🎉 Based on an automated assessment of this PR, the linked Wave issue(s) no longer count against your application limits. You can now already apply to more issues while waiting for a review of this PR. Keep up the great work! 🚀 |
Both Frontend CI and Server CI start with `pnpm install --frozen-lockfile`, which now fails with ERR_PNPM_OUTDATED_LOCKFILE because frontend/package.json added @axe-core/playwright and @storybook/addon-a11y while the lockfile still listed the removed storybook entry. Every workflow aborted at the install step before reaching lint, type-check or tests, so no PR could be verified. Regenerated with the repo's pinned pnpm 10.28.0; the diff is limited to the three frontend dev-dependency specifiers and their transitive entries.
`pnpm test -- --run --json --outputFile=test-results.json` expands to `jest --runInBand --forceExit --run ...`, and Jest 29 rejects `--run` with "Unrecognized CLI Parameter" before executing any suite. No test-results.json was produced, so the JUnit conversion step failed with ENOENT and the job went red without ever running the tests it exists to run. Verified locally with the CI environment (NODE_ENV=test, .env.test): 27 suites / 198 tests execute and test-results.xml is written.
This was referenced Sep 30, 2026
Merged
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
The sell form collected a naira amount and
services/stellar.tsmultiplied itby
1_000_000on the way to the escrow contract, so the unit conversion wasimplicit and buried in the service. A second conversion anywhere on the path
would silently create a listing for a million times the intended amount.
This makes the conversion explicit and shared: the client converts once with
toStroops()frompackages/shared, the create-listing API validates theamount as a positive integer already in stroops, and the service forwards
that exact integer to the contract. There is no scale factor left on the
contract path.
Closes #292
Type of Change
feat— new featurefix— bug fixrefactor— code change with no behaviour changedocs— documentation onlychore— build, deps, configcontract— Soroban smart contract changeWhat Changed.
packages/shared/units.js/units.d.ts.d.tsmodule:toStroops(amount: number): bigint(pure converter,Math.round(amount * 1e6), rejects non-finite),fromStroops()(accepts bigint/number/PostgresNUMERICstring),asStroops(), andSTROOPS_PER_UNIT/MAX_TRADE_UNITS/MAX_TRADE_STROOPSconstants.packages/shared/package.jsonexportsmap exposing@airflex/shared/units(plus".") so the Next app and the Express server resolve the same implementation without a build step.frontend/app/sell/page.tsxNumber(toStroops(parseFloat(fields.amount)))— the naira the seller typed converted to stroops exactly once.server/src/schemas/trade.schemas.tsamountis nowz.number().int().positive().max(MAX_TRADE_STROOPS)— a positive integer already in stroops (ceiling = the sell form's ₦1,000,000 cap).server/src/routes/trades.tscreateListing(asStroops(amount), no* 1_000_000) and persists the naira equivalent withfromStroops; buy/prepare derives the deposit amount withtoStroops(Number(trade.amount)).server/src/services/stellar.tscreateListing/buildEscrowDepositXdrtakeamountStroops: bigintand pass it tonativeToScVal(..., "i128")unchanged; both* 1_000_000calls are gone and the span attribute is nowtrade.amount_stroops.server/src/schemas/trade.schemas.test.ts,server/src/utils/units.test.tsserver/src/routes/trades.create.test.tsfrontend/app/sell/sell.test.tsx5_000_000_000for a ₦5,000 listing.server/src/openapi.ts,server/openapi.json,docs/api-reference.md,docs/getting-started.mdamountas stroops onPOST /api/v1/tradesand note the naira ledger unit.How to Test
Manual check (server running, authenticated seller with verified KYC):
Checklist
General
tsc --noEmit).env.exampleupdated if new env vars were added (none added)API changes
docs/api-reference.mdSmart contract changes
Database changes
trade_offers.amountkeeps its existing naira meaningDocs changes
docs/updated or createdNotes for Reviewer
Unit boundary — please read.
POST /api/v1/tradesnow takesamountinstroops (the escrow contract's unit, per the issue) while responses keep
reporting
amountin naira. The platform ledger —trade_offers.amount,fee_amount,seller_net_amount, wallets andtransactions— is denominatedin naira, so the route stores
fromStroops(amount)rather than rewriting thecolumn's unit (which would have misread every existing row and needed a
migration to backfill). Only the request body and the contract call speak
stroops;
fromStroops()is used on the way back in for the deposit.toStroopsis a pure converter. It does not reject 0 or negatives —validation lives in the schema (
int().positive().max()) and in the sellform's
validate(). The unit tests pin the 0 / negative / fractional behaviourso it cannot drift.
Base commit. This branch sits on
a65afac(CI repair for the duplicategetServerKeypairand related breakage left by recent merges), which is alsothe base of #423. That commit is unrelated to #292 and is included only because
maindoes not currently build; once either merges the shared commit drops outof the diff.
Follow-ups worth considering (out of scope here): normalising the amount
filters (
GET /trades?minAmount=) and the admin analytics volume sums if theledger is ever switched wholesale to stroops.