Skip to content

Release: develop -> main - #4442

Merged
TaprootFreak merged 2 commits into
mainfrom
develop
Jul 29, 2026
Merged

Release: develop -> main#4442
TaprootFreak merged 2 commits into
mainfrom
develop

Conversation

@github-actions

Copy link
Copy Markdown

Automatic Release PR

This PR was automatically created after changes were pushed to develop.

Commits: 1 new commit(s)

Checklist

  • Review all changes
  • Verify CI passes
  • Approve and merge when ready for production

* feat(custody): say outright whether the caller owns an account

Clients had to infer ownership from the absence of the owner field, which is
not a contract but a side effect: own accounts are loaded without the owner
relation, foreign ones with it. Add the relation somewhere and the inference
flips silently.

It is not a cosmetic distinction. A client decides on it whether to offer
transacting at all: orders carry no account, so an order placed while looking
at someone else's Safe would be booked against the caller's own. Acting on
another person's behalf does not exist here — initiatedBy is never set.

isOwner states it. Creating an account yields true by definition; the update
route answers it from the account, since a grantee passes the write guard too.

* refactor(custody): ask the account who owns it

The ownership comparison lived in seven places, one of them the controller —
where the contributing guide says business logic does not belong. It is one
method on the entity now, and the controller asks it.

That also makes it testable, which the update route needed: it is the reason
the flag exists, since a grantee passes the write guard without owning
anything, and until now nothing exercised that. The entity spec covers it,
including that an active grant does not make a grantee an owner.

* refactor(custody): finish what the last commit claimed to have finished

Three ownership comparisons were left behind: the multi-account shortcut in
resolveOwnerAccountId, and the two grant guards. A textual search for the
owner field misses the first, which is how the previous commit undercounted
and then overclaimed. None of them behaved wrongly — they computed the same
boolean — but three private copies of one predicate are three chances to
drift.

No raw comparison remains outside the entity.
@github-actions
github-actions Bot requested a review from davidleomay as a code owner July 29, 2026 09:23
* fix(custody): give the Safe balances a stable order

The balances came back in whatever order the database chose — nothing orders
that query. The same holdings could therefore appear in a different order on
every request, with the list visibly reshuffling for no reason a customer
could see. It also made screenshots of the Safe unreproducible.

Largest position first, name as tiebreak. That is both stable and the order
someone reading a portfolio expects.

* fix(custody): rank a non-finite balance last instead of comparing it

NaN makes every difference falsy, so a damaged value fell through to the name
comparison — against every other entry, whatever its worth. The ordering lost
its transitivity and the position depended on the input order again: exactly
the unpredictability this sort was added to remove, only triggered by a broken
figure instead of a missing ORDER BY.

Reproduced before and after: the same four positions, fed forwards and
backwards, now come out identical.

It is ranked rather than thrown for the reason already recorded a few lines
up in the caller — one damaged position must not take the customer's whole
balance response down with it.

Tests cover the negative case too. There is a negative balance row in
production, so that is not hypothetical.

* test(custody): make the ordering test actually catch the bug it names

Its point is that the same holdings come back in the same order however they
went in — but with three values that held for the broken comparator too, so
the assertion would have watched the defect return without a word. Only the
position checks were doing any work.

Four values with a negative among them separate the two: the old comparator
yields different orders forwards and backwards, the new one does not. Verified
by putting the old line back and watching both tests go red.

Also covers a real Infinity, which the ranking treats like NaN, and says in
the comment why: not because infinities break the comparison, but because
nothing legitimate produces one here — the same reading of corrupted data the
interest calculation already applies.
@github-actions
github-actions Bot requested a review from TaprootFreak as a code owner July 29, 2026 09:23
@TaprootFreak
TaprootFreak merged commit b3d718a into main Jul 29, 2026
16 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