fix(node): stop paying the merchant's royalty to our own treasury - #51
Merged
Conversation
When a merchant has not named an ip_creator, the Node client resolved the
royalty slot by reading B2BSplitter.treasury() and passing that. The comment
justified it: address(0) "would skip the transfer and permanently strand the
1bp inside B2BSplitter — the contract has no sweep function."
The premise is false. B2BSplitter._split(), contracts/B2BSplitter.sol:238-244:
if (_ipCreator != address(0)) { ipAmt = _total * ipCreatorBps / D; }
// else: ipAmt stays 0 and is absorbed into merchantAmt below
merchantAmt = _total - treasuryAmt - ipAmt;
Nothing is stranded — with address(0) the royalty folds into the merchant's
leg, which is where it belongs when nobody named a creator. So the fallback
was not rescuing a basis point, it was taking one off every such payment and
paying it to us. That is the on-chain source of the "0.01% creator fee" that
QA raised.
Python was corrected in #42 on 2026-08-27. Node was not. Same defect, same
week, twin file left alone — the third instance of that pattern this month
(gate/src/scope.ts against backend/aifp/scope.js was the second).
A unit test on either client alone passes throughout, so the guard added here
reads both files and fails when they disagree. Verified failing on the defect
before it was accepted. The dead splitterTreasury() helper and its cache are
removed with it; leaving the machinery in place is how this comes back.
The Solana branch keeps routing through treasury in both clients. That one is
matched and deliberate — the Solana bridges do not surface an ip_creator at
all — and the fourth assertion pins it so nobody "fixes" it by symmetry.
Full Node suite: 189/189.
daochild
previously approved these changes
Sep 2, 2026
The published-files gate is right: unifiedAgent.ts ships to users, so two different builds must not both call themselves rc.3. rc.6 rather than rc.4 on purpose. rc.4 is claimed by #46 and rc.5 by #49, both still open. Taking the next number above every open claim means this can merge in any order without making anyone renumber — and a royalty fix should not wait on a release-numbering negotiation.
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.
Ports the #42 royalty fix from the Python client to the Node client, five weeks late.
The defect
When a merchant has not named an
ip_creator, the Node client filled the royalty slot by readingB2BSplitter.treasury(). The comment argued for it —address(0)would supposedly strand the 1bp in the contract, which has no sweep function.That is not what the contract does.
B2BSplitter._split(),contracts/B2BSplitter.sol:238-244:With
address(0)the royalty is folded into the merchant's leg. Nothing is stranded. So the fallback took 1bp off every payment whose merchant had not set a creator and paid it to our treasury. This is the on-chain source of the 0.01% creator fee QA raised.Why a test that reads two files
Python was fixed in #42 on 2026-08-27; Node was not touched. A unit test on either client alone passes the whole time — the clients only disagree with each other.
node/tests/royalty-parity-with-python.test.tstherefore reads both sources and asserts they resolve the slot the same way. Verified failing on the defect (2 of 4 assertions) before being accepted.This is the third instance of the same shape this month — one implementation corrected, its twin left alone. The second was
gate/src/scope.tsagainstbackend/aifp/scope.js(#50).Also in this PR
splitterTreasury()helper and its cache are removed. Leaving the machinery in place is how the defect comes back.ip_creator— and the fourth assertion pins it so nobody "fixes" it for symmetry.Checks
tsc --noEmitclean