Note NFT
The Note NFT (TSL1_LT_NOTE) is a fuelled token whose terms say, for each way it can end, how much of its satoshis the holder gets. The Issuer (payTo) gets the rest. It turns out that one Script covers a whole family of instruments: a voucher, a time-locked claim, a callable claim, a deposit, an insured voucher.
A note ends in one of three ways:
| Ending | Who sends it | When | Holder’s amount |
|---|---|---|---|
| exit | the holder | any time | exit |
| mature exit | the holder | from block maturity |
mature |
| call | the issuer (the payTo key) |
from block callAt |
call |
An amount of 0 gives the holder nothing on that path, and NoteTerms.everything gives the holder everything. A height of NoteTerms.never is never reached. Neither side can take the other’s share, because the Script fixes the Outputs: the holder’s at least its amount, and the Issuer’s at least the rest less feeCap. There is no burn.
A deposit
Section titled “A deposit”The examples on this page follow a deposit. The holder can leave at any time with 30,000 satoshis, or with everything from block 900,000.
// A deposit: the holder may leave at any time with 30,000 sats, or with// everything from block 900,000. The Issuer keeps the difference on an// early exit and can never call it.final terms = NoteTerms.deposit(payTo: pkhOf(issuerKey), feeCap: 500, at: 900000, early: 30000);final iss = b.issue(first: coinOf(issuerKey, 50300), issuerPkh: issuer.lt.pkh, owner: pkhOf(issuerKey), terms: terms, fee: 300);iss.x = issuer.lt.attest(iss.txid);final note = b.transfer(from: iss, ownerKey: issuerKey.lt, to: pkhOf(alice), fee: 100);expect(note.value, 49900);Showing what each ending pays
Section titled “Showing what each ending pays”The fuel is not checked against the amounts, and each transfer pays its fee from it. So before a person accepts or pays for a note, we show them what they get now, at maturity, and if called, at the note’s current value. NoteMath computes the builder’s own numbers:
// 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 callableInstrument math lists all of NoteMath.
The holder ends the note before maturity. With an amount somewhere between nothing and everything, the ending has two Outputs: Output 0 to the Issuer, and Output 1 to the holder.
final exit = b.exit(from: note, ownerKey: alice.lt, fee: 300);spends(exit, 0, note.lock(b.code), note.value);expect(paid(exit), [('issuer', 19600), ('holder', 30000)]);Mature exit
Section titled “Mature exit”From maturity onwards, the holder’s ending pays the mature amount. The Transaction carries the maturity height as its nLockTime, with a non-final sequence, so it is only mined from that height:
// From maturity onwards. nLockTime is the maturity height, so it is mined// only from then.final mature = b.matureExit(from: note, ownerKey: alice.lt, fee: 300);spends(mature, 0, note.lock(b.code), note.value);expect(paid(mature), [('holder', 49600)]);expect(Transaction.fromHex(hex.encode(mature)).nLockTime, 900000);A callable note lets the Issuer end it from callAt, paying the holder the call amount. callCost is what the holder loses if the note is called instead of being left to mature:
final terms = NoteTerms.callable(payTo: pkhOf(issuerKey), feeCap: 500, at: 900000, callAt: 880000, floor: 20000);final note = heldByAlice(terms);expect(terms.callableBeforeMaturity, isTrue);expect(terms.callCost(value: note.value, fee: 300), note.value - 300 - 20000); // what a call costs Alice
final call = b.call(from: note, issuerKey: issuerKey.lt, fee: 300); // signed by the payTo keyspends(call, 0, note.lock(b.code), note.value);expect(paid(call), [('issuer', note.value - 300 - 20000), ('holder', 20000)]);expect(Transaction.fromHex(hex.encode(call)).nLockTime, 880000);The presets
Section titled “The presets”NoteTerms has a constructor per instrument:
| Preset | exit | mature (at) | call (callAt) | The holder is exposed to |
|---|---|---|---|---|
voucher |
0 | never | 0 from expiry |
the issuer’s word for everything |
zeroCoupon |
0 | everything from at |
never | time only: it is fully collateralised |
callable |
0 | everything from at |
floor from callAt |
being ended early at the floor |
deposit |
early |
everything from at |
never | its own impatience |
insured |
0 | never | floor from callAt |
being taken back, never for less than the floor |
final presets = { 'voucher': NoteTerms.voucher(payTo: payTo, feeCap: 500, expiry: 900000), 'zeroCoupon': NoteTerms.zeroCoupon(payTo: payTo, feeCap: 500, at: 900000), 'callable': NoteTerms.callable(payTo: payTo, feeCap: 500, at: 900000, callAt: 880000, floor: 20000), 'deposit': NoteTerms.deposit(payTo: payTo, feeCap: 500, at: 900000, early: 30000), 'insured': NoteTerms.insured(payTo: payTo, feeCap: 500, callAt: 880000, floor: 20000),};// What the holder gets at maturity, from a note of 50,000 sats:expect({for (final e in presets.entries) e.key: e.value.holderAtMaturity(value: 50000, fee: 300)}, { 'voucher': 0, 'zeroCoupon': 49700, 'callable': 49700, 'deposit': 49700, 'insured': 0,});A zero-coupon note is a time-locked claim, priced at a discount for time. Nobody owes anything. For anything else, NoteTerms(...) takes the eight fields directly, and maxHops (default 0, i.e. no limit) caps the transfers.
Reissuing, and selling
Section titled “Reissuing, and selling”A reissue from an ending that paid payTo works as it does for the voucher, and its link carries the predecessor’s 61 bytes of terms. An ending that paid only the holder leaves nothing for a successor. The sale version is FnftBuilder.saleableNote(). A buyer gets the same terms, so price a note at what its amounts are worth at their heights.
In a wallet
Section titled “In a wallet”LtNotePlugin takes the terms as params, and its metadata gives holderOnExit, holderAtMaturity, holderIfCalled and callableBeforeMaturity at the current fuel. See Plugin reference.
The code is 3,479 bytes, and the header 215. A two-Output exit is about 3,890 bytes.

