A promo with an expiry
Grant marketing credit that spends first and claws itself back when the deadline passes.
Source src/operations/promo.ts#L30grantPromosrc/worker/promos.ts#L49sweepExpiredPromos
You want to hand out launch credit that costs the house nothing if it goes unused. That is what a
promo grant is: marketing money with a deadline, kept in its own
promo balance so it never masquerades as money
the user paid.
Two properties do all the work:
- Promo spends first. Any
spenddraws the buyer’spromobalance before touchingspendable, so granted credit is always the first to go. - Expiry needs no code. The background worker’s
promossweep finds grants whoseexpiresAthas passed and posts back whatever remains unspent. You set the deadline at grant time and never schedule anything.
import {
createEconomy,
credits,
encodeAmount,
grantPromo,
promo,
spend,
spendable,
systemActor,
topUp,
userActor,
} from '@pwngh/economy-lab';
import type { SnippetReport } from './context.ts';
// A promo grant is marketing money with a deadline: it lands in the promo balance, spends
// first when the user buys, and the worker's promos sweep claws back whatever is left once
// expiresAt passes. The grant and the promo-first draw are the recipe; the expiry needs no code.
export async function run(): Promise<SnippetReport> {
const economy = await createEconomy();
await economy.submit(
topUp({
idempotencyKey: 'idem_fund',
actor: systemActor('payments'),
userId: 'usr_p',
amount: credits(100),
source: 'card',
}),
);
await economy.submit(
grantPromo({
idempotencyKey: 'promo_launch_usr_p',
actor: systemActor('marketing'),
userId: 'usr_p',
amount: credits(200),
expiresAt: Date.now() + 30 * 86_400_000, // the claw-back deadline
}),
);
await economy.submit(
spend({
idempotencyKey: 'idem_buy',
actor: userActor('usr_p'),
orderId: 'ord_p1',
buyerId: 'usr_p',
sku: 'Starter Pack',
price: credits(150),
recipients: [{ sellerId: 'usr_s', shareBps: 10_000 }],
}),
);
const [promoLeft, cash] = await Promise.all([
economy.read.balance(promo('usr_p')),
economy.read.balance(spendable('usr_p')),
]);
await economy.close();
return {
lines: [
'granted 200 promo (expires in 30 days) on top of 100 topped up',
'a 150 spend drew the promo balance first',
`promo left: ${encodeAmount(promoLeft)} · spendable untouched: ${encodeAmount(cash)}`,
],
consolePath: '/market',
};
}The grant is restricted to system and operator
actors — a user cannot grant themselves credit — and expiresAt must be a future timestamp within
five years, checked at submit.
Since a promo-funded purchase is not the buyer’s money, the seller is still paid real earnings: the promo part of a sale routes from house revenue, which is exactly why an expired, unspent grant can be reclaimed without touching anyone else’s balance.