DOCUMENTATION

How Ceiling bills.

Ceiling commits pricing and acceptance rules before output exists, meters only the output that satisfies them, and publishes everything needed to recompute the bill independently.

NETWORKMonad Testnet · 10143
REGISTRY0xbd06bb4d…2e7d61 ↗
SOURCEgithub.com/FUSIONIFY-ID/Ceiling ↗

Overview

Normal usage billing asks the buyer to trust how the vendor defines a unit, counts usage, treats invalid output, and calculates the final invoice. The buyer receives one number and has no way to check it.

Ceiling precommits five things before processing begins: the unit definition, the deterministic validator, the failure policy, the unit price, and the maximum number of units. After processing, it publishes the output and billing metadata needed to rerun the same validator and recompute the charge.

The core claim

A Ceiling invoice is derived from a published session artifact rather than accepted as an opaque vendor total.

Mechanism

  1. .001 Commit

    Canonicalize the policy, hash it with Keccak-256, and record the hash on-chain before any output exists.

  2. .002 Authorize

    The buyer authorizes no more than maxUnits × unitPrice. In the reference session that is 25 × $0.002 = $0.050. No valid execution can bill more.

  3. .003 Meter

    Records are validated in order. Only accepted units are counted. The first invalid record ends the billable stream.

  4. .004 Settle

    The actual bill is computed, the outcome is recorded, and the session artifact is published for recomputation.

Policy and validator

The policy is versioned and serialized canonically — object keys are sorted recursively so that insertion order cannot change the hash. Including maxUnits in the policy prevents the billing ceiling from being changed after the policy is accepted.

{
  "failurePolicy": "cut-on-first-invalid",
  "maxUnits": 25,
  "schema": { "name": "string", "score": "finite-number" },
  "unitDefinition": "one canonical JSON record",
  "unitPriceAtomic": "2000",
  "validator": "canonical-json-record-v1",
  "version": 1
}

The reference validator accepts a record only when name is a non-empty string and score is a finite number. Every rejection carries a machine-readable reason: record_not_object, name_not_string, name_empty, or score_not_finite.

Your validator must be deterministic

Same input, same result, on any machine, at any time. No model calls, no randomness, no clocks, no network. If the buyer cannot reproduce it, the whole guarantee collapses.

Metering and failure

Under cut-on-first-invalid, the first rejected record is not billed and every record after it falls outside the billable stream. The meter also slices the produced records to maxUnits before iterating, so a producer cannot exceed the committed ceiling.

record 01–17   ACCEPT
record 18      REJECT — score_not_finite
STREAM CUT
record 19–25   not charged

Money arithmetic

Monad Testnet USDC has six decimals. Every amount is an integer in atomic units held as bigint. Floating point is never used for billing; decimal strings are display formatting only.

QuantityFormulaReference session
CeilingmaxUnits × unitPriceAtomic25 × 2,000 = 50,000
ActualacceptedUnits × unitPriceAtomic17 × 2,000 = 34,000
Unusedceiling − actual50,000 − 34,000 = 16,000

Recomputation

An independent party needs the published artifact, then:

  1. Canonicalize the policy and hash it; compare to policyHash.
  2. Canonicalize the full output and hash it; compare to outputHash.
  3. Rerun the validator in order, stopping at the first invalid record.
  4. Count accepted units before the cut.
  5. Recompute actual and unused amounts with integer arithmetic.
  6. Compare every result against the published values.

recomputeSession returns five booleans: policyHashMatches, outputHashMatches, acceptedUnitsMatches, actualAmountMatches, and billingMatches.

Why the contract alone is not enough

The registry can only check that settledAmount == acceptedUnits × unitPrice. A seller who inflates the unit count and keeps the arithmetic consistent still passes that check. Only rerunning the validator over the published output catches it — which is why the raw records are published, not just the totals.

On-chain registry

CeilingRegistry is a registry, not an escrow and not an output validator. It never receives raw output, never custodies payment, and never initiates a refund. It enforces:

  • each sessionId can be committed once;
  • a session must exist before an outcome is recorded;
  • only the session creator can record its outcome;
  • an outcome can be recorded once;
  • acceptedUnits ≤ maxUnits;
  • settledAmount == acceptedUnits × unitPrice.
POLICY HASH
0x2649acf4fe4f9f179b61b2b717d3b58299298de37eefcc8118aaafc328c3f123
OUTPUT HASH
0xc63b3f62cfe8ee0fced6f8488804b36825f42f3f251de2196bea7e8903e1f986

Payment paths

Path A — x402 upto

The intended native path. The buyer signs a maximum authorization, the server meters output, and the facilitator settles an amount less than or equal to that maximum. The unused portion never moves.

Path B — Ceiling Boomerang

The fallback. Charge the full ceiling with x402 exact, run the same deterministic pipeline, then refund the difference. It preserves the product economics but is not protocol-equivalent to upto: the full ceiling reaches the receiver first, and the refund is a separate transaction with its own funding and failure assumptions.

Current payment status

Integration READY. Facilitator preflight PASS. Live settlement WAITING FOR TEST USDC. No live USDC payment or refund is claimed, and no placeholder transaction is shown in its place.

Running locally

Node.js 20 or newer.

npm install
npm run check          # typecheck
npm run test:core      # deterministic self-check
npm run contract:compile
npm run demo:core      # full session in the terminal

The website and interactive session:

cd web
npm install
npm run dev

Payment commands need a funded test wallet and a local .env.local. Never commit private keys, and use dedicated burner wallets only.

Limitations

  • The validator is deterministic and domain-specific. It proves compliance with declared field rules, not semantic truth or output quality.
  • The seeded producer exists to make the demonstration reproducible. It is not a real model call.
  • The registry does not inspect raw output.
  • External recomputation depends on the published artifact remaining available.
  • Canonical serialization follows JSON number formatting, so a non-JavaScript reimplementation must match those rules to reproduce outputHash.
  • The exact-and-refund fallback has two on-chain operations. A failed refund leaves the ceiling charge in place.