Skip to content

Script templates

Script templates describe known script shapes. Each template can tell whether a script matches, extract the values in it, build Locking and Unlocking Script builders, and say whether a set of Public Keys can spend it. The ScriptTemplateRegistry holds the templates, and a wallet uses it to classify the Outputs it receives.

The built-in templates are P2PKH, P2PK, P2SH, P2MS, OP_RETURN, AUTHOR_IDENTITY (Author Identity Protocol), B_PROTOCOL (B:// files) and HODLOCKER.

The examples use the keys and the fundingTransaction helper described in About the examples.

TemplateRegistry.initialize(); // once, at start-up, to register the built-in templates
final registry = ScriptTemplateRegistry(); // a singleton
registry.identifyScriptType(p2pkh); // 'P2PKH'
registry.identifyScriptType(opReturn); // 'OP_RETURN'
registry.identifyScriptType(multisig); // 'P2MS'
registry.identifyScriptType(custom); // null: no template matches

extractScriptInfo returns a map whose keys depend on the template: pubKeyHash for P2PKH, publicKey for P2PK, scriptHash for P2SH, and threshold, totalKeys and publicKeys for P2MS.

final info = registry.extractScriptInfo(p2pkh)!;
info['type']; // 'P2PKH'
HEX.encode(info['pubKeyHash']); // 94837d2d5d6106aa97db38957dcc294181ee91e9
final myKeys = [aliceKey.publicKey];
registry.canBeSatisfiedBy(mine, myKeys); // true
registry.canBeSatisfiedBy(theirs, myKeys); // false
// For multisig the answer means "holds at least one of the keys", not "can sign alone".
registry.canBeSatisfiedBy(multisig, myKeys); // true, although this 2-of-2 also needs Bob

createBuilder and createUnlockingBuilder take the template’s parameters as a map, which suits scripts that are described in configuration or received over an API:

final locker = registry.createBuilder('P2PKH', {
'pubKeyHash': Uint8List.fromList(HEX.decode(myAddress.pubkeyHash160)),
})!;
final script = locker.getScriptPubkey(); // same bytes as P2PKHLockBuilder.fromAddress(myAddress)

We implement ScriptTemplate and register it:

/// Recognises the locking script `OP_ADD OP_5 OP_EQUAL`.
class AddFivePuzzleTemplate implements ScriptTemplate {
@override
String get name => 'ADD_FIVE';
@override
bool matches(SVScript script) => script.toHex() == SVScript.fromString('OP_ADD OP_5 OP_EQUAL').toHex();
@override
Map<String, dynamic> extractScriptInfo(SVScript script) => {'type': name};
@override
bool canBeSatisfiedBy(List<SVPublicKey> availableKeys, SVScript script) => matches(script); // no key needed
@override
LockingScriptBuilder createBuilder(Map<String, dynamic> params) =>
DefaultLockBuilder.fromScript(SVScript.fromString('OP_ADD OP_5 OP_EQUAL'));
@override
UnlockingScriptBuilder createUnlockingBuilder(Map<String, dynamic> params) =>
DefaultUnlockBuilder.fromScript(ScriptBuilder().number(params['a']).number(params['b']).build());
}
ScriptTemplateRegistry().register(AddFivePuzzleTemplate());
ScriptTemplateRegistry().identifyScriptType(SVScript.fromString('OP_ADD OP_5 OP_EQUAL')); // 'ADD_FIVE'

The registry checks templates in the order they were registered, and returns the first match.