Skip to content

Commit

Permalink
feat: Add Etch and Mint to cmd
Browse files Browse the repository at this point in the history
--story=1
  • Loading branch information
studyzy committed Apr 28, 2024
1 parent 6774402 commit 5a49c30
Show file tree
Hide file tree
Showing 13 changed files with 1,636 additions and 73 deletions.
77 changes: 77 additions & 0 deletions cmd/runestonecli/address.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
package main

import (
"github.com/btcsuite/btcd/btcec/v2"
"github.com/btcsuite/btcd/btcec/v2/schnorr"
"github.com/btcsuite/btcd/btcutil"
"github.com/btcsuite/btcd/chaincfg"
"github.com/btcsuite/btcd/txscript"
)

func GetTapScriptAddress(pk *btcec.PublicKey, revealedScript []byte, net *chaincfg.Params) (btcutil.Address, error) {
pubkey33 := pk.SerializeCompressed()
if pubkey33[0] == 0x02 {
pubkey33[0] = byte(txscript.BaseLeafVersion)
} else {
pubkey33[0] = byte(txscript.BaseLeafVersion) + 1
}

controlBlock, err := txscript.ParseControlBlock(
pubkey33,
)
if err != nil {
return nil, err
}
rootHash := controlBlock.RootHash(revealedScript)

// Next, we'll construct the final commitment (creating the external or
// taproot output key) as a function of this commitment and the
// included internal key: taprootKey = internalKey + (tPoint*G).
taprootKey := txscript.ComputeTaprootOutputKey(
controlBlock.InternalKey, rootHash,
)

// If we convert the taproot key to a witness program (we just need to
// serialize the public key), then it should exactly match the witness
// program passed in.
tapKeyBytes := schnorr.SerializePubKey(taprootKey)

addr, err := btcutil.NewAddressTaproot(
tapKeyBytes,
net,
)
return addr, nil
}
func GetTaprootPubkey(pubkey *btcec.PublicKey, revealedScript []byte) (*btcec.PublicKey, error) {
controlBlock := txscript.ControlBlock{}
controlBlock.InternalKey = pubkey
rootHash := controlBlock.RootHash(revealedScript)

// Next, we'll construct the final commitment (creating the external or
// taproot output key) as a function of this commitment and the
// included internal key: taprootKey = internalKey + (tPoint*G).
taprootKey := txscript.ComputeTaprootOutputKey(
controlBlock.InternalKey, rootHash,
)
return taprootKey, nil
}

// GetP2TRAddress returns a taproot address for a given public key.
func GetP2TRAddress(pubKey *btcec.PublicKey, net *chaincfg.Params) (string, error) {
addr, err := getP2TRAddress(pubKey, net)
if err != nil {
return "", err

}
return addr.EncodeAddress(), nil
}
func getP2TRAddress(pubKey *btcec.PublicKey, net *chaincfg.Params) (btcutil.Address, error) {
tapKey := txscript.ComputeTaprootKeyNoScript(pubKey)
addr, err := btcutil.NewAddressTaproot(
schnorr.SerializePubKey(tapKey), net,
)
if err != nil {
return nil, err
}
return addr, nil
}
186 changes: 186 additions & 0 deletions cmd/runestonecli/config.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,186 @@
package main

import (
"encoding/hex"
"errors"
"unicode/utf8"

"github.com/btcsuite/btcd/btcec/v2"
"github.com/btcsuite/btcd/btcec/v2/schnorr"
"github.com/btcsuite/btcd/btcutil"
"github.com/btcsuite/btcd/chaincfg"
"github.com/btcsuite/btcd/txscript"
"github.com/bxelab/runestone"
"lukechampine.com/uint128"
)

type Config struct {
PrivateKey string
FeePerByte int64
UtxoAmount int64
Network string
RpcUrl string
Etching *struct {
Rune string
Symbol *string
Premine *uint64
Amount *uint64
Cap *uint64
Divisibility *int
HeightStart *int
HeightEnd *int
HeightOffsetStart *int
HeightOffsetEnd *int
}
Mint *struct {
RuneId string
}
}

func DefaultConfig() Config {
return Config{
FeePerByte: 5,
UtxoAmount: 1000,
Network: "mainnet",
RpcUrl: "https://mempool.space/api",
}

}
func (c Config) GetFeePerByte() int64 {
if c.FeePerByte == 0 {
return 5
}
return c.FeePerByte
}
func (c Config) GetUtxoAmount() int64 {
if c.UtxoAmount == 0 {
return 666
}
return c.UtxoAmount
}

func (c Config) GetEtching() (*runestone.Etching, error) {
if c.Etching == nil {
return nil, errors.New("Etching config is required")
}
if c.Etching.Rune == "" {
return nil, errors.New("Rune is required")
}
if c.Etching.Symbol != nil {
runeCount := utf8.RuneCountInString(*c.Etching.Symbol)
if runeCount != 1 {
return nil, errors.New("Symbol must be a single character")
}
}
etching := &runestone.Etching{}
r, err := runestone.SpacedRuneFromString(c.Etching.Rune)
if err != nil {
return nil, err
}
etching.Rune = &r.Rune
etching.Spacers = &r.Spacers
if c.Etching.Symbol != nil {
symbolStr := *c.Etching.Symbol
symbol := rune(symbolStr[0])
etching.Symbol = &symbol
}
if c.Etching.Premine != nil {
premine := uint128.From64(*c.Etching.Premine)
etching.Premine = &premine
}
if c.Etching.Amount != nil {
amount := uint128.From64(*c.Etching.Amount)
if etching.Terms == nil {
etching.Terms = &runestone.Terms{}
}
etching.Terms.Amount = &amount
}
if c.Etching.Cap != nil {
cap := uint128.From64(*c.Etching.Cap)
etching.Terms.Cap = &cap
}
if c.Etching.Divisibility != nil {
d := uint8(*c.Etching.Divisibility)
etching.Divisibility = &d
}
if c.Etching.HeightStart != nil {
h := uint64(*c.Etching.HeightStart)
if etching.Terms == nil {
etching.Terms = &runestone.Terms{}
}
etching.Terms.Height[0] = &h
}
if c.Etching.HeightEnd != nil {
h := uint64(*c.Etching.HeightEnd)
if etching.Terms == nil {
etching.Terms = &runestone.Terms{}
}
etching.Terms.Height[1] = &h
}
if c.Etching.HeightOffsetStart != nil {
h := uint64(*c.Etching.HeightOffsetStart)
if etching.Terms == nil {
etching.Terms = &runestone.Terms{}
}
etching.Terms.Offset[0] = &h
}
if c.Etching.HeightOffsetEnd != nil {
h := uint64(*c.Etching.HeightOffsetEnd)
if etching.Terms == nil {
etching.Terms = &runestone.Terms{}
}
etching.Terms.Offset[1] = &h
}
return etching, nil
}
func (c Config) GetMint() (*runestone.RuneId, error) {
if c.Mint == nil {
return nil, errors.New("Mint config is required")
}
if c.Mint.RuneId == "" {
return nil, errors.New("RuneId is required")
}
runeId, err := runestone.RuneIdFromString(c.Mint.RuneId)
if err != nil {
return nil, err
}
return runeId, nil
}
func (c Config) GetNetwork() *chaincfg.Params {
if c.Network == "mainnet" {
return &chaincfg.MainNetParams
}
if c.Network == "testnet" {
return &chaincfg.TestNet3Params
}
if c.Network == "regtest" {
return &chaincfg.RegressionNetParams
}
if c.Network == "signet" {
return &chaincfg.SigNetParams
}
panic("unknown network")
}

func (c Config) GetPrivateKeyAddr() (*btcec.PrivateKey, string, error) {
if c.PrivateKey == "" {
return nil, "", errors.New("PrivateKey is required")
}
pkBytes, err := hex.DecodeString(c.PrivateKey)
if err != nil {
return nil, "", err
}
privKey, pubKey := btcec.PrivKeyFromBytes(pkBytes)
if err != nil {
return nil, "", err
}
tapKey := txscript.ComputeTaprootKeyNoScript(pubKey)
addr, err := btcutil.NewAddressTaproot(
schnorr.SerializePubKey(tapKey), c.GetNetwork(),
)
if err != nil {
return nil, "", err
}
address := addr.EncodeAddress()
return privKey, address, nil
}
18 changes: 18 additions & 0 deletions cmd/runestonecli/config.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
PrivateKey: "1234567890"
Network: "testnet" # mainnet or testnet
RpcUrl: "https://blockstream.info/testnet/api" #https://mempool.space/api https://mempool.space/testnet/api
FeePerByte: 5
UtxoAmount: 1000
Etching:
Rune: "STUDYZY"
Symbol: ""
Premine: 1000000
Amount: 1000
Cap: 20000
# Divisibility: 0
# HeightStart: 0
# HeightEnd: 0
# HeightOffsetStart: 0
# HeightOffsetEnd: 0
Mint:
RuneId: "2609649:946"
75 changes: 75 additions & 0 deletions cmd/runestonecli/example.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
package main

import (
"encoding/hex"
"encoding/json"
"fmt"

"github.com/btcsuite/btcd/txscript"
"github.com/btcsuite/btcd/wire"
"github.com/bxelab/runestone"
"lukechampine.com/uint128"
)

func testEtching() {
runeName := "STUDYZY.GMAIL.COM"
symbol := '曾'
myRune, err := runestone.SpacedRuneFromString(runeName)
if err != nil {
fmt.Println(err)
return
}
amt := uint128.From64(666666)
ca := uint128.From64(21000000)
etching := &runestone.Etching{
Rune: &myRune.Rune,
Spacers: &myRune.Spacers,
Symbol: &symbol,
Terms: &runestone.Terms{
Amount: &amt,
Cap: &ca,
},
}
r := runestone.Runestone{Etching: etching}
data, err := r.Encipher()
if err != nil {
fmt.Println(err)
}
fmt.Printf("Etching data: 0x%x\n", data)
dataString, _ := txscript.DisasmString(data)
fmt.Printf("Etching Script: %s\n", dataString)
}
func testMint() {
runeIdStr := "2609649:946"
runeId, _ := runestone.RuneIdFromString(runeIdStr)
r := runestone.Runestone{Mint: runeId}
data, err := r.Encipher()
if err != nil {
fmt.Println(err)
}
fmt.Printf("Mint Rune[%s] data: 0x%x\n", runeIdStr, data)
dataString, _ := txscript.DisasmString(data)
fmt.Printf("Mint Script: %s\n", dataString)
}
func testDecode() {
data, _ := hex.DecodeString("140114001600") //Mint UNCOMMON•GOODS
var tx wire.MsgTx
builder := txscript.NewScriptBuilder()
// Push opcode OP_RETURN
builder.AddOp(txscript.OP_RETURN)
// Push MAGIC_NUMBER
builder.AddOp(runestone.MAGIC_NUMBER)
// Push payload
builder.AddData(data)
pkScript, _ := builder.Script()
txOut := wire.NewTxOut(0, pkScript)
tx.AddTxOut(txOut)
r := &runestone.Runestone{}
artifact, err := r.Decipher(&tx)
if err != nil {
fmt.Println(err)
return
}
a, _ := json.Marshal(artifact)
fmt.Printf("Artifact: %s\n", string(a))
}
Loading

0 comments on commit 5a49c30

Please sign in to comment.