Skip to content

Commit 5a49c30

Browse files
committed
feat: Add Etch and Mint to cmd
--story=1
1 parent 6774402 commit 5a49c30

13 files changed

+1636
-73
lines changed

cmd/runestonecli/address.go

+77
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,77 @@
1+
package main
2+
3+
import (
4+
"github.com/btcsuite/btcd/btcec/v2"
5+
"github.com/btcsuite/btcd/btcec/v2/schnorr"
6+
"github.com/btcsuite/btcd/btcutil"
7+
"github.com/btcsuite/btcd/chaincfg"
8+
"github.com/btcsuite/btcd/txscript"
9+
)
10+
11+
func GetTapScriptAddress(pk *btcec.PublicKey, revealedScript []byte, net *chaincfg.Params) (btcutil.Address, error) {
12+
pubkey33 := pk.SerializeCompressed()
13+
if pubkey33[0] == 0x02 {
14+
pubkey33[0] = byte(txscript.BaseLeafVersion)
15+
} else {
16+
pubkey33[0] = byte(txscript.BaseLeafVersion) + 1
17+
}
18+
19+
controlBlock, err := txscript.ParseControlBlock(
20+
pubkey33,
21+
)
22+
if err != nil {
23+
return nil, err
24+
}
25+
rootHash := controlBlock.RootHash(revealedScript)
26+
27+
// Next, we'll construct the final commitment (creating the external or
28+
// taproot output key) as a function of this commitment and the
29+
// included internal key: taprootKey = internalKey + (tPoint*G).
30+
taprootKey := txscript.ComputeTaprootOutputKey(
31+
controlBlock.InternalKey, rootHash,
32+
)
33+
34+
// If we convert the taproot key to a witness program (we just need to
35+
// serialize the public key), then it should exactly match the witness
36+
// program passed in.
37+
tapKeyBytes := schnorr.SerializePubKey(taprootKey)
38+
39+
addr, err := btcutil.NewAddressTaproot(
40+
tapKeyBytes,
41+
net,
42+
)
43+
return addr, nil
44+
}
45+
func GetTaprootPubkey(pubkey *btcec.PublicKey, revealedScript []byte) (*btcec.PublicKey, error) {
46+
controlBlock := txscript.ControlBlock{}
47+
controlBlock.InternalKey = pubkey
48+
rootHash := controlBlock.RootHash(revealedScript)
49+
50+
// Next, we'll construct the final commitment (creating the external or
51+
// taproot output key) as a function of this commitment and the
52+
// included internal key: taprootKey = internalKey + (tPoint*G).
53+
taprootKey := txscript.ComputeTaprootOutputKey(
54+
controlBlock.InternalKey, rootHash,
55+
)
56+
return taprootKey, nil
57+
}
58+
59+
// GetP2TRAddress returns a taproot address for a given public key.
60+
func GetP2TRAddress(pubKey *btcec.PublicKey, net *chaincfg.Params) (string, error) {
61+
addr, err := getP2TRAddress(pubKey, net)
62+
if err != nil {
63+
return "", err
64+
65+
}
66+
return addr.EncodeAddress(), nil
67+
}
68+
func getP2TRAddress(pubKey *btcec.PublicKey, net *chaincfg.Params) (btcutil.Address, error) {
69+
tapKey := txscript.ComputeTaprootKeyNoScript(pubKey)
70+
addr, err := btcutil.NewAddressTaproot(
71+
schnorr.SerializePubKey(tapKey), net,
72+
)
73+
if err != nil {
74+
return nil, err
75+
}
76+
return addr, nil
77+
}

cmd/runestonecli/config.go

