Files
gaze-indexer/modules/runes/runes/rune.go
2024-08-28 23:34:54 +07:00

184 lines
6.5 KiB
Go

package runes
import (
"slices"
"github.com/Cleverse/go-utilities/utils"
"github.com/cockroachdb/errors"
"github.com/gaze-network/indexer-network/common"
"github.com/gaze-network/uint128"
)
type Rune uint128.Uint128
func (r Rune) Uint128() uint128.Uint128 {
return uint128.Uint128(r)
}
func NewRune(value uint64) Rune {
return Rune(uint128.From64(value))
}
func NewRuneFromUint128(value uint128.Uint128) Rune {
return Rune(value)
}
var ErrInvalidBase26 = errors.New("invalid base-26 character: must be in the range [A-Z]")
// NewRuneFromString creates a new Rune from a string of modified base-26 integer
func NewRuneFromString(value string) (Rune, error) {
n := uint128.From64(0)
for i, char := range value {
// skip spacers
if char == '.' || char == '•' {
continue
}
if i > 0 {
n = n.Add(uint128.From64(1))
}
n = n.Mul(uint128.From64(26))
if char < 'A' || char > 'Z' {
return Rune{}, ErrInvalidBase26
}
n = n.Add(uint128.From64(uint64(char - 'A')))
}
return Rune(n), nil
}
var firstReservedRune = NewRuneFromUint128(utils.Must(uint128.FromString("6402364363415443603228541259936211926")))
var unlockSteps = []uint128.Uint128{
utils.Must(uint128.FromString("0")), // A
utils.Must(uint128.FromString("26")), // AA
utils.Must(uint128.FromString("702")), // AAA
utils.Must(uint128.FromString("18278")), // AAAA
utils.Must(uint128.FromString("475254")), // AAAAA
utils.Must(uint128.FromString("12356630")), // AAAAAA
utils.Must(uint128.FromString("321272406")), // AAAAAAA
utils.Must(uint128.FromString("8353082582")), // AAAAAAAA
utils.Must(uint128.FromString("217180147158")), // AAAAAAAAA
utils.Must(uint128.FromString("5646683826134")), // AAAAAAAAAA
utils.Must(uint128.FromString("146813779479510")), // AAAAAAAAAAA
utils.Must(uint128.FromString("3817158266467286")), // AAAAAAAAAAAA
utils.Must(uint128.FromString("99246114928149462")), // AAAAAAAAAAAAA
utils.Must(uint128.FromString("2580398988131886038")), // AAAAAAAAAAAAAA
utils.Must(uint128.FromString("67090373691429037014")), // AAAAAAAAAAAAAAA
utils.Must(uint128.FromString("1744349715977154962390")), // AAAAAAAAAAAAAAAA
utils.Must(uint128.FromString("45353092615406029022166")), // AAAAAAAAAAAAAAAAA
utils.Must(uint128.FromString("1179180408000556754576342")), // AAAAAAAAAAAAAAAAAA
utils.Must(uint128.FromString("30658690608014475618984918")), // AAAAAAAAAAAAAAAAAAA
utils.Must(uint128.FromString("797125955808376366093607894")), // AAAAAAAAAAAAAAAAAAAA
utils.Must(uint128.FromString("20725274851017785518433805270")), // AAAAAAAAAAAAAAAAAAAAA
utils.Must(uint128.FromString("538857146126462423479278937046")), // AAAAAAAAAAAAAAAAAAAAAA
utils.Must(uint128.FromString("14010285799288023010461252363222")), // AAAAAAAAAAAAAAAAAAAAAAA
utils.Must(uint128.FromString("364267430781488598271992561443798")), // AAAAAAAAAAAAAAAAAAAAAAAA
utils.Must(uint128.FromString("9470953200318703555071806597538774")), // AAAAAAAAAAAAAAAAAAAAAAAAA
utils.Must(uint128.FromString("246244783208286292431866971536008150")), // AAAAAAAAAAAAAAAAAAAAAAAAAA
utils.Must(uint128.FromString("6402364363415443603228541259936211926")), // AAAAAAAAAAAAAAAAAAAAAAAAAAA
utils.Must(uint128.FromString("166461473448801533683942072758341510102")), // AAAAAAAAAAAAAAAAAAAAAAAAAAAA
}
func (r Rune) IsReserved() bool {
return r.Uint128().Cmp(firstReservedRune.Uint128()) >= 0
}
// Commitment returns the commitment of the rune. The commitment is the little-endian encoding of the rune.
func (r Rune) Commitment() []byte {
bytes := make([]byte, 16)
r.Uint128().PutBytes(bytes)
end := len(bytes)
for end > 0 && bytes[end-1] == 0 {
end--
}
return bytes[:end]
}
// String returns the string representation of the rune in modified base-26 integer
func (r Rune) String() string {
if r.Uint128() == uint128.Max {
return "BCGDENLQRQWDSLRUGSNLBTMFIJAV"
}
chars := "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
value := r.Uint128().Add64(1)
var encoded []byte
for !value.IsZero() {
idx := value.Sub64(1).Mod64(26)
encoded = append(encoded, chars[idx])
value = value.Sub64(1).Div64(26)
}
slices.Reverse(encoded)
return string(encoded)
}
func (r Rune) Cmp(other Rune) int {
return r.Uint128().Cmp(other.Uint128())
}
func FirstRuneHeight(network common.Network) uint64 {
switch network {
case common.NetworkMainnet:
return common.HalvingInterval * 4
case common.NetworkTestnet:
return common.HalvingInterval * 12
case common.NetworkFractalMainnet:
return 84000
case common.NetworkFractalTestnet:
return 84000
}
panic("invalid network")
}
func MinimumRuneAtHeight(network common.Network, height uint64) Rune {
offset := height + 1
interval := common.HalvingInterval / 12
// runes are gradually unlocked from rune activation height until the next halving
start := FirstRuneHeight(network)
end := start + common.HalvingInterval
if offset < start {
return (Rune)(unlockSteps[12])
}
if offset >= end {
return (Rune)(unlockSteps[0])
}
progress := offset - start
length := 12 - progress/uint64(interval)
startRune := unlockSteps[length]
endRune := unlockSteps[length-1] // length cannot be 0 because we checked that offset < end
remainder := progress % uint64(interval)
// result = startRune - ((startRune - endRune) * remainder / interval)
result := startRune.Sub(startRune.Sub(endRune).Mul64(remainder).Div64(uint64(interval)))
return Rune(result)
}
func GetReservedRune(blockHeight uint64, txIndex uint32) Rune {
// firstReservedRune + ((blockHeight << 32) | txIndex)
delta := uint128.From64(blockHeight).Lsh(32).Or64(uint64(txIndex))
return Rune(firstReservedRune.Uint128().Add(delta))
}
// MarshalJSON implements json.Marshaler
func (r Rune) MarshalJSON() ([]byte, error) {
return []byte(`"` + r.String() + `"`), nil
}
// UnmarshalJSON implements json.Unmarshaler
func (r *Rune) UnmarshalJSON(data []byte) error {
// data must be quoted
if len(data) < 2 || data[0] != '"' || data[len(data)-1] != '"' {
return errors.New("must be string")
}
data = data[1 : len(data)-1]
parsed, err := NewRuneFromString(string(data))
if err != nil {
return errors.WithStack(err)
}
*r = parsed
return nil
}