-
Notifications
You must be signed in to change notification settings - Fork 22
Creating custom Casts
TxForge ships with a collection of built in casts that are great for most common transaction use cases. However, as an adventurous and ambitious apprentice in the art of crafting transactions, you'll be pleased to hear you have access to all the tools you need to start building any kind of transaction template you can imagine.
Creating a Cast is a case of defining a class that extends from Cast
, and defining the locking and unlocking scripts using the Tape API.
import { Cast } from 'txforge'
class MyPuzzle extends Cast {
init(params, opts) {
// a useful hook to validate and/or coerce parameters
}
lockingScript(params, opts) {
// define the locking script
}
unlockingScript(params, opts) {
// define the unlocking script
}
}
The init()
function is called after a Cast is instantiated. It provides a hook where parameters can be validated and values coerced.
init(params) {
if (typeof params.address === 'string') {
this.params.address = Address.fromString(params.address)
}
if (this.mode === 'lock' && !this.params.address) {
throw new Error('address param is required')
}
}
The lockingScript()
function is used to build the locking script on this.script
using the Tape API.
lockingScript({ address }) {
this.script
.push(OP_DUP)
.push(OP_HASH160)
.push(address.pubkeyhash)
.push(OP_EQUALVERIFY)
.push(OP_CHECKSIG)
}
The unlockingScript()
function is used to build the unlocking script on this.script
using the Tape API.
unlockingScript({ privkey }) {
this.script
.apply(signTx, [privkey])
.push(privkey.toPublicKey())
}
This example locks satoshis to a number (the answer
). The satoshis can then be unlocked by specifying two numbers (a
and b
) that when added together equal the answer
.
Putting aside the obvious flaw that coins locked to a puzzle like this can be spent by anyone who can add two numbers together, it's a nice example to demonstrate the basic anatomy of a Cast.
import { Cast, helpers } from 'txforge'
import nimble from '@runonbitcoin/nimble'
const { num } = helpers
const { OP_ADD, OP_EQUAL } = nimble.constants.opcodes
class MathPuzzle extends Cast {
lockingScript({ answer }) {
this.script
.push(OP_ADD)
.push(num(answer))
.push(OP_EQUAL)
}
unlockingScript({ a, b }) {
this.script
.push(num(a))
.push(num(b))
}
}
The following shows satoshis being locked and then spent using the MathPuzzle
Cast.
import { forgeTx, getUTXO } from 'txforge'
const tx1 = forgeTx({
inputs: [...],
outputs: [
MathPuzzle.lock(10000, { answer: 21 })
]
})
const utxo = getUTXO(tx1, 0)
const tx2 = forgeTx({
inputs: [
MathPuzzle.unlock(utxo, { a: 9, b: 12 })
],
outputs: [...]
})
A hash puzzle involves locking satoshis to the hash of a secret. The coins can be unlocked by proving that you know that secret.
import { Cast } from 'txforge'
import nimble from '@runonbitcoin/nimble'
const { OP_EQUAL, OP_SHA256 } = nimble.constants.opcodes
const { sha256, sha256d } = nimble.functions
class HashPuzzle extends Cast {
lockingScript({ secret }) {
this.script
.push(OP_SHA256)
.push(sha256d(secret))
.push(OP_EQUAL)
}
unlockingScript({ secret }) {
this.script
.push(sha256(secret))
}
}
The following demonstrates how the HashPuzzle
Cast is used to lock and unlock coins.
import { forgeTx, getUTXO } from 'txforge'
const secret = 'My Secret Password!!!1'
const tx1 = forgeTx({
inputs: [...],
outputs: [
HashPuzzle.lock(10000, { secret })
]
})
const utxo = getUTXO(tx1, 0)
const tx2 = forgeTx({
inputs: [
HashPuzzle.unlock(utxo, { secret })
],
outputs: [...]
})