research

Matching Capital with Transparent, Bounded Mathematics

Naseeg's funding model makes the match pool, prize pool, caps, and eligible support explicit.

Kiyan ResearchAug 3, 20265 min read
Editorial artwork for Matching Capital with Transparent, Bounded Mathematics

Matching can make small acts of support more meaningful, but only if the rules are understandable before money moves. Naseeg makes the structure of a Fund explicit.

The split

Each Fund divides its pool into a 90% match pool and a 10% prize pool. A project's unlocked match is bounded by the smaller of two values: the matching amount derived from settled project support and the project's cap within that Fund.

unlocked match = min(
  floor(paid project sales × match multiple),
  project cap
)

The default project cap is an equal share of the match pool, unless a Fund configures an explicit cap. The prize pool is reserved for the top direct-sales project, with ties split rather than arbitrarily broken.

Support does not choose a Fund

A supporter chooses a project. If that project has been curated by several Funds in the open round, the settled support can unlock matching independently in each eligible Fund. This reflects the fact that a single project can be relevant to several curatorial contexts without asking a supporter to understand every Fund's internal configuration.

Only paid and settled support counts. Refunds and chargebacks are excluded. The model is deliberately more specific than a claim that every payment simply "gets matched."

The current funding function is small enough to inspect

Naseeg's funding math lives in a focused function rather than being scattered across a checkout page, a dashboard, and an administrator spreadsheet. It receives a Fund's total pool, match multiple, count of curated projects, direct paid sales for one project, and an optional explicit cap. It returns the match pool, prize pool, project cap, and unlocked match.

export function calculateFunding(input: FundingInput): FundingAllocation {
  const matchPoolCents = Math.floor(input.totalPoolCents * 0.9);
  const prizePoolCents = input.totalPoolCents - matchPoolCents;
  const safeProjectCount = Math.max(1, input.curatedProjectCount);
  const projectCapCents = input.explicitProjectCapCents ??
    Math.floor(matchPoolCents / safeProjectCount);
  const multiplied = Math.floor(
    (Math.max(0, input.directSalesCents) * input.matchMultipleBps) / 10_000,
  );

  return {
    matchPoolCents,
    prizePoolCents,
    projectCapCents,
    unlockedMatchCents: Math.min(multiplied, projectCapCents),
  };
}

This is intentionally not mysterious. A person can inspect the split, see how the cap is derived, and understand that direct sales do not unlock unlimited matching.

01 / pool

90 / 10 split

The current funding function allocates match and prize pools separately.

02 / cap

Bounded unlock

Direct sales are multiplied, then capped per curated project.

03 / record

Allocation rows

Match and prize records are written per Fund, project, and round.

Each Fund calculates its own match and prize outputs from a bounded pool, curated-project count, direct paid sales, and an explicit cap.

The pool is split before individual allocation

The current implementation allocates 90% of a Fund's total pool to matching and reserves the remaining 10% for prizes. The equal-share project cap is the default; a Fund can supply an explicit cap. The match unlocked for a project is the smaller of the multiplied direct paid sales and that cap.

match pool  = floor(total pool × 0.90)
prize pool  = total pool − match pool
project cap = explicit cap OR floor(match pool ÷ curated project count)
unlocked    = min(floor(direct paid sales × match multiple), project cap)

There are two practical safeguards in the code. Math.max(1, curatedProjectCount) prevents a divide-by-zero shape from creating an undefined cap. Math.max(0, directSalesCents) prevents a malformed negative input from becoming a negative allocation. These are not substitutes for validation elsewhere, but they make the financial function's behavior stable at its own boundary.

What counts as direct paid support

Allocations derive direct sales from contribution rows for the same project and round. The current backend includes only paid, signal_pending, signaled, badge_claimed, and settled statuses. Refunds and chargebacks are excluded. That means a Stripe Checkout session being created, a browser visiting a success page, or an optional signal being sent before settlement does not by itself unlock matching.

const directSalesCents = contributions
  .filter((item) =>
    item.roundId === roundId && countedStatuses.includes(item.status),
  )
  .reduce((sum, item) => sum + item.grossAmountCents, 0);

The status set is part of the product's truthfulness. Payment state is not inferred from intention; it comes from the authoritative Stripe/Convex path described in the contribution workflow.

Calculation is per Fund and per round

The recalculateFund mutation requires a Fund role, checks that the caller owns the Fund or has administrator authority, requires the Fund's round to be open, reads that Fund's submissions, and filters them to curated. It then calculates and upserts match rows for each eligible project and creates prize rows from the same bounded set.

const eligible = submissions.filter((item) => item.status === "curated");

for (const submission of eligible) {
  const directSalesCents = await directSalesForProject(
    ctx,
    submission.projectId,
    fund.roundId,
  );
  const allocation = calculateFunding({
    totalPoolCents: fund.totalPoolCents,
    matchMultipleBps: fund.matchMultipleBps,
    curatedProjectCount: eligible.length,
    directSalesCents,
    explicitProjectCapCents: fund.explicitProjectCapCents,
  });
  await upsertMatch(ctx, { ...allocation, fundId: fund._id, roundId: fund.roundId });
}

One paid Artifact sale can be relevant to more than one Fund if each Fund curated that project in the open round. That is an explicit Naseeg rule: the supporter chooses the project, while each Fund independently applies its own pool and caps. The supporter is not asked to select or understand every Fund configuration at checkout.

The prize rule is also bounded

The current prize calculation uses the reserved prize pool for the project or projects with the highest direct sales. If there is a tie, the prize pool is divided across the tied winners and the remainder is distributed deterministically. If there are no candidates or no sales, the function produces no prize rows.

This behavior is more accountable than a vague “winner gets a bonus” promise. The relevant inputs and the tie behavior are visible in code and can be tested against a small set of contribution fixtures.

What the math does not claim

The function does not custody fiat, calculate a live exchange rate, guarantee a project payout, or decide whether a contribution was settled. It turns already-authoritative round, curation, Fund, and payment inputs into bounded allocation records. Payouts remain a separate operational step in the current product direction.

Naseeg remains Coming Soon. The formula and code examples describe the current implementation, not a public commitment that a specific Fund, round, cap, or payout workflow is live today.