Skip to content

Add StrKey-backed Stellar address validation to all funder/recipient DTO fields - #84

Merged
chonilius merged 11 commits into
MergeFi:mainfrom
oluwarantimini:feat/stellar-address-validator-60
Aug 19, 2026
Merged

Add StrKey-backed Stellar address validation to all funder/recipient DTO fields#84
chonilius merged 11 commits into
MergeFi:mainfrom
oluwarantimini:feat/stellar-address-validator-60

Conversation

@oluwarantimini

Copy link
Copy Markdown
Contributor

Summary

Every DTO field representing a Stellar public key — funderAddress on FundEscrowDto/DepositDto/FundBountyDto/FundMilestoneDto, recipientAddress on ReleaseEscrowDto/SplitRecipientDto/AssignRewardDto/ResolveIssueDto — was validated with nothing more than @IsString(). A malformed address (empty, syntactically invalid, wrong length, or checksum-invalid) fell through all the way to SorobanClientService.toScVal's heuristic, which silently downgrades an unparseable "address-shaped" string to a generic string encoding instead of rejecting it — never producing a clean 400 anywhere in the pipeline.

  • Added IsStellarAddress() in src/common/validators/stellar-address.validator.ts, mirroring the existing IsMoneyAmount/IsSupportedEscrowAsset pattern in money.validator.ts. Backed by @stellar/stellar-sdk's own StrKey.isValidEd25519PublicKey (base32 decode + version byte + CRC16 checksum), not a hand-rolled regex.
  • Applied it to every field the issue names explicitly, plus ResolveIssueDto.recipientAddress in the milestones controller — not named in the issue's own Requirements bullet list, but its Precise References section cites the exact line range containing it, and it flows into escrowService.releasePartial the same as every other recipientAddress field this issue targets. Leaving it out would have been an inconsistent, incomplete fix for the same gap.
  • By the time execution reaches SorobanClientService.toScVal, every address it sees has already been validated — its silent try/catch fallback is no longer the only thing standing between a malformed address and a Soroban call.

Test plan

  • npx tsc --noEmit
  • npm run lint (0 errors; pre-existing no-unsafe-argument warnings only, same pattern as the existing escrow-idempotency.e2e-spec.ts)
  • npm run build
  • npm run test — 130 passing; the only failure is escrow-fk-integrity.integration.spec.ts, which requires a real local Postgres (role "postgres" does not exist without one) — unrelated to this change, and CI provides a real Postgres service container so this isn't expected to fail there
  • npm run test:e2e — 41 passing across my 3 new e2e spec files (escrow, maintenance-pool, bounties/milestones) plus existing ones; the only failure is app.e2e-spec.ts, which needs real GITHUB_CLIENT_ID/GITHUB_CLIENT_SECRET to bootstrap the full AppModule — unrelated to this change (this repo's own CI conditionally skips e2e entirely when those secrets aren't configured)
  • New e2e tests (37 total, across test/stellar-address-validation-{escrow,maintenance-pool,bounties-milestones}.e2e-spec.ts) feed an empty string, a syntactically-invalid string, a wrong-length string, and a checksum-invalid-but-right-length string directly to every affected endpoint through a real Nest app with ValidationPipe applied (mirroring main.ts's config — no existing e2e spec in this repo applies it, so it doesn't come for free), asserting 400 and that the underlying service is never called; each suite also confirms a freshly-generated valid Keypair address still passes through, so this isn't just testing the rejection path

Closes #60

…ergeFi#60)

Mirrors the existing IsMoneyAmount/IsSupportedEscrowAsset pattern in
money.validator.ts. Backed by @stellar/stellar-sdk's own
StrKey.isValidEd25519PublicKey rather than a hand-rolled regex, so a
syntactically-plausible-but-checksum-invalid 56-character string is
rejected the same as an obviously malformed one.

Not yet applied to any DTO.
Not explicitly named in the issue's own Requirements list, but its
Precise References section cites milestones.controller.ts:8-20, which
covers this class too, and recipientAddress here flows into
escrowService.releasePartial the same as every other recipientAddress
field this issue targets -- leaving it out would be an inconsistent,
incomplete fix for the same underlying gap.
…MergeFi#60)

Feeds an empty string, a syntactically-invalid string, a wrong-length
string, and a checksum-invalid-but-right-length string directly to
POST /escrow/fund, /escrow/:id/release, and /escrow/:id/split-release
through a real Nest app with ValidationPipe applied (mirroring
main.ts's config, since no existing e2e spec in this repo applies it),
asserting a 400 and that EscrowService is never called. A valid,
freshly-generated Keypair address is confirmed to still pass through
to the (mocked) service, so this isn't just testing the rejection path.
…nance-pool (MergeFi#60)

Same coverage shape as the escrow e2e tests, applied to
POST /maintenance-pools/:id/deposit (funderAddress) and
POST /maintenance-pools/:id/assign-reward (recipientAddress).
…es/milestones (MergeFi#60)

Same coverage shape, applied to POST /bounties/:id/fund
(funderAddress), POST /milestones/:id/fund (funderAddress), and
POST /milestones/:id/issues/:issueId/resolve (recipientAddress —
the ResolveIssueDto field this issue's Requirements list doesn't
name explicitly but its own Precise References section covers).
@vercel

vercel Bot commented Aug 19, 2026

Copy link
Copy Markdown

@oluwarantimini is attempting to deploy a commit to the chonilius' projects Team on Vercel.

A member of the Team first needs to authorize it.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

2 participants