Skip to content

feat(realunit): introduce /quote/* endpoint family, deprecate /brokerbot/*#3783

Merged
TaprootFreak merged 3 commits into
developfrom
feat/realunit-quote-endpoints
May 28, 2026
Merged

feat(realunit): introduce /quote/* endpoint family, deprecate /brokerbot/*#3783
TaprootFreak merged 3 commits into
developfrom
feat/realunit-quote-endpoints

Conversation

@TaprootFreak
Copy link
Copy Markdown
Collaborator

Summary

Introduces a /v1/realunit/quote/* mirror of the existing /v1/realunit/brokerbot/* routes. The mirror is the correctly-named, forward-going API surface; the legacy paths stay live but are now annotated deprecated: true in Swagger. Wire format is byte-identical — same service methods, same DTO shapes — so consumers can migrate at their own pace without breakage.

Why

The brokerbot/* family name leaks an implementation detail that is, on top of being internal, mostly wrong:

  • GET /brokerbot/price / buyPrice / buyShares / sellPrice / sellShares / info are pricing oracles. They call Aktionariat's REST API (/directinvestment/getPrice, 30 s cache). They never touch the on-chain Brokerbot smart contract.
  • PUT /buy is a fiat bank-transfer flow. Aktionariat allocates the shares off-chain via directinvestment/payAndAllocate. No Brokerbot smart contract involvement.
  • Only the sell side — PUT /sell/:id/unsigned-transactions + /broadcast plus RealUnitBlockchainService.getBrokerbotSellPrice — actually invokes the on-chain Brokerbot smart contract (the user signs an EIP-7702 transaction that we then broadcast to it).

Calling the entire price oracle surface "brokerbot" misleads every reader who looks at the controller and assumes there's an on-chain dependency.

What changed

src/subdomains/supporting/realunit/controllers/realunit.controller.ts

  • New section --- Quote Endpoints --- adds 6 routes mirroring the brokerbot ones:
    • GET /quote/info → delegates to getBrokerbotInfo
    • GET /quote/price → delegates to getBrokerbotPrice
    • GET /quote/buyPrice?shares=N → delegates to getBrokerbotBuyPrice
    • GET /quote/buyShares?amount=N → delegates to getBrokerbotBuyShares
    • GET /quote/sellPrice?shares=N → delegates to getBrokerbotSellPrice (auth-guarded as before)
    • GET /quote/sellShares?amount=N → delegates to getBrokerbotSellShares (auth-guarded as before)
  • The Swagger descriptions on the new routes explicitly call out the data source (Aktionariat REST, not the on-chain contract) so the next reader doesn't repeat the original misreading.
  • All six brokerbot/* routes get deprecated: true on @ApiOperation. Same handlers, same DTOs, same behaviour — only the Swagger flag changes.

CONTRIBUTING.md

  • New subsection "RealUnit: `/quote/` vs `/brokerbot/`" under "API Design". Tabular layout of which endpoints touch the on-chain Brokerbot vs which are pure price oracles, plus the operational consequences (treat /quote/* as cacheable pricing API; keep getBrokerbot… naming for the genuine on-chain methods only).

What did not change

  • DTO class names (BrokerbotPriceDto, BrokerbotBuyPriceDto, …). The wire JSON shape is part of the consumer contract; renaming the TypeScript classes would force a coordinated client migration that is out of scope for an additive rename.
  • The RealUnitBlockchainService methods. getBrokerbotSellPrice / getBrokerbotInfo keep their names because those do concern the on-chain Brokerbot contract.
  • Config (brokerbotAddress), the EIP-7702 delegation service references — anything that genuinely talks to the on-chain contract stays named as it was.

Backwards compatibility

100 % additive. No existing client breaks.

  • /v1/realunit/brokerbot/* routes continue to work, return the same response, with the same authentication requirements.
  • New /v1/realunit/quote/* routes are available as the migration target.
  • App-side migration follows in a separate PR; this PR unblocks it.

Verification

  • npm run lint → clean
  • npm run format:check → clean
  • npm run build → clean
  • npm test -- --testPathPattern=realunit.service → 14 / 14 passing (no behavioural change to assert against; existing brokerbot wiring is untouched)

…bot/*

The `/v1/realunit/brokerbot/*` family was misnamed: only the on-chain
sell-flow ever talks to the Brokerbot smart contract. The price/quote
endpoints (`/price`, `/buyPrice`, `/buyShares`, `/sellPrice`,
`/sellShares`, `/info`) are pricing oracles fed by Aktionariat's REST
API (`/directinvestment/getPrice`, 30 s cache) — and the buy flow
itself is a fiat bank transfer + off-chain `payAndAllocate` call.
Calling that surface "brokerbot" leaked an implementation detail that
isn't even accurate.

Adds the additive `/quote/*` mirror family. Each new route delegates
to the same service method as its `/brokerbot/*` counterpart and
returns the same DTO shape, so the wire format is byte-identical for
existing clients. The legacy `/brokerbot/*` routes are kept and
annotated `deprecated: true` in Swagger so consumers see the
migration target without being broken.

Also adds a CONTRIBUTING section ("RealUnit: /quote/* vs /brokerbot/*")
that lays out which endpoints touch the on-chain Brokerbot vs which
are pure price oracles, so the naming distinction sticks past this PR.
- Move `getQuoteSellShares` out of its accidental position below the
  deprecated `/brokerbot/*` block and into the Quote group next to
  `getQuoteSellPrice`, mirroring the brokerbot ordering.
- Reword the four `/quote/*` summaries from the novel `Quote: …`
  prefix to the controller's standard `Get …` verb form.
- Replace the six deprecated `/brokerbot/*` summary descriptions
  (one of which still claimed the value came `from the Brokerbot
  smart contract`) with neutral pointers to the canonical
  `/quote/*` mirror. Keeps the deprecated routes from contradicting
  the new CONTRIBUTING section.
- Rewrite the CONTRIBUTING table so every `PUT /sell*` route is
  listed with its actual on-chain footprint:
  `/sell`, `/sell/:id/unsigned-transactions` and `/sell/:id/confirm`
  all call `getBrokerbotSellPrice` (viem `readContract`);
  `/sell/:id/broadcast` does not — it only submits the signed tx.
…able

The block comment introducing `/quote/*` still claimed the on-chain
Brokerbot is touched in `unsigned-transactions + broadcast`, but the
table that the previous fix commit put into CONTRIBUTING (and the
service code itself) shows the on-chain reads happen in
`PUT /sell` + `/sell/:id/unsigned-transactions` + `/sell/:id/confirm`
— `/broadcast` only submits the signed transaction. Update the
comment to match.
@TaprootFreak TaprootFreak marked this pull request as ready for review May 28, 2026 08:05
@TaprootFreak TaprootFreak requested a review from davidleomay as a code owner May 28, 2026 08:05
@TaprootFreak
Copy link
Copy Markdown
Collaborator Author

Self-reviewed via independent subagent loop before flipping to ready:

  • Pass 1 (5 findings: 1 MUST, 3 SHOULD, 1 NIT) — getQuoteSellShares was orphaned outside the Quote group; CONTRIBUTING table missed two on-chain sell entry points and conflated /unsigned-transactions with /broadcast; deprecated brokerbot descriptions still claimed on-chain semantics; summary style drifted with a novel Quote: … prefix. Fixed in 00ba66d.
  • Pass 2 (1 SHOULD) — Section comment above --- Quote Endpoints --- still cross-referenced /broadcast as an on-chain entry, contradicting the table the same commit added. Fixed in 4e9cd64.
  • Pass 3VERDICT: PASS_CLEAN. All four getBrokerbotSellPrice (viem readContract) call sites verified against the table; lint / format / build / 35 realunit tests green.

@TaprootFreak TaprootFreak merged commit 0ca0d67 into develop May 28, 2026
7 checks passed
@TaprootFreak TaprootFreak deleted the feat/realunit-quote-endpoints branch May 28, 2026 08:17
TaprootFreak added a commit that referenced this pull request May 28, 2026
… /wallet/status

The endpoint that drives the RealUnit registration UX historically lived
under `/v1/realunit/wallet/status`. That path is misleading: the
resource described is the user's Aktionariat registration, not a
generic wallet status, and clients never ask "what is the wallet's
status?" — they ask "what do I need to do to be RealUnit-registered?".

Following the precedent set by the `/quote/*` vs `/brokerbot/*` rename
(PR #3783), this commit:

- adds `GET /v1/realunit/registration` as the canonical endpoint
- keeps `GET /v1/realunit/wallet/status` as a `deprecated: true` mirror
  that calls the same service method and returns the same DTO instance
- renames `RealUnitWalletStatusDto` to `RealUnitRegistrationInfoDto`
  (wire-neutral: TS class names don't appear on the JSON; field names
  `state` / `userData` / `isRegistered` are unchanged)
- renames `RealUnitService.getAddressWalletStatus` to
  `getRegistrationInfo`; both controller routes delegate to it
- extends CONTRIBUTING.md "API Design" with a sister-section to the
  `/quote/*` rename, summarising the old→new mapping and the
  operational consequence (legacy path kept for backwards compatibility
  on existing clients only)
TaprootFreak added a commit that referenced this pull request May 28, 2026
)

* feat(realunit): pre-fill registration form from existing KYC data

Users with completed DFX KYC who initiate a RealUnit purchase get
sent through the Aktionariat registration flow, where the form
controllers currently start empty. They must retype data the
backend already holds, and `completeRegistration`'s
`isPersonalDataMatching` rejects any deviation byte-for-byte —
turning a one-tap signing step into a guessing game.

Make `GET /v1/realunit/wallet/status` fall back to the underlying
`user_data` record when no `RealUnitRegistration` KycStep exists
yet. The app already consumes `RealUnitWalletStatusDto.userData`
for the merge-existing-registration path; this extends the same
shape to first-time registrations so the form can pre-fill with
values that will pass server-side validation verbatim.

Controller loads the country / nationality / language relations
needed by the new mapper. Mapper degrades gracefully: no
firstname/surname → `userData: undefined` (back to manual entry,
same as before this PR).

* feat(realunit): expose registration state on wallet/status

Add a four-state discriminator (alreadyRegistered, addWallet,
newRegistration, kycRequired) to GET /v1/realunit/wallet/status so the
app can route to the right UX without inferring it locally. isRegistered
is preserved unchanged for backwards compatibility and remains
semantically equivalent to state === alreadyRegistered.

* fix(realunit): address self-review on the registration-state field

- Mark `isRegistered` with `@ApiProperty({ deprecated: true })` so
  Swagger UI and generated clients render the strikethrough — the
  prose-only deprecation in the previous commit relied on consumers
  reading the description text.
- Switch `RealUnitRegistrationState` enum values from camelCase
  (`alreadyRegistered`, …) to the CONTRIBUTING-mandated PascalCase
  (`AlreadyRegistered`, …). The pre-existing `RealUnitRegistrationStatus`
  enum in this file uses snake_case which is also non-compliant, but
  that's a separate consumer-breaking rename and stays out of scope
  here — the new enum follows the rule.
- Reword the `state` field description as four short
  `<value>: <action>` clauses separated by periods instead of the
  arrow-separated single sentence, so the rendered OpenAPI/Swagger
  output stays readable in ASCII-only tooling.

* feat(realunit): expose /registration as canonical endpoint, deprecate /wallet/status

The endpoint that drives the RealUnit registration UX historically lived
under `/v1/realunit/wallet/status`. That path is misleading: the
resource described is the user's Aktionariat registration, not a
generic wallet status, and clients never ask "what is the wallet's
status?" — they ask "what do I need to do to be RealUnit-registered?".

Following the precedent set by the `/quote/*` vs `/brokerbot/*` rename
(PR #3783), this commit:

- adds `GET /v1/realunit/registration` as the canonical endpoint
- keeps `GET /v1/realunit/wallet/status` as a `deprecated: true` mirror
  that calls the same service method and returns the same DTO instance
- renames `RealUnitWalletStatusDto` to `RealUnitRegistrationInfoDto`
  (wire-neutral: TS class names don't appear on the JSON; field names
  `state` / `userData` / `isRegistered` are unchanged)
- renames `RealUnitService.getAddressWalletStatus` to
  `getRegistrationInfo`; both controller routes delegate to it
- extends CONTRIBUTING.md "API Design" with a sister-section to the
  `/quote/*` rename, summarising the old→new mapping and the
  operational consequence (legacy path kept for backwards compatibility
  on existing clients only)
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