Getting started
In this section we will set up libtokensv inside an app that already runs a libspiffy wallet. For the wallet itself (creating it, funding it, the actor system and its storage) please refer to libspiffy.dev.
Installing the package
Section titled “Installing the package”We add the package next to libspiffy and dartsv:
dependencies: libtokensv: 0.2.0 libspiffy: ^5.4.0 dartsv: ^3.1.1dart pub add libtokensv:0.2.0Everything that is public lives in a single library:
import 'package:libtokensv/libtokensv.dart';Registering the plugins
Section titled “Registering the plugins”libspiffy only recognises a token Output through the plugin that owns its Script. This means we need to register each plugin we intend to use, in the isolate the wallet runs in, and we need to do so before libspiffy loads a wallet or receives a Transaction. A token that arrives while its plugin is not registered is not recorded as the wallet’s.
// In the isolate the wallet runs in, before libspiffy loads or receives// anything: a token received while its plugin is not registered is not// recorded as the wallet's.final nft = LtNftPlugin(network: dartsv.NetworkType.TEST);final fnft = LtFnftPlugin(network: dartsv.NetworkType.TEST);setUp(() { PluginRegistry().register(nft); PluginRegistry().register(fnft);});tearDown(() { PluginRegistry().unregister(LtNftPlugin.id); PluginRegistry().unregister(LtFnftPlugin.id);});The plugins are:
| Plugin | Token | Page |
|---|---|---|
PushDropPlugin |
PushDrop | PushDrop |
LtNftPlugin |
Covenant NFT | NFT |
LtLnftPlugin |
Listed NFT | Listed NFT |
LtFnftPlugin, LtFnftSalePlugin |
Fuelled NFT and its sale version | Fuelled NFT |
LtVnftPlugin, LtVnftSalePlugin |
Voucher NFT and its sale version | Voucher NFT |
LtNotePlugin, LtNoteSalePlugin |
Note NFT and its sale version | Note NFT |
Do note that the fungible Lineage Token has no plugin in 0.2.0. For that token we build the Transactions with
LtBuilder, and fund and broadcast them ourselves.
dartsv has the same trap waiting for us with Addresses. toAddress takes its own network argument and defaults to mainnet, whatever network the key was created with.
final k = SVPrivateKey.fromHex('11' * 32, NetworkType.TEST);// The key's network is not used. toAddress defaults to mainnet.expect(k.toAddress().networkType, NetworkType.MAIN);expect(k.toAddress(networkType: NetworkType.TEST).networkType, NetworkType.TEST);Making a token payment
Section titled “Making a token payment”A token operation is an ordinary libspiffy payment whose Output is a PluginOutputSpec. The spec holds the plugin’s id and script type, the action and its params, and an amount. libspiffy selects coins for the amount, calls the plugin to build the Transaction, and signs every Input (the token Input with the key the plugin names). It then records the result and returns its BEEF.
// A plugin payment is an invoice whose Output is the plugin's spec. Its// amount makes libspiffy select coins. Above one 60,000-sat coin, it takes two.final issued = await h.ask<BEEFPaymentResponse>( h.spiffy.paymentCoordinator, PayInvoiceMessage( walletId: h.wallet, invoiceId: 'issue-1', addresses: const [], amount: BigInt.from(70000), outputs: [ PluginOutputSpec( pluginId: LtNftPlugin.id, pluginScriptType: LtNftPlugin.scriptType, params: {'action': 'issue', 'issuer': hex.encode(issuer.lt.pkh)}, amount: BigInt.from(70000), ), ], ),);expect(issued.success, isTrue, reason: issued.error);- Send the payment, and check
success. On failure,errortells us why (the plugin’s own refusals come back here too). - Settle it. We have libspiffy broadcast the BEEF (
SettleBEEFCommand) and follow it until ARC reports it as seen or mined. - For a transfer to someone else, we send them the BEEF along with whatever that kind of token needs (the Issuer’s anchor, reissue links, a payload Transaction). The payee then runs its receive check. See Wallet plugins.
The wallet keeps token Outputs apart from its coins. It tracks them under the plugin, and never offers them up for an ordinary payment or counts them in the balance:
// The wallet tracks the token Output under the plugin, and never offers// it as a coin for an ordinary payment.final tokens = await h.spiffy.walletStorage.getUTXOsByPlugin(h.wallet, LtNftPlugin.id);final coins = await h.spiffy.walletStorage.getPaymentUTXOs(h.wallet);expect(tokens.map((u) => u.key), contains('${t1.id}:1'));expect(coins.where((c) => tokens.any((t) => t.key == c.key)), isEmpty);How the examples run
Section titled “How the examples run”The wallet tests in the examples start a real libspiffy wallet on a temporary Isar store with ARC stubbed out, in the same way that libtokensv’s own tests do. The harness is examples/libtokensv/test/support/spiffy_harness.dart in the site’s repository. It needs dactor, isar_community and spiffynode as dev dependencies, as well as an override:
dev_dependencies: dactor: ^1.3.0 isar_community: 3.3.2 spiffynode: ^1.1.0
dependency_overrides: # isar_community 3.4 asserts on the 3.3.2 schemas libspiffy's # dependencies were generated with. isar_community: 3.3.2An app pins whatever Isar version its libspiffy needs. The override above is only there for running the examples.
Where to next
Section titled “Where to next”- If you are new to the Lineage Tokens, Concepts explains what a token proves and what you need to check.
- If you are setting up as an Issuer, start with The issuer.
- If you want to publish to an overlay, read PushDrop and Overlay services.