+186
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,186 @@
1+
package main
2+
3+
import (
4+
"encoding/hex"
5+
"errors"
6+
"unicode/utf8"
7+
8+
"github.com/btcsuite/btcd/btcec/v2"
9+
"github.com/btcsuite/btcd/btcec/v2/schnorr"
10+
"github.com/btcsuite/btcd/btcutil"
11+
"github.com/btcsuite/btcd/chaincfg"
12+
"github.com/btcsuite/btcd/txscript"
13+
"github.com/bxelab/runestone"
14+
"lukechampine.com/uint128"
15+
)
16+
17+
type Config struct {
18+
PrivateKey string
19+
FeePerByte int64
20+
UtxoAmount int64
21+
Network string
22+
RpcUrl string
23+
Etching *struct {
24+
Rune string
25+
Symbol *string
26+
Premine *uint64
27+
Amount *uint64
28+
Cap *uint64
29+
Divisibility *int
30+
HeightStart *int
31+
HeightEnd *int
32+
HeightOffsetStart *int
33+
HeightOffsetEnd *int
34+
}
35+
Mint *struct {
36+
RuneId string
37+
}
38+
}
39+
40+
func DefaultConfig() Config {
41+
return Config{
42+
FeePerByte: 5,
43+
UtxoAmount: 1000,
44+
Network: "mainnet",
45+
RpcUrl: "https://mempool.space/api",
46+
}
47+
48+
}
49+
func (c Config) GetFeePerByte() int64 {
50+
if c.FeePerByte == 0 {
51+
return 5
52+
}
53+
return c.FeePerByte
54+
}
55+
func (c Config) GetUtxoAmount() int64 {
56+
if c.UtxoAmount == 0 {
57+
return 666
58+
}
59+
return c.UtxoAmount
60+
}
61+
62+
func (c Config) GetEtching() (*runestone.Etching, error) {
63+
if c.Etching == nil {
64+
return nil, errors.New("Etching config is required")
65+
}
66+
if c.Etching.Rune == "" {
67+
return nil, errors.New("Rune is required")
68+
}
69+
if c.Etching.Symbol != nil {
70+
runeCount := utf8.RuneCountInString(*c.Etching.Symbol)
71+
if runeCount != 1 {
72+
return nil, errors.New("Symbol must be a single character")
73+
}
74+
}
75+
etching := &runestone.Etching{}
76+
r, err := runestone.SpacedRuneFromString(c.Etching.Rune)
77+
if err != nil {
78+
return nil, err
79+
}
80+
etching.Rune = &r.Rune
81+
etching.Spacers = &r.Spacers
82+
if c.Etching.Symbol != nil {
83+
symbolStr := *c.Etching.Symbol
84+
symbol := rune(symbolStr[0])
85+
etching.Symbol = &symbol
86+
}
87+
if c.Etching.Premine != nil {
88+
premine := uint128.From64(*c.Etching.Premine)
89+
etching.Premine = &premine
90+
}
91+
if c.Etching.Amount != nil {
92+
amount := uint128.From64(*c.Etching.Amount)
93+
if etching.Terms == nil {
94+
etching.Terms = &runestone.Terms{}
95+
}
96+
etching.Terms.Amount = &amount
97+
}
98+
if c.Etching.Cap != nil {
99+
cap := uint128.From64(*c.Etching.Cap)
100+
etching.Terms.Cap = &cap
101+
}
102+
if c.Etching.Divisibility != nil {
103+
d := uint8(*c.Etching.Divisibility)
104+
etching.Divisibility = &d
105+
}
106+
if c.Etching.HeightStart != nil {
107+
h := uint64(*c.Etching.HeightStart)
108+
if etching.Terms == nil {
109+
etching.Terms = &runestone.Terms{}
110+
}
111+
etching.Terms.Height[0] = &h
112+
}
113+
if c.Etching.HeightEnd != nil {
114+
h := uint64(*c.Etching.HeightEnd)
115+
if etching.Terms == nil {
116+
etching.Terms = &runestone.Terms{}
117+
}
118+
etching.Terms.Height[1] = &h
119+
}
120+
if c.Etching.HeightOffsetStart != nil {
121+
h := uint64(*c.Etching.HeightOffsetStart)
122+
if etching.Terms == nil {
123+
etching.Terms = &runestone.Terms{}
124+
}
125+
etching.Terms.Offset[0] = &h
126+
}
127+
if c.Etching.HeightOffsetEnd != nil {
128+
h := uint64(*c.Etching.HeightOffsetEnd)
129+
if etching.Terms == nil {
130+
etching.Terms = &runestone.Terms{}
131+
}
132+
etching.Terms.Offset[1] = &h
133+
}
134+
return etching, nil
135+
}
136+
func (c Config) GetMint() (*runestone.RuneId, error) {
137+
if c.Mint == nil {
138+
return nil, errors.New("Mint config is required")
139+
}
140+
if c.Mint.RuneId == "" {
141+
return nil, errors.New("RuneId is required")
142+
}
143+
runeId, err := runestone.RuneIdFromString(c.Mint.RuneId)
144+
if err != nil {
145+
return nil, err
146+
}
147+
return runeId, nil
148+
}
149+
func (c Config) GetNetwork() *chaincfg.Params {
150+
if c.Network == "mainnet" {
151+
return &chaincfg.MainNetParams
152+
}
153+
if c.Network == "testnet" {
154+
return &chaincfg.TestNet3Params
155+
}
156+
if c.Network == "regtest" {
157+
return &chaincfg.RegressionNetParams
158+
}
159+
if c.Network == "signet" {
160+
return &chaincfg.SigNetParams
161+
}
162+
panic("unknown network")
163+
}
164+
165+
func (c Config) GetPrivateKeyAddr() (*btcec.PrivateKey, string, error) {
166+
if c.PrivateKey == "" {
167+
return nil, "", errors.New("PrivateKey is required")
168+
}
169+
pkBytes, err := hex.DecodeString(c.PrivateKey)
170+
if err != nil {
171+
return nil, "", err
172+
}
173+
privKey, pubKey := btcec.PrivKeyFromBytes(pkBytes)
174+
if err != nil {
175+
return nil, "", err
176+
}
177+
tapKey := txscript.ComputeTaprootKeyNoScript(pubKey)
178+
addr, err := btcutil.NewAddressTaproot(
179+
schnorr.SerializePubKey(tapKey), c.GetNetwork(),
180+
)
181+
if err != nil {
182+
return nil, "", err
183+
}
184+
address := addr.EncodeAddress()
185+
return privKey, address, nil
186+
}

cmd/runestonecli/config.yaml

+18
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
PrivateKey: "1234567890"
2+
Network: "testnet" # mainnet or testnet
3+
RpcUrl: "https://blockstream.info/testnet/api" #https://mempool.space/api https://mempool.space/testnet/api
4+
FeePerByte: 5
5+
UtxoAmount: 1000
6+
Etching:
7+
Rune: "STUDYZY"
8+
Symbol: ""
9+
Premine: 1000000
10+
Amount: 1000
11+
Cap: 20000
12+
# Divisibility: 0
13+
# HeightStart: 0
14+
# HeightEnd: 0
15+
# HeightOffsetStart: 0
16+
# HeightOffsetEnd: 0
17+
Mint:
18+
RuneId: "2609649:946"

cmd/runestonecli/example.go

+75
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,75 @@
1+
package main
2+
3+
import (
4+
"encoding/hex"
5+
"encoding/json"
6+
"fmt"
7+
8+
"github.com/btcsuite/btcd/txscript"
9+
"github.com/btcsuite/btcd/wire"
10+
"github.com/bxelab/runestone"
11+
"lukechampine.com/uint128"
12+
)
13+
14+
func testEtching() {
15+
runeName := "STUDYZY.GMAIL.COM"
16+
symbol := '曾'
17+
myRune, err := runestone.SpacedRuneFromString(runeName)
18+
if err != nil {
19+
fmt.Println(err)
20+
return
21+
}
22+
amt := uint128.From64(666666)
23+
ca := uint128.From64(21000000)
24+
etching := &runestone.Etching{
25+
Rune: &myRune.Rune,
26+
Spacers: &myRune.Spacers,
27+
Symbol: &symbol,
28+
Terms: &runestone.Terms{
29+
Amount: &amt,
30+
Cap: &ca,
31+
},
32+
}
33+
r := runestone.Runestone{Etching: etching}
34+
data, err := r.Encipher()
35+
if err != nil {
36+
fmt.Println(err)
37+
}
38+
fmt.Printf("Etching data: 0x%x\n", data)
39+
dataString, _ := txscript.DisasmString(data)
40+
fmt.Printf("Etching Script: %s\n", dataString)
41+
}
42+
func testMint() {
43+
runeIdStr := "2609649:946"
44+
runeId, _ := runestone.RuneIdFromString(runeIdStr)
45+
r := runestone.Runestone{Mint: runeId}
46+
data, err := r.Encipher()
47+
if err != nil {
48+
fmt.Println(err)
49+
}
50+
fmt.Printf("Mint Rune[%s] data: 0x%x\n", runeIdStr, data)
51+
dataString, _ := txscript.DisasmString(data)
52+
fmt.Printf("Mint Script: %s\n", dataString)
53+
}
54+
func testDecode() {
55+
data, _ := hex.DecodeString("140114001600") //Mint UNCOMMON•GOODS
56+
var tx wire.MsgTx
57+
builder := txscript.NewScriptBuilder()
58+
// Push opcode OP_RETURN
59+
builder.AddOp(txscript.OP_RETURN)
60+
// Push MAGIC_NUMBER
61+
builder.AddOp(runestone.MAGIC_NUMBER)
62+
// Push payload
63+
builder.AddData(data)
64+
pkScript, _ := builder.Script()
65+
txOut := wire.NewTxOut(0, pkScript)
66+
tx.AddTxOut(txOut)
67+
r := &runestone.Runestone{}
68+
artifact, err := r.Decipher(&tx)
69+
if err != nil {
70+
fmt.Println(err)
71+
return
72+
}
73+
a, _ := json.Marshal(artifact)
74+
fmt.Printf("Artifact: %s\n", string(a))
75+
}

0 commit comments

Comments
 (0)