Plugin reference
This page gathers every plugin’s actions in one place. Params are a Map<String, dynamic> in the PluginOutputSpec, with 'action' naming the action. Key hashes, txids (in display order), Attestations and raw Transactions are hex strings, while heights, prices and amounts are ints. “Coins” is how many wallet coins the action takes. Where that is 0, send amount: 0 (see How much to pay).
The examples call each plugin with a stand-in wallet (
test/support/standin_wallet.dart). In an app, the same params go in a payment.
PushDrop: PushDropPlugin
Section titled “PushDrop: PushDropPlugin”Id and script type pushdrop. We build the spec with PushDropPlugin.outputSpec(outputs:, spends:, label:) rather than by hand. Its action is create when it makes tokens, and spend when it only spends them. It takes one or more coins for the fee, by the spec’s amount. See PushDrop.
Metadata: ownerAddress (the locking key’s Address), lockingPublicKey, fields (hex).
Covenant NFT: LtNftPlugin
Section titled “Covenant NFT: LtNftPlugin”Id tsl1_lt_nft, script type lt_nft. The token is Output 1.
| Action | Params | Coins | Result |
|---|---|---|---|
issue |
issuer (the Rabin key hash); optional payload |
2 | The issuance, owned by the first coin’s key. With payload, the primary transaction is the payload transaction and the paired one the issuance. |
transfer |
token, to; attestation when token is the issuance |
1 | A transfer |
end |
token, irreversible: true |
1 | The token spent on the cheap path; everything to its owner |
reissue |
token, issuer (must be the token’s issuer) |
2 | The end (primary) and the successor issued on its output 0 (paired) |
swapBid, swapAccept, swapAsk, swapTake |
see the swap actions | 0 |
Metadata: ownerAddress, ownerPKH, issuer, tokenId (display order; null on an issuance, whose txid is the id).
final plugin = LtNftPlugin(network: NetworkType.TEST);
// issue takes two funding coins, and the token is owned by the first coin's keyfinal issued = await plugin.buildTransaction(issuerWallet.request({'action': 'issue', 'issuer': issuerHex()}, coins: 2));final iss = issued.primaryTx;issuerWallet.record(iss);expect(plugin.extractMetadata(iss.outputs[1].script)!['ownerAddress'], addressOf(issuerKey));// The first transfer carries the Issuer's Attestation (hex), which// the plugin checks before it builds anything.final t1 = (await plugin.buildTransaction(issuerWallet.request({ 'action': 'transfer', 'token': iss.id, // display-order txid 'to': pkhHex(alice), // key hash, hex 'attestation': attestationFor(issuer.lt, iss),}))) .primaryTx;end and reissue stop a token for good. The irreversible: true and issuer params are your app’s statement of intent, so give them only after a person or a policy has approved the operation.
// end destroys the token, so the plugin refuses it without the statement.expect(() => plugin.buildTransaction(aliceWallet.request({'action': 'end', 'token': t1.id})), throwsArgumentError);final end = (await plugin.buildTransaction(aliceWallet.request({'action': 'end', 'token': t1.id, 'irreversible': true}))).primaryTx;expect(plugin.validateTransactionStructure(end, 'end'), isTrue);Receive check: LtNftReceive.check(plugin:, beef:, ownerAddress:, anchorTx:, links:, payloadTx:), which returns an LtHolding. Helpers: plugin.anchorTxid(transferRaw), plugin.link(endRaw, redeemedRaw), plugin.builder.
Listed NFT: LtLnftPlugin
Section titled “Listed NFT: LtLnftPlugin”Id tsl1_lt_lnft, script type lt_lnft. It has every LtNftPlugin action, and in addition:
| Action | Who | Params | Coins | Result |
|---|---|---|---|---|
list |
the owner | token, price (at least 1) |
1 | A transfer to the owner’s own key, listed at price |
withdraw |
the owner | token |
1 | A transfer to the owner’s own key, unlisted |
buy |
anyone | listingTx (raw), coin (txid:vout, worth exactly price plus fee), maxPrice, anchorTx (raw); optional links, newOwner |
0 | The purchase |
Metadata adds price (0 means not listed). buy runs the payee check on the listing first. It refuses a token that is not listed, is listed above maxPrice, or is held by this wallet, as well as a coin that leaves no fee. For received tokens, use LtNftReceive.check with this plugin. See Listed NFT.
Fuelled NFT: LtFnftPlugin
Section titled “Fuelled NFT: LtFnftPlugin”Id tsl1_lt_fnft, script type lt_fnft. The token is Output 0.
| Action | Params | Coins | Result |
|---|---|---|---|
issue |
issuer |
1 or 2, by the amount | The issuance; the coins less the fee are the fuel |
transfer |
token, to; attestation for an issuance |
0 | A transfer paid from the fuel |
topup |
token, to; attestation for an issuance |
1 | A transfer that sweeps the coin into the fuel. To refuel without moving it, give the owner’s own key hash as to. |
end |
token, irreversible: true |
0 | The burn: the fuel less the fee to the owner |
reissue |
token, issuer |
0 | The end and the successor, fuelled by the ended token |
Metadata adds fuel. Receive check: LtFnftReceive.check, which returns an FnftHolding (value is the fuel). The errors you are most likely to meet are the token would hold <n> sats: the fuel does not cover the fee (top up, reissue or burn it), topup needs 1 funding coin(s), got 0 and an issuance takes one or two coins of fuel. See Fuelled NFT.
Voucher NFT: LtVnftPlugin
Section titled “Voucher NFT: LtVnftPlugin”Id tsl1_lt_vnft, script type lt_vnft. No end.
| Action | Who | Params | Coins | Result |
|---|---|---|---|---|
issue |
the issuer | issuer, maxHops, expiry, feeCap; optional redeemTo (default the first coin’s key) |
1 or 2 | The issuance |
transfer |
the owner | token, to; attestation for an issuance |
0 | A transfer; refused when no hops are left |
topup |
the owner | as transfer |
1 | A transfer that adds the coin to the fuel; it uses a hop |
redeem |
the owner | token |
0 | One output to redeemTo, fee from the fuel up to feeCap |
redeemFunded |
the owner | token |
1 | redeem with the fee paid by the coin |
forceRedeem |
the issuer | token; tokenRaw when the wallet does not have it; optional lockHeight (default expiry) |
0 | The forced redemption, time-locked |
reissue |
the issuer | ended or endedRaw (a redemption or forced redemption), issuer, the new terms |
0 | The successor on the ended transaction’s output 0 |
Metadata adds fuel, maxHops, hops, hopsLeft, expiry, forcedRedemptionHeight, redeemTo, feeCap.
final plugin = LtVnftPlugin(network: NetworkType.TEST);final iss = (await plugin.buildTransaction(issuerWallet.request( {'action': 'issue', 'issuer': issuerHex(), 'maxHops': 2, 'expiry': 900000, 'feeCap': 500}))) .primaryTx;issuerWallet.record(iss);// A transfer takes no wallet coin. libspiffy is told so, and selects none.expect(plugin.requiredFundingUtxoCount('transfer'), 0);final t1 = (await plugin.buildTransaction(issuerWallet.request( {'action': 'transfer', 'token': iss.id, 'to': pkhHex(alice), 'attestation': attestationFor(issuer.lt, iss)}, coins: 0))) .primaryTx;
// What the wallet records about the Output, to show before acceptance.final m = plugin.extractMetadata(t1.outputs[0].script)!;expect([m['hops'], m['hopsLeft'], m['expiry'], m['redeemTo']], [1, 1, 900000, pkhHex(issuerKey)]);expect(m['fuel'], t1.outputs[0].satoshis.toInt());// The Issuer's wallet does not follow the voucher, so it is given the// holder's Transaction as tokenRaw.final forced = (await plugin.buildTransaction(issuerWallet.request( {'action': 'forceRedeem', 'token': t1.id, 'tokenRaw': t1.serialize()}, coins: 0))) .primaryTx;expect(forced.nLockTime, 900000);final r = (await plugin.buildTransaction(aliceWallet.request({'action': 'redeem', 'token': t1.id}, coins: 0))).primaryTx;expect(r.outputs.single.script.toHex(), hex.encode(p2pkhOf(issuerKey)));Receive check: LtVnftReceive.check. Show holding.value, holding.tx.hopsLeft and holding.tx.terms.expiry before acceptance. The errors you are most likely to meet are the voucher has made its <n> transfers: it can only be redeemed, the fee <n> takes more than the redemption fee cap <c> from the token: add a funding coin (use redeemFunded), the wallet does not hold transaction <txid> (give tokenRaw) and maxHops is 1 to 255. See Voucher NFT.
Note NFT: LtNotePlugin
Section titled “Note NFT: LtNotePlugin”Id tsl1_lt_note, script type lt_note. No end.
| Action | Who | Params | Coins |
|---|---|---|---|
issue |
the issuer | issuer, feeCap, exit, maturity, mature, callAt, call; optional maxHops (default 0, no limit), payTo (default the first coin’s key) |
1 or 2 |
transfer, topup |
the owner | as for the Fuelled NFT | 0, 1 |
exit |
the owner | token |
0 |
matureExit |
the owner | token |
0 |
call |
the issuer | token; tokenRaw when the wallet does not have it; optional lockHeight |
0 |
reissue |
the issuer | ended or endedRaw, issuer, the new terms |
0 |
Metadata adds what the holder gets at the current fuel: holderOnExit, holderAtMaturity, holderIfCalled, and callableBeforeMaturity.
final plugin = LtNotePlugin(network: NetworkType.TEST);// A deposit: 30,000 sats on an early exit, everything from block 900,000.final iss = (await plugin.buildTransaction(issuerWallet.request({ 'action': 'issue', 'issuer': issuerHex(), 'feeCap': 500, 'exit': 30000, 'maturity': 900000, 'mature': NoteTerms.everything, 'callAt': NoteTerms.never, 'call': 0,}))) .primaryTx;issuerWallet.record(iss);final t1 = (await plugin.buildTransaction(issuerWallet.request( {'action': 'transfer', 'token': iss.id, 'to': pkhHex(alice), 'attestation': attestationFor(issuer.lt, iss)}, coins: 0))) .primaryTx;final m = plugin.extractMetadata(t1.outputs[0].script)!;expect(m['holderOnExit'], 30000);expect(m['holderIfCalled'], 0);expect(m['callableBeforeMaturity'], isFalse);Receive check: LtNoteReceive.check. holding.tx.noteTerms and holding.value tell you what each ending pays. The errors you are most likely to meet are the note has made its <n> transfers: it can only be ended, the fee <n> is over the note's fee cap <c> and output 0 of the ended transaction is not paid to <key> (reissue only from an ending that paid payTo). See Note NFT.
Sale versions
Section titled “Sale versions”LtFnftSalePlugin (tsl1_lt_fnft_s), LtVnftSalePlugin (tsl1_lt_vnft_s) and LtNoteSalePlugin (tsl1_lt_note_s) have every action of their base plugin, as well as the four swap actions. They only move tokens that were issued as sale versions.
The swap actions
Section titled “The swap actions”All four take no coin. Do not settle a half. It is a deferred payment, which holds your Input until the other side completes it.
| Action | Who | Params | Result |
|---|---|---|---|
swapBid |
the buyer, first | tokenTx (raw), payTo, price, coin (txid:vout), anchorTx (raw); optional links, newOwner |
A half with the buyer’s signature |
swapAccept |
the seller, second | half (raw), fundTx (the coin’s transaction, raw), price |
The complete swap |
swapAsk |
the seller, first | token, price, fundOut (txid:vout), fundSats, fundPub, newOwner; optional payTo |
A half with the seller’s signature |
swapTake |
the buyer, second | half (raw), tokenTx (raw), price, anchorTx (raw); optional links |
The complete swap |
swapBid runs the payee check on the token before signing, and refuses a token that this wallet holds. swapAccept refuses a half that pays a different price from the one you pass, pays a key this wallet does not hold, is for a token this wallet does not hold, or whose buyer Signature or coin does not verify. After the second side settles the swap, the first side hands it to libspiffy with CompleteDeferredPaymentCommand(walletId, txid: halfTxid, rawHex: swapRaw). See Sales and swaps.

