Documentation
¶
Overview ¶
Package mint builds the mined transactions an application's state lives on: a transition of its state token, a funding tree, and a plain payment, each with a fee input and a change output supplied by the caller.
Every lock script arrives as a parameter and every input arrives with the template that signs it, so the package knows no application's derivation or tags and holds no key material. What it owns is the fee logic: a loop that signs to measure the size and rebuilds at the rate until the fee covers it.
A carrier is not built here: it is never mined and pays no fee, so package carrier builds it.
Index ¶
- Constants
- Variables
- func FundingTree(lock *script.Script, count int, sats uint64, fee Input, change *script.Script, ...) (*transaction.Transaction, error)
- func Payment(ctx context.Context, dest *script.Script, sats uint64, fee Input, ...) (*transaction.Transaction, error)
- func Transition(lock *script.Script, sats uint64, prev *Input, fee Input, ...) (*transaction.Transaction, error)
- type Fees
- type Input
Examples ¶
Constants ¶
const MaxFundingOutputs = 1023
MaxFundingOutputs is the most funding outputs one funding tree holds, its change outputs aside. A sweep of every funding output of a tree with one fee input then spends at most 1024 inputs, the least a host that admits a sweep must accept, so the kill switch for a whole tree is one transaction every such host takes. Every carrier's BEEF carries its whole tree as well, so an application usually wants a tree far smaller.
Variables ¶
var ( ErrInsufficient = errors.New("mint: fee input cannot cover the outputs and the fee") ErrNoChange = errors.New("mint: nil change script") // ErrTreeTooLarge is a funding tree of more than MaxFundingOutputs // funding outputs. ErrTreeTooLarge = errors.New("mint: a funding tree holds at most 1023 funding outputs") )
var DefaultFees = Fees{SatPerByte: 1, Floor: 250}
DefaultFees is one satoshi per byte with a 250 satoshi floor.
Functions ¶
func FundingTree ¶
func FundingTree(lock *script.Script, count int, sats uint64, fee Input, change *script.Script, fees Fees) (*transaction.Transaction, error)
FundingTree builds and signs a funding tree: count outputs of sats each, locked with lock, plus change. Each carrier spends one; a mined spend of any of them by the owner (a kill switch) is a transaction the caller builds with the unlocker for lock. count is 1 to MaxFundingOutputs; a larger one is refused with an error wrapping ErrTreeTooLarge.
Example ¶
A funding tree on the test chain: four funding outputs of 1,000 satoshis under the application's funding lock, paid for by the coin, with the fee settled by the fee loop and the change sent back to the payer. The tree is not mined, so it verifies through its proven parent, and the state an application keeps about it carries the BEEF that proves it.
package main
import (
"context"
"fmt"
"github.com/bsv-blockchain/go-sdk/chainhash"
"github.com/bsv-blockchain/go-sdk/script"
"github.com/bsv-blockchain/go-sdk/transaction"
"github.com/bsv-blockchain/go-sdk/transaction/template/p2pkh"
"github.com/bsv-blockchain/go-sdk/wallet"
"github.com/lightwebinc/bcommon/carrier"
"github.com/lightwebinc/bcommon/funding"
"github.com/lightwebinc/bcommon/goldentest"
"github.com/lightwebinc/bcommon/mint"
"github.com/lightwebinc/bcommon/pushdrop"
"github.com/lightwebinc/bcommon/verify"
)
// testCoin is a stand-in mined coin on a test chain that exists only in this
// process: one P2PKH output of 50,000 satoshis to the fixed test key, placed
// by a stand-in proof at offset 1 of a two-transaction block at height 90.
// The tracker knows that block's merkle root and nothing else, so SPV runs
// against it exactly as it would against a reader's own header source.
func testCoin() (*transaction.Transaction, *goldentest.Tracker, error) {
addr, err := script.NewAddressFromPublicKey(goldentest.FixedKey().PubKey(), false)
if err != nil {
return nil, nil, err
}
lock, err := p2pkh.Lock(addr)
if err != nil {
return nil, nil, err
}
coin := transaction.NewTransaction()
nowhere := chainhash.Hash(goldentest.Fill(0x11))
coin.AddInput(&transaction.TransactionInput{SourceTXID: &nowhere, UnlockingScript: &script.Script{},
SequenceNumber: transaction.MaxTxInSequenceNum})
coin.AddOutput(&transaction.TransactionOutput{Satoshis: 50000, LockingScript: lock})
sibling := chainhash.Hash(goldentest.Fill(0x33))
isTxid := true
coin.MerklePath = transaction.NewMerklePath(90, [][]*transaction.PathElement{{
{Offset: 0, Hash: &sibling},
{Offset: 1, Hash: coin.TxID(), Txid: &isTxid},
}})
root, err := coin.MerklePath.ComputeRoot(coin.TxID())
if err != nil {
return nil, nil, err
}
return coin, &goldentest.Tracker{Roots: map[uint32]string{90: root.String()}, Tip: 100}, nil
}
// A funding tree on the test chain: four funding outputs of 1,000 satoshis
// under the application's funding lock, paid for by the coin, with the fee
// settled by the fee loop and the change sent back to the payer. The tree
// is not mined, so it verifies through its proven parent, and the state an
// application keeps about it carries the BEEF that proves it.
func main() {
ctx := context.Background()
coin, tracker, err := testCoin()
if err != nil {
fmt.Println(err)
return
}
payer, err := p2pkh.Unlock(goldentest.FixedKey(), nil)
if err != nil {
fmt.Println(err)
return
}
w, err := wallet.NewCompletedProtoWallet(goldentest.FixedKey())
if err != nil {
fmt.Println(err)
return
}
// The application's carrier parameters: its derivation and funding tag.
// These are the test vectors' own, reserved for tests.
params := carrier.Params{
Derivation: pushdrop.Derivation{
Protocol: wallet.Protocol{SecurityLevel: wallet.SecurityLevelEveryApp, Protocol: "vector sample"},
KeyID: "object",
},
FundingTag: []byte{'v', 'x', 0x02},
}
lock, err := carrier.FundingLock(ctx, w, "example.com", params)
if err != nil {
fmt.Println(err)
return
}
fee := mint.Input{Tx: coin, Vout: 0, Unlocker: payer}
change := coin.Outputs[0].LockingScript
tree, err := mint.FundingTree(lock, 4, 1000, fee, change, mint.DefaultFees)
if err != nil {
fmt.Println(err)
return
}
for i, out := range tree.Outputs {
_, isFunding := carrier.DecodeFunding(out.LockingScript, params.FundingTag)
fmt.Printf("output %d: %d sats, funding=%t\n", i, out.Satoshis, isFunding)
}
fmt.Println("fee:", 50000-tree.TotalOutputSatoshis(), "sats for", tree.Size(), "bytes")
verdict, err := verify.Check(ctx, tree, tracker)
fmt.Println("proves through its parent:", verdict == verify.Passed, err)
beef, err := funding.KeepBEEF(tree, nil)
if err != nil {
fmt.Println(err)
return
}
state := funding.Tree{Txid: tree.TxID().String(), RawHex: tree.Hex(), BeefHex: beef, Sats: 1000, Count: 4}
fmt.Println("carriers it can fund:", state.Remaining())
}
Output: output 0: 1000 sats, funding=true output 1: 1000 sats, funding=true output 2: 1000 sats, funding=true output 3: 1000 sats, funding=true output 4: 45610 sats, funding=false fee: 390 sats for 388 bytes proves through its parent: true <nil> carriers it can fund: 4
func Payment ¶
func Payment(ctx context.Context, dest *script.Script, sats uint64, fee Input, change *script.Script, fees Fees) (*transaction.Transaction, error)
Payment builds and signs a simple payment: one output of sats to dest, change back to the payer. It is the BRC-29 leg: the destination is a key derived from the recipient's identity, and the transaction itself is an ordinary P2PKH spend.
ctx is unused: nothing here reaches the network, and a template that signs through a wallet carries its own context. It is kept so the signature stays stable for callers if a later version needs it.
func Transition ¶
func Transition(lock *script.Script, sats uint64, prev *Input, fee Input, change *script.Script, fees Fees) (*transaction.Transaction, error)
Transition builds and signs a state token transaction: one output of sats locked with lock.
Inputs: the previous token output when prev is not nil (an update spends its predecessor; a create spends none), then the fee input. Outputs: the token at index 0, then change. Change below the fee floor is dropped rather than left as dust.
prev.Unlocker is required. The key that signs the previous token is not always the one the new lock names (after a rotation the predecessor signs and the successor is locked to), so which one it is stays the caller's decision rather than a default here.
Types ¶
type Fees ¶
Fees is the fee policy: a rate per byte of the signed transaction and a floor under it. The floor keeps a fee meaningful where the per-byte rate alone would round to almost nothing.
type Input ¶
type Input struct {
Tx *transaction.Transaction
Vout uint32
Unlocker transaction.UnlockingScriptTemplate
}
Input is one input the caller funds a transaction with: its source transaction, the output index, and the template that signs it. The template is what keeps key material out of this package: an embedded wallet's funding unlocker signs through wallet.CreateSignature.