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.
NoteMath
Section titled “NoteMath”// 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.
VoucherMath
Section titled “VoucherMath”| 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 Issuerexpect(held.tx.hopsLeft, 1); // transfers left before it must be redeemedexpect(held.tx.terms.expiry, 900000); // when the Issuer may take it backexpect(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);
