A marketplace fee split
One sale, several sellers: the fee off the top, shares in basis points, no unclaimed remainder.
Source src/pricing.ts#L52splitLegssrc/operations/spend.ts#L80spend
A collab sells: an artist and an animator split the price, and the platform keeps its cut. That is
one spend with several recipients — never several
transfers to reconcile after the fact.
The split rule is owned by the pricing policy and runs inside the sale’s one balanced transaction:
- The platform’s fee comes off the top.
- Each recipient takes their
shareBpsof the net — basis points, and the shares must sum to exactly10_000, so no share of the price can go unclaimed. - Whatever rounding leaves over goes to the house, not into thin air: the books stay balanced to the minor unit.
import {
SYSTEM,
createEconomy,
credits,
earned,
encodeAmount,
spend,
systemActor,
topUp,
userActor,
} from '@pwngh/economy-lab';
import type { SnippetReport } from './context.ts';
// One spend, two sellers: the platform fee comes off the top, each recipient takes their
// basis points of the net, and any rounding leftover stays with the house — the shares must
// sum to exactly 10,000 so no remainder goes unclaimed.
export async function run(): Promise<SnippetReport> {
const economy = await createEconomy();
await economy.submit(
topUp({
idempotencyKey: 'idem_fund',
actor: systemActor('payments'),
userId: 'usr_b',
amount: credits(1_000),
source: 'card',
}),
);
await economy.submit(
spend({
idempotencyKey: 'idem_sale',
actor: userActor('usr_b'),
orderId: 'ord_f1',
buyerId: 'usr_b',
sku: 'Collab Piece',
price: credits(1_000),
recipients: [
{ sellerId: 'usr_artist', shareBps: 6_000 },
{ sellerId: 'usr_animator', shareBps: 4_000 },
],
}),
);
const [artist, animator, house] = await Promise.all([
economy.read.balance(earned('usr_artist')),
economy.read.balance(earned('usr_animator')),
economy.read.balance(SYSTEM.REVENUE),
]);
await economy.close();
return {
lines: [
'a 1,000-credit sale, split 60/40 behind the default fee',
`artist (6,000 bps): ${encodeAmount(artist)}`,
`animator (4,000 bps): ${encodeAmount(animator)}`,
`house (fee + rounding leftover): ${encodeAmount(house)}`,
],
consolePath: '/market',
};
}Each seller’s cut lands in their earned balance,
which is the balance requestPayout later pays
out as real money. A refund of the order unwinds every
share — both sellers and the fee — in one opposite posting.