mirror of
https://github.com/alexgo-io/gaze-indexer.git
synced 2026-01-13 08:40:30 +08:00
Compare commits
90 Commits
feature/br
...
alex-main
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
11f1e8ac2f | ||
|
|
58f8497997 | ||
|
|
920f7fe07b | ||
|
|
0cb66232ef | ||
|
|
4074548b3e | ||
|
|
c5c9a7bdeb | ||
|
|
58334dd3e4 | ||
|
|
cffe378beb | ||
|
|
9a7ee49228 | ||
|
|
9739f61067 | ||
|
|
f1267b387e | ||
|
|
8883c24c77 | ||
|
|
e9ce8df01a | ||
|
|
3ff73a99f8 | ||
|
|
96afdfd255 | ||
|
|
c49e39be97 | ||
|
|
12985ae432 | ||
|
|
2d51e52b83 | ||
|
|
618220d0cb | ||
|
|
6004744721 | ||
|
|
90ed7bc350 | ||
|
|
7a0fe84e40 | ||
|
|
f1d4651042 | ||
|
|
5f4f50a9e5 | ||
|
|
32c3c5c1d4 | ||
|
|
2a572e6d1e | ||
|
|
aa25a6882b | ||
|
|
6182c63150 | ||
|
|
e1f8eaa3e1 | ||
|
|
107836ae39 | ||
|
|
1bd84b0154 | ||
|
|
de26a4c21d | ||
|
|
1dc57d74e0 | ||
|
|
7c0e28d8ea | ||
|
|
754fd1e997 | ||
|
|
66f03f7107 | ||
|
|
7a863987ec | ||
|
|
f9c6ef8dfd | ||
|
|
22a32468ef | ||
|
|
b1d9f4f574 | ||
|
|
6a5ba528a8 | ||
|
|
6484887710 | ||
|
|
9a1382fb9f | ||
|
|
3d5f3b414c | ||
|
|
6e8a846c27 | ||
|
|
8b690c4f7f | ||
|
|
cc37807ff9 | ||
|
|
9ab16d21e1 | ||
|
|
32fec89914 | ||
|
|
0131de6717 | ||
|
|
206eb65ee7 | ||
|
|
fa810b0aed | ||
|
|
dca63a49fe | ||
|
|
05ade4b9d5 | ||
|
|
074458584b | ||
|
|
db5dc75c41 | ||
|
|
0474627336 | ||
|
|
359436e6eb | ||
|
|
1967895d6d | ||
|
|
7dcbd082ee | ||
|
|
880f4b2e6a | ||
|
|
3f727dc11b | ||
|
|
60717ecc65 | ||
|
|
6998adedb0 | ||
|
|
add0a541b5 | ||
|
|
dad02bf61a | ||
|
|
694baef0aa | ||
|
|
47119c3220 | ||
|
|
6203b104db | ||
|
|
b24f27ec9a | ||
|
|
90f1fd0a6c | ||
|
|
aace33b382 | ||
|
|
a663f909fa | ||
|
|
0263ec5622 | ||
|
|
8760baf42b | ||
|
|
5aca9f7f19 | ||
|
|
07aa84019f | ||
|
|
a5fc803371 | ||
|
|
72ca151fd3 | ||
|
|
53a4d1a4c3 | ||
|
|
3322f4a034 | ||
|
|
dcb220bddb | ||
|
|
b6ff7e41bd | ||
|
|
7cb717af11 | ||
|
|
0d1ae0ef5e | ||
|
|
81ba7792ea | ||
|
|
b5851a39ab | ||
|
|
b44fb870a3 | ||
|
|
373ea50319 | ||
|
|
a1d7524615 |
3
.github/workflows/code-analysis.yml
vendored
3
.github/workflows/code-analysis.yml
vendored
@@ -58,6 +58,9 @@ jobs:
|
||||
cache: true # caching and restoring go modules and build outputs.
|
||||
- run: echo "GOVERSION=$(go version)" >> $GITHUB_ENV
|
||||
|
||||
- name: Touch test result file
|
||||
run: echo "" > test_output.json
|
||||
|
||||
- name: Build
|
||||
run: go build -v ./...
|
||||
|
||||
|
||||
@@ -7,7 +7,7 @@ on:
|
||||
- "v**" # e.g. v1.0.0
|
||||
|
||||
env:
|
||||
IMAGE_REPO: ghcr.io/gaze-network/gaze-indexer
|
||||
IMAGE_REPO: ghcr.io/alexgo-io/gaze-indexer
|
||||
|
||||
jobs:
|
||||
prepare:
|
||||
@@ -33,5 +33,5 @@ jobs:
|
||||
with:
|
||||
context: .
|
||||
dockerfile: Dockerfile
|
||||
image-repo: "ghcr.io/gaze-network/gaze-indexer"
|
||||
image-repo: "ghcr.io/alexgo-io/gaze-indexer"
|
||||
image-tag: ${{ needs.prepare.outputs.tag }}
|
||||
2
.github/workflows/sqlc-verify.yml
vendored
2
.github/workflows/sqlc-verify.yml
vendored
@@ -22,7 +22,7 @@ jobs:
|
||||
- name: Setup Sqlc
|
||||
uses: sqlc-dev/setup-sqlc@v4
|
||||
with:
|
||||
sqlc-version: "1.26.0"
|
||||
sqlc-version: "1.27.0"
|
||||
|
||||
- name: Check Diff
|
||||
run: sqlc diff
|
||||
|
||||
@@ -101,3 +101,6 @@ linters-settings:
|
||||
attr-only: true
|
||||
key-naming-case: snake
|
||||
args-on-sep-lines: true
|
||||
gosec:
|
||||
excludes:
|
||||
- G115
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
FROM golang:1.22 as builder
|
||||
FROM golang:1.24.2 as builder
|
||||
|
||||
WORKDIR /app
|
||||
|
||||
|
||||
@@ -1,8 +1,10 @@
|
||||
<!-- omit from toc -->
|
||||
|
||||
- [Türkçe](https://github.com/Rumeyst/gaze-indexer/blob/turkish-translation/docs/README_tr.md)
|
||||
|
||||
# Gaze Indexer
|
||||
|
||||
Gaze Indexer is an open-source and modular indexing client for Bitcoin meta-protocols. It has support for Runes out of the box, with **Unified Consistent APIs** across fungible token protocols.
|
||||
Gaze Indexer is an open-source and modular indexing client for Bitcoin meta-protocols with **Unified Consistent APIs** across fungible token protocols.
|
||||
|
||||
Gaze Indexer is built with **modularity** in mind, allowing users to run all modules in one monolithic instance with a single command, or as a distributed cluster of micro-services.
|
||||
|
||||
|
||||
@@ -17,12 +17,12 @@ import (
|
||||
"github.com/gaze-network/indexer-network/common/errs"
|
||||
"github.com/gaze-network/indexer-network/core/indexer"
|
||||
"github.com/gaze-network/indexer-network/internal/config"
|
||||
"github.com/gaze-network/indexer-network/modules/brc20"
|
||||
"github.com/gaze-network/indexer-network/modules/nodesale"
|
||||
"github.com/gaze-network/indexer-network/modules/runes"
|
||||
"github.com/gaze-network/indexer-network/pkg/automaxprocs"
|
||||
"github.com/gaze-network/indexer-network/pkg/errorhandler"
|
||||
"github.com/gaze-network/indexer-network/pkg/logger"
|
||||
"github.com/gaze-network/indexer-network/pkg/logger/slogx"
|
||||
"github.com/gaze-network/indexer-network/pkg/middleware/errorhandler"
|
||||
"github.com/gaze-network/indexer-network/pkg/middleware/requestcontext"
|
||||
"github.com/gaze-network/indexer-network/pkg/middleware/requestlogger"
|
||||
"github.com/gaze-network/indexer-network/pkg/reportingclient"
|
||||
@@ -40,7 +40,7 @@ import (
|
||||
// Register Modules
|
||||
var Modules = do.Package(
|
||||
do.LazyNamed("runes", runes.New),
|
||||
do.LazyNamed("brc20", brc20.New),
|
||||
do.LazyNamed("nodesale", nodesale.New),
|
||||
)
|
||||
|
||||
func NewRunCommand() *cobra.Command {
|
||||
@@ -138,8 +138,16 @@ func runHandler(cmd *cobra.Command, _ []string) error {
|
||||
// Initialize HTTP server
|
||||
do.Provide(injector, func(i do.Injector) (*fiber.App, error) {
|
||||
app := fiber.New(fiber.Config{
|
||||
AppName: "Gaze Indexer",
|
||||
ErrorHandler: errorhandler.NewHTTPErrorHandler(),
|
||||
AppName: "Gaze Indexer",
|
||||
ErrorHandler: func(c *fiber.Ctx, err error) error {
|
||||
logger.ErrorContext(c.UserContext(), "Something went wrong, unhandled api error",
|
||||
slogx.String("event", "api_unhandled_error"),
|
||||
slogx.Error(err),
|
||||
)
|
||||
return errors.WithStack(c.Status(http.StatusInternalServerError).JSON(fiber.Map{
|
||||
"error": "Internal Server Error",
|
||||
}))
|
||||
},
|
||||
})
|
||||
app.
|
||||
Use(favicon.New()).
|
||||
@@ -158,6 +166,7 @@ func runHandler(cmd *cobra.Command, _ []string) error {
|
||||
logger.ErrorContext(c.UserContext(), "Something went wrong, panic in http handler", slogx.Any("panic", e), slog.String("stacktrace", string(buf)))
|
||||
},
|
||||
})).
|
||||
Use(errorhandler.New()).
|
||||
Use(compress.New(compress.Config{
|
||||
Level: compress.LevelDefault,
|
||||
}))
|
||||
|
||||
@@ -6,13 +6,15 @@ import (
|
||||
"github.com/cockroachdb/errors"
|
||||
"github.com/gaze-network/indexer-network/common/errs"
|
||||
"github.com/gaze-network/indexer-network/core/constants"
|
||||
"github.com/gaze-network/indexer-network/modules/runes"
|
||||
"github.com/gaze-network/indexer-network/modules/nodesale"
|
||||
runesconstants "github.com/gaze-network/indexer-network/modules/runes/constants"
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
var versions = map[string]string{
|
||||
"": constants.Version,
|
||||
"runes": runes.Version,
|
||||
"": constants.Version,
|
||||
"runes": runesconstants.Version,
|
||||
"nodesale": nodesale.Version,
|
||||
}
|
||||
|
||||
type versionCmdOptions struct {
|
||||
|
||||
@@ -17,7 +17,7 @@ import (
|
||||
|
||||
type migrateDownCmdOptions struct {
|
||||
DatabaseURL string
|
||||
Modules string
|
||||
Runes bool
|
||||
All bool
|
||||
}
|
||||
|
||||
@@ -59,7 +59,7 @@ func NewMigrateDownCommand() *cobra.Command {
|
||||
}
|
||||
|
||||
flags := cmd.Flags()
|
||||
flags.StringVar(&opts.Modules, "modules", "", "Modules to apply up migrations")
|
||||
flags.BoolVar(&opts.Runes, "runes", false, "Apply Runes down migrations")
|
||||
flags.StringVar(&opts.DatabaseURL, "database", "", "Database url to run migration on")
|
||||
flags.BoolVar(&opts.All, "all", false, "Confirm apply ALL down migrations without prompt")
|
||||
|
||||
@@ -87,8 +87,6 @@ func migrateDownHandler(opts *migrateDownCmdOptions, _ *cobra.Command, args migr
|
||||
}
|
||||
}
|
||||
|
||||
modules := strings.Split(opts.Modules, ",")
|
||||
|
||||
applyDownMigrations := func(module string, sourcePath string, migrationTable string) error {
|
||||
newDatabaseURL := cloneURLWithQuery(databaseURL, url.Values{"x-migrations-table": {migrationTable}})
|
||||
sourceURL := "file://" + sourcePath
|
||||
@@ -118,15 +116,10 @@ func migrateDownHandler(opts *migrateDownCmdOptions, _ *cobra.Command, args migr
|
||||
return nil
|
||||
}
|
||||
|
||||
if lo.Contains(modules, "runes") {
|
||||
if opts.Runes {
|
||||
if err := applyDownMigrations("Runes", runesMigrationSource, "runes_schema_migrations"); err != nil {
|
||||
return errors.WithStack(err)
|
||||
}
|
||||
}
|
||||
if lo.Contains(modules, "brc20") {
|
||||
if err := applyDownMigrations("BRC20", brc20MigrationSource, "brc20_schema_migrations"); err != nil {
|
||||
return errors.WithStack(err)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -11,13 +11,12 @@ import (
|
||||
"github.com/golang-migrate/migrate/v4"
|
||||
_ "github.com/golang-migrate/migrate/v4/database/postgres"
|
||||
_ "github.com/golang-migrate/migrate/v4/source/file"
|
||||
"github.com/samber/lo"
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
type migrateUpCmdOptions struct {
|
||||
DatabaseURL string
|
||||
Modules string
|
||||
Runes bool
|
||||
}
|
||||
|
||||
type migrateUpCmdArgs struct {
|
||||
@@ -55,7 +54,7 @@ func NewMigrateUpCommand() *cobra.Command {
|
||||
}
|
||||
|
||||
flags := cmd.Flags()
|
||||
flags.StringVar(&opts.Modules, "modules", "", "Modules to apply up migrations")
|
||||
flags.BoolVar(&opts.Runes, "runes", false, "Apply Runes up migrations")
|
||||
flags.StringVar(&opts.DatabaseURL, "database", "", "Database url to run migration on")
|
||||
|
||||
return cmd
|
||||
@@ -73,8 +72,6 @@ func migrateUpHandler(opts *migrateUpCmdOptions, _ *cobra.Command, args migrateU
|
||||
return errors.Errorf("unsupported database driver: %s", databaseURL.Scheme)
|
||||
}
|
||||
|
||||
modules := strings.Split(opts.Modules, ",")
|
||||
|
||||
applyUpMigrations := func(module string, sourcePath string, migrationTable string) error {
|
||||
newDatabaseURL := cloneURLWithQuery(databaseURL, url.Values{"x-migrations-table": {migrationTable}})
|
||||
sourceURL := "file://" + sourcePath
|
||||
@@ -104,15 +101,10 @@ func migrateUpHandler(opts *migrateUpCmdOptions, _ *cobra.Command, args migrateU
|
||||
return nil
|
||||
}
|
||||
|
||||
if lo.Contains(modules, "runes") {
|
||||
if opts.Runes {
|
||||
if err := applyUpMigrations("Runes", runesMigrationSource, "runes_schema_migrations"); err != nil {
|
||||
return errors.WithStack(err)
|
||||
}
|
||||
}
|
||||
if lo.Contains(modules, "brc20") {
|
||||
if err := applyUpMigrations("BRC20", brc20MigrationSource, "brc20_schema_migrations"); err != nil {
|
||||
return errors.WithStack(err)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -4,7 +4,6 @@ import "net/url"
|
||||
|
||||
const (
|
||||
runesMigrationSource = "modules/runes/database/postgresql/migrations"
|
||||
brc20MigrationSource = "modules/brc20/database/postgresql/migrations"
|
||||
)
|
||||
|
||||
func cloneURLWithQuery(u *url.URL, newQuery url.Values) *url.URL {
|
||||
|
||||
@@ -1,4 +0,0 @@
|
||||
package common
|
||||
|
||||
// HalvingInterval is the number of blocks between each halving event.
|
||||
const HalvingInterval = 210_000
|
||||
@@ -24,6 +24,9 @@ var (
|
||||
// Skippable is returned when got an error but it can be skipped or ignored and continue
|
||||
Skippable = errors.NewWithDepth(depth, "skippable")
|
||||
|
||||
// Retryable is returned when got an error but it can be retried
|
||||
Retryable = errors.NewWithDepth(depth, "retryable")
|
||||
|
||||
// Unsupported is returned when a feature or result is not supported
|
||||
Unsupported = errors.NewWithDepth(depth, "unsupported")
|
||||
|
||||
|
||||
@@ -11,6 +11,7 @@ import (
|
||||
type PublicError struct {
|
||||
err error
|
||||
message string
|
||||
code string // code is optional, it can be used to identify the error type
|
||||
}
|
||||
|
||||
func (p PublicError) Error() string {
|
||||
@@ -21,6 +22,10 @@ func (p PublicError) Message() string {
|
||||
return p.message
|
||||
}
|
||||
|
||||
func (p PublicError) Code() string {
|
||||
return p.code
|
||||
}
|
||||
|
||||
func (p PublicError) Unwrap() error {
|
||||
return p.err
|
||||
}
|
||||
@@ -29,6 +34,10 @@ func NewPublicError(message string) error {
|
||||
return withstack.WithStackDepth(&PublicError{err: errors.New(message), message: message}, 1)
|
||||
}
|
||||
|
||||
func NewPublicErrorWithCode(message string, code string) error {
|
||||
return withstack.WithStackDepth(&PublicError{err: errors.New(message), message: message, code: code}, 1)
|
||||
}
|
||||
|
||||
func WithPublicMessage(err error, prefix string) error {
|
||||
if err == nil {
|
||||
return nil
|
||||
@@ -41,3 +50,16 @@ func WithPublicMessage(err error, prefix string) error {
|
||||
}
|
||||
return withstack.WithStackDepth(&PublicError{err: err, message: message}, 1)
|
||||
}
|
||||
|
||||
func WithPublicMessageCode(err error, prefix string, code string) error {
|
||||
if err == nil {
|
||||
return nil
|
||||
}
|
||||
var message string
|
||||
if prefix != "" {
|
||||
message = fmt.Sprintf("%s: %s", prefix, err.Error())
|
||||
} else {
|
||||
message = err.Error()
|
||||
}
|
||||
return withstack.WithStackDepth(&PublicError{err: err, message: message, code: code}, 1)
|
||||
}
|
||||
|
||||
@@ -1,6 +0,0 @@
|
||||
package common
|
||||
|
||||
type HttpResponse[T any] struct {
|
||||
Error *string `json:"error"`
|
||||
Result *T `json:"result,omitempty"`
|
||||
}
|
||||
@@ -1,22 +1,31 @@
|
||||
package common
|
||||
|
||||
import "github.com/btcsuite/btcd/chaincfg"
|
||||
import (
|
||||
"github.com/btcsuite/btcd/chaincfg"
|
||||
"github.com/gaze-network/indexer-network/pkg/logger"
|
||||
)
|
||||
|
||||
type Network string
|
||||
|
||||
const (
|
||||
NetworkMainnet Network = "mainnet"
|
||||
NetworkTestnet Network = "testnet"
|
||||
NetworkMainnet Network = "mainnet"
|
||||
NetworkTestnet Network = "testnet"
|
||||
NetworkFractalMainnet Network = "fractal-mainnet"
|
||||
NetworkFractalTestnet Network = "fractal-testnet"
|
||||
)
|
||||
|
||||
var supportedNetworks = map[Network]struct{}{
|
||||
NetworkMainnet: {},
|
||||
NetworkTestnet: {},
|
||||
NetworkMainnet: {},
|
||||
NetworkTestnet: {},
|
||||
NetworkFractalMainnet: {},
|
||||
NetworkFractalTestnet: {},
|
||||
}
|
||||
|
||||
var chainParams = map[Network]*chaincfg.Params{
|
||||
NetworkMainnet: &chaincfg.MainNetParams,
|
||||
NetworkTestnet: &chaincfg.TestNet3Params,
|
||||
NetworkMainnet: &chaincfg.MainNetParams,
|
||||
NetworkTestnet: &chaincfg.TestNet3Params,
|
||||
NetworkFractalMainnet: &chaincfg.MainNetParams,
|
||||
NetworkFractalTestnet: &chaincfg.MainNetParams,
|
||||
}
|
||||
|
||||
func (n Network) IsSupported() bool {
|
||||
@@ -31,3 +40,15 @@ func (n Network) ChainParams() *chaincfg.Params {
|
||||
func (n Network) String() string {
|
||||
return string(n)
|
||||
}
|
||||
|
||||
func (n Network) HalvingInterval() uint64 {
|
||||
switch n {
|
||||
case NetworkMainnet, NetworkTestnet:
|
||||
return 210_000
|
||||
case NetworkFractalMainnet, NetworkFractalTestnet:
|
||||
return 2_100_000
|
||||
default:
|
||||
logger.Panic("invalid network")
|
||||
return 0
|
||||
}
|
||||
}
|
||||
|
||||
@@ -34,19 +34,6 @@ http_server:
|
||||
|
||||
# Meta-protocol modules configuration options.
|
||||
modules:
|
||||
# Configuration options for BRC20 module. Can be removed if not used.
|
||||
brc20:
|
||||
database: "postgres" # Database to store BRC20 data. current supported databases: "postgres"
|
||||
datasource: "database" # Data source to be used for Bitcoin data. current supported data sources: "bitcoin-node".
|
||||
api_handlers: # API handlers to enable. current supported handlers: "http"
|
||||
- http
|
||||
postgres:
|
||||
host: "localhost"
|
||||
port: 5432
|
||||
user: "postgres"
|
||||
password: "password"
|
||||
db_name: "postgres"
|
||||
# url: "postgres://postgres:password@localhost:5432/postgres?sslmode=prefer" # [Optional] This will override other database credentials above.
|
||||
# Configuration options for Runes module. Can be removed if not used.
|
||||
runes:
|
||||
database: "postgres" # Database to store Runes data. current supported databases: "postgres"
|
||||
@@ -60,3 +47,11 @@ modules:
|
||||
password: "password"
|
||||
db_name: "postgres"
|
||||
# url: "postgres://postgres:password@localhost:5432/postgres?sslmode=prefer" # [Optional] This will override other database credentials above.
|
||||
nodesale:
|
||||
postgres:
|
||||
host: "localhost"
|
||||
port: 5432
|
||||
user: "postgres"
|
||||
password: "P@ssw0rd"
|
||||
db_name: "postgres"
|
||||
last_block_default: 400
|
||||
@@ -243,39 +243,32 @@ func (d *BitcoinNodeDatasource) prepareRange(fromHeight, toHeight int64) (start,
|
||||
}
|
||||
|
||||
// GetTransaction fetch transaction from Bitcoin node
|
||||
func (d *BitcoinNodeDatasource) GetTransactionByHash(ctx context.Context, txHash chainhash.Hash) (*types.Transaction, error) {
|
||||
func (d *BitcoinNodeDatasource) GetRawTransactionAndHeightByTxHash(ctx context.Context, txHash chainhash.Hash) (*wire.MsgTx, int64, error) {
|
||||
rawTxVerbose, err := d.btcclient.GetRawTransactionVerbose(&txHash)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "failed to get raw transaction")
|
||||
return nil, 0, errors.Wrap(err, "failed to get raw transaction")
|
||||
}
|
||||
|
||||
blockHash, err := chainhash.NewHashFromStr(rawTxVerbose.BlockHash)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "failed to parse block hash")
|
||||
return nil, 0, errors.Wrap(err, "failed to parse block hash")
|
||||
}
|
||||
block, err := d.btcclient.GetBlockVerboseTx(blockHash)
|
||||
block, err := d.btcclient.GetBlockVerbose(blockHash)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "failed to get block header")
|
||||
return nil, 0, errors.Wrap(err, "failed to get block header")
|
||||
}
|
||||
|
||||
// parse tx
|
||||
txBytes, err := hex.DecodeString(rawTxVerbose.Hex)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "failed to decode transaction hex")
|
||||
return nil, 0, errors.Wrap(err, "failed to decode transaction hex")
|
||||
}
|
||||
var msgTx wire.MsgTx
|
||||
if err := msgTx.Deserialize(bytes.NewReader(txBytes)); err != nil {
|
||||
return nil, errors.Wrap(err, "failed to deserialize transaction")
|
||||
}
|
||||
var txIndex uint32
|
||||
for i, tx := range block.Tx {
|
||||
if tx.Hex == rawTxVerbose.Hex {
|
||||
txIndex = uint32(i)
|
||||
break
|
||||
}
|
||||
return nil, 0, errors.Wrap(err, "failed to deserialize transaction")
|
||||
}
|
||||
|
||||
return types.ParseMsgTx(&msgTx, block.Height, *blockHash, txIndex), nil
|
||||
return &msgTx, block.Height, nil
|
||||
}
|
||||
|
||||
// GetBlockHeader fetch block header from Bitcoin node
|
||||
@@ -293,18 +286,11 @@ func (d *BitcoinNodeDatasource) GetBlockHeader(ctx context.Context, height int64
|
||||
return types.ParseMsgBlockHeader(*block, height), nil
|
||||
}
|
||||
|
||||
// GetTransaction fetch transaction from Bitcoin node
|
||||
func (d *BitcoinNodeDatasource) GetTransactionOutputs(ctx context.Context, txHash chainhash.Hash) ([]*types.TxOut, error) {
|
||||
rawTx, err := d.btcclient.GetRawTransaction(&txHash)
|
||||
func (d *BitcoinNodeDatasource) GetRawTransactionByTxHash(ctx context.Context, txHash chainhash.Hash) (*wire.MsgTx, error) {
|
||||
transaction, err := d.btcclient.GetRawTransaction(&txHash)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "failed to get raw transaction")
|
||||
}
|
||||
|
||||
msgTx := rawTx.MsgTx()
|
||||
txOuts := make([]*types.TxOut, 0, len(msgTx.TxOut))
|
||||
for _, txOut := range msgTx.TxOut {
|
||||
txOuts = append(txOuts, types.ParseTxOut(txOut))
|
||||
}
|
||||
|
||||
return txOuts, nil
|
||||
return transaction.MsgTx(), nil
|
||||
}
|
||||
|
||||
@@ -6,6 +6,7 @@ import (
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/btcsuite/btcd/chaincfg/chainhash"
|
||||
"github.com/cockroachdb/errors"
|
||||
"github.com/gaze-network/indexer-network/common/errs"
|
||||
"github.com/gaze-network/indexer-network/core/datasources"
|
||||
@@ -142,7 +143,7 @@ func (i *Indexer[T]) process(ctx context.Context) (err error) {
|
||||
// validate reorg from first input
|
||||
{
|
||||
remoteBlockHeader := firstInputHeader
|
||||
if !remoteBlockHeader.PrevBlock.IsEqual(&i.currentBlock.Hash) {
|
||||
if i.currentBlock.Hash != (chainhash.Hash{}) && !remoteBlockHeader.PrevBlock.IsEqual(&i.currentBlock.Hash) {
|
||||
logger.WarnContext(ctx, "Detected chain reorganization. Searching for fork point...",
|
||||
slogx.String("event", "reorg_detected"),
|
||||
slogx.Stringer("current_hash", i.currentBlock.Hash),
|
||||
@@ -215,7 +216,7 @@ func (i *Indexer[T]) process(ctx context.Context) (err error) {
|
||||
return errors.Wrapf(errs.InternalError, "input is not continuous, input[%d] height: %d, input[%d] height: %d", i-1, prevHeader.Height, i, header.Height)
|
||||
}
|
||||
|
||||
if !header.PrevBlock.IsEqual(&prevHeader.Hash) {
|
||||
if prevHeader.Hash != (chainhash.Hash{}) && !header.PrevBlock.IsEqual(&prevHeader.Hash) {
|
||||
logger.WarnContext(ctx, "Chain Reorganization occurred in the middle of batch fetching inputs, need to try to fetch again")
|
||||
|
||||
// end current round
|
||||
|
||||
165
docs/README_tr.md
Normal file
165
docs/README_tr.md
Normal file
@@ -0,0 +1,165 @@
|
||||
## Çeviriler
|
||||
- [English (İngilizce)](../README.md)
|
||||
|
||||
**Son Güncelleme:** 21 Ağustos 2024
|
||||
> **Not:** Bu belge, topluluk tarafından yapılmış bir çeviridir. Ana README.md dosyasındaki güncellemeler buraya otomatik olarak yansıtılmayabilir. En güncel bilgiler için [İngilizce sürümü](../README.md) inceleyin.
|
||||
|
||||
|
||||
# Gaze Indexer
|
||||
|
||||
Gaze Indexer, değiştirilebilir token protokolleri arasında **Birleştirilmiş Tutarlı API'lere** sahip Bitcoin meta-protokolleri için açık kaynaklı ve modüler bir indeksleme istemcisidir.
|
||||
|
||||
Gaze Indexer, kullanıcıların tüm modülleri tek bir komutla tek bir monolitik örnekte veya dağıtılmış bir mikro hizmet kümesi olarak çalıştırmasına olanak tanıyan **modülerlik** göz önünde bulundurularak oluşturulmuştur.
|
||||
|
||||
Gaze Indexer, verimli veri getirme, yeniden düzenleme algılama ve veritabanı taşıma aracı ile HERHANGİ bir meta-protokol indeksleyici oluşturmak için bir temel görevi görür.
|
||||
Bu, geliştiricilerin **gerçekten** önemli olana odaklanmasını sağlar: Meta-protokol indeksleme mantığı. Yeni meta-protokoller, yeni modüller uygulanarak kolayca eklenebilir.
|
||||
|
||||
- [Modüller](#modules)
|
||||
- [1. Runes](#1-runes)
|
||||
- [Kurulum](#installation)
|
||||
- [Önkoşullar](#prerequisites)
|
||||
- [1. Donanım Gereksinimleri](#1-hardware-requirements)
|
||||
- [2. Bitcoin Core RPC sunucusunu hazırlayın.](#2-prepare-bitcoin-core-rpc-server)
|
||||
- [3. Veritabanı hazırlayın.](#3-prepare-database)
|
||||
- [4. `config.yaml` dosyasını hazırlayın.](#4-prepare-configyaml-file)
|
||||
- [Docker ile yükle (önerilir)](#install-with-docker-recommended)
|
||||
- [Kaynaktan yükle](#install-from-source)
|
||||
|
||||
## Modüller
|
||||
|
||||
### 1. Runes
|
||||
|
||||
Runes Dizinleyici ilk meta-protokol dizinleyicimizdir. Bitcoin işlemlerini kullanarak Runes durumlarını, işlemlerini, rün taşlarını ve bakiyelerini indeksler.
|
||||
Geçmiş Runes verilerini sorgulamak için bir dizi API ile birlikte gelir. Tüm ayrıntılar için [API Referansı] (https://api-docs.gaze.network) adresimize bakın.
|
||||
|
||||
|
||||
## Kurulum
|
||||
|
||||
### Önkoşullar
|
||||
|
||||
#### 1. Donanım Gereksinimleri
|
||||
|
||||
Her modül farklı donanım gereksinimleri gerektirir.
|
||||
| Modül | CPU | RAM |
|
||||
| ------ | --------- | ---- |
|
||||
| Runes | 0,5 çekirdek | 1 GB |
|
||||
|
||||
#### 2. Bitcoin Core RPC sunucusunu hazırlayın.
|
||||
|
||||
Gaze Indexer'ın işlem verilerini kendi barındırdığı ya da QuickNode gibi yönetilen sağlayıcıları kullanan bir Bitcoin Core RPC'den alması gerekir.
|
||||
Bir Bitcoin Core'u kendiniz barındırmak için bkz. https://bitcoin.org/en/full-node.
|
||||
|
||||
#### 3. Veritabanını hazırlayın.
|
||||
|
||||
Gaze Indexer PostgreSQL için birinci sınıf desteğe sahiptir. Diğer veritabanlarını kullanmak isterseniz, her modülün Veri Ağ Geçidi arayüzünü karşılayan kendi veritabanı havuzunuzu uygulayabilirsiniz.
|
||||
İşte her modül için minimum veritabanı disk alanı gereksinimimiz.
|
||||
| Modül | Veritabanı Depolama Alanı (mevcut) | Veritabanı Depolama Alanı (1 yıl içinde) |
|
||||
| ------ | -------------------------- | ---------------------------- |
|
||||
| Runes | 10 GB | 150 GB |
|
||||
|
||||
#### 4. config.yaml` dosyasını hazırlayın.
|
||||
|
||||
```yaml
|
||||
# config.yaml
|
||||
logger:
|
||||
output: TEXT # Output format for logs. current supported formats: "TEXT" | "JSON" | "GCP"
|
||||
debug: false
|
||||
|
||||
# Network to run the indexer on. Current supported networks: "mainnet" | "testnet"
|
||||
network: mainnet
|
||||
|
||||
# Bitcoin Core RPC configuration options.
|
||||
bitcoin_node:
|
||||
host: "" # [Required] Host of Bitcoin Core RPC (without https://)
|
||||
user: "" # Username to authenticate with Bitcoin Core RPC
|
||||
pass: "" # Password to authenticate with Bitcoin Core RPC
|
||||
disable_tls: false # Set to true to disable tls
|
||||
|
||||
# Block reporting configuration options. See Block Reporting section for more details.
|
||||
reporting:
|
||||
disabled: false # Set to true to disable block reporting to Gaze Network. Default is false.
|
||||
base_url: "https://indexer.api.gaze.network" # Defaults to "https://indexer.api.gaze.network" if left empty
|
||||
name: "" # [Required if not disabled] Name of this indexer to show on the Gaze Network dashboard
|
||||
website_url: "" # Public website URL to show on the dashboard. Can be left empty.
|
||||
indexer_api_url: "" # Public url to access this indexer's API. Can be left empty if you want to keep your indexer private.
|
||||
|
||||
# HTTP server configuration options.
|
||||
http_server:
|
||||
port: 8080 # Port to run the HTTP server on for modules with HTTP API handlers.
|
||||
|
||||
# Meta-protocol modules configuration options.
|
||||
modules:
|
||||
# Configuration options for Runes module. Can be removed if not used.
|
||||
runes:
|
||||
database: "postgres" # Database to store Runes data. current supported databases: "postgres"
|
||||
datasource: "bitcoin-node" # Data source to be used for Bitcoin data. current supported data sources: "bitcoin-node".
|
||||
api_handlers: # API handlers to enable. current supported handlers: "http"
|
||||
- http
|
||||
postgres:
|
||||
host: "localhost"
|
||||
port: 5432
|
||||
user: "postgres"
|
||||
password: "password"
|
||||
db_name: "postgres"
|
||||
# url: "postgres://postgres:password@localhost:5432/postgres?sslmode=prefer" # [Optional] This will override other database credentials above.
|
||||
```
|
||||
|
||||
### Docker ile yükleyin (önerilir)
|
||||
|
||||
Kurulum kılavuzumuz için `docker-compose` kullanacağız. Docker-compose.yaml` dosyasının `config.yaml` dosyası ile aynı dizinde olduğundan emin olun.
|
||||
|
||||
```yaml
|
||||
# docker-compose.yaml
|
||||
services:
|
||||
gaze-indexer:
|
||||
image: ghcr.io/gaze-network/gaze-indexer:v0.2.1
|
||||
container_name: gaze-indexer
|
||||
restart: unless-stopped
|
||||
ports:
|
||||
- 8080:8080 # Expose HTTP server port to host
|
||||
volumes:
|
||||
- "./config.yaml:/app/config.yaml" # mount config.yaml file to the container as "/app/config.yaml"
|
||||
command: ["/app/main", "run", "--modules", "runes"] # Put module flags after "run" commands to select which modules to run.
|
||||
```
|
||||
|
||||
### Kaynaktan yükleyin
|
||||
|
||||
1. Go` sürüm 1.22 veya daha üstünü yükleyin. Go kurulum kılavuzuna bakın [burada](https://go.dev/doc/install).
|
||||
2. Bu depoyu klonlayın.
|
||||
|
||||
```bash
|
||||
git clone https://github.com/gaze-network/gaze-indexer.git
|
||||
cd gaze-indexer
|
||||
```
|
||||
|
||||
3. Ana ikili dosyayı oluşturun.
|
||||
|
||||
```bash
|
||||
# Bağımlılıkları al
|
||||
go mod indir
|
||||
|
||||
# Ana ikili dosyayı oluşturun
|
||||
go build -o gaze main.go
|
||||
```
|
||||
|
||||
4. Veritabanı geçişlerini `migrate` komutu ve modül bayrakları ile çalıştırın.
|
||||
|
||||
```bash
|
||||
./gaze migrate up --runes --database postgres://postgres:password@localhost:5432/postgres
|
||||
```
|
||||
|
||||
5. Dizinleyiciyi `run` komutu ve modül bayrakları ile başlatın.
|
||||
|
||||
```bash
|
||||
./gaze run --modules runes
|
||||
```
|
||||
|
||||
Eğer `config.yaml` dosyası `./app/config.yaml` adresinde bulunmuyorsa, `config.yaml` dosyasının yolunu belirtmek için `--config` bayrağını kullanın.
|
||||
|
||||
```bash
|
||||
./gaze run --modules runes --config /path/to/config.yaml
|
||||
```
|
||||
|
||||
|
||||
## Çeviriler
|
||||
- [English (İngilizce)](../README.md)
|
||||
9
go.mod
9
go.mod
@@ -12,7 +12,6 @@ require (
|
||||
github.com/gaze-network/uint128 v1.3.0
|
||||
github.com/gofiber/fiber/v2 v2.52.4
|
||||
github.com/golang-migrate/migrate/v4 v4.17.1
|
||||
github.com/hashicorp/golang-lru/v2 v2.0.7
|
||||
github.com/jackc/pgx/v5 v5.5.5
|
||||
github.com/mcosta74/pgx-slog v0.3.0
|
||||
github.com/planxnx/concurrent-stream v0.1.5
|
||||
@@ -26,12 +25,15 @@ require (
|
||||
github.com/valyala/fasthttp v1.51.0
|
||||
go.uber.org/automaxprocs v1.5.3
|
||||
golang.org/x/sync v0.7.0
|
||||
google.golang.org/protobuf v1.33.0
|
||||
)
|
||||
|
||||
require github.com/stretchr/objx v0.5.2 // indirect
|
||||
|
||||
require (
|
||||
github.com/andybalholm/brotli v1.0.5 // indirect
|
||||
github.com/bitonicnl/verify-signed-message v0.7.1
|
||||
github.com/btcsuite/btcd/btcec/v2 v2.3.3 // indirect
|
||||
github.com/btcsuite/btcd/btcec/v2 v2.3.3
|
||||
github.com/btcsuite/btclog v0.0.0-20170628155309-84c8d2346e9f // indirect
|
||||
github.com/btcsuite/go-socks v0.0.0-20170105172521-4720035b7bfd // indirect
|
||||
github.com/btcsuite/websocket v0.0.0-20150119174127-31079b680792 // indirect
|
||||
@@ -39,7 +41,7 @@ require (
|
||||
github.com/cockroachdb/redact v1.1.5 // indirect
|
||||
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect
|
||||
github.com/decred/dcrd/crypto/blake256 v1.0.1 // indirect
|
||||
github.com/decred/dcrd/dcrec/secp256k1/v4 v4.3.0 // indirect
|
||||
github.com/decred/dcrd/dcrec/secp256k1/v4 v4.3.0
|
||||
github.com/fsnotify/fsnotify v1.7.0 // indirect
|
||||
github.com/getsentry/sentry-go v0.18.0 // indirect
|
||||
github.com/gogo/protobuf v1.3.2 // indirect
|
||||
@@ -47,7 +49,6 @@ require (
|
||||
github.com/hashicorp/errwrap v1.1.0 // indirect
|
||||
github.com/hashicorp/go-multierror v1.1.1 // indirect
|
||||
github.com/hashicorp/hcl v1.0.0 // indirect
|
||||
github.com/holiman/uint256 v1.2.4 // indirect
|
||||
github.com/inconshreveable/mousetrap v1.1.0 // indirect
|
||||
github.com/jackc/pgpassfile v1.0.0 // indirect
|
||||
github.com/jackc/pgservicefile v0.0.0-20221227161230-091c0ba34f0a // indirect
|
||||
|
||||
8
go.sum
8
go.sum
@@ -115,12 +115,8 @@ github.com/hashicorp/errwrap v1.1.0 h1:OxrOeh75EUXMY8TBjag2fzXGZ40LB6IKw45YeGUDY
|
||||
github.com/hashicorp/errwrap v1.1.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4=
|
||||
github.com/hashicorp/go-multierror v1.1.1 h1:H5DkEtf6CXdFp0N0Em5UCwQpXMWke8IA0+lD48awMYo=
|
||||
github.com/hashicorp/go-multierror v1.1.1/go.mod h1:iw975J/qwKPdAO1clOe2L8331t/9/fmwbPZ6JB6eMoM=
|
||||
github.com/hashicorp/golang-lru/v2 v2.0.7 h1:a+bsQ5rvGLjzHuww6tVxozPZFVghXaHOwFs4luLUK2k=
|
||||
github.com/hashicorp/golang-lru/v2 v2.0.7/go.mod h1:QeFd9opnmA6QUJc5vARoKUSoFhyfM2/ZepoAG6RGpeM=
|
||||
github.com/hashicorp/hcl v1.0.0 h1:0Anlzjpi4vEasTeNFn2mLJgTSwt0+6sfsiTG8qcWGx4=
|
||||
github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ=
|
||||
github.com/holiman/uint256 v1.2.4 h1:jUc4Nk8fm9jZabQuqr2JzednajVmBpC+oiTiXZJEApU=
|
||||
github.com/holiman/uint256 v1.2.4/go.mod h1:EOMSn4q6Nyt9P6efbI3bueV4e1b3dGlUCXeiRV4ng7E=
|
||||
github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU=
|
||||
github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8=
|
||||
github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw=
|
||||
@@ -226,6 +222,8 @@ github.com/spf13/viper v1.18.2/go.mod h1:EKmWIqdnk5lOcmR72yw6hS+8OPYcwD0jteitLMV
|
||||
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||
github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
|
||||
github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
|
||||
github.com/stretchr/objx v0.5.2 h1:xuMeJ0Sdp5ZMRXx/aWO6RZxdr3beISkG5/G/aIRr3pY=
|
||||
github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA=
|
||||
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
|
||||
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||
@@ -316,6 +314,8 @@ google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQ
|
||||
google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE=
|
||||
google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo=
|
||||
google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
|
||||
google.golang.org/protobuf v1.33.0 h1:uNO2rsAINq/JlFpSdYEKIZ0uKD/R9cpdv0T+yoGwGmI=
|
||||
google.golang.org/protobuf v1.33.0/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos=
|
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
|
||||
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
|
||||
|
||||
@@ -8,7 +8,7 @@ import (
|
||||
|
||||
"github.com/cockroachdb/errors"
|
||||
"github.com/gaze-network/indexer-network/common"
|
||||
brc20config "github.com/gaze-network/indexer-network/modules/brc20/config"
|
||||
nodesaleconfig "github.com/gaze-network/indexer-network/modules/nodesale/config"
|
||||
runesconfig "github.com/gaze-network/indexer-network/modules/runes/config"
|
||||
"github.com/gaze-network/indexer-network/pkg/logger"
|
||||
"github.com/gaze-network/indexer-network/pkg/logger/slogx"
|
||||
@@ -62,8 +62,8 @@ type BitcoinNodeClient struct {
|
||||
}
|
||||
|
||||
type Modules struct {
|
||||
Runes runesconfig.Config `mapstructure:"runes"`
|
||||
BRC20 brc20config.Config `mapstructure:"brc20"`
|
||||
Runes runesconfig.Config `mapstructure:"runes"`
|
||||
NodeSale nodesaleconfig.Config `mapstructure:"nodesale"`
|
||||
}
|
||||
|
||||
type HTTPServerConfig struct {
|
||||
|
||||
@@ -1,115 +0,0 @@
|
||||
package httphandler
|
||||
|
||||
import (
|
||||
"slices"
|
||||
|
||||
"github.com/cockroachdb/errors"
|
||||
"github.com/gaze-network/indexer-network/common"
|
||||
"github.com/gaze-network/indexer-network/common/errs"
|
||||
"github.com/gaze-network/indexer-network/pkg/btcutils"
|
||||
"github.com/gaze-network/indexer-network/pkg/decimals"
|
||||
"github.com/gofiber/fiber/v2"
|
||||
"github.com/holiman/uint256"
|
||||
"github.com/samber/lo"
|
||||
)
|
||||
|
||||
type getBalancesByAddressRequest struct {
|
||||
Wallet string `params:"wallet"`
|
||||
Id string `query:"id"`
|
||||
BlockHeight uint64 `query:"blockHeight"`
|
||||
}
|
||||
|
||||
func (r getBalancesByAddressRequest) Validate() error {
|
||||
var errList []error
|
||||
if r.Wallet == "" {
|
||||
errList = append(errList, errors.New("'wallet' is required"))
|
||||
}
|
||||
return errs.WithPublicMessage(errors.Join(errList...), "validation error")
|
||||
}
|
||||
|
||||
type balanceExtend struct {
|
||||
Transferable *uint256.Int `json:"transferable"`
|
||||
Available *uint256.Int `json:"available"`
|
||||
}
|
||||
|
||||
type balance struct {
|
||||
Amount *uint256.Int `json:"amount"`
|
||||
Id string `json:"id"`
|
||||
Name string `json:"name"`
|
||||
Symbol string `json:"symbol"`
|
||||
Decimals uint16 `json:"decimals"`
|
||||
Extend balanceExtend `json:"extend"`
|
||||
}
|
||||
|
||||
type getBalancesByAddressResult struct {
|
||||
List []balance `json:"list"`
|
||||
BlockHeight uint64 `json:"blockHeight"`
|
||||
}
|
||||
|
||||
type getBalancesByAddressResponse = common.HttpResponse[getBalancesByAddressResult]
|
||||
|
||||
func (h *HttpHandler) GetBalancesByAddress(ctx *fiber.Ctx) (err error) {
|
||||
var req getBalancesByAddressRequest
|
||||
if err := ctx.ParamsParser(&req); err != nil {
|
||||
return errors.WithStack(err)
|
||||
}
|
||||
if err := ctx.QueryParser(&req); err != nil {
|
||||
return errors.WithStack(err)
|
||||
}
|
||||
if err := req.Validate(); err != nil {
|
||||
return errors.WithStack(err)
|
||||
}
|
||||
|
||||
pkScript, err := btcutils.ToPkScript(h.network, req.Wallet)
|
||||
if err != nil {
|
||||
return errs.NewPublicError("unable to resolve pkscript from \"wallet\"")
|
||||
}
|
||||
|
||||
blockHeight := req.BlockHeight
|
||||
if blockHeight == 0 {
|
||||
blockHeader, err := h.usecase.GetLatestBlock(ctx.UserContext())
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "error during GetLatestBlock")
|
||||
}
|
||||
blockHeight = uint64(blockHeader.Height)
|
||||
}
|
||||
|
||||
balances, err := h.usecase.GetBalancesByPkScript(ctx.UserContext(), pkScript, blockHeight)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "error during GetBalancesByPkScript")
|
||||
}
|
||||
|
||||
ticks := lo.Keys(balances)
|
||||
entries, err := h.usecase.GetTickEntryByTickBatch(ctx.UserContext(), ticks)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "error during GetTickEntryByTickBatch")
|
||||
}
|
||||
|
||||
balanceList := make([]balance, 0, len(balances))
|
||||
for id, b := range balances {
|
||||
entry := entries[id]
|
||||
balanceList = append(balanceList, balance{
|
||||
Amount: decimals.ToUint256(b.OverallBalance, entry.Decimals),
|
||||
Id: id,
|
||||
Name: entry.OriginalTick,
|
||||
Symbol: entry.Tick,
|
||||
Decimals: entry.Decimals,
|
||||
Extend: balanceExtend{
|
||||
Transferable: decimals.ToUint256(b.OverallBalance.Sub(b.AvailableBalance), entry.Decimals),
|
||||
Available: decimals.ToUint256(b.AvailableBalance, entry.Decimals),
|
||||
},
|
||||
})
|
||||
}
|
||||
slices.SortFunc(balanceList, func(i, j balance) int {
|
||||
return j.Amount.Cmp(i.Amount)
|
||||
})
|
||||
|
||||
resp := getBalancesByAddressResponse{
|
||||
Result: &getBalancesByAddressResult{
|
||||
BlockHeight: blockHeight,
|
||||
List: balanceList,
|
||||
},
|
||||
}
|
||||
|
||||
return errors.WithStack(ctx.JSON(resp))
|
||||
}
|
||||
@@ -1,125 +0,0 @@
|
||||
package httphandler
|
||||
|
||||
import (
|
||||
"context"
|
||||
"slices"
|
||||
|
||||
"github.com/cockroachdb/errors"
|
||||
"github.com/gaze-network/indexer-network/common"
|
||||
"github.com/gaze-network/indexer-network/common/errs"
|
||||
"github.com/gaze-network/indexer-network/pkg/btcutils"
|
||||
"github.com/gaze-network/indexer-network/pkg/decimals"
|
||||
"github.com/gofiber/fiber/v2"
|
||||
"github.com/samber/lo"
|
||||
"golang.org/x/sync/errgroup"
|
||||
)
|
||||
|
||||
type getBalancesByAddressBatchRequest struct {
|
||||
Queries []getBalancesByAddressRequest `json:"queries"`
|
||||
}
|
||||
|
||||
func (r getBalancesByAddressBatchRequest) Validate() error {
|
||||
var errList []error
|
||||
for _, query := range r.Queries {
|
||||
if query.Wallet == "" {
|
||||
errList = append(errList, errors.Errorf("queries[%d]: 'wallet' is required"))
|
||||
}
|
||||
}
|
||||
return errs.WithPublicMessage(errors.Join(errList...), "validation error")
|
||||
}
|
||||
|
||||
type getBalancesByAddressBatchResult struct {
|
||||
List []*getBalancesByAddressResult `json:"list"`
|
||||
}
|
||||
|
||||
type getBalancesByAddressBatchResponse = common.HttpResponse[getBalancesByAddressBatchResult]
|
||||
|
||||
func (h *HttpHandler) GetBalancesByAddressBatch(ctx *fiber.Ctx) (err error) {
|
||||
var req getBalancesByAddressBatchRequest
|
||||
if err := ctx.BodyParser(&req); err != nil {
|
||||
return errors.WithStack(err)
|
||||
}
|
||||
if err := req.Validate(); err != nil {
|
||||
return errors.WithStack(err)
|
||||
}
|
||||
|
||||
var latestBlockHeight uint64
|
||||
blockHeader, err := h.usecase.GetLatestBlock(ctx.UserContext())
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "error during GetLatestBlock")
|
||||
}
|
||||
latestBlockHeight = uint64(blockHeader.Height)
|
||||
|
||||
processQuery := func(ctx context.Context, query getBalancesByAddressRequest) (*getBalancesByAddressResult, error) {
|
||||
pkScript, err := btcutils.ToPkScript(h.network, query.Wallet)
|
||||
if err != nil {
|
||||
return nil, errs.NewPublicError("unable to resolve pkscript from \"wallet\"")
|
||||
}
|
||||
|
||||
blockHeight := query.BlockHeight
|
||||
if blockHeight == 0 {
|
||||
blockHeight = latestBlockHeight
|
||||
}
|
||||
|
||||
balances, err := h.usecase.GetBalancesByPkScript(ctx, pkScript, blockHeight)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "error during GetBalancesByPkScript")
|
||||
}
|
||||
|
||||
balanceRuneIds := lo.Keys(balances)
|
||||
entries, err := h.usecase.GetTickEntryByTickBatch(ctx, balanceRuneIds)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "error during GetTickEntryByTickBatch")
|
||||
}
|
||||
|
||||
balanceList := make([]balance, 0, len(balances))
|
||||
for id, b := range balances {
|
||||
entry := entries[id]
|
||||
balanceList = append(balanceList, balance{
|
||||
Amount: decimals.ToUint256(b.OverallBalance, entry.Decimals),
|
||||
Id: id,
|
||||
Name: entry.OriginalTick,
|
||||
Symbol: entry.Tick,
|
||||
Decimals: entry.Decimals,
|
||||
Extend: balanceExtend{
|
||||
Transferable: decimals.ToUint256(b.OverallBalance.Sub(b.AvailableBalance), entry.Decimals),
|
||||
Available: decimals.ToUint256(b.AvailableBalance, entry.Decimals),
|
||||
},
|
||||
})
|
||||
}
|
||||
slices.SortFunc(balanceList, func(i, j balance) int {
|
||||
return j.Amount.Cmp(i.Amount)
|
||||
})
|
||||
|
||||
return &getBalancesByAddressResult{
|
||||
BlockHeight: blockHeight,
|
||||
List: balanceList,
|
||||
}, nil
|
||||
}
|
||||
|
||||
results := make([]*getBalancesByAddressResult, len(req.Queries))
|
||||
eg, ectx := errgroup.WithContext(ctx.UserContext())
|
||||
for i, query := range req.Queries {
|
||||
i := i
|
||||
query := query
|
||||
eg.Go(func() error {
|
||||
result, err := processQuery(ectx, query)
|
||||
if err != nil {
|
||||
return errors.Wrapf(err, "error during processQuery for query %d", i)
|
||||
}
|
||||
results[i] = result
|
||||
return nil
|
||||
})
|
||||
}
|
||||
if err := eg.Wait(); err != nil {
|
||||
return errors.WithStack(err)
|
||||
}
|
||||
|
||||
resp := getBalancesByAddressBatchResponse{
|
||||
Result: &getBalancesByAddressBatchResult{
|
||||
List: results,
|
||||
},
|
||||
}
|
||||
|
||||
return errors.WithStack(ctx.JSON(resp))
|
||||
}
|
||||
@@ -1,49 +0,0 @@
|
||||
package httphandler
|
||||
|
||||
import (
|
||||
"github.com/Cleverse/go-utilities/utils"
|
||||
"github.com/btcsuite/btcd/chaincfg/chainhash"
|
||||
"github.com/cockroachdb/errors"
|
||||
"github.com/gaze-network/indexer-network/common"
|
||||
"github.com/gaze-network/indexer-network/common/errs"
|
||||
"github.com/gaze-network/indexer-network/core/types"
|
||||
"github.com/gofiber/fiber/v2"
|
||||
)
|
||||
|
||||
// TODO: use modules/brc20/constants.go
|
||||
var startingBlockHeader = map[common.Network]types.BlockHeader{
|
||||
common.NetworkMainnet: {
|
||||
Height: 767429,
|
||||
Hash: *utils.Must(chainhash.NewHashFromStr("00000000000000000002b35aef66eb15cd2b232a800f75a2f25cedca4cfe52c4")),
|
||||
},
|
||||
common.NetworkTestnet: {
|
||||
Height: 2413342,
|
||||
Hash: *utils.Must(chainhash.NewHashFromStr("00000000000022e97030b143af785de812f836dd0651b6ac2b7dd9e90dc9abf9")),
|
||||
},
|
||||
}
|
||||
|
||||
type getCurrentBlockResult struct {
|
||||
Hash string `json:"hash"`
|
||||
Height int64 `json:"height"`
|
||||
}
|
||||
|
||||
type getCurrentBlockResponse = common.HttpResponse[getCurrentBlockResult]
|
||||
|
||||
func (h *HttpHandler) GetCurrentBlock(ctx *fiber.Ctx) (err error) {
|
||||
blockHeader, err := h.usecase.GetLatestBlock(ctx.UserContext())
|
||||
if err != nil {
|
||||
if !errors.Is(err, errs.NotFound) {
|
||||
return errors.Wrap(err, "error during get latest block")
|
||||
}
|
||||
blockHeader = startingBlockHeader[h.network]
|
||||
}
|
||||
|
||||
resp := getCurrentBlockResponse{
|
||||
Result: &getCurrentBlockResult{
|
||||
Hash: blockHeader.Hash.String(),
|
||||
Height: blockHeader.Height,
|
||||
},
|
||||
}
|
||||
|
||||
return errors.WithStack(ctx.JSON(resp))
|
||||
}
|
||||
@@ -1,107 +0,0 @@
|
||||
package httphandler
|
||||
|
||||
import (
|
||||
"encoding/hex"
|
||||
|
||||
"github.com/cockroachdb/errors"
|
||||
"github.com/gaze-network/indexer-network/common"
|
||||
"github.com/gaze-network/indexer-network/common/errs"
|
||||
"github.com/gaze-network/indexer-network/pkg/btcutils"
|
||||
"github.com/gaze-network/indexer-network/pkg/decimals"
|
||||
"github.com/gofiber/fiber/v2"
|
||||
"github.com/holiman/uint256"
|
||||
)
|
||||
|
||||
type getHoldersRequest struct {
|
||||
Id string `params:"id"`
|
||||
BlockHeight uint64 `query:"blockHeight"`
|
||||
}
|
||||
|
||||
func (r getHoldersRequest) Validate() error {
|
||||
var errList []error
|
||||
return errs.WithPublicMessage(errors.Join(errList...), "validation error")
|
||||
}
|
||||
|
||||
type holdingBalanceExtend struct {
|
||||
Transferable *uint256.Int `json:"transferable"`
|
||||
Available *uint256.Int `json:"available"`
|
||||
}
|
||||
|
||||
type holdingBalance struct {
|
||||
Address string `json:"address"`
|
||||
PkScript string `json:"pkScript"`
|
||||
Amount *uint256.Int `json:"amount"`
|
||||
Percent float64 `json:"percent"`
|
||||
Extend holdingBalanceExtend `json:"extend"`
|
||||
}
|
||||
|
||||
type getHoldersResult struct {
|
||||
BlockHeight uint64 `json:"blockHeight"`
|
||||
TotalSupply *uint256.Int `json:"totalSupply"`
|
||||
MintedAmount *uint256.Int `json:"mintedAmount"`
|
||||
Decimals uint16 `json:"decimals"`
|
||||
List []holdingBalance `json:"list"`
|
||||
}
|
||||
|
||||
type getHoldersResponse = common.HttpResponse[getHoldersResult]
|
||||
|
||||
func (h *HttpHandler) GetHolders(ctx *fiber.Ctx) (err error) {
|
||||
var req getHoldersRequest
|
||||
if err := ctx.ParamsParser(&req); err != nil {
|
||||
return errors.WithStack(err)
|
||||
}
|
||||
if err := ctx.QueryParser(&req); err != nil {
|
||||
return errors.WithStack(err)
|
||||
}
|
||||
if err := req.Validate(); err != nil {
|
||||
return errors.WithStack(err)
|
||||
}
|
||||
|
||||
blockHeight := req.BlockHeight
|
||||
if blockHeight == 0 {
|
||||
blockHeader, err := h.usecase.GetLatestBlock(ctx.UserContext())
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "error during GetLatestBlock")
|
||||
}
|
||||
blockHeight = uint64(blockHeader.Height)
|
||||
}
|
||||
|
||||
entry, err := h.usecase.GetTickEntryByTickAndHeight(ctx.UserContext(), req.Id, blockHeight)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "error during GetTickEntryByTickAndHeight")
|
||||
}
|
||||
holdingBalances, err := h.usecase.GetBalancesByTick(ctx.UserContext(), req.Id, blockHeight)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "error during GetBalancesByTick")
|
||||
}
|
||||
|
||||
list := make([]holdingBalance, 0, len(holdingBalances))
|
||||
for _, balance := range holdingBalances {
|
||||
address, err := btcutils.PkScriptToAddress(balance.PkScript, h.network)
|
||||
if err != nil {
|
||||
return errors.Wrapf(err, "can't convert pkscript(%x) to address", balance.PkScript)
|
||||
}
|
||||
percent := balance.OverallBalance.Div(entry.TotalSupply)
|
||||
list = append(list, holdingBalance{
|
||||
Address: address,
|
||||
PkScript: hex.EncodeToString(balance.PkScript),
|
||||
Amount: decimals.ToUint256(balance.OverallBalance, entry.Decimals),
|
||||
Percent: percent.InexactFloat64(),
|
||||
Extend: holdingBalanceExtend{
|
||||
Transferable: decimals.ToUint256(balance.OverallBalance.Sub(balance.AvailableBalance), entry.Decimals),
|
||||
Available: decimals.ToUint256(balance.AvailableBalance, entry.Decimals),
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
resp := getHoldersResponse{
|
||||
Result: &getHoldersResult{
|
||||
BlockHeight: blockHeight,
|
||||
TotalSupply: decimals.ToUint256(entry.TotalSupply, entry.Decimals), // TODO: convert to wei
|
||||
MintedAmount: decimals.ToUint256(entry.MintedAmount, entry.Decimals), // TODO: convert to wei
|
||||
List: list,
|
||||
},
|
||||
}
|
||||
|
||||
return errors.WithStack(ctx.JSON(resp))
|
||||
}
|
||||
@@ -1,150 +0,0 @@
|
||||
package httphandler
|
||||
|
||||
import (
|
||||
"github.com/cockroachdb/errors"
|
||||
"github.com/gaze-network/indexer-network/common"
|
||||
"github.com/gaze-network/indexer-network/common/errs"
|
||||
"github.com/gaze-network/indexer-network/modules/brc20/internal/entity"
|
||||
"github.com/gaze-network/indexer-network/pkg/btcutils"
|
||||
"github.com/gaze-network/indexer-network/pkg/decimals"
|
||||
"github.com/gofiber/fiber/v2"
|
||||
"github.com/holiman/uint256"
|
||||
"github.com/samber/lo"
|
||||
"golang.org/x/sync/errgroup"
|
||||
)
|
||||
|
||||
type getTokenInfoRequest struct {
|
||||
Id string `params:"id"`
|
||||
BlockHeight uint64 `query:"blockHeight"`
|
||||
}
|
||||
|
||||
func (r getTokenInfoRequest) Validate() error {
|
||||
var errList []error
|
||||
return errs.WithPublicMessage(errors.Join(errList...), "validation error")
|
||||
}
|
||||
|
||||
type tokenInfoExtend struct {
|
||||
DeployedBy string `json:"deployedBy"`
|
||||
LimitPerMint *uint256.Int `json:"limitPerMint"`
|
||||
DeployInscriptionId string `json:"deployInscriptionId"`
|
||||
DeployInscriptionNumber int64 `json:"deployInscriptionNumber"`
|
||||
InscriptionStartNumber int64 `json:"inscriptionStartNumber"`
|
||||
InscriptionEndNumber int64 `json:"inscriptionEndNumber"`
|
||||
}
|
||||
|
||||
type getTokenInfoResult struct {
|
||||
Id string `json:"id"`
|
||||
Name string `json:"name"`
|
||||
Symbol string `json:"symbol"`
|
||||
TotalSupply *uint256.Int `json:"totalSupply"`
|
||||
CirculatingSupply *uint256.Int `json:"circulatingSupply"`
|
||||
MintedAmount *uint256.Int `json:"mintedAmount"`
|
||||
BurnedAmount *uint256.Int `json:"burnedAmount"`
|
||||
Decimals uint16 `json:"decimals"`
|
||||
DeployedAt uint64 `json:"deployedAt"`
|
||||
DeployedAtHeight uint64 `json:"deployedAtHeight"`
|
||||
CompletedAt *uint64 `json:"completedAt"`
|
||||
CompletedAtHeight *uint64 `json:"completedAtHeight"`
|
||||
HoldersCount int `json:"holdersCount"`
|
||||
Extend tokenInfoExtend `json:"extend"`
|
||||
}
|
||||
|
||||
type getTokenInfoResponse = common.HttpResponse[getTokenInfoResult]
|
||||
|
||||
func (h *HttpHandler) GetTokenInfo(ctx *fiber.Ctx) (err error) {
|
||||
var req getTokenInfoRequest
|
||||
if err := ctx.ParamsParser(&req); err != nil {
|
||||
return errors.WithStack(err)
|
||||
}
|
||||
if err := ctx.QueryParser(&req); err != nil {
|
||||
return errors.WithStack(err)
|
||||
}
|
||||
if err := req.Validate(); err != nil {
|
||||
return errors.WithStack(err)
|
||||
}
|
||||
|
||||
blockHeight := req.BlockHeight
|
||||
if blockHeight == 0 {
|
||||
blockHeader, err := h.usecase.GetLatestBlock(ctx.UserContext())
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "error during GetLatestBlock")
|
||||
}
|
||||
blockHeight = uint64(blockHeader.Height)
|
||||
}
|
||||
|
||||
group, groupctx := errgroup.WithContext(ctx.UserContext())
|
||||
var (
|
||||
entry *entity.TickEntry
|
||||
firstInscriptionNumber, lastInscriptionNumber int64
|
||||
deployEvent *entity.EventDeploy
|
||||
holdingBalances []*entity.Balance
|
||||
)
|
||||
group.Go(func() error {
|
||||
deployEvent, err = h.usecase.GetDeployEventByTick(groupctx, req.Id)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "error during GetDeployEventByTick")
|
||||
}
|
||||
return nil
|
||||
})
|
||||
group.Go(func() error {
|
||||
// TODO: at block height to parameter.
|
||||
firstInscriptionNumber, lastInscriptionNumber, err = h.usecase.GetFirstLastInscriptionNumberByTick(groupctx, req.Id)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "error during GetFirstLastInscriptionNumberByTick")
|
||||
}
|
||||
return nil
|
||||
})
|
||||
group.Go(func() error {
|
||||
entry, err = h.usecase.GetTickEntryByTickAndHeight(groupctx, req.Id, blockHeight)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "error during GetTickEntryByTickAndHeight")
|
||||
}
|
||||
return nil
|
||||
})
|
||||
group.Go(func() error {
|
||||
balances, err := h.usecase.GetBalancesByTick(groupctx, req.Id, blockHeight)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "error during GetBalancesByRuneId")
|
||||
}
|
||||
holdingBalances = lo.Filter(balances, func(b *entity.Balance, _ int) bool {
|
||||
return !b.OverallBalance.IsZero()
|
||||
})
|
||||
return nil
|
||||
})
|
||||
if err := group.Wait(); err != nil {
|
||||
return errors.WithStack(err)
|
||||
}
|
||||
|
||||
address, err := btcutils.PkScriptToAddress(deployEvent.PkScript, h.network)
|
||||
if err != nil {
|
||||
return errors.Wrapf(err, `error during PkScriptToAddress for pkscript: %x, network: %v`, deployEvent.PkScript, h.network)
|
||||
}
|
||||
|
||||
resp := getTokenInfoResponse{
|
||||
Result: &getTokenInfoResult{
|
||||
Id: entry.Tick,
|
||||
Name: entry.OriginalTick,
|
||||
Symbol: entry.Tick,
|
||||
TotalSupply: decimals.ToUint256(entry.TotalSupply, entry.Decimals),
|
||||
CirculatingSupply: decimals.ToUint256(entry.MintedAmount.Sub(entry.BurnedAmount), entry.Decimals),
|
||||
MintedAmount: decimals.ToUint256(entry.MintedAmount, entry.Decimals),
|
||||
BurnedAmount: decimals.ToUint256(entry.BurnedAmount, entry.Decimals),
|
||||
Decimals: entry.Decimals,
|
||||
DeployedAt: uint64(entry.DeployedAt.Unix()),
|
||||
DeployedAtHeight: entry.DeployedAtHeight,
|
||||
CompletedAt: lo.Ternary(entry.CompletedAt.IsZero(), nil, lo.ToPtr(uint64(entry.CompletedAt.Unix()))),
|
||||
CompletedAtHeight: lo.Ternary(entry.CompletedAtHeight == 0, nil, lo.ToPtr(entry.CompletedAtHeight)),
|
||||
HoldersCount: len(holdingBalances),
|
||||
Extend: tokenInfoExtend{
|
||||
DeployedBy: address,
|
||||
LimitPerMint: decimals.ToUint256(entry.LimitPerMint, entry.Decimals),
|
||||
DeployInscriptionId: deployEvent.InscriptionId.String(),
|
||||
DeployInscriptionNumber: deployEvent.InscriptionNumber,
|
||||
InscriptionStartNumber: lo.Ternary(firstInscriptionNumber < 0, deployEvent.InscriptionNumber, firstInscriptionNumber),
|
||||
InscriptionEndNumber: lo.Ternary(lastInscriptionNumber < 0, deployEvent.InscriptionNumber, lastInscriptionNumber),
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
return errors.WithStack(ctx.JSON(resp))
|
||||
}
|
||||
@@ -1,454 +0,0 @@
|
||||
package httphandler
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"cmp"
|
||||
"encoding/hex"
|
||||
"slices"
|
||||
"strings"
|
||||
|
||||
"github.com/btcsuite/btcd/chaincfg/chainhash"
|
||||
"github.com/cockroachdb/errors"
|
||||
"github.com/gaze-network/indexer-network/common"
|
||||
"github.com/gaze-network/indexer-network/common/errs"
|
||||
"github.com/gaze-network/indexer-network/modules/brc20/internal/entity"
|
||||
"github.com/gaze-network/indexer-network/pkg/btcutils"
|
||||
"github.com/gaze-network/indexer-network/pkg/decimals"
|
||||
"github.com/gofiber/fiber/v2"
|
||||
"github.com/holiman/uint256"
|
||||
"github.com/samber/lo"
|
||||
"github.com/shopspring/decimal"
|
||||
"golang.org/x/sync/errgroup"
|
||||
)
|
||||
|
||||
var ops = []string{"inscribe-deploy", "inscribe-mint", "inscribe-transfer", "transfer-transfer"}
|
||||
|
||||
type getTransactionsRequest struct {
|
||||
Wallet string `query:"wallet"`
|
||||
Id string `query:"id"`
|
||||
BlockHeight uint64 `query:"blockHeight"`
|
||||
Op string `query:"op"`
|
||||
}
|
||||
|
||||
func (r getTransactionsRequest) Validate() error {
|
||||
var errList []error
|
||||
if r.Op != "" {
|
||||
if !lo.Contains(ops, r.Op) {
|
||||
errList = append(errList, errors.Errorf("invalid 'op' value: %s, supported values: %s", r.Op, strings.Join(ops, ", ")))
|
||||
}
|
||||
}
|
||||
return errs.WithPublicMessage(errors.Join(errList...), "validation error")
|
||||
}
|
||||
|
||||
type txOpDeployArg struct {
|
||||
Op string `json:"op"`
|
||||
Tick string `json:"tick"`
|
||||
Max decimal.Decimal `json:"max"`
|
||||
Lim decimal.Decimal `json:"lim"`
|
||||
Dec uint16 `json:"dec"`
|
||||
SelfMint bool `json:"self_mint"`
|
||||
}
|
||||
|
||||
type txOpGeneralArg struct {
|
||||
Op string `json:"op"`
|
||||
Tick string `json:"tick"`
|
||||
Amount decimal.Decimal `json:"amt"`
|
||||
}
|
||||
|
||||
type txOperation[T any] struct {
|
||||
InscriptionId string `json:"inscriptionId"`
|
||||
InscriptionNumber int64 `json:"inscriptionNumber"`
|
||||
Op string `json:"op"`
|
||||
Args T `json:"args"`
|
||||
}
|
||||
|
||||
type txOperationsDeploy struct {
|
||||
txOperation[txOpDeployArg]
|
||||
Address string `json:"address"`
|
||||
}
|
||||
|
||||
type txOperationsMint struct {
|
||||
txOperation[txOpGeneralArg]
|
||||
Address string `json:"address"`
|
||||
}
|
||||
|
||||
type txOperationsInscribeTransfer struct {
|
||||
txOperation[txOpGeneralArg]
|
||||
Address string `json:"address"`
|
||||
OutputIndex uint32 `json:"outputIndex"`
|
||||
Sats uint64 `json:"sats"`
|
||||
}
|
||||
|
||||
type txOperationsTransferTransfer struct {
|
||||
txOperation[txOpGeneralArg]
|
||||
FromAddress string `json:"fromAddress"`
|
||||
ToAddress string `json:"toAddress"`
|
||||
}
|
||||
|
||||
type transactionExtend struct {
|
||||
Operations []any `json:"operations"`
|
||||
}
|
||||
|
||||
type amountWithDecimal struct {
|
||||
Amount *uint256.Int `json:"amount"`
|
||||
Decimals uint16 `json:"decimals"`
|
||||
}
|
||||
|
||||
type txInputOutput struct {
|
||||
PkScript string `json:"pkScript"`
|
||||
Address string `json:"address"`
|
||||
Id string `json:"id"`
|
||||
Amount *uint256.Int `json:"amount"`
|
||||
Decimals uint16 `json:"decimals"`
|
||||
Index uint32 `json:"index"`
|
||||
}
|
||||
|
||||
type transaction struct {
|
||||
TxHash chainhash.Hash `json:"txHash"`
|
||||
BlockHeight uint64 `json:"blockHeight"`
|
||||
Index uint32 `json:"index"`
|
||||
Timestamp int64 `json:"timestamp"`
|
||||
Inputs []txInputOutput `json:"inputs"`
|
||||
Outputs []txInputOutput `json:"outputs"`
|
||||
Mints map[string]amountWithDecimal `json:"mints"`
|
||||
Burns map[string]amountWithDecimal `json:"burns"`
|
||||
Extend transactionExtend `json:"extend"`
|
||||
}
|
||||
|
||||
type getTransactionsResult struct {
|
||||
List []transaction `json:"list"`
|
||||
}
|
||||
|
||||
type getTransactionsResponse = common.HttpResponse[getTransactionsResult]
|
||||
|
||||
func (h *HttpHandler) GetTransactions(ctx *fiber.Ctx) (err error) {
|
||||
var req getTransactionsRequest
|
||||
if err := ctx.QueryParser(&req); err != nil {
|
||||
return errors.WithStack(err)
|
||||
}
|
||||
if err := req.Validate(); err != nil {
|
||||
return errors.WithStack(err)
|
||||
}
|
||||
|
||||
var pkScript []byte
|
||||
if req.Wallet != "" {
|
||||
pkScript, err = btcutils.ToPkScript(h.network, req.Wallet)
|
||||
if err != nil {
|
||||
return errs.NewPublicError("unable to resolve pkscript from \"wallet\"")
|
||||
}
|
||||
}
|
||||
|
||||
blockHeight := req.BlockHeight
|
||||
// set blockHeight to the latest block height blockHeight, pkScript, and runeId are not provided
|
||||
if blockHeight == 0 && pkScript == nil && req.Id == "" {
|
||||
blockHeader, err := h.usecase.GetLatestBlock(ctx.UserContext())
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "error during GetLatestBlock")
|
||||
}
|
||||
blockHeight = uint64(blockHeader.Height)
|
||||
}
|
||||
|
||||
var (
|
||||
deployEvents []*entity.EventDeploy
|
||||
mintEvents []*entity.EventMint
|
||||
transferTransferEvents []*entity.EventTransferTransfer
|
||||
inscribeTransferEvents []*entity.EventInscribeTransfer
|
||||
)
|
||||
|
||||
group, groupctx := errgroup.WithContext(ctx.UserContext())
|
||||
|
||||
if req.Op == "" || req.Op == "inscribe-deploy" {
|
||||
group.Go(func() error {
|
||||
events, err := h.usecase.GetDeployEvents(groupctx, pkScript, req.Id, blockHeight)
|
||||
deployEvents = events
|
||||
return errors.Wrap(err, "error during get inscribe-deploy events")
|
||||
})
|
||||
}
|
||||
if req.Op == "" || req.Op == "inscribe-mint" {
|
||||
group.Go(func() error {
|
||||
events, err := h.usecase.GetMintEvents(groupctx, pkScript, req.Id, blockHeight)
|
||||
mintEvents = events
|
||||
return errors.Wrap(err, "error during get inscribe-mint events")
|
||||
})
|
||||
}
|
||||
if req.Op == "" || req.Op == "transfer-transfer" {
|
||||
group.Go(func() error {
|
||||
events, err := h.usecase.GetTransferTransferEvents(groupctx, pkScript, req.Id, blockHeight)
|
||||
transferTransferEvents = events
|
||||
return errors.Wrap(err, "error during get transfer-transfer events")
|
||||
})
|
||||
}
|
||||
if req.Op == "" || req.Op == "inscribe-transfer" {
|
||||
group.Go(func() error {
|
||||
events, err := h.usecase.GetInscribeTransferEvents(groupctx, pkScript, req.Id, blockHeight)
|
||||
inscribeTransferEvents = events
|
||||
return errors.Wrap(err, "error during get inscribe-transfer events")
|
||||
})
|
||||
}
|
||||
if err := group.Wait(); err != nil {
|
||||
return errors.WithStack(err)
|
||||
}
|
||||
|
||||
allTicks := make([]string, 0, len(deployEvents)+len(mintEvents)+len(transferTransferEvents)+len(inscribeTransferEvents))
|
||||
allTicks = append(allTicks, lo.Map(deployEvents, func(event *entity.EventDeploy, _ int) string { return event.Tick })...)
|
||||
allTicks = append(allTicks, lo.Map(mintEvents, func(event *entity.EventMint, _ int) string { return event.Tick })...)
|
||||
allTicks = append(allTicks, lo.Map(transferTransferEvents, func(event *entity.EventTransferTransfer, _ int) string { return event.Tick })...)
|
||||
allTicks = append(allTicks, lo.Map(inscribeTransferEvents, func(event *entity.EventInscribeTransfer, _ int) string { return event.Tick })...)
|
||||
entries, err := h.usecase.GetTickEntryByTickBatch(ctx.UserContext(), lo.Uniq(allTicks))
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "error during GetTickEntryByTickBatch")
|
||||
}
|
||||
|
||||
rawTxList := make([]transaction, 0, len(deployEvents)+len(mintEvents)+len(transferTransferEvents)+len(inscribeTransferEvents))
|
||||
|
||||
// Deploy events
|
||||
for _, event := range deployEvents {
|
||||
address, err := btcutils.PkScriptToAddress(event.PkScript, h.network)
|
||||
if err != nil {
|
||||
return errors.Wrapf(err, `error during PkScriptToAddress for deploy event %s, pkscript: %x, network: %v`, event.TxHash, event.PkScript, h.network)
|
||||
}
|
||||
respTx := transaction{
|
||||
TxHash: event.TxHash,
|
||||
BlockHeight: event.BlockHeight,
|
||||
Index: event.TxIndex,
|
||||
Timestamp: event.Timestamp.Unix(),
|
||||
Mints: map[string]amountWithDecimal{},
|
||||
Burns: map[string]amountWithDecimal{},
|
||||
Extend: transactionExtend{
|
||||
Operations: []any{
|
||||
txOperationsDeploy{
|
||||
txOperation: txOperation[txOpDeployArg]{
|
||||
InscriptionId: event.InscriptionId.String(),
|
||||
InscriptionNumber: event.InscriptionNumber,
|
||||
Op: "deploy",
|
||||
Args: txOpDeployArg{
|
||||
Op: "deploy",
|
||||
Tick: event.Tick,
|
||||
Max: event.TotalSupply,
|
||||
Lim: event.LimitPerMint,
|
||||
Dec: event.Decimals,
|
||||
SelfMint: event.IsSelfMint,
|
||||
},
|
||||
},
|
||||
Address: address,
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
rawTxList = append(rawTxList, respTx)
|
||||
}
|
||||
|
||||
// Mint events
|
||||
for _, event := range mintEvents {
|
||||
entry := entries[event.Tick]
|
||||
address, err := btcutils.PkScriptToAddress(event.PkScript, h.network)
|
||||
if err != nil {
|
||||
return errors.Wrapf(err, `error during PkScriptToAddress for deploy event %s, pkscript: %x, network: %v`, event.TxHash, event.PkScript, h.network)
|
||||
}
|
||||
amtWei := decimals.ToUint256(event.Amount, entry.Decimals)
|
||||
respTx := transaction{
|
||||
TxHash: event.TxHash,
|
||||
BlockHeight: event.BlockHeight,
|
||||
Index: event.TxIndex,
|
||||
Timestamp: event.Timestamp.Unix(),
|
||||
Outputs: []txInputOutput{
|
||||
{
|
||||
PkScript: hex.EncodeToString(event.PkScript),
|
||||
Address: address,
|
||||
Id: event.Tick,
|
||||
Amount: amtWei,
|
||||
Decimals: entry.Decimals,
|
||||
Index: event.TxIndex,
|
||||
},
|
||||
},
|
||||
Mints: map[string]amountWithDecimal{
|
||||
event.Tick: {
|
||||
Amount: amtWei,
|
||||
Decimals: entry.Decimals,
|
||||
},
|
||||
},
|
||||
Extend: transactionExtend{
|
||||
Operations: []any{
|
||||
txOperationsMint{
|
||||
txOperation: txOperation[txOpGeneralArg]{
|
||||
InscriptionId: event.InscriptionId.String(),
|
||||
InscriptionNumber: event.InscriptionNumber,
|
||||
Op: "inscribe-mint",
|
||||
Args: txOpGeneralArg{
|
||||
Op: "inscribe-mint",
|
||||
Tick: event.Tick,
|
||||
Amount: event.Amount,
|
||||
},
|
||||
},
|
||||
Address: address,
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
rawTxList = append(rawTxList, respTx)
|
||||
}
|
||||
|
||||
// Inscribe Transfer events
|
||||
for _, event := range inscribeTransferEvents {
|
||||
address, err := btcutils.PkScriptToAddress(event.PkScript, h.network)
|
||||
if err != nil {
|
||||
return errors.Wrapf(err, `error during PkScriptToAddress for deploy event %s, pkscript: %x, network: %v`, event.TxHash, event.PkScript, h.network)
|
||||
}
|
||||
respTx := transaction{
|
||||
TxHash: event.TxHash,
|
||||
BlockHeight: event.BlockHeight,
|
||||
Index: event.TxIndex,
|
||||
Timestamp: event.Timestamp.Unix(),
|
||||
Mints: map[string]amountWithDecimal{},
|
||||
Burns: map[string]amountWithDecimal{},
|
||||
Extend: transactionExtend{
|
||||
Operations: []any{
|
||||
txOperationsInscribeTransfer{
|
||||
txOperation: txOperation[txOpGeneralArg]{
|
||||
InscriptionId: event.InscriptionId.String(),
|
||||
InscriptionNumber: event.InscriptionNumber,
|
||||
Op: "inscribe-transfer",
|
||||
Args: txOpGeneralArg{
|
||||
Op: "inscribe-transfer",
|
||||
Tick: event.Tick,
|
||||
Amount: event.Amount,
|
||||
},
|
||||
},
|
||||
Address: address,
|
||||
OutputIndex: event.SatPoint.OutPoint.Index,
|
||||
Sats: event.SatsAmount,
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
rawTxList = append(rawTxList, respTx)
|
||||
}
|
||||
|
||||
// Transfer Transfer events
|
||||
for _, event := range transferTransferEvents {
|
||||
entry := entries[event.Tick]
|
||||
amntWei := decimals.ToUint256(event.Amount, entry.Decimals)
|
||||
fromAddress, err := btcutils.PkScriptToAddress(event.FromPkScript, h.network)
|
||||
if err != nil {
|
||||
return errors.Wrapf(err, `error during PkScriptToAddress for deploy event %s, pkscript: %x, network: %v`, event.TxHash, event.FromPkScript, h.network)
|
||||
}
|
||||
toAddress := ""
|
||||
if len(event.ToPkScript) > 0 && !bytes.Equal(event.ToPkScript, []byte{0x6a}) {
|
||||
toAddress, err = btcutils.PkScriptToAddress(event.ToPkScript, h.network)
|
||||
if err != nil {
|
||||
return errors.Wrapf(err, `error during PkScriptToAddress for deploy event %s, pkscript: %x, network: %v`, event.TxHash, event.FromPkScript, h.network)
|
||||
}
|
||||
}
|
||||
|
||||
// if toAddress is empty, it's a burn.
|
||||
burns := map[string]amountWithDecimal{}
|
||||
if len(toAddress) == 0 {
|
||||
burns[event.Tick] = amountWithDecimal{
|
||||
Amount: amntWei,
|
||||
Decimals: entry.Decimals,
|
||||
}
|
||||
}
|
||||
|
||||
respTx := transaction{
|
||||
TxHash: event.TxHash,
|
||||
BlockHeight: event.BlockHeight,
|
||||
Index: event.TxIndex,
|
||||
Timestamp: event.Timestamp.Unix(),
|
||||
Inputs: []txInputOutput{
|
||||
{
|
||||
PkScript: hex.EncodeToString(event.FromPkScript),
|
||||
Address: fromAddress,
|
||||
Id: event.Tick,
|
||||
Amount: amntWei,
|
||||
Decimals: entry.Decimals,
|
||||
Index: event.ToOutputIndex,
|
||||
},
|
||||
},
|
||||
Outputs: []txInputOutput{
|
||||
{
|
||||
PkScript: hex.EncodeToString(event.ToPkScript),
|
||||
Address: fromAddress,
|
||||
Id: event.Tick,
|
||||
Amount: amntWei,
|
||||
Decimals: entry.Decimals,
|
||||
Index: event.ToOutputIndex,
|
||||
},
|
||||
},
|
||||
Mints: map[string]amountWithDecimal{},
|
||||
Burns: burns,
|
||||
Extend: transactionExtend{
|
||||
Operations: []any{
|
||||
txOperationsTransferTransfer{
|
||||
txOperation: txOperation[txOpGeneralArg]{
|
||||
InscriptionId: event.InscriptionId.String(),
|
||||
InscriptionNumber: event.InscriptionNumber,
|
||||
Op: "transfer-transfer",
|
||||
Args: txOpGeneralArg{
|
||||
Op: "transfer-transfer",
|
||||
Tick: event.Tick,
|
||||
Amount: event.Amount,
|
||||
},
|
||||
},
|
||||
FromAddress: fromAddress,
|
||||
ToAddress: toAddress,
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
rawTxList = append(rawTxList, respTx)
|
||||
}
|
||||
|
||||
// merge brc-20 tx events that have the same tx hash
|
||||
txList := make([]transaction, 0, len(rawTxList))
|
||||
groupedTxs := lo.GroupBy(rawTxList, func(tx transaction) chainhash.Hash { return tx.TxHash })
|
||||
for _, txs := range groupedTxs {
|
||||
tx := txs[0]
|
||||
if tx.Mints == nil {
|
||||
tx.Mints = map[string]amountWithDecimal{}
|
||||
}
|
||||
if tx.Burns == nil {
|
||||
tx.Burns = map[string]amountWithDecimal{}
|
||||
}
|
||||
for _, tx2 := range txs[1:] {
|
||||
tx.Inputs = append(tx.Inputs, tx2.Inputs...)
|
||||
tx.Outputs = append(tx.Outputs, tx2.Outputs...)
|
||||
if len(tx2.Mints) > 0 {
|
||||
return errors.Wrap(errs.InvalidState, "transaction can't have multiple mints")
|
||||
}
|
||||
for tick, tx2Ammt := range tx2.Burns {
|
||||
if txAmmt, ok := tx.Burns[tick]; ok {
|
||||
tx.Burns[tick] = amountWithDecimal{
|
||||
Amount: new(uint256.Int).Add(txAmmt.Amount, tx2Ammt.Amount),
|
||||
Decimals: txAmmt.Decimals,
|
||||
}
|
||||
} else {
|
||||
tx.Burns[tick] = tx2Ammt
|
||||
}
|
||||
}
|
||||
tx.Extend.Operations = append(tx.Extend.Operations, tx2.Extend.Operations...)
|
||||
}
|
||||
slices.SortFunc(tx.Inputs, func(i, j txInputOutput) int {
|
||||
return cmp.Compare(i.Index, j.Index)
|
||||
})
|
||||
slices.SortFunc(tx.Outputs, func(i, j txInputOutput) int {
|
||||
return cmp.Compare(i.Index, j.Index)
|
||||
})
|
||||
txList = append(txList, tx)
|
||||
}
|
||||
|
||||
// sort by block height ASC, then index ASC
|
||||
slices.SortFunc(txList, func(t1, t2 transaction) int {
|
||||
if t1.BlockHeight != t2.BlockHeight {
|
||||
return int(t1.BlockHeight - t2.BlockHeight)
|
||||
}
|
||||
return int(t1.Index - t2.Index)
|
||||
})
|
||||
|
||||
resp := getTransactionsResponse{
|
||||
Result: &getTransactionsResult{
|
||||
List: txList,
|
||||
},
|
||||
}
|
||||
|
||||
return errors.WithStack(ctx.JSON(resp))
|
||||
}
|
||||
@@ -1,136 +0,0 @@
|
||||
package httphandler
|
||||
|
||||
import (
|
||||
"strings"
|
||||
|
||||
"github.com/btcsuite/btcd/chaincfg/chainhash"
|
||||
"github.com/btcsuite/btcd/wire"
|
||||
"github.com/cockroachdb/errors"
|
||||
"github.com/gaze-network/indexer-network/common"
|
||||
"github.com/gaze-network/indexer-network/common/errs"
|
||||
"github.com/gaze-network/indexer-network/modules/brc20/internal/entity"
|
||||
"github.com/gaze-network/indexer-network/pkg/btcutils"
|
||||
"github.com/gaze-network/indexer-network/pkg/decimals"
|
||||
"github.com/gofiber/fiber/v2"
|
||||
"github.com/holiman/uint256"
|
||||
"github.com/samber/lo"
|
||||
)
|
||||
|
||||
type getUTXOsByAddressRequest struct {
|
||||
Wallet string `params:"wallet"`
|
||||
Id string `query:"id"`
|
||||
BlockHeight uint64 `query:"blockHeight"`
|
||||
}
|
||||
|
||||
func (r getUTXOsByAddressRequest) Validate() error {
|
||||
var errList []error
|
||||
if r.Wallet == "" {
|
||||
errList = append(errList, errors.New("'wallet' is required"))
|
||||
}
|
||||
return errs.WithPublicMessage(errors.Join(errList...), "validation error")
|
||||
}
|
||||
|
||||
type transferableInscription struct {
|
||||
Ticker string `json:"ticker"`
|
||||
Amount *uint256.Int `json:"amount"`
|
||||
Decimals uint16 `json:"decimals"`
|
||||
}
|
||||
|
||||
type utxoExtend struct {
|
||||
TransferableInscriptions []transferableInscription `json:"transferableInscriptions"`
|
||||
}
|
||||
|
||||
type utxo struct {
|
||||
TxHash chainhash.Hash `json:"txHash"`
|
||||
OutputIndex uint32 `json:"outputIndex"`
|
||||
Extend utxoExtend `json:"extend"`
|
||||
}
|
||||
|
||||
type getUTXOsByAddressResult struct {
|
||||
List []utxo `json:"list"`
|
||||
BlockHeight uint64 `json:"blockHeight"`
|
||||
}
|
||||
|
||||
type getUTXOsByAddressResponse = common.HttpResponse[getUTXOsByAddressResult]
|
||||
|
||||
func (h *HttpHandler) GetUTXOsByAddress(ctx *fiber.Ctx) (err error) {
|
||||
var req getUTXOsByAddressRequest
|
||||
if err := ctx.ParamsParser(&req); err != nil {
|
||||
return errors.WithStack(err)
|
||||
}
|
||||
if err := ctx.QueryParser(&req); err != nil {
|
||||
return errors.WithStack(err)
|
||||
}
|
||||
if err := req.Validate(); err != nil {
|
||||
return errors.WithStack(err)
|
||||
}
|
||||
|
||||
pkScript, err := btcutils.ToPkScript(h.network, req.Wallet)
|
||||
if err != nil {
|
||||
return errs.NewPublicError("unable to resolve pkscript from \"wallet\"")
|
||||
}
|
||||
|
||||
blockHeight := req.BlockHeight
|
||||
if blockHeight == 0 {
|
||||
blockHeader, err := h.usecase.GetLatestBlock(ctx.UserContext())
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "error during GetLatestBlock")
|
||||
}
|
||||
blockHeight = uint64(blockHeader.Height)
|
||||
}
|
||||
|
||||
transferables, err := h.usecase.GetTransferableTransfersByPkScript(ctx.UserContext(), pkScript, blockHeight)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "error during GetTransferableTransfersByPkScript")
|
||||
}
|
||||
|
||||
transferableTicks := lo.Map(transferables, func(src *entity.EventInscribeTransfer, _ int) string { return src.Tick })
|
||||
entries, err := h.usecase.GetTickEntryByTickBatch(ctx.UserContext(), transferableTicks)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "error during GetTickEntryByTickBatch")
|
||||
}
|
||||
|
||||
groupedtransferableTi := lo.GroupBy(transferables, func(src *entity.EventInscribeTransfer) wire.OutPoint { return src.SatPoint.OutPoint })
|
||||
utxoList := make([]utxo, 0, len(groupedtransferableTi))
|
||||
for outPoint, transferables := range groupedtransferableTi {
|
||||
transferableInscriptions := make([]transferableInscription, 0, len(transferables))
|
||||
for _, transferable := range transferables {
|
||||
entry := entries[transferable.Tick]
|
||||
transferableInscriptions = append(transferableInscriptions, transferableInscription{
|
||||
Ticker: transferable.Tick,
|
||||
Amount: decimals.ToUint256(transferable.Amount, entry.Decimals),
|
||||
Decimals: entry.Decimals,
|
||||
})
|
||||
}
|
||||
|
||||
utxoList = append(utxoList, utxo{
|
||||
TxHash: outPoint.Hash,
|
||||
OutputIndex: outPoint.Index,
|
||||
Extend: utxoExtend{
|
||||
TransferableInscriptions: transferableInscriptions,
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
// TODO: filter tickers in pg query
|
||||
// filter by req.Id if exists
|
||||
{
|
||||
utxoList = lo.Filter(utxoList, func(u utxo, _ int) bool {
|
||||
for _, transferableInscriptions := range u.Extend.TransferableInscriptions {
|
||||
if ok := strings.EqualFold(req.Id, transferableInscriptions.Ticker); ok {
|
||||
return ok
|
||||
}
|
||||
}
|
||||
return false
|
||||
})
|
||||
}
|
||||
|
||||
resp := getUTXOsByAddressResponse{
|
||||
Result: &getUTXOsByAddressResult{
|
||||
BlockHeight: blockHeight,
|
||||
List: utxoList,
|
||||
},
|
||||
}
|
||||
|
||||
return errors.WithStack(ctx.JSON(resp))
|
||||
}
|
||||
@@ -1,18 +0,0 @@
|
||||
package httphandler
|
||||
|
||||
import (
|
||||
"github.com/gaze-network/indexer-network/common"
|
||||
"github.com/gaze-network/indexer-network/modules/brc20/internal/usecase"
|
||||
)
|
||||
|
||||
type HttpHandler struct {
|
||||
usecase *usecase.Usecase
|
||||
network common.Network
|
||||
}
|
||||
|
||||
func New(network common.Network, usecase *usecase.Usecase) *HttpHandler {
|
||||
return &HttpHandler{
|
||||
network: network,
|
||||
usecase: usecase,
|
||||
}
|
||||
}
|
||||
@@ -1,19 +0,0 @@
|
||||
package httphandler
|
||||
|
||||
import (
|
||||
"github.com/gofiber/fiber/v2"
|
||||
)
|
||||
|
||||
func (h *HttpHandler) Mount(router fiber.Router) error {
|
||||
r := router.Group("/v2/brc20")
|
||||
|
||||
r.Post("/balances/wallet/batch", h.GetBalancesByAddressBatch)
|
||||
r.Get("/balances/wallet/:wallet", h.GetBalancesByAddress)
|
||||
r.Get("/transactions", h.GetTransactions)
|
||||
r.Get("/holders/:id", h.GetHolders)
|
||||
r.Get("/info/:id", h.GetTokenInfo)
|
||||
r.Get("/utxos/wallet/:wallet", h.GetUTXOsByAddress)
|
||||
r.Get("/block", h.GetCurrentBlock)
|
||||
|
||||
return nil
|
||||
}
|
||||
@@ -1,93 +0,0 @@
|
||||
package brc20
|
||||
|
||||
import (
|
||||
"context"
|
||||
"strings"
|
||||
|
||||
"github.com/btcsuite/btcd/rpcclient"
|
||||
"github.com/cockroachdb/errors"
|
||||
"github.com/gaze-network/indexer-network/common/errs"
|
||||
"github.com/gaze-network/indexer-network/core/datasources"
|
||||
"github.com/gaze-network/indexer-network/core/indexer"
|
||||
"github.com/gaze-network/indexer-network/core/types"
|
||||
"github.com/gaze-network/indexer-network/internal/config"
|
||||
"github.com/gaze-network/indexer-network/internal/postgres"
|
||||
"github.com/gaze-network/indexer-network/modules/brc20/api/httphandler"
|
||||
"github.com/gaze-network/indexer-network/modules/brc20/internal/datagateway"
|
||||
brc20postgres "github.com/gaze-network/indexer-network/modules/brc20/internal/repository/postgres"
|
||||
"github.com/gaze-network/indexer-network/modules/brc20/internal/usecase"
|
||||
"github.com/gaze-network/indexer-network/pkg/btcclient"
|
||||
"github.com/gaze-network/indexer-network/pkg/logger"
|
||||
"github.com/gofiber/fiber/v2"
|
||||
"github.com/samber/do/v2"
|
||||
"github.com/samber/lo"
|
||||
)
|
||||
|
||||
func New(injector do.Injector) (indexer.IndexerWorker, error) {
|
||||
ctx := do.MustInvoke[context.Context](injector)
|
||||
conf := do.MustInvoke[config.Config](injector)
|
||||
// reportingClient := do.MustInvoke[*reportingclient.ReportingClient](injector)
|
||||
|
||||
cleanupFuncs := make([]func(context.Context) error, 0)
|
||||
var brc20Dg datagateway.BRC20DataGateway
|
||||
var indexerInfoDg datagateway.IndexerInfoDataGateway
|
||||
switch strings.ToLower(conf.Modules.BRC20.Database) {
|
||||
case "postgresql", "postgres", "pg":
|
||||
pg, err := postgres.NewPool(ctx, conf.Modules.BRC20.Postgres)
|
||||
if err != nil {
|
||||
if errors.Is(err, errs.InvalidArgument) {
|
||||
return nil, errors.Wrap(err, "Invalid Postgres configuration for indexer")
|
||||
}
|
||||
return nil, errors.Wrap(err, "can't create Postgres connection pool")
|
||||
}
|
||||
cleanupFuncs = append(cleanupFuncs, func(ctx context.Context) error {
|
||||
pg.Close()
|
||||
return nil
|
||||
})
|
||||
brc20Repo := brc20postgres.NewRepository(pg)
|
||||
brc20Dg = brc20Repo
|
||||
indexerInfoDg = brc20Repo
|
||||
default:
|
||||
return nil, errors.Wrapf(errs.Unsupported, "%q database for indexer is not supported", conf.Modules.BRC20.Database)
|
||||
}
|
||||
|
||||
var bitcoinDatasource datasources.Datasource[*types.Block]
|
||||
var bitcoinClient btcclient.Contract
|
||||
switch strings.ToLower(conf.Modules.BRC20.Datasource) {
|
||||
case "bitcoin-node":
|
||||
btcClient := do.MustInvoke[*rpcclient.Client](injector)
|
||||
bitcoinNodeDatasource := datasources.NewBitcoinNode(btcClient)
|
||||
bitcoinDatasource = bitcoinNodeDatasource
|
||||
bitcoinClient = bitcoinNodeDatasource
|
||||
default:
|
||||
return nil, errors.Wrapf(errs.Unsupported, "%q datasource is not supported", conf.Modules.BRC20.Datasource)
|
||||
}
|
||||
|
||||
processor, err := NewProcessor(brc20Dg, indexerInfoDg, bitcoinClient, conf.Network, cleanupFuncs)
|
||||
if err != nil {
|
||||
return nil, errors.WithStack(err)
|
||||
}
|
||||
if err := processor.VerifyStates(ctx); err != nil {
|
||||
return nil, errors.WithStack(err)
|
||||
}
|
||||
|
||||
// Mount API
|
||||
apiHandlers := lo.Uniq(conf.Modules.BRC20.APIHandlers)
|
||||
for _, handler := range apiHandlers {
|
||||
switch handler { // TODO: support more handlers (e.g. gRPC)
|
||||
case "http":
|
||||
httpServer := do.MustInvoke[*fiber.App](injector)
|
||||
uc := usecase.New(brc20Dg, bitcoinClient)
|
||||
httpHandler := httphandler.New(conf.Network, uc)
|
||||
if err := httpHandler.Mount(httpServer); err != nil {
|
||||
return nil, errors.Wrap(err, "can't mount API")
|
||||
}
|
||||
logger.InfoContext(ctx, "Mounted HTTP handler")
|
||||
default:
|
||||
return nil, errors.Wrapf(errs.Unsupported, "%q API handler is not supported", handler)
|
||||
}
|
||||
}
|
||||
|
||||
indexer := indexer.New(processor, bitcoinDatasource)
|
||||
return indexer, nil
|
||||
}
|
||||
@@ -1,10 +0,0 @@
|
||||
package config
|
||||
|
||||
import "github.com/gaze-network/indexer-network/internal/postgres"
|
||||
|
||||
type Config struct {
|
||||
Datasource string `mapstructure:"datasource"` // Datasource to fetch bitcoin data for Meta-Protocol e.g. `bitcoin-node`
|
||||
Database string `mapstructure:"database"` // Database to store data.
|
||||
APIHandlers []string `mapstructure:"api_handlers"` // List of API handlers to enable. (e.g. `http`)
|
||||
Postgres postgres.Config `mapstructure:"postgres"`
|
||||
}
|
||||
@@ -1,25 +0,0 @@
|
||||
package brc20
|
||||
|
||||
import (
|
||||
"github.com/Cleverse/go-utilities/utils"
|
||||
"github.com/btcsuite/btcd/chaincfg/chainhash"
|
||||
"github.com/gaze-network/indexer-network/common"
|
||||
"github.com/gaze-network/indexer-network/core/types"
|
||||
)
|
||||
|
||||
const (
|
||||
ClientVersion = "v0.0.1"
|
||||
DBVersion = 1
|
||||
EventHashVersion = 1
|
||||
)
|
||||
|
||||
var startingBlockHeader = map[common.Network]types.BlockHeader{
|
||||
common.NetworkMainnet: {
|
||||
Height: 767429,
|
||||
Hash: *utils.Must(chainhash.NewHashFromStr("00000000000000000002b35aef66eb15cd2b232a800f75a2f25cedca4cfe52c4")),
|
||||
},
|
||||
common.NetworkTestnet: {
|
||||
Height: 2413342,
|
||||
Hash: *utils.Must(chainhash.NewHashFromStr("00000000000022e97030b143af785de812f836dd0651b6ac2b7dd9e90dc9abf9")),
|
||||
},
|
||||
}
|
||||
@@ -1,17 +0,0 @@
|
||||
BEGIN;
|
||||
|
||||
DROP TABLE IF EXISTS "brc20_indexer_states";
|
||||
DROP TABLE IF EXISTS "brc20_indexed_blocks";
|
||||
DROP TABLE IF EXISTS "brc20_processor_stats";
|
||||
DROP TABLE IF EXISTS "brc20_tick_entries";
|
||||
DROP TABLE IF EXISTS "brc20_tick_entry_states";
|
||||
DROP TABLE IF EXISTS "brc20_event_deploys";
|
||||
DROP TABLE IF EXISTS "brc20_event_mints";
|
||||
DROP TABLE IF EXISTS "brc20_event_inscribe_transfers";
|
||||
DROP TABLE IF EXISTS "brc20_event_transfer_transfers";
|
||||
DROP TABLE IF EXISTS "brc20_balances";
|
||||
DROP TABLE IF EXISTS "brc20_inscription_entries";
|
||||
DROP TABLE IF EXISTS "brc20_inscription_entry_states";
|
||||
DROP TABLE IF EXISTS "brc20_inscription_transfers";
|
||||
|
||||
COMMIT;
|
||||
@@ -1,191 +0,0 @@
|
||||
BEGIN;
|
||||
|
||||
-- Indexer Client Information
|
||||
|
||||
CREATE TABLE IF NOT EXISTS "brc20_indexer_states" (
|
||||
"id" BIGSERIAL PRIMARY KEY,
|
||||
"client_version" TEXT NOT NULL,
|
||||
"network" TEXT NOT NULL,
|
||||
"db_version" INT NOT NULL,
|
||||
"event_hash_version" INT NOT NULL,
|
||||
"created_at" TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT CURRENT_TIMESTAMP
|
||||
);
|
||||
CREATE INDEX IF NOT EXISTS brc20_indexer_state_created_at_idx ON "brc20_indexer_states" USING BTREE ("created_at" DESC);
|
||||
|
||||
-- BRC20 data
|
||||
|
||||
CREATE TABLE IF NOT EXISTS "brc20_indexed_blocks" (
|
||||
"height" INT NOT NULL PRIMARY KEY,
|
||||
"hash" TEXT NOT NULL,
|
||||
"event_hash" TEXT NOT NULL,
|
||||
"cumulative_event_hash" TEXT NOT NULL
|
||||
);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS "brc20_processor_stats" (
|
||||
"block_height" INT NOT NULL PRIMARY KEY,
|
||||
"cursed_inscription_count" INT NOT NULL,
|
||||
"blessed_inscription_count" INT NOT NULL,
|
||||
"lost_sats" BIGINT NOT NULL
|
||||
);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS "brc20_tick_entries" (
|
||||
"tick" TEXT NOT NULL PRIMARY KEY, -- lowercase of original_tick
|
||||
"original_tick" TEXT NOT NULL,
|
||||
"total_supply" DECIMAL NOT NULL,
|
||||
"decimals" SMALLINT NOT NULL,
|
||||
"limit_per_mint" DECIMAL NOT NULL,
|
||||
"is_self_mint" BOOLEAN NOT NULL,
|
||||
"deploy_inscription_id" TEXT NOT NULL,
|
||||
"deployed_at" TIMESTAMP NOT NULL,
|
||||
"deployed_at_height" INT NOT NULL
|
||||
);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS "brc20_tick_entry_states" (
|
||||
"tick" TEXT NOT NULL,
|
||||
"block_height" INT NOT NULL,
|
||||
"minted_amount" DECIMAL NOT NULL,
|
||||
"burned_amount" DECIMAL NOT NULL,
|
||||
"completed_at" TIMESTAMP,
|
||||
"completed_at_height" INT,
|
||||
PRIMARY KEY ("tick", "block_height")
|
||||
);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS "brc20_event_deploys" (
|
||||
"id" BIGINT PRIMARY KEY NOT NULL,
|
||||
"inscription_id" TEXT NOT NULL,
|
||||
"inscription_number" BIGINT NOT NULL,
|
||||
"tick" TEXT NOT NULL, -- lowercase of original_tick
|
||||
"original_tick" TEXT NOT NULL,
|
||||
"tx_hash" TEXT NOT NULL,
|
||||
"block_height" INT NOT NULL,
|
||||
"tx_index" INT NOT NULL,
|
||||
"timestamp" TIMESTAMP NOT NULL,
|
||||
|
||||
"pkscript" TEXT NOT NULL,
|
||||
"satpoint" TEXT NOT NULL,
|
||||
"total_supply" DECIMAL NOT NULL,
|
||||
"decimals" SMALLINT NOT NULL,
|
||||
"limit_per_mint" DECIMAL NOT NULL,
|
||||
"is_self_mint" BOOLEAN NOT NULL
|
||||
);
|
||||
CREATE INDEX IF NOT EXISTS brc20_event_deploys_block_height_idx ON "brc20_event_deploys" USING BTREE ("block_height");
|
||||
|
||||
CREATE TABLE IF NOT EXISTS "brc20_event_mints" (
|
||||
"id" BIGINT PRIMARY KEY NOT NULL,
|
||||
"inscription_id" TEXT NOT NULL,
|
||||
"inscription_number" BIGINT NOT NULL,
|
||||
"tick" TEXT NOT NULL, -- lowercase of original_tick
|
||||
"original_tick" TEXT NOT NULL,
|
||||
"tx_hash" TEXT NOT NULL,
|
||||
"block_height" INT NOT NULL,
|
||||
"tx_index" INT NOT NULL,
|
||||
"timestamp" TIMESTAMP NOT NULL,
|
||||
|
||||
"pkscript" TEXT NOT NULL,
|
||||
"satpoint" TEXT NOT NULL,
|
||||
"amount" DECIMAL NOT NULL,
|
||||
"parent_id" TEXT -- requires parent deploy inscription id if minting a self-mint ticker
|
||||
);
|
||||
CREATE INDEX IF NOT EXISTS brc20_event_mints_block_height_idx ON "brc20_event_mints" USING BTREE ("block_height");
|
||||
|
||||
CREATE TABLE IF NOT EXISTS "brc20_event_inscribe_transfers" (
|
||||
"id" BIGINT PRIMARY KEY NOT NULL,
|
||||
"inscription_id" TEXT NOT NULL,
|
||||
"inscription_number" BIGINT NOT NULL,
|
||||
"tick" TEXT NOT NULL, -- lowercase of original_tick
|
||||
"original_tick" TEXT NOT NULL,
|
||||
"tx_hash" TEXT NOT NULL,
|
||||
"block_height" INT NOT NULL,
|
||||
"tx_index" INT NOT NULL,
|
||||
"timestamp" TIMESTAMP NOT NULL,
|
||||
|
||||
"pkscript" TEXT NOT NULL,
|
||||
"satpoint" TEXT NOT NULL,
|
||||
"output_index" INT NOT NULL,
|
||||
"sats_amount" BIGINT NOT NULL,
|
||||
"amount" DECIMAL NOT NULL
|
||||
);
|
||||
CREATE INDEX IF NOT EXISTS brc20_event_inscribe_transfers_block_height_idx ON "brc20_event_inscribe_transfers" USING BTREE ("block_height");
|
||||
CREATE INDEX IF NOT EXISTS brc20_event_inscribe_transfers_inscription_id_idx ON "brc20_event_inscribe_transfers" USING BTREE ("inscription_id"); -- used for validating transfer transfer events
|
||||
|
||||
CREATE TABLE IF NOT EXISTS "brc20_event_transfer_transfers" (
|
||||
"id" BIGINT PRIMARY KEY NOT NULL,
|
||||
"inscription_id" TEXT NOT NULL,
|
||||
"inscription_number" BIGINT NOT NULL,
|
||||
"tick" TEXT NOT NULL, -- lowercase of original_tick
|
||||
"original_tick" TEXT NOT NULL,
|
||||
"tx_hash" TEXT NOT NULL,
|
||||
"block_height" INT NOT NULL,
|
||||
"tx_index" INT NOT NULL,
|
||||
"timestamp" TIMESTAMP NOT NULL,
|
||||
|
||||
"from_pkscript" TEXT NOT NULL,
|
||||
"from_satpoint" TEXT NOT NULL,
|
||||
"from_input_index" INT NOT NULL,
|
||||
"to_pkscript" TEXT NOT NULL,
|
||||
"to_satpoint" TEXT NOT NULL,
|
||||
"to_output_index" INT NOT NULL,
|
||||
"spent_as_fee" BOOLEAN NOT NULL,
|
||||
"amount" DECIMAL NOT NULL
|
||||
);
|
||||
CREATE INDEX IF NOT EXISTS brc20_event_transfer_transfers_block_height_idx ON "brc20_event_transfer_transfers" USING BTREE ("block_height");
|
||||
|
||||
CREATE TABLE IF NOT EXISTS "brc20_balances" (
|
||||
"pkscript" TEXT NOT NULL,
|
||||
"block_height" INT NOT NULL,
|
||||
"tick" TEXT NOT NULL,
|
||||
"overall_balance" DECIMAL NOT NULL, -- overall balance = available_balance + transferable_balance
|
||||
"available_balance" DECIMAL NOT NULL,
|
||||
PRIMARY KEY ("pkscript", "tick", "block_height")
|
||||
);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS "brc20_inscription_entries" (
|
||||
"id" TEXT NOT NULL PRIMARY KEY,
|
||||
"number" BIGINT NOT NULL,
|
||||
"sequence_number" BIGINT NOT NULL,
|
||||
"delegate" TEXT, -- delegate inscription id
|
||||
"metadata" BYTEA,
|
||||
"metaprotocol" TEXT,
|
||||
"parents" TEXT[], -- parent inscription id, 0.14 only supports 1 parent per inscription
|
||||
"pointer" BIGINT,
|
||||
"content" JSONB, -- can use jsonb because we only track brc20 inscriptions
|
||||
"content_encoding" TEXT,
|
||||
"content_type" TEXT,
|
||||
"cursed" BOOLEAN NOT NULL, -- inscriptions after jubilee are no longer cursed in 0.14, which affects inscription number
|
||||
"cursed_for_brc20" BOOLEAN NOT NULL, -- however, inscriptions that would normally be cursed are still considered cursed for brc20
|
||||
"created_at" TIMESTAMP NOT NULL,
|
||||
"created_at_height" INT NOT NULL
|
||||
);
|
||||
CREATE INDEX IF NOT EXISTS brc20_inscription_entries_id_number_idx ON "brc20_inscription_entries" USING BTREE ("id", "number");
|
||||
|
||||
CREATE TABLE IF NOT EXISTS "brc20_inscription_entry_states" (
|
||||
"id" TEXT NOT NULL,
|
||||
"block_height" INT NOT NULL,
|
||||
"transfer_count" INT NOT NULL,
|
||||
PRIMARY KEY ("id", "block_height")
|
||||
);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS "brc20_inscription_transfers" (
|
||||
"inscription_id" TEXT NOT NULL,
|
||||
"inscription_number" BIGINT NOT NULL,
|
||||
"inscription_sequence_number" BIGINT NOT NULL,
|
||||
"block_height" INT NOT NULL,
|
||||
"tx_index" INT NOT NULL,
|
||||
"tx_hash" TEXT NOT NULL,
|
||||
"from_input_index" INT NOT NULL,
|
||||
"old_satpoint_tx_hash" TEXT,
|
||||
"old_satpoint_out_idx" INT,
|
||||
"old_satpoint_offset" BIGINT,
|
||||
"new_satpoint_tx_hash" TEXT,
|
||||
"new_satpoint_out_idx" INT,
|
||||
"new_satpoint_offset" BIGINT,
|
||||
"new_pkscript" TEXT NOT NULL,
|
||||
"new_output_value" BIGINT NOT NULL,
|
||||
"sent_as_fee" BOOLEAN NOT NULL,
|
||||
"transfer_count" INT NOT NULL,
|
||||
PRIMARY KEY ("inscription_id", "block_height", "tx_index")
|
||||
);
|
||||
CREATE INDEX IF NOT EXISTS brc20_inscription_transfers_block_height_tx_index_idx ON "brc20_inscription_transfers" USING BTREE ("block_height", "tx_index");
|
||||
CREATE INDEX IF NOT EXISTS brc20_inscription_transfers_new_satpoint_idx ON "brc20_inscription_transfers" USING BTREE ("new_satpoint_tx_hash", "new_satpoint_out_idx", "new_satpoint_offset");
|
||||
|
||||
COMMIT;
|
||||
@@ -1,263 +0,0 @@
|
||||
-- name: GetTransferableTransfersByPkScript :many
|
||||
SELECT *
|
||||
FROM "brc20_event_inscribe_transfers"
|
||||
WHERE
|
||||
pkscript = $1
|
||||
AND "brc20_event_inscribe_transfers"."block_height" <= $2
|
||||
AND NOT EXISTS (
|
||||
SELECT NULL
|
||||
FROM "brc20_event_transfer_transfers"
|
||||
WHERE "brc20_event_transfer_transfers"."inscription_id" = "brc20_event_inscribe_transfers"."inscription_id"
|
||||
)
|
||||
ORDER BY "brc20_event_inscribe_transfers"."block_height" DESC;
|
||||
|
||||
-- name: GetBalancesByPkScript :many
|
||||
WITH balances AS (
|
||||
SELECT DISTINCT ON (tick) * FROM brc20_balances WHERE pkscript = $1 AND block_height <= $2 ORDER BY tick, overall_balance DESC
|
||||
)
|
||||
SELECT * FROM balances WHERE overall_balance > 0;
|
||||
|
||||
-- name: GetBalancesByTick :many
|
||||
WITH balances AS (
|
||||
SELECT DISTINCT ON (pkscript) * FROM brc20_balances WHERE tick = $1 AND block_height <= $2 ORDER BY pkscript, block_height DESC
|
||||
)
|
||||
SELECT * FROM balances WHERE overall_balance > 0;
|
||||
|
||||
-- name: GetDeployEventByTick :one
|
||||
SELECT * FROM brc20_event_deploys WHERE tick = $1;
|
||||
|
||||
-- name: GetFirstLastInscriptionNumberByTick :one
|
||||
SELECT
|
||||
COALESCE(MIN("inscription_number"), -1)::BIGINT AS "first_inscription_number",
|
||||
COALESCE(MAX("inscription_number"), -1)::BIGINT AS "last_inscription_number"
|
||||
FROM (
|
||||
SELECT inscription_number FROM "brc20_event_mints" WHERE "brc20_event_mints"."tick" = $1
|
||||
UNION ALL
|
||||
SELECT inscription_number FROM "brc20_event_inscribe_transfers" WHERE "brc20_event_inscribe_transfers"."tick" = $1
|
||||
UNION ALL
|
||||
SELECT inscription_number FROM "brc20_event_transfer_transfers" WHERE "brc20_event_transfer_transfers"."tick" = $1
|
||||
) as events;
|
||||
-- WITH
|
||||
-- "first_mint" AS (SELECT "inscription_number" FROM "brc20_event_mints" WHERE "brc20_event_mints".tick = $1 ORDER BY "id" ASC LIMIT 1),
|
||||
-- "latest_mint" AS (SELECT "inscription_number" FROM "brc20_event_mints" WHERE "brc20_event_mints".tick = $1 ORDER BY "id" DESC LIMIT 1),
|
||||
-- "first_inscribe_transfer" AS (SELECT "inscription_number" FROM "brc20_event_inscribe_transfers" WHERE "brc20_event_inscribe_transfers".tick = $1 ORDER BY "id" ASC LIMIT 1),
|
||||
-- "latest_inscribe_transfer" AS (SELECT "inscription_number" FROM "brc20_event_inscribe_transfers" WHERE "brc20_event_inscribe_transfers".tick = $1 ORDER BY "id" DESC LIMIT 1)
|
||||
-- SELECT
|
||||
-- COALESCE(
|
||||
-- LEAST(
|
||||
-- (SELECT "inscription_number" FROM "first_mint"),
|
||||
-- (SELECT "inscription_number" FROM "first_inscribe_transfer")
|
||||
-- ),
|
||||
-- -1
|
||||
-- ) AS "first_inscription_number",
|
||||
-- COALESCE(
|
||||
-- GREATEST(
|
||||
-- (SELECT "inscription_number" FROM "latest_mint"),
|
||||
-- (SELECT "inscription_number" FROM "latest_inscribe_transfer")
|
||||
-- ),
|
||||
-- -1
|
||||
-- ) AS "last_inscription_number";
|
||||
|
||||
-- name: GetTickEntriesByTicksAndHeight :many
|
||||
WITH "states" AS (
|
||||
-- select latest state
|
||||
SELECT DISTINCT ON ("tick") * FROM "brc20_tick_entry_states" WHERE "tick" = ANY(@ticks::text[]) AND block_height <= @height ORDER BY "tick", "block_height" DESC
|
||||
)
|
||||
SELECT * FROM "brc20_tick_entries"
|
||||
LEFT JOIN "states" ON "brc20_tick_entries"."tick" = "states"."tick"
|
||||
WHERE "brc20_tick_entries"."tick" = ANY(@ticks::text[]) AND deployed_at_height <= @height;;
|
||||
|
||||
-- name: GetDeployEvents :many
|
||||
SELECT * FROM "brc20_event_deploys"
|
||||
WHERE (
|
||||
@filter_pk_script::BOOLEAN = FALSE -- if @filter_pk_script is TRUE, apply pk_script filter
|
||||
OR pkscript = @pk_script
|
||||
) AND (
|
||||
@filter_ticker::BOOLEAN = FALSE -- if @filter_ticker is TRUE, apply ticker filter
|
||||
OR tick = @ticker
|
||||
) AND (
|
||||
@block_height::INT = 0 OR block_height = @block_height::INT -- if @block_height > 0, apply block_height filter
|
||||
);
|
||||
|
||||
-- name: GetMintEvents :many
|
||||
SELECT * FROM "brc20_event_mints"
|
||||
WHERE (
|
||||
@filter_pk_script::BOOLEAN = FALSE -- if @filter_pk_script is TRUE, apply pk_script filter
|
||||
OR pkscript = @pk_script
|
||||
) AND (
|
||||
@filter_ticker::BOOLEAN = FALSE -- if @filter_ticker is TRUE, apply ticker filter
|
||||
OR tick = @ticker
|
||||
) AND (
|
||||
@block_height::INT = 0 OR block_height = @block_height::INT -- if @block_height > 0, apply block_height filter
|
||||
);
|
||||
|
||||
-- name: GetInscribeTransferEvents :many
|
||||
SELECT * FROM "brc20_event_inscribe_transfers"
|
||||
WHERE (
|
||||
@filter_pk_script::BOOLEAN = FALSE -- if @filter_pk_script is TRUE, apply pk_script filter
|
||||
OR pkscript = @pk_script
|
||||
) AND (
|
||||
@filter_ticker::BOOLEAN = FALSE -- if @filter_ticker is TRUE, apply ticker filter
|
||||
OR tick = @ticker
|
||||
) AND (
|
||||
@block_height::INT = 0 OR block_height = @block_height::INT -- if @block_height > 0, apply block_height filter
|
||||
);
|
||||
|
||||
-- name: GetTransferTransferEvents :many
|
||||
SELECT * FROM "brc20_event_transfer_transfers"
|
||||
WHERE (
|
||||
@filter_pk_script::BOOLEAN = FALSE -- if @filter_pk_script is TRUE, apply pk_script filter
|
||||
OR from_pkscript = @pk_script
|
||||
OR to_pkscript = @pk_script
|
||||
) AND (
|
||||
@filter_ticker::BOOLEAN = FALSE -- if @filter_ticker is TRUE, apply ticker filter
|
||||
OR tick = @ticker
|
||||
) AND (
|
||||
@block_height::INT = 0 OR block_height = @block_height::INT -- if @block_height > 0, apply block_height filter
|
||||
);
|
||||
|
||||
-- name: GetLatestIndexedBlock :one
|
||||
SELECT * FROM "brc20_indexed_blocks" ORDER BY "height" DESC LIMIT 1;
|
||||
|
||||
-- name: GetIndexedBlockByHeight :one
|
||||
SELECT * FROM "brc20_indexed_blocks" WHERE "height" = $1;
|
||||
|
||||
-- name: GetLatestProcessorStats :one
|
||||
SELECT * FROM "brc20_processor_stats" ORDER BY "block_height" DESC LIMIT 1;
|
||||
|
||||
-- name: GetInscriptionTransfersInOutPoints :many
|
||||
SELECT "it".*, "ie"."content" FROM (
|
||||
SELECT
|
||||
unnest(@tx_hash_arr::text[]) AS "tx_hash",
|
||||
unnest(@tx_out_idx_arr::int[]) AS "tx_out_idx"
|
||||
) "inputs"
|
||||
INNER JOIN "brc20_inscription_transfers" it ON "inputs"."tx_hash" = "it"."new_satpoint_tx_hash" AND "inputs"."tx_out_idx" = "it"."new_satpoint_out_idx"
|
||||
LEFT JOIN "brc20_inscription_entries" ie ON "it"."inscription_id" = "ie"."id";
|
||||
;
|
||||
|
||||
-- name: GetInscriptionEntriesByIds :many
|
||||
WITH "states" AS (
|
||||
-- select latest state
|
||||
SELECT DISTINCT ON ("id") * FROM "brc20_inscription_entry_states" WHERE "id" = ANY(@inscription_ids::text[]) ORDER BY "id", "block_height" DESC
|
||||
)
|
||||
SELECT * FROM "brc20_inscription_entries"
|
||||
LEFT JOIN "states" ON "brc20_inscription_entries"."id" = "states"."id"
|
||||
WHERE "brc20_inscription_entries"."id" = ANY(@inscription_ids::text[]);
|
||||
|
||||
-- name: GetTickEntriesByTicks :many
|
||||
WITH "states" AS (
|
||||
-- select latest state
|
||||
SELECT DISTINCT ON ("tick") * FROM "brc20_tick_entry_states" WHERE "tick" = ANY(@ticks::text[]) ORDER BY "tick", "block_height" DESC
|
||||
)
|
||||
SELECT * FROM "brc20_tick_entries"
|
||||
LEFT JOIN "states" ON "brc20_tick_entries"."tick" = "states"."tick"
|
||||
WHERE "brc20_tick_entries"."tick" = ANY(@ticks::text[]);
|
||||
|
||||
-- name: GetInscriptionNumbersByIds :many
|
||||
SELECT id, number FROM "brc20_inscription_entries" WHERE "id" = ANY(@inscription_ids::text[]);
|
||||
|
||||
-- name: GetInscriptionParentsByIds :many
|
||||
SELECT id, parents FROM "brc20_inscription_entries" WHERE "id" = ANY(@inscription_ids::text[]);
|
||||
|
||||
-- name: GetLatestEventIds :one
|
||||
WITH "latest_deploy_id" AS (
|
||||
SELECT "id" FROM "brc20_event_deploys" ORDER BY "id" DESC LIMIT 1
|
||||
),
|
||||
"latest_mint_id" AS (
|
||||
SELECT "id" FROM "brc20_event_mints" ORDER BY "id" DESC LIMIT 1
|
||||
),
|
||||
"latest_inscribe_transfer_id" AS (
|
||||
SELECT "id" FROM "brc20_event_inscribe_transfers" ORDER BY "id" DESC LIMIT 1
|
||||
),
|
||||
"latest_transfer_transfer_id" AS (
|
||||
SELECT "id" FROM "brc20_event_transfer_transfers" ORDER BY "id" DESC LIMIT 1
|
||||
)
|
||||
SELECT
|
||||
COALESCE((SELECT "id" FROM "latest_deploy_id"), -1) AS "event_deploy_id",
|
||||
COALESCE((SELECT "id" FROM "latest_mint_id"), -1) AS "event_mint_id",
|
||||
COALESCE((SELECT "id" FROM "latest_inscribe_transfer_id"), -1) AS "event_inscribe_transfer_id",
|
||||
COALESCE((SELECT "id" FROM "latest_transfer_transfer_id"), -1) AS "event_transfer_transfer_id";
|
||||
|
||||
-- name: GetBalancesBatchAtHeight :many
|
||||
SELECT DISTINCT ON ("brc20_balances"."pkscript", "brc20_balances"."tick") "brc20_balances".* FROM "brc20_balances"
|
||||
INNER JOIN (
|
||||
SELECT
|
||||
unnest(@pkscript_arr::text[]) AS "pkscript",
|
||||
unnest(@tick_arr::text[]) AS "tick"
|
||||
) "queries" ON "brc20_balances"."pkscript" = "queries"."pkscript" AND "brc20_balances"."tick" = "queries"."tick" AND "brc20_balances"."block_height" <= @block_height
|
||||
ORDER BY "brc20_balances"."pkscript", "brc20_balances"."tick", "block_height" DESC;
|
||||
|
||||
-- name: GetEventInscribeTransfersByInscriptionIds :many
|
||||
SELECT * FROM "brc20_event_inscribe_transfers" WHERE "inscription_id" = ANY(@inscription_ids::text[]);
|
||||
|
||||
-- name: CreateIndexedBlock :exec
|
||||
INSERT INTO "brc20_indexed_blocks" ("height", "hash", "event_hash", "cumulative_event_hash") VALUES ($1, $2, $3, $4);
|
||||
|
||||
-- name: CreateProcessorStats :exec
|
||||
INSERT INTO "brc20_processor_stats" ("block_height", "cursed_inscription_count", "blessed_inscription_count", "lost_sats") VALUES ($1, $2, $3, $4);
|
||||
|
||||
-- name: CreateTickEntries :batchexec
|
||||
INSERT INTO "brc20_tick_entries" ("tick", "original_tick", "total_supply", "decimals", "limit_per_mint", "is_self_mint", "deploy_inscription_id", "deployed_at", "deployed_at_height") VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9);
|
||||
|
||||
-- name: CreateTickEntryStates :batchexec
|
||||
INSERT INTO "brc20_tick_entry_states" ("tick", "block_height", "minted_amount", "burned_amount", "completed_at", "completed_at_height") VALUES ($1, $2, $3, $4, $5, $6);
|
||||
|
||||
-- name: CreateInscriptionEntries :batchexec
|
||||
INSERT INTO "brc20_inscription_entries" ("id", "number", "sequence_number", "delegate", "metadata", "metaprotocol", "parents", "pointer", "content", "content_encoding", "content_type", "cursed", "cursed_for_brc20", "created_at", "created_at_height") VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13, $14, $15);
|
||||
|
||||
-- name: CreateInscriptionEntryStates :batchexec
|
||||
INSERT INTO "brc20_inscription_entry_states" ("id", "block_height", "transfer_count") VALUES ($1, $2, $3);
|
||||
|
||||
-- name: CreateInscriptionTransfers :batchexec
|
||||
INSERT INTO "brc20_inscription_transfers" ("inscription_id", "inscription_number", "inscription_sequence_number", "block_height", "tx_index", "tx_hash", "from_input_index", "old_satpoint_tx_hash", "old_satpoint_out_idx", "old_satpoint_offset", "new_satpoint_tx_hash", "new_satpoint_out_idx", "new_satpoint_offset", "new_pkscript", "new_output_value", "sent_as_fee", "transfer_count") VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13, $14, $15, $16, $17);
|
||||
|
||||
-- name: CreateEventDeploys :batchexec
|
||||
INSERT INTO "brc20_event_deploys" ("id", "inscription_id", "inscription_number", "tick", "original_tick", "tx_hash", "block_height", "tx_index", "timestamp", "pkscript", "satpoint", "total_supply", "decimals", "limit_per_mint", "is_self_mint") VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13, $14, $15);
|
||||
|
||||
-- name: CreateEventMints :batchexec
|
||||
INSERT INTO "brc20_event_mints" ("id", "inscription_id", "inscription_number", "tick", "original_tick", "tx_hash", "block_height", "tx_index", "timestamp", "pkscript", "satpoint", "amount", "parent_id") VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13);
|
||||
|
||||
-- name: CreateEventInscribeTransfers :batchexec
|
||||
INSERT INTO "brc20_event_inscribe_transfers" ("id", "inscription_id", "inscription_number", "tick", "original_tick", "tx_hash", "block_height", "tx_index", "timestamp", "pkscript", "satpoint", "output_index", "sats_amount", "amount") VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13, $14);
|
||||
|
||||
-- name: CreateEventTransferTransfers :batchexec
|
||||
INSERT INTO "brc20_event_transfer_transfers" ("id", "inscription_id", "inscription_number", "tick", "original_tick", "tx_hash", "block_height", "tx_index", "timestamp", "from_pkscript", "from_satpoint", "from_input_index", "to_pkscript", "to_satpoint", "to_output_index", "spent_as_fee", "amount") VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13, $14, $15, $16, $17);
|
||||
|
||||
-- name: CreateBalances :batchexec
|
||||
INSERT INTO "brc20_balances" ("pkscript", "block_height", "tick", "overall_balance", "available_balance") VALUES ($1, $2, $3, $4, $5);
|
||||
|
||||
-- name: DeleteIndexedBlocksSinceHeight :exec
|
||||
DELETE FROM "brc20_indexed_blocks" WHERE "height" >= $1;
|
||||
|
||||
-- name: DeleteProcessorStatsSinceHeight :exec
|
||||
DELETE FROM "brc20_processor_stats" WHERE "block_height" >= $1;
|
||||
|
||||
-- name: DeleteTickEntriesSinceHeight :exec
|
||||
DELETE FROM "brc20_tick_entries" WHERE "deployed_at_height" >= $1;
|
||||
|
||||
-- name: DeleteTickEntryStatesSinceHeight :exec
|
||||
DELETE FROM "brc20_tick_entry_states" WHERE "block_height" >= $1;
|
||||
|
||||
-- name: DeleteEventDeploysSinceHeight :exec
|
||||
DELETE FROM "brc20_event_deploys" WHERE "block_height" >= $1;
|
||||
|
||||
-- name: DeleteEventMintsSinceHeight :exec
|
||||
DELETE FROM "brc20_event_mints" WHERE "block_height" >= $1;
|
||||
|
||||
-- name: DeleteEventInscribeTransfersSinceHeight :exec
|
||||
DELETE FROM "brc20_event_inscribe_transfers" WHERE "block_height" >= $1;
|
||||
|
||||
-- name: DeleteEventTransferTransfersSinceHeight :exec
|
||||
DELETE FROM "brc20_event_transfer_transfers" WHERE "block_height" >= $1;
|
||||
|
||||
-- name: DeleteBalancesSinceHeight :exec
|
||||
DELETE FROM "brc20_balances" WHERE "block_height" >= $1;
|
||||
|
||||
-- name: DeleteInscriptionEntriesSinceHeight :exec
|
||||
DELETE FROM "brc20_inscription_entries" WHERE "created_at_height" >= $1;
|
||||
|
||||
-- name: DeleteInscriptionEntryStatesSinceHeight :exec
|
||||
DELETE FROM "brc20_inscription_entry_states" WHERE "block_height" >= $1;
|
||||
|
||||
-- name: DeleteInscriptionTransfersSinceHeight :exec
|
||||
DELETE FROM "brc20_inscription_transfers" WHERE "block_height" >= $1;
|
||||
@@ -1,5 +0,0 @@
|
||||
-- name: GetLatestIndexerState :one
|
||||
SELECT * FROM brc20_indexer_states ORDER BY created_at DESC LIMIT 1;
|
||||
|
||||
-- name: CreateIndexerState :exec
|
||||
INSERT INTO brc20_indexer_states (client_version, network, db_version, event_hash_version) VALUES ($1, $2, $3, $4);
|
||||
@@ -1,69 +0,0 @@
|
||||
package brc20
|
||||
|
||||
import (
|
||||
"encoding/hex"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"github.com/gaze-network/indexer-network/modules/brc20/internal/entity"
|
||||
"github.com/samber/lo"
|
||||
)
|
||||
|
||||
const eventHashSeparator = "|"
|
||||
|
||||
func getEventDeployString(event *entity.EventDeploy) string {
|
||||
var sb strings.Builder
|
||||
sb.WriteString("deploy-inscribe;")
|
||||
sb.WriteString(event.InscriptionId.String() + ";")
|
||||
sb.WriteString(hex.EncodeToString(event.PkScript) + ";")
|
||||
sb.WriteString(event.Tick + ";")
|
||||
sb.WriteString(event.OriginalTick + ";")
|
||||
sb.WriteString(event.TotalSupply.StringFixed(int32(event.Decimals)) + ";")
|
||||
sb.WriteString(strconv.Itoa(int(event.Decimals)) + ";")
|
||||
sb.WriteString(event.LimitPerMint.StringFixed(int32(event.Decimals)) + ";")
|
||||
sb.WriteString(lo.Ternary(event.IsSelfMint, "True", "False"))
|
||||
return sb.String()
|
||||
}
|
||||
|
||||
func getEventMintString(event *entity.EventMint, decimals uint16) string {
|
||||
var sb strings.Builder
|
||||
var parentId string
|
||||
if event.ParentId != nil {
|
||||
parentId = event.ParentId.String()
|
||||
}
|
||||
sb.WriteString("mint-inscribe;")
|
||||
sb.WriteString(event.InscriptionId.String() + ";")
|
||||
sb.WriteString(hex.EncodeToString(event.PkScript) + ";")
|
||||
sb.WriteString(event.Tick + ";")
|
||||
sb.WriteString(event.OriginalTick + ";")
|
||||
sb.WriteString(event.Amount.StringFixed(int32(decimals)) + ";")
|
||||
sb.WriteString(parentId)
|
||||
return sb.String()
|
||||
}
|
||||
|
||||
func getEventInscribeTransferString(event *entity.EventInscribeTransfer, decimals uint16) string {
|
||||
var sb strings.Builder
|
||||
sb.WriteString("inscribe-transfer;")
|
||||
sb.WriteString(event.InscriptionId.String() + ";")
|
||||
sb.WriteString(hex.EncodeToString(event.PkScript) + ";")
|
||||
sb.WriteString(event.Tick + ";")
|
||||
sb.WriteString(event.OriginalTick + ";")
|
||||
sb.WriteString(event.Amount.StringFixed(int32(decimals)))
|
||||
return sb.String()
|
||||
}
|
||||
|
||||
func getEventTransferTransferString(event *entity.EventTransferTransfer, decimals uint16) string {
|
||||
var sb strings.Builder
|
||||
sb.WriteString("transfer-transfer;")
|
||||
sb.WriteString(event.InscriptionId.String() + ";")
|
||||
sb.WriteString(hex.EncodeToString(event.FromPkScript) + ";")
|
||||
if event.SpentAsFee {
|
||||
sb.WriteString(";")
|
||||
} else {
|
||||
sb.WriteString(hex.EncodeToString(event.ToPkScript) + ";")
|
||||
}
|
||||
sb.WriteString(event.Tick + ";")
|
||||
sb.WriteString(event.OriginalTick + ";")
|
||||
sb.WriteString(event.Amount.StringFixed(int32(decimals)))
|
||||
return sb.String()
|
||||
}
|
||||
@@ -1,16 +0,0 @@
|
||||
package brc20
|
||||
|
||||
import "github.com/gaze-network/indexer-network/common"
|
||||
|
||||
var selfMintActivationHeights = map[common.Network]uint64{
|
||||
common.NetworkMainnet: 837090,
|
||||
common.NetworkTestnet: 837090,
|
||||
}
|
||||
|
||||
func isSelfMintActivated(height uint64, network common.Network) bool {
|
||||
activationHeight, ok := selfMintActivationHeights[network]
|
||||
if !ok {
|
||||
return false
|
||||
}
|
||||
return height >= activationHeight
|
||||
}
|
||||
@@ -1,21 +0,0 @@
|
||||
package brc20
|
||||
|
||||
type Operation string
|
||||
|
||||
const (
|
||||
OperationDeploy Operation = "deploy"
|
||||
OperationMint Operation = "mint"
|
||||
OperationTransfer Operation = "transfer"
|
||||
)
|
||||
|
||||
func (o Operation) IsValid() bool {
|
||||
switch o {
|
||||
case OperationDeploy, OperationMint, OperationTransfer:
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func (o Operation) String() string {
|
||||
return string(o)
|
||||
}
|
||||
@@ -1,173 +0,0 @@
|
||||
package brc20
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"math"
|
||||
"math/big"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"github.com/cockroachdb/errors"
|
||||
"github.com/gaze-network/indexer-network/modules/brc20/internal/entity"
|
||||
"github.com/shopspring/decimal"
|
||||
)
|
||||
|
||||
type rawPayload struct {
|
||||
P string // required
|
||||
Op string `json:"op"` // required
|
||||
Tick string `json:"tick"` // required
|
||||
|
||||
// for deploy operations
|
||||
Max string `json:"max"` // required
|
||||
Lim *string `json:"lim"`
|
||||
Dec *string `json:"dec"`
|
||||
SelfMint *string `json:"self_mint"`
|
||||
|
||||
// for mint/transfer operations
|
||||
Amt string `json:"amt"` // required
|
||||
}
|
||||
|
||||
type Payload struct {
|
||||
Transfer *entity.InscriptionTransfer
|
||||
P string
|
||||
Op Operation
|
||||
Tick string // lower-cased tick
|
||||
OriginalTick string // original tick before lower-cased
|
||||
|
||||
// for deploy operations
|
||||
Max decimal.Decimal
|
||||
Lim decimal.Decimal
|
||||
Dec uint16
|
||||
SelfMint bool
|
||||
|
||||
// for mint/transfer operations
|
||||
Amt decimal.Decimal
|
||||
}
|
||||
|
||||
var (
|
||||
ErrInvalidProtocol = errors.New("invalid protocol: must be 'brc20'")
|
||||
ErrInvalidOperation = errors.New("invalid operation for brc20: must be one of 'deploy', 'mint', or 'transfer'")
|
||||
ErrInvalidTickLength = errors.New("invalid tick length: must be 4 or 5 bytes")
|
||||
ErrEmptyTick = errors.New("empty tick")
|
||||
ErrEmptyMax = errors.New("empty max")
|
||||
ErrInvalidMax = errors.New("invalid max")
|
||||
ErrInvalidDec = errors.New("invalid dec")
|
||||
ErrInvalidSelfMint = errors.New("invalid self_mint")
|
||||
ErrInvalidAmt = errors.New("invalid amt")
|
||||
ErrNumberOverflow = errors.New("number overflow: max value is (2^64-1)")
|
||||
)
|
||||
|
||||
func ParsePayload(transfer *entity.InscriptionTransfer) (*Payload, error) {
|
||||
var p rawPayload
|
||||
err := json.Unmarshal(transfer.Content, &p)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "failed to unmarshal payload as json")
|
||||
}
|
||||
|
||||
if p.P != "brc-20" {
|
||||
return nil, errors.WithStack(ErrInvalidProtocol)
|
||||
}
|
||||
if !Operation(p.Op).IsValid() {
|
||||
return nil, errors.WithStack(ErrInvalidOperation)
|
||||
}
|
||||
if p.Tick == "" {
|
||||
return nil, errors.WithStack(ErrEmptyTick)
|
||||
}
|
||||
if len(p.Tick) != 4 && len(p.Tick) != 5 {
|
||||
return nil, errors.WithStack(ErrInvalidTickLength)
|
||||
}
|
||||
|
||||
parsed := Payload{
|
||||
Transfer: transfer,
|
||||
P: p.P,
|
||||
Op: Operation(p.Op),
|
||||
Tick: strings.ToLower(p.Tick),
|
||||
OriginalTick: p.Tick,
|
||||
}
|
||||
|
||||
switch parsed.Op {
|
||||
case OperationDeploy:
|
||||
if p.Max == "" {
|
||||
return nil, errors.WithStack(ErrEmptyMax)
|
||||
}
|
||||
var rawDec string
|
||||
if p.Dec != nil {
|
||||
rawDec = *p.Dec
|
||||
}
|
||||
if rawDec == "" {
|
||||
rawDec = "18"
|
||||
}
|
||||
dec, ok := strconv.ParseUint(rawDec, 10, 16)
|
||||
if ok != nil {
|
||||
return nil, errors.Wrap(ok, "failed to parse dec")
|
||||
}
|
||||
if dec > 18 {
|
||||
return nil, errors.WithStack(ErrInvalidDec)
|
||||
}
|
||||
parsed.Dec = uint16(dec)
|
||||
|
||||
max, err := parseNumericString(p.Max, dec)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "failed to parse max")
|
||||
}
|
||||
parsed.Max = max
|
||||
|
||||
limit := max
|
||||
if p.Lim != nil {
|
||||
limit, err = parseNumericString(*p.Lim, dec)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "failed to parse lim")
|
||||
}
|
||||
}
|
||||
parsed.Lim = limit
|
||||
|
||||
// 5-bytes ticks are self-mint only
|
||||
if len(parsed.OriginalTick) == 5 {
|
||||
if p.SelfMint == nil || *p.SelfMint != "true" {
|
||||
return nil, errors.WithStack(ErrInvalidSelfMint)
|
||||
}
|
||||
// infinite mints if tick is self-mint, and max is set to 0
|
||||
if parsed.Max.IsZero() {
|
||||
parsed.Max = maxNumber
|
||||
if parsed.Lim.IsZero() {
|
||||
parsed.Lim = maxNumber
|
||||
}
|
||||
}
|
||||
}
|
||||
if parsed.Max.IsZero() {
|
||||
return nil, errors.WithStack(ErrInvalidMax)
|
||||
}
|
||||
case OperationMint, OperationTransfer:
|
||||
if p.Amt == "" {
|
||||
return nil, errors.WithStack(ErrInvalidAmt)
|
||||
}
|
||||
// NOTE: check tick decimals after parsing payload
|
||||
amt, err := parseNumericString(p.Amt, 18)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "failed to parse amt")
|
||||
}
|
||||
parsed.Amt = amt
|
||||
default:
|
||||
return nil, errors.WithStack(ErrInvalidOperation)
|
||||
}
|
||||
return &parsed, nil
|
||||
}
|
||||
|
||||
// max number for all numeric fields (except dec) is (2^64-1)
|
||||
var (
|
||||
maxNumber = decimal.NewFromBigInt(new(big.Int).SetUint64(math.MaxUint64), 0)
|
||||
)
|
||||
|
||||
func parseNumericString(s string, maxDec uint64) (decimal.Decimal, error) {
|
||||
d, err := decimal.NewFromString(s)
|
||||
if err != nil {
|
||||
return decimal.Decimal{}, errors.Wrap(err, "failed to parse decimal number")
|
||||
}
|
||||
if -d.Exponent() > int32(maxDec) {
|
||||
return decimal.Decimal{}, errors.Errorf("cannot parse decimal number: too many decimal points: expected %d got %d", maxDec, d.Exponent())
|
||||
}
|
||||
if d.GreaterThan(maxNumber) {
|
||||
return decimal.Decimal{}, errors.WithStack(ErrNumberOverflow)
|
||||
}
|
||||
return d, nil
|
||||
}
|
||||
@@ -1,81 +0,0 @@
|
||||
package datagateway
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/btcsuite/btcd/wire"
|
||||
"github.com/gaze-network/indexer-network/core/types"
|
||||
"github.com/gaze-network/indexer-network/modules/brc20/internal/entity"
|
||||
"github.com/gaze-network/indexer-network/modules/brc20/internal/ordinals"
|
||||
)
|
||||
|
||||
type BRC20DataGateway interface {
|
||||
BRC20ReaderDataGateway
|
||||
BRC20WriterDataGateway
|
||||
|
||||
// BeginBRC20Tx returns a new BRC20DataGateway with transaction enabled. All write operations performed in this datagateway must be committed to persist changes.
|
||||
BeginBRC20Tx(ctx context.Context) (BRC20DataGatewayWithTx, error)
|
||||
}
|
||||
|
||||
type BRC20DataGatewayWithTx interface {
|
||||
BRC20DataGateway
|
||||
Tx
|
||||
}
|
||||
|
||||
type BRC20ReaderDataGateway interface {
|
||||
GetLatestBlock(ctx context.Context) (types.BlockHeader, error)
|
||||
GetIndexedBlockByHeight(ctx context.Context, height int64) (*entity.IndexedBlock, error)
|
||||
GetProcessorStats(ctx context.Context) (*entity.ProcessorStats, error)
|
||||
GetInscriptionTransfersInOutPoints(ctx context.Context, outPoints []wire.OutPoint) (map[ordinals.SatPoint][]*entity.InscriptionTransfer, error)
|
||||
GetInscriptionEntriesByIds(ctx context.Context, ids []ordinals.InscriptionId) (map[ordinals.InscriptionId]*ordinals.InscriptionEntry, error)
|
||||
GetInscriptionNumbersByIds(ctx context.Context, ids []ordinals.InscriptionId) (map[ordinals.InscriptionId]int64, error)
|
||||
GetInscriptionParentsByIds(ctx context.Context, ids []ordinals.InscriptionId) (map[ordinals.InscriptionId]ordinals.InscriptionId, error)
|
||||
GetBalancesBatchAtHeight(ctx context.Context, blockHeight uint64, queries []GetBalancesBatchAtHeightQuery) (map[string]map[string]*entity.Balance, error)
|
||||
GetTickEntriesByTicks(ctx context.Context, ticks []string) (map[string]*entity.TickEntry, error)
|
||||
GetEventInscribeTransfersByInscriptionIds(ctx context.Context, ids []ordinals.InscriptionId) (map[ordinals.InscriptionId]*entity.EventInscribeTransfer, error)
|
||||
GetLatestEventId(ctx context.Context) (int64, error)
|
||||
GetBalancesByTick(ctx context.Context, tick string, blockHeight uint64) ([]*entity.Balance, error)
|
||||
GetBalancesByPkScript(ctx context.Context, pkScript []byte, blockHeight uint64) (map[string]*entity.Balance, error)
|
||||
GetTransferableTransfersByPkScript(ctx context.Context, pkScript []byte, blockHeight uint64) ([]*entity.EventInscribeTransfer, error)
|
||||
GetDeployEventByTick(ctx context.Context, tick string) (*entity.EventDeploy, error)
|
||||
GetFirstLastInscriptionNumberByTick(ctx context.Context, tick string) (first, last int64, err error)
|
||||
GetDeployEvents(ctx context.Context, pkScript []byte, tick string, height uint64) ([]*entity.EventDeploy, error)
|
||||
GetMintEvents(ctx context.Context, pkScript []byte, tick string, height uint64) ([]*entity.EventMint, error)
|
||||
GetInscribeTransferEvents(ctx context.Context, pkScript []byte, tick string, height uint64) ([]*entity.EventInscribeTransfer, error)
|
||||
GetTransferTransferEvents(ctx context.Context, pkScript []byte, tick string, height uint64) ([]*entity.EventTransferTransfer, error)
|
||||
}
|
||||
|
||||
type BRC20WriterDataGateway interface {
|
||||
CreateIndexedBlock(ctx context.Context, block *entity.IndexedBlock) error
|
||||
CreateProcessorStats(ctx context.Context, stats *entity.ProcessorStats) error
|
||||
CreateTickEntries(ctx context.Context, blockHeight uint64, entries []*entity.TickEntry) error
|
||||
CreateTickEntryStates(ctx context.Context, blockHeight uint64, entryStates []*entity.TickEntry) error
|
||||
CreateInscriptionEntries(ctx context.Context, blockHeight uint64, entries []*ordinals.InscriptionEntry) error
|
||||
CreateInscriptionEntryStates(ctx context.Context, blockHeight uint64, entryStates []*ordinals.InscriptionEntry) error
|
||||
CreateInscriptionTransfers(ctx context.Context, transfers []*entity.InscriptionTransfer) error
|
||||
CreateEventDeploys(ctx context.Context, events []*entity.EventDeploy) error
|
||||
CreateEventMints(ctx context.Context, events []*entity.EventMint) error
|
||||
CreateEventInscribeTransfers(ctx context.Context, events []*entity.EventInscribeTransfer) error
|
||||
CreateEventTransferTransfers(ctx context.Context, events []*entity.EventTransferTransfer) error
|
||||
CreateBalances(ctx context.Context, balances []*entity.Balance) error
|
||||
|
||||
// used for revert data
|
||||
DeleteIndexedBlocksSinceHeight(ctx context.Context, height uint64) error
|
||||
DeleteProcessorStatsSinceHeight(ctx context.Context, height uint64) error
|
||||
DeleteTickEntriesSinceHeight(ctx context.Context, height uint64) error
|
||||
DeleteTickEntryStatesSinceHeight(ctx context.Context, height uint64) error
|
||||
DeleteEventDeploysSinceHeight(ctx context.Context, height uint64) error
|
||||
DeleteEventMintsSinceHeight(ctx context.Context, height uint64) error
|
||||
DeleteEventInscribeTransfersSinceHeight(ctx context.Context, height uint64) error
|
||||
DeleteEventTransferTransfersSinceHeight(ctx context.Context, height uint64) error
|
||||
DeleteBalancesSinceHeight(ctx context.Context, height uint64) error
|
||||
DeleteInscriptionEntriesSinceHeight(ctx context.Context, height uint64) error
|
||||
DeleteInscriptionEntryStatesSinceHeight(ctx context.Context, height uint64) error
|
||||
DeleteInscriptionTransfersSinceHeight(ctx context.Context, height uint64) error
|
||||
}
|
||||
|
||||
type GetBalancesBatchAtHeightQuery struct {
|
||||
PkScriptHex string
|
||||
Tick string
|
||||
BlockHeight uint64
|
||||
}
|
||||
@@ -1,12 +0,0 @@
|
||||
package datagateway
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/gaze-network/indexer-network/modules/brc20/internal/entity"
|
||||
)
|
||||
|
||||
type IndexerInfoDataGateway interface {
|
||||
GetLatestIndexerState(ctx context.Context) (entity.IndexerState, error)
|
||||
CreateIndexerState(ctx context.Context, state entity.IndexerState) error
|
||||
}
|
||||
@@ -1,11 +0,0 @@
|
||||
package entity
|
||||
|
||||
import "github.com/shopspring/decimal"
|
||||
|
||||
type Balance struct {
|
||||
PkScript []byte
|
||||
Tick string
|
||||
BlockHeight uint64
|
||||
OverallBalance decimal.Decimal
|
||||
AvailableBalance decimal.Decimal
|
||||
}
|
||||
@@ -1,28 +0,0 @@
|
||||
package entity
|
||||
|
||||
import (
|
||||
"time"
|
||||
|
||||
"github.com/btcsuite/btcd/chaincfg/chainhash"
|
||||
"github.com/gaze-network/indexer-network/modules/brc20/internal/ordinals"
|
||||
"github.com/shopspring/decimal"
|
||||
)
|
||||
|
||||
type EventDeploy struct {
|
||||
Id int64
|
||||
InscriptionId ordinals.InscriptionId
|
||||
InscriptionNumber int64
|
||||
Tick string
|
||||
OriginalTick string
|
||||
TxHash chainhash.Hash
|
||||
BlockHeight uint64
|
||||
TxIndex uint32
|
||||
Timestamp time.Time
|
||||
|
||||
PkScript []byte
|
||||
SatPoint ordinals.SatPoint
|
||||
TotalSupply decimal.Decimal
|
||||
Decimals uint16
|
||||
LimitPerMint decimal.Decimal
|
||||
IsSelfMint bool
|
||||
}
|
||||
@@ -1,27 +0,0 @@
|
||||
package entity
|
||||
|
||||
import (
|
||||
"time"
|
||||
|
||||
"github.com/btcsuite/btcd/chaincfg/chainhash"
|
||||
"github.com/gaze-network/indexer-network/modules/brc20/internal/ordinals"
|
||||
"github.com/shopspring/decimal"
|
||||
)
|
||||
|
||||
type EventInscribeTransfer struct {
|
||||
Id int64
|
||||
InscriptionId ordinals.InscriptionId
|
||||
InscriptionNumber int64
|
||||
Tick string
|
||||
OriginalTick string
|
||||
TxHash chainhash.Hash
|
||||
BlockHeight uint64
|
||||
TxIndex uint32
|
||||
Timestamp time.Time
|
||||
|
||||
PkScript []byte
|
||||
SatPoint ordinals.SatPoint
|
||||
OutputIndex uint32
|
||||
SatsAmount uint64
|
||||
Amount decimal.Decimal
|
||||
}
|
||||
@@ -1,26 +0,0 @@
|
||||
package entity
|
||||
|
||||
import (
|
||||
"time"
|
||||
|
||||
"github.com/btcsuite/btcd/chaincfg/chainhash"
|
||||
"github.com/gaze-network/indexer-network/modules/brc20/internal/ordinals"
|
||||
"github.com/shopspring/decimal"
|
||||
)
|
||||
|
||||
type EventMint struct {
|
||||
Id int64
|
||||
InscriptionId ordinals.InscriptionId
|
||||
InscriptionNumber int64
|
||||
Tick string
|
||||
OriginalTick string
|
||||
TxHash chainhash.Hash
|
||||
BlockHeight uint64
|
||||
TxIndex uint32
|
||||
Timestamp time.Time
|
||||
|
||||
PkScript []byte
|
||||
SatPoint ordinals.SatPoint
|
||||
Amount decimal.Decimal
|
||||
ParentId *ordinals.InscriptionId
|
||||
}
|
||||
@@ -1,30 +0,0 @@
|
||||
package entity
|
||||
|
||||
import (
|
||||
"time"
|
||||
|
||||
"github.com/btcsuite/btcd/chaincfg/chainhash"
|
||||
"github.com/gaze-network/indexer-network/modules/brc20/internal/ordinals"
|
||||
"github.com/shopspring/decimal"
|
||||
)
|
||||
|
||||
type EventTransferTransfer struct {
|
||||
Id int64
|
||||
InscriptionId ordinals.InscriptionId
|
||||
InscriptionNumber int64
|
||||
Tick string
|
||||
OriginalTick string
|
||||
TxHash chainhash.Hash
|
||||
BlockHeight uint64
|
||||
TxIndex uint32
|
||||
Timestamp time.Time
|
||||
|
||||
FromPkScript []byte
|
||||
FromSatPoint ordinals.SatPoint
|
||||
FromInputIndex uint32
|
||||
ToPkScript []byte
|
||||
ToSatPoint ordinals.SatPoint
|
||||
ToOutputIndex uint32
|
||||
SpentAsFee bool
|
||||
Amount decimal.Decimal
|
||||
}
|
||||
@@ -1,31 +0,0 @@
|
||||
package entity
|
||||
|
||||
import (
|
||||
"github.com/gaze-network/indexer-network/core/types"
|
||||
"github.com/gaze-network/indexer-network/modules/brc20/internal/ordinals"
|
||||
)
|
||||
|
||||
type OriginOld struct {
|
||||
Content []byte
|
||||
OldSatPoint ordinals.SatPoint
|
||||
InputIndex uint32
|
||||
}
|
||||
type OriginNew struct {
|
||||
Inscription ordinals.Inscription
|
||||
Parent *ordinals.InscriptionId
|
||||
Pointer *uint64
|
||||
Fee uint64
|
||||
Cursed bool
|
||||
CursedForBRC20 bool
|
||||
Hidden bool
|
||||
Reinscription bool
|
||||
Unbound bool
|
||||
}
|
||||
|
||||
type Flotsam struct {
|
||||
Tx *types.Transaction
|
||||
OriginOld *OriginOld // OriginOld and OriginNew are mutually exclusive
|
||||
OriginNew *OriginNew // OriginOld and OriginNew are mutually exclusive
|
||||
Offset uint64
|
||||
InscriptionId ordinals.InscriptionId
|
||||
}
|
||||
@@ -1,10 +0,0 @@
|
||||
package entity
|
||||
|
||||
import "github.com/btcsuite/btcd/chaincfg/chainhash"
|
||||
|
||||
type IndexedBlock struct {
|
||||
Height uint64
|
||||
Hash chainhash.Hash
|
||||
EventHash []byte
|
||||
CumulativeEventHash []byte
|
||||
}
|
||||
@@ -1,15 +0,0 @@
|
||||
package entity
|
||||
|
||||
import (
|
||||
"time"
|
||||
|
||||
"github.com/gaze-network/indexer-network/common"
|
||||
)
|
||||
|
||||
type IndexerState struct {
|
||||
CreatedAt time.Time
|
||||
ClientVersion string
|
||||
DBVersion int32
|
||||
EventHashVersion int32
|
||||
Network common.Network
|
||||
}
|
||||
@@ -1,23 +0,0 @@
|
||||
package entity
|
||||
|
||||
import (
|
||||
"github.com/btcsuite/btcd/chaincfg/chainhash"
|
||||
"github.com/gaze-network/indexer-network/modules/brc20/internal/ordinals"
|
||||
)
|
||||
|
||||
type InscriptionTransfer struct {
|
||||
InscriptionId ordinals.InscriptionId
|
||||
InscriptionNumber int64
|
||||
InscriptionSequenceNumber uint64
|
||||
BlockHeight uint64
|
||||
TxIndex uint32
|
||||
TxHash chainhash.Hash
|
||||
Content []byte
|
||||
FromInputIndex uint32
|
||||
OldSatPoint ordinals.SatPoint
|
||||
NewSatPoint ordinals.SatPoint
|
||||
NewPkScript []byte
|
||||
NewOutputValue uint64
|
||||
SentAsFee bool
|
||||
TransferCount uint32
|
||||
}
|
||||
@@ -1,8 +0,0 @@
|
||||
package entity
|
||||
|
||||
type ProcessorStats struct {
|
||||
BlockHeight uint64
|
||||
CursedInscriptionCount uint64
|
||||
BlessedInscriptionCount uint64
|
||||
LostSats uint64
|
||||
}
|
||||
@@ -1,25 +0,0 @@
|
||||
package entity
|
||||
|
||||
import (
|
||||
"time"
|
||||
|
||||
"github.com/gaze-network/indexer-network/modules/brc20/internal/ordinals"
|
||||
"github.com/shopspring/decimal"
|
||||
)
|
||||
|
||||
type TickEntry struct {
|
||||
Tick string
|
||||
OriginalTick string
|
||||
TotalSupply decimal.Decimal
|
||||
Decimals uint16
|
||||
LimitPerMint decimal.Decimal
|
||||
IsSelfMint bool
|
||||
DeployInscriptionId ordinals.InscriptionId
|
||||
DeployedAt time.Time
|
||||
DeployedAtHeight uint64
|
||||
|
||||
MintedAmount decimal.Decimal
|
||||
BurnedAmount decimal.Decimal
|
||||
CompletedAt time.Time
|
||||
CompletedAtHeight uint64
|
||||
}
|
||||
@@ -1,285 +0,0 @@
|
||||
package ordinals
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/binary"
|
||||
|
||||
"github.com/btcsuite/btcd/txscript"
|
||||
"github.com/gaze-network/indexer-network/core/types"
|
||||
"github.com/samber/lo"
|
||||
)
|
||||
|
||||
type Envelope struct {
|
||||
Inscription Inscription
|
||||
InputIndex uint32 // Index of input that contains the envelope
|
||||
Offset int // Number of envelope in the input
|
||||
PushNum bool // True if envelope contains pushnum opcodes
|
||||
Stutter bool // True if envelope matches stuttering curse structure
|
||||
IncompleteField bool // True if payload is incomplete
|
||||
DuplicateField bool // True if payload contains duplicated field
|
||||
UnrecognizedEvenField bool // True if payload contains unrecognized even field
|
||||
}
|
||||
|
||||
func ParseEnvelopesFromTx(tx *types.Transaction) []*Envelope {
|
||||
envelopes := make([]*Envelope, 0)
|
||||
|
||||
for i, txIn := range tx.TxIn {
|
||||
tapScript, ok := extractTapScript(txIn.Witness)
|
||||
if !ok {
|
||||
continue
|
||||
}
|
||||
|
||||
newEnvelopes := envelopesFromTapScript(tapScript, i)
|
||||
envelopes = append(envelopes, newEnvelopes...)
|
||||
}
|
||||
|
||||
return envelopes
|
||||
}
|
||||
|
||||
var protocolId = []byte("ord")
|
||||
|
||||
func envelopesFromTapScript(tokenizer txscript.ScriptTokenizer, inputIndex int) []*Envelope {
|
||||
envelopes := make([]*Envelope, 0)
|
||||
|
||||
var stuttered bool
|
||||
for tokenizer.Next() {
|
||||
if tokenizer.Err() != nil {
|
||||
break
|
||||
}
|
||||
if tokenizer.Opcode() == txscript.OP_FALSE {
|
||||
envelope, stutter := envelopeFromTokenizer(tokenizer, inputIndex, len(envelopes), stuttered)
|
||||
if envelope != nil {
|
||||
envelopes = append(envelopes, envelope)
|
||||
} else {
|
||||
stuttered = stutter
|
||||
}
|
||||
}
|
||||
}
|
||||
if tokenizer.Err() != nil {
|
||||
return envelopes
|
||||
}
|
||||
return envelopes
|
||||
}
|
||||
|
||||
func envelopeFromTokenizer(tokenizer txscript.ScriptTokenizer, inputIndex int, offset int, stuttered bool) (*Envelope, bool) {
|
||||
tokenizer.Next()
|
||||
if tokenizer.Opcode() != txscript.OP_IF {
|
||||
return nil, tokenizer.Opcode() == txscript.OP_FALSE
|
||||
}
|
||||
|
||||
tokenizer.Next()
|
||||
if !bytes.Equal(tokenizer.Data(), protocolId) {
|
||||
return nil, tokenizer.Opcode() == txscript.OP_FALSE
|
||||
}
|
||||
|
||||
var pushNum bool
|
||||
payload := make([][]byte, 0)
|
||||
for tokenizer.Next() {
|
||||
if tokenizer.Err() != nil {
|
||||
return nil, false
|
||||
}
|
||||
opCode := tokenizer.Opcode()
|
||||
if opCode == txscript.OP_ENDIF {
|
||||
break
|
||||
}
|
||||
switch opCode {
|
||||
case txscript.OP_1NEGATE:
|
||||
pushNum = true
|
||||
payload = append(payload, []byte{0x81})
|
||||
case txscript.OP_1:
|
||||
pushNum = true
|
||||
payload = append(payload, []byte{0x01})
|
||||
case txscript.OP_2:
|
||||
pushNum = true
|
||||
payload = append(payload, []byte{0x02})
|
||||
case txscript.OP_3:
|
||||
pushNum = true
|
||||
payload = append(payload, []byte{0x03})
|
||||
case txscript.OP_4:
|
||||
pushNum = true
|
||||
payload = append(payload, []byte{0x04})
|
||||
case txscript.OP_5:
|
||||
pushNum = true
|
||||
payload = append(payload, []byte{0x05})
|
||||
case txscript.OP_6:
|
||||
pushNum = true
|
||||
payload = append(payload, []byte{0x06})
|
||||
case txscript.OP_7:
|
||||
pushNum = true
|
||||
payload = append(payload, []byte{0x07})
|
||||
case txscript.OP_8:
|
||||
pushNum = true
|
||||
payload = append(payload, []byte{0x08})
|
||||
case txscript.OP_9:
|
||||
pushNum = true
|
||||
payload = append(payload, []byte{0x09})
|
||||
case txscript.OP_10:
|
||||
pushNum = true
|
||||
payload = append(payload, []byte{0x10})
|
||||
case txscript.OP_11:
|
||||
pushNum = true
|
||||
payload = append(payload, []byte{0x11})
|
||||
case txscript.OP_12:
|
||||
pushNum = true
|
||||
payload = append(payload, []byte{0x12})
|
||||
case txscript.OP_13:
|
||||
pushNum = true
|
||||
payload = append(payload, []byte{0x13})
|
||||
case txscript.OP_14:
|
||||
pushNum = true
|
||||
payload = append(payload, []byte{0x14})
|
||||
case txscript.OP_15:
|
||||
pushNum = true
|
||||
payload = append(payload, []byte{0x15})
|
||||
case txscript.OP_16:
|
||||
pushNum = true
|
||||
payload = append(payload, []byte{0x16})
|
||||
case txscript.OP_0:
|
||||
// OP_0 is a special case, it is accepted in ord's implementation
|
||||
payload = append(payload, []byte{})
|
||||
default:
|
||||
data := tokenizer.Data()
|
||||
if data == nil {
|
||||
return nil, false
|
||||
}
|
||||
payload = append(payload, data)
|
||||
}
|
||||
}
|
||||
// incomplete envelope
|
||||
if tokenizer.Done() && tokenizer.Opcode() != txscript.OP_ENDIF {
|
||||
return nil, false
|
||||
}
|
||||
|
||||
// find body (empty data push in even index payload)
|
||||
bodyIndex := -1
|
||||
for i, value := range payload {
|
||||
if i%2 == 0 && len(value) == 0 {
|
||||
bodyIndex = i
|
||||
break
|
||||
}
|
||||
}
|
||||
var fieldPayloads [][]byte
|
||||
var body []byte
|
||||
if bodyIndex != -1 {
|
||||
fieldPayloads = payload[:bodyIndex]
|
||||
body = lo.Flatten(payload[bodyIndex+1:])
|
||||
} else {
|
||||
fieldPayloads = payload[:]
|
||||
}
|
||||
|
||||
var incompleteField bool
|
||||
fields := make(Fields)
|
||||
for _, chunk := range lo.Chunk(fieldPayloads, 2) {
|
||||
if len(chunk) != 2 {
|
||||
incompleteField = true
|
||||
break
|
||||
}
|
||||
key := chunk[0]
|
||||
value := chunk[1]
|
||||
// key cannot be empty, as checked by bodyIndex above
|
||||
tag := Tag(key[0])
|
||||
fields[tag] = append(fields[tag], value)
|
||||
}
|
||||
|
||||
var duplicateField bool
|
||||
for _, values := range fields {
|
||||
if len(values) > 1 {
|
||||
duplicateField = true
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
rawContentEncoding := fields.Take(TagContentEncoding)
|
||||
rawContentType := fields.Take(TagContentType)
|
||||
rawDelegate := fields.Take(TagDelegate)
|
||||
rawMetadata := fields.Take(TagMetadata)
|
||||
rawMetaprotocol := fields.Take(TagMetaprotocol)
|
||||
rawParent := fields.Take(TagParent)
|
||||
rawPointer := fields.Take(TagPointer)
|
||||
|
||||
unrecognizedEvenField := lo.SomeBy(lo.Keys(fields), func(key Tag) bool {
|
||||
return key%2 == 0
|
||||
})
|
||||
|
||||
var delegate, parent *InscriptionId
|
||||
inscriptionId, err := NewInscriptionIdFromString(string(rawDelegate))
|
||||
if err == nil {
|
||||
delegate = &inscriptionId
|
||||
}
|
||||
inscriptionId, err = NewInscriptionIdFromString(string(rawParent))
|
||||
if err == nil {
|
||||
parent = &inscriptionId
|
||||
}
|
||||
|
||||
var pointer *uint64
|
||||
// if rawPointer is not nil and fits in uint64
|
||||
if rawPointer != nil && (len(rawPointer) <= 8 || lo.EveryBy(rawPointer[8:], func(value byte) bool {
|
||||
return value != 0
|
||||
})) {
|
||||
// pad zero bytes to 8 bytes
|
||||
if len(rawPointer) < 8 {
|
||||
rawPointer = append(rawPointer, make([]byte, 8-len(rawPointer))...)
|
||||
}
|
||||
pointer = lo.ToPtr(binary.LittleEndian.Uint64(rawPointer))
|
||||
}
|
||||
|
||||
inscription := Inscription{
|
||||
Content: body,
|
||||
ContentEncoding: string(rawContentEncoding),
|
||||
ContentType: string(rawContentType),
|
||||
Delegate: delegate,
|
||||
Metadata: rawMetadata,
|
||||
Metaprotocol: string(rawMetaprotocol),
|
||||
Parent: parent,
|
||||
Pointer: pointer,
|
||||
}
|
||||
return &Envelope{
|
||||
Inscription: inscription,
|
||||
InputIndex: uint32(inputIndex),
|
||||
Offset: offset,
|
||||
PushNum: pushNum,
|
||||
Stutter: stuttered,
|
||||
IncompleteField: incompleteField,
|
||||
DuplicateField: duplicateField,
|
||||
UnrecognizedEvenField: unrecognizedEvenField,
|
||||
}, false
|
||||
}
|
||||
|
||||
type Fields map[Tag][][]byte
|
||||
|
||||
func (fields Fields) Take(tag Tag) []byte {
|
||||
values, ok := fields[tag]
|
||||
if !ok {
|
||||
return nil
|
||||
}
|
||||
if tag.IsChunked() {
|
||||
delete(fields, tag)
|
||||
return lo.Flatten(values)
|
||||
} else {
|
||||
first := values[0]
|
||||
values = values[1:]
|
||||
if len(values) == 0 {
|
||||
delete(fields, tag)
|
||||
} else {
|
||||
fields[tag] = values
|
||||
}
|
||||
return first
|
||||
}
|
||||
}
|
||||
|
||||
func extractTapScript(witness [][]byte) (txscript.ScriptTokenizer, bool) {
|
||||
witness = removeAnnexFromWitness(witness)
|
||||
if len(witness) < 2 {
|
||||
return txscript.ScriptTokenizer{}, false
|
||||
}
|
||||
script := witness[len(witness)-2]
|
||||
|
||||
return txscript.MakeScriptTokenizer(0, script), true
|
||||
}
|
||||
|
||||
func removeAnnexFromWitness(witness [][]byte) [][]byte {
|
||||
if len(witness) >= 2 && len(witness[len(witness)-1]) > 0 && witness[len(witness)-1][0] == txscript.TaprootAnnexTag {
|
||||
return witness[:len(witness)-1]
|
||||
}
|
||||
return witness
|
||||
}
|
||||
@@ -1,742 +0,0 @@
|
||||
package ordinals
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/Cleverse/go-utilities/utils"
|
||||
"github.com/btcsuite/btcd/txscript"
|
||||
"github.com/btcsuite/btcd/wire"
|
||||
"github.com/gaze-network/indexer-network/core/types"
|
||||
"github.com/samber/lo"
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestParseEnvelopesFromTx(t *testing.T) {
|
||||
testTx := func(t *testing.T, tx *types.Transaction, expected []*Envelope) {
|
||||
t.Helper()
|
||||
|
||||
envelopes := ParseEnvelopesFromTx(tx)
|
||||
assert.Equal(t, expected, envelopes)
|
||||
}
|
||||
testParseWitness := func(t *testing.T, tapScript []byte, expected []*Envelope) {
|
||||
t.Helper()
|
||||
|
||||
tx := &types.Transaction{
|
||||
Version: 2,
|
||||
LockTime: 0,
|
||||
TxIn: []*types.TxIn{
|
||||
{
|
||||
Witness: wire.TxWitness{
|
||||
tapScript,
|
||||
{},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
testTx(t, tx, expected)
|
||||
}
|
||||
testEnvelope := func(t *testing.T, payload [][]byte, expected []*Envelope) {
|
||||
t.Helper()
|
||||
|
||||
builder := NewPushScriptBuilder().
|
||||
AddOp(txscript.OP_FALSE).
|
||||
AddOp(txscript.OP_IF)
|
||||
for _, data := range payload {
|
||||
builder.AddData(data)
|
||||
}
|
||||
builder.AddOp(txscript.OP_ENDIF)
|
||||
script, err := builder.Script()
|
||||
assert.NoError(t, err)
|
||||
|
||||
testParseWitness(
|
||||
t,
|
||||
script,
|
||||
expected,
|
||||
)
|
||||
}
|
||||
|
||||
t.Run("empty_witness", func(t *testing.T) {
|
||||
testTx(t, &types.Transaction{
|
||||
Version: 2,
|
||||
LockTime: 0,
|
||||
TxIn: []*types.TxIn{{
|
||||
Witness: wire.TxWitness{},
|
||||
}},
|
||||
}, []*Envelope{})
|
||||
})
|
||||
t.Run("ignore_key_path_spends", func(t *testing.T) {
|
||||
testTx(t, &types.Transaction{
|
||||
Version: 2,
|
||||
LockTime: 0,
|
||||
TxIn: []*types.TxIn{{
|
||||
Witness: wire.TxWitness{
|
||||
utils.Must(NewPushScriptBuilder().
|
||||
AddOp(txscript.OP_FALSE).
|
||||
AddOp(txscript.OP_IF).
|
||||
AddData(protocolId).
|
||||
AddOp(txscript.OP_ENDIF).
|
||||
Script()),
|
||||
},
|
||||
}},
|
||||
}, []*Envelope{})
|
||||
})
|
||||
t.Run("ignore_key_path_spends_with_annex", func(t *testing.T) {
|
||||
testTx(t, &types.Transaction{
|
||||
Version: 2,
|
||||
LockTime: 0,
|
||||
TxIn: []*types.TxIn{{
|
||||
Witness: wire.TxWitness{
|
||||
utils.Must(NewPushScriptBuilder().
|
||||
AddOp(txscript.OP_FALSE).
|
||||
AddOp(txscript.OP_IF).
|
||||
AddData(protocolId).
|
||||
AddOp(txscript.OP_ENDIF).
|
||||
Script()),
|
||||
[]byte{txscript.TaprootAnnexTag},
|
||||
},
|
||||
}},
|
||||
}, []*Envelope{})
|
||||
})
|
||||
t.Run("parse_from_tapscript", func(t *testing.T) {
|
||||
testParseWitness(
|
||||
t,
|
||||
utils.Must(NewPushScriptBuilder().
|
||||
AddOp(txscript.OP_FALSE).
|
||||
AddOp(txscript.OP_IF).
|
||||
AddData(protocolId).
|
||||
AddOp(txscript.OP_ENDIF).
|
||||
Script()),
|
||||
[]*Envelope{{}},
|
||||
)
|
||||
})
|
||||
t.Run("ignore_unparsable_scripts", func(t *testing.T) {
|
||||
script := utils.Must(NewPushScriptBuilder().
|
||||
AddOp(txscript.OP_FALSE).
|
||||
AddOp(txscript.OP_IF).
|
||||
AddData(protocolId).
|
||||
AddOp(txscript.OP_ENDIF).
|
||||
Script())
|
||||
|
||||
script = append(script, 0x01)
|
||||
testParseWitness(
|
||||
t,
|
||||
script,
|
||||
[]*Envelope{
|
||||
{},
|
||||
},
|
||||
)
|
||||
})
|
||||
t.Run("no_inscription", func(t *testing.T) {
|
||||
testParseWitness(
|
||||
t,
|
||||
utils.Must(NewPushScriptBuilder().
|
||||
Script()),
|
||||
[]*Envelope{},
|
||||
)
|
||||
})
|
||||
t.Run("duplicate_field", func(t *testing.T) {
|
||||
testEnvelope(
|
||||
t,
|
||||
[][]byte{
|
||||
protocolId,
|
||||
TagNop.Bytes(),
|
||||
{},
|
||||
TagNop.Bytes(),
|
||||
{},
|
||||
},
|
||||
[]*Envelope{
|
||||
{
|
||||
DuplicateField: true,
|
||||
},
|
||||
},
|
||||
)
|
||||
})
|
||||
t.Run("with_content_type", func(t *testing.T) {
|
||||
testEnvelope(
|
||||
t,
|
||||
[][]byte{
|
||||
protocolId,
|
||||
TagContentType.Bytes(),
|
||||
[]byte("text/plain;charset=utf-8"),
|
||||
TagBody.Bytes(),
|
||||
[]byte("ord"),
|
||||
},
|
||||
[]*Envelope{
|
||||
{
|
||||
Inscription: Inscription{
|
||||
Content: []byte("ord"),
|
||||
ContentType: "text/plain;charset=utf-8",
|
||||
},
|
||||
},
|
||||
},
|
||||
)
|
||||
})
|
||||
t.Run("with_content_encoding", func(t *testing.T) {
|
||||
testEnvelope(
|
||||
t,
|
||||
[][]byte{
|
||||
protocolId,
|
||||
TagContentType.Bytes(),
|
||||
[]byte("text/plain;charset=utf-8"),
|
||||
TagContentEncoding.Bytes(),
|
||||
[]byte("br"),
|
||||
TagBody.Bytes(),
|
||||
[]byte("ord"),
|
||||
},
|
||||
[]*Envelope{
|
||||
{
|
||||
Inscription: Inscription{
|
||||
Content: []byte("ord"),
|
||||
ContentType: "text/plain;charset=utf-8",
|
||||
ContentEncoding: "br",
|
||||
},
|
||||
},
|
||||
},
|
||||
)
|
||||
})
|
||||
t.Run("with_unknown_tag", func(t *testing.T) {
|
||||
testEnvelope(
|
||||
t,
|
||||
[][]byte{
|
||||
protocolId,
|
||||
TagContentType.Bytes(),
|
||||
[]byte("text/plain;charset=utf-8"),
|
||||
TagNop.Bytes(),
|
||||
[]byte("bar"),
|
||||
TagBody.Bytes(),
|
||||
[]byte("ord"),
|
||||
},
|
||||
[]*Envelope{
|
||||
{
|
||||
Inscription: Inscription{
|
||||
Content: []byte("ord"),
|
||||
ContentType: "text/plain;charset=utf-8",
|
||||
},
|
||||
},
|
||||
},
|
||||
)
|
||||
})
|
||||
t.Run("no_body", func(t *testing.T) {
|
||||
testEnvelope(
|
||||
t,
|
||||
[][]byte{
|
||||
protocolId,
|
||||
TagContentType.Bytes(),
|
||||
[]byte("text/plain;charset=utf-8"),
|
||||
},
|
||||
[]*Envelope{
|
||||
{
|
||||
Inscription: Inscription{
|
||||
ContentType: "text/plain;charset=utf-8",
|
||||
},
|
||||
},
|
||||
},
|
||||
)
|
||||
})
|
||||
t.Run("no_content_type", func(t *testing.T) {
|
||||
testEnvelope(
|
||||
t,
|
||||
[][]byte{
|
||||
protocolId,
|
||||
TagBody.Bytes(),
|
||||
[]byte("foo"),
|
||||
},
|
||||
[]*Envelope{
|
||||
{
|
||||
Inscription: Inscription{
|
||||
Content: []byte("foo"),
|
||||
},
|
||||
},
|
||||
},
|
||||
)
|
||||
})
|
||||
t.Run("valid_body_in_multiple_pushes", func(t *testing.T) {
|
||||
testEnvelope(
|
||||
t,
|
||||
[][]byte{
|
||||
protocolId,
|
||||
TagContentType.Bytes(),
|
||||
[]byte("text/plain;charset=utf-8"),
|
||||
TagBody.Bytes(),
|
||||
[]byte("foo"),
|
||||
[]byte("bar"),
|
||||
},
|
||||
[]*Envelope{
|
||||
{
|
||||
Inscription: Inscription{
|
||||
Content: []byte("foobar"),
|
||||
ContentType: "text/plain;charset=utf-8",
|
||||
},
|
||||
},
|
||||
},
|
||||
)
|
||||
})
|
||||
t.Run("valid_body_in_zero_pushes", func(t *testing.T) {
|
||||
testEnvelope(
|
||||
t,
|
||||
[][]byte{
|
||||
protocolId,
|
||||
TagContentType.Bytes(),
|
||||
[]byte("text/plain;charset=utf-8"),
|
||||
TagBody.Bytes(),
|
||||
},
|
||||
[]*Envelope{
|
||||
{
|
||||
Inscription: Inscription{
|
||||
Content: []byte(""),
|
||||
ContentType: "text/plain;charset=utf-8",
|
||||
},
|
||||
},
|
||||
},
|
||||
)
|
||||
})
|
||||
t.Run("valid_body_in_multiple_empty_pushes", func(t *testing.T) {
|
||||
testEnvelope(
|
||||
t,
|
||||
[][]byte{
|
||||
protocolId,
|
||||
TagContentType.Bytes(),
|
||||
[]byte("text/plain;charset=utf-8"),
|
||||
TagBody.Bytes(),
|
||||
{},
|
||||
{},
|
||||
{},
|
||||
{},
|
||||
{},
|
||||
{},
|
||||
{},
|
||||
},
|
||||
[]*Envelope{
|
||||
{
|
||||
Inscription: Inscription{
|
||||
Content: []byte(""),
|
||||
ContentType: "text/plain;charset=utf-8",
|
||||
},
|
||||
},
|
||||
},
|
||||
)
|
||||
})
|
||||
t.Run("valid_ignore_trailing", func(t *testing.T) {
|
||||
testParseWitness(
|
||||
t,
|
||||
utils.Must(NewPushScriptBuilder().
|
||||
AddOp(txscript.OP_FALSE).
|
||||
AddOp(txscript.OP_IF).
|
||||
AddData(protocolId).
|
||||
AddData(TagContentType.Bytes()).
|
||||
AddData([]byte("text/plain;charset=utf-8")).
|
||||
AddData(TagBody.Bytes()).
|
||||
AddData([]byte("ord")).
|
||||
AddOp(txscript.OP_ENDIF).
|
||||
AddOp(txscript.OP_CHECKSIG).
|
||||
Script()),
|
||||
[]*Envelope{
|
||||
{
|
||||
Inscription: Inscription{
|
||||
Content: []byte("ord"),
|
||||
ContentType: "text/plain;charset=utf-8",
|
||||
},
|
||||
},
|
||||
},
|
||||
)
|
||||
})
|
||||
t.Run("valid_ignore_preceding", func(t *testing.T) {
|
||||
testParseWitness(
|
||||
t,
|
||||
utils.Must(NewPushScriptBuilder().
|
||||
AddOp(txscript.OP_CHECKSIG).
|
||||
AddOp(txscript.OP_FALSE).
|
||||
AddOp(txscript.OP_IF).
|
||||
AddData(protocolId).
|
||||
AddData(TagContentType.Bytes()).
|
||||
AddData([]byte("text/plain;charset=utf-8")).
|
||||
AddData(TagBody.Bytes()).
|
||||
AddData([]byte("ord")).
|
||||
AddOp(txscript.OP_ENDIF).
|
||||
Script()),
|
||||
[]*Envelope{
|
||||
{
|
||||
Inscription: Inscription{
|
||||
Content: []byte("ord"),
|
||||
ContentType: "text/plain;charset=utf-8",
|
||||
},
|
||||
},
|
||||
},
|
||||
)
|
||||
})
|
||||
t.Run("multiple_inscriptions_in_a_single_witness", func(t *testing.T) {
|
||||
testParseWitness(
|
||||
t,
|
||||
utils.Must(NewPushScriptBuilder().
|
||||
AddOp(txscript.OP_FALSE).
|
||||
AddOp(txscript.OP_IF).
|
||||
AddData(protocolId).
|
||||
AddData(TagContentType.Bytes()).
|
||||
AddData([]byte("text/plain;charset=utf-8")).
|
||||
AddData(TagBody.Bytes()).
|
||||
AddData([]byte("foo")).
|
||||
AddOp(txscript.OP_ENDIF).
|
||||
AddOp(txscript.OP_FALSE).
|
||||
AddOp(txscript.OP_IF).
|
||||
AddData(protocolId).
|
||||
AddData(TagContentType.Bytes()).
|
||||
AddData([]byte("text/plain;charset=utf-8")).
|
||||
AddData(TagBody.Bytes()).
|
||||
AddData([]byte("bar")).
|
||||
AddOp(txscript.OP_ENDIF).
|
||||
Script()),
|
||||
[]*Envelope{
|
||||
{
|
||||
Inscription: Inscription{
|
||||
Content: []byte("foo"),
|
||||
ContentType: "text/plain;charset=utf-8",
|
||||
},
|
||||
},
|
||||
{
|
||||
Inscription: Inscription{
|
||||
Content: []byte("bar"),
|
||||
ContentType: "text/plain;charset=utf-8",
|
||||
},
|
||||
Offset: 1,
|
||||
},
|
||||
},
|
||||
)
|
||||
})
|
||||
t.Run("invalid_utf8_does_not_render_inscription_invalid", func(t *testing.T) {
|
||||
testEnvelope(
|
||||
t,
|
||||
[][]byte{
|
||||
protocolId,
|
||||
TagContentType.Bytes(),
|
||||
[]byte("text/plain;charset=utf-8"),
|
||||
TagBody.Bytes(),
|
||||
{0b10000000},
|
||||
},
|
||||
[]*Envelope{
|
||||
{
|
||||
Inscription: Inscription{
|
||||
Content: []byte{0b10000000},
|
||||
ContentType: "text/plain;charset=utf-8",
|
||||
},
|
||||
},
|
||||
},
|
||||
)
|
||||
})
|
||||
t.Run("no_endif", func(t *testing.T) {
|
||||
testParseWitness(
|
||||
t,
|
||||
utils.Must(NewPushScriptBuilder().
|
||||
AddOp(txscript.OP_FALSE).
|
||||
AddOp(txscript.OP_IF).
|
||||
AddData(protocolId).
|
||||
Script()),
|
||||
[]*Envelope{},
|
||||
)
|
||||
})
|
||||
t.Run("no_op_false", func(t *testing.T) {
|
||||
testParseWitness(
|
||||
t,
|
||||
utils.Must(NewPushScriptBuilder().
|
||||
AddOp(txscript.OP_IF).
|
||||
AddData(protocolId).
|
||||
AddOp(txscript.OP_ENDIF).
|
||||
Script()),
|
||||
[]*Envelope{},
|
||||
)
|
||||
})
|
||||
t.Run("empty_envelope", func(t *testing.T) {
|
||||
testEnvelope(
|
||||
t,
|
||||
[][]byte{},
|
||||
[]*Envelope{},
|
||||
)
|
||||
})
|
||||
t.Run("wrong_protocol_identifier", func(t *testing.T) {
|
||||
testEnvelope(
|
||||
t,
|
||||
[][]byte{
|
||||
[]byte("foo"),
|
||||
},
|
||||
[]*Envelope{},
|
||||
)
|
||||
})
|
||||
t.Run("extract_from_second_input", func(t *testing.T) {
|
||||
testTx(
|
||||
t,
|
||||
&types.Transaction{
|
||||
Version: 2,
|
||||
LockTime: 0,
|
||||
TxIn: []*types.TxIn{{}, {
|
||||
Witness: wire.TxWitness{
|
||||
utils.Must(NewPushScriptBuilder().
|
||||
AddOp(txscript.OP_FALSE).
|
||||
AddOp(txscript.OP_IF).
|
||||
AddData(protocolId).
|
||||
AddData(TagContentType.Bytes()).
|
||||
AddData([]byte("text/plain;charset=utf-8")).
|
||||
AddData(TagBody.Bytes()).
|
||||
AddData([]byte("ord")).
|
||||
AddOp(txscript.OP_ENDIF).
|
||||
Script(),
|
||||
),
|
||||
{},
|
||||
},
|
||||
}},
|
||||
},
|
||||
[]*Envelope{
|
||||
{
|
||||
Inscription: Inscription{
|
||||
Content: []byte("ord"),
|
||||
ContentType: "text/plain;charset=utf-8",
|
||||
},
|
||||
InputIndex: 1,
|
||||
},
|
||||
},
|
||||
)
|
||||
})
|
||||
t.Run("inscribe_png", func(t *testing.T) {
|
||||
testEnvelope(
|
||||
t,
|
||||
[][]byte{
|
||||
protocolId,
|
||||
TagContentType.Bytes(),
|
||||
[]byte("image/png"),
|
||||
TagBody.Bytes(),
|
||||
{0x01, 0x02, 0x03},
|
||||
},
|
||||
[]*Envelope{
|
||||
{
|
||||
Inscription: Inscription{
|
||||
Content: []byte{0x01, 0x02, 0x03},
|
||||
ContentType: "image/png",
|
||||
},
|
||||
},
|
||||
},
|
||||
)
|
||||
})
|
||||
t.Run("unknown_odd_fields", func(t *testing.T) {
|
||||
testEnvelope(
|
||||
t,
|
||||
[][]byte{
|
||||
protocolId,
|
||||
TagNop.Bytes(),
|
||||
{0x00},
|
||||
},
|
||||
[]*Envelope{
|
||||
{
|
||||
Inscription: Inscription{},
|
||||
},
|
||||
},
|
||||
)
|
||||
})
|
||||
t.Run("unknown_even_fields", func(t *testing.T) {
|
||||
testEnvelope(
|
||||
t,
|
||||
[][]byte{
|
||||
protocolId,
|
||||
TagUnbound.Bytes(),
|
||||
{0x00},
|
||||
},
|
||||
[]*Envelope{
|
||||
{
|
||||
Inscription: Inscription{},
|
||||
UnrecognizedEvenField: true,
|
||||
},
|
||||
},
|
||||
)
|
||||
})
|
||||
t.Run("pointer_field_is_recognized", func(t *testing.T) {
|
||||
testEnvelope(
|
||||
t,
|
||||
[][]byte{
|
||||
protocolId,
|
||||
TagPointer.Bytes(),
|
||||
{0x01},
|
||||
},
|
||||
[]*Envelope{
|
||||
{
|
||||
Inscription: Inscription{
|
||||
Pointer: lo.ToPtr(uint64(1)),
|
||||
},
|
||||
},
|
||||
},
|
||||
)
|
||||
})
|
||||
t.Run("duplicate_pointer_field_makes_inscription_unbound", func(t *testing.T) {
|
||||
testEnvelope(
|
||||
t,
|
||||
[][]byte{
|
||||
protocolId,
|
||||
TagPointer.Bytes(),
|
||||
{0x01},
|
||||
TagPointer.Bytes(),
|
||||
{0x00},
|
||||
},
|
||||
[]*Envelope{
|
||||
{
|
||||
Inscription: Inscription{
|
||||
Pointer: lo.ToPtr(uint64(1)),
|
||||
},
|
||||
DuplicateField: true,
|
||||
UnrecognizedEvenField: true,
|
||||
},
|
||||
},
|
||||
)
|
||||
})
|
||||
t.Run("incomplete_field", func(t *testing.T) {
|
||||
testEnvelope(
|
||||
t,
|
||||
[][]byte{
|
||||
protocolId,
|
||||
TagNop.Bytes(),
|
||||
},
|
||||
[]*Envelope{
|
||||
{
|
||||
Inscription: Inscription{},
|
||||
IncompleteField: true,
|
||||
},
|
||||
},
|
||||
)
|
||||
})
|
||||
t.Run("metadata_is_parsed_correctly", func(t *testing.T) {
|
||||
testEnvelope(
|
||||
t,
|
||||
[][]byte{
|
||||
protocolId,
|
||||
TagMetadata.Bytes(),
|
||||
{},
|
||||
},
|
||||
[]*Envelope{
|
||||
{
|
||||
Inscription: Inscription{
|
||||
Metadata: []byte{},
|
||||
},
|
||||
},
|
||||
},
|
||||
)
|
||||
})
|
||||
t.Run("metadata_is_parsed_correctly_from_chunks", func(t *testing.T) {
|
||||
testEnvelope(
|
||||
t,
|
||||
[][]byte{
|
||||
protocolId,
|
||||
TagMetadata.Bytes(),
|
||||
{0x00},
|
||||
TagMetadata.Bytes(),
|
||||
{0x01},
|
||||
},
|
||||
[]*Envelope{
|
||||
{
|
||||
Inscription: Inscription{
|
||||
Metadata: []byte{0x00, 0x01},
|
||||
},
|
||||
DuplicateField: true,
|
||||
},
|
||||
},
|
||||
)
|
||||
})
|
||||
t.Run("pushnum_opcodes_are_parsed_correctly", func(t *testing.T) {
|
||||
pushNumOpCodes := map[byte][]byte{
|
||||
txscript.OP_1NEGATE: {0x81},
|
||||
txscript.OP_1: {0x01},
|
||||
txscript.OP_2: {0x02},
|
||||
txscript.OP_3: {0x03},
|
||||
txscript.OP_4: {0x04},
|
||||
txscript.OP_5: {0x05},
|
||||
txscript.OP_6: {0x06},
|
||||
txscript.OP_7: {0x07},
|
||||
txscript.OP_8: {0x08},
|
||||
txscript.OP_9: {0x09},
|
||||
txscript.OP_10: {0x10},
|
||||
txscript.OP_11: {0x11},
|
||||
txscript.OP_12: {0x12},
|
||||
txscript.OP_13: {0x13},
|
||||
txscript.OP_14: {0x14},
|
||||
txscript.OP_15: {0x15},
|
||||
txscript.OP_16: {0x16},
|
||||
}
|
||||
for opCode, value := range pushNumOpCodes {
|
||||
script := utils.Must(NewPushScriptBuilder().
|
||||
AddOp(txscript.OP_FALSE).
|
||||
AddOp(txscript.OP_IF).
|
||||
AddData(protocolId).
|
||||
AddData(TagBody.Bytes()).
|
||||
AddOp(opCode).
|
||||
AddOp(txscript.OP_ENDIF).
|
||||
Script())
|
||||
|
||||
testParseWitness(
|
||||
t,
|
||||
script,
|
||||
[]*Envelope{
|
||||
{
|
||||
Inscription: Inscription{
|
||||
Content: value,
|
||||
},
|
||||
PushNum: true,
|
||||
},
|
||||
},
|
||||
)
|
||||
}
|
||||
})
|
||||
t.Run("stuttering", func(t *testing.T) {
|
||||
script := utils.Must(NewPushScriptBuilder().
|
||||
AddOp(txscript.OP_FALSE).
|
||||
AddOp(txscript.OP_FALSE).
|
||||
AddOp(txscript.OP_IF).
|
||||
AddData(protocolId).
|
||||
AddOp(txscript.OP_ENDIF).
|
||||
Script())
|
||||
testParseWitness(
|
||||
t,
|
||||
script,
|
||||
[]*Envelope{
|
||||
{
|
||||
Inscription: Inscription{},
|
||||
Stutter: true,
|
||||
},
|
||||
},
|
||||
)
|
||||
|
||||
script = utils.Must(NewPushScriptBuilder().
|
||||
AddOp(txscript.OP_FALSE).
|
||||
AddOp(txscript.OP_IF).
|
||||
AddOp(txscript.OP_FALSE).
|
||||
AddOp(txscript.OP_IF).
|
||||
AddOp(txscript.OP_FALSE).
|
||||
AddOp(txscript.OP_IF).
|
||||
AddData(protocolId).
|
||||
AddOp(txscript.OP_ENDIF).
|
||||
Script())
|
||||
testParseWitness(
|
||||
t,
|
||||
script,
|
||||
[]*Envelope{
|
||||
{
|
||||
Inscription: Inscription{},
|
||||
Stutter: true,
|
||||
},
|
||||
},
|
||||
)
|
||||
|
||||
script = utils.Must(NewPushScriptBuilder().
|
||||
AddOp(txscript.OP_FALSE).
|
||||
AddOp(txscript.OP_FALSE).
|
||||
AddOp(txscript.OP_AND).
|
||||
AddOp(txscript.OP_FALSE).
|
||||
AddOp(txscript.OP_IF).
|
||||
AddData(protocolId).
|
||||
AddOp(txscript.OP_ENDIF).
|
||||
Script())
|
||||
testParseWitness(
|
||||
t,
|
||||
script,
|
||||
[]*Envelope{
|
||||
{
|
||||
Inscription: Inscription{},
|
||||
Stutter: false,
|
||||
},
|
||||
},
|
||||
)
|
||||
})
|
||||
}
|
||||
@@ -1,13 +0,0 @@
|
||||
package ordinals
|
||||
|
||||
import "github.com/gaze-network/indexer-network/common"
|
||||
|
||||
func GetJubileeHeight(network common.Network) uint64 {
|
||||
switch network {
|
||||
case common.NetworkMainnet:
|
||||
return 824544
|
||||
case common.NetworkTestnet:
|
||||
return 2544192
|
||||
}
|
||||
panic("unsupported network")
|
||||
}
|
||||
@@ -1,27 +0,0 @@
|
||||
package ordinals
|
||||
|
||||
import "time"
|
||||
|
||||
type Inscription struct {
|
||||
Content []byte
|
||||
ContentEncoding string
|
||||
ContentType string
|
||||
Delegate *InscriptionId
|
||||
Metadata []byte
|
||||
Metaprotocol string
|
||||
Parent *InscriptionId // in 0.14, inscription has only one parent
|
||||
Pointer *uint64
|
||||
}
|
||||
|
||||
// TODO: refactor ordinals.InscriptionEntry to entity.InscriptionEntry
|
||||
type InscriptionEntry struct {
|
||||
Id InscriptionId
|
||||
Number int64
|
||||
SequenceNumber uint64
|
||||
Cursed bool
|
||||
CursedForBRC20 bool
|
||||
CreatedAt time.Time
|
||||
CreatedAtHeight uint64
|
||||
Inscription Inscription
|
||||
TransferCount uint32
|
||||
}
|
||||
@@ -1,67 +0,0 @@
|
||||
package ordinals
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"github.com/btcsuite/btcd/chaincfg/chainhash"
|
||||
"github.com/cockroachdb/errors"
|
||||
)
|
||||
|
||||
type InscriptionId struct {
|
||||
TxHash chainhash.Hash
|
||||
Index uint32
|
||||
}
|
||||
|
||||
func (i InscriptionId) String() string {
|
||||
return fmt.Sprintf("%si%d", i.TxHash.String(), i.Index)
|
||||
}
|
||||
|
||||
func NewInscriptionId(txHash chainhash.Hash, index uint32) InscriptionId {
|
||||
return InscriptionId{
|
||||
TxHash: txHash,
|
||||
Index: index,
|
||||
}
|
||||
}
|
||||
|
||||
var ErrInscriptionIdInvalidSeparator = fmt.Errorf("invalid inscription id: must contain exactly one separator")
|
||||
|
||||
func NewInscriptionIdFromString(s string) (InscriptionId, error) {
|
||||
parts := strings.SplitN(s, "i", 2)
|
||||
if len(parts) != 2 {
|
||||
return InscriptionId{}, errors.WithStack(ErrInscriptionIdInvalidSeparator)
|
||||
}
|
||||
txHash, err := chainhash.NewHashFromStr(parts[0])
|
||||
if err != nil {
|
||||
return InscriptionId{}, errors.Wrap(err, "invalid inscription id: cannot parse txHash")
|
||||
}
|
||||
index, err := strconv.ParseUint(parts[1], 10, 32)
|
||||
if err != nil {
|
||||
return InscriptionId{}, errors.Wrap(err, "invalid inscription id: cannot parse index")
|
||||
}
|
||||
return InscriptionId{
|
||||
TxHash: *txHash,
|
||||
Index: uint32(index),
|
||||
}, nil
|
||||
}
|
||||
|
||||
// MarshalJSON implements json.Marshaler
|
||||
func (r InscriptionId) MarshalJSON() ([]byte, error) {
|
||||
return []byte(`"` + r.String() + `"`), nil
|
||||
}
|
||||
|
||||
// UnmarshalJSON implements json.Unmarshaler
|
||||
func (r *InscriptionId) 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 := NewInscriptionIdFromString(string(data))
|
||||
if err != nil {
|
||||
return errors.WithStack(err)
|
||||
}
|
||||
*r = parsed
|
||||
return nil
|
||||
}
|
||||
@@ -1,109 +0,0 @@
|
||||
package ordinals
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/Cleverse/go-utilities/utils"
|
||||
"github.com/btcsuite/btcd/chaincfg/chainhash"
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestNewInscriptionIdFromString(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
input string
|
||||
expected InscriptionId
|
||||
shouldError bool
|
||||
}{
|
||||
{
|
||||
name: "valid inscription id 1",
|
||||
input: "1111111111111111111111111111111111111111111111111111111111111111i0",
|
||||
expected: InscriptionId{
|
||||
TxHash: *utils.Must(chainhash.NewHashFromStr("1111111111111111111111111111111111111111111111111111111111111111")),
|
||||
Index: 0,
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "valid inscription id 2",
|
||||
input: "1111111111111111111111111111111111111111111111111111111111111111i1",
|
||||
expected: InscriptionId{
|
||||
TxHash: *utils.Must(chainhash.NewHashFromStr("1111111111111111111111111111111111111111111111111111111111111111")),
|
||||
Index: 1,
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "valid inscription id 3",
|
||||
input: "1111111111111111111111111111111111111111111111111111111111111111i4294967295",
|
||||
expected: InscriptionId{
|
||||
TxHash: *utils.Must(chainhash.NewHashFromStr("1111111111111111111111111111111111111111111111111111111111111111")),
|
||||
Index: 4294967295,
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "error no separator",
|
||||
input: "abc",
|
||||
shouldError: true,
|
||||
},
|
||||
{
|
||||
name: "error invalid index",
|
||||
input: "xyzixyz",
|
||||
shouldError: true,
|
||||
},
|
||||
{
|
||||
name: "error invalid index",
|
||||
input: "abcixyz",
|
||||
shouldError: true,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
actual, err := NewInscriptionIdFromString(tt.input)
|
||||
if tt.shouldError {
|
||||
assert.Error(t, err)
|
||||
} else {
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, tt.expected, actual)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestInscriptionIdString(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
expected string
|
||||
input InscriptionId
|
||||
}{
|
||||
{
|
||||
name: "valid inscription id 1",
|
||||
expected: "1111111111111111111111111111111111111111111111111111111111111111i0",
|
||||
input: InscriptionId{
|
||||
TxHash: *utils.Must(chainhash.NewHashFromStr("1111111111111111111111111111111111111111111111111111111111111111")),
|
||||
Index: 0,
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "valid inscription id 2",
|
||||
expected: "1111111111111111111111111111111111111111111111111111111111111111i1",
|
||||
input: InscriptionId{
|
||||
TxHash: *utils.Must(chainhash.NewHashFromStr("1111111111111111111111111111111111111111111111111111111111111111")),
|
||||
Index: 1,
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "valid inscription id 3",
|
||||
expected: "1111111111111111111111111111111111111111111111111111111111111111i4294967295",
|
||||
input: InscriptionId{
|
||||
TxHash: *utils.Must(chainhash.NewHashFromStr("1111111111111111111111111111111111111111111111111111111111111111")),
|
||||
Index: 4294967295,
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
assert.Equal(t, tt.expected, tt.input.String())
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -1,68 +0,0 @@
|
||||
package ordinals
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"github.com/btcsuite/btcd/chaincfg/chainhash"
|
||||
"github.com/btcsuite/btcd/wire"
|
||||
"github.com/cockroachdb/errors"
|
||||
)
|
||||
|
||||
type SatPoint struct {
|
||||
OutPoint wire.OutPoint
|
||||
Offset uint64
|
||||
}
|
||||
|
||||
func (s SatPoint) String() string {
|
||||
return fmt.Sprintf("%s:%d", s.OutPoint.String(), s.Offset)
|
||||
}
|
||||
|
||||
var ErrSatPointInvalidSeparator = fmt.Errorf("invalid sat point: must contain exactly two separators")
|
||||
|
||||
func NewSatPointFromString(s string) (SatPoint, error) {
|
||||
parts := strings.SplitN(s, ":", 3)
|
||||
if len(parts) != 3 {
|
||||
return SatPoint{}, errors.WithStack(ErrSatPointInvalidSeparator)
|
||||
}
|
||||
txHash, err := chainhash.NewHashFromStr(parts[0])
|
||||
if err != nil {
|
||||
return SatPoint{}, errors.Wrap(err, "invalid inscription id: cannot parse txHash")
|
||||
}
|
||||
index, err := strconv.ParseUint(parts[1], 10, 32)
|
||||
if err != nil {
|
||||
return SatPoint{}, errors.Wrap(err, "invalid inscription id: cannot parse index")
|
||||
}
|
||||
offset, err := strconv.ParseUint(parts[2], 10, 64)
|
||||
if err != nil {
|
||||
return SatPoint{}, errors.Wrap(err, "invalid sat point: cannot parse offset")
|
||||
}
|
||||
return SatPoint{
|
||||
OutPoint: wire.OutPoint{
|
||||
Hash: *txHash,
|
||||
Index: uint32(index),
|
||||
},
|
||||
Offset: offset,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// MarshalJSON implements json.Marshaler
|
||||
func (r SatPoint) MarshalJSON() ([]byte, error) {
|
||||
return []byte(`"` + r.String() + `"`), nil
|
||||
}
|
||||
|
||||
// UnmarshalJSON implements json.Unmarshaler
|
||||
func (r *SatPoint) 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 := NewSatPointFromString(string(data))
|
||||
if err != nil {
|
||||
return errors.WithStack(err)
|
||||
}
|
||||
*r = parsed
|
||||
return nil
|
||||
}
|
||||
@@ -1,89 +0,0 @@
|
||||
package ordinals
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/Cleverse/go-utilities/utils"
|
||||
"github.com/btcsuite/btcd/chaincfg/chainhash"
|
||||
"github.com/btcsuite/btcd/wire"
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestNewSatPointFromString(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
input string
|
||||
expected SatPoint
|
||||
shouldError bool
|
||||
}{
|
||||
{
|
||||
name: "valid sat point",
|
||||
input: "1111111111111111111111111111111111111111111111111111111111111111:1:2",
|
||||
expected: SatPoint{
|
||||
OutPoint: wire.OutPoint{
|
||||
Hash: *utils.Must(chainhash.NewHashFromStr("1111111111111111111111111111111111111111111111111111111111111111")),
|
||||
Index: 1,
|
||||
},
|
||||
Offset: 2,
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "error no separator",
|
||||
input: "abc",
|
||||
shouldError: true,
|
||||
},
|
||||
{
|
||||
name: "error invalid output index",
|
||||
input: "abc:xyz",
|
||||
shouldError: true,
|
||||
},
|
||||
{
|
||||
name: "error no offset",
|
||||
input: "1111111111111111111111111111111111111111111111111111111111111111:1",
|
||||
shouldError: true,
|
||||
},
|
||||
{
|
||||
name: "error invalid offset",
|
||||
input: "1111111111111111111111111111111111111111111111111111111111111111:1:foo",
|
||||
shouldError: true,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
actual, err := NewSatPointFromString(tt.input)
|
||||
if tt.shouldError {
|
||||
assert.Error(t, err)
|
||||
} else {
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, tt.expected, actual)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestSatPointString(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
input SatPoint
|
||||
expected string
|
||||
}{
|
||||
{
|
||||
name: "valid sat point",
|
||||
input: SatPoint{
|
||||
OutPoint: wire.OutPoint{
|
||||
Hash: *utils.Must(chainhash.NewHashFromStr("1111111111111111111111111111111111111111111111111111111111111111")),
|
||||
Index: 1,
|
||||
},
|
||||
Offset: 2,
|
||||
},
|
||||
expected: "1111111111111111111111111111111111111111111111111111111111111111:1:2",
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
assert.Equal(t, tt.expected, tt.input.String())
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -1,170 +0,0 @@
|
||||
package ordinals
|
||||
|
||||
import (
|
||||
"encoding/binary"
|
||||
"fmt"
|
||||
|
||||
"github.com/btcsuite/btcd/txscript"
|
||||
)
|
||||
|
||||
// PushScriptBuilder is a helper to build scripts that requires data pushes to use OP_PUSHDATA* or OP_DATA_* opcodes only.
|
||||
// Empty data pushes are still encoded as OP_0.
|
||||
type PushScriptBuilder struct {
|
||||
script []byte
|
||||
err error
|
||||
}
|
||||
|
||||
func NewPushScriptBuilder() *PushScriptBuilder {
|
||||
return &PushScriptBuilder{}
|
||||
}
|
||||
|
||||
// canonicalDataSize returns the number of bytes the canonical encoding of the
|
||||
// data will take.
|
||||
func canonicalDataSize(data []byte) int {
|
||||
dataLen := len(data)
|
||||
|
||||
// When the data consists of a single number that can be represented
|
||||
// by one of the "small integer" opcodes, that opcode will be instead
|
||||
// of a data push opcode followed by the number.
|
||||
if dataLen == 0 {
|
||||
return 1
|
||||
}
|
||||
|
||||
if dataLen < txscript.OP_PUSHDATA1 {
|
||||
return 1 + dataLen
|
||||
} else if dataLen <= 0xff {
|
||||
return 2 + dataLen
|
||||
} else if dataLen <= 0xffff {
|
||||
return 3 + dataLen
|
||||
}
|
||||
|
||||
return 5 + dataLen
|
||||
}
|
||||
|
||||
func pushDataToBytes(data []byte) []byte {
|
||||
if len(data) == 0 {
|
||||
return []byte{txscript.OP_0}
|
||||
}
|
||||
script := make([]byte, 0)
|
||||
dataLen := len(data)
|
||||
if dataLen < txscript.OP_PUSHDATA1 {
|
||||
script = append(script, byte(txscript.OP_DATA_1-1+dataLen))
|
||||
} else if dataLen <= 0xff {
|
||||
script = append(script, txscript.OP_PUSHDATA1, byte(dataLen))
|
||||
} else if dataLen <= 0xffff {
|
||||
buf := make([]byte, 2)
|
||||
binary.LittleEndian.PutUint16(buf, uint16(dataLen))
|
||||
script = append(script, txscript.OP_PUSHDATA2)
|
||||
script = append(script, buf...)
|
||||
} else {
|
||||
buf := make([]byte, 4)
|
||||
binary.LittleEndian.PutUint32(buf, uint32(dataLen))
|
||||
script = append(script, txscript.OP_PUSHDATA4)
|
||||
script = append(script, buf...)
|
||||
}
|
||||
// Append the actual data.
|
||||
script = append(script, data...)
|
||||
return script
|
||||
}
|
||||
|
||||
// AddData pushes the passed data to the end of the script. It automatically
|
||||
// chooses canonical opcodes depending on the length of the data. A zero length
|
||||
// buffer will lead to a push of empty data onto the stack (OP_0) and any push
|
||||
// of data greater than MaxScriptElementSize will not modify the script since
|
||||
// that is not allowed by the script engine. Also, the script will not be
|
||||
// modified if pushing the data would cause the script to exceed the maximum
|
||||
// allowed script engine size.
|
||||
func (b *PushScriptBuilder) AddData(data []byte) *PushScriptBuilder {
|
||||
if b.err != nil {
|
||||
return b
|
||||
}
|
||||
// Pushes that would cause the script to exceed the largest allowed
|
||||
// script size would result in a non-canonical script.
|
||||
dataSize := canonicalDataSize(data)
|
||||
if len(b.script)+dataSize > txscript.MaxScriptSize {
|
||||
str := fmt.Sprintf("adding %d bytes of data would exceed the "+
|
||||
"maximum allowed canonical script length of %d",
|
||||
dataSize, txscript.MaxScriptSize)
|
||||
b.err = txscript.ErrScriptNotCanonical(str)
|
||||
return b
|
||||
}
|
||||
|
||||
// Pushes larger than the max script element size would result in a
|
||||
// script that is not canonical.
|
||||
dataLen := len(data)
|
||||
if dataLen > txscript.MaxScriptElementSize {
|
||||
str := fmt.Sprintf("adding a data element of %d bytes would "+
|
||||
"exceed the maximum allowed script element size of %d",
|
||||
dataLen, txscript.MaxScriptElementSize)
|
||||
b.err = txscript.ErrScriptNotCanonical(str)
|
||||
return b
|
||||
}
|
||||
|
||||
b.script = append(b.script, pushDataToBytes(data)...)
|
||||
return b
|
||||
}
|
||||
|
||||
// AddFullData should not typically be used by ordinary users as it does not
|
||||
// include the checks which prevent data pushes larger than the maximum allowed
|
||||
// sizes which leads to scripts that can't be executed. This is provided for
|
||||
// testing purposes such as regression tests where sizes are intentionally made
|
||||
// larger than allowed.
|
||||
//
|
||||
// Use AddData instead.
|
||||
func (b *PushScriptBuilder) AddFullData(data []byte) *PushScriptBuilder {
|
||||
if b.err != nil {
|
||||
return b
|
||||
}
|
||||
|
||||
b.script = append(b.script, pushDataToBytes(data)...)
|
||||
return b
|
||||
}
|
||||
|
||||
// AddOp pushes the passed opcode to the end of the script. The script will not
|
||||
// be modified if pushing the opcode would cause the script to exceed the
|
||||
// maximum allowed script engine size.
|
||||
func (b *PushScriptBuilder) AddOp(opcode byte) *PushScriptBuilder {
|
||||
if b.err != nil {
|
||||
return b
|
||||
}
|
||||
|
||||
// Pushes that would cause the script to exceed the largest allowed
|
||||
// script size would result in a non-canonical script.
|
||||
if len(b.script)+1 > txscript.MaxScriptSize {
|
||||
str := fmt.Sprintf("adding an opcode would exceed the maximum "+
|
||||
"allowed canonical script length of %d", txscript.MaxScriptSize)
|
||||
b.err = txscript.ErrScriptNotCanonical(str)
|
||||
return b
|
||||
}
|
||||
|
||||
b.script = append(b.script, opcode)
|
||||
return b
|
||||
}
|
||||
|
||||
// AddOps pushes the passed opcodes to the end of the script. The script will
|
||||
// not be modified if pushing the opcodes would cause the script to exceed the
|
||||
// maximum allowed script engine size.
|
||||
func (b *PushScriptBuilder) AddOps(opcodes []byte) *PushScriptBuilder {
|
||||
if b.err != nil {
|
||||
return b
|
||||
}
|
||||
|
||||
// Pushes that would cause the script to exceed the largest allowed
|
||||
// script size would result in a non-canonical script.
|
||||
if len(b.script)+len(opcodes) > txscript.MaxScriptSize {
|
||||
str := fmt.Sprintf("adding opcodes would exceed the maximum "+
|
||||
"allowed canonical script length of %d", txscript.MaxScriptSize)
|
||||
b.err = txscript.ErrScriptNotCanonical(str)
|
||||
return b
|
||||
}
|
||||
|
||||
b.script = append(b.script, opcodes...)
|
||||
return b
|
||||
}
|
||||
|
||||
// Script returns the currently built script. When any errors occurred while
|
||||
// building the script, the script will be returned up the point of the first
|
||||
// error along with the error.
|
||||
func (b *PushScriptBuilder) Script() ([]byte, error) {
|
||||
return b.script, b.err
|
||||
}
|
||||
@@ -1,81 +0,0 @@
|
||||
package ordinals
|
||||
|
||||
// Tags represent data fields in a runestone. Unrecognized odd tags are ignored. Unrecognized even tags produce a cenotaph.
|
||||
type Tag uint8
|
||||
|
||||
var (
|
||||
TagBody = Tag(0)
|
||||
TagPointer = Tag(2)
|
||||
// TagUnbound is unrecognized
|
||||
TagUnbound = Tag(66)
|
||||
|
||||
TagContentType = Tag(1)
|
||||
TagParent = Tag(3)
|
||||
TagMetadata = Tag(5)
|
||||
TagMetaprotocol = Tag(7)
|
||||
TagContentEncoding = Tag(9)
|
||||
TagDelegate = Tag(11)
|
||||
// TagNop is unrecognized
|
||||
TagNop = Tag(255)
|
||||
)
|
||||
|
||||
var allTags = map[Tag]struct{}{
|
||||
TagPointer: {},
|
||||
|
||||
TagContentType: {},
|
||||
TagParent: {},
|
||||
TagMetadata: {},
|
||||
TagMetaprotocol: {},
|
||||
TagContentEncoding: {},
|
||||
TagDelegate: {},
|
||||
}
|
||||
|
||||
func (t Tag) IsValid() bool {
|
||||
_, ok := allTags[t]
|
||||
return ok
|
||||
}
|
||||
|
||||
var chunkedTags = map[Tag]struct{}{
|
||||
TagMetadata: {},
|
||||
}
|
||||
|
||||
func (t Tag) IsChunked() bool {
|
||||
_, ok := chunkedTags[t]
|
||||
return ok
|
||||
}
|
||||
|
||||
func (t Tag) Bytes() []byte {
|
||||
if t == TagBody {
|
||||
return []byte{} // body tag is empty data push
|
||||
}
|
||||
return []byte{byte(t)}
|
||||
}
|
||||
|
||||
func ParseTag(input interface{}) (Tag, error) {
|
||||
switch input := input.(type) {
|
||||
case Tag:
|
||||
return input, nil
|
||||
case int:
|
||||
return Tag(input), nil
|
||||
case int8:
|
||||
return Tag(input), nil
|
||||
case int16:
|
||||
return Tag(input), nil
|
||||
case int32:
|
||||
return Tag(input), nil
|
||||
case int64:
|
||||
return Tag(input), nil
|
||||
case uint:
|
||||
return Tag(input), nil
|
||||
case uint8:
|
||||
return Tag(input), nil
|
||||
case uint16:
|
||||
return Tag(input), nil
|
||||
case uint32:
|
||||
return Tag(input), nil
|
||||
case uint64:
|
||||
return Tag(input), nil
|
||||
default:
|
||||
panic("invalid tag input type")
|
||||
}
|
||||
}
|
||||
@@ -1,716 +0,0 @@
|
||||
package postgres
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/hex"
|
||||
|
||||
"github.com/btcsuite/btcd/chaincfg/chainhash"
|
||||
"github.com/btcsuite/btcd/wire"
|
||||
"github.com/cockroachdb/errors"
|
||||
"github.com/gaze-network/indexer-network/common/errs"
|
||||
"github.com/gaze-network/indexer-network/core/types"
|
||||
"github.com/gaze-network/indexer-network/modules/brc20/internal/datagateway"
|
||||
"github.com/gaze-network/indexer-network/modules/brc20/internal/entity"
|
||||
"github.com/gaze-network/indexer-network/modules/brc20/internal/ordinals"
|
||||
"github.com/gaze-network/indexer-network/modules/brc20/internal/repository/postgres/gen"
|
||||
"github.com/jackc/pgx/v5"
|
||||
"github.com/samber/lo"
|
||||
)
|
||||
|
||||
var _ datagateway.BRC20DataGateway = (*Repository)(nil)
|
||||
|
||||
// warning: GetLatestBlock currently returns a types.BlockHeader with only Height and Hash fields populated.
|
||||
// This is because it is known that all usage of this function only requires these fields. In the future, we may want to populate all fields for type safety.
|
||||
func (r *Repository) GetLatestBlock(ctx context.Context) (types.BlockHeader, error) {
|
||||
block, err := r.queries.GetLatestIndexedBlock(ctx)
|
||||
if err != nil {
|
||||
if errors.Is(err, pgx.ErrNoRows) {
|
||||
return types.BlockHeader{}, errors.WithStack(errs.NotFound)
|
||||
}
|
||||
return types.BlockHeader{}, errors.Wrap(err, "error during query")
|
||||
}
|
||||
hash, err := chainhash.NewHashFromStr(block.Hash)
|
||||
if err != nil {
|
||||
return types.BlockHeader{}, errors.Wrap(err, "failed to parse block hash")
|
||||
}
|
||||
return types.BlockHeader{
|
||||
Height: int64(block.Height),
|
||||
Hash: *hash,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// GetIndexedBlockByHeight implements datagateway.BRC20DataGateway.
|
||||
func (r *Repository) GetIndexedBlockByHeight(ctx context.Context, height int64) (*entity.IndexedBlock, error) {
|
||||
model, err := r.queries.GetIndexedBlockByHeight(ctx, int32(height))
|
||||
if err != nil {
|
||||
if errors.Is(err, pgx.ErrNoRows) {
|
||||
return nil, errors.WithStack(errs.NotFound)
|
||||
}
|
||||
return nil, errors.Wrap(err, "error during query")
|
||||
}
|
||||
|
||||
indexedBlock, err := mapIndexedBlockModelToType(model)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "failed to parse indexed block model")
|
||||
}
|
||||
return &indexedBlock, nil
|
||||
}
|
||||
|
||||
func (r *Repository) GetProcessorStats(ctx context.Context) (*entity.ProcessorStats, error) {
|
||||
model, err := r.queries.GetLatestProcessorStats(ctx)
|
||||
if err != nil {
|
||||
if errors.Is(err, pgx.ErrNoRows) {
|
||||
return nil, errors.WithStack(errs.NotFound)
|
||||
}
|
||||
return nil, errors.WithStack(err)
|
||||
}
|
||||
stats := mapProcessorStatsModelToType(model)
|
||||
return &stats, nil
|
||||
}
|
||||
|
||||
func (r *Repository) GetInscriptionTransfersInOutPoints(ctx context.Context, outPoints []wire.OutPoint) (map[ordinals.SatPoint][]*entity.InscriptionTransfer, error) {
|
||||
txHashArr := lo.Map(outPoints, func(outPoint wire.OutPoint, _ int) string {
|
||||
return outPoint.Hash.String()
|
||||
})
|
||||
txOutIdxArr := lo.Map(outPoints, func(outPoint wire.OutPoint, _ int) int32 {
|
||||
return int32(outPoint.Index)
|
||||
})
|
||||
models, err := r.queries.GetInscriptionTransfersInOutPoints(ctx, gen.GetInscriptionTransfersInOutPointsParams{
|
||||
TxHashArr: txHashArr,
|
||||
TxOutIdxArr: txOutIdxArr,
|
||||
})
|
||||
if err != nil {
|
||||
return nil, errors.WithStack(err)
|
||||
}
|
||||
results := make(map[ordinals.SatPoint][]*entity.InscriptionTransfer)
|
||||
for _, model := range models {
|
||||
inscriptionTransfer, err := mapInscriptionTransferModelToType(model)
|
||||
if err != nil {
|
||||
return nil, errors.WithStack(err)
|
||||
}
|
||||
results[inscriptionTransfer.NewSatPoint] = append(results[inscriptionTransfer.NewSatPoint], &inscriptionTransfer)
|
||||
}
|
||||
return results, nil
|
||||
}
|
||||
|
||||
func (r *Repository) GetInscriptionEntriesByIds(ctx context.Context, ids []ordinals.InscriptionId) (map[ordinals.InscriptionId]*ordinals.InscriptionEntry, error) {
|
||||
idStrs := lo.Map(ids, func(id ordinals.InscriptionId, _ int) string { return id.String() })
|
||||
models, err := r.queries.GetInscriptionEntriesByIds(ctx, idStrs)
|
||||
if err != nil {
|
||||
return nil, errors.WithStack(err)
|
||||
}
|
||||
|
||||
result := make(map[ordinals.InscriptionId]*ordinals.InscriptionEntry)
|
||||
for _, model := range models {
|
||||
inscriptionEntry, err := mapInscriptionEntryModelToType(model)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "failed to parse inscription entry model")
|
||||
}
|
||||
result[inscriptionEntry.Id] = &inscriptionEntry
|
||||
}
|
||||
return result, nil
|
||||
}
|
||||
|
||||
func (r *Repository) GetInscriptionNumbersByIds(ctx context.Context, ids []ordinals.InscriptionId) (map[ordinals.InscriptionId]int64, error) {
|
||||
idStrs := lo.Map(ids, func(id ordinals.InscriptionId, _ int) string { return id.String() })
|
||||
models, err := r.queries.GetInscriptionNumbersByIds(ctx, idStrs)
|
||||
if err != nil {
|
||||
return nil, errors.WithStack(err)
|
||||
}
|
||||
|
||||
result := make(map[ordinals.InscriptionId]int64)
|
||||
for _, model := range models {
|
||||
inscriptionId, err := ordinals.NewInscriptionIdFromString(model.Id)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "failed to parse inscription id")
|
||||
}
|
||||
result[inscriptionId] = model.Number
|
||||
}
|
||||
return result, nil
|
||||
}
|
||||
|
||||
func (r *Repository) GetInscriptionParentsByIds(ctx context.Context, ids []ordinals.InscriptionId) (map[ordinals.InscriptionId]ordinals.InscriptionId, error) {
|
||||
idStrs := lo.Map(ids, func(id ordinals.InscriptionId, _ int) string { return id.String() })
|
||||
models, err := r.queries.GetInscriptionParentsByIds(ctx, idStrs)
|
||||
if err != nil {
|
||||
return nil, errors.WithStack(err)
|
||||
}
|
||||
|
||||
result := make(map[ordinals.InscriptionId]ordinals.InscriptionId)
|
||||
for _, model := range models {
|
||||
if len(model.Parents) == 0 {
|
||||
// no parent
|
||||
continue
|
||||
}
|
||||
if len(model.Parents) > 1 {
|
||||
// sanity check, should not happen since 0.14 ord supports only 1 parent
|
||||
continue
|
||||
}
|
||||
inscriptionId, err := ordinals.NewInscriptionIdFromString(model.Id)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "failed to parse inscription id")
|
||||
}
|
||||
parentId, err := ordinals.NewInscriptionIdFromString(model.Parents[0])
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "failed to parse parent id")
|
||||
}
|
||||
result[inscriptionId] = parentId
|
||||
}
|
||||
return result, nil
|
||||
}
|
||||
|
||||
func (r *Repository) GetLatestEventId(ctx context.Context) (int64, error) {
|
||||
row, err := r.queries.GetLatestEventIds(ctx)
|
||||
if err != nil {
|
||||
return 0, errors.WithStack(err)
|
||||
}
|
||||
return max(row.EventDeployID.(int64), row.EventMintID.(int64), row.EventInscribeTransferID.(int64), row.EventTransferTransferID.(int64)), nil
|
||||
}
|
||||
|
||||
func (r *Repository) GetBalancesBatchAtHeight(ctx context.Context, blockHeight uint64, queries []datagateway.GetBalancesBatchAtHeightQuery) (map[string]map[string]*entity.Balance, error) {
|
||||
pkScripts := make([]string, 0)
|
||||
ticks := make([]string, 0)
|
||||
for _, query := range queries {
|
||||
pkScripts = append(pkScripts, query.PkScriptHex)
|
||||
ticks = append(ticks, query.Tick)
|
||||
}
|
||||
models, err := r.queries.GetBalancesBatchAtHeight(ctx, gen.GetBalancesBatchAtHeightParams{
|
||||
PkscriptArr: pkScripts,
|
||||
TickArr: ticks,
|
||||
BlockHeight: int32(blockHeight),
|
||||
})
|
||||
if err != nil {
|
||||
return nil, errors.WithStack(err)
|
||||
}
|
||||
|
||||
result := make(map[string]map[string]*entity.Balance)
|
||||
for _, model := range models {
|
||||
balance, err := mapBalanceModelToType(model)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "failed to parse balance model")
|
||||
}
|
||||
if _, ok := result[model.Pkscript]; !ok {
|
||||
result[model.Pkscript] = make(map[string]*entity.Balance)
|
||||
}
|
||||
result[model.Pkscript][model.Tick] = &balance
|
||||
}
|
||||
return result, nil
|
||||
}
|
||||
|
||||
func (r *Repository) GetEventInscribeTransfersByInscriptionIds(ctx context.Context, ids []ordinals.InscriptionId) (map[ordinals.InscriptionId]*entity.EventInscribeTransfer, error) {
|
||||
idStrs := lo.Map(ids, func(id ordinals.InscriptionId, _ int) string { return id.String() })
|
||||
models, err := r.queries.GetEventInscribeTransfersByInscriptionIds(ctx, idStrs)
|
||||
if err != nil {
|
||||
return nil, errors.WithStack(err)
|
||||
}
|
||||
|
||||
result := make(map[ordinals.InscriptionId]*entity.EventInscribeTransfer)
|
||||
for _, model := range models {
|
||||
event, err := mapEventInscribeTransferModelToType(model)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "failed to parse event inscribe transfer model")
|
||||
}
|
||||
result[event.InscriptionId] = &event
|
||||
}
|
||||
return result, nil
|
||||
}
|
||||
|
||||
func (r *Repository) GetTickEntriesByTicks(ctx context.Context, ticks []string) (map[string]*entity.TickEntry, error) {
|
||||
models, err := r.queries.GetTickEntriesByTicks(ctx, ticks)
|
||||
if err != nil {
|
||||
return nil, errors.WithStack(err)
|
||||
}
|
||||
|
||||
result := make(map[string]*entity.TickEntry)
|
||||
for _, model := range models {
|
||||
tickEntry, err := mapTickEntryModelToType(model)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "failed to parse tick entry model")
|
||||
}
|
||||
result[tickEntry.Tick] = &tickEntry
|
||||
}
|
||||
return result, nil
|
||||
}
|
||||
|
||||
func (r *Repository) GetBalancesByTick(ctx context.Context, tick string, blockHeight uint64) ([]*entity.Balance, error) {
|
||||
models, err := r.queries.GetBalancesByTick(ctx, gen.GetBalancesByTickParams{
|
||||
Tick: tick,
|
||||
BlockHeight: int32(blockHeight),
|
||||
})
|
||||
if err != nil {
|
||||
return nil, errors.WithStack(err)
|
||||
}
|
||||
|
||||
result := make([]*entity.Balance, 0, len(models))
|
||||
for _, model := range models {
|
||||
balance, err := mapBalanceModelToType(gen.Brc20Balance(model))
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "failed to parse balance model")
|
||||
}
|
||||
result = append(result, &balance)
|
||||
}
|
||||
return result, nil
|
||||
}
|
||||
|
||||
func (r *Repository) GetBalancesByPkScript(ctx context.Context, pkScript []byte, blockHeight uint64) (map[string]*entity.Balance, error) {
|
||||
models, err := r.queries.GetBalancesByPkScript(ctx, gen.GetBalancesByPkScriptParams{
|
||||
Pkscript: hex.EncodeToString(pkScript),
|
||||
BlockHeight: int32(blockHeight),
|
||||
})
|
||||
if err != nil {
|
||||
return nil, errors.WithStack(err)
|
||||
}
|
||||
|
||||
result := make(map[string]*entity.Balance)
|
||||
for _, model := range models {
|
||||
balance, err := mapBalanceModelToType(gen.Brc20Balance(model))
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "failed to parse balance model")
|
||||
}
|
||||
result[balance.Tick] = &balance
|
||||
}
|
||||
return result, nil
|
||||
}
|
||||
|
||||
func (r *Repository) GetTransferableTransfersByPkScript(ctx context.Context, pkScript []byte, blockHeight uint64) ([]*entity.EventInscribeTransfer, error) {
|
||||
models, err := r.queries.GetTransferableTransfersByPkScript(ctx, gen.GetTransferableTransfersByPkScriptParams{
|
||||
Pkscript: hex.EncodeToString(pkScript),
|
||||
BlockHeight: int32(blockHeight),
|
||||
})
|
||||
if err != nil {
|
||||
return nil, errors.WithStack(err)
|
||||
}
|
||||
|
||||
result := make([]*entity.EventInscribeTransfer, 0, len(models))
|
||||
for _, model := range models {
|
||||
ent, err := mapEventInscribeTransferModelToType(model)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "failed to parse event model")
|
||||
}
|
||||
result = append(result, &ent)
|
||||
}
|
||||
return result, nil
|
||||
}
|
||||
|
||||
func (r *Repository) GetDeployEventByTick(ctx context.Context, tick string) (*entity.EventDeploy, error) {
|
||||
model, err := r.queries.GetDeployEventByTick(ctx, tick)
|
||||
if err != nil {
|
||||
return nil, errors.WithStack(err)
|
||||
}
|
||||
|
||||
ent, err := mapEventDeployModelToType(model)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "failed to parse event model")
|
||||
}
|
||||
return &ent, nil
|
||||
}
|
||||
|
||||
func (r *Repository) GetFirstLastInscriptionNumberByTick(ctx context.Context, tick string) (first, last int64, err error) {
|
||||
model, err := r.queries.GetFirstLastInscriptionNumberByTick(ctx, tick)
|
||||
if err != nil {
|
||||
return -1, -1, errors.WithStack(err)
|
||||
}
|
||||
return model.FirstInscriptionNumber, model.LastInscriptionNumber, nil
|
||||
}
|
||||
|
||||
func (r *Repository) GetDeployEvents(ctx context.Context, pkScript []byte, tick string, height uint64) ([]*entity.EventDeploy, error) {
|
||||
models, err := r.queries.GetDeployEvents(ctx, gen.GetDeployEventsParams{
|
||||
FilterPkScript: pkScript != nil,
|
||||
PkScript: hex.EncodeToString(pkScript),
|
||||
FilterTicker: tick != "",
|
||||
Ticker: tick,
|
||||
BlockHeight: int32(height),
|
||||
})
|
||||
if err != nil {
|
||||
return nil, errors.WithStack(err)
|
||||
}
|
||||
|
||||
result := make([]*entity.EventDeploy, 0, len(models))
|
||||
for _, model := range models {
|
||||
ent, err := mapEventDeployModelToType(model)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "failed to parse event model")
|
||||
}
|
||||
result = append(result, &ent)
|
||||
}
|
||||
return result, nil
|
||||
}
|
||||
|
||||
func (r *Repository) GetMintEvents(ctx context.Context, pkScript []byte, tick string, height uint64) ([]*entity.EventMint, error) {
|
||||
models, err := r.queries.GetMintEvents(ctx, gen.GetMintEventsParams{
|
||||
FilterPkScript: pkScript != nil,
|
||||
PkScript: hex.EncodeToString(pkScript),
|
||||
FilterTicker: tick != "",
|
||||
Ticker: tick,
|
||||
BlockHeight: int32(height),
|
||||
})
|
||||
if err != nil {
|
||||
return nil, errors.WithStack(err)
|
||||
}
|
||||
|
||||
result := make([]*entity.EventMint, 0, len(models))
|
||||
for _, model := range models {
|
||||
ent, err := mapEventMintModelToType(model)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "failed to parse event model")
|
||||
}
|
||||
result = append(result, &ent)
|
||||
}
|
||||
return result, nil
|
||||
}
|
||||
|
||||
func (r *Repository) GetInscribeTransferEvents(ctx context.Context, pkScript []byte, tick string, height uint64) ([]*entity.EventInscribeTransfer, error) {
|
||||
models, err := r.queries.GetInscribeTransferEvents(ctx, gen.GetInscribeTransferEventsParams{
|
||||
FilterPkScript: pkScript != nil,
|
||||
PkScript: hex.EncodeToString(pkScript),
|
||||
FilterTicker: tick != "",
|
||||
Ticker: tick,
|
||||
BlockHeight: int32(height),
|
||||
})
|
||||
if err != nil {
|
||||
return nil, errors.WithStack(err)
|
||||
}
|
||||
|
||||
result := make([]*entity.EventInscribeTransfer, 0, len(models))
|
||||
for _, model := range models {
|
||||
ent, err := mapEventInscribeTransferModelToType(model)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "failed to parse event model")
|
||||
}
|
||||
result = append(result, &ent)
|
||||
}
|
||||
return result, nil
|
||||
}
|
||||
|
||||
func (r *Repository) GetTransferTransferEvents(ctx context.Context, pkScript []byte, tick string, height uint64) ([]*entity.EventTransferTransfer, error) {
|
||||
models, err := r.queries.GetTransferTransferEvents(ctx, gen.GetTransferTransferEventsParams{
|
||||
FilterPkScript: pkScript != nil,
|
||||
PkScript: hex.EncodeToString(pkScript),
|
||||
FilterTicker: tick != "",
|
||||
Ticker: tick,
|
||||
BlockHeight: int32(height),
|
||||
})
|
||||
if err != nil {
|
||||
return nil, errors.WithStack(err)
|
||||
}
|
||||
|
||||
result := make([]*entity.EventTransferTransfer, 0, len(models))
|
||||
for _, model := range models {
|
||||
ent, err := mapEventTransferTransferModelToType(model)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "failed to parse event model")
|
||||
}
|
||||
result = append(result, &ent)
|
||||
}
|
||||
return result, nil
|
||||
}
|
||||
|
||||
func (r *Repository) CreateIndexedBlock(ctx context.Context, block *entity.IndexedBlock) error {
|
||||
params := mapIndexedBlockTypeToParams(*block)
|
||||
if err := r.queries.CreateIndexedBlock(ctx, params); err != nil {
|
||||
return errors.WithStack(err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (r *Repository) CreateProcessorStats(ctx context.Context, stats *entity.ProcessorStats) error {
|
||||
params := mapProcessorStatsTypeToParams(*stats)
|
||||
if err := r.queries.CreateProcessorStats(ctx, params); err != nil {
|
||||
return errors.WithStack(err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (r *Repository) CreateTickEntries(ctx context.Context, blockHeight uint64, entries []*entity.TickEntry) error {
|
||||
entryParams := make([]gen.CreateTickEntriesParams, 0)
|
||||
for _, entry := range entries {
|
||||
params, _, err := mapTickEntryTypeToParams(*entry, blockHeight)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "cannot map tick entry to create params")
|
||||
}
|
||||
entryParams = append(entryParams, params)
|
||||
}
|
||||
results := r.queries.CreateTickEntries(ctx, entryParams)
|
||||
var execErrors []error
|
||||
results.Exec(func(i int, err error) {
|
||||
if err != nil {
|
||||
execErrors = append(execErrors, err)
|
||||
}
|
||||
})
|
||||
if len(execErrors) > 0 {
|
||||
return errors.Wrap(errors.Join(execErrors...), "error during exec")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (r *Repository) CreateTickEntryStates(ctx context.Context, blockHeight uint64, entryStates []*entity.TickEntry) error {
|
||||
entryParams := make([]gen.CreateTickEntryStatesParams, 0)
|
||||
for _, entry := range entryStates {
|
||||
_, params, err := mapTickEntryTypeToParams(*entry, blockHeight)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "cannot map tick entry to create params")
|
||||
}
|
||||
entryParams = append(entryParams, params)
|
||||
}
|
||||
results := r.queries.CreateTickEntryStates(ctx, entryParams)
|
||||
var execErrors []error
|
||||
results.Exec(func(i int, err error) {
|
||||
if err != nil {
|
||||
execErrors = append(execErrors, err)
|
||||
}
|
||||
})
|
||||
if len(execErrors) > 0 {
|
||||
return errors.Wrap(errors.Join(execErrors...), "error during exec")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (r *Repository) CreateInscriptionEntries(ctx context.Context, blockHeight uint64, entries []*ordinals.InscriptionEntry) error {
|
||||
inscriptionEntryParams := make([]gen.CreateInscriptionEntriesParams, 0)
|
||||
for _, entry := range entries {
|
||||
params, _, err := mapInscriptionEntryTypeToParams(*entry, blockHeight)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "cannot map inscription entry to create params")
|
||||
}
|
||||
inscriptionEntryParams = append(inscriptionEntryParams, params)
|
||||
}
|
||||
results := r.queries.CreateInscriptionEntries(ctx, inscriptionEntryParams)
|
||||
var execErrors []error
|
||||
results.Exec(func(i int, err error) {
|
||||
if err != nil {
|
||||
execErrors = append(execErrors, err)
|
||||
}
|
||||
})
|
||||
if len(execErrors) > 0 {
|
||||
return errors.Wrap(errors.Join(execErrors...), "error during exec")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (r *Repository) CreateInscriptionEntryStates(ctx context.Context, blockHeight uint64, entryStates []*ordinals.InscriptionEntry) error {
|
||||
inscriptionEntryStatesParams := make([]gen.CreateInscriptionEntryStatesParams, 0)
|
||||
for _, entry := range entryStates {
|
||||
_, params, err := mapInscriptionEntryTypeToParams(*entry, blockHeight)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "cannot map inscription entry to create params")
|
||||
}
|
||||
inscriptionEntryStatesParams = append(inscriptionEntryStatesParams, params)
|
||||
}
|
||||
results := r.queries.CreateInscriptionEntryStates(ctx, inscriptionEntryStatesParams)
|
||||
var execErrors []error
|
||||
results.Exec(func(i int, err error) {
|
||||
if err != nil {
|
||||
execErrors = append(execErrors, err)
|
||||
}
|
||||
})
|
||||
if len(execErrors) > 0 {
|
||||
return errors.Wrap(errors.Join(execErrors...), "error during exec")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (r *Repository) CreateInscriptionTransfers(ctx context.Context, transfers []*entity.InscriptionTransfer) error {
|
||||
params := lo.Map(transfers, func(transfer *entity.InscriptionTransfer, _ int) gen.CreateInscriptionTransfersParams {
|
||||
return mapInscriptionTransferTypeToParams(*transfer)
|
||||
})
|
||||
results := r.queries.CreateInscriptionTransfers(ctx, params)
|
||||
var execErrors []error
|
||||
results.Exec(func(i int, err error) {
|
||||
if err != nil {
|
||||
execErrors = append(execErrors, err)
|
||||
}
|
||||
})
|
||||
if len(execErrors) > 0 {
|
||||
return errors.Wrap(errors.Join(execErrors...), "error during exec")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (r *Repository) CreateEventDeploys(ctx context.Context, events []*entity.EventDeploy) error {
|
||||
params := make([]gen.CreateEventDeploysParams, 0)
|
||||
for _, event := range events {
|
||||
param, err := mapEventDeployTypeToParams(*event)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "cannot map event deploy to create params")
|
||||
}
|
||||
params = append(params, param)
|
||||
}
|
||||
results := r.queries.CreateEventDeploys(ctx, params)
|
||||
var execErrors []error
|
||||
results.Exec(func(i int, err error) {
|
||||
if err != nil {
|
||||
execErrors = append(execErrors, err)
|
||||
}
|
||||
})
|
||||
if len(execErrors) > 0 {
|
||||
return errors.Wrap(errors.Join(execErrors...), "error during exec")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (r *Repository) CreateEventMints(ctx context.Context, events []*entity.EventMint) error {
|
||||
params := make([]gen.CreateEventMintsParams, 0)
|
||||
for _, event := range events {
|
||||
param, err := mapEventMintTypeToParams(*event)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "cannot map event mint to create params")
|
||||
}
|
||||
params = append(params, param)
|
||||
}
|
||||
results := r.queries.CreateEventMints(ctx, params)
|
||||
var execErrors []error
|
||||
results.Exec(func(i int, err error) {
|
||||
if err != nil {
|
||||
execErrors = append(execErrors, err)
|
||||
}
|
||||
})
|
||||
if len(execErrors) > 0 {
|
||||
return errors.Wrap(errors.Join(execErrors...), "error during exec")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (r *Repository) CreateEventInscribeTransfers(ctx context.Context, events []*entity.EventInscribeTransfer) error {
|
||||
params := make([]gen.CreateEventInscribeTransfersParams, 0)
|
||||
for _, event := range events {
|
||||
param, err := mapEventInscribeTransferTypeToParams(*event)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "cannot map event transfer to create params")
|
||||
}
|
||||
params = append(params, param)
|
||||
}
|
||||
results := r.queries.CreateEventInscribeTransfers(ctx, params)
|
||||
var execErrors []error
|
||||
results.Exec(func(i int, err error) {
|
||||
if err != nil {
|
||||
execErrors = append(execErrors, err)
|
||||
}
|
||||
})
|
||||
if len(execErrors) > 0 {
|
||||
return errors.Wrap(errors.Join(execErrors...), "error during exec")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (r *Repository) CreateEventTransferTransfers(ctx context.Context, events []*entity.EventTransferTransfer) error {
|
||||
params := make([]gen.CreateEventTransferTransfersParams, 0)
|
||||
for _, event := range events {
|
||||
param, err := mapEventTransferTransferTypeToParams(*event)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "cannot map event transfer to create params")
|
||||
}
|
||||
params = append(params, param)
|
||||
}
|
||||
results := r.queries.CreateEventTransferTransfers(ctx, params)
|
||||
var execErrors []error
|
||||
results.Exec(func(i int, err error) {
|
||||
if err != nil {
|
||||
execErrors = append(execErrors, err)
|
||||
}
|
||||
})
|
||||
if len(execErrors) > 0 {
|
||||
return errors.Wrap(errors.Join(execErrors...), "error during exec")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (r *Repository) CreateBalances(ctx context.Context, balances []*entity.Balance) error {
|
||||
params := lo.Map(balances, func(balance *entity.Balance, _ int) gen.CreateBalancesParams {
|
||||
return mapBalanceTypeToParams(*balance)
|
||||
})
|
||||
results := r.queries.CreateBalances(ctx, params)
|
||||
var execErrors []error
|
||||
results.Exec(func(i int, err error) {
|
||||
if err != nil {
|
||||
execErrors = append(execErrors, err)
|
||||
}
|
||||
})
|
||||
if len(execErrors) > 0 {
|
||||
return errors.Wrap(errors.Join(execErrors...), "error during exec")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (r *Repository) DeleteIndexedBlocksSinceHeight(ctx context.Context, height uint64) error {
|
||||
if err := r.queries.DeleteIndexedBlocksSinceHeight(ctx, int32(height)); err != nil {
|
||||
return errors.Wrap(err, "error during exec")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (r *Repository) DeleteProcessorStatsSinceHeight(ctx context.Context, height uint64) error {
|
||||
if err := r.queries.DeleteProcessorStatsSinceHeight(ctx, int32(height)); err != nil {
|
||||
return errors.Wrap(err, "error during exec")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (r *Repository) DeleteTickEntriesSinceHeight(ctx context.Context, height uint64) error {
|
||||
if err := r.queries.DeleteTickEntriesSinceHeight(ctx, int32(height)); err != nil {
|
||||
return errors.Wrap(err, "error during exec")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (r *Repository) DeleteTickEntryStatesSinceHeight(ctx context.Context, height uint64) error {
|
||||
if err := r.queries.DeleteTickEntryStatesSinceHeight(ctx, int32(height)); err != nil {
|
||||
return errors.Wrap(err, "error during exec")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (r *Repository) DeleteEventDeploysSinceHeight(ctx context.Context, height uint64) error {
|
||||
if err := r.queries.DeleteEventDeploysSinceHeight(ctx, int32(height)); err != nil {
|
||||
return errors.Wrap(err, "error during exec")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (r *Repository) DeleteEventMintsSinceHeight(ctx context.Context, height uint64) error {
|
||||
if err := r.queries.DeleteEventMintsSinceHeight(ctx, int32(height)); err != nil {
|
||||
return errors.Wrap(err, "error during exec")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (r *Repository) DeleteEventInscribeTransfersSinceHeight(ctx context.Context, height uint64) error {
|
||||
if err := r.queries.DeleteEventInscribeTransfersSinceHeight(ctx, int32(height)); err != nil {
|
||||
return errors.Wrap(err, "error during exec")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (r *Repository) DeleteEventTransferTransfersSinceHeight(ctx context.Context, height uint64) error {
|
||||
if err := r.queries.DeleteEventTransferTransfersSinceHeight(ctx, int32(height)); err != nil {
|
||||
return errors.Wrap(err, "error during exec")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (r *Repository) DeleteBalancesSinceHeight(ctx context.Context, height uint64) error {
|
||||
if err := r.queries.DeleteBalancesSinceHeight(ctx, int32(height)); err != nil {
|
||||
return errors.Wrap(err, "error during exec")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (r *Repository) DeleteInscriptionEntriesSinceHeight(ctx context.Context, height uint64) error {
|
||||
if err := r.queries.DeleteInscriptionEntriesSinceHeight(ctx, int32(height)); err != nil {
|
||||
return errors.Wrap(err, "error during exec")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (r *Repository) DeleteInscriptionEntryStatesSinceHeight(ctx context.Context, height uint64) error {
|
||||
if err := r.queries.DeleteInscriptionEntryStatesSinceHeight(ctx, int32(height)); err != nil {
|
||||
return errors.Wrap(err, "error during exec")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (r *Repository) DeleteInscriptionTransfersSinceHeight(ctx context.Context, height uint64) error {
|
||||
if err := r.queries.DeleteInscriptionTransfersSinceHeight(ctx, int32(height)); err != nil {
|
||||
return errors.Wrap(err, "error during exec")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
@@ -1,696 +0,0 @@
|
||||
// Code generated by sqlc. DO NOT EDIT.
|
||||
// versions:
|
||||
// sqlc v1.26.0
|
||||
// source: batch.go
|
||||
|
||||
package gen
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
|
||||
"github.com/jackc/pgx/v5"
|
||||
"github.com/jackc/pgx/v5/pgtype"
|
||||
)
|
||||
|
||||
var (
|
||||
ErrBatchAlreadyClosed = errors.New("batch already closed")
|
||||
)
|
||||
|
||||
const createBalances = `-- name: CreateBalances :batchexec
|
||||
INSERT INTO "brc20_balances" ("pkscript", "block_height", "tick", "overall_balance", "available_balance") VALUES ($1, $2, $3, $4, $5)
|
||||
`
|
||||
|
||||
type CreateBalancesBatchResults struct {
|
||||
br pgx.BatchResults
|
||||
tot int
|
||||
closed bool
|
||||
}
|
||||
|
||||
type CreateBalancesParams struct {
|
||||
Pkscript string
|
||||
BlockHeight int32
|
||||
Tick string
|
||||
OverallBalance pgtype.Numeric
|
||||
AvailableBalance pgtype.Numeric
|
||||
}
|
||||
|
||||
func (q *Queries) CreateBalances(ctx context.Context, arg []CreateBalancesParams) *CreateBalancesBatchResults {
|
||||
batch := &pgx.Batch{}
|
||||
for _, a := range arg {
|
||||
vals := []interface{}{
|
||||
a.Pkscript,
|
||||
a.BlockHeight,
|
||||
a.Tick,
|
||||
a.OverallBalance,
|
||||
a.AvailableBalance,
|
||||
}
|
||||
batch.Queue(createBalances, vals...)
|
||||
}
|
||||
br := q.db.SendBatch(ctx, batch)
|
||||
return &CreateBalancesBatchResults{br, len(arg), false}
|
||||
}
|
||||
|
||||
func (b *CreateBalancesBatchResults) Exec(f func(int, error)) {
|
||||
defer b.br.Close()
|
||||
for t := 0; t < b.tot; t++ {
|
||||
if b.closed {
|
||||
if f != nil {
|
||||
f(t, ErrBatchAlreadyClosed)
|
||||
}
|
||||
continue
|
||||
}
|
||||
_, err := b.br.Exec()
|
||||
if f != nil {
|
||||
f(t, err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (b *CreateBalancesBatchResults) Close() error {
|
||||
b.closed = true
|
||||
return b.br.Close()
|
||||
}
|
||||
|
||||
const createEventDeploys = `-- name: CreateEventDeploys :batchexec
|
||||
INSERT INTO "brc20_event_deploys" ("id", "inscription_id", "inscription_number", "tick", "original_tick", "tx_hash", "block_height", "tx_index", "timestamp", "pkscript", "satpoint", "total_supply", "decimals", "limit_per_mint", "is_self_mint") VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13, $14, $15)
|
||||
`
|
||||
|
||||
type CreateEventDeploysBatchResults struct {
|
||||
br pgx.BatchResults
|
||||
tot int
|
||||
closed bool
|
||||
}
|
||||
|
||||
type CreateEventDeploysParams struct {
|
||||
Id int64
|
||||
InscriptionID string
|
||||
InscriptionNumber int64
|
||||
Tick string
|
||||
OriginalTick string
|
||||
TxHash string
|
||||
BlockHeight int32
|
||||
TxIndex int32
|
||||
Timestamp pgtype.Timestamp
|
||||
Pkscript string
|
||||
Satpoint string
|
||||
TotalSupply pgtype.Numeric
|
||||
Decimals int16
|
||||
LimitPerMint pgtype.Numeric
|
||||
IsSelfMint bool
|
||||
}
|
||||
|
||||
func (q *Queries) CreateEventDeploys(ctx context.Context, arg []CreateEventDeploysParams) *CreateEventDeploysBatchResults {
|
||||
batch := &pgx.Batch{}
|
||||
for _, a := range arg {
|
||||
vals := []interface{}{
|
||||
a.Id,
|
||||
a.InscriptionID,
|
||||
a.InscriptionNumber,
|
||||
a.Tick,
|
||||
a.OriginalTick,
|
||||
a.TxHash,
|
||||
a.BlockHeight,
|
||||
a.TxIndex,
|
||||
a.Timestamp,
|
||||
a.Pkscript,
|
||||
a.Satpoint,
|
||||
a.TotalSupply,
|
||||
a.Decimals,
|
||||
a.LimitPerMint,
|
||||
a.IsSelfMint,
|
||||
}
|
||||
batch.Queue(createEventDeploys, vals...)
|
||||
}
|
||||
br := q.db.SendBatch(ctx, batch)
|
||||
return &CreateEventDeploysBatchResults{br, len(arg), false}
|
||||
}
|
||||
|
||||
func (b *CreateEventDeploysBatchResults) Exec(f func(int, error)) {
|
||||
defer b.br.Close()
|
||||
for t := 0; t < b.tot; t++ {
|
||||
if b.closed {
|
||||
if f != nil {
|
||||
f(t, ErrBatchAlreadyClosed)
|
||||
}
|
||||
continue
|
||||
}
|
||||
_, err := b.br.Exec()
|
||||
if f != nil {
|
||||
f(t, err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (b *CreateEventDeploysBatchResults) Close() error {
|
||||
b.closed = true
|
||||
return b.br.Close()
|
||||
}
|
||||
|
||||
const createEventInscribeTransfers = `-- name: CreateEventInscribeTransfers :batchexec
|
||||
INSERT INTO "brc20_event_inscribe_transfers" ("id", "inscription_id", "inscription_number", "tick", "original_tick", "tx_hash", "block_height", "tx_index", "timestamp", "pkscript", "satpoint", "output_index", "sats_amount", "amount") VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13, $14)
|
||||
`
|
||||
|
||||
type CreateEventInscribeTransfersBatchResults struct {
|
||||
br pgx.BatchResults
|
||||
tot int
|
||||
closed bool
|
||||
}
|
||||
|
||||
type CreateEventInscribeTransfersParams struct {
|
||||
Id int64
|
||||
InscriptionID string
|
||||
InscriptionNumber int64
|
||||
Tick string
|
||||
OriginalTick string
|
||||
TxHash string
|
||||
BlockHeight int32
|
||||
TxIndex int32
|
||||
Timestamp pgtype.Timestamp
|
||||
Pkscript string
|
||||
Satpoint string
|
||||
OutputIndex int32
|
||||
SatsAmount int64
|
||||
Amount pgtype.Numeric
|
||||
}
|
||||
|
||||
func (q *Queries) CreateEventInscribeTransfers(ctx context.Context, arg []CreateEventInscribeTransfersParams) *CreateEventInscribeTransfersBatchResults {
|
||||
batch := &pgx.Batch{}
|
||||
for _, a := range arg {
|
||||
vals := []interface{}{
|
||||
a.Id,
|
||||
a.InscriptionID,
|
||||
a.InscriptionNumber,
|
||||
a.Tick,
|
||||
a.OriginalTick,
|
||||
a.TxHash,
|
||||
a.BlockHeight,
|
||||
a.TxIndex,
|
||||
a.Timestamp,
|
||||
a.Pkscript,
|
||||
a.Satpoint,
|
||||
a.OutputIndex,
|
||||
a.SatsAmount,
|
||||
a.Amount,
|
||||
}
|
||||
batch.Queue(createEventInscribeTransfers, vals...)
|
||||
}
|
||||
br := q.db.SendBatch(ctx, batch)
|
||||
return &CreateEventInscribeTransfersBatchResults{br, len(arg), false}
|
||||
}
|
||||
|
||||
func (b *CreateEventInscribeTransfersBatchResults) Exec(f func(int, error)) {
|
||||
defer b.br.Close()
|
||||
for t := 0; t < b.tot; t++ {
|
||||
if b.closed {
|
||||
if f != nil {
|
||||
f(t, ErrBatchAlreadyClosed)
|
||||
}
|
||||
continue
|
||||
}
|
||||
_, err := b.br.Exec()
|
||||
if f != nil {
|
||||
f(t, err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (b *CreateEventInscribeTransfersBatchResults) Close() error {
|
||||
b.closed = true
|
||||
return b.br.Close()
|
||||
}
|
||||
|
||||
const createEventMints = `-- name: CreateEventMints :batchexec
|
||||
INSERT INTO "brc20_event_mints" ("id", "inscription_id", "inscription_number", "tick", "original_tick", "tx_hash", "block_height", "tx_index", "timestamp", "pkscript", "satpoint", "amount", "parent_id") VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13)
|
||||
`
|
||||
|
||||
type CreateEventMintsBatchResults struct {
|
||||
br pgx.BatchResults
|
||||
tot int
|
||||
closed bool
|
||||
}
|
||||
|
||||
type CreateEventMintsParams struct {
|
||||
Id int64
|
||||
InscriptionID string
|
||||
InscriptionNumber int64
|
||||
Tick string
|
||||
OriginalTick string
|
||||
TxHash string
|
||||
BlockHeight int32
|
||||
TxIndex int32
|
||||
Timestamp pgtype.Timestamp
|
||||
Pkscript string
|
||||
Satpoint string
|
||||
Amount pgtype.Numeric
|
||||
ParentID pgtype.Text
|
||||
}
|
||||
|
||||
func (q *Queries) CreateEventMints(ctx context.Context, arg []CreateEventMintsParams) *CreateEventMintsBatchResults {
|
||||
batch := &pgx.Batch{}
|
||||
for _, a := range arg {
|
||||
vals := []interface{}{
|
||||
a.Id,
|
||||
a.InscriptionID,
|
||||
a.InscriptionNumber,
|
||||
a.Tick,
|
||||
a.OriginalTick,
|
||||
a.TxHash,
|
||||
a.BlockHeight,
|
||||
a.TxIndex,
|
||||
a.Timestamp,
|
||||
a.Pkscript,
|
||||
a.Satpoint,
|
||||
a.Amount,
|
||||
a.ParentID,
|
||||
}
|
||||
batch.Queue(createEventMints, vals...)
|
||||
}
|
||||
br := q.db.SendBatch(ctx, batch)
|
||||
return &CreateEventMintsBatchResults{br, len(arg), false}
|
||||
}
|
||||
|
||||
func (b *CreateEventMintsBatchResults) Exec(f func(int, error)) {
|
||||
defer b.br.Close()
|
||||
for t := 0; t < b.tot; t++ {
|
||||
if b.closed {
|
||||
if f != nil {
|
||||
f(t, ErrBatchAlreadyClosed)
|
||||
}
|
||||
continue
|
||||
}
|
||||
_, err := b.br.Exec()
|
||||
if f != nil {
|
||||
f(t, err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (b *CreateEventMintsBatchResults) Close() error {
|
||||
b.closed = true
|
||||
return b.br.Close()
|
||||
}
|
||||
|
||||
const createEventTransferTransfers = `-- name: CreateEventTransferTransfers :batchexec
|
||||
INSERT INTO "brc20_event_transfer_transfers" ("id", "inscription_id", "inscription_number", "tick", "original_tick", "tx_hash", "block_height", "tx_index", "timestamp", "from_pkscript", "from_satpoint", "from_input_index", "to_pkscript", "to_satpoint", "to_output_index", "spent_as_fee", "amount") VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13, $14, $15, $16, $17)
|
||||
`
|
||||
|
||||
type CreateEventTransferTransfersBatchResults struct {
|
||||
br pgx.BatchResults
|
||||
tot int
|
||||
closed bool
|
||||
}
|
||||
|
||||
type CreateEventTransferTransfersParams struct {
|
||||
Id int64
|
||||
InscriptionID string
|
||||
InscriptionNumber int64
|
||||
Tick string
|
||||
OriginalTick string
|
||||
TxHash string
|
||||
BlockHeight int32
|
||||
TxIndex int32
|
||||
Timestamp pgtype.Timestamp
|
||||
FromPkscript string
|
||||
FromSatpoint string
|
||||
FromInputIndex int32
|
||||
ToPkscript string
|
||||
ToSatpoint string
|
||||
ToOutputIndex int32
|
||||
SpentAsFee bool
|
||||
Amount pgtype.Numeric
|
||||
}
|
||||
|
||||
func (q *Queries) CreateEventTransferTransfers(ctx context.Context, arg []CreateEventTransferTransfersParams) *CreateEventTransferTransfersBatchResults {
|
||||
batch := &pgx.Batch{}
|
||||
for _, a := range arg {
|
||||
vals := []interface{}{
|
||||
a.Id,
|
||||
a.InscriptionID,
|
||||
a.InscriptionNumber,
|
||||
a.Tick,
|
||||
a.OriginalTick,
|
||||
a.TxHash,
|
||||
a.BlockHeight,
|
||||
a.TxIndex,
|
||||
a.Timestamp,
|
||||
a.FromPkscript,
|
||||
a.FromSatpoint,
|
||||
a.FromInputIndex,
|
||||
a.ToPkscript,
|
||||
a.ToSatpoint,
|
||||
a.ToOutputIndex,
|
||||
a.SpentAsFee,
|
||||
a.Amount,
|
||||
}
|
||||
batch.Queue(createEventTransferTransfers, vals...)
|
||||
}
|
||||
br := q.db.SendBatch(ctx, batch)
|
||||
return &CreateEventTransferTransfersBatchResults{br, len(arg), false}
|
||||
}
|
||||
|
||||
func (b *CreateEventTransferTransfersBatchResults) Exec(f func(int, error)) {
|
||||
defer b.br.Close()
|
||||
for t := 0; t < b.tot; t++ {
|
||||
if b.closed {
|
||||
if f != nil {
|
||||
f(t, ErrBatchAlreadyClosed)
|
||||
}
|
||||
continue
|
||||
}
|
||||
_, err := b.br.Exec()
|
||||
if f != nil {
|
||||
f(t, err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (b *CreateEventTransferTransfersBatchResults) Close() error {
|
||||
b.closed = true
|
||||
return b.br.Close()
|
||||
}
|
||||
|
||||
const createInscriptionEntries = `-- name: CreateInscriptionEntries :batchexec
|
||||
INSERT INTO "brc20_inscription_entries" ("id", "number", "sequence_number", "delegate", "metadata", "metaprotocol", "parents", "pointer", "content", "content_encoding", "content_type", "cursed", "cursed_for_brc20", "created_at", "created_at_height") VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13, $14, $15)
|
||||
`
|
||||
|
||||
type CreateInscriptionEntriesBatchResults struct {
|
||||
br pgx.BatchResults
|
||||
tot int
|
||||
closed bool
|
||||
}
|
||||
|
||||
type CreateInscriptionEntriesParams struct {
|
||||
Id string
|
||||
Number int64
|
||||
SequenceNumber int64
|
||||
Delegate pgtype.Text
|
||||
Metadata []byte
|
||||
Metaprotocol pgtype.Text
|
||||
Parents []string
|
||||
Pointer pgtype.Int8
|
||||
Content []byte
|
||||
ContentEncoding pgtype.Text
|
||||
ContentType pgtype.Text
|
||||
Cursed bool
|
||||
CursedForBrc20 bool
|
||||
CreatedAt pgtype.Timestamp
|
||||
CreatedAtHeight int32
|
||||
}
|
||||
|
||||
func (q *Queries) CreateInscriptionEntries(ctx context.Context, arg []CreateInscriptionEntriesParams) *CreateInscriptionEntriesBatchResults {
|
||||
batch := &pgx.Batch{}
|
||||
for _, a := range arg {
|
||||
vals := []interface{}{
|
||||
a.Id,
|
||||
a.Number,
|
||||
a.SequenceNumber,
|
||||
a.Delegate,
|
||||
a.Metadata,
|
||||
a.Metaprotocol,
|
||||
a.Parents,
|
||||
a.Pointer,
|
||||
a.Content,
|
||||
a.ContentEncoding,
|
||||
a.ContentType,
|
||||
a.Cursed,
|
||||
a.CursedForBrc20,
|
||||
a.CreatedAt,
|
||||
a.CreatedAtHeight,
|
||||
}
|
||||
batch.Queue(createInscriptionEntries, vals...)
|
||||
}
|
||||
br := q.db.SendBatch(ctx, batch)
|
||||
return &CreateInscriptionEntriesBatchResults{br, len(arg), false}
|
||||
}
|
||||
|
||||
func (b *CreateInscriptionEntriesBatchResults) Exec(f func(int, error)) {
|
||||
defer b.br.Close()
|
||||
for t := 0; t < b.tot; t++ {
|
||||
if b.closed {
|
||||
if f != nil {
|
||||
f(t, ErrBatchAlreadyClosed)
|
||||
}
|
||||
continue
|
||||
}
|
||||
_, err := b.br.Exec()
|
||||
if f != nil {
|
||||
f(t, err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (b *CreateInscriptionEntriesBatchResults) Close() error {
|
||||
b.closed = true
|
||||
return b.br.Close()
|
||||
}
|
||||
|
||||
const createInscriptionEntryStates = `-- name: CreateInscriptionEntryStates :batchexec
|
||||
INSERT INTO "brc20_inscription_entry_states" ("id", "block_height", "transfer_count") VALUES ($1, $2, $3)
|
||||
`
|
||||
|
||||
type CreateInscriptionEntryStatesBatchResults struct {
|
||||
br pgx.BatchResults
|
||||
tot int
|
||||
closed bool
|
||||
}
|
||||
|
||||
type CreateInscriptionEntryStatesParams struct {
|
||||
Id string
|
||||
BlockHeight int32
|
||||
TransferCount int32
|
||||
}
|
||||
|
||||
func (q *Queries) CreateInscriptionEntryStates(ctx context.Context, arg []CreateInscriptionEntryStatesParams) *CreateInscriptionEntryStatesBatchResults {
|
||||
batch := &pgx.Batch{}
|
||||
for _, a := range arg {
|
||||
vals := []interface{}{
|
||||
a.Id,
|
||||
a.BlockHeight,
|
||||
a.TransferCount,
|
||||
}
|
||||
batch.Queue(createInscriptionEntryStates, vals...)
|
||||
}
|
||||
br := q.db.SendBatch(ctx, batch)
|
||||
return &CreateInscriptionEntryStatesBatchResults{br, len(arg), false}
|
||||
}
|
||||
|
||||
func (b *CreateInscriptionEntryStatesBatchResults) Exec(f func(int, error)) {
|
||||
defer b.br.Close()
|
||||
for t := 0; t < b.tot; t++ {
|
||||
if b.closed {
|
||||
if f != nil {
|
||||
f(t, ErrBatchAlreadyClosed)
|
||||
}
|
||||
continue
|
||||
}
|
||||
_, err := b.br.Exec()
|
||||
if f != nil {
|
||||
f(t, err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (b *CreateInscriptionEntryStatesBatchResults) Close() error {
|
||||
b.closed = true
|
||||
return b.br.Close()
|
||||
}
|
||||
|
||||
const createInscriptionTransfers = `-- name: CreateInscriptionTransfers :batchexec
|
||||
INSERT INTO "brc20_inscription_transfers" ("inscription_id", "inscription_number", "inscription_sequence_number", "block_height", "tx_index", "tx_hash", "from_input_index", "old_satpoint_tx_hash", "old_satpoint_out_idx", "old_satpoint_offset", "new_satpoint_tx_hash", "new_satpoint_out_idx", "new_satpoint_offset", "new_pkscript", "new_output_value", "sent_as_fee", "transfer_count") VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13, $14, $15, $16, $17)
|
||||
`
|
||||
|
||||
type CreateInscriptionTransfersBatchResults struct {
|
||||
br pgx.BatchResults
|
||||
tot int
|
||||
closed bool
|
||||
}
|
||||
|
||||
type CreateInscriptionTransfersParams struct {
|
||||
InscriptionID string
|
||||
InscriptionNumber int64
|
||||
InscriptionSequenceNumber int64
|
||||
BlockHeight int32
|
||||
TxIndex int32
|
||||
TxHash string
|
||||
FromInputIndex int32
|
||||
OldSatpointTxHash pgtype.Text
|
||||
OldSatpointOutIdx pgtype.Int4
|
||||
OldSatpointOffset pgtype.Int8
|
||||
NewSatpointTxHash pgtype.Text
|
||||
NewSatpointOutIdx pgtype.Int4
|
||||
NewSatpointOffset pgtype.Int8
|
||||
NewPkscript string
|
||||
NewOutputValue int64
|
||||
SentAsFee bool
|
||||
TransferCount int32
|
||||
}
|
||||
|
||||
func (q *Queries) CreateInscriptionTransfers(ctx context.Context, arg []CreateInscriptionTransfersParams) *CreateInscriptionTransfersBatchResults {
|
||||
batch := &pgx.Batch{}
|
||||
for _, a := range arg {
|
||||
vals := []interface{}{
|
||||
a.InscriptionID,
|
||||
a.InscriptionNumber,
|
||||
a.InscriptionSequenceNumber,
|
||||
a.BlockHeight,
|
||||
a.TxIndex,
|
||||
a.TxHash,
|
||||
a.FromInputIndex,
|
||||
a.OldSatpointTxHash,
|
||||
a.OldSatpointOutIdx,
|
||||
a.OldSatpointOffset,
|
||||
a.NewSatpointTxHash,
|
||||
a.NewSatpointOutIdx,
|
||||
a.NewSatpointOffset,
|
||||
a.NewPkscript,
|
||||
a.NewOutputValue,
|
||||
a.SentAsFee,
|
||||
a.TransferCount,
|
||||
}
|
||||
batch.Queue(createInscriptionTransfers, vals...)
|
||||
}
|
||||
br := q.db.SendBatch(ctx, batch)
|
||||
return &CreateInscriptionTransfersBatchResults{br, len(arg), false}
|
||||
}
|
||||
|
||||
func (b *CreateInscriptionTransfersBatchResults) Exec(f func(int, error)) {
|
||||
defer b.br.Close()
|
||||
for t := 0; t < b.tot; t++ {
|
||||
if b.closed {
|
||||
if f != nil {
|
||||
f(t, ErrBatchAlreadyClosed)
|
||||
}
|
||||
continue
|
||||
}
|
||||
_, err := b.br.Exec()
|
||||
if f != nil {
|
||||
f(t, err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (b *CreateInscriptionTransfersBatchResults) Close() error {
|
||||
b.closed = true
|
||||
return b.br.Close()
|
||||
}
|
||||
|
||||
const createTickEntries = `-- name: CreateTickEntries :batchexec
|
||||
INSERT INTO "brc20_tick_entries" ("tick", "original_tick", "total_supply", "decimals", "limit_per_mint", "is_self_mint", "deploy_inscription_id", "deployed_at", "deployed_at_height") VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9)
|
||||
`
|
||||
|
||||
type CreateTickEntriesBatchResults struct {
|
||||
br pgx.BatchResults
|
||||
tot int
|
||||
closed bool
|
||||
}
|
||||
|
||||
type CreateTickEntriesParams struct {
|
||||
Tick string
|
||||
OriginalTick string
|
||||
TotalSupply pgtype.Numeric
|
||||
Decimals int16
|
||||
LimitPerMint pgtype.Numeric
|
||||
IsSelfMint bool
|
||||
DeployInscriptionID string
|
||||
DeployedAt pgtype.Timestamp
|
||||
DeployedAtHeight int32
|
||||
}
|
||||
|
||||
func (q *Queries) CreateTickEntries(ctx context.Context, arg []CreateTickEntriesParams) *CreateTickEntriesBatchResults {
|
||||
batch := &pgx.Batch{}
|
||||
for _, a := range arg {
|
||||
vals := []interface{}{
|
||||
a.Tick,
|
||||
a.OriginalTick,
|
||||
a.TotalSupply,
|
||||
a.Decimals,
|
||||
a.LimitPerMint,
|
||||
a.IsSelfMint,
|
||||
a.DeployInscriptionID,
|
||||
a.DeployedAt,
|
||||
a.DeployedAtHeight,
|
||||
}
|
||||
batch.Queue(createTickEntries, vals...)
|
||||
}
|
||||
br := q.db.SendBatch(ctx, batch)
|
||||
return &CreateTickEntriesBatchResults{br, len(arg), false}
|
||||
}
|
||||
|
||||
func (b *CreateTickEntriesBatchResults) Exec(f func(int, error)) {
|
||||
defer b.br.Close()
|
||||
for t := 0; t < b.tot; t++ {
|
||||
if b.closed {
|
||||
if f != nil {
|
||||
f(t, ErrBatchAlreadyClosed)
|
||||
}
|
||||
continue
|
||||
}
|
||||
_, err := b.br.Exec()
|
||||
if f != nil {
|
||||
f(t, err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (b *CreateTickEntriesBatchResults) Close() error {
|
||||
b.closed = true
|
||||
return b.br.Close()
|
||||
}
|
||||
|
||||
const createTickEntryStates = `-- name: CreateTickEntryStates :batchexec
|
||||
INSERT INTO "brc20_tick_entry_states" ("tick", "block_height", "minted_amount", "burned_amount", "completed_at", "completed_at_height") VALUES ($1, $2, $3, $4, $5, $6)
|
||||
`
|
||||
|
||||
type CreateTickEntryStatesBatchResults struct {
|
||||
br pgx.BatchResults
|
||||
tot int
|
||||
closed bool
|
||||
}
|
||||
|
||||
type CreateTickEntryStatesParams struct {
|
||||
Tick string
|
||||
BlockHeight int32
|
||||
MintedAmount pgtype.Numeric
|
||||
BurnedAmount pgtype.Numeric
|
||||
CompletedAt pgtype.Timestamp
|
||||
CompletedAtHeight pgtype.Int4
|
||||
}
|
||||
|
||||
func (q *Queries) CreateTickEntryStates(ctx context.Context, arg []CreateTickEntryStatesParams) *CreateTickEntryStatesBatchResults {
|
||||
batch := &pgx.Batch{}
|
||||
for _, a := range arg {
|
||||
vals := []interface{}{
|
||||
a.Tick,
|
||||
a.BlockHeight,
|
||||
a.MintedAmount,
|
||||
a.BurnedAmount,
|
||||
a.CompletedAt,
|
||||
a.CompletedAtHeight,
|
||||
}
|
||||
batch.Queue(createTickEntryStates, vals...)
|
||||
}
|
||||
br := q.db.SendBatch(ctx, batch)
|
||||
return &CreateTickEntryStatesBatchResults{br, len(arg), false}
|
||||
}
|
||||
|
||||
func (b *CreateTickEntryStatesBatchResults) Exec(f func(int, error)) {
|
||||
defer b.br.Close()
|
||||
for t := 0; t < b.tot; t++ {
|
||||
if b.closed {
|
||||
if f != nil {
|
||||
f(t, ErrBatchAlreadyClosed)
|
||||
}
|
||||
continue
|
||||
}
|
||||
_, err := b.br.Exec()
|
||||
if f != nil {
|
||||
f(t, err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (b *CreateTickEntryStatesBatchResults) Close() error {
|
||||
b.closed = true
|
||||
return b.br.Close()
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,49 +0,0 @@
|
||||
// Code generated by sqlc. DO NOT EDIT.
|
||||
// versions:
|
||||
// sqlc v1.26.0
|
||||
// source: info.sql
|
||||
|
||||
package gen
|
||||
|
||||
import (
|
||||
"context"
|
||||
)
|
||||
|
||||
const createIndexerState = `-- name: CreateIndexerState :exec
|
||||
INSERT INTO brc20_indexer_states (client_version, network, db_version, event_hash_version) VALUES ($1, $2, $3, $4)
|
||||
`
|
||||
|
||||
type CreateIndexerStateParams struct {
|
||||
ClientVersion string
|
||||
Network string
|
||||
DbVersion int32
|
||||
EventHashVersion int32
|
||||
}
|
||||
|
||||
func (q *Queries) CreateIndexerState(ctx context.Context, arg CreateIndexerStateParams) error {
|
||||
_, err := q.db.Exec(ctx, createIndexerState,
|
||||
arg.ClientVersion,
|
||||
arg.Network,
|
||||
arg.DbVersion,
|
||||
arg.EventHashVersion,
|
||||
)
|
||||
return err
|
||||
}
|
||||
|
||||
const getLatestIndexerState = `-- name: GetLatestIndexerState :one
|
||||
SELECT id, client_version, network, db_version, event_hash_version, created_at FROM brc20_indexer_states ORDER BY created_at DESC LIMIT 1
|
||||
`
|
||||
|
||||
func (q *Queries) GetLatestIndexerState(ctx context.Context) (Brc20IndexerState, error) {
|
||||
row := q.db.QueryRow(ctx, getLatestIndexerState)
|
||||
var i Brc20IndexerState
|
||||
err := row.Scan(
|
||||
&i.Id,
|
||||
&i.ClientVersion,
|
||||
&i.Network,
|
||||
&i.DbVersion,
|
||||
&i.EventHashVersion,
|
||||
&i.CreatedAt,
|
||||
)
|
||||
return i, err
|
||||
}
|
||||
@@ -1,176 +0,0 @@
|
||||
// Code generated by sqlc. DO NOT EDIT.
|
||||
// versions:
|
||||
// sqlc v1.26.0
|
||||
|
||||
package gen
|
||||
|
||||
import (
|
||||
"github.com/jackc/pgx/v5/pgtype"
|
||||
)
|
||||
|
||||
type Brc20Balance struct {
|
||||
Pkscript string
|
||||
BlockHeight int32
|
||||
Tick string
|
||||
OverallBalance pgtype.Numeric
|
||||
AvailableBalance pgtype.Numeric
|
||||
}
|
||||
|
||||
type Brc20EventDeploy struct {
|
||||
Id int64
|
||||
InscriptionID string
|
||||
InscriptionNumber int64
|
||||
Tick string
|
||||
OriginalTick string
|
||||
TxHash string
|
||||
BlockHeight int32
|
||||
TxIndex int32
|
||||
Timestamp pgtype.Timestamp
|
||||
Pkscript string
|
||||
Satpoint string
|
||||
TotalSupply pgtype.Numeric
|
||||
Decimals int16
|
||||
LimitPerMint pgtype.Numeric
|
||||
IsSelfMint bool
|
||||
}
|
||||
|
||||
type Brc20EventInscribeTransfer struct {
|
||||
Id int64
|
||||
InscriptionID string
|
||||
InscriptionNumber int64
|
||||
Tick string
|
||||
OriginalTick string
|
||||
TxHash string
|
||||
BlockHeight int32
|
||||
TxIndex int32
|
||||
Timestamp pgtype.Timestamp
|
||||
Pkscript string
|
||||
Satpoint string
|
||||
OutputIndex int32
|
||||
SatsAmount int64
|
||||
Amount pgtype.Numeric
|
||||
}
|
||||
|
||||
type Brc20EventMint struct {
|
||||
Id int64
|
||||
InscriptionID string
|
||||
InscriptionNumber int64
|
||||
Tick string
|
||||
OriginalTick string
|
||||
TxHash string
|
||||
BlockHeight int32
|
||||
TxIndex int32
|
||||
Timestamp pgtype.Timestamp
|
||||
Pkscript string
|
||||
Satpoint string
|
||||
Amount pgtype.Numeric
|
||||
ParentID pgtype.Text
|
||||
}
|
||||
|
||||
type Brc20EventTransferTransfer struct {
|
||||
Id int64
|
||||
InscriptionID string
|
||||
InscriptionNumber int64
|
||||
Tick string
|
||||
OriginalTick string
|
||||
TxHash string
|
||||
BlockHeight int32
|
||||
TxIndex int32
|
||||
Timestamp pgtype.Timestamp
|
||||
FromPkscript string
|
||||
FromSatpoint string
|
||||
FromInputIndex int32
|
||||
ToPkscript string
|
||||
ToSatpoint string
|
||||
ToOutputIndex int32
|
||||
SpentAsFee bool
|
||||
Amount pgtype.Numeric
|
||||
}
|
||||
|
||||
type Brc20IndexedBlock struct {
|
||||
Height int32
|
||||
Hash string
|
||||
EventHash string
|
||||
CumulativeEventHash string
|
||||
}
|
||||
|
||||
type Brc20IndexerState struct {
|
||||
Id int64
|
||||
ClientVersion string
|
||||
Network string
|
||||
DbVersion int32
|
||||
EventHashVersion int32
|
||||
CreatedAt pgtype.Timestamptz
|
||||
}
|
||||
|
||||
type Brc20InscriptionEntry struct {
|
||||
Id string
|
||||
Number int64
|
||||
SequenceNumber int64
|
||||
Delegate pgtype.Text
|
||||
Metadata []byte
|
||||
Metaprotocol pgtype.Text
|
||||
Parents []string
|
||||
Pointer pgtype.Int8
|
||||
Content []byte
|
||||
ContentEncoding pgtype.Text
|
||||
ContentType pgtype.Text
|
||||
Cursed bool
|
||||
CursedForBrc20 bool
|
||||
CreatedAt pgtype.Timestamp
|
||||
CreatedAtHeight int32
|
||||
}
|
||||
|
||||
type Brc20InscriptionEntryState struct {
|
||||
Id string
|
||||
BlockHeight int32
|
||||
TransferCount int32
|
||||
}
|
||||
|
||||
type Brc20InscriptionTransfer struct {
|
||||
InscriptionID string
|
||||
InscriptionNumber int64
|
||||
InscriptionSequenceNumber int64
|
||||
BlockHeight int32
|
||||
TxIndex int32
|
||||
TxHash string
|
||||
FromInputIndex int32
|
||||
OldSatpointTxHash pgtype.Text
|
||||
OldSatpointOutIdx pgtype.Int4
|
||||
OldSatpointOffset pgtype.Int8
|
||||
NewSatpointTxHash pgtype.Text
|
||||
NewSatpointOutIdx pgtype.Int4
|
||||
NewSatpointOffset pgtype.Int8
|
||||
NewPkscript string
|
||||
NewOutputValue int64
|
||||
SentAsFee bool
|
||||
TransferCount int32
|
||||
}
|
||||
|
||||
type Brc20ProcessorStat struct {
|
||||
BlockHeight int32
|
||||
CursedInscriptionCount int32
|
||||
BlessedInscriptionCount int32
|
||||
LostSats int64
|
||||
}
|
||||
|
||||
type Brc20TickEntry struct {
|
||||
Tick string
|
||||
OriginalTick string
|
||||
TotalSupply pgtype.Numeric
|
||||
Decimals int16
|
||||
LimitPerMint pgtype.Numeric
|
||||
IsSelfMint bool
|
||||
DeployInscriptionID string
|
||||
DeployedAt pgtype.Timestamp
|
||||
DeployedAtHeight int32
|
||||
}
|
||||
|
||||
type Brc20TickEntryState struct {
|
||||
Tick string
|
||||
BlockHeight int32
|
||||
MintedAmount pgtype.Numeric
|
||||
BurnedAmount pgtype.Numeric
|
||||
CompletedAt pgtype.Timestamp
|
||||
CompletedAtHeight pgtype.Int4
|
||||
}
|
||||
@@ -1,33 +0,0 @@
|
||||
package postgres
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/cockroachdb/errors"
|
||||
"github.com/gaze-network/indexer-network/common/errs"
|
||||
"github.com/gaze-network/indexer-network/modules/brc20/internal/datagateway"
|
||||
"github.com/gaze-network/indexer-network/modules/brc20/internal/entity"
|
||||
"github.com/jackc/pgx/v5"
|
||||
)
|
||||
|
||||
var _ datagateway.IndexerInfoDataGateway = (*Repository)(nil)
|
||||
|
||||
func (r *Repository) GetLatestIndexerState(ctx context.Context) (entity.IndexerState, error) {
|
||||
model, err := r.queries.GetLatestIndexerState(ctx)
|
||||
if err != nil {
|
||||
if errors.Is(err, pgx.ErrNoRows) {
|
||||
return entity.IndexerState{}, errors.WithStack(errs.NotFound)
|
||||
}
|
||||
return entity.IndexerState{}, errors.Wrap(err, "error during query")
|
||||
}
|
||||
state := mapIndexerStatesModelToType(model)
|
||||
return state, nil
|
||||
}
|
||||
|
||||
func (r *Repository) CreateIndexerState(ctx context.Context, state entity.IndexerState) error {
|
||||
params := mapIndexerStatesTypeToParams(state)
|
||||
if err := r.queries.CreateIndexerState(ctx, params); err != nil {
|
||||
return errors.Wrap(err, "error during exec")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
@@ -1,622 +0,0 @@
|
||||
package postgres
|
||||
|
||||
import (
|
||||
"encoding/hex"
|
||||
"time"
|
||||
|
||||
"github.com/btcsuite/btcd/chaincfg/chainhash"
|
||||
"github.com/btcsuite/btcd/wire"
|
||||
"github.com/cockroachdb/errors"
|
||||
"github.com/gaze-network/indexer-network/common"
|
||||
"github.com/gaze-network/indexer-network/modules/brc20/internal/entity"
|
||||
"github.com/gaze-network/indexer-network/modules/brc20/internal/ordinals"
|
||||
"github.com/gaze-network/indexer-network/modules/brc20/internal/repository/postgres/gen"
|
||||
"github.com/jackc/pgx/v5/pgtype"
|
||||
"github.com/samber/lo"
|
||||
"github.com/shopspring/decimal"
|
||||
)
|
||||
|
||||
func decimalFromNumeric(src pgtype.Numeric) decimal.NullDecimal {
|
||||
if !src.Valid || src.NaN || src.InfinityModifier != pgtype.Finite {
|
||||
return decimal.NullDecimal{}
|
||||
}
|
||||
result := decimal.NewFromBigInt(src.Int, src.Exp)
|
||||
return decimal.NewNullDecimal(result)
|
||||
}
|
||||
|
||||
func numericFromDecimal(src decimal.Decimal) pgtype.Numeric {
|
||||
result := pgtype.Numeric{
|
||||
Int: src.Coefficient(),
|
||||
Exp: src.Exponent(),
|
||||
NaN: false,
|
||||
InfinityModifier: pgtype.Finite,
|
||||
Valid: true,
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
func numericFromNullDecimal(src decimal.NullDecimal) pgtype.Numeric {
|
||||
if !src.Valid {
|
||||
return pgtype.Numeric{}
|
||||
}
|
||||
return numericFromDecimal(src.Decimal)
|
||||
}
|
||||
|
||||
func mapIndexerStatesModelToType(src gen.Brc20IndexerState) entity.IndexerState {
|
||||
var createdAt time.Time
|
||||
if src.CreatedAt.Valid {
|
||||
createdAt = src.CreatedAt.Time
|
||||
}
|
||||
return entity.IndexerState{
|
||||
ClientVersion: src.ClientVersion,
|
||||
Network: common.Network(src.Network),
|
||||
DBVersion: int32(src.DbVersion),
|
||||
EventHashVersion: int32(src.EventHashVersion),
|
||||
CreatedAt: createdAt,
|
||||
}
|
||||
}
|
||||
|
||||
func mapIndexerStatesTypeToParams(src entity.IndexerState) gen.CreateIndexerStateParams {
|
||||
return gen.CreateIndexerStateParams{
|
||||
ClientVersion: src.ClientVersion,
|
||||
Network: string(src.Network),
|
||||
DbVersion: int32(src.DBVersion),
|
||||
EventHashVersion: int32(src.EventHashVersion),
|
||||
}
|
||||
}
|
||||
|
||||
func mapIndexedBlockModelToType(src gen.Brc20IndexedBlock) (entity.IndexedBlock, error) {
|
||||
hash, err := chainhash.NewHashFromStr(src.Hash)
|
||||
if err != nil {
|
||||
return entity.IndexedBlock{}, errors.Wrap(err, "invalid block hash")
|
||||
}
|
||||
eventHash, err := hex.DecodeString(src.EventHash)
|
||||
if err != nil {
|
||||
return entity.IndexedBlock{}, errors.Wrap(err, "invalid event hash")
|
||||
}
|
||||
cumulativeEventHash, err := hex.DecodeString(src.CumulativeEventHash)
|
||||
if err != nil {
|
||||
return entity.IndexedBlock{}, errors.Wrap(err, "invalid cumulative event hash")
|
||||
}
|
||||
return entity.IndexedBlock{
|
||||
Height: uint64(src.Height),
|
||||
Hash: *hash,
|
||||
EventHash: eventHash,
|
||||
CumulativeEventHash: cumulativeEventHash,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func mapIndexedBlockTypeToParams(src entity.IndexedBlock) gen.CreateIndexedBlockParams {
|
||||
return gen.CreateIndexedBlockParams{
|
||||
Height: int32(src.Height),
|
||||
Hash: src.Hash.String(),
|
||||
EventHash: hex.EncodeToString(src.EventHash),
|
||||
CumulativeEventHash: hex.EncodeToString(src.CumulativeEventHash),
|
||||
}
|
||||
}
|
||||
|
||||
func mapProcessorStatsModelToType(src gen.Brc20ProcessorStat) entity.ProcessorStats {
|
||||
return entity.ProcessorStats{
|
||||
BlockHeight: uint64(src.BlockHeight),
|
||||
CursedInscriptionCount: uint64(src.CursedInscriptionCount),
|
||||
BlessedInscriptionCount: uint64(src.BlessedInscriptionCount),
|
||||
LostSats: uint64(src.LostSats),
|
||||
}
|
||||
}
|
||||
|
||||
func mapProcessorStatsTypeToParams(src entity.ProcessorStats) gen.CreateProcessorStatsParams {
|
||||
return gen.CreateProcessorStatsParams{
|
||||
BlockHeight: int32(src.BlockHeight),
|
||||
CursedInscriptionCount: int32(src.CursedInscriptionCount),
|
||||
BlessedInscriptionCount: int32(src.BlessedInscriptionCount),
|
||||
LostSats: int64(src.LostSats),
|
||||
}
|
||||
}
|
||||
|
||||
func mapTickEntryModelToType(src gen.GetTickEntriesByTicksRow) (entity.TickEntry, error) {
|
||||
deployInscriptionId, err := ordinals.NewInscriptionIdFromString(src.DeployInscriptionID)
|
||||
if err != nil {
|
||||
return entity.TickEntry{}, errors.Wrap(err, "invalid deployInscriptionId")
|
||||
}
|
||||
var completedAt time.Time
|
||||
if src.CompletedAt.Valid {
|
||||
completedAt = src.CompletedAt.Time
|
||||
}
|
||||
return entity.TickEntry{
|
||||
Tick: src.Tick,
|
||||
OriginalTick: src.OriginalTick,
|
||||
TotalSupply: decimalFromNumeric(src.TotalSupply).Decimal,
|
||||
Decimals: uint16(src.Decimals),
|
||||
LimitPerMint: decimalFromNumeric(src.LimitPerMint).Decimal,
|
||||
IsSelfMint: src.IsSelfMint,
|
||||
DeployInscriptionId: deployInscriptionId,
|
||||
DeployedAt: src.DeployedAt.Time,
|
||||
DeployedAtHeight: uint64(src.DeployedAtHeight),
|
||||
MintedAmount: decimalFromNumeric(src.MintedAmount).Decimal,
|
||||
BurnedAmount: decimalFromNumeric(src.BurnedAmount).Decimal,
|
||||
CompletedAt: completedAt,
|
||||
CompletedAtHeight: lo.Ternary(src.CompletedAtHeight.Valid, uint64(src.CompletedAtHeight.Int32), 0),
|
||||
}, nil
|
||||
}
|
||||
|
||||
func mapTickEntryTypeToParams(src entity.TickEntry, blockHeight uint64) (gen.CreateTickEntriesParams, gen.CreateTickEntryStatesParams, error) {
|
||||
return gen.CreateTickEntriesParams{
|
||||
Tick: src.Tick,
|
||||
OriginalTick: src.OriginalTick,
|
||||
TotalSupply: numericFromDecimal(src.TotalSupply),
|
||||
Decimals: int16(src.Decimals),
|
||||
LimitPerMint: numericFromDecimal(src.LimitPerMint),
|
||||
IsSelfMint: src.IsSelfMint,
|
||||
DeployInscriptionID: src.DeployInscriptionId.String(),
|
||||
DeployedAt: pgtype.Timestamp{Time: src.DeployedAt, Valid: true},
|
||||
DeployedAtHeight: int32(src.DeployedAtHeight),
|
||||
}, gen.CreateTickEntryStatesParams{
|
||||
Tick: src.Tick,
|
||||
BlockHeight: int32(blockHeight),
|
||||
CompletedAt: pgtype.Timestamp{Time: src.CompletedAt, Valid: !src.CompletedAt.IsZero()},
|
||||
CompletedAtHeight: pgtype.Int4{Int32: int32(src.CompletedAtHeight), Valid: src.CompletedAtHeight != 0},
|
||||
MintedAmount: numericFromDecimal(src.MintedAmount),
|
||||
BurnedAmount: numericFromDecimal(src.BurnedAmount),
|
||||
}, nil
|
||||
}
|
||||
|
||||
func mapInscriptionEntryModelToType(src gen.GetInscriptionEntriesByIdsRow) (ordinals.InscriptionEntry, error) {
|
||||
inscriptionId, err := ordinals.NewInscriptionIdFromString(src.Id)
|
||||
if err != nil {
|
||||
return ordinals.InscriptionEntry{}, errors.Wrap(err, "invalid inscription id")
|
||||
}
|
||||
|
||||
var delegate, parent *ordinals.InscriptionId
|
||||
if src.Delegate.Valid {
|
||||
delegateValue, err := ordinals.NewInscriptionIdFromString(src.Delegate.String)
|
||||
if err != nil {
|
||||
return ordinals.InscriptionEntry{}, errors.Wrap(err, "invalid delegate id")
|
||||
}
|
||||
delegate = &delegateValue
|
||||
}
|
||||
// ord 0.14.0 supports only one parent
|
||||
if len(src.Parents) > 0 {
|
||||
parentValue, err := ordinals.NewInscriptionIdFromString(src.Parents[0])
|
||||
if err != nil {
|
||||
return ordinals.InscriptionEntry{}, errors.Wrap(err, "invalid parent id")
|
||||
}
|
||||
parent = &parentValue
|
||||
}
|
||||
|
||||
inscription := ordinals.Inscription{
|
||||
Content: src.Content,
|
||||
ContentEncoding: lo.Ternary(src.ContentEncoding.Valid, src.ContentEncoding.String, ""),
|
||||
ContentType: lo.Ternary(src.ContentType.Valid, src.ContentType.String, ""),
|
||||
Delegate: delegate,
|
||||
Metadata: src.Metadata,
|
||||
Metaprotocol: lo.Ternary(src.Metaprotocol.Valid, src.Metaprotocol.String, ""),
|
||||
Parent: parent,
|
||||
Pointer: lo.Ternary(src.Pointer.Valid, lo.ToPtr(uint64(src.Pointer.Int64)), nil),
|
||||
}
|
||||
|
||||
return ordinals.InscriptionEntry{
|
||||
Id: inscriptionId,
|
||||
Number: src.Number,
|
||||
SequenceNumber: uint64(src.SequenceNumber),
|
||||
Cursed: src.Cursed,
|
||||
CursedForBRC20: src.CursedForBrc20,
|
||||
CreatedAt: lo.Ternary(src.CreatedAt.Valid, src.CreatedAt.Time, time.Time{}),
|
||||
CreatedAtHeight: uint64(src.CreatedAtHeight),
|
||||
Inscription: inscription,
|
||||
TransferCount: lo.Ternary(src.TransferCount.Valid, uint32(src.TransferCount.Int32), 0),
|
||||
}, nil
|
||||
}
|
||||
|
||||
func mapInscriptionEntryTypeToParams(src ordinals.InscriptionEntry, blockHeight uint64) (gen.CreateInscriptionEntriesParams, gen.CreateInscriptionEntryStatesParams, error) {
|
||||
var delegate, metaprotocol, contentEncoding, contentType pgtype.Text
|
||||
if src.Inscription.Delegate != nil {
|
||||
delegate = pgtype.Text{String: src.Inscription.Delegate.String(), Valid: true}
|
||||
}
|
||||
if src.Inscription.Metaprotocol != "" {
|
||||
metaprotocol = pgtype.Text{String: src.Inscription.Metaprotocol, Valid: true}
|
||||
}
|
||||
if src.Inscription.ContentEncoding != "" {
|
||||
contentEncoding = pgtype.Text{String: src.Inscription.ContentEncoding, Valid: true}
|
||||
}
|
||||
if src.Inscription.ContentType != "" {
|
||||
contentType = pgtype.Text{String: src.Inscription.ContentType, Valid: true}
|
||||
}
|
||||
var parents []string
|
||||
if src.Inscription.Parent != nil {
|
||||
parents = append(parents, src.Inscription.Parent.String())
|
||||
}
|
||||
var pointer pgtype.Int8
|
||||
if src.Inscription.Pointer != nil {
|
||||
pointer = pgtype.Int8{Int64: int64(*src.Inscription.Pointer), Valid: true}
|
||||
}
|
||||
return gen.CreateInscriptionEntriesParams{
|
||||
Id: src.Id.String(),
|
||||
Number: src.Number,
|
||||
SequenceNumber: int64(src.SequenceNumber),
|
||||
Delegate: delegate,
|
||||
Metadata: src.Inscription.Metadata,
|
||||
Metaprotocol: metaprotocol,
|
||||
Parents: parents,
|
||||
Pointer: pointer,
|
||||
Content: src.Inscription.Content,
|
||||
ContentEncoding: contentEncoding,
|
||||
ContentType: contentType,
|
||||
Cursed: src.Cursed,
|
||||
CursedForBrc20: src.CursedForBRC20,
|
||||
CreatedAt: lo.Ternary(!src.CreatedAt.IsZero(), pgtype.Timestamp{Time: src.CreatedAt, Valid: true}, pgtype.Timestamp{}),
|
||||
CreatedAtHeight: int32(src.CreatedAtHeight),
|
||||
}, gen.CreateInscriptionEntryStatesParams{
|
||||
Id: src.Id.String(),
|
||||
BlockHeight: int32(blockHeight),
|
||||
TransferCount: int32(src.TransferCount),
|
||||
}, nil
|
||||
}
|
||||
|
||||
func mapInscriptionTransferModelToType(src gen.GetInscriptionTransfersInOutPointsRow) (entity.InscriptionTransfer, error) {
|
||||
inscriptionId, err := ordinals.NewInscriptionIdFromString(src.InscriptionID)
|
||||
if err != nil {
|
||||
return entity.InscriptionTransfer{}, errors.Wrap(err, "invalid inscription id")
|
||||
}
|
||||
txHash, err := chainhash.NewHashFromStr(src.TxHash)
|
||||
if err != nil {
|
||||
return entity.InscriptionTransfer{}, errors.Wrap(err, "invalid tx hash")
|
||||
}
|
||||
var oldSatPoint, newSatPoint ordinals.SatPoint
|
||||
if src.OldSatpointTxHash.Valid {
|
||||
if !src.OldSatpointOutIdx.Valid || !src.OldSatpointOffset.Valid {
|
||||
return entity.InscriptionTransfer{}, errors.New("old satpoint out idx and offset must exist if hash exists")
|
||||
}
|
||||
txHash, err := chainhash.NewHashFromStr(src.OldSatpointTxHash.String)
|
||||
if err != nil {
|
||||
return entity.InscriptionTransfer{}, errors.Wrap(err, "invalid old satpoint tx hash")
|
||||
}
|
||||
oldSatPoint = ordinals.SatPoint{
|
||||
OutPoint: wire.OutPoint{
|
||||
Hash: *txHash,
|
||||
Index: uint32(src.OldSatpointOutIdx.Int32),
|
||||
},
|
||||
Offset: uint64(src.OldSatpointOffset.Int64),
|
||||
}
|
||||
}
|
||||
if src.NewSatpointTxHash.Valid {
|
||||
if !src.NewSatpointOutIdx.Valid || !src.NewSatpointOffset.Valid {
|
||||
return entity.InscriptionTransfer{}, errors.New("new satpoint out idx and offset must exist if hash exists")
|
||||
}
|
||||
txHash, err := chainhash.NewHashFromStr(src.NewSatpointTxHash.String)
|
||||
if err != nil {
|
||||
return entity.InscriptionTransfer{}, errors.Wrap(err, "invalid new satpoint tx hash")
|
||||
}
|
||||
newSatPoint = ordinals.SatPoint{
|
||||
OutPoint: wire.OutPoint{
|
||||
Hash: *txHash,
|
||||
Index: uint32(src.NewSatpointOutIdx.Int32),
|
||||
},
|
||||
Offset: uint64(src.NewSatpointOffset.Int64),
|
||||
}
|
||||
}
|
||||
newPkScript, err := hex.DecodeString(src.NewPkscript)
|
||||
if err != nil {
|
||||
return entity.InscriptionTransfer{}, errors.Wrap(err, "failed to parse pkscript")
|
||||
}
|
||||
|
||||
return entity.InscriptionTransfer{
|
||||
InscriptionId: inscriptionId,
|
||||
InscriptionNumber: src.InscriptionNumber,
|
||||
InscriptionSequenceNumber: uint64(src.InscriptionSequenceNumber),
|
||||
BlockHeight: uint64(src.BlockHeight),
|
||||
TxIndex: uint32(src.TxIndex),
|
||||
TxHash: *txHash,
|
||||
FromInputIndex: uint32(src.FromInputIndex),
|
||||
Content: src.Content,
|
||||
OldSatPoint: oldSatPoint,
|
||||
NewSatPoint: newSatPoint,
|
||||
NewPkScript: newPkScript,
|
||||
NewOutputValue: uint64(src.NewOutputValue),
|
||||
SentAsFee: src.SentAsFee,
|
||||
TransferCount: uint32(src.TransferCount),
|
||||
}, nil
|
||||
}
|
||||
|
||||
func mapInscriptionTransferTypeToParams(src entity.InscriptionTransfer) gen.CreateInscriptionTransfersParams {
|
||||
return gen.CreateInscriptionTransfersParams{
|
||||
InscriptionID: src.InscriptionId.String(),
|
||||
InscriptionNumber: src.InscriptionNumber,
|
||||
InscriptionSequenceNumber: int64(src.InscriptionSequenceNumber),
|
||||
BlockHeight: int32(src.BlockHeight),
|
||||
TxIndex: int32(src.TxIndex),
|
||||
TxHash: src.TxHash.String(),
|
||||
FromInputIndex: int32(src.FromInputIndex),
|
||||
OldSatpointTxHash: lo.Ternary(src.OldSatPoint != ordinals.SatPoint{}, pgtype.Text{String: src.OldSatPoint.OutPoint.Hash.String(), Valid: true}, pgtype.Text{}),
|
||||
OldSatpointOutIdx: lo.Ternary(src.OldSatPoint != ordinals.SatPoint{}, pgtype.Int4{Int32: int32(src.OldSatPoint.OutPoint.Index), Valid: true}, pgtype.Int4{}),
|
||||
OldSatpointOffset: lo.Ternary(src.OldSatPoint != ordinals.SatPoint{}, pgtype.Int8{Int64: int64(src.OldSatPoint.Offset), Valid: true}, pgtype.Int8{}),
|
||||
NewSatpointTxHash: lo.Ternary(src.NewSatPoint != ordinals.SatPoint{}, pgtype.Text{String: src.NewSatPoint.OutPoint.Hash.String(), Valid: true}, pgtype.Text{}),
|
||||
NewSatpointOutIdx: lo.Ternary(src.NewSatPoint != ordinals.SatPoint{}, pgtype.Int4{Int32: int32(src.NewSatPoint.OutPoint.Index), Valid: true}, pgtype.Int4{}),
|
||||
NewSatpointOffset: lo.Ternary(src.NewSatPoint != ordinals.SatPoint{}, pgtype.Int8{Int64: int64(src.NewSatPoint.Offset), Valid: true}, pgtype.Int8{}),
|
||||
NewPkscript: hex.EncodeToString(src.NewPkScript),
|
||||
NewOutputValue: int64(src.NewOutputValue),
|
||||
SentAsFee: src.SentAsFee,
|
||||
TransferCount: int32(src.TransferCount),
|
||||
}
|
||||
}
|
||||
|
||||
func mapEventDeployModelToType(src gen.Brc20EventDeploy) (entity.EventDeploy, error) {
|
||||
inscriptionId, err := ordinals.NewInscriptionIdFromString(src.InscriptionID)
|
||||
if err != nil {
|
||||
return entity.EventDeploy{}, errors.Wrap(err, "invalid inscription id")
|
||||
}
|
||||
txHash, err := chainhash.NewHashFromStr(src.TxHash)
|
||||
if err != nil {
|
||||
return entity.EventDeploy{}, errors.Wrap(err, "invalid tx hash")
|
||||
}
|
||||
pkScript, err := hex.DecodeString(src.Pkscript)
|
||||
if err != nil {
|
||||
return entity.EventDeploy{}, errors.Wrap(err, "failed to parse pkscript")
|
||||
}
|
||||
satPoint, err := ordinals.NewSatPointFromString(src.Satpoint)
|
||||
if err != nil {
|
||||
return entity.EventDeploy{}, errors.Wrap(err, "cannot parse satpoint")
|
||||
}
|
||||
return entity.EventDeploy{
|
||||
Id: src.Id,
|
||||
InscriptionId: inscriptionId,
|
||||
InscriptionNumber: src.InscriptionNumber,
|
||||
Tick: src.Tick,
|
||||
OriginalTick: src.OriginalTick,
|
||||
TxHash: *txHash,
|
||||
BlockHeight: uint64(src.BlockHeight),
|
||||
TxIndex: uint32(src.TxIndex),
|
||||
Timestamp: src.Timestamp.Time,
|
||||
PkScript: pkScript,
|
||||
SatPoint: satPoint,
|
||||
TotalSupply: decimalFromNumeric(src.TotalSupply).Decimal,
|
||||
Decimals: uint16(src.Decimals),
|
||||
LimitPerMint: decimalFromNumeric(src.LimitPerMint).Decimal,
|
||||
IsSelfMint: src.IsSelfMint,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func mapEventDeployTypeToParams(src entity.EventDeploy) (gen.CreateEventDeploysParams, error) {
|
||||
var timestamp pgtype.Timestamp
|
||||
if !src.Timestamp.IsZero() {
|
||||
timestamp = pgtype.Timestamp{Time: src.Timestamp, Valid: true}
|
||||
}
|
||||
return gen.CreateEventDeploysParams{
|
||||
Id: src.Id,
|
||||
InscriptionID: src.InscriptionId.String(),
|
||||
InscriptionNumber: src.InscriptionNumber,
|
||||
Tick: src.Tick,
|
||||
OriginalTick: src.OriginalTick,
|
||||
TxHash: src.TxHash.String(),
|
||||
BlockHeight: int32(src.BlockHeight),
|
||||
TxIndex: int32(src.TxIndex),
|
||||
Timestamp: timestamp,
|
||||
Pkscript: hex.EncodeToString(src.PkScript),
|
||||
Satpoint: src.SatPoint.String(),
|
||||
TotalSupply: numericFromDecimal(src.TotalSupply),
|
||||
Decimals: int16(src.Decimals),
|
||||
LimitPerMint: numericFromDecimal(src.LimitPerMint),
|
||||
IsSelfMint: src.IsSelfMint,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func mapEventMintModelToType(src gen.Brc20EventMint) (entity.EventMint, error) {
|
||||
inscriptionId, err := ordinals.NewInscriptionIdFromString(src.InscriptionID)
|
||||
if err != nil {
|
||||
return entity.EventMint{}, errors.Wrap(err, "invalid inscription id")
|
||||
}
|
||||
txHash, err := chainhash.NewHashFromStr(src.TxHash)
|
||||
if err != nil {
|
||||
return entity.EventMint{}, errors.Wrap(err, "invalid tx hash")
|
||||
}
|
||||
pkScript, err := hex.DecodeString(src.Pkscript)
|
||||
if err != nil {
|
||||
return entity.EventMint{}, errors.Wrap(err, "failed to parse pkscript")
|
||||
}
|
||||
satPoint, err := ordinals.NewSatPointFromString(src.Satpoint)
|
||||
if err != nil {
|
||||
return entity.EventMint{}, errors.Wrap(err, "cannot parse satpoint")
|
||||
}
|
||||
var parentId *ordinals.InscriptionId
|
||||
if src.ParentID.Valid {
|
||||
parentIdValue, err := ordinals.NewInscriptionIdFromString(src.ParentID.String)
|
||||
if err != nil {
|
||||
return entity.EventMint{}, errors.Wrap(err, "invalid parent id")
|
||||
}
|
||||
parentId = &parentIdValue
|
||||
}
|
||||
return entity.EventMint{
|
||||
Id: src.Id,
|
||||
InscriptionId: inscriptionId,
|
||||
InscriptionNumber: src.InscriptionNumber,
|
||||
Tick: src.Tick,
|
||||
OriginalTick: src.OriginalTick,
|
||||
TxHash: *txHash,
|
||||
BlockHeight: uint64(src.BlockHeight),
|
||||
TxIndex: uint32(src.TxIndex),
|
||||
Timestamp: src.Timestamp.Time,
|
||||
PkScript: pkScript,
|
||||
SatPoint: satPoint,
|
||||
Amount: decimalFromNumeric(src.Amount).Decimal,
|
||||
ParentId: parentId,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func mapEventMintTypeToParams(src entity.EventMint) (gen.CreateEventMintsParams, error) {
|
||||
var timestamp pgtype.Timestamp
|
||||
if !src.Timestamp.IsZero() {
|
||||
timestamp = pgtype.Timestamp{Time: src.Timestamp, Valid: true}
|
||||
}
|
||||
var parentId pgtype.Text
|
||||
if src.ParentId != nil {
|
||||
parentId = pgtype.Text{String: src.ParentId.String(), Valid: true}
|
||||
}
|
||||
return gen.CreateEventMintsParams{
|
||||
Id: src.Id,
|
||||
InscriptionID: src.InscriptionId.String(),
|
||||
InscriptionNumber: src.InscriptionNumber,
|
||||
Tick: src.Tick,
|
||||
OriginalTick: src.OriginalTick,
|
||||
TxHash: src.TxHash.String(),
|
||||
BlockHeight: int32(src.BlockHeight),
|
||||
TxIndex: int32(src.TxIndex),
|
||||
Timestamp: timestamp,
|
||||
Pkscript: hex.EncodeToString(src.PkScript),
|
||||
Satpoint: src.SatPoint.String(),
|
||||
Amount: numericFromDecimal(src.Amount),
|
||||
ParentID: parentId,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func mapEventInscribeTransferModelToType(src gen.Brc20EventInscribeTransfer) (entity.EventInscribeTransfer, error) {
|
||||
inscriptionId, err := ordinals.NewInscriptionIdFromString(src.InscriptionID)
|
||||
if err != nil {
|
||||
return entity.EventInscribeTransfer{}, errors.Wrap(err, "cannot parse inscription id")
|
||||
}
|
||||
txHash, err := chainhash.NewHashFromStr(src.TxHash)
|
||||
if err != nil {
|
||||
return entity.EventInscribeTransfer{}, errors.Wrap(err, "cannot parse hash")
|
||||
}
|
||||
pkScript, err := hex.DecodeString(src.Pkscript)
|
||||
if err != nil {
|
||||
return entity.EventInscribeTransfer{}, errors.Wrap(err, "cannot parse pkScript")
|
||||
}
|
||||
satPoint, err := ordinals.NewSatPointFromString(src.Satpoint)
|
||||
if err != nil {
|
||||
return entity.EventInscribeTransfer{}, errors.Wrap(err, "cannot parse satPoint")
|
||||
}
|
||||
return entity.EventInscribeTransfer{
|
||||
Id: src.Id,
|
||||
InscriptionId: inscriptionId,
|
||||
InscriptionNumber: src.InscriptionNumber,
|
||||
Tick: src.Tick,
|
||||
OriginalTick: src.OriginalTick,
|
||||
TxHash: *txHash,
|
||||
BlockHeight: uint64(src.BlockHeight),
|
||||
TxIndex: uint32(src.TxIndex),
|
||||
Timestamp: src.Timestamp.Time,
|
||||
PkScript: pkScript,
|
||||
SatPoint: satPoint,
|
||||
OutputIndex: uint32(src.OutputIndex),
|
||||
SatsAmount: uint64(src.SatsAmount),
|
||||
Amount: decimalFromNumeric(src.Amount).Decimal,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func mapEventInscribeTransferTypeToParams(src entity.EventInscribeTransfer) (gen.CreateEventInscribeTransfersParams, error) {
|
||||
var timestamp pgtype.Timestamp
|
||||
if !src.Timestamp.IsZero() {
|
||||
timestamp = pgtype.Timestamp{Time: src.Timestamp, Valid: true}
|
||||
}
|
||||
return gen.CreateEventInscribeTransfersParams{
|
||||
Id: src.Id,
|
||||
InscriptionID: src.InscriptionId.String(),
|
||||
InscriptionNumber: src.InscriptionNumber,
|
||||
Tick: src.Tick,
|
||||
OriginalTick: src.OriginalTick,
|
||||
TxHash: src.TxHash.String(),
|
||||
BlockHeight: int32(src.BlockHeight),
|
||||
TxIndex: int32(src.TxIndex),
|
||||
Timestamp: timestamp,
|
||||
Pkscript: hex.EncodeToString(src.PkScript),
|
||||
Satpoint: src.SatPoint.String(),
|
||||
OutputIndex: int32(src.OutputIndex),
|
||||
SatsAmount: int64(src.SatsAmount),
|
||||
Amount: numericFromDecimal(src.Amount),
|
||||
}, nil
|
||||
}
|
||||
|
||||
func mapEventTransferTransferModelToType(src gen.Brc20EventTransferTransfer) (entity.EventTransferTransfer, error) {
|
||||
inscriptionId, err := ordinals.NewInscriptionIdFromString(src.InscriptionID)
|
||||
if err != nil {
|
||||
return entity.EventTransferTransfer{}, errors.Wrap(err, "cannot parse inscription id")
|
||||
}
|
||||
txHash, err := chainhash.NewHashFromStr(src.TxHash)
|
||||
if err != nil {
|
||||
return entity.EventTransferTransfer{}, errors.Wrap(err, "cannot parse hash")
|
||||
}
|
||||
fromPkScript, err := hex.DecodeString(src.FromPkscript)
|
||||
if err != nil {
|
||||
return entity.EventTransferTransfer{}, errors.Wrap(err, "cannot parse fromPkScript")
|
||||
}
|
||||
fromSatPoint, err := ordinals.NewSatPointFromString(src.FromSatpoint)
|
||||
if err != nil {
|
||||
return entity.EventTransferTransfer{}, errors.Wrap(err, "cannot parse fromSatPoint")
|
||||
}
|
||||
toPkScript, err := hex.DecodeString(src.ToPkscript)
|
||||
if err != nil {
|
||||
return entity.EventTransferTransfer{}, errors.Wrap(err, "cannot parse toPkScript")
|
||||
}
|
||||
toSatPoint, err := ordinals.NewSatPointFromString(src.ToSatpoint)
|
||||
if err != nil {
|
||||
return entity.EventTransferTransfer{}, errors.Wrap(err, "cannot parse toSatPoint")
|
||||
}
|
||||
return entity.EventTransferTransfer{
|
||||
Id: src.Id,
|
||||
InscriptionId: inscriptionId,
|
||||
InscriptionNumber: src.InscriptionNumber,
|
||||
Tick: src.Tick,
|
||||
OriginalTick: src.OriginalTick,
|
||||
TxHash: *txHash,
|
||||
BlockHeight: uint64(src.BlockHeight),
|
||||
TxIndex: uint32(src.TxIndex),
|
||||
Timestamp: src.Timestamp.Time,
|
||||
FromPkScript: fromPkScript,
|
||||
FromSatPoint: fromSatPoint,
|
||||
FromInputIndex: uint32(src.FromInputIndex),
|
||||
ToPkScript: toPkScript,
|
||||
ToSatPoint: toSatPoint,
|
||||
ToOutputIndex: uint32(src.ToOutputIndex),
|
||||
SpentAsFee: src.SpentAsFee,
|
||||
Amount: decimalFromNumeric(src.Amount).Decimal,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func mapEventTransferTransferTypeToParams(src entity.EventTransferTransfer) (gen.CreateEventTransferTransfersParams, error) {
|
||||
var timestamp pgtype.Timestamp
|
||||
if !src.Timestamp.IsZero() {
|
||||
timestamp = pgtype.Timestamp{Time: src.Timestamp, Valid: true}
|
||||
}
|
||||
return gen.CreateEventTransferTransfersParams{
|
||||
Id: src.Id,
|
||||
InscriptionID: src.InscriptionId.String(),
|
||||
InscriptionNumber: src.InscriptionNumber,
|
||||
Tick: src.Tick,
|
||||
OriginalTick: src.OriginalTick,
|
||||
TxHash: src.TxHash.String(),
|
||||
BlockHeight: int32(src.BlockHeight),
|
||||
TxIndex: int32(src.TxIndex),
|
||||
Timestamp: timestamp,
|
||||
FromPkscript: hex.EncodeToString(src.FromPkScript),
|
||||
FromSatpoint: src.FromSatPoint.String(),
|
||||
FromInputIndex: int32(src.FromInputIndex),
|
||||
ToPkscript: hex.EncodeToString(src.ToPkScript),
|
||||
ToSatpoint: src.ToSatPoint.String(),
|
||||
ToOutputIndex: int32(src.ToOutputIndex),
|
||||
SpentAsFee: src.SpentAsFee,
|
||||
Amount: numericFromDecimal(src.Amount),
|
||||
}, nil
|
||||
}
|
||||
|
||||
func mapBalanceModelToType(src gen.Brc20Balance) (entity.Balance, error) {
|
||||
pkScript, err := hex.DecodeString(src.Pkscript)
|
||||
if err != nil {
|
||||
return entity.Balance{}, errors.Wrap(err, "failed to parse pkscript")
|
||||
}
|
||||
return entity.Balance{
|
||||
PkScript: pkScript,
|
||||
Tick: src.Tick,
|
||||
BlockHeight: uint64(src.BlockHeight),
|
||||
OverallBalance: decimalFromNumeric(src.OverallBalance).Decimal,
|
||||
AvailableBalance: decimalFromNumeric(src.AvailableBalance).Decimal,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func mapBalanceTypeToParams(src entity.Balance) gen.CreateBalancesParams {
|
||||
return gen.CreateBalancesParams{
|
||||
Pkscript: hex.EncodeToString(src.PkScript),
|
||||
Tick: src.Tick,
|
||||
BlockHeight: int32(src.BlockHeight),
|
||||
OverallBalance: numericFromDecimal(src.OverallBalance),
|
||||
AvailableBalance: numericFromDecimal(src.AvailableBalance),
|
||||
}
|
||||
}
|
||||
@@ -1,20 +0,0 @@
|
||||
package postgres
|
||||
|
||||
import (
|
||||
"github.com/gaze-network/indexer-network/internal/postgres"
|
||||
"github.com/gaze-network/indexer-network/modules/brc20/internal/repository/postgres/gen"
|
||||
"github.com/jackc/pgx/v5"
|
||||
)
|
||||
|
||||
type Repository struct {
|
||||
db postgres.DB
|
||||
queries *gen.Queries
|
||||
tx pgx.Tx
|
||||
}
|
||||
|
||||
func NewRepository(db postgres.DB) *Repository {
|
||||
return &Repository{
|
||||
db: db,
|
||||
queries: gen.New(db),
|
||||
}
|
||||
}
|
||||
@@ -1,24 +0,0 @@
|
||||
package usecase
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/cockroachdb/errors"
|
||||
"github.com/gaze-network/indexer-network/modules/brc20/internal/entity"
|
||||
)
|
||||
|
||||
func (u *Usecase) GetBalancesByPkScript(ctx context.Context, pkScript []byte, blockHeight uint64) (map[string]*entity.Balance, error) {
|
||||
balances, err := u.dg.GetBalancesByPkScript(ctx, pkScript, blockHeight)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "error during GetBalancesByPkScript")
|
||||
}
|
||||
return balances, nil
|
||||
}
|
||||
|
||||
func (u *Usecase) GetBalancesByTick(ctx context.Context, tick string, blockHeight uint64) ([]*entity.Balance, error) {
|
||||
balances, err := u.dg.GetBalancesByTick(ctx, tick, blockHeight)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "failed to get balance by tick")
|
||||
}
|
||||
return balances, nil
|
||||
}
|
||||
@@ -1,33 +0,0 @@
|
||||
package usecase
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/cockroachdb/errors"
|
||||
"github.com/gaze-network/indexer-network/common/errs"
|
||||
"github.com/gaze-network/indexer-network/modules/brc20/internal/entity"
|
||||
)
|
||||
|
||||
func (u *Usecase) GetTickEntryByTickBatch(ctx context.Context, ticks []string) (map[string]*entity.TickEntry, error) {
|
||||
entries, err := u.dg.GetTickEntriesByTicks(ctx, ticks)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "error during GetTickEntriesByTicks")
|
||||
}
|
||||
return entries, nil
|
||||
}
|
||||
|
||||
func (u *Usecase) GetTickEntryByTickAndHeight(ctx context.Context, tick string, blockHeight uint64) (*entity.TickEntry, error) {
|
||||
entries, err := u.GetTickEntryByTickAndHeightBatch(ctx, []string{tick}, blockHeight)
|
||||
if err != nil {
|
||||
return nil, errors.WithStack(err)
|
||||
}
|
||||
entry, ok := entries[tick]
|
||||
if !ok {
|
||||
return nil, errors.Wrap(errs.NotFound, "entry not found")
|
||||
}
|
||||
return entry, nil
|
||||
}
|
||||
|
||||
func (u *Usecase) GetTickEntryByTickAndHeightBatch(ctx context.Context, ticks []string, blockHeight uint64) (map[string]*entity.TickEntry, error) {
|
||||
return nil, nil
|
||||
}
|
||||
@@ -1,15 +0,0 @@
|
||||
package usecase
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/cockroachdb/errors"
|
||||
)
|
||||
|
||||
func (u *Usecase) GetFirstLastInscriptionNumberByTick(ctx context.Context, tick string) (int64, int64, error) {
|
||||
first, last, err := u.dg.GetFirstLastInscriptionNumberByTick(ctx, tick)
|
||||
if err != nil {
|
||||
return -1, -1, errors.Wrap(err, "error during GetFirstLastInscriptionNumberByTick")
|
||||
}
|
||||
return first, last, nil
|
||||
}
|
||||
@@ -1,16 +0,0 @@
|
||||
package usecase
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/cockroachdb/errors"
|
||||
"github.com/gaze-network/indexer-network/core/types"
|
||||
)
|
||||
|
||||
func (u *Usecase) GetLatestBlock(ctx context.Context) (types.BlockHeader, error) {
|
||||
blockHeader, err := u.dg.GetLatestBlock(ctx)
|
||||
if err != nil {
|
||||
return types.BlockHeader{}, errors.Wrap(err, "failed to get latest block")
|
||||
}
|
||||
return blockHeader, nil
|
||||
}
|
||||
@@ -1,16 +0,0 @@
|
||||
package usecase
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/cockroachdb/errors"
|
||||
"github.com/gaze-network/indexer-network/modules/brc20/internal/entity"
|
||||
)
|
||||
|
||||
func (u *Usecase) GetDeployEventByTick(ctx context.Context, tick string) (*entity.EventDeploy, error) {
|
||||
result, err := u.dg.GetDeployEventByTick(ctx, tick)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "error during GetDeployEventByTick")
|
||||
}
|
||||
return result, nil
|
||||
}
|
||||
@@ -1,40 +0,0 @@
|
||||
package usecase
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/cockroachdb/errors"
|
||||
"github.com/gaze-network/indexer-network/modules/brc20/internal/entity"
|
||||
)
|
||||
|
||||
func (u *Usecase) GetDeployEvents(ctx context.Context, pkScript []byte, tick string, height uint64) ([]*entity.EventDeploy, error) {
|
||||
result, err := u.dg.GetDeployEvents(ctx, pkScript, tick, height)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "error during GetDeployEvents")
|
||||
}
|
||||
return result, nil
|
||||
}
|
||||
|
||||
func (u *Usecase) GetMintEvents(ctx context.Context, pkScript []byte, tick string, height uint64) ([]*entity.EventMint, error) {
|
||||
result, err := u.dg.GetMintEvents(ctx, pkScript, tick, height)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "error during GetMintEvents")
|
||||
}
|
||||
return result, nil
|
||||
}
|
||||
|
||||
func (u *Usecase) GetInscribeTransferEvents(ctx context.Context, pkScript []byte, tick string, height uint64) ([]*entity.EventInscribeTransfer, error) {
|
||||
result, err := u.dg.GetInscribeTransferEvents(ctx, pkScript, tick, height)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "error during GetInscribeTransferEvents")
|
||||
}
|
||||
return result, nil
|
||||
}
|
||||
|
||||
func (u *Usecase) GetTransferTransferEvents(ctx context.Context, pkScript []byte, tick string, height uint64) ([]*entity.EventTransferTransfer, error) {
|
||||
result, err := u.dg.GetTransferTransferEvents(ctx, pkScript, tick, height)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "error during GetTransferTransfersEvents")
|
||||
}
|
||||
return result, nil
|
||||
}
|
||||
@@ -1,16 +0,0 @@
|
||||
package usecase
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/cockroachdb/errors"
|
||||
"github.com/gaze-network/indexer-network/modules/brc20/internal/entity"
|
||||
)
|
||||
|
||||
func (u *Usecase) GetTransferableTransfersByPkScript(ctx context.Context, pkScript []byte, blockHeight uint64) ([]*entity.EventInscribeTransfer, error) {
|
||||
result, err := u.dg.GetTransferableTransfersByPkScript(ctx, pkScript, blockHeight)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "error during GetTransferableTransfersByPkScript")
|
||||
}
|
||||
return result, nil
|
||||
}
|
||||
@@ -1,18 +0,0 @@
|
||||
package usecase
|
||||
|
||||
import (
|
||||
"github.com/gaze-network/indexer-network/modules/brc20/internal/datagateway"
|
||||
"github.com/gaze-network/indexer-network/pkg/btcclient"
|
||||
)
|
||||
|
||||
type Usecase struct {
|
||||
dg datagateway.BRC20DataGateway
|
||||
bitcoinClient btcclient.Contract
|
||||
}
|
||||
|
||||
func New(dg datagateway.BRC20DataGateway, bitcoinClient btcclient.Contract) *Usecase {
|
||||
return &Usecase{
|
||||
dg: dg,
|
||||
bitcoinClient: bitcoinClient,
|
||||
}
|
||||
}
|
||||
@@ -1,238 +0,0 @@
|
||||
package brc20
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/btcsuite/btcd/wire"
|
||||
"github.com/cockroachdb/errors"
|
||||
"github.com/gaze-network/indexer-network/common"
|
||||
"github.com/gaze-network/indexer-network/common/errs"
|
||||
"github.com/gaze-network/indexer-network/core/indexer"
|
||||
"github.com/gaze-network/indexer-network/core/types"
|
||||
"github.com/gaze-network/indexer-network/modules/brc20/internal/datagateway"
|
||||
"github.com/gaze-network/indexer-network/modules/brc20/internal/entity"
|
||||
"github.com/gaze-network/indexer-network/modules/brc20/internal/ordinals"
|
||||
"github.com/gaze-network/indexer-network/pkg/btcclient"
|
||||
"github.com/gaze-network/indexer-network/pkg/logger"
|
||||
"github.com/gaze-network/indexer-network/pkg/logger/slogx"
|
||||
"github.com/gaze-network/indexer-network/pkg/lru"
|
||||
)
|
||||
|
||||
// Make sure to implement the Bitcoin Processor interface
|
||||
var _ indexer.Processor[*types.Block] = (*Processor)(nil)
|
||||
|
||||
type Processor struct {
|
||||
brc20Dg datagateway.BRC20DataGateway
|
||||
indexerInfoDg datagateway.IndexerInfoDataGateway
|
||||
btcClient btcclient.Contract
|
||||
network common.Network
|
||||
cleanupFuncs []func(context.Context) error
|
||||
|
||||
// block states
|
||||
flotsamsSentAsFee []*entity.Flotsam
|
||||
blockReward uint64
|
||||
|
||||
// processor stats
|
||||
cursedInscriptionCount uint64
|
||||
blessedInscriptionCount uint64
|
||||
lostSats uint64
|
||||
|
||||
// cache
|
||||
outPointValueCache *lru.Cache[wire.OutPoint, uint64]
|
||||
|
||||
// flush buffers - inscription states
|
||||
newInscriptionTransfers []*entity.InscriptionTransfer
|
||||
newInscriptionEntries map[ordinals.InscriptionId]*ordinals.InscriptionEntry
|
||||
newInscriptionEntryStates map[ordinals.InscriptionId]*ordinals.InscriptionEntry
|
||||
// flush buffers - brc20 states
|
||||
newTickEntries map[string]*entity.TickEntry
|
||||
newTickEntryStates map[string]*entity.TickEntry
|
||||
newEventDeploys []*entity.EventDeploy
|
||||
newEventMints []*entity.EventMint
|
||||
newEventInscribeTransfers []*entity.EventInscribeTransfer
|
||||
newEventTransferTransfers []*entity.EventTransferTransfer
|
||||
newBalances map[string]map[string]*entity.Balance // pkscript -> tick -> balance
|
||||
|
||||
eventHashString string
|
||||
}
|
||||
|
||||
// TODO: move this to config
|
||||
const outPointValueCacheSize = 100000
|
||||
|
||||
func NewProcessor(brc20Dg datagateway.BRC20DataGateway, indexerInfoDg datagateway.IndexerInfoDataGateway, btcClient btcclient.Contract, network common.Network, cleanupFuncs []func(context.Context) error) (*Processor, error) {
|
||||
outPointValueCache, err := lru.New[wire.OutPoint, uint64](outPointValueCacheSize)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "failed to create outPointValueCache")
|
||||
}
|
||||
|
||||
return &Processor{
|
||||
brc20Dg: brc20Dg,
|
||||
indexerInfoDg: indexerInfoDg,
|
||||
btcClient: btcClient,
|
||||
network: network,
|
||||
cleanupFuncs: cleanupFuncs,
|
||||
|
||||
flotsamsSentAsFee: make([]*entity.Flotsam, 0),
|
||||
blockReward: 0,
|
||||
|
||||
cursedInscriptionCount: 0, // to be initialized by p.VerifyStates()
|
||||
blessedInscriptionCount: 0, // to be initialized by p.VerifyStates()
|
||||
lostSats: 0, // to be initialized by p.VerifyStates()
|
||||
outPointValueCache: outPointValueCache,
|
||||
|
||||
newInscriptionTransfers: make([]*entity.InscriptionTransfer, 0),
|
||||
newInscriptionEntries: make(map[ordinals.InscriptionId]*ordinals.InscriptionEntry),
|
||||
newInscriptionEntryStates: make(map[ordinals.InscriptionId]*ordinals.InscriptionEntry),
|
||||
|
||||
newTickEntries: make(map[string]*entity.TickEntry),
|
||||
newTickEntryStates: make(map[string]*entity.TickEntry),
|
||||
newEventDeploys: make([]*entity.EventDeploy, 0),
|
||||
newEventMints: make([]*entity.EventMint, 0),
|
||||
newEventInscribeTransfers: make([]*entity.EventInscribeTransfer, 0),
|
||||
newEventTransferTransfers: make([]*entity.EventTransferTransfer, 0),
|
||||
newBalances: make(map[string]map[string]*entity.Balance),
|
||||
}, nil
|
||||
}
|
||||
|
||||
// VerifyStates implements indexer.Processor.
|
||||
func (p *Processor) VerifyStates(ctx context.Context) error {
|
||||
indexerState, err := p.indexerInfoDg.GetLatestIndexerState(ctx)
|
||||
if err != nil && !errors.Is(err, errs.NotFound) {
|
||||
return errors.Wrap(err, "failed to get latest indexer state")
|
||||
}
|
||||
// if not found, create indexer state
|
||||
if errors.Is(err, errs.NotFound) {
|
||||
if err := p.indexerInfoDg.CreateIndexerState(ctx, entity.IndexerState{
|
||||
ClientVersion: ClientVersion,
|
||||
DBVersion: DBVersion,
|
||||
EventHashVersion: EventHashVersion,
|
||||
Network: p.network,
|
||||
}); err != nil {
|
||||
return errors.Wrap(err, "failed to set indexer state")
|
||||
}
|
||||
} else {
|
||||
if indexerState.DBVersion != DBVersion {
|
||||
return errors.Wrapf(errs.ConflictSetting, "db version mismatch: current version is %d. Please upgrade to version %d", indexerState.DBVersion, DBVersion)
|
||||
}
|
||||
if indexerState.EventHashVersion != EventHashVersion {
|
||||
return errors.Wrapf(errs.ConflictSetting, "event version mismatch: current version is %d. Please reset rune's db first.", indexerState.EventHashVersion, EventHashVersion)
|
||||
}
|
||||
if indexerState.Network != p.network {
|
||||
return errors.Wrapf(errs.ConflictSetting, "network mismatch: latest indexed network is %d, configured network is %d. If you want to change the network, please reset the database", indexerState.Network, p.network)
|
||||
}
|
||||
}
|
||||
|
||||
stats, err := p.brc20Dg.GetProcessorStats(ctx)
|
||||
if err != nil {
|
||||
if !errors.Is(err, errs.NotFound) {
|
||||
return errors.Wrap(err, "failed to count cursed inscriptions")
|
||||
}
|
||||
stats = &entity.ProcessorStats{
|
||||
BlockHeight: uint64(startingBlockHeader[p.network].Height),
|
||||
CursedInscriptionCount: 0,
|
||||
BlessedInscriptionCount: 0,
|
||||
LostSats: 0,
|
||||
}
|
||||
}
|
||||
p.cursedInscriptionCount = stats.CursedInscriptionCount
|
||||
p.blessedInscriptionCount = stats.BlessedInscriptionCount
|
||||
p.lostSats = stats.LostSats
|
||||
return nil
|
||||
}
|
||||
|
||||
// CurrentBlock implements indexer.Processor.
|
||||
func (p *Processor) CurrentBlock(ctx context.Context) (types.BlockHeader, error) {
|
||||
blockHeader, err := p.brc20Dg.GetLatestBlock(ctx)
|
||||
if err != nil {
|
||||
if errors.Is(err, errs.NotFound) {
|
||||
return startingBlockHeader[p.network], nil
|
||||
}
|
||||
return types.BlockHeader{}, errors.Wrap(err, "failed to get latest block")
|
||||
}
|
||||
return blockHeader, nil
|
||||
}
|
||||
|
||||
// GetIndexedBlock implements indexer.Processor.
|
||||
func (p *Processor) GetIndexedBlock(ctx context.Context, height int64) (types.BlockHeader, error) {
|
||||
block, err := p.brc20Dg.GetIndexedBlockByHeight(ctx, height)
|
||||
if err != nil {
|
||||
return types.BlockHeader{}, errors.Wrap(err, "failed to get indexed block")
|
||||
}
|
||||
return types.BlockHeader{
|
||||
Height: int64(block.Height),
|
||||
Hash: block.Hash,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// Name implements indexer.Processor.
|
||||
func (p *Processor) Name() string {
|
||||
return "brc20"
|
||||
}
|
||||
|
||||
// RevertData implements indexer.Processor.
|
||||
func (p *Processor) RevertData(ctx context.Context, from int64) error {
|
||||
brc20DgTx, err := p.brc20Dg.BeginBRC20Tx(ctx)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "failed to begin transaction")
|
||||
}
|
||||
defer func() {
|
||||
if err := brc20DgTx.Rollback(ctx); err != nil {
|
||||
logger.WarnContext(ctx, "failed to rollback transaction",
|
||||
slogx.Error(err),
|
||||
slogx.String("event", "rollback_brc20_insertion"),
|
||||
)
|
||||
}
|
||||
}()
|
||||
|
||||
if err := brc20DgTx.DeleteIndexedBlocksSinceHeight(ctx, uint64(from)); err != nil {
|
||||
return errors.Wrap(err, "failed to delete indexed blocks")
|
||||
}
|
||||
if err := brc20DgTx.DeleteProcessorStatsSinceHeight(ctx, uint64(from)); err != nil {
|
||||
return errors.Wrap(err, "failed to delete processor stats")
|
||||
}
|
||||
if err := brc20DgTx.DeleteTickEntriesSinceHeight(ctx, uint64(from)); err != nil {
|
||||
return errors.Wrap(err, "failed to delete ticks")
|
||||
}
|
||||
if err := brc20DgTx.DeleteTickEntryStatesSinceHeight(ctx, uint64(from)); err != nil {
|
||||
return errors.Wrap(err, "failed to delete tick states")
|
||||
}
|
||||
if err := brc20DgTx.DeleteEventDeploysSinceHeight(ctx, uint64(from)); err != nil {
|
||||
return errors.Wrap(err, "failed to delete deploy events")
|
||||
}
|
||||
if err := brc20DgTx.DeleteEventMintsSinceHeight(ctx, uint64(from)); err != nil {
|
||||
return errors.Wrap(err, "failed to delete mint events")
|
||||
}
|
||||
if err := brc20DgTx.DeleteEventInscribeTransfersSinceHeight(ctx, uint64(from)); err != nil {
|
||||
return errors.Wrap(err, "failed to delete inscribe transfer events")
|
||||
}
|
||||
if err := brc20DgTx.DeleteEventTransferTransfersSinceHeight(ctx, uint64(from)); err != nil {
|
||||
return errors.Wrap(err, "failed to delete transfer transfer events")
|
||||
}
|
||||
if err := brc20DgTx.DeleteBalancesSinceHeight(ctx, uint64(from)); err != nil {
|
||||
return errors.Wrap(err, "failed to delete balances")
|
||||
}
|
||||
if err := brc20DgTx.DeleteInscriptionEntriesSinceHeight(ctx, uint64(from)); err != nil {
|
||||
return errors.Wrap(err, "failed to delete inscription entries")
|
||||
}
|
||||
if err := brc20DgTx.DeleteInscriptionEntryStatesSinceHeight(ctx, uint64(from)); err != nil {
|
||||
return errors.Wrap(err, "failed to delete inscription entry states")
|
||||
}
|
||||
if err := brc20DgTx.DeleteInscriptionTransfersSinceHeight(ctx, uint64(from)); err != nil {
|
||||
return errors.Wrap(err, "failed to delete inscription transfers")
|
||||
}
|
||||
|
||||
if err := brc20DgTx.Commit(ctx); err != nil {
|
||||
return errors.Wrap(err, "failed to commit transaction")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (p *Processor) Shutdown(ctx context.Context) error {
|
||||
var errs []error
|
||||
for _, cleanup := range p.cleanupFuncs {
|
||||
if err := cleanup(ctx); err != nil {
|
||||
errs = append(errs, err)
|
||||
}
|
||||
}
|
||||
return errors.WithStack(errors.Join(errs...))
|
||||
}
|
||||
@@ -1,448 +0,0 @@
|
||||
package brc20
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"encoding/hex"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/cockroachdb/errors"
|
||||
"github.com/gaze-network/indexer-network/core/types"
|
||||
"github.com/gaze-network/indexer-network/modules/brc20/internal/brc20"
|
||||
"github.com/gaze-network/indexer-network/modules/brc20/internal/datagateway"
|
||||
"github.com/gaze-network/indexer-network/modules/brc20/internal/entity"
|
||||
"github.com/gaze-network/indexer-network/modules/brc20/internal/ordinals"
|
||||
"github.com/samber/lo"
|
||||
"github.com/shopspring/decimal"
|
||||
)
|
||||
|
||||
func (p *Processor) processBRC20States(ctx context.Context, transfers []*entity.InscriptionTransfer, blockHeader types.BlockHeader) error {
|
||||
payloads := make([]*brc20.Payload, 0)
|
||||
ticks := make(map[string]struct{})
|
||||
for _, transfer := range transfers {
|
||||
if transfer.Content == nil {
|
||||
// skip empty content
|
||||
continue
|
||||
}
|
||||
payload, err := brc20.ParsePayload(transfer)
|
||||
if err != nil {
|
||||
// skip invalid payloads
|
||||
continue
|
||||
}
|
||||
payloads = append(payloads, payload)
|
||||
ticks[payload.Tick] = struct{}{}
|
||||
}
|
||||
if len(payloads) == 0 {
|
||||
// skip if no valid payloads
|
||||
return nil
|
||||
}
|
||||
// TODO: concurrently fetch from db to optimize speed
|
||||
tickEntries, err := p.brc20Dg.GetTickEntriesByTicks(ctx, lo.Keys(ticks))
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "failed to get inscription entries by ids")
|
||||
}
|
||||
|
||||
// preload required data to reduce individual data fetching during process
|
||||
inscriptionIds := make([]ordinals.InscriptionId, 0)
|
||||
inscriptionIdsToFetchParent := make([]ordinals.InscriptionId, 0)
|
||||
inscriptionIdsToFetchEventInscribeTransfer := make([]ordinals.InscriptionId, 0)
|
||||
balancesToFetch := make([]datagateway.GetBalancesBatchAtHeightQuery, 0) // pkscript -> tick -> struct{}
|
||||
for _, payload := range payloads {
|
||||
inscriptionIds = append(inscriptionIds, payload.Transfer.InscriptionId)
|
||||
if payload.Op == brc20.OperationMint {
|
||||
// preload parent id to validate mint events with self mint
|
||||
if entry := tickEntries[payload.Tick]; entry.IsSelfMint {
|
||||
inscriptionIdsToFetchParent = append(inscriptionIdsToFetchParent, payload.Transfer.InscriptionId)
|
||||
}
|
||||
}
|
||||
if payload.Op == brc20.OperationTransfer {
|
||||
if payload.Transfer.OldSatPoint == (ordinals.SatPoint{}) {
|
||||
// preload balance to validate inscribe transfer event
|
||||
balancesToFetch = append(balancesToFetch, datagateway.GetBalancesBatchAtHeightQuery{
|
||||
PkScriptHex: hex.EncodeToString(payload.Transfer.NewPkScript),
|
||||
Tick: payload.Tick,
|
||||
})
|
||||
} else {
|
||||
// preload inscribe-transfer events to validate transfer-transfer event
|
||||
inscriptionIdsToFetchEventInscribeTransfer = append(inscriptionIdsToFetchEventInscribeTransfer, payload.Transfer.InscriptionId)
|
||||
}
|
||||
}
|
||||
}
|
||||
inscriptionIdsToNumber, err := p.getInscriptionNumbersByIds(ctx, lo.Uniq(inscriptionIds))
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "failed to get inscription numbers by ids")
|
||||
}
|
||||
inscriptionIdsToParent, err := p.getInscriptionParentsByIds(ctx, lo.Uniq(inscriptionIdsToFetchParent))
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "failed to get inscription parents by ids")
|
||||
}
|
||||
latestEventId, err := p.brc20Dg.GetLatestEventId(ctx)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "failed to get latest event id")
|
||||
}
|
||||
// pkscript -> tick -> balance
|
||||
balances, err := p.brc20Dg.GetBalancesBatchAtHeight(ctx, uint64(blockHeader.Height-1), balancesToFetch)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "failed to get balances batch at height")
|
||||
}
|
||||
eventInscribeTransfers, err := p.brc20Dg.GetEventInscribeTransfersByInscriptionIds(ctx, lo.Uniq(inscriptionIdsToFetchEventInscribeTransfer))
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "failed to get event inscribe transfers by inscription ids")
|
||||
}
|
||||
|
||||
newTickEntries := make(map[string]*entity.TickEntry)
|
||||
newTickEntryStates := make(map[string]*entity.TickEntry)
|
||||
newEventDeploys := make([]*entity.EventDeploy, 0)
|
||||
newEventMints := make([]*entity.EventMint, 0)
|
||||
newEventInscribeTransfers := make([]*entity.EventInscribeTransfer, 0)
|
||||
newEventTransferTransfers := make([]*entity.EventTransferTransfer, 0)
|
||||
newBalances := make(map[string]map[string]*entity.Balance)
|
||||
var eventHashBuilder strings.Builder
|
||||
|
||||
handleEventDeploy := func(payload *brc20.Payload, tickEntry *entity.TickEntry) {
|
||||
if payload.Transfer.TransferCount > 1 {
|
||||
// skip used deploy inscriptions
|
||||
return
|
||||
}
|
||||
if tickEntry != nil {
|
||||
// skip deploy inscriptions for duplicate ticks
|
||||
return
|
||||
}
|
||||
newEntry := &entity.TickEntry{
|
||||
Tick: payload.Tick,
|
||||
OriginalTick: payload.OriginalTick,
|
||||
TotalSupply: payload.Max,
|
||||
Decimals: payload.Dec,
|
||||
LimitPerMint: payload.Lim,
|
||||
IsSelfMint: payload.SelfMint,
|
||||
DeployInscriptionId: payload.Transfer.InscriptionId,
|
||||
DeployedAt: blockHeader.Timestamp,
|
||||
DeployedAtHeight: payload.Transfer.BlockHeight,
|
||||
MintedAmount: decimal.Zero,
|
||||
BurnedAmount: decimal.Zero,
|
||||
CompletedAt: time.Time{},
|
||||
CompletedAtHeight: 0,
|
||||
}
|
||||
newTickEntries[payload.Tick] = newEntry
|
||||
newTickEntryStates[payload.Tick] = newEntry
|
||||
// update entries for other operations in same block
|
||||
tickEntries[payload.Tick] = newEntry
|
||||
|
||||
event := &entity.EventDeploy{
|
||||
Id: latestEventId + 1,
|
||||
InscriptionId: payload.Transfer.InscriptionId,
|
||||
InscriptionNumber: inscriptionIdsToNumber[payload.Transfer.InscriptionId],
|
||||
Tick: payload.Tick,
|
||||
OriginalTick: payload.OriginalTick,
|
||||
TxHash: payload.Transfer.TxHash,
|
||||
BlockHeight: payload.Transfer.BlockHeight,
|
||||
TxIndex: payload.Transfer.TxIndex,
|
||||
Timestamp: blockHeader.Timestamp,
|
||||
PkScript: payload.Transfer.NewPkScript,
|
||||
SatPoint: payload.Transfer.NewSatPoint,
|
||||
TotalSupply: payload.Max,
|
||||
Decimals: payload.Dec,
|
||||
LimitPerMint: payload.Lim,
|
||||
IsSelfMint: payload.SelfMint,
|
||||
}
|
||||
newEventDeploys = append(newEventDeploys, event)
|
||||
latestEventId++
|
||||
|
||||
eventHashBuilder.WriteString(getEventDeployString(event) + eventHashSeparator)
|
||||
}
|
||||
handleEventMint := func(payload *brc20.Payload, tickEntry *entity.TickEntry) {
|
||||
if payload.Transfer.TransferCount > 1 {
|
||||
// skip used mint inscriptions that are already used
|
||||
return
|
||||
}
|
||||
if tickEntry == nil {
|
||||
// skip mint inscriptions for non-existent ticks
|
||||
return
|
||||
}
|
||||
if -payload.Amt.Exponent() > int32(tickEntry.Decimals) {
|
||||
// skip mint inscriptions with decimals greater than allowed
|
||||
return
|
||||
}
|
||||
if tickEntry.MintedAmount.GreaterThanOrEqual(tickEntry.TotalSupply) {
|
||||
// skip mint inscriptions for ticks with completed mints
|
||||
return
|
||||
}
|
||||
if payload.Amt.GreaterThan(tickEntry.LimitPerMint) {
|
||||
// skip mint inscriptions with amount greater than limit per mint
|
||||
return
|
||||
}
|
||||
mintableAmount := tickEntry.TotalSupply.Sub(tickEntry.MintedAmount)
|
||||
if payload.Amt.GreaterThan(mintableAmount) {
|
||||
payload.Amt = mintableAmount
|
||||
}
|
||||
var parentId *ordinals.InscriptionId
|
||||
if tickEntry.IsSelfMint {
|
||||
parentIdValue, ok := inscriptionIdsToParent[payload.Transfer.InscriptionId]
|
||||
if !ok {
|
||||
// skip mint inscriptions for self mint ticks without parent inscription
|
||||
return
|
||||
}
|
||||
if parentIdValue != tickEntry.DeployInscriptionId {
|
||||
// skip mint inscriptions for self mint ticks with invalid parent inscription
|
||||
return
|
||||
}
|
||||
parentId = &parentIdValue
|
||||
}
|
||||
|
||||
tickEntry.MintedAmount = tickEntry.MintedAmount.Add(payload.Amt)
|
||||
// mark as completed if this mint completes the total supply
|
||||
if tickEntry.MintedAmount.GreaterThanOrEqual(tickEntry.TotalSupply) {
|
||||
tickEntry.CompletedAt = blockHeader.Timestamp
|
||||
tickEntry.CompletedAtHeight = payload.Transfer.BlockHeight
|
||||
}
|
||||
|
||||
newTickEntryStates[payload.Tick] = tickEntry
|
||||
event := &entity.EventMint{
|
||||
Id: latestEventId + 1,
|
||||
InscriptionId: payload.Transfer.InscriptionId,
|
||||
InscriptionNumber: inscriptionIdsToNumber[payload.Transfer.InscriptionId],
|
||||
Tick: payload.Tick,
|
||||
OriginalTick: payload.OriginalTick,
|
||||
TxHash: payload.Transfer.TxHash,
|
||||
BlockHeight: payload.Transfer.BlockHeight,
|
||||
TxIndex: payload.Transfer.TxIndex,
|
||||
Timestamp: blockHeader.Timestamp,
|
||||
PkScript: payload.Transfer.NewPkScript,
|
||||
SatPoint: payload.Transfer.NewSatPoint,
|
||||
Amount: payload.Amt,
|
||||
ParentId: parentId,
|
||||
}
|
||||
newEventMints = append(newEventMints, event)
|
||||
latestEventId++
|
||||
|
||||
eventHashBuilder.WriteString(getEventMintString(event, tickEntry.Decimals) + eventHashSeparator)
|
||||
}
|
||||
handleEventInscribeTransfer := func(payload *brc20.Payload, tickEntry *entity.TickEntry) {
|
||||
// inscribe transfer event
|
||||
pkScriptHex := hex.EncodeToString(payload.Transfer.NewPkScript)
|
||||
balance, ok := balances[pkScriptHex][payload.Tick]
|
||||
if !ok {
|
||||
balance = &entity.Balance{
|
||||
PkScript: payload.Transfer.NewPkScript,
|
||||
Tick: payload.Tick,
|
||||
BlockHeight: uint64(blockHeader.Height - 1),
|
||||
OverallBalance: decimal.Zero, // defaults balance to zero if not found
|
||||
AvailableBalance: decimal.Zero,
|
||||
}
|
||||
}
|
||||
if payload.Amt.GreaterThan(balance.AvailableBalance) {
|
||||
// skip inscribe transfer event if amount exceeds available balance
|
||||
return
|
||||
}
|
||||
// update balance state
|
||||
balance.BlockHeight = uint64(blockHeader.Height)
|
||||
balance.AvailableBalance = balance.AvailableBalance.Sub(payload.Amt)
|
||||
if _, ok := balances[pkScriptHex]; !ok {
|
||||
balances[pkScriptHex] = make(map[string]*entity.Balance)
|
||||
}
|
||||
balances[pkScriptHex][payload.Tick] = balance
|
||||
if _, ok := newBalances[pkScriptHex]; !ok {
|
||||
newBalances[pkScriptHex] = make(map[string]*entity.Balance)
|
||||
}
|
||||
newBalances[pkScriptHex][payload.Tick] = &entity.Balance{}
|
||||
|
||||
event := &entity.EventInscribeTransfer{
|
||||
Id: latestEventId + 1,
|
||||
InscriptionId: payload.Transfer.InscriptionId,
|
||||
InscriptionNumber: inscriptionIdsToNumber[payload.Transfer.InscriptionId],
|
||||
Tick: payload.Tick,
|
||||
OriginalTick: payload.OriginalTick,
|
||||
TxHash: payload.Transfer.TxHash,
|
||||
BlockHeight: payload.Transfer.BlockHeight,
|
||||
TxIndex: payload.Transfer.TxIndex,
|
||||
Timestamp: blockHeader.Timestamp,
|
||||
PkScript: payload.Transfer.NewPkScript,
|
||||
SatPoint: payload.Transfer.NewSatPoint,
|
||||
OutputIndex: payload.Transfer.NewSatPoint.OutPoint.Index,
|
||||
SatsAmount: payload.Transfer.NewOutputValue,
|
||||
Amount: payload.Amt,
|
||||
}
|
||||
latestEventId++
|
||||
eventInscribeTransfers[payload.Transfer.InscriptionId] = event
|
||||
newEventInscribeTransfers = append(newEventInscribeTransfers, event)
|
||||
|
||||
eventHashBuilder.WriteString(getEventInscribeTransferString(event, tickEntry.Decimals) + eventHashSeparator)
|
||||
}
|
||||
handleEventTransferTransferAsFee := func(payload *brc20.Payload, tickEntry *entity.TickEntry, inscribeTransfer *entity.EventInscribeTransfer) {
|
||||
// return balance to sender
|
||||
fromPkScriptHex := hex.EncodeToString(inscribeTransfer.PkScript)
|
||||
fromBalance, ok := balances[fromPkScriptHex][payload.Tick]
|
||||
if !ok {
|
||||
fromBalance = &entity.Balance{
|
||||
PkScript: inscribeTransfer.PkScript,
|
||||
Tick: payload.Tick,
|
||||
BlockHeight: uint64(blockHeader.Height),
|
||||
OverallBalance: decimal.Zero, // defaults balance to zero if not found
|
||||
AvailableBalance: decimal.Zero,
|
||||
}
|
||||
}
|
||||
fromBalance.BlockHeight = uint64(blockHeader.Height)
|
||||
fromBalance.AvailableBalance = fromBalance.AvailableBalance.Add(payload.Amt)
|
||||
if _, ok := balances[fromPkScriptHex]; !ok {
|
||||
balances[fromPkScriptHex] = make(map[string]*entity.Balance)
|
||||
}
|
||||
balances[fromPkScriptHex][payload.Tick] = fromBalance
|
||||
if _, ok := newBalances[fromPkScriptHex]; !ok {
|
||||
newBalances[fromPkScriptHex] = make(map[string]*entity.Balance)
|
||||
}
|
||||
newBalances[fromPkScriptHex][payload.Tick] = fromBalance
|
||||
|
||||
event := &entity.EventTransferTransfer{
|
||||
Id: latestEventId + 1,
|
||||
InscriptionId: payload.Transfer.InscriptionId,
|
||||
InscriptionNumber: inscriptionIdsToNumber[payload.Transfer.InscriptionId],
|
||||
Tick: payload.Tick,
|
||||
OriginalTick: payload.OriginalTick,
|
||||
TxHash: payload.Transfer.TxHash,
|
||||
BlockHeight: payload.Transfer.BlockHeight,
|
||||
TxIndex: payload.Transfer.TxIndex,
|
||||
Timestamp: blockHeader.Timestamp,
|
||||
FromPkScript: inscribeTransfer.PkScript,
|
||||
FromSatPoint: inscribeTransfer.SatPoint,
|
||||
FromInputIndex: payload.Transfer.FromInputIndex,
|
||||
ToPkScript: payload.Transfer.NewPkScript,
|
||||
ToSatPoint: payload.Transfer.NewSatPoint,
|
||||
ToOutputIndex: payload.Transfer.NewSatPoint.OutPoint.Index,
|
||||
SpentAsFee: true,
|
||||
Amount: payload.Amt,
|
||||
}
|
||||
newEventTransferTransfers = append(newEventTransferTransfers, event)
|
||||
|
||||
eventHashBuilder.WriteString(getEventTransferTransferString(event, tickEntry.Decimals) + eventHashSeparator)
|
||||
}
|
||||
handleEventTransferTransferNormal := func(payload *brc20.Payload, tickEntry *entity.TickEntry, inscribeTransfer *entity.EventInscribeTransfer) {
|
||||
// subtract balance from sender
|
||||
fromPkScriptHex := hex.EncodeToString(inscribeTransfer.PkScript)
|
||||
fromBalance, ok := balances[fromPkScriptHex][payload.Tick]
|
||||
if !ok {
|
||||
// skip transfer transfer event if from balance does not exist
|
||||
return
|
||||
}
|
||||
fromBalance.BlockHeight = uint64(blockHeader.Height)
|
||||
fromBalance.OverallBalance = fromBalance.OverallBalance.Sub(payload.Amt)
|
||||
if _, ok := balances[fromPkScriptHex]; !ok {
|
||||
balances[fromPkScriptHex] = make(map[string]*entity.Balance)
|
||||
}
|
||||
balances[fromPkScriptHex][payload.Tick] = fromBalance
|
||||
if _, ok := newBalances[fromPkScriptHex]; !ok {
|
||||
newBalances[fromPkScriptHex] = make(map[string]*entity.Balance)
|
||||
}
|
||||
newBalances[fromPkScriptHex][payload.Tick] = fromBalance
|
||||
|
||||
// add balance to receiver
|
||||
if bytes.Equal(payload.Transfer.NewPkScript, []byte{0x6a}) {
|
||||
// burn if sent to OP_RETURN
|
||||
tickEntry.BurnedAmount = tickEntry.BurnedAmount.Add(payload.Amt)
|
||||
tickEntries[payload.Tick] = tickEntry
|
||||
newTickEntryStates[payload.Tick] = tickEntry
|
||||
} else {
|
||||
toPkScriptHex := hex.EncodeToString(payload.Transfer.NewPkScript)
|
||||
toBalance, ok := balances[toPkScriptHex][payload.Tick]
|
||||
if !ok {
|
||||
toBalance = &entity.Balance{
|
||||
PkScript: payload.Transfer.NewPkScript,
|
||||
Tick: payload.Tick,
|
||||
BlockHeight: uint64(blockHeader.Height),
|
||||
OverallBalance: decimal.Zero, // defaults balance to zero if not found
|
||||
AvailableBalance: decimal.Zero,
|
||||
}
|
||||
}
|
||||
toBalance.BlockHeight = uint64(blockHeader.Height)
|
||||
toBalance.OverallBalance = toBalance.OverallBalance.Add(payload.Amt)
|
||||
toBalance.AvailableBalance = toBalance.AvailableBalance.Add(payload.Amt)
|
||||
if _, ok := balances[toPkScriptHex]; !ok {
|
||||
balances[toPkScriptHex] = make(map[string]*entity.Balance)
|
||||
}
|
||||
balances[toPkScriptHex][payload.Tick] = toBalance
|
||||
if _, ok := newBalances[toPkScriptHex]; !ok {
|
||||
newBalances[toPkScriptHex] = make(map[string]*entity.Balance)
|
||||
}
|
||||
newBalances[toPkScriptHex][payload.Tick] = toBalance
|
||||
}
|
||||
|
||||
event := &entity.EventTransferTransfer{
|
||||
Id: latestEventId + 1,
|
||||
InscriptionId: payload.Transfer.InscriptionId,
|
||||
InscriptionNumber: inscriptionIdsToNumber[payload.Transfer.InscriptionId],
|
||||
Tick: payload.Tick,
|
||||
OriginalTick: payload.OriginalTick,
|
||||
TxHash: payload.Transfer.TxHash,
|
||||
BlockHeight: payload.Transfer.BlockHeight,
|
||||
TxIndex: payload.Transfer.TxIndex,
|
||||
Timestamp: blockHeader.Timestamp,
|
||||
FromPkScript: inscribeTransfer.PkScript,
|
||||
FromSatPoint: inscribeTransfer.SatPoint,
|
||||
FromInputIndex: payload.Transfer.FromInputIndex,
|
||||
ToPkScript: payload.Transfer.NewPkScript,
|
||||
ToSatPoint: payload.Transfer.NewSatPoint,
|
||||
ToOutputIndex: payload.Transfer.NewSatPoint.OutPoint.Index,
|
||||
SpentAsFee: false,
|
||||
Amount: payload.Amt,
|
||||
}
|
||||
newEventTransferTransfers = append(newEventTransferTransfers, event)
|
||||
|
||||
eventHashBuilder.WriteString(getEventTransferTransferString(event, tickEntry.Decimals) + eventHashSeparator)
|
||||
}
|
||||
|
||||
for _, payload := range payloads {
|
||||
tickEntry := tickEntries[payload.Tick]
|
||||
|
||||
if payload.Transfer.SentAsFee && payload.Transfer.OldSatPoint == (ordinals.SatPoint{}) {
|
||||
// skip inscriptions inscribed as fee
|
||||
continue
|
||||
}
|
||||
|
||||
switch payload.Op {
|
||||
case brc20.OperationDeploy:
|
||||
handleEventDeploy(payload, tickEntry)
|
||||
case brc20.OperationMint:
|
||||
handleEventMint(payload, tickEntry)
|
||||
case brc20.OperationTransfer:
|
||||
if payload.Transfer.TransferCount > 2 {
|
||||
// skip used transfer inscriptions
|
||||
continue
|
||||
}
|
||||
if tickEntry == nil {
|
||||
// skip transfer inscriptions for non-existent ticks
|
||||
continue
|
||||
}
|
||||
if -payload.Amt.Exponent() > int32(tickEntry.Decimals) {
|
||||
// skip transfer inscriptions with decimals greater than allowed
|
||||
continue
|
||||
}
|
||||
|
||||
if payload.Transfer.OldSatPoint == (ordinals.SatPoint{}) {
|
||||
handleEventInscribeTransfer(payload, tickEntry)
|
||||
} else {
|
||||
// transfer transfer event
|
||||
inscribeTransfer, ok := eventInscribeTransfers[payload.Transfer.InscriptionId]
|
||||
if !ok {
|
||||
// skip transfer transfer event if prior inscribe transfer event does not exist
|
||||
continue
|
||||
}
|
||||
|
||||
if payload.Transfer.SentAsFee {
|
||||
handleEventTransferTransferAsFee(payload, tickEntry, inscribeTransfer)
|
||||
} else {
|
||||
handleEventTransferTransferNormal(payload, tickEntry, inscribeTransfer)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
p.newTickEntries = newTickEntries
|
||||
p.newTickEntryStates = newTickEntryStates
|
||||
p.newEventDeploys = newEventDeploys
|
||||
p.newEventMints = newEventMints
|
||||
p.newEventInscribeTransfers = newEventInscribeTransfers
|
||||
p.newEventTransferTransfers = newEventTransferTransfers
|
||||
p.newBalances = newBalances
|
||||
p.eventHashString = eventHashBuilder.String()
|
||||
return nil
|
||||
}
|
||||
@@ -1,625 +0,0 @@
|
||||
package brc20
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"slices"
|
||||
"sync"
|
||||
|
||||
"github.com/btcsuite/btcd/blockchain"
|
||||
"github.com/btcsuite/btcd/chaincfg/chainhash"
|
||||
"github.com/btcsuite/btcd/wire"
|
||||
"github.com/cockroachdb/errors"
|
||||
"github.com/gaze-network/indexer-network/common/errs"
|
||||
"github.com/gaze-network/indexer-network/core/types"
|
||||
"github.com/gaze-network/indexer-network/modules/brc20/internal/entity"
|
||||
"github.com/gaze-network/indexer-network/modules/brc20/internal/ordinals"
|
||||
"github.com/gaze-network/indexer-network/pkg/logger"
|
||||
"github.com/gaze-network/indexer-network/pkg/logger/slogx"
|
||||
"github.com/samber/lo"
|
||||
"golang.org/x/sync/errgroup"
|
||||
)
|
||||
|
||||
func (p *Processor) processInscriptionTx(ctx context.Context, tx *types.Transaction, blockHeader types.BlockHeader, transfersInOutPoints map[wire.OutPoint]map[ordinals.SatPoint][]*entity.InscriptionTransfer, outpointValues map[wire.OutPoint]uint64) error {
|
||||
ctx = logger.WithContext(ctx, slogx.String("tx_hash", tx.TxHash.String()))
|
||||
envelopes := ordinals.ParseEnvelopesFromTx(tx)
|
||||
inputOutPoints := lo.Map(tx.TxIn, func(txIn *types.TxIn, _ int) wire.OutPoint {
|
||||
return wire.OutPoint{
|
||||
Hash: txIn.PreviousOutTxHash,
|
||||
Index: txIn.PreviousOutIndex,
|
||||
}
|
||||
})
|
||||
// cache outpoint values for future blocks
|
||||
for outIndex, txOut := range tx.TxOut {
|
||||
outPoint := wire.OutPoint{
|
||||
Hash: tx.TxHash,
|
||||
Index: uint32(outIndex),
|
||||
}
|
||||
p.outPointValueCache.Add(outPoint, uint64(txOut.Value))
|
||||
outpointValues[outPoint] = uint64(txOut.Value)
|
||||
}
|
||||
outPointsWithTransfers := lo.Keys(transfersInOutPoints)
|
||||
txContainsTransfers := len(lo.Intersect(inputOutPoints, outPointsWithTransfers)) > 0
|
||||
isCoinbase := tx.TxIn[0].PreviousOutTxHash.IsEqual(&chainhash.Hash{})
|
||||
if len(envelopes) == 0 && !txContainsTransfers && !isCoinbase {
|
||||
// no inscription activity, skip
|
||||
return nil
|
||||
}
|
||||
|
||||
// Ensure outpoint values exists for all inputs. Some tx inputs may not be prefetched if it contains inscriptions transfers from other txs in the same block.
|
||||
if err := p.ensureOutPointValues(ctx, outpointValues, inputOutPoints); err != nil {
|
||||
return errors.Wrap(err, "failed to ensure outpoint values")
|
||||
}
|
||||
|
||||
floatingInscriptions := make([]*entity.Flotsam, 0)
|
||||
totalInputValue := uint64(0)
|
||||
totalOutputValue := lo.SumBy(tx.TxOut, func(txOut *types.TxOut) uint64 { return uint64(txOut.Value) })
|
||||
inscribeOffsets := make(map[uint64]*struct {
|
||||
inscriptionId ordinals.InscriptionId
|
||||
count int
|
||||
})
|
||||
idCounter := uint32(0)
|
||||
for i, input := range tx.TxIn {
|
||||
// skip coinbase inputs since there can't be an inscription in coinbase
|
||||
if input.PreviousOutTxHash.IsEqual(&chainhash.Hash{}) {
|
||||
totalInputValue += p.getBlockSubsidy(uint64(tx.BlockHeight))
|
||||
continue
|
||||
}
|
||||
inputOutPoint := wire.OutPoint{
|
||||
Hash: input.PreviousOutTxHash,
|
||||
Index: input.PreviousOutIndex,
|
||||
}
|
||||
inputValue, ok := outpointValues[inputOutPoint]
|
||||
if !ok {
|
||||
return errors.Wrapf(errs.NotFound, "outpoint value not found for %s", inputOutPoint.String())
|
||||
}
|
||||
|
||||
transfersInOutPoint := transfersInOutPoints[inputOutPoint]
|
||||
for satPoint, transfers := range transfersInOutPoint {
|
||||
offset := totalInputValue + satPoint.Offset
|
||||
for _, transfer := range transfers {
|
||||
floatingInscriptions = append(floatingInscriptions, &entity.Flotsam{
|
||||
Offset: offset,
|
||||
InscriptionId: transfer.InscriptionId,
|
||||
Tx: tx,
|
||||
OriginOld: &entity.OriginOld{
|
||||
OldSatPoint: satPoint,
|
||||
Content: transfer.Content,
|
||||
InputIndex: uint32(i),
|
||||
},
|
||||
})
|
||||
if _, ok := inscribeOffsets[offset]; !ok {
|
||||
inscribeOffsets[offset] = &struct {
|
||||
inscriptionId ordinals.InscriptionId
|
||||
count int
|
||||
}{transfer.InscriptionId, 0}
|
||||
}
|
||||
inscribeOffsets[offset].count++
|
||||
}
|
||||
}
|
||||
// offset on output to inscribe new inscriptions from this input
|
||||
offset := totalInputValue
|
||||
totalInputValue += inputValue
|
||||
|
||||
envelopesInInput := lo.Filter(envelopes, func(envelope *ordinals.Envelope, _ int) bool {
|
||||
return envelope.InputIndex == uint32(i)
|
||||
})
|
||||
for _, envelope := range envelopesInInput {
|
||||
inscriptionId := ordinals.InscriptionId{
|
||||
TxHash: tx.TxHash,
|
||||
Index: idCounter,
|
||||
}
|
||||
var cursed, cursedForBRC20 bool
|
||||
if envelope.UnrecognizedEvenField || // unrecognized even field
|
||||
envelope.DuplicateField || // duplicate field
|
||||
envelope.IncompleteField || // incomplete field
|
||||
envelope.InputIndex != 0 || // not first input
|
||||
envelope.Offset != 0 || // not first envelope in input
|
||||
envelope.Inscription.Pointer != nil || // contains pointer
|
||||
envelope.PushNum || // contains pushnum opcodes
|
||||
envelope.Stutter { // contains stuttering curse structure
|
||||
cursed = true
|
||||
cursedForBRC20 = true
|
||||
}
|
||||
if initial, ok := inscribeOffsets[offset]; !cursed && ok {
|
||||
if initial.count > 1 {
|
||||
cursed = true // reinscription
|
||||
cursedForBRC20 = true
|
||||
} else {
|
||||
initialInscriptionEntry, err := p.getInscriptionEntryById(ctx, initial.inscriptionId)
|
||||
if err != nil {
|
||||
return errors.Wrapf(err, "failed to get inscription entry id %s", initial.inscriptionId)
|
||||
}
|
||||
if !initialInscriptionEntry.Cursed {
|
||||
cursed = true // reinscription curse if initial inscription is not cursed
|
||||
}
|
||||
if !initialInscriptionEntry.CursedForBRC20 {
|
||||
cursedForBRC20 = true
|
||||
}
|
||||
}
|
||||
}
|
||||
// inscriptions are no longer cursed after jubilee, but BRC20 still considers them as cursed
|
||||
if cursed && uint64(tx.BlockHeight) >= ordinals.GetJubileeHeight(p.network) {
|
||||
cursed = false
|
||||
}
|
||||
|
||||
unbound := inputValue == 0 || envelope.UnrecognizedEvenField
|
||||
if envelope.Inscription.Pointer != nil && *envelope.Inscription.Pointer < totalOutputValue {
|
||||
offset = *envelope.Inscription.Pointer
|
||||
}
|
||||
|
||||
floatingInscriptions = append(floatingInscriptions, &entity.Flotsam{
|
||||
Offset: offset,
|
||||
InscriptionId: inscriptionId,
|
||||
Tx: tx,
|
||||
OriginNew: &entity.OriginNew{
|
||||
Reinscription: inscribeOffsets[offset] != nil,
|
||||
Cursed: cursed,
|
||||
CursedForBRC20: cursedForBRC20,
|
||||
Fee: 0,
|
||||
Hidden: false, // we don't care about this field for brc20
|
||||
Parent: envelope.Inscription.Parent,
|
||||
Pointer: envelope.Inscription.Pointer,
|
||||
Unbound: unbound,
|
||||
Inscription: envelope.Inscription,
|
||||
},
|
||||
})
|
||||
|
||||
if _, ok := inscribeOffsets[offset]; !ok {
|
||||
inscribeOffsets[offset] = &struct {
|
||||
inscriptionId ordinals.InscriptionId
|
||||
count int
|
||||
}{inscriptionId, 0}
|
||||
}
|
||||
inscribeOffsets[offset].count++
|
||||
idCounter++
|
||||
}
|
||||
}
|
||||
|
||||
// parents must exist in floatingInscriptions to be valid
|
||||
potentialParents := make(map[ordinals.InscriptionId]struct{})
|
||||
for _, flotsam := range floatingInscriptions {
|
||||
potentialParents[flotsam.InscriptionId] = struct{}{}
|
||||
}
|
||||
for _, flotsam := range floatingInscriptions {
|
||||
if flotsam.OriginNew != nil && flotsam.OriginNew.Parent != nil {
|
||||
if _, ok := potentialParents[*flotsam.OriginNew.Parent]; !ok {
|
||||
// parent not found, ignore parent
|
||||
flotsam.OriginNew.Parent = nil
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// calculate fee for each new inscription
|
||||
for _, flotsam := range floatingInscriptions {
|
||||
if flotsam.OriginNew != nil {
|
||||
flotsam.OriginNew.Fee = (totalInputValue - totalOutputValue) / uint64(idCounter)
|
||||
}
|
||||
}
|
||||
|
||||
// if tx is coinbase, add inscriptions sent as fee to outputs of this tx
|
||||
ownInscriptionCount := len(floatingInscriptions)
|
||||
if isCoinbase {
|
||||
floatingInscriptions = append(floatingInscriptions, p.flotsamsSentAsFee...)
|
||||
}
|
||||
// sort floatingInscriptions by offset
|
||||
slices.SortFunc(floatingInscriptions, func(i, j *entity.Flotsam) int {
|
||||
return int(i.Offset) - int(j.Offset)
|
||||
})
|
||||
|
||||
outputValue := uint64(0)
|
||||
curIncrIdx := 0
|
||||
// newLocations := make(map[ordinals.SatPoint][]*Flotsam)
|
||||
type location struct {
|
||||
satPoint ordinals.SatPoint
|
||||
flotsam *entity.Flotsam
|
||||
sentAsFee bool
|
||||
}
|
||||
newLocations := make([]*location, 0)
|
||||
outputToSumValue := make([]uint64, 0, len(tx.TxOut))
|
||||
for outIndex, txOut := range tx.TxOut {
|
||||
end := outputValue + uint64(txOut.Value)
|
||||
|
||||
// process all inscriptions that are supposed to be inscribed in this output
|
||||
for curIncrIdx < len(floatingInscriptions) && floatingInscriptions[curIncrIdx].Offset < end {
|
||||
newSatPoint := ordinals.SatPoint{
|
||||
OutPoint: wire.OutPoint{
|
||||
Hash: tx.TxHash,
|
||||
Index: uint32(outIndex),
|
||||
},
|
||||
Offset: floatingInscriptions[curIncrIdx].Offset - outputValue,
|
||||
}
|
||||
// newLocations[newSatPoint] = append(newLocations[newSatPoint], floatingInscriptions[curIncrIdx])
|
||||
newLocations = append(newLocations, &location{
|
||||
satPoint: newSatPoint,
|
||||
flotsam: floatingInscriptions[curIncrIdx],
|
||||
sentAsFee: isCoinbase && curIncrIdx >= ownInscriptionCount, // if curIncrIdx >= ownInscriptionCount, then current inscription came from p.flotSamsSentAsFee
|
||||
})
|
||||
curIncrIdx++
|
||||
}
|
||||
|
||||
outputValue = end
|
||||
outputToSumValue = append(outputToSumValue, outputValue)
|
||||
}
|
||||
|
||||
for _, loc := range newLocations {
|
||||
satPoint := loc.satPoint
|
||||
flotsam := loc.flotsam
|
||||
sentAsFee := loc.sentAsFee
|
||||
// TODO: not sure if we still need to handle pointer here, it's already handled above.
|
||||
if flotsam.OriginNew != nil && flotsam.OriginNew.Pointer != nil {
|
||||
pointer := *flotsam.OriginNew.Pointer
|
||||
for outIndex, outputValue := range outputToSumValue {
|
||||
start := uint64(0)
|
||||
if outIndex > 0 {
|
||||
start = outputToSumValue[outIndex-1]
|
||||
}
|
||||
end := outputValue
|
||||
if start <= pointer && pointer < end {
|
||||
satPoint.Offset = pointer - start
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
if err := p.updateInscriptionLocation(ctx, satPoint, flotsam, sentAsFee, tx, blockHeader, transfersInOutPoints); err != nil {
|
||||
return errors.Wrap(err, "failed to update inscription location")
|
||||
}
|
||||
}
|
||||
|
||||
// handle leftover flotsams (flotsams with offset over total output value) )
|
||||
if isCoinbase {
|
||||
// if there are leftover inscriptions in coinbase, they are lost permanently
|
||||
for _, flotsam := range floatingInscriptions[curIncrIdx:] {
|
||||
newSatPoint := ordinals.SatPoint{
|
||||
OutPoint: wire.OutPoint{},
|
||||
Offset: p.lostSats + flotsam.Offset - totalOutputValue,
|
||||
}
|
||||
if err := p.updateInscriptionLocation(ctx, newSatPoint, flotsam, false, tx, blockHeader, transfersInOutPoints); err != nil {
|
||||
return errors.Wrap(err, "failed to update inscription location")
|
||||
}
|
||||
}
|
||||
p.lostSats += p.blockReward - totalOutputValue
|
||||
} else {
|
||||
// if there are leftover inscriptions in non-coinbase tx, they are stored in p.flotsamsSentAsFee for processing in this block's coinbase tx
|
||||
for _, flotsam := range floatingInscriptions[curIncrIdx:] {
|
||||
flotsam.Offset = p.blockReward + flotsam.Offset - totalOutputValue
|
||||
p.flotsamsSentAsFee = append(p.flotsamsSentAsFee, flotsam)
|
||||
}
|
||||
// add fees to block reward
|
||||
p.blockReward = totalInputValue - totalOutputValue
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (p *Processor) updateInscriptionLocation(ctx context.Context, newSatPoint ordinals.SatPoint, flotsam *entity.Flotsam, sentAsFee bool, tx *types.Transaction, blockHeader types.BlockHeader, transfersInOutPoints map[wire.OutPoint]map[ordinals.SatPoint][]*entity.InscriptionTransfer) error {
|
||||
txOut := tx.TxOut[newSatPoint.OutPoint.Index]
|
||||
if flotsam.OriginOld != nil {
|
||||
entry, err := p.getInscriptionEntryById(ctx, flotsam.InscriptionId)
|
||||
if err != nil {
|
||||
return errors.Wrapf(err, "failed to get inscription entry id %s", flotsam.InscriptionId)
|
||||
}
|
||||
entry.TransferCount++
|
||||
transfer := &entity.InscriptionTransfer{
|
||||
InscriptionId: flotsam.InscriptionId,
|
||||
InscriptionNumber: entry.Number,
|
||||
InscriptionSequenceNumber: entry.SequenceNumber,
|
||||
BlockHeight: uint64(flotsam.Tx.BlockHeight), // use flotsam's tx to track tx that initiated the transfer
|
||||
TxIndex: flotsam.Tx.Index, // use flotsam's tx to track tx that initiated the transfer
|
||||
TxHash: flotsam.Tx.TxHash,
|
||||
Content: flotsam.OriginOld.Content,
|
||||
FromInputIndex: flotsam.OriginOld.InputIndex,
|
||||
OldSatPoint: flotsam.OriginOld.OldSatPoint,
|
||||
NewSatPoint: newSatPoint,
|
||||
NewPkScript: txOut.PkScript,
|
||||
NewOutputValue: uint64(txOut.Value),
|
||||
SentAsFee: sentAsFee,
|
||||
TransferCount: entry.TransferCount,
|
||||
}
|
||||
|
||||
// track transfers even if transfer count exceeds 2 (because we need to check for reinscriptions)
|
||||
p.newInscriptionTransfers = append(p.newInscriptionTransfers, transfer)
|
||||
p.newInscriptionEntryStates[entry.Id] = entry
|
||||
|
||||
// add new transfer to transfersInOutPoints cache
|
||||
if _, ok := transfersInOutPoints[newSatPoint.OutPoint]; !ok {
|
||||
transfersInOutPoints[newSatPoint.OutPoint] = make(map[ordinals.SatPoint][]*entity.InscriptionTransfer)
|
||||
}
|
||||
transfersInOutPoints[newSatPoint.OutPoint][newSatPoint] = append(transfersInOutPoints[newSatPoint.OutPoint][newSatPoint], transfer)
|
||||
return nil
|
||||
}
|
||||
|
||||
if flotsam.OriginNew != nil {
|
||||
origin := flotsam.OriginNew
|
||||
var inscriptionNumber int64
|
||||
sequenceNumber := p.cursedInscriptionCount + p.blessedInscriptionCount
|
||||
if origin.Cursed {
|
||||
inscriptionNumber = -int64(p.cursedInscriptionCount + 1)
|
||||
p.cursedInscriptionCount++
|
||||
} else {
|
||||
inscriptionNumber = int64(p.blessedInscriptionCount)
|
||||
p.blessedInscriptionCount++
|
||||
}
|
||||
// if not valid brc20 inscription, delete content to save space
|
||||
if !isBRC20Inscription(origin.Inscription) {
|
||||
origin.Inscription.Content = nil
|
||||
origin.Inscription.ContentType = ""
|
||||
origin.Inscription.ContentEncoding = ""
|
||||
}
|
||||
transfer := &entity.InscriptionTransfer{
|
||||
InscriptionId: flotsam.InscriptionId,
|
||||
InscriptionNumber: inscriptionNumber,
|
||||
InscriptionSequenceNumber: sequenceNumber,
|
||||
BlockHeight: uint64(flotsam.Tx.BlockHeight), // use flotsam's tx to track tx that initiated the transfer
|
||||
TxIndex: flotsam.Tx.Index, // use flotsam's tx to track tx that initiated the transfer
|
||||
TxHash: flotsam.Tx.TxHash,
|
||||
Content: origin.Inscription.Content,
|
||||
FromInputIndex: 0, // unused
|
||||
OldSatPoint: ordinals.SatPoint{},
|
||||
NewSatPoint: newSatPoint,
|
||||
NewPkScript: txOut.PkScript,
|
||||
NewOutputValue: uint64(txOut.Value),
|
||||
SentAsFee: sentAsFee,
|
||||
TransferCount: 1, // count inscription as first transfer
|
||||
}
|
||||
entry := &ordinals.InscriptionEntry{
|
||||
Id: flotsam.InscriptionId,
|
||||
Number: inscriptionNumber,
|
||||
SequenceNumber: sequenceNumber,
|
||||
Cursed: origin.Cursed,
|
||||
CursedForBRC20: origin.CursedForBRC20,
|
||||
CreatedAt: blockHeader.Timestamp,
|
||||
CreatedAtHeight: uint64(blockHeader.Height),
|
||||
Inscription: origin.Inscription,
|
||||
TransferCount: 1, // count inscription as first transfer
|
||||
}
|
||||
p.newInscriptionTransfers = append(p.newInscriptionTransfers, transfer)
|
||||
p.newInscriptionEntries[entry.Id] = entry
|
||||
p.newInscriptionEntryStates[entry.Id] = entry
|
||||
|
||||
// add new transfer to transfersInOutPoints cache
|
||||
if _, ok := transfersInOutPoints[newSatPoint.OutPoint]; !ok {
|
||||
transfersInOutPoints[newSatPoint.OutPoint] = make(map[ordinals.SatPoint][]*entity.InscriptionTransfer)
|
||||
}
|
||||
transfersInOutPoints[newSatPoint.OutPoint][newSatPoint] = append(transfersInOutPoints[newSatPoint.OutPoint][newSatPoint], transfer)
|
||||
return nil
|
||||
}
|
||||
panic("unreachable")
|
||||
}
|
||||
|
||||
func (p *Processor) ensureOutPointValues(ctx context.Context, outPointValues map[wire.OutPoint]uint64, outPoints []wire.OutPoint) error {
|
||||
missingOutPoints := make([]wire.OutPoint, 0)
|
||||
for _, outPoint := range outPoints {
|
||||
if _, ok := outPointValues[outPoint]; !ok {
|
||||
missingOutPoints = append(missingOutPoints, outPoint)
|
||||
}
|
||||
}
|
||||
if len(missingOutPoints) == 0 {
|
||||
return nil
|
||||
}
|
||||
missingOutPointValues, err := p.getOutPointValues(ctx, missingOutPoints)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "failed to get outpoint values")
|
||||
}
|
||||
for outPoint, value := range missingOutPointValues {
|
||||
outPointValues[outPoint] = value
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
type brc20Inscription struct {
|
||||
P string `json:"p"`
|
||||
}
|
||||
|
||||
func isBRC20Inscription(inscription ordinals.Inscription) bool {
|
||||
if inscription.ContentType != "application/json" && inscription.ContentType != "text/plain" {
|
||||
return false
|
||||
}
|
||||
|
||||
// attempt to parse content as json
|
||||
if inscription.Content == nil {
|
||||
return false
|
||||
}
|
||||
var parsed brc20Inscription
|
||||
if err := json.Unmarshal(inscription.Content, &parsed); err != nil {
|
||||
return false
|
||||
}
|
||||
if parsed.P != "brc-20" {
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
func (p *Processor) getOutPointValues(ctx context.Context, outPoints []wire.OutPoint) (map[wire.OutPoint]uint64, error) {
|
||||
// try to get from cache if exists
|
||||
cacheValues := p.outPointValueCache.MGet(outPoints)
|
||||
result := make(map[wire.OutPoint]uint64)
|
||||
|
||||
outPointsToFetch := make([]wire.OutPoint, 0)
|
||||
for i, outPoint := range outPoints {
|
||||
if outPoint.Hash == (chainhash.Hash{}) {
|
||||
// skip coinbase input
|
||||
continue
|
||||
}
|
||||
if cacheValues[i] != 0 {
|
||||
result[outPoint] = cacheValues[i]
|
||||
} else {
|
||||
outPointsToFetch = append(outPointsToFetch, outPoint)
|
||||
}
|
||||
}
|
||||
eg, ectx := errgroup.WithContext(ctx)
|
||||
txHashes := make(map[chainhash.Hash]struct{})
|
||||
for _, outPoint := range outPointsToFetch {
|
||||
txHashes[outPoint.Hash] = struct{}{}
|
||||
}
|
||||
txOutsByHash := make(map[chainhash.Hash][]*types.TxOut)
|
||||
var mutex sync.Mutex
|
||||
for txHash := range txHashes {
|
||||
txHash := txHash
|
||||
eg.Go(func() error {
|
||||
txOuts, err := p.btcClient.GetTransactionOutputs(ectx, txHash)
|
||||
if err != nil {
|
||||
return errors.Wrapf(err, "failed to get transaction outputs for hash %s", txHash)
|
||||
}
|
||||
|
||||
// update cache
|
||||
mutex.Lock()
|
||||
defer mutex.Unlock()
|
||||
|
||||
txOutsByHash[txHash] = txOuts
|
||||
for i, txOut := range txOuts {
|
||||
p.outPointValueCache.Add(wire.OutPoint{Hash: txHash, Index: uint32(i)}, uint64(txOut.Value))
|
||||
}
|
||||
return nil
|
||||
})
|
||||
}
|
||||
if err := eg.Wait(); err != nil {
|
||||
return nil, errors.WithStack(err)
|
||||
}
|
||||
for i := range outPoints {
|
||||
if outPoints[i].Hash == (chainhash.Hash{}) {
|
||||
// skip coinbase input
|
||||
continue
|
||||
}
|
||||
if result[outPoints[i]] == 0 {
|
||||
result[outPoints[i]] = uint64(txOutsByHash[outPoints[i].Hash][outPoints[i].Index].Value)
|
||||
}
|
||||
}
|
||||
return result, nil
|
||||
}
|
||||
|
||||
func (p *Processor) getInscriptionTransfersInOutPoints(ctx context.Context, outPoints []wire.OutPoint) (map[wire.OutPoint]map[ordinals.SatPoint][]*entity.InscriptionTransfer, error) {
|
||||
outPoints = lo.Uniq(outPoints)
|
||||
// try to get from flush buffer if exists
|
||||
result := make(map[wire.OutPoint]map[ordinals.SatPoint][]*entity.InscriptionTransfer)
|
||||
|
||||
outPointsToFetch := make([]wire.OutPoint, 0)
|
||||
for _, outPoint := range outPoints {
|
||||
var found bool
|
||||
for _, transfer := range p.newInscriptionTransfers {
|
||||
if transfer.NewSatPoint.OutPoint == outPoint {
|
||||
found = true
|
||||
if _, ok := result[outPoint]; !ok {
|
||||
result[outPoint] = make(map[ordinals.SatPoint][]*entity.InscriptionTransfer)
|
||||
}
|
||||
result[outPoint][transfer.NewSatPoint] = append(result[outPoint][transfer.NewSatPoint], transfer)
|
||||
}
|
||||
}
|
||||
if !found {
|
||||
outPointsToFetch = append(outPointsToFetch, outPoint)
|
||||
}
|
||||
}
|
||||
|
||||
transfers, err := p.brc20Dg.GetInscriptionTransfersInOutPoints(ctx, outPointsToFetch)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "failed to get inscriptions by outpoint")
|
||||
}
|
||||
|
||||
for satPoint, transferList := range transfers {
|
||||
if _, ok := result[satPoint.OutPoint]; !ok {
|
||||
result[satPoint.OutPoint] = make(map[ordinals.SatPoint][]*entity.InscriptionTransfer)
|
||||
}
|
||||
result[satPoint.OutPoint][satPoint] = append(result[satPoint.OutPoint][satPoint], transferList...)
|
||||
}
|
||||
for _, transfersBySatPoint := range result {
|
||||
for satPoint := range transfersBySatPoint {
|
||||
// sort all transfers by sequence number
|
||||
slices.SortFunc(transfersBySatPoint[satPoint], func(i, j *entity.InscriptionTransfer) int {
|
||||
return int(i.InscriptionSequenceNumber) - int(j.InscriptionSequenceNumber)
|
||||
})
|
||||
}
|
||||
}
|
||||
return result, nil
|
||||
}
|
||||
|
||||
func (p *Processor) getInscriptionEntryById(ctx context.Context, id ordinals.InscriptionId) (*ordinals.InscriptionEntry, error) {
|
||||
inscriptions, err := p.getInscriptionEntriesByIds(ctx, []ordinals.InscriptionId{id})
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "failed to get inscriptions by outpoint")
|
||||
}
|
||||
inscription, ok := inscriptions[id]
|
||||
if !ok {
|
||||
return nil, errors.Wrap(errs.NotFound, "inscription not found")
|
||||
}
|
||||
return inscription, nil
|
||||
}
|
||||
|
||||
func (p *Processor) getInscriptionEntriesByIds(ctx context.Context, ids []ordinals.InscriptionId) (map[ordinals.InscriptionId]*ordinals.InscriptionEntry, error) {
|
||||
// try to get from cache if exists
|
||||
result := make(map[ordinals.InscriptionId]*ordinals.InscriptionEntry)
|
||||
|
||||
idsToFetch := make([]ordinals.InscriptionId, 0)
|
||||
for _, id := range ids {
|
||||
if inscriptionEntry, ok := p.newInscriptionEntryStates[id]; ok {
|
||||
result[id] = inscriptionEntry
|
||||
} else {
|
||||
idsToFetch = append(idsToFetch, id)
|
||||
}
|
||||
}
|
||||
|
||||
if len(idsToFetch) > 0 {
|
||||
inscriptions, err := p.brc20Dg.GetInscriptionEntriesByIds(ctx, idsToFetch)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "failed to get inscriptions by outpoint")
|
||||
}
|
||||
for id, inscription := range inscriptions {
|
||||
result[id] = inscription
|
||||
}
|
||||
}
|
||||
return result, nil
|
||||
}
|
||||
|
||||
func (p *Processor) getInscriptionNumbersByIds(ctx context.Context, ids []ordinals.InscriptionId) (map[ordinals.InscriptionId]int64, error) {
|
||||
// try to get from cache if exists
|
||||
result := make(map[ordinals.InscriptionId]int64)
|
||||
|
||||
idsToFetch := make([]ordinals.InscriptionId, 0)
|
||||
for _, id := range ids {
|
||||
if entry, ok := p.newInscriptionEntryStates[id]; ok {
|
||||
result[id] = int64(entry.Number)
|
||||
} else {
|
||||
idsToFetch = append(idsToFetch, id)
|
||||
}
|
||||
}
|
||||
|
||||
if len(idsToFetch) > 0 {
|
||||
inscriptions, err := p.brc20Dg.GetInscriptionNumbersByIds(ctx, idsToFetch)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "failed to get inscriptions by outpoint")
|
||||
}
|
||||
for id, number := range inscriptions {
|
||||
result[id] = number
|
||||
}
|
||||
}
|
||||
return result, nil
|
||||
}
|
||||
|
||||
func (p *Processor) getInscriptionParentsByIds(ctx context.Context, ids []ordinals.InscriptionId) (map[ordinals.InscriptionId]ordinals.InscriptionId, error) {
|
||||
// try to get from cache if exists
|
||||
result := make(map[ordinals.InscriptionId]ordinals.InscriptionId)
|
||||
|
||||
idsToFetch := make([]ordinals.InscriptionId, 0)
|
||||
for _, id := range ids {
|
||||
if entry, ok := p.newInscriptionEntryStates[id]; ok {
|
||||
if entry.Inscription.Parent != nil {
|
||||
result[id] = *entry.Inscription.Parent
|
||||
}
|
||||
} else {
|
||||
idsToFetch = append(idsToFetch, id)
|
||||
}
|
||||
}
|
||||
|
||||
if len(idsToFetch) > 0 {
|
||||
inscriptions, err := p.brc20Dg.GetInscriptionParentsByIds(ctx, idsToFetch)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "failed to get inscriptions by outpoint")
|
||||
}
|
||||
for id, parent := range inscriptions {
|
||||
result[id] = parent
|
||||
}
|
||||
}
|
||||
return result, nil
|
||||
}
|
||||
|
||||
func (p *Processor) getBlockSubsidy(blockHeight uint64) uint64 {
|
||||
return uint64(blockchain.CalcBlockSubsidy(int32(blockHeight), p.network.ChainParams()))
|
||||
}
|
||||
@@ -1,271 +0,0 @@
|
||||
package brc20
|
||||
|
||||
import (
|
||||
"context"
|
||||
"crypto/sha256"
|
||||
"encoding/hex"
|
||||
"slices"
|
||||
|
||||
"github.com/btcsuite/btcd/chaincfg/chainhash"
|
||||
"github.com/btcsuite/btcd/wire"
|
||||
"github.com/cockroachdb/errors"
|
||||
"github.com/gaze-network/indexer-network/common/errs"
|
||||
"github.com/gaze-network/indexer-network/core/types"
|
||||
"github.com/gaze-network/indexer-network/modules/brc20/internal/entity"
|
||||
"github.com/gaze-network/indexer-network/modules/brc20/internal/ordinals"
|
||||
"github.com/gaze-network/indexer-network/pkg/logger"
|
||||
"github.com/gaze-network/indexer-network/pkg/logger/slogx"
|
||||
"github.com/samber/lo"
|
||||
)
|
||||
|
||||
// Process implements indexer.Processor.
|
||||
func (p *Processor) Process(ctx context.Context, blocks []*types.Block) error {
|
||||
for _, block := range blocks {
|
||||
ctx = logger.WithContext(ctx, slogx.Uint64("height", uint64(block.Header.Height)))
|
||||
p.blockReward = p.getBlockSubsidy(uint64(block.Header.Height))
|
||||
p.flotsamsSentAsFee = make([]*entity.Flotsam, 0)
|
||||
|
||||
// put coinbase tx (first tx) at the end of block
|
||||
transactions := append(block.Transactions[1:], block.Transactions[0])
|
||||
|
||||
var inputOutPoints []wire.OutPoint
|
||||
for _, tx := range transactions {
|
||||
for _, txIn := range tx.TxIn {
|
||||
if txIn.PreviousOutTxHash == (chainhash.Hash{}) {
|
||||
// skip coinbase input
|
||||
continue
|
||||
}
|
||||
inputOutPoints = append(inputOutPoints, wire.OutPoint{
|
||||
Hash: txIn.PreviousOutTxHash,
|
||||
Index: txIn.PreviousOutIndex,
|
||||
})
|
||||
}
|
||||
}
|
||||
// pre-fetch inscriptions in outpoints
|
||||
transfersInOutPoints, err := p.getInscriptionTransfersInOutPoints(ctx, inputOutPoints)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "failed to get inscriptions in outpoints")
|
||||
}
|
||||
// pre-fetch outpoint values for transactions with inscriptions/envelopes
|
||||
outPointsToFetchValues := make([]wire.OutPoint, 0)
|
||||
for _, tx := range transactions {
|
||||
txInputOutPoints := lo.Map(tx.TxIn, func(txIn *types.TxIn, _ int) wire.OutPoint {
|
||||
return wire.OutPoint{
|
||||
Hash: txIn.PreviousOutTxHash,
|
||||
Index: txIn.PreviousOutIndex,
|
||||
}
|
||||
})
|
||||
envelopes := ordinals.ParseEnvelopesFromTx(tx)
|
||||
outPointsWithTransfers := lo.Keys(transfersInOutPoints)
|
||||
txContainsTransfers := len(lo.Intersect(txInputOutPoints, outPointsWithTransfers)) > 0
|
||||
isCoinbase := tx.TxIn[0].PreviousOutTxHash.IsEqual(&chainhash.Hash{})
|
||||
if len(envelopes) == 0 && !txContainsTransfers && !isCoinbase {
|
||||
// no inscription activity, skip tx
|
||||
continue
|
||||
}
|
||||
outPointsToFetchValues = append(outPointsToFetchValues, lo.Map(tx.TxIn, func(txIn *types.TxIn, _ int) wire.OutPoint {
|
||||
return wire.OutPoint{
|
||||
Hash: txIn.PreviousOutTxHash,
|
||||
Index: txIn.PreviousOutIndex,
|
||||
}
|
||||
})...)
|
||||
}
|
||||
outPointValues, err := p.getOutPointValues(ctx, outPointsToFetchValues)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "failed to get input values")
|
||||
}
|
||||
|
||||
for _, tx := range transactions {
|
||||
if err := p.processInscriptionTx(ctx, tx, block.Header, transfersInOutPoints, outPointValues); err != nil {
|
||||
return errors.Wrap(err, "failed to process tx")
|
||||
}
|
||||
}
|
||||
|
||||
// sort transfers by tx index, output index, output sat offset
|
||||
// NOTE: ord indexes inscription transfers spent as fee at the end of the block, but brc20 indexes them as soon as they are sent
|
||||
slices.SortFunc(p.newInscriptionTransfers, func(t1, t2 *entity.InscriptionTransfer) int {
|
||||
if t1.TxIndex != t2.TxIndex {
|
||||
return int(t1.TxIndex) - int(t2.TxIndex)
|
||||
}
|
||||
if t1.SentAsFee != t2.SentAsFee {
|
||||
// transfers sent as fee should be ordered after non-fees
|
||||
if t1.SentAsFee {
|
||||
return 1
|
||||
}
|
||||
return -1
|
||||
}
|
||||
if t1.NewSatPoint.OutPoint.Index != t2.NewSatPoint.OutPoint.Index {
|
||||
return int(t1.NewSatPoint.OutPoint.Index) - int(t2.NewSatPoint.OutPoint.Index)
|
||||
}
|
||||
return int(t1.NewSatPoint.Offset) - int(t2.NewSatPoint.Offset)
|
||||
})
|
||||
|
||||
if err := p.processBRC20States(ctx, p.newInscriptionTransfers, block.Header); err != nil {
|
||||
return errors.Wrap(err, "failed to process brc20 states")
|
||||
}
|
||||
|
||||
if err := p.flushBlock(ctx, block.Header); err != nil {
|
||||
return errors.Wrap(err, "failed to flush block")
|
||||
}
|
||||
|
||||
logger.DebugContext(ctx, "Inserted new block")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (p *Processor) flushBlock(ctx context.Context, blockHeader types.BlockHeader) error {
|
||||
brc20DgTx, err := p.brc20Dg.BeginBRC20Tx(ctx)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "failed to begin transaction")
|
||||
}
|
||||
defer func() {
|
||||
if err := brc20DgTx.Rollback(ctx); err != nil {
|
||||
logger.WarnContext(ctx, "failed to rollback transaction",
|
||||
slogx.Error(err),
|
||||
slogx.String("event", "rollback_brc20_insertion"),
|
||||
)
|
||||
}
|
||||
}()
|
||||
|
||||
blockHeight := uint64(blockHeader.Height)
|
||||
|
||||
// calculate event hash
|
||||
{
|
||||
eventHashString := p.eventHashString
|
||||
if len(eventHashString) > 0 && eventHashString[len(eventHashString)-1:] == eventHashSeparator {
|
||||
eventHashString = eventHashString[:len(eventHashString)-1]
|
||||
}
|
||||
eventHash := sha256.Sum256([]byte(eventHashString))
|
||||
prevIndexedBlock, err := brc20DgTx.GetIndexedBlockByHeight(ctx, blockHeader.Height-1)
|
||||
if err != nil && errors.Is(err, errs.NotFound) && blockHeader.Height-1 == startingBlockHeader[p.network].Height {
|
||||
prevIndexedBlock = &entity.IndexedBlock{
|
||||
Height: uint64(startingBlockHeader[p.network].Height),
|
||||
Hash: startingBlockHeader[p.network].Hash,
|
||||
EventHash: []byte{},
|
||||
CumulativeEventHash: []byte{},
|
||||
}
|
||||
err = nil
|
||||
}
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "failed to get previous indexed block")
|
||||
}
|
||||
var cumulativeEventHash [32]byte
|
||||
if len(prevIndexedBlock.CumulativeEventHash) == 0 {
|
||||
cumulativeEventHash = eventHash
|
||||
} else {
|
||||
cumulativeEventHash = sha256.Sum256([]byte(hex.EncodeToString(prevIndexedBlock.CumulativeEventHash[:]) + hex.EncodeToString(eventHash[:])))
|
||||
}
|
||||
if err := brc20DgTx.CreateIndexedBlock(ctx, &entity.IndexedBlock{
|
||||
Height: blockHeight,
|
||||
Hash: blockHeader.Hash,
|
||||
EventHash: eventHash[:],
|
||||
CumulativeEventHash: cumulativeEventHash[:],
|
||||
}); err != nil {
|
||||
return errors.Wrap(err, "failed to create indexed block")
|
||||
}
|
||||
p.eventHashString = ""
|
||||
}
|
||||
|
||||
// flush new inscription entries
|
||||
{
|
||||
newInscriptionEntries := lo.Values(p.newInscriptionEntries)
|
||||
if err := brc20DgTx.CreateInscriptionEntries(ctx, blockHeight, newInscriptionEntries); err != nil {
|
||||
return errors.Wrap(err, "failed to create inscription entries")
|
||||
}
|
||||
p.newInscriptionEntries = make(map[ordinals.InscriptionId]*ordinals.InscriptionEntry)
|
||||
}
|
||||
|
||||
// flush new inscription entry states
|
||||
{
|
||||
newInscriptionEntryStates := lo.Values(p.newInscriptionEntryStates)
|
||||
if err := brc20DgTx.CreateInscriptionEntryStates(ctx, blockHeight, newInscriptionEntryStates); err != nil {
|
||||
return errors.Wrap(err, "failed to create inscription entry states")
|
||||
}
|
||||
p.newInscriptionEntryStates = make(map[ordinals.InscriptionId]*ordinals.InscriptionEntry)
|
||||
}
|
||||
|
||||
// flush new inscription entry states
|
||||
{
|
||||
if err := brc20DgTx.CreateInscriptionTransfers(ctx, p.newInscriptionTransfers); err != nil {
|
||||
return errors.Wrap(err, "failed to create inscription transfers")
|
||||
}
|
||||
p.newInscriptionTransfers = make([]*entity.InscriptionTransfer, 0)
|
||||
}
|
||||
|
||||
// flush processor stats
|
||||
{
|
||||
stats := &entity.ProcessorStats{
|
||||
BlockHeight: blockHeight,
|
||||
CursedInscriptionCount: p.cursedInscriptionCount,
|
||||
BlessedInscriptionCount: p.blessedInscriptionCount,
|
||||
LostSats: p.lostSats,
|
||||
}
|
||||
if err := brc20DgTx.CreateProcessorStats(ctx, stats); err != nil {
|
||||
return errors.Wrap(err, "failed to create processor stats")
|
||||
}
|
||||
}
|
||||
// newTickEntries map[string]*entity.TickEntry
|
||||
// newTickEntryStates map[string]*entity.TickEntry
|
||||
// newEventDeploys []*entity.EventDeploy
|
||||
// newEventMints []*entity.EventMint
|
||||
// newEventInscribeTransfers []*entity.EventInscribeTransfer
|
||||
// newEventTransferTransfers []*entity.EventTransferTransfer
|
||||
// newBalances map[string]map[string]*entity.Balance
|
||||
|
||||
// flush new tick entries
|
||||
{
|
||||
newTickEntries := lo.Values(p.newTickEntries)
|
||||
if err := brc20DgTx.CreateTickEntries(ctx, blockHeight, newTickEntries); err != nil {
|
||||
return errors.Wrap(err, "failed to create tick entries")
|
||||
}
|
||||
p.newTickEntries = make(map[string]*entity.TickEntry)
|
||||
}
|
||||
|
||||
// flush new tick entry states
|
||||
{
|
||||
newTickEntryStates := lo.Values(p.newTickEntryStates)
|
||||
if err := brc20DgTx.CreateTickEntryStates(ctx, blockHeight, newTickEntryStates); err != nil {
|
||||
return errors.Wrap(err, "failed to create tick entry states")
|
||||
}
|
||||
p.newTickEntryStates = make(map[string]*entity.TickEntry)
|
||||
}
|
||||
|
||||
// flush new events
|
||||
{
|
||||
if err := brc20DgTx.CreateEventDeploys(ctx, p.newEventDeploys); err != nil {
|
||||
return errors.Wrap(err, "failed to create event deploys")
|
||||
}
|
||||
if err := brc20DgTx.CreateEventMints(ctx, p.newEventMints); err != nil {
|
||||
return errors.Wrap(err, "failed to create event mints")
|
||||
}
|
||||
if err := brc20DgTx.CreateEventInscribeTransfers(ctx, p.newEventInscribeTransfers); err != nil {
|
||||
return errors.Wrap(err, "failed to create event inscribe transfers")
|
||||
}
|
||||
if err := brc20DgTx.CreateEventTransferTransfers(ctx, p.newEventTransferTransfers); err != nil {
|
||||
return errors.Wrap(err, "failed to create event transfer transfers")
|
||||
}
|
||||
p.newEventDeploys = make([]*entity.EventDeploy, 0)
|
||||
p.newEventMints = make([]*entity.EventMint, 0)
|
||||
p.newEventInscribeTransfers = make([]*entity.EventInscribeTransfer, 0)
|
||||
p.newEventTransferTransfers = make([]*entity.EventTransferTransfer, 0)
|
||||
}
|
||||
|
||||
// flush new balances
|
||||
{
|
||||
newBalances := make([]*entity.Balance, 0)
|
||||
for _, tickBalances := range p.newBalances {
|
||||
for _, balance := range tickBalances {
|
||||
newBalances = append(newBalances, balance)
|
||||
}
|
||||
}
|
||||
if err := brc20DgTx.CreateBalances(ctx, newBalances); err != nil {
|
||||
return errors.Wrap(err, "failed to create balances")
|
||||
}
|
||||
p.newBalances = make(map[string]map[string]*entity.Balance)
|
||||
}
|
||||
|
||||
if err := brc20DgTx.Commit(ctx); err != nil {
|
||||
return errors.Wrap(err, "failed to commit transaction")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
99
modules/nodesale/api/httphandler/deploy.go
Normal file
99
modules/nodesale/api/httphandler/deploy.go
Normal file
@@ -0,0 +1,99 @@
|
||||
package httphandler
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/cockroachdb/errors"
|
||||
"github.com/gaze-network/indexer-network/common/errs"
|
||||
"github.com/gaze-network/indexer-network/modules/nodesale/datagateway"
|
||||
"github.com/gaze-network/indexer-network/modules/nodesale/protobuf"
|
||||
"github.com/gofiber/fiber/v2"
|
||||
"google.golang.org/protobuf/encoding/protojson"
|
||||
)
|
||||
|
||||
type deployRequest struct {
|
||||
DeployID string `params:"deployId"`
|
||||
}
|
||||
|
||||
type tierResponse struct {
|
||||
PriceSat uint32 `json:"priceSat"`
|
||||
Limit uint32 `json:"limit"`
|
||||
MaxPerAddress uint32 `json:"maxPerAddress"`
|
||||
Sold int64 `json:"sold"`
|
||||
}
|
||||
|
||||
type deployResponse struct {
|
||||
Id string `json:"id"`
|
||||
Name string `json:"name"`
|
||||
StartsAt int64 `json:"startsAt"`
|
||||
EndsAt int64 `json:"endsAt"`
|
||||
Tiers []tierResponse `json:"tiers"`
|
||||
SellerPublicKey string `json:"sellerPublicKey"`
|
||||
MaxPerAddress uint32 `json:"maxPerAddress"`
|
||||
DeployTxHash string `json:"deployTxHash"`
|
||||
}
|
||||
|
||||
func (h *handler) deployHandler(ctx *fiber.Ctx) error {
|
||||
var request deployRequest
|
||||
err := ctx.ParamsParser(&request)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "cannot parse param")
|
||||
}
|
||||
var blockHeight uint64
|
||||
var txIndex uint32
|
||||
count, err := fmt.Sscanf(request.DeployID, "%d-%d", &blockHeight, &txIndex)
|
||||
if count != 2 || err != nil {
|
||||
return errs.NewPublicError("Invalid deploy ID")
|
||||
}
|
||||
deploys, err := h.nodeSaleDg.GetNodeSale(ctx.UserContext(), datagateway.GetNodeSaleParams{
|
||||
BlockHeight: blockHeight,
|
||||
TxIndex: txIndex,
|
||||
})
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "Cannot get NodeSale from db")
|
||||
}
|
||||
if len(deploys) < 1 {
|
||||
return errs.NewPublicError("NodeSale not found")
|
||||
}
|
||||
|
||||
deploy := deploys[0]
|
||||
|
||||
nodeCount, err := h.nodeSaleDg.GetNodeCountByTierIndex(ctx.UserContext(), datagateway.GetNodeCountByTierIndexParams{
|
||||
SaleBlock: deploy.BlockHeight,
|
||||
SaleTxIndex: deploy.TxIndex,
|
||||
FromTier: 0,
|
||||
ToTier: uint32(len(deploy.Tiers) - 1),
|
||||
})
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "Cannot get node count from db")
|
||||
}
|
||||
|
||||
tiers := make([]protobuf.Tier, len(deploy.Tiers))
|
||||
tierResponses := make([]tierResponse, len(deploy.Tiers))
|
||||
for i, tierJson := range deploy.Tiers {
|
||||
tier := &tiers[i]
|
||||
err := protojson.Unmarshal(tierJson, tier)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "Failed to decode tiers json")
|
||||
}
|
||||
tierResponses[i].Limit = tiers[i].Limit
|
||||
tierResponses[i].MaxPerAddress = tiers[i].MaxPerAddress
|
||||
tierResponses[i].PriceSat = tiers[i].PriceSat
|
||||
tierResponses[i].Sold = nodeCount[i].Count
|
||||
}
|
||||
|
||||
err = ctx.JSON(&deployResponse{
|
||||
Id: request.DeployID,
|
||||
Name: deploy.Name,
|
||||
StartsAt: deploy.StartsAt.UTC().Unix(),
|
||||
EndsAt: deploy.EndsAt.UTC().Unix(),
|
||||
Tiers: tierResponses,
|
||||
SellerPublicKey: deploy.SellerPublicKey,
|
||||
MaxPerAddress: deploy.MaxPerAddress,
|
||||
DeployTxHash: deploy.DeployTxHash,
|
||||
})
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "Go fiber cannot parse JSON")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
56
modules/nodesale/api/httphandler/events.go
Normal file
56
modules/nodesale/api/httphandler/events.go
Normal file
@@ -0,0 +1,56 @@
|
||||
package httphandler
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"time"
|
||||
|
||||
"github.com/cockroachdb/errors"
|
||||
"github.com/gaze-network/indexer-network/modules/nodesale/protobuf"
|
||||
"github.com/gofiber/fiber/v2"
|
||||
)
|
||||
|
||||
type eventRequest struct {
|
||||
WalletAddress string `query:"walletAddress"`
|
||||
}
|
||||
|
||||
type eventResposne struct {
|
||||
TxHash string `json:"txHash"`
|
||||
BlockHeight int64 `json:"blockHeight"`
|
||||
TxIndex int32 `json:"txIndex"`
|
||||
WalletAddress string `json:"walletAddress"`
|
||||
Action string `json:"action"`
|
||||
ParsedMessage json.RawMessage `json:"parsedMessage"`
|
||||
BlockTimestamp time.Time `json:"blockTimestamp"`
|
||||
BlockHash string `json:"blockHash"`
|
||||
}
|
||||
|
||||
func (h *handler) eventsHandler(ctx *fiber.Ctx) error {
|
||||
var request eventRequest
|
||||
err := ctx.QueryParser(&request)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "cannot parse query")
|
||||
}
|
||||
|
||||
events, err := h.nodeSaleDg.GetEventsByWallet(ctx.UserContext(), request.WalletAddress)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "Can't get events from db")
|
||||
}
|
||||
|
||||
responses := make([]eventResposne, len(events))
|
||||
for i, event := range events {
|
||||
responses[i].TxHash = event.TxHash
|
||||
responses[i].BlockHeight = event.BlockHeight
|
||||
responses[i].TxIndex = event.TxIndex
|
||||
responses[i].WalletAddress = event.WalletAddress
|
||||
responses[i].Action = protobuf.Action_name[event.Action]
|
||||
responses[i].ParsedMessage = event.ParsedMessage
|
||||
responses[i].BlockTimestamp = event.BlockTimestamp
|
||||
responses[i].BlockHash = event.BlockHash
|
||||
}
|
||||
|
||||
err = ctx.JSON(responses)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "Go fiber cannot parse JSON")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
15
modules/nodesale/api/httphandler/handler.go
Normal file
15
modules/nodesale/api/httphandler/handler.go
Normal file
@@ -0,0 +1,15 @@
|
||||
package httphandler
|
||||
|
||||
import (
|
||||
"github.com/gaze-network/indexer-network/modules/nodesale/datagateway"
|
||||
)
|
||||
|
||||
type handler struct {
|
||||
nodeSaleDg datagateway.NodeSaleDataGateway
|
||||
}
|
||||
|
||||
func New(datagateway datagateway.NodeSaleDataGateway) *handler {
|
||||
h := handler{}
|
||||
h.nodeSaleDg = datagateway
|
||||
return &h
|
||||
}
|
||||
26
modules/nodesale/api/httphandler/info.go
Normal file
26
modules/nodesale/api/httphandler/info.go
Normal file
@@ -0,0 +1,26 @@
|
||||
package httphandler
|
||||
|
||||
import (
|
||||
"github.com/cockroachdb/errors"
|
||||
"github.com/gofiber/fiber/v2"
|
||||
)
|
||||
|
||||
type infoResponse struct {
|
||||
IndexedBlockHeight int64 `json:"indexedBlockHeight"`
|
||||
IndexedBlockHash string `json:"indexedBlockHash"`
|
||||
}
|
||||
|
||||
func (h *handler) infoHandler(ctx *fiber.Ctx) error {
|
||||
block, err := h.nodeSaleDg.GetLastProcessedBlock(ctx.UserContext())
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "Cannot get last processed block")
|
||||
}
|
||||
err = ctx.JSON(infoResponse{
|
||||
IndexedBlockHeight: block.BlockHeight,
|
||||
IndexedBlockHash: block.BlockHash,
|
||||
})
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "Go fiber cannot parse JSON")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
82
modules/nodesale/api/httphandler/nodes.go
Normal file
82
modules/nodesale/api/httphandler/nodes.go
Normal file
@@ -0,0 +1,82 @@
|
||||
package httphandler
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/cockroachdb/errors"
|
||||
"github.com/gaze-network/indexer-network/common/errs"
|
||||
"github.com/gaze-network/indexer-network/modules/nodesale/datagateway"
|
||||
"github.com/gaze-network/indexer-network/modules/nodesale/internal/entity"
|
||||
"github.com/gofiber/fiber/v2"
|
||||
)
|
||||
|
||||
type nodeRequest struct {
|
||||
DeployId string `query:"deployId"`
|
||||
OwnerPublicKey string `query:"ownerPublicKey"`
|
||||
DelegateePublicKey string `query:"delegateePublicKey"`
|
||||
}
|
||||
|
||||
type nodeResponse struct {
|
||||
DeployId string `json:"deployId"`
|
||||
NodeId uint32 `json:"nodeId"`
|
||||
TierIndex int32 `json:"tierIndex"`
|
||||
DelegatedTo string `json:"delegatedTo"`
|
||||
OwnerPublicKey string `json:"ownerPublicKey"`
|
||||
PurchaseTxHash string `json:"purchaseTxHash"`
|
||||
DelegateTxHash string `json:"delegateTxHash"`
|
||||
PurchaseBlockHeight int32 `json:"purchaseBlockHeight"`
|
||||
}
|
||||
|
||||
func (h *handler) nodesHandler(ctx *fiber.Ctx) error {
|
||||
var request nodeRequest
|
||||
err := ctx.QueryParser(&request)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "cannot parse query")
|
||||
}
|
||||
|
||||
ownerPublicKey := request.OwnerPublicKey
|
||||
delegateePublicKey := request.DelegateePublicKey
|
||||
|
||||
var blockHeight int64
|
||||
var txIndex int32
|
||||
count, err := fmt.Sscanf(request.DeployId, "%d-%d", &blockHeight, &txIndex)
|
||||
if count != 2 || err != nil {
|
||||
return errs.NewPublicError("Invalid deploy ID")
|
||||
}
|
||||
|
||||
var nodes []entity.Node
|
||||
if ownerPublicKey == "" {
|
||||
nodes, err = h.nodeSaleDg.GetNodesByDeployment(ctx.UserContext(), blockHeight, txIndex)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "Can't get nodes from db")
|
||||
}
|
||||
} else {
|
||||
nodes, err = h.nodeSaleDg.GetNodesByPubkey(ctx.UserContext(), datagateway.GetNodesByPubkeyParams{
|
||||
SaleBlock: blockHeight,
|
||||
SaleTxIndex: txIndex,
|
||||
OwnerPublicKey: ownerPublicKey,
|
||||
DelegatedTo: delegateePublicKey,
|
||||
})
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "Can't get nodes from db")
|
||||
}
|
||||
}
|
||||
|
||||
responses := make([]nodeResponse, len(nodes))
|
||||
for i, node := range nodes {
|
||||
responses[i].DeployId = request.DeployId
|
||||
responses[i].NodeId = node.NodeID
|
||||
responses[i].TierIndex = node.TierIndex
|
||||
responses[i].DelegatedTo = node.DelegatedTo
|
||||
responses[i].OwnerPublicKey = node.OwnerPublicKey
|
||||
responses[i].PurchaseTxHash = node.PurchaseTxHash
|
||||
responses[i].DelegateTxHash = node.DelegateTxHash
|
||||
responses[i].PurchaseBlockHeight = txIndex
|
||||
}
|
||||
|
||||
err = ctx.JSON(responses)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "Go fiber cannot parse JSON")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
16
modules/nodesale/api/httphandler/routes.go
Normal file
16
modules/nodesale/api/httphandler/routes.go
Normal file
@@ -0,0 +1,16 @@
|
||||
package httphandler
|
||||
|
||||
import (
|
||||
"github.com/gofiber/fiber/v2"
|
||||
)
|
||||
|
||||
func (h *handler) Mount(router fiber.Router) error {
|
||||
r := router.Group("/nodesale/v1")
|
||||
|
||||
r.Get("/info", h.infoHandler)
|
||||
r.Get("/deploy/:deployId", h.deployHandler)
|
||||
r.Get("/nodes", h.nodesHandler)
|
||||
r.Get("/events", h.eventsHandler)
|
||||
|
||||
return nil
|
||||
}
|
||||
8
modules/nodesale/config/config.go
Normal file
8
modules/nodesale/config/config.go
Normal file
@@ -0,0 +1,8 @@
|
||||
package config
|
||||
|
||||
import "github.com/gaze-network/indexer-network/internal/postgres"
|
||||
|
||||
type Config struct {
|
||||
Postgres postgres.Config `mapstructure:"postgres"`
|
||||
LastBlockDefault int64 `mapstructure:"last_block_default"`
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
BEGIN;
|
||||
|
||||
DROP TABLE IF EXISTS nodes;
|
||||
DROP TABLE IF EXISTS node_sales;
|
||||
DROP TABLE IF EXISTS events;
|
||||
DROP TABLE IF EXISTS blocks;
|
||||
|
||||
|
||||
COMMIT;
|
||||
@@ -0,0 +1,64 @@
|
||||
BEGIN;
|
||||
|
||||
CREATE TABLE IF NOT EXISTS blocks (
|
||||
"block_height" BIGINT NOT NULL,
|
||||
"block_hash" TEXT NOT NULL,
|
||||
"module" TEXT NOT NULL,
|
||||
PRIMARY KEY("block_height", "block_hash")
|
||||
);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS events (
|
||||
"tx_hash" TEXT NOT NULL PRIMARY KEY,
|
||||
"block_height" BIGINT NOT NULL,
|
||||
"tx_index" INTEGER NOT NULL,
|
||||
"wallet_address" TEXT NOT NULL,
|
||||
"valid" BOOLEAN NOT NULL,
|
||||
"action" INTEGER NOT NULL,
|
||||
"raw_message" BYTEA NOT NULL,
|
||||
"parsed_message" JSONB NOT NULL DEFAULT '{}',
|
||||
"block_timestamp" TIMESTAMP NOT NULL,
|
||||
"block_hash" TEXT NOT NULL,
|
||||
"metadata" JSONB NOT NULL DEFAULT '{}',
|
||||
"reason" TEXT NOT NULL DEFAULT ''
|
||||
);
|
||||
|
||||
INSERT INTO events("tx_hash", "block_height", "tx_index",
|
||||
"wallet_address", "valid", "action",
|
||||
"raw_message", "parsed_message", "block_timestamp",
|
||||
"block_hash", "metadata")
|
||||
VALUES ('', -1, -1,
|
||||
'', false, -1,
|
||||
'', '{}', NOW(),
|
||||
'', '{}');
|
||||
|
||||
CREATE TABLE IF NOT EXISTS node_sales (
|
||||
"block_height" BIGINT NOT NULL,
|
||||
"tx_index" INTEGER NOT NULL,
|
||||
"name" TEXT NOT NULL,
|
||||
"starts_at" TIMESTAMP NOT NULL,
|
||||
"ends_at" TIMESTAMP NOT NULL,
|
||||
"tiers" JSONB[] NOT NULL,
|
||||
"seller_public_key" TEXT NOT NULL,
|
||||
"max_per_address" INTEGER NOT NULL,
|
||||
"deploy_tx_hash" TEXT NOT NULL REFERENCES events(tx_hash) ON DELETE CASCADE,
|
||||
"max_discount_percentage" INTEGER NOT NULL,
|
||||
"seller_wallet" TEXT NOT NULL,
|
||||
PRIMARY KEY ("block_height", "tx_index")
|
||||
);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS nodes (
|
||||
"sale_block" BIGINT NOT NULL,
|
||||
"sale_tx_index" INTEGER NOT NULL,
|
||||
"node_id" INTEGER NOT NULL,
|
||||
"tier_index" INTEGER NOT NULL,
|
||||
"delegated_to" TEXT NOT NULL DEFAULT '',
|
||||
"owner_public_key" TEXT NOT NULL,
|
||||
"purchase_tx_hash" TEXT NOT NULL REFERENCES events(tx_hash) ON DELETE CASCADE,
|
||||
"delegate_tx_hash" TEXT NOT NULL DEFAULT '' REFERENCES events(tx_hash) ON DELETE SET DEFAULT,
|
||||
PRIMARY KEY("sale_block", "sale_tx_index", "node_id"),
|
||||
FOREIGN KEY("sale_block", "sale_tx_index") REFERENCES node_sales("block_height", "tx_index")
|
||||
);
|
||||
|
||||
|
||||
|
||||
COMMIT;
|
||||
15
modules/nodesale/database/postgresql/queries/blocks.sql
Normal file
15
modules/nodesale/database/postgresql/queries/blocks.sql
Normal file
@@ -0,0 +1,15 @@
|
||||
-- name: GetLastProcessedBlock :one
|
||||
SELECT * FROM blocks ORDER BY block_height DESC LIMIT 1;
|
||||
|
||||
|
||||
-- name: GetBlock :one
|
||||
SELECT * FROM blocks
|
||||
WHERE "block_height" = $1;
|
||||
|
||||
-- name: RemoveBlockFrom :execrows
|
||||
DELETE FROM blocks
|
||||
WHERE "block_height" >= @from_block;
|
||||
|
||||
-- name: CreateBlock :exec
|
||||
INSERT INTO blocks ("block_height", "block_hash", "module")
|
||||
VALUES ($1, $2, $3);
|
||||
14
modules/nodesale/database/postgresql/queries/events.sql
Normal file
14
modules/nodesale/database/postgresql/queries/events.sql
Normal file
@@ -0,0 +1,14 @@
|
||||
-- name: RemoveEventsFromBlock :execrows
|
||||
DELETE FROM events
|
||||
WHERE "block_height" >= @from_block;
|
||||
|
||||
-- name: CreateEvent :exec
|
||||
INSERT INTO events ("tx_hash", "block_height", "tx_index", "wallet_address", "valid", "action",
|
||||
"raw_message", "parsed_message", "block_timestamp", "block_hash", "metadata",
|
||||
"reason")
|
||||
VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12);
|
||||
|
||||
-- name: GetEventsByWallet :many
|
||||
SELECT *
|
||||
FROM events
|
||||
WHERE wallet_address = $1;
|
||||
57
modules/nodesale/database/postgresql/queries/nodes.sql
Normal file
57
modules/nodesale/database/postgresql/queries/nodes.sql
Normal file
@@ -0,0 +1,57 @@
|
||||
-- name: ClearDelegate :execrows
|
||||
UPDATE nodes
|
||||
SET "delegated_to" = ''
|
||||
WHERE "delegate_tx_hash" = '';
|
||||
|
||||
-- name: SetDelegates :execrows
|
||||
UPDATE nodes
|
||||
SET delegated_to = @delegatee, delegate_tx_hash = $3
|
||||
WHERE sale_block = $1 AND
|
||||
sale_tx_index = $2 AND
|
||||
node_id = ANY (@node_ids::int[]);
|
||||
|
||||
-- name: GetNodesByIds :many
|
||||
SELECT *
|
||||
FROM nodes
|
||||
WHERE sale_block = $1 AND
|
||||
sale_tx_index = $2 AND
|
||||
node_id = ANY (@node_ids::int[]);
|
||||
|
||||
|
||||
-- name: GetNodesByOwner :many
|
||||
SELECT *
|
||||
FROM nodes
|
||||
WHERE sale_block = $1 AND
|
||||
sale_tx_index = $2 AND
|
||||
owner_public_key = $3
|
||||
ORDER BY tier_index;
|
||||
|
||||
-- name: GetNodesByPubkey :many
|
||||
SELECT nodes.*
|
||||
FROM nodes JOIN events ON nodes.purchase_tx_hash = events.tx_hash
|
||||
WHERE sale_block = $1 AND
|
||||
sale_tx_index = $2 AND
|
||||
owner_public_key = $3 AND
|
||||
delegated_to = $4;
|
||||
|
||||
-- name: CreateNode :exec
|
||||
INSERT INTO nodes (sale_block, sale_tx_index, node_id, tier_index, delegated_to, owner_public_key, purchase_tx_hash, delegate_tx_hash)
|
||||
VALUES ($1, $2, $3, $4, $5, $6, $7, $8);
|
||||
|
||||
-- name: GetNodeCountByTierIndex :many
|
||||
SELECT (tiers.tier_index)::int AS tier_index, count(nodes.tier_index)
|
||||
FROM generate_series(@from_tier::int,@to_tier::int) AS tiers(tier_index)
|
||||
LEFT JOIN
|
||||
(SELECT *
|
||||
FROM nodes
|
||||
WHERE sale_block = $1 AND
|
||||
sale_tx_index= $2)
|
||||
AS nodes ON tiers.tier_index = nodes.tier_index
|
||||
GROUP BY tiers.tier_index
|
||||
ORDER BY tiers.tier_index;
|
||||
|
||||
-- name: GetNodesByDeployment :many
|
||||
SELECT *
|
||||
FROM nodes
|
||||
WHERE sale_block = $1 AND
|
||||
sale_tx_index = $2;
|
||||
@@ -0,0 +1,9 @@
|
||||
-- name: CreateNodeSale :exec
|
||||
INSERT INTO node_sales ("block_height", "tx_index", "name", "starts_at", "ends_at", "tiers", "seller_public_key", "max_per_address", "deploy_tx_hash", "max_discount_percentage", "seller_wallet")
|
||||
VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11);
|
||||
|
||||
-- name: GetNodeSale :many
|
||||
SELECT *
|
||||
FROM node_sales
|
||||
WHERE block_height = $1 AND
|
||||
tx_index = $2;
|
||||
3
modules/nodesale/database/postgresql/queries/test.sql
Normal file
3
modules/nodesale/database/postgresql/queries/test.sql
Normal file
@@ -0,0 +1,3 @@
|
||||
-- name: ClearEvents :exec
|
||||
DELETE FROM events
|
||||
WHERE tx_hash <> '';
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user