Skip to content

Instrument math

A Note or a Voucher is worth what its endings pay, at the heights where they can happen. NoteMath (on NoteTerms) and VoucherMath (on VoucherTerms) are extensions that work out those numbers from the terms, a value and a height. They give exactly what the builder’s Transactions pay, so a wallet can show them before a person accepts, buys or prices one.

// Before Alice accepts the note, we show her what each ending pays at the
// current height and value. NoteMath gives the builder's own numbers.
final now = terms.endingsAt(height: 899000, value: note.value, fee: 300);
expect([for (final e in now) (e.name, e.available, e.holder, e.issuer)], [
('exit', true, 30000, 19600),
('mature', false, 49600, 0),
('call', false, 0, 49600),
]);
expect(terms.blocksToMaturity(899000), 1000);
expect(terms.blocksToCall(899000), isNull); // never callable
Member Gives
endingsAt(height:, value:, fee:) The three endings (exit, mature, call) as NoteEndings: whether each is open at that height, who may send it, and what the holder and the issuer get
holderAt(height:, value:, fee:) What the holder can take by its own hand at that height: the mature amount from maturity, the exit amount before
holderAtMaturity(value:, fee:) What a mature exit pays the holder
holderIfCalled(value:, fee:) What a call pays the holder
blocksToMaturity(height), blocksToCall(height) Blocks until each, or null when it never comes
yieldPerBlock(price:, value:, height:, fee:) The simple yield per block of paying price now and holding to maturity; null when it never matures or already has
callCost(value:, fee:) What the holder loses if called instead of left to mature
callableBeforeMaturity Whether the issuer may call before the note matures

NoteTerms.split(amount, value, fee) is the rule underneath all of these. It gives the Issuer’s and the holder’s share of an ending with that holder amount.

We pass in the note’s current value (holding.value) and the fee we expect the ending to pay. The amounts live in the terms, but the value falls with each transfer’s fee. A note whose value has fallen below an amount pays the holder everything that is left on that ending.

Member Gives
blocksToExpiry(height) Blocks until the issuer may force the redemption; 0 or less once it may
forcedRedemptionHeight The height from which it may (the expiry)
redeemed(value:, fee:) What a holder’s redemption pays redeemTo; throws when the fee is over feeCap
final held = await FnftReceive.check(b: b, raw: t1.raw, ownerPkh: pkhOf(alice), anchorTx: issuer.anchor);
// What to show before a person accepts a voucher:
expect(held.value, 29300); // its fuel, which belongs to the Issuer
expect(held.tx.hopsLeft, 1); // transfers left before it must be redeemed
expect(held.tx.terms.expiry, 900000); // when the Issuer may take it back
expect(held.tx.terms.blocksToExpiry(899000), 1000); // VoucherMath
// One Output, to redeemTo. The fee comes from the fuel, up to feeCap.
final r = b.redeem(from: t1, ownerKey: alice.lt, fee: 400);
spends(r, 0, t1.lock(b.code), t1.value);
final paid = Transaction.fromHex(hex.encode(r)).outputs.single;
expect(paid.satoshis.toInt(), terms.redeemed(value: t1.value, fee: 400));
expect(hex.encode(paid.script.buffer), hex.encode(p2pkhOf(issuerKey)));
// A fee above the cap needs a coin of the holder's to pay it.
expect(() => b.redeem(from: t1, ownerKey: alice.lt, fee: 1500), throwsArgumentError);
final funded = b.redeem(from: t1, ownerKey: alice.lt, fund: coinOf(funder, 3000), fee: 1500);
spends(funded, 0, t1.lock(b.code), t1.value);