├── .gitignore ├── LICENSE ├── README.md ├── cmd └── main.go ├── config.ini.sample ├── go.mod ├── go.sum ├── handlers ├── fetch.go ├── global.go ├── ignore.go ├── indexer.go ├── rpc.go ├── snapshot.go ├── storage.go └── sync.go ├── model ├── asc20.go ├── ddecimal.go ├── evmlog.go ├── fetch │ ├── block_response.go │ ├── logs_response.go │ └── number_response.go ├── inscription.go ├── list.go ├── record.go ├── serialize │ └── indexer.pb.go ├── status.go ├── token.go ├── transaction.go └── transfer.go └── utils ├── decimal └── decimal.go └── utils.go /.gitignore: -------------------------------------------------------------------------------- 1 | .idea 2 | /bin/ 3 | /snapshots/ 4 | *.db/ 5 | .DS_Store 6 | *.output.txt 7 | /logs.txt 8 | /config.ini 9 | ./indexer 10 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2022-2023 Avascriptions 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # open-indexer 2 | 3 | Open source indexer for avascriptions 4 | 5 | ## How to use 6 | copy config.ini.sample to config.ini and configure it accordingly 7 | build: go build -o ./indexer ./cmd/main.go 8 | run: ./indexer 9 | 10 | ## Data source 11 | This program has come with block data grabbing function, you can also use an external data grabbing program, We also provide an open source program for grabbing chain data and storing it in mongodb for your reference: https://github.com/avascriptions/fetch-chaindata 12 | 13 | ## Snapshot 14 | Currently the indexed data is not persisted, you must rely on a snapshot to start each time you start, if you do not specify a snapshot, the indexer will start indexing from the initial data. 15 | Specify the snapshot file command: 16 | ./indexer --snapshot snapshots/snap-xxx.bin 17 | 18 | Of course if you can't run from the genesis block, you can download snapshots from us and the following snapshots are updated regularly. 19 | 01-28 [snap-40953600.bin.zip](https://snapshots.avascriptions.com/snap-40953600.bin.zip) MD5: 73dfea130fe840b81289ac4169f68678 20 | 02-04 [snap-41256000.bin.zip](https://snapshots.avascriptions.com/snap-41256000.bin.zip) MD5: 9e0c2468d0fe44566eafc209bc22f998 21 | 22 | ## RPC Interfaces 23 | The indexer implements simple RPC interfaces, the list of interfaces is as follows 24 | GET /v1/tokens/ 25 | GET /v1/token/:tick 26 | GET /v1/token/:tick/holders 27 | GET /v1/address/:addr 28 | GET /v1/address/:addr/:tick 29 | -------------------------------------------------------------------------------- /cmd/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "open-indexer/handlers" 5 | "os" 6 | "os/signal" 7 | "time" 8 | ) 9 | 10 | func main() { 11 | 12 | oss := make(chan os.Signal) 13 | 14 | var logger = handlers.GetLogger() 15 | 16 | logger.Info("start indexer") 17 | 18 | handlers.InitFromSnapshot() 19 | 20 | go func() { 21 | handlers.StartRpc() 22 | }() 23 | 24 | if handlers.DataSourceType == "rpc" { 25 | go func() { 26 | handlers.StartFetch() 27 | }() 28 | } 29 | 30 | go func() { 31 | handlers.StartSync() 32 | }() 33 | 34 | stop := func() { 35 | logger.Info("app is stopping") 36 | go func() { 37 | handlers.StopRpc() 38 | }() 39 | go func() { 40 | handlers.StopSync() 41 | }() 42 | go func() { 43 | handlers.StopFetch() 44 | }() 45 | 46 | // wait all stopped 47 | for handlers.StopSuccessCount < 3 { 48 | time.Sleep(time.Duration(100) * time.Millisecond) 49 | } 50 | 51 | // close db 52 | handlers.CloseDb() 53 | 54 | logger.Info("app stopped.") 55 | 56 | } 57 | 58 | // 监听信号 59 | signal.Notify(oss, os.Interrupt, os.Kill) 60 | 61 | for { 62 | select { 63 | case <-oss: // kill -9 pid,no effect 64 | logger.Info("stopped by system...") 65 | stop() 66 | logger.Info("gracefully stopped.") 67 | return 68 | case <-handlers.QuitChan: 69 | logger.Info("stopped by app.") 70 | stop() 71 | logger.Info("app is auto stopped.") 72 | return 73 | } 74 | } 75 | } 76 | -------------------------------------------------------------------------------- /config.ini.sample: -------------------------------------------------------------------------------- 1 | [leveldb] 2 | path=data/data.db 3 | 4 | [data-source] 5 | type=rpc 6 | uri=https://api.avax.network/ext/bc/C/rpc 7 | #type=mongo 8 | #uri=mongodb://127.0.0.1:27017/database 9 | 10 | [rpc] 11 | host=* 12 | port=3030 13 | 14 | [sync] 15 | start=38234549 16 | end=0 17 | size=1 -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module open-indexer 2 | 3 | go 1.20 4 | 5 | require ( 6 | github.com/sirupsen/logrus v1.9.2 7 | github.com/syndtr/goleveldb v1.0.0 8 | github.com/wealdtech/go-merkletree v1.0.0 9 | go.mongodb.org/mongo-driver v1.13.1 10 | gopkg.in/ini.v1 v1.67.0 11 | ) 12 | 13 | require ( 14 | github.com/andybalholm/brotli v1.1.0 // indirect 15 | github.com/go-resty/resty/v2 v2.11.0 // indirect 16 | github.com/gofiber/fiber/v2 v2.52.0 // indirect 17 | github.com/gofiber/utils/v2 v2.0.0-beta.3 // indirect 18 | github.com/golang/snappy v0.0.1 // indirect 19 | github.com/google/uuid v1.5.0 // indirect 20 | github.com/klauspost/compress v1.17.4 // indirect 21 | github.com/mattn/go-colorable v0.1.13 // indirect 22 | github.com/mattn/go-isatty v0.0.20 // indirect 23 | github.com/mattn/go-runewidth v0.0.15 // indirect 24 | github.com/montanaflynn/stats v0.0.0-20171201202039-1bf9dbcd8cbe // indirect 25 | github.com/rivo/uniseg v0.2.0 // indirect 26 | github.com/valyala/bytebufferpool v1.0.0 // indirect 27 | github.com/valyala/fasthttp v1.51.0 // indirect 28 | github.com/valyala/tcplisten v1.0.0 // indirect 29 | github.com/xdg-go/pbkdf2 v1.0.0 // indirect 30 | github.com/xdg-go/scram v1.1.2 // indirect 31 | github.com/xdg-go/stringprep v1.0.4 // indirect 32 | github.com/youmark/pkcs8 v0.0.0-20181117223130-1be2e3e5546d // indirect 33 | golang.org/x/crypto v0.17.0 // indirect 34 | golang.org/x/net v0.17.0 // indirect 35 | golang.org/x/sync v0.1.0 // indirect 36 | golang.org/x/sys v0.16.0 // indirect 37 | golang.org/x/text v0.14.0 // indirect 38 | google.golang.org/protobuf v1.32.0 // indirect 39 | ) 40 | -------------------------------------------------------------------------------- /go.sum: -------------------------------------------------------------------------------- 1 | github.com/andybalholm/brotli v1.1.0 h1:eLKJA0d02Lf0mVpIDgYnqXcUn0GqVmEFny3VuID1U3M= 2 | github.com/andybalholm/brotli v1.1.0/go.mod h1:sms7XGricyQI9K10gOSf56VKKWS4oLer58Q+mhRPtnY= 3 | github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 4 | github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= 5 | github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 6 | github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= 7 | github.com/go-resty/resty/v2 v2.11.0 h1:i7jMfNOJYMp69lq7qozJP+bjgzfAzeOhuGlyDrqxT/8= 8 | github.com/go-resty/resty/v2 v2.11.0/go.mod h1:iiP/OpA0CkcL3IGt1O0+/SIItFUbkkyw5BGXiVdTu+A= 9 | github.com/gofiber/fiber/v2 v2.52.0 h1:S+qXi7y+/Pgvqq4DrSmREGiFwtB7Bu6+QFLuIHYw/UE= 10 | github.com/gofiber/fiber/v2 v2.52.0/go.mod h1:KEOE+cXMhXG0zHc9d8+E38hoX+ZN7bhOtgeF2oT6jrQ= 11 | github.com/gofiber/fiber/v3 v3.0.0-20240118224840-603fbde9b61d h1:jUgG9783cL+R/oHfV5yhved2iupKY0oAE5Q4sMV56t4= 12 | github.com/gofiber/fiber/v3 v3.0.0-20240118224840-603fbde9b61d/go.mod h1:v2BObVKru97QNIjADj7eUOExhxFs1PV29HEB+St8Rng= 13 | github.com/gofiber/utils/v2 v2.0.0-beta.3 h1:pfOhUDDVjBJpkWv6C5jaDyYLvpui7zQ97zpyFFsUOKw= 14 | github.com/gofiber/utils/v2 v2.0.0-beta.3/go.mod h1:jsl17+MsKfwJjM3ONCE9Rzji/j8XNbwjhUVTjzgfDCo= 15 | github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= 16 | github.com/golang/snappy v0.0.0-20180518054509-2e65f85255db/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= 17 | github.com/golang/snappy v0.0.1 h1:Qgr9rKW7uDUkrbSmQeiDsGa8SjGyCOGtuasMWwvp2P4= 18 | github.com/golang/snappy v0.0.1/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= 19 | github.com/google/go-cmp v0.5.2 h1:X2ev0eStA3AbceY54o37/0PQ/UWqKEiiO2dKL5OPaFM= 20 | github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= 21 | github.com/google/go-cmp v0.5.5 h1:Khx7svrCpmxxtHBq5j2mp/xVjsi8hQMfNLvJFAlrGgU= 22 | github.com/google/uuid v1.5.0 h1:1p67kYwdtXjb0gL0BPiP1Av9wiZPo5A8z2cWkTZ+eyU= 23 | github.com/google/uuid v1.5.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= 24 | github.com/hpcloud/tail v1.0.0 h1:nfCOvKYfkgYP8hkirhJocXT2+zOD8yUNjXaWfTlyFKI= 25 | github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU= 26 | github.com/klauspost/compress v1.13.6/go.mod h1:/3/Vjq9QcHkK5uEr5lBEmyoZ1iFhe47etQ6QUkpK6sk= 27 | github.com/klauspost/compress v1.17.4 h1:Ej5ixsIri7BrIjBkRZLTo6ghwrEtHFk7ijlczPW4fZ4= 28 | github.com/klauspost/compress v1.17.4/go.mod h1:/dCuZOvVtNoHsyb+cuJD3itjs3NbnF6KH9zAO4BDxPM= 29 | github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA= 30 | github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg= 31 | github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM= 32 | github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY= 33 | github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= 34 | github.com/mattn/go-runewidth v0.0.15 h1:UNAjwbU9l54TA3KzvqLGxwWjHmMgBUVhBiTjelZgg3U= 35 | github.com/mattn/go-runewidth v0.0.15/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w= 36 | github.com/montanaflynn/stats v0.0.0-20171201202039-1bf9dbcd8cbe h1:iruDEfMl2E6fbMZ9s0scYfZQ84/6SPL6zC8ACM2oIL0= 37 | github.com/montanaflynn/stats v0.0.0-20171201202039-1bf9dbcd8cbe/go.mod h1:wL8QJuTMNUDYhXwkmfOly8iTdp5TEcJFWZD2D7SIkUc= 38 | github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= 39 | github.com/onsi/ginkgo v1.7.0 h1:WSHQ+IS43OoUrWtD1/bbclrwK8TTH5hzp+umCiuxHgs= 40 | github.com/onsi/ginkgo v1.7.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= 41 | github.com/onsi/gomega v1.4.3 h1:RE1xgDvH7imwFD45h+u2SgIfERHlS2yNG4DObb5BSKU= 42 | github.com/onsi/gomega v1.4.3/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY= 43 | github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= 44 | github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= 45 | github.com/rivo/uniseg v0.2.0 h1:S1pD9weZBuJdFmowNwbpi7BJ8TNftyUImj/0WQi72jY= 46 | github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc= 47 | github.com/sirupsen/logrus v1.9.2 h1:oxx1eChJGI6Uks2ZC4W1zpLlVgqB8ner4EuQwV4Ik1Y= 48 | github.com/sirupsen/logrus v1.9.2/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ= 49 | github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= 50 | github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= 51 | github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk= 52 | github.com/syndtr/goleveldb v1.0.0 h1:fBdIW9lB4Iz0n9khmH8w27SJ3QEJ7+IgjPEwGSZiFdE= 53 | github.com/syndtr/goleveldb v1.0.0/go.mod h1:ZVVdQEZoIme9iO1Ch2Jdy24qqXrMMOU6lpPAyBWyWuQ= 54 | github.com/valyala/bytebufferpool v1.0.0 h1:GqA5TC/0021Y/b9FG4Oi9Mr3q7XYx6KllzawFIhcdPw= 55 | github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc= 56 | github.com/valyala/fasthttp v1.51.0 h1:8b30A5JlZ6C7AS81RsWjYMQmrZG6feChmgAolCl1SqA= 57 | github.com/valyala/fasthttp v1.51.0/go.mod h1:oI2XroL+lI7vdXyYoQk03bXBThfFl2cVdIA3Xl7cH8g= 58 | github.com/valyala/tcplisten v1.0.0 h1:rBHj/Xf+E1tRGZyWIWwJDiRY0zc1Js+CV5DqwacVSA8= 59 | github.com/valyala/tcplisten v1.0.0/go.mod h1:T0xQ8SeCZGxckz9qRXTfG43PvQ/mcWh7FwZEA7Ioqkc= 60 | github.com/wealdtech/go-merkletree v1.0.0 h1:DsF1xMzj5rK3pSQM6mPv8jlyJyHXhFxpnA2bwEjMMBY= 61 | github.com/wealdtech/go-merkletree v1.0.0/go.mod h1:cdil512d/8ZC7Kx3bfrDvGMQXB25NTKbsm0rFrmDax4= 62 | github.com/xdg-go/pbkdf2 v1.0.0 h1:Su7DPu48wXMwC3bs7MCNG+z4FhcyEuz5dlvchbq0B0c= 63 | github.com/xdg-go/pbkdf2 v1.0.0/go.mod h1:jrpuAogTd400dnrH08LKmI/xc1MbPOebTwRqcT5RDeI= 64 | github.com/xdg-go/scram v1.1.2 h1:FHX5I5B4i4hKRVRBCFRxq1iQRej7WO3hhBuJf+UUySY= 65 | github.com/xdg-go/scram v1.1.2/go.mod h1:RT/sEzTbU5y00aCK8UOx6R7YryM0iF1N2MOmC3kKLN4= 66 | github.com/xdg-go/stringprep v1.0.4 h1:XLI/Ng3O1Atzq0oBs3TWm+5ZVgkq2aqdlvP9JtoZ6c8= 67 | github.com/xdg-go/stringprep v1.0.4/go.mod h1:mPGuuIYwz7CmR2bT9j4GbQqutWS1zV24gijq1dTyGkM= 68 | github.com/youmark/pkcs8 v0.0.0-20181117223130-1be2e3e5546d h1:splanxYIlg+5LfHAM6xpdFEAYOk8iySO56hMFq6uLyA= 69 | github.com/youmark/pkcs8 v0.0.0-20181117223130-1be2e3e5546d/go.mod h1:rHwXgn7JulP+udvsHwJoVG1YGAP6VLg4y9I5dyZdqmA= 70 | github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= 71 | go.mongodb.org/mongo-driver v1.13.1 h1:YIc7HTYsKndGK4RFzJ3covLz1byri52x0IoMB0Pt/vk= 72 | go.mongodb.org/mongo-driver v1.13.1/go.mod h1:wcDf1JBCXy2mOW0bWHwO/IOYqdca1MPCwDtFu/Z9+eo= 73 | golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= 74 | golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= 75 | golang.org/x/crypto v0.0.0-20220622213112-05595931fe9d/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= 76 | golang.org/x/crypto v0.14.0/go.mod h1:MVFd36DqK4CsrnJYDkBA3VC4m2GkXAM0PvzMCn4JQf4= 77 | golang.org/x/crypto v0.17.0 h1:r8bRNjWL3GshPW3gkd+RpvzWrZAwPS49OmTGZ/uhM4k= 78 | golang.org/x/crypto v0.17.0/go.mod h1:gCAAfMLgwOJRpTjQ2zCCt2OcSfYMTeZVSRtQlPC7Nq4= 79 | golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= 80 | golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= 81 | golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= 82 | golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= 83 | golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= 84 | golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= 85 | golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= 86 | golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= 87 | golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg= 88 | golang.org/x/net v0.17.0 h1:pVaXccu2ozPjCXewfr1S7xza/zcXTity9cCdXQYSjIM= 89 | golang.org/x/net v0.17.0/go.mod h1:NxSsAGuq816PNPmqtQdLE42eU2Fs7NoRIZrHJAlaCOE= 90 | golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 91 | golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 92 | golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4 h1:uVc8UZUe6tr40fFVnUP5Oj+veunVezqYl9z7DYw9xzw= 93 | golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 94 | golang.org/x/sync v0.1.0 h1:wsuoTGHzEhffawBOhz5CYhcrV4IdKZbEyZjBMuTp12o= 95 | golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 96 | golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 97 | golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 98 | golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 99 | golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 100 | golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 101 | golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 102 | golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 103 | golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 104 | golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 105 | golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 106 | golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 107 | golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 108 | golang.org/x/sys v0.13.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 109 | golang.org/x/sys v0.16.0 h1:xWw16ngr6ZMtmxDyKyIgsE93KNKz5HKmMa3b8ALHidU= 110 | golang.org/x/sys v0.16.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= 111 | golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= 112 | golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= 113 | golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k= 114 | golang.org/x/term v0.8.0/go.mod h1:xPskH00ivmX89bAKVGSKKtLOWNx2+17Eiy94tnKShWo= 115 | golang.org/x/term v0.13.0/go.mod h1:LTmsnFJwVN6bCy1rVCoS+qHT1HhALEFxKncY3WNNh4U= 116 | golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= 117 | golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= 118 | golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= 119 | golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= 120 | golang.org/x/text v0.3.8/go.mod h1:E6s5w1FMmriuDzIBO73fBruAKo1PCIq6d2Q6DHfQ8WQ= 121 | golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= 122 | golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8= 123 | golang.org/x/text v0.13.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE= 124 | golang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ= 125 | golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= 126 | golang.org/x/time v0.3.0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= 127 | golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= 128 | golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= 129 | golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= 130 | golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU= 131 | golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 132 | golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543 h1:E7g+9GITq07hpfrRu66IVDexMakfv52eLZ2CXBWiKr4= 133 | golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 134 | google.golang.org/protobuf v1.32.0 h1:pPC6BG5ex8PDFnkbrGU3EixyhKcQ2aDuBS36lqK/C7I= 135 | google.golang.org/protobuf v1.32.0/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos= 136 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 137 | gopkg.in/fsnotify.v1 v1.4.7 h1:xOHLXZwVvI9hhs+cLKq5+I5onOuwQLhQwiu63xxlHs4= 138 | gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys= 139 | gopkg.in/ini.v1 v1.67.0 h1:Dgnx+6+nfE+IfzjUEISNeydPJh9AXNNsWbGP9KzCsOA= 140 | gopkg.in/ini.v1 v1.67.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= 141 | gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkepLTh2hOroT7a+7czfdQ= 142 | gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw= 143 | gopkg.in/yaml.v2 v2.2.1 h1:mUhvW9EsL+naU5Q3cakzfE91YhliOondGd6ZrsDBHQE= 144 | gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= 145 | gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= 146 | gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= 147 | -------------------------------------------------------------------------------- /handlers/fetch.go: -------------------------------------------------------------------------------- 1 | package handlers 2 | 3 | import ( 4 | "encoding/json" 5 | "errors" 6 | "fmt" 7 | "github.com/go-resty/resty/v2" 8 | "open-indexer/model" 9 | "open-indexer/model/fetch" 10 | "open-indexer/utils" 11 | "strconv" 12 | "sync" 13 | "time" 14 | ) 15 | 16 | var req = resty.New().SetTimeout(3 * time.Second) 17 | var fetchUrl = "" 18 | 19 | var fetchDataBlock uint64 20 | var fetchInterrupt bool 21 | var lastBlockNumber uint64 22 | 23 | var cachedTranscriptions = make(map[uint64][]*model.Transaction) 24 | var cachedLogs = make(map[uint64][]*model.EvmLog) 25 | var cachedBlockNumber uint64 26 | 27 | func StartFetch() { 28 | //value, err := db.Get([]byte("h-data-block"), nil) 29 | //if err == nil { 30 | // fetchDataBlock = utils.BytesToUint64(value) 31 | //} else { 32 | // fetchDataBlock = syncFromBlock - 1 33 | //} 34 | fetchDataBlock = syncFromBlock - 1 35 | logger.Println("start fetch data from ", fetchDataBlock+1) 36 | 37 | if lastBlockNumber == 0 { 38 | var err error 39 | lastBlockNumber, err = fetchLastBlockNumber() 40 | if err != nil { 41 | panic("fetch last block number error") 42 | } 43 | logger.Println("fetch: last block number", lastBlockNumber) 44 | } 45 | 46 | // fetch 47 | fetchInterrupt = false 48 | for !fetchInterrupt { 49 | var trxsResp fetch.BlockResponse 50 | var logsResp fetch.LogsResponse 51 | fetchDataBlock++ 52 | err := fetchData(fetchDataBlock, &trxsResp, &logsResp) 53 | if err != nil { 54 | logger.Println("fetch error:", err.Error()) 55 | fetchDataBlock-- 56 | time.Sleep(time.Duration(1) * time.Second) 57 | } else { 58 | err = saveData(&trxsResp, &logsResp) 59 | if err != nil { 60 | logger.Println("fetch: save error:", err.Error()) 61 | QuitChan <- true 62 | break 63 | } 64 | } 65 | } 66 | 67 | StopSuccessCount++ 68 | logger.Println("fetch stopped") 69 | } 70 | 71 | func StopFetch() { 72 | fetchInterrupt = true 73 | if DataSourceType != "rpc" { 74 | StopSuccessCount++ 75 | } 76 | } 77 | 78 | func fetchData(blockNumber uint64, blockResp *fetch.BlockResponse, logsResp *fetch.LogsResponse) error { 79 | start := time.Now().UnixMilli() 80 | 81 | if blockNumber > lastBlockNumber { 82 | lastBlock, err := fetchLastBlockNumber() 83 | if err != nil { 84 | return err 85 | } 86 | lastBlockNumber = lastBlock 87 | } 88 | 89 | if blockNumber > lastBlockNumber { 90 | return errors.New("no new blocks to be fetched") 91 | } 92 | 93 | var wg sync.WaitGroup 94 | var err0 error 95 | var err1 error 96 | 97 | wg.Add(2) 98 | go func() { 99 | err0 = fetchTransactions(blockNumber, blockResp) 100 | wg.Done() 101 | }() 102 | go func() { 103 | err1 = fetchContractLogs(blockNumber, logsResp) 104 | wg.Done() 105 | }() 106 | 107 | wg.Wait() 108 | 109 | if err0 != nil { 110 | return err0 111 | } 112 | if err1 != nil { 113 | return err1 114 | } 115 | costs := time.Now().UnixMilli() - start 116 | if costs > 200 { 117 | logger.Info("fetch data at #", blockNumber, " costs ", costs, " ms") 118 | } 119 | return nil 120 | } 121 | 122 | func fetchLastBlockNumber() (uint64, error) { 123 | //start := time.Now().UnixMilli() 124 | reqJson := fmt.Sprintf(`{"id": "indexer","jsonrpc": "2.0","method": "eth_blockNumber","params": []}`) 125 | resp, rerr := req.R().EnableTrace(). 126 | SetHeader("Content-Type", "application/json"). 127 | SetHeader("Accept", "application/json"). 128 | SetBody(reqJson). 129 | Post(fetchUrl) 130 | if rerr != nil { 131 | logger.Info("fetch url error:", rerr) 132 | return 0, rerr 133 | } 134 | 135 | var response fetch.NumberResponse 136 | uerr := json.Unmarshal(resp.Body(), &response) 137 | if uerr != nil { 138 | logger.Info("json parse error: ", uerr) 139 | fmt.Println(string(resp.Body())) 140 | return 0, rerr 141 | } 142 | if response.Error.Code != 0 && response.Error.Message != "" { 143 | return 0, errors.New(fmt.Sprintf("fetch error code: %d, msg: %s", response.Error.Code, response.Error.Message)) 144 | } 145 | if response.Id != "indexer" || response.JsonRpc != "2.0" { 146 | return 0, errors.New("fetch error data") 147 | } 148 | 149 | blockNumber := utils.HexToUint64(response.Result) - 2 150 | 151 | return blockNumber, nil 152 | } 153 | 154 | func fetchTransactions(blockNumber uint64, response *fetch.BlockResponse) (err error) { 155 | //start := time.Now().UnixMilli() 156 | block := strconv.FormatUint(blockNumber, 16) 157 | reqJson := fmt.Sprintf(`{"id": "indexer","jsonrpc": "2.0","method": "eth_getBlockByNumber","params": ["0x%s", %t]}`, block, true) 158 | resp, rerr := req.R().EnableTrace(). 159 | SetHeader("Content-Type", "application/json"). 160 | SetHeader("Accept", "application/json"). 161 | SetBody(reqJson). 162 | Post(fetchUrl) 163 | if rerr != nil { 164 | logger.Info("fetch url error:", rerr) 165 | err = rerr 166 | return 167 | } 168 | 169 | uerr := json.Unmarshal(resp.Body(), &response) 170 | if uerr != nil { 171 | logger.Info("json parse error: ", uerr) 172 | fmt.Println(string(resp.Body())) 173 | err = uerr 174 | return 175 | } 176 | if response.Error.Code != 0 && response.Error.Message != "" { 177 | err = errors.New(fmt.Sprintf("fetch error code: %d, msg: %s", response.Error.Code, response.Error.Message)) 178 | return 179 | } 180 | if response.Id != "indexer" || response.JsonRpc != "2.0" || response.Result.Hash == "" { 181 | err = errors.New("fetch error data") 182 | return 183 | } 184 | 185 | //costs := time.Now().UnixMilli() - start 186 | //if costs > 200 { 187 | // logger.Info("fetch trxs at #", blockNumber, ", costs ", costs, " ms") 188 | //} 189 | return 190 | } 191 | 192 | func fetchContractLogs(blockNumber uint64, response *fetch.LogsResponse) (err error) { 193 | //start := time.Now().UnixMilli() 194 | block := strconv.FormatUint(blockNumber, 16) 195 | reqJson := fmt.Sprintf(`{"id": "indexer","jsonrpc": "2.0","method": "eth_getLogs","params": [{"fromBlock": "0x%s","toBlock": "0x%s"}]}`, block, block) 196 | resp, rerr := req.R().EnableTrace(). 197 | SetHeader("Content-Type", "application/json"). 198 | SetHeader("Accept", "application/json"). 199 | SetBody(reqJson). 200 | Post(fetchUrl) 201 | if rerr != nil { 202 | logger.Info("fetch url error:", rerr) 203 | err = rerr 204 | return 205 | } 206 | 207 | uerr := json.Unmarshal(resp.Body(), &response) 208 | if uerr != nil { 209 | logger.Info("json parse error: ", uerr) 210 | fmt.Println(string(resp.Body())) 211 | err = uerr 212 | return 213 | } 214 | if response.Error.Code != 0 && response.Error.Message != "" { 215 | err = errors.New(fmt.Sprintf("fetch error code: %d, msg: %s", response.Error.Code, response.Error.Message)) 216 | return 217 | } 218 | if response.Id != "indexer" || response.JsonRpc != "2.0" { 219 | err = errors.New("fetch error data") 220 | return 221 | } 222 | 223 | //costs := time.Now().UnixMilli() - start 224 | //if costs > 200 { 225 | // logger.Info("fetch logs at #", blockNumber, " costs ", costs, " ms") 226 | //} 227 | return 228 | } 229 | 230 | func saveData(blockResp *fetch.BlockResponse, logsResp *fetch.LogsResponse) error { 231 | var err error 232 | 233 | var blockNumber = utils.HexToUint64(blockResp.Result.Number) 234 | var timestamp = utils.HexToUint64(blockResp.Result.Timestamp) 235 | 236 | // save trxs 237 | trxs := make([]*model.Transaction, len(blockResp.Result.Transactions)) 238 | for ti := range blockResp.Result.Transactions { 239 | _trx := blockResp.Result.Transactions[ti] 240 | trx := &model.Transaction{ 241 | Id: _trx.Hash, 242 | From: _trx.From, 243 | To: _trx.To, 244 | Block: blockNumber, 245 | Idx: utils.HexToUint32(_trx.TransactionIndex), 246 | Timestamp: timestamp, 247 | Input: _trx.Input, 248 | } 249 | trxs[ti] = trx 250 | } 251 | cachedTranscriptions[blockNumber] = trxs 252 | 253 | // save logs 254 | logs := make([]*model.EvmLog, len(logsResp.Result)) 255 | for li := range logsResp.Result { 256 | _log := logsResp.Result[li] 257 | log := &model.EvmLog{ 258 | Hash: _log.TransactionHash, 259 | Address: _log.Address, 260 | Topics: _log.Topics, 261 | Data: _log.Data, 262 | Block: blockNumber, 263 | TrxIndex: utils.HexToUint32(_log.TransactionIndex), 264 | LogIndex: utils.HexToUint32(_log.LogIndex), 265 | Timestamp: timestamp, 266 | } 267 | logs[li] = log 268 | } 269 | cachedLogs[blockNumber] = logs 270 | 271 | cachedBlockNumber = blockNumber 272 | 273 | // Only saved to memory for now, considering leveldb in the future 274 | 275 | //batch := new(leveldb.Batch) 276 | // save block height 277 | //batch.Put([]byte("h-data-block"), utils.Uint64ToBytes(blockNumber)) 278 | // 279 | //// batch write 280 | //err = db.Write(batch, nil) 281 | //if err != nil { 282 | // logger.Fatal(err) 283 | //} 284 | 285 | return err 286 | } 287 | -------------------------------------------------------------------------------- /handlers/global.go: -------------------------------------------------------------------------------- 1 | package handlers 2 | 3 | import ( 4 | "context" 5 | "flag" 6 | "github.com/sirupsen/logrus" 7 | "github.com/syndtr/goleveldb/leveldb" 8 | "go.mongodb.org/mongo-driver/mongo" 9 | "go.mongodb.org/mongo-driver/mongo/options" 10 | "go.mongodb.org/mongo-driver/x/mongo/driver/connstring" 11 | "gopkg.in/ini.v1" 12 | "io" 13 | "log" 14 | "open-indexer/utils" 15 | "os" 16 | "time" 17 | ) 18 | 19 | var cfg *ini.File 20 | var logger *logrus.Logger 21 | var db *leveldb.DB 22 | 23 | var DataSourceType string 24 | 25 | var mgCtx *context.Context 26 | var mongodb *mongo.Database 27 | 28 | var snapFile string 29 | 30 | var QuitChan = make(chan bool) 31 | var StopSuccessCount uint = 0 32 | 33 | func init() { 34 | log.Println("global init") 35 | var snapshotAt string 36 | flag.StringVar(&snapFile, "snapshot", "", "the filename of snapshot") 37 | flag.StringVar(&snapshotAt, "snapshot-at", "", "the block that create snapshot") 38 | flag.Parse() 39 | 40 | if snapshotAt != "" { 41 | createSnapshotBlock = uint64(utils.ParseInt64(snapshotAt)) 42 | } 43 | 44 | var err error 45 | cfg, err = ini.ShadowLoad("config.ini") 46 | if err != nil { 47 | cfg, err = ini.ShadowLoad("../config.ini") 48 | if err != nil { 49 | panic("read config.ini file error: " + err.Error()) 50 | //os.Exit(-1) 51 | } 52 | } 53 | 54 | initLogger() 55 | initLevelDb() 56 | 57 | initDataSource() 58 | 59 | initSync() 60 | 61 | // read data 62 | err = initFromStorage() 63 | if err != nil { 64 | panic(err) 65 | } 66 | } 67 | 68 | func initLogger() { 69 | writerStd := os.Stdout 70 | writerFile, err := os.OpenFile("logs.txt", os.O_WRONLY|os.O_CREATE, 0755) 71 | if err != nil { 72 | logrus.Fatalf("create file logs.txt failed: %v", err) 73 | } 74 | 75 | logger = logrus.New() 76 | logger.SetLevel(logrus.InfoLevel) 77 | logger.SetFormatter(&logrus.TextFormatter{}) 78 | logger.SetOutput(io.MultiWriter(writerStd, writerFile)) 79 | } 80 | 81 | func initLevelDb() { 82 | dbCfg := cfg.Section("leveldb") 83 | dbPath := dbCfg.Key("path").String() 84 | 85 | var err error 86 | if snapFile != "" { 87 | _, err := os.Stat(dbPath) 88 | if err == nil { 89 | panic("when specifying a snapshot, the database must be empty") 90 | } 91 | } 92 | 93 | db, err = leveldb.OpenFile(dbPath, nil) 94 | if err != nil { 95 | panic("open database failed:" + err.Error()) 96 | } 97 | } 98 | 99 | func initDataSource() { 100 | dsCfg := cfg.Section("data-source") 101 | DataSourceType = dsCfg.Key("type").String() 102 | dsUri := dsCfg.Key("uri").String() 103 | 104 | if DataSourceType == "mongo" { 105 | cs, err := connstring.ParseAndValidate(dsUri) 106 | ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second) 107 | defer cancel() 108 | mgCtx = &ctx 109 | client, err := mongo.Connect(ctx, options.Client().ApplyURI(dsUri)) 110 | if err != nil { 111 | panic("connect to mongo failed:" + err.Error()) 112 | } 113 | mongodb = client.Database(cs.Database) 114 | } else if DataSourceType == "rpc" { 115 | fetchUrl = dsUri 116 | } else { 117 | panic("error data source type") 118 | } 119 | } 120 | 121 | func GetLogger() *logrus.Logger { 122 | return logger 123 | } 124 | 125 | func CloseDb() { 126 | db.Close() 127 | db = nil 128 | if mongodb != nil { 129 | mongodb.Client().Disconnect(*mgCtx) 130 | } 131 | mongodb = nil 132 | } 133 | -------------------------------------------------------------------------------- /handlers/ignore.go: -------------------------------------------------------------------------------- 1 | package handlers 2 | 3 | /** 4 | * Special Note 5 | * Because there was a calculation bug in the indexer that caused some transactions to be judged as illegal, 6 | * in order to ensure the consistency of the indexing results, the following transactions will be added to the ignore list, 7 | * and this behavior will not lead to the loss of user assets. 8 | */ 9 | var ignoreHashes = map[string]bool{ 10 | "0x6f90e3494cd4db61cdb283a9d130aa9a37840051f6919efcd700385367937d90": true, 11 | "0x163c6711c2e6d5ff4dc5caeeba83940374215777cf4fb4b36d18d3e3d3bb77fa": true, 12 | "0x02c3cb8be4c3f2bb509ad52172c9bd647ac2da2698a86a7f777cdfdc9a3279c5": true, 13 | "0xfaf735dae7d74f5499ac3f3a2e29ee6a6923400731d7db4c2ad897ba139fc4a1": true, 14 | "0x7e74ae67a4fb3253f09adf55027669206b702ea1f1d5a21a513f47849190b2d8": true, 15 | "0x5beb9d504a488970dc05309b50a3edec7b96576cf47d0966d87ea12f3e680042": true, 16 | "0x198c7616858b8be2e3e456be1358f5441ec24ad114a4852ef94b99d0dfc22de3": true, 17 | "0x5762d36755b184b7f71a4102b50fa19eadc818c914a7ce2b48b587055558af29": true, 18 | "0x58d56ec52fa53473c40d98cc7db5db971bf3048396fff3fe73db89503906b63f": true, 19 | "0x98d6b7cfdd82c3081f90ad6184a6f1269b1e1454024f703c6c8bd9650c590b6a": true, // ?? 20 | "0x3204ae2cc70a052a13810f45107328d0d44ce26129a0a8655b0883aa318c306c": true, 21 | "0xd7fcee6eaa0a7ff618c41390cf0ffaffa64449150b05c1bf2f987ca42622d51b": true, 22 | "0x520f1950e63c2399341c68669f71e1374017f8781556e18ad05d078ac53162a6": true, 23 | "0xfa7d679f5ce9e0d8ce1117a39e5235ad7feb2480eb7fc403e71d8905bbd757c8": true, 24 | "0x9af01c234216f43e5fbde9e4e331d0500b7554240e3da608341892b06bc76f4e": true, 25 | "0x63bb7700cc37e3e72a46ebabfd1409a70a2665fa976c1e45846ff0a1f694dea7": true, 26 | "0x719c9b64bb19cc938c4261c3f9abc5cf0f44500ddb380a37e8f2e81a222bee04": true, 27 | "0xe61b87d941779df20703a97bb87fcc4e99034a6c990cf2206349572b607d9370": true, 28 | "0x79b670c7965f550fb3cdd38019745cb6316192e22984240bf7aa667195301a48": true, 29 | "0x4c48dfa81d7f3f428bccee692f3269460d5da772d8451e7c3e7e4e65be205dd0": true, 30 | "0x7666a44b8219c3035e6acf55596c2de20719a6477d5bc493319bb9fe4c196a5f": true, 31 | "0x4aa3d664e2f5a5a92220c5399de1ee991ea6a5fed9ea87c6a067094fd7b7456a": true, 32 | "0x86d86f43622d4af584207500b82b19618aa3465dd7398b9556e0502a59bf29c3": true, 33 | "0xefa7b21e42fcd17774f183527877e5facd050b8dcd995f6a042a2bf70e142fa3": true, 34 | "0x84c54197cace6abc5c44efe2bd130fdfcb7051a858f4dfd15061c6eae02996e9": true, 35 | "0x08019ea69808925736025b2cdd5ef0dba03b6367cec138e9a4dd2a185d8c0195": true, 36 | "0x2073be8389c8f03b49be5fc9143e599a63a27c1b544319cdf8a5713ba3476d33": true, 37 | "0x3c1e9f27c664fe8fa6574625048bc97bdf5f24d33b954bc9afa682dc82bf7f32": true, 38 | "0xb063d6eb7ee05b4f071f56860d6844f706d7a60140986b58618b1b741a573a37": true, 39 | "0xda552e809628a3572d6bdb0ae9dbc4a3a4404e990ebc29addffed884cd904c96": true, 40 | "0xed4d2919f963a651c378e89064dbb2dbbf50df8a4260d1748a05635024cacfbb": true, 41 | } 42 | -------------------------------------------------------------------------------- /handlers/indexer.go: -------------------------------------------------------------------------------- 1 | package handlers 2 | 3 | import ( 4 | "encoding/hex" 5 | "encoding/json" 6 | "errors" 7 | "math/big" 8 | "open-indexer/model" 9 | "open-indexer/utils" 10 | "os" 11 | "sort" 12 | "strings" 13 | "time" 14 | ) 15 | 16 | // The following data is only stored in memory, in practice it should be stored in a database such as mysql or mongodb 17 | // var inscriptions []*model.Inscription 18 | // var logEvents []*model.EvmLog 19 | 20 | var tokens = make(map[string]*model.Token) 21 | var tokensByHash = make(map[string]*model.Token) 22 | var tokenHolders = make(map[string]map[string]*model.DDecimal) 23 | var balances = make(map[string]map[string]*model.DDecimal) 24 | var lists = make(map[string]*model.List) 25 | 26 | var asc20Records []*model.Asc20 27 | 28 | var updatedBalances = make(map[string]string) 29 | var updatedLists = make(map[string]bool) 30 | var updatedTokens = make(map[string]bool) 31 | 32 | var inscriptionNumber uint64 = 0 33 | var asc20RecordId uint64 = 0 34 | 35 | var asc20File *os.File 36 | 37 | func mixRecords(trxs []*model.Transaction, logs []*model.EvmLog) []*model.Record { 38 | var records []*model.Record 39 | for _, trx := range trxs { 40 | var record model.Record 41 | record.IsLog = false 42 | record.Transaction = trx 43 | record.Block = trx.Block 44 | record.TransactionIndex = trx.Idx 45 | record.LogIndex = 0 46 | records = append(records, &record) 47 | } 48 | for _, log := range logs { 49 | var record model.Record 50 | record.IsLog = true 51 | record.EvmLog = log 52 | record.Block = log.Block 53 | record.TransactionIndex = log.TrxIndex 54 | record.LogIndex = log.LogIndex 55 | records = append(records, &record) 56 | } 57 | // resort 58 | sort.SliceStable(records, func(i, j int) bool { 59 | record0 := records[i] 60 | record1 := records[j] 61 | if record0.Block == record1.Block { 62 | if record0.TransactionIndex == record1.TransactionIndex { 63 | return record0.LogIndex+utils.BoolToUint32(record0.IsLog) < record1.LogIndex+utils.BoolToUint32(record1.IsLog) 64 | } 65 | return record0.TransactionIndex < record1.TransactionIndex 66 | } 67 | return record0.Block < record1.Block 68 | }) 69 | return records 70 | } 71 | 72 | func processRecords(records []*model.Record) error { 73 | if len(records) == 0 { 74 | return nil 75 | } 76 | logger.Println("process ", len(records), " records") 77 | 78 | var err error 79 | for _, record := range records { 80 | if record.IsLog { 81 | err = indexLog(record.EvmLog) 82 | } else { 83 | err = indexTransaction(record.Transaction) 84 | } 85 | if err != nil { 86 | return err 87 | } 88 | } 89 | return nil 90 | } 91 | 92 | func indexTransaction(trx *model.Transaction) error { 93 | // filter 94 | if ignoreHashes[trx.Id] { 95 | return nil 96 | } 97 | // data:, 98 | if !strings.HasPrefix(trx.Input, "0x646174613a") { 99 | return nil 100 | } 101 | bytes, err := hex.DecodeString(trx.Input[2:]) 102 | if err != nil { 103 | logger.Warn("inscribe err", err, " at block ", trx.Block, ":", trx.Idx) 104 | return nil 105 | } 106 | input := string(bytes) 107 | 108 | sepIdx := strings.Index(input, ",") 109 | if sepIdx == -1 || sepIdx == len(input)-1 { 110 | return nil 111 | } 112 | contentType := "text/plain" 113 | if sepIdx > 5 { 114 | contentType = input[5:sepIdx] 115 | } 116 | content := input[sepIdx+1:] 117 | 118 | // save inscription 119 | inscriptionNumber++ 120 | var inscription model.Inscription 121 | inscription.Number = inscriptionNumber 122 | inscription.Id = trx.Id 123 | inscription.From = trx.From 124 | inscription.To = trx.To 125 | inscription.Block = trx.Block 126 | inscription.Idx = trx.Idx 127 | inscription.Timestamp = trx.Timestamp 128 | inscription.ContentType = contentType 129 | inscription.Content = content 130 | 131 | if trx.To != "" { 132 | if err := handleProtocols(&inscription); err != nil { 133 | logger.Info("error at ", inscription.Number) 134 | return err 135 | } 136 | } 137 | 138 | // todo: save inscription 139 | // inscriptions = append(inscriptions, &inscription) 140 | 141 | return nil 142 | } 143 | 144 | func indexLog(log *model.EvmLog) error { 145 | if len(log.Topics) < 3 { 146 | return nil 147 | } 148 | var topicType uint8 149 | if log.Topics[0] == "0x8cdf9e10a7b20e7a9c4e778fc3eb28f2766e438a9856a62eac39fbd2be98cbc2" { 150 | // avascriptions_protocol_TransferASC20Token(address,address,string,uint256) 151 | topicType = 1 152 | } else if log.Topics[0] == "0xe2750d6418e3719830794d3db788aa72febcd657bcd18ed8f1facdbf61a69a9a" { 153 | // avascriptions_protocol_TransferASC20TokenForListing(address,address,bytes32) 154 | topicType = 2 155 | } else if log.Topics[0] == "0xb2de1aec31252e2ec7dd0493ffe16c32a9f86939071394c278aad324fea582e2" { 156 | // avascriptions_protocol_ListASC20Token(address,address,string,uint256) 157 | topicType = 3 158 | } else { 159 | return nil 160 | } 161 | 162 | var asc20 model.Asc20 163 | asc20RecordId++ 164 | asc20.Id = asc20RecordId 165 | asc20.Number = 0 166 | asc20.Operation = "transfer" 167 | asc20.From = utils.TopicToAddress(log.Topics[1]) 168 | asc20.To = utils.TopicToAddress(log.Topics[2]) 169 | asc20.Block = log.Block 170 | asc20.Timestamp = log.Timestamp 171 | asc20.Hash = log.Hash 172 | if topicType == 1 || topicType == 3 { 173 | // transfer 174 | if asc20.From == log.Address { 175 | hash := "-" 176 | if len(log.Topics) > 3 { 177 | topic3 := log.Topics[3] 178 | if len(topic3) > 2 { 179 | hash = topic3[2:] 180 | } 181 | } 182 | token, ok := tokensByHash[hash] 183 | if ok { 184 | asc20.Tick = token.Tick 185 | 186 | var err error 187 | asc20.Amount, asc20.Precision, err = model.NewDecimalFromString(utils.TopicToBigInt(log.Data).String()) 188 | if err != nil { 189 | asc20.Valid = -56 190 | } else { 191 | if topicType == 1 { 192 | // do transfer 193 | asc20.Valid, err = _transferToken(&asc20) 194 | } else { 195 | // check list 196 | if asc20.Block < 42000000 { 197 | // No support yet 198 | asc20.Valid = -58 199 | } else { 200 | _, exists := lists[asc20.Hash] 201 | if exists { 202 | // only the first valid in the same block 203 | asc20.Valid = -57 204 | } else { 205 | // do list 206 | asc20.Valid, err = _listToken(&asc20) 207 | } 208 | } 209 | } 210 | if err != nil { 211 | return err 212 | } 213 | } 214 | } else { 215 | asc20.Valid = -51 216 | } 217 | } else { 218 | asc20.Valid = -52 219 | logger.Warningln("failed to validate transfer from:", asc20.From, "address:", log.Address) 220 | } 221 | } else if topicType == 2 { 222 | // exchange 223 | asc20.Operation = "exchange" 224 | 225 | list, ok := lists[log.Data] 226 | if ok { 227 | if list.Owner == asc20.From && list.Exchange == log.Address { 228 | asc20.Tick = list.Tick 229 | asc20.Amount = list.Amount 230 | asc20.Precision = list.Precision 231 | 232 | // update from to exchange 233 | asc20.From = log.Address 234 | 235 | // do transfer 236 | var err error 237 | asc20.Valid, err = exchangeToken(list, asc20.To) 238 | if err != nil { 239 | return err 240 | } 241 | } else { 242 | if list.Owner != asc20.From { 243 | asc20.Valid = -54 244 | logger.Warningln("failed to validate transfer from:", asc20.From, list.Owner) 245 | } else { 246 | asc20.Valid = -55 247 | logger.Warningln("failed to validate exchange:", log.Address, list.Exchange) 248 | } 249 | } 250 | 251 | } else { 252 | asc20.Valid = -53 253 | logger.Warningln("failed to transfer, list not found, id:", log.Data) 254 | } 255 | } 256 | 257 | // save asc20 record 258 | asc20Records = append(asc20Records, &asc20) 259 | 260 | // todo: save log 261 | // logEvents = append(l ogEvents, log) 262 | return nil 263 | } 264 | 265 | func handleProtocols(inscription *model.Inscription) error { 266 | content := strings.TrimSpace(inscription.Content) 267 | if len(content) > 0 && content[0] == '{' { 268 | var protoData map[string]string 269 | err := json.Unmarshal([]byte(content), &protoData) 270 | if err != nil { 271 | //logger.Info("json parse error: ", err, ", at ", inscription.Number) 272 | } else { 273 | value, ok := protoData["p"] 274 | if ok && strings.TrimSpace(value) != "" { 275 | protocol := strings.ToLower(value) 276 | if protocol == "asc-20" { 277 | var asc20 model.Asc20 278 | asc20RecordId++ 279 | asc20.Id = asc20RecordId 280 | asc20.Number = inscription.Number 281 | asc20.From = inscription.From 282 | asc20.To = inscription.To 283 | asc20.Block = inscription.Block 284 | asc20.Timestamp = inscription.Timestamp 285 | asc20.Hash = inscription.Id 286 | if value, ok = protoData["tick"]; ok { 287 | asc20.Tick = value 288 | } 289 | if value, ok = protoData["op"]; ok { 290 | asc20.Operation = value 291 | } 292 | 293 | var err error 294 | if strings.TrimSpace(asc20.Tick) == "" { 295 | asc20.Valid = -1 // empty tick 296 | } else if len(asc20.Tick) > 18 { 297 | asc20.Valid = -2 // too long tick 298 | } else if asc20.Operation == "deploy" { 299 | asc20.Valid, err = deployToken(&asc20, protoData) 300 | } else if asc20.Operation == "mint" { 301 | asc20.Valid, err = mintToken(&asc20, protoData) 302 | } else if asc20.Operation == "transfer" { 303 | asc20.Valid, err = transferToken(&asc20, protoData) 304 | } else if asc20.Operation == "list" { 305 | asc20.Valid, err = listToken(&asc20, protoData) 306 | } else { 307 | asc20.Valid = -3 // wrong operation 308 | } 309 | if err != nil { 310 | return err 311 | } 312 | 313 | // save asc20 records 314 | asc20Records = append(asc20Records, &asc20) 315 | 316 | return nil 317 | } 318 | } 319 | } 320 | } 321 | return nil 322 | } 323 | 324 | func deployToken(asc20 *model.Asc20, params map[string]string) (int8, error) { 325 | 326 | value, ok := params["max"] 327 | if !ok { 328 | return -11, nil 329 | } 330 | max, precision, err1 := model.NewDecimalFromString(value) 331 | if err1 != nil { 332 | return -12, nil 333 | } 334 | if precision != 0 { 335 | // Currently only 0 precision is supported 336 | return -12, nil 337 | } 338 | value, ok = params["lim"] 339 | if !ok { 340 | return -13, nil 341 | } 342 | limit, _, err2 := model.NewDecimalFromString(value) 343 | if err2 != nil { 344 | return -14, nil 345 | } 346 | if max.Sign() <= 0 || limit.Sign() <= 0 { 347 | return -15, nil 348 | } 349 | if utils.ParseInt64(max.String()) == 0 || utils.ParseInt64(limit.String()) == 0 { 350 | return -15, nil 351 | } 352 | if max.Cmp(limit) < 0 { 353 | return -16, nil 354 | } 355 | 356 | asc20.Amount = max 357 | asc20.Precision = precision 358 | asc20.Limit = limit 359 | 360 | // 已经 deploy 361 | asc20.Tick = strings.TrimSpace(asc20.Tick) // trim tick 362 | lowerTick := strings.ToLower(asc20.Tick) 363 | _, exists := tokens[lowerTick] 364 | if exists { 365 | //logger.Info("token ", asc20.Tick, " has deployed at ", asc20.Number) 366 | return -17, nil 367 | } 368 | 369 | logger.Info("token ", asc20.Tick, " deployed at ", asc20.Number) 370 | 371 | token := &model.Token{ 372 | Tick: asc20.Tick, 373 | Number: asc20.Number, 374 | Precision: precision, 375 | Max: max, 376 | Limit: limit, 377 | Minted: model.NewDecimal(), 378 | Progress: 0, 379 | CreatedAt: asc20.Timestamp, 380 | CompletedAt: uint64(0), 381 | Hash: utils.Keccak256(strings.ToLower(asc20.Tick)), 382 | } 383 | updatedTokens[lowerTick] = true 384 | 385 | // save 386 | tokens[lowerTick] = token 387 | tokenHolders[lowerTick] = make(map[string]*model.DDecimal) 388 | tokensByHash[token.Hash] = token 389 | 390 | return 1, nil 391 | } 392 | 393 | func mintToken(asc20 *model.Asc20, params map[string]string) (int8, error) { 394 | value, ok := params["amt"] 395 | if !ok { 396 | return -21, nil 397 | } 398 | amt, precision, err := model.NewDecimalFromString(value) 399 | if err != nil { 400 | return -22, nil 401 | } 402 | 403 | asc20.Amount = amt 404 | 405 | // check token 406 | lowerTick := strings.ToLower(asc20.Tick) 407 | token, exists := tokens[lowerTick] 408 | if !exists { 409 | return -23, nil 410 | } 411 | asc20.Tick = token.Tick 412 | 413 | // check precision 414 | if precision > token.Precision { 415 | return -24, nil 416 | } 417 | 418 | if amt.Sign() <= 0 { 419 | return -25, nil 420 | } 421 | 422 | if amt.Cmp(token.Limit) == 1 { 423 | return -26, nil 424 | } 425 | 426 | var left = token.Max.Sub(token.Minted) 427 | 428 | if left.Cmp(amt) == -1 { 429 | if left.Sign() > 0 { 430 | amt = left 431 | } else { 432 | // exceed max 433 | return -27, nil 434 | } 435 | } 436 | // update amount 437 | asc20.Amount = amt 438 | asc20.Precision = precision 439 | 440 | newHolder, err := addBalance(asc20.To, lowerTick, amt) 441 | if err != nil { 442 | return 0, err 443 | } 444 | 445 | // update token 446 | token.Minted = token.Minted.Add(amt) 447 | token.Trxs++ 448 | 449 | if token.Minted.Cmp(token.Max) >= 0 { 450 | token.Progress = 1000000 451 | } else { 452 | progress, _ := new(big.Int).SetString(token.Minted.String(), 10) 453 | max, _ := new(big.Int).SetString(token.Max.String(), 10) 454 | progress.Mul(progress, new(big.Int).SetInt64(1000000)) 455 | progress.Div(progress, max) 456 | token.Progress = uint32(progress.Int64()) 457 | } 458 | 459 | if token.Minted.Cmp(token.Max) == 0 { 460 | token.CompletedAt = uint64(time.Now().Unix()) 461 | } 462 | if newHolder { 463 | token.Holders++ 464 | } 465 | 466 | updatedTokens[lowerTick] = true 467 | 468 | return 1, err 469 | } 470 | 471 | func transferToken(asc20 *model.Asc20, params map[string]string) (int8, error) { 472 | value, ok := params["amt"] 473 | if !ok { 474 | return -31, nil 475 | } 476 | amt, precision, err := model.NewDecimalFromString(value) 477 | if err != nil { 478 | return -32, nil 479 | } 480 | 481 | asc20.Amount = amt 482 | asc20.Precision = precision 483 | 484 | return _transferToken(asc20) 485 | } 486 | 487 | func listToken(asc20 *model.Asc20, params map[string]string) (int8, error) { 488 | value, ok := params["amt"] 489 | if !ok { 490 | return -31, nil 491 | } 492 | amt, precision, err := model.NewDecimalFromString(value) 493 | if err != nil { 494 | return -32, nil 495 | } 496 | 497 | asc20.Amount = amt 498 | asc20.Precision = precision 499 | 500 | return _listToken(asc20) 501 | } 502 | 503 | func _listToken(asc20 *model.Asc20) (int8, error) { 504 | 505 | // check token 506 | lowerTick := strings.ToLower(asc20.Tick) 507 | token, exists := tokens[lowerTick] 508 | if !exists { 509 | return -33, nil 510 | } 511 | asc20.Tick = token.Tick 512 | 513 | // check precision 514 | if asc20.Precision > token.Precision { 515 | return -34, nil 516 | } 517 | 518 | if asc20.Amount.Sign() <= 0 { 519 | return -35, nil 520 | } 521 | 522 | if asc20.From == asc20.To { 523 | // list to self 524 | return -36, nil 525 | } 526 | 527 | // sub balance 528 | reduceHolder, err := subBalance(asc20.From, lowerTick, asc20.Amount) 529 | if err != nil { 530 | if err.Error() == "insufficient balance" { 531 | return -37, nil 532 | } 533 | return 0, err 534 | } 535 | 536 | // add list 537 | var list model.List 538 | list.InsId = asc20.Hash 539 | list.Owner = asc20.From 540 | list.Exchange = asc20.To 541 | list.Tick = token.Tick 542 | list.Amount = asc20.Amount 543 | list.Precision = asc20.Precision 544 | 545 | lists[list.InsId] = &list 546 | updatedLists[list.InsId] = true 547 | 548 | token.Trxs++ 549 | 550 | if reduceHolder { 551 | token.Holders-- 552 | } 553 | 554 | updatedTokens[lowerTick] = true 555 | 556 | return 1, err 557 | } 558 | 559 | func exchangeToken(list *model.List, sendTo string) (int8, error) { 560 | 561 | // add balance 562 | newHolder, err := addBalance(sendTo, list.Tick, list.Amount) 563 | if err != nil { 564 | return 0, err 565 | } 566 | 567 | // update token 568 | lowerTick := strings.ToLower(list.Tick) 569 | token, exists := tokens[lowerTick] 570 | if !exists { 571 | return -33, nil 572 | } 573 | 574 | token.Trxs++ 575 | 576 | if newHolder { 577 | token.Holders++ 578 | } 579 | 580 | updatedTokens[lowerTick] = true 581 | 582 | // delete list from lists 583 | delete(lists, list.InsId) 584 | updatedLists[list.InsId] = false 585 | 586 | //logger.Println("exchange", list.Amount) 587 | return 1, err 588 | } 589 | 590 | func _transferToken(asc20 *model.Asc20) (int8, error) { 591 | 592 | // check token 593 | lowerTick := strings.ToLower(asc20.Tick) 594 | token, exists := tokens[lowerTick] 595 | if !exists { 596 | return -33, nil 597 | } 598 | asc20.Tick = token.Tick 599 | 600 | if asc20.Precision > token.Precision { 601 | return -34, nil 602 | } 603 | 604 | if asc20.Amount.Sign() <= 0 { 605 | return -35, nil 606 | } 607 | 608 | if asc20.From == "" || asc20.To == "" { 609 | // send to self 610 | return -9, nil 611 | } 612 | if asc20.From == asc20.To { 613 | // send to self 614 | return -36, nil 615 | } 616 | 617 | // From 618 | reduceHolder, err := subBalance(asc20.From, lowerTick, asc20.Amount) 619 | if err != nil { 620 | if err.Error() == "insufficient balance" { 621 | return -37, nil 622 | } 623 | return 0, err 624 | } 625 | 626 | // To 627 | newHolder, err := addBalance(asc20.To, lowerTick, asc20.Amount) 628 | if err != nil { 629 | return 0, err 630 | } 631 | 632 | // update token 633 | if reduceHolder { 634 | token.Holders-- 635 | } 636 | if newHolder { 637 | token.Holders++ 638 | } 639 | token.Trxs++ 640 | 641 | updatedTokens[lowerTick] = true 642 | 643 | return 1, err 644 | } 645 | 646 | func subBalance(owner string, tick string, amount *model.DDecimal) (bool, error) { 647 | lowerTick := strings.ToLower(tick) 648 | _, exists := tokens[lowerTick] 649 | if !exists { 650 | return false, errors.New("token not found") 651 | } 652 | fromBalance, ok := tokenHolders[lowerTick][owner] 653 | if !ok || fromBalance.Sign() == 0 || amount.Cmp(fromBalance) == 1 { 654 | return false, errors.New("insufficient balance") 655 | } 656 | 657 | fromBalance = fromBalance.Sub(amount) 658 | 659 | var reduceHolder = false 660 | if fromBalance.Sign() == 0 { 661 | reduceHolder = true 662 | } 663 | 664 | // save 665 | tokenHolders[lowerTick][owner] = fromBalance 666 | updatedBalances[owner+lowerTick] = fromBalance.String() 667 | 668 | if _, ok := balances[owner]; !ok { 669 | balances[owner] = make(map[string]*model.DDecimal) 670 | } 671 | balances[owner][lowerTick] = fromBalance 672 | 673 | return reduceHolder, nil 674 | } 675 | 676 | func addBalance(owner string, tick string, amount *model.DDecimal) (bool, error) { 677 | lowerTick := strings.ToLower(tick) 678 | _, exists := tokens[lowerTick] 679 | if !exists { 680 | return false, errors.New("token not found") 681 | } 682 | var newHolder = false 683 | toBalance, ok := tokenHolders[lowerTick][owner] 684 | if !ok { 685 | toBalance = model.NewDecimal() 686 | newHolder = true 687 | } 688 | 689 | toBalance = toBalance.Add(amount) 690 | 691 | // save 692 | tokenHolders[lowerTick][owner] = toBalance 693 | updatedBalances[owner+lowerTick] = toBalance.String() 694 | 695 | if _, ok := balances[owner]; !ok { 696 | balances[owner] = make(map[string]*model.DDecimal) 697 | } 698 | balances[owner][lowerTick] = toBalance 699 | 700 | return newHolder, nil 701 | } 702 | -------------------------------------------------------------------------------- /handlers/rpc.go: -------------------------------------------------------------------------------- 1 | package handlers 2 | 3 | import ( 4 | "errors" 5 | "fmt" 6 | "github.com/gofiber/fiber/v2" 7 | "github.com/gofiber/fiber/v2/middleware/cors" 8 | "github.com/syndtr/goleveldb/leveldb/util" 9 | "google.golang.org/protobuf/proto" 10 | "open-indexer/model" 11 | "open-indexer/model/serialize" 12 | "open-indexer/utils" 13 | "regexp" 14 | "strings" 15 | "time" 16 | ) 17 | 18 | var app *fiber.App 19 | 20 | func StartRpc() { 21 | 22 | rpcCfg := cfg.Section("mongo") 23 | rpcHost := rpcCfg.Key("host").String() 24 | rpcPort := rpcCfg.Key("port").MustUint(3030) 25 | 26 | if rpcHost == "*" { 27 | rpcHost = "" 28 | } 29 | 30 | fiberCfg := fiber.Config{ 31 | ErrorHandler: func(c *fiber.Ctx, err error) error { 32 | return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{ 33 | "code": fiber.StatusInternalServerError, 34 | "msg": err.Error()}, 35 | ) 36 | }, 37 | } 38 | 39 | app = fiber.New(fiberCfg) 40 | app.Use(cors.New()) 41 | //app.Use(notFound) 42 | 43 | api := app.Group("/v1", wrapResult) 44 | 45 | api.Get("/tokens/", getTokens) 46 | api.Get("/token/:tick", getToken) 47 | api.Get("/token/:tick/holders", getTokenHolders) 48 | api.Get("/address/:addr", getAddress) 49 | api.Get("/address/:addr/:tick", getAddress) 50 | api.Get("/records", getRecordsByBlock) 51 | //api.Get("/records-by-address/:address", getRecordsByAddress) 52 | api.Get("/records-by-txid/:txid", getRecordsByTxId) 53 | api.Get("/snapshot/create", createSnapshot) 54 | 55 | app.Listen(fmt.Sprintf("%s:%d", rpcHost, rpcPort)) 56 | } 57 | 58 | func StopRpc() { 59 | if app != nil { 60 | app.ShutdownWithTimeout(time.Duration(5) * time.Second) // 5s 61 | app = nil 62 | } 63 | 64 | StopSuccessCount++ 65 | logger.Println("rpc stopped") 66 | } 67 | 68 | func isNumeric(s string) bool { 69 | re := regexp.MustCompile(`^\d+(\.\d+)?$`) 70 | return re.MatchString(s) 71 | } 72 | 73 | func wrapResult(c *fiber.Ctx) error { 74 | if db == nil { 75 | return errors.New("database is closed") 76 | } else { 77 | err := c.Next() 78 | if err != nil { 79 | return err 80 | } 81 | } 82 | body := string(c.Response().Body()) 83 | resp := fmt.Sprintf(`{"code":200,"data":%s}`, body) 84 | c.Set("Content-type", "application/json; charset=utf-8") 85 | c.Status(fiber.StatusOK).SendString(resp) 86 | return nil 87 | } 88 | 89 | func getTokens(c *fiber.Ctx) error { 90 | allTokens := make([]*model.Token, 0, len(tokens)) 91 | for _, token := range tokens { 92 | allTokens = append(allTokens, token) 93 | } 94 | return c.JSON(allTokens) 95 | } 96 | 97 | func getToken(c *fiber.Ctx) error { 98 | tick := strings.ToLower(c.Params("tick")) 99 | token, ok := tokens[tick] 100 | if !ok { 101 | return errors.New("token not found") 102 | } 103 | return c.JSON(token) 104 | } 105 | 106 | func getTokenHolders(c *fiber.Ctx) error { 107 | tick := strings.ToLower(c.Params("tick")) 108 | holders, ok := tokenHolders[tick] 109 | if !ok { 110 | return errors.New("token not found") 111 | } 112 | return c.JSON(holders) 113 | } 114 | 115 | func getAddress(c *fiber.Ctx) error { 116 | addr := strings.ToLower(c.Params("addr")) 117 | tick := strings.ToLower(c.Params("tick")) 118 | addrBalances, ok := balances[addr] 119 | if !ok { 120 | return c.SendString("[]") 121 | } 122 | if tick != "" { 123 | balance, ok := addrBalances[tick] 124 | if ok { 125 | return c.SendString(balance.String()) 126 | } else { 127 | return errors.New("this address doesn't have this token") 128 | } 129 | } 130 | return c.JSON(addrBalances) 131 | } 132 | 133 | func getRecordsByBlock(c *fiber.Ctx) error { 134 | if db == nil { 135 | return errors.New("database is closed") 136 | } 137 | // read tokens 138 | fromBlock := utils.ParseInt64(c.Query("fromBlock", "0")) 139 | toBlock := utils.ParseInt64(c.Query("toBlock", "0")) 140 | if fromBlock <= 0 { 141 | return errors.New("fromBlock parameter error") 142 | } 143 | if toBlock <= 0 || toBlock < fromBlock { 144 | return errors.New("toBlock parameter error") 145 | } 146 | 147 | fromKey := fmt.Sprintf("r-%d-0", fromBlock) 148 | //toKey := fmt.Sprintf("r-%d", toBlock) 149 | iter := db.NewIterator(nil, nil) 150 | 151 | var records []*model.Asc20 152 | var count uint32 153 | for ok := iter.Seek([]byte(fromKey)); ok; ok = iter.Next() { 154 | keys := strings.Split(string(iter.Key()), "-") 155 | block := utils.ParseInt64(keys[1]) 156 | if block > toBlock { 157 | break 158 | } 159 | protoRecord := &serialize.ProtoRecord{} 160 | err := proto.Unmarshal(iter.Value(), protoRecord) 161 | if err != nil { 162 | return errors.New("read token error") 163 | } 164 | record := model.Asc20FromProto(protoRecord) 165 | records = append(records, record) 166 | if count++; count >= 10000 { 167 | // max item is 10000 168 | break 169 | } 170 | } 171 | iter.Release() 172 | 173 | return c.JSON(records) 174 | } 175 | 176 | //func getRecordsByAddress(c fiber.Ctx) error { 177 | // return nil 178 | //} 179 | 180 | func getRecordsByTxId(c *fiber.Ctx) error { 181 | txid := strings.ToLower(c.Params("txid")) 182 | if !strings.HasPrefix(txid, "0x") || len(txid) != 66 { 183 | return errors.New("incorrect txid format") 184 | } 185 | intBytes, err := db.Get([]byte("h-"+txid), nil) 186 | if err != nil { 187 | return errors.New("txid not found") 188 | } 189 | block := utils.BytesToUint64(intBytes) 190 | iter := db.NewIterator(util.BytesPrefix([]byte(fmt.Sprintf("r-%d-", block))), nil) 191 | var records []*model.Asc20 192 | for iter.Next() { 193 | protoRecord := &serialize.ProtoRecord{} 194 | err = proto.Unmarshal(iter.Value(), protoRecord) 195 | if err != nil { 196 | return err 197 | } 198 | record := model.Asc20FromProto(protoRecord) 199 | if record.Hash == txid { 200 | records = append(records, record) 201 | } 202 | } 203 | return c.JSON(records) 204 | } 205 | 206 | func createSnapshot(c *fiber.Ctx) error { 207 | block := c.Query("block", "0") 208 | if block == "0" { 209 | createSnapshotFlag = true 210 | } else { 211 | createSnapshotBlock = uint64(utils.ParseInt64(block)) 212 | } 213 | return c.JSON("ok") 214 | } 215 | -------------------------------------------------------------------------------- /handlers/snapshot.go: -------------------------------------------------------------------------------- 1 | package handlers 2 | 3 | import ( 4 | "bufio" 5 | "google.golang.org/protobuf/proto" 6 | "io" 7 | "open-indexer/model" 8 | "open-indexer/model/serialize" 9 | "open-indexer/utils" 10 | "os" 11 | "strconv" 12 | "strings" 13 | "time" 14 | ) 15 | 16 | func InitFromSnapshot() { 17 | if snapFile == "" { 18 | return 19 | } 20 | if strings.HasSuffix(snapFile, ".txt") { 21 | readSnapshotFromText() 22 | return 23 | } 24 | 25 | // read from binary 26 | file, err := os.Open(snapFile) 27 | if err != nil { 28 | panic(err) 29 | } 30 | defer file.Close() 31 | 32 | fs, err := file.Stat() 33 | if err != nil { 34 | panic(err) 35 | } 36 | buffer := make([]byte, fs.Size()) 37 | _, err = io.ReadFull(file, buffer) 38 | if err != nil { 39 | panic(err) 40 | } 41 | 42 | protoSnapshot := &serialize.Snapshot{} 43 | err = proto.Unmarshal(buffer, protoSnapshot) 44 | if err != nil { 45 | panic(err) 46 | } 47 | 48 | // save data 49 | syncFromBlock = protoSnapshot.Block 50 | inscriptionNumber = protoSnapshot.Number 51 | asc20RecordId = protoSnapshot.RecordId 52 | 53 | var tokenCount uint64 54 | var listCount uint64 55 | var holderCount uint64 56 | 57 | for idx := range protoSnapshot.Tokens { 58 | token := model.TokenFromProto(protoSnapshot.Tokens[idx]) 59 | 60 | lowerTick := strings.ToLower(token.Tick) 61 | tokens[lowerTick] = token 62 | tokensByHash[token.Hash] = token 63 | 64 | tokenHolders[lowerTick] = make(map[string]*model.DDecimal) 65 | 66 | updatedTokens[lowerTick] = true 67 | 68 | tokenCount++ 69 | } 70 | 71 | for idx := range protoSnapshot.Lists { 72 | list := model.ListFromProto(protoSnapshot.Lists[idx]) 73 | lists[list.InsId] = list 74 | 75 | updatedLists[list.InsId] = true 76 | 77 | listCount++ 78 | } 79 | 80 | for idx := range protoSnapshot.Balances { 81 | userBalances := protoSnapshot.Balances[idx] 82 | address := utils.BytesToHexStr(userBalances.Address) 83 | if address == "0x" { 84 | continue 85 | } 86 | balances[address] = make(map[string]*model.DDecimal) 87 | for idx2 := range userBalances.Balances { 88 | tickBalance := userBalances.Balances[idx2] 89 | 90 | lowerTick := strings.ToLower(tickBalance.Tick) 91 | if lowerTick == "" { 92 | continue 93 | } 94 | balance, _, _ := model.NewDecimalFromString(tickBalance.Amount) 95 | 96 | balances[address][lowerTick] = balance 97 | tokenHolders[lowerTick][address] = balance 98 | 99 | updatedBalances[address+lowerTick] = tickBalance.Amount 100 | 101 | holderCount++ 102 | } 103 | } 104 | 105 | logger.Printf("init from snapshot, block %d, inscriptionNumber %d, asc20RecordId %d, tokens %d lists %d holders %d", syncFromBlock, inscriptionNumber, asc20RecordId, tokenCount, listCount, holderCount) 106 | 107 | // todo: set 108 | //var updatedTokens 109 | //var updatedBalances = make(map[string]string) 110 | //var updatedLists = make(map[string]bool) 111 | 112 | saveToStorage(syncFromBlock) 113 | 114 | syncFromBlock++ 115 | } 116 | 117 | func readSnapshotFromText() { 118 | file, err := os.Open(snapFile) 119 | if err != nil { 120 | panic(err) 121 | } 122 | defer file.Close() 123 | scanner := bufio.NewScanner(file) 124 | max := 100 * 1024 * 1024 // 100m 125 | buf := make([]byte, max) 126 | scanner.Buffer(buf, max) 127 | 128 | nowType := 0 129 | tokenCount := 0 130 | listCount := 0 131 | holderCount := 0 132 | for scanner.Scan() { 133 | line := scanner.Text() 134 | if strings.HasPrefix(line, "-- ") { 135 | dataType := line[3:] 136 | if dataType == "block" { 137 | nowType = 1 138 | } else if dataType == "tokens" { 139 | nowType = 2 140 | } else if dataType == "lists" { 141 | nowType = 3 142 | } else if dataType == "balances" { 143 | nowType = 4 144 | } 145 | continue 146 | } 147 | if nowType == 1 { 148 | syncFromBlock = uint64(utils.ParseInt64(line)) 149 | if scanner.Scan() { 150 | inscriptionNumber = uint64(utils.ParseInt64(scanner.Text())) 151 | } 152 | if scanner.Scan() { 153 | asc20RecordId = uint64(utils.ParseInt64(scanner.Text())) 154 | } 155 | } else if nowType == 2 { 156 | tokenCount++ 157 | readToken(line) 158 | } else if nowType == 3 { 159 | listCount++ 160 | readList(line) 161 | } else if nowType == 4 { 162 | holderCount++ 163 | readBalance(line) 164 | } 165 | } 166 | 167 | logger.Printf("init from snapshot, block %d, inscriptionNumber %d, asc20RecordId %d, tokens %d lists %d holders %d", syncFromBlock, inscriptionNumber, asc20RecordId, tokenCount, listCount, holderCount) 168 | saveToStorage(syncFromBlock) 169 | 170 | syncFromBlock++ 171 | } 172 | 173 | func readToken(line string) { 174 | row := strings.Split(line, ",") 175 | 176 | if len(row) != 12 { 177 | panic("invalid token format:" + line) 178 | } 179 | var token model.Token 180 | token.Tick = strings.Replace(row[0], "[*_*]", ",", -1) 181 | token.Number = uint64(utils.ParseInt64(row[1])) 182 | token.Precision = int(utils.ParseInt32(row[2])) 183 | token.Max, _, _ = model.NewDecimalFromString(row[3]) 184 | token.Limit, _, _ = model.NewDecimalFromString(row[4]) 185 | token.Minted, _, _ = model.NewDecimalFromString(row[5]) 186 | token.Progress = uint32(utils.ParseInt32(row[6])) 187 | token.Holders = uint32(utils.ParseInt32(row[7])) 188 | token.Trxs = uint32(utils.ParseInt32(row[8])) 189 | token.CreatedAt = uint64(utils.ParseInt64(row[9])) 190 | token.CompletedAt = uint64(utils.ParseInt64(row[10])) 191 | token.Hash = row[11] 192 | 193 | lowerTick := strings.ToLower(token.Tick) 194 | tokens[lowerTick] = &token 195 | tokensByHash[token.Hash] = &token 196 | 197 | tokenHolders[lowerTick] = make(map[string]*model.DDecimal) 198 | 199 | updatedTokens[lowerTick] = true 200 | } 201 | 202 | func readList(line string) { 203 | row := strings.Split(line, ",") 204 | 205 | if len(row) != 6 { 206 | panic("invalid list format:" + line) 207 | } 208 | 209 | var list model.List 210 | list.InsId = row[0] 211 | list.Owner = row[1] 212 | list.Exchange = row[2] 213 | list.Tick = strings.Replace(row[3], "[*_*]", ",", -1) 214 | list.Amount, _, _ = model.NewDecimalFromString(row[4]) 215 | list.Precision = int(utils.ParseInt32(row[5])) 216 | 217 | lists[list.InsId] = &list 218 | 219 | updatedLists[list.InsId] = true 220 | } 221 | 222 | func readBalance(line string) { 223 | row := strings.Split(line, ",") 224 | 225 | if len(row) != 3 { 226 | panic("invalid balance format:" + line) 227 | } 228 | 229 | tick := strings.Replace(row[0], "[*_*]", ",", -1) 230 | lowerTick := strings.ToLower(tick) 231 | address := row[1] 232 | balance, _, _ := model.NewDecimalFromString(row[2]) 233 | 234 | tokenHolders[tick][address] = balance 235 | 236 | if _, ok := balances[address]; !ok { 237 | balances[address] = make(map[string]*model.DDecimal) 238 | } 239 | balances[address][tick] = balance 240 | 241 | updatedBalances[address+lowerTick] = row[2] 242 | } 243 | 244 | func snapshot(block uint64) { 245 | start := time.Now().UnixMilli() 246 | logger.Println("save snapshot at ", block) 247 | snapshotFile := "./snapshots/snap-" + strconv.FormatUint(block, 10) + ".bin" 248 | file, err := os.OpenFile(snapshotFile, os.O_RDWR|os.O_CREATE|os.O_TRUNC, 0777) 249 | if err != nil { 250 | return 251 | } 252 | 253 | msgTokens := make([]*serialize.ProtoToken, len(tokens)) 254 | msgLists := make([]*serialize.ProtoList, len(lists)) 255 | msgBalances := make([]*serialize.UserBalance, len(balances)) 256 | 257 | // save tokens 258 | var idx = uint64(0) 259 | for _, token := range tokens { 260 | msgTokens[idx] = token.ToProtoToken() 261 | idx++ 262 | } 263 | 264 | // save lists 265 | idx = 0 266 | for _, list := range lists { 267 | msgLists[idx] = list.ToProtoList() 268 | idx++ 269 | } 270 | 271 | // save balance 272 | idx = 0 273 | var balanceCount = uint64(0) 274 | for address, userBalances := range balances { 275 | msgUserBalances := make([]*serialize.TickBalance, len(userBalances)) 276 | var idx2 = uint64(0) 277 | for tick, balance := range userBalances { 278 | if balance.Sign() == 0 { 279 | continue 280 | } 281 | msgUserBalances[idx2] = &serialize.TickBalance{ 282 | Tick: tick, 283 | Amount: balance.String(), 284 | } 285 | idx2++ 286 | balanceCount++ 287 | } 288 | msgBalances[idx] = &serialize.UserBalance{ 289 | Address: utils.HexStrToBytes(address), 290 | Balances: msgUserBalances, 291 | } 292 | idx++ 293 | } 294 | 295 | msgSnapshot := &serialize.Snapshot{ 296 | Block: block, 297 | Number: inscriptionNumber, 298 | RecordId: asc20RecordId, 299 | Tokens: msgTokens, 300 | Lists: msgLists, 301 | Balances: msgBalances, 302 | } 303 | snapBytes, err := proto.Marshal(msgSnapshot) 304 | if err != nil { 305 | panic("snapshot error: " + err.Error()) 306 | } 307 | file.Write(snapBytes) 308 | 309 | costs := time.Now().UnixMilli() - start 310 | logger.Println("save", len(tokens), " tokens, ", len(lists), " lists, ", balanceCount, " balances successfully costs ", costs, "ms at ", block) 311 | } 312 | -------------------------------------------------------------------------------- /handlers/storage.go: -------------------------------------------------------------------------------- 1 | package handlers 2 | 3 | import ( 4 | "errors" 5 | "fmt" 6 | "github.com/syndtr/goleveldb/leveldb" 7 | "github.com/syndtr/goleveldb/leveldb/util" 8 | "google.golang.org/protobuf/proto" 9 | "open-indexer/model" 10 | "open-indexer/model/serialize" 11 | "open-indexer/utils" 12 | "strings" 13 | ) 14 | 15 | func initFromStorage() error { 16 | // read block 17 | value, err := db.Get([]byte("h-block"), nil) 18 | if err != nil { 19 | if strings.Index(err.Error(), "not found") >= 0 { 20 | // empty database 21 | logger.Println("start with empty database") 22 | return nil 23 | } 24 | return err 25 | } 26 | 27 | blockNumber := utils.BytesToUint64(value) 28 | if blockNumber == 0 { 29 | return errors.New("read block from database error") 30 | } 31 | syncFromBlock = blockNumber + 1 32 | 33 | value, err = db.Get([]byte("h-number"), nil) 34 | if err != nil { 35 | return err 36 | } 37 | inscriptionNumber = utils.BytesToUint64(value) 38 | 39 | value, err = db.Get([]byte("h-record-id"), nil) 40 | if err != nil { 41 | return err 42 | } 43 | asc20RecordId = utils.BytesToUint64(value) 44 | 45 | logger.Printf("block: %d, number: %d, asc20 id: %d", syncFromBlock, inscriptionNumber, asc20RecordId) 46 | 47 | //if syncFromBlock > 0 { 48 | // return errors.New("test here") 49 | //} 50 | 51 | // read tokens 52 | iter := db.NewIterator(util.BytesPrefix([]byte("t-")), nil) 53 | for iter.Next() { 54 | protoToken := &serialize.ProtoToken{} 55 | err = proto.Unmarshal(iter.Value(), protoToken) 56 | if err != nil { 57 | return err 58 | } 59 | token := model.TokenFromProto(protoToken) 60 | 61 | lowerTick := strings.ToLower(token.Tick) 62 | tokens[lowerTick] = token 63 | tokensByHash[token.Hash] = token 64 | tokenHolders[lowerTick] = make(map[string]*model.DDecimal) 65 | } 66 | iter.Release() 67 | 68 | // read lists 69 | iter = db.NewIterator(util.BytesPrefix([]byte("l-")), nil) 70 | for iter.Next() { 71 | protoList := &serialize.ProtoList{} 72 | err = proto.Unmarshal(iter.Value(), protoList) 73 | if err != nil { 74 | return err 75 | } 76 | list := model.ListFromProto(protoList) 77 | 78 | lists[list.InsId] = list 79 | } 80 | iter.Release() 81 | 82 | // read balances 83 | iter = db.NewIterator(util.BytesPrefix([]byte("b-")), nil) 84 | for iter.Next() { 85 | bkey := string(iter.Key())[2:] 86 | address := bkey[0:42] 87 | tick := bkey[42:] 88 | balance, _, err := model.NewDecimalFromString(string(iter.Value())) 89 | if err != nil { 90 | return err 91 | } 92 | 93 | tokenHolders[tick][address] = balance 94 | 95 | if _, ok := balances[address]; !ok { 96 | balances[address] = make(map[string]*model.DDecimal) 97 | } 98 | balances[address][tick] = balance 99 | } 100 | iter.Release() 101 | 102 | logger.Println("current block", syncFromBlock) 103 | 104 | return nil 105 | } 106 | 107 | /*** 108 | * Need to write data to storage, next version implement write to local database and initialize from local database, 109 | * currently only initialize from snapshot 110 | */ 111 | func saveToStorage(blockHeight uint64) error { 112 | var count = 0 113 | var err error 114 | batch := new(leveldb.Batch) 115 | 116 | // save tokens 117 | for tick, _ := range updatedTokens { 118 | count++ 119 | token := tokens[tick] 120 | bytes, err := proto.Marshal(token.ToProtoToken()) 121 | if err != nil { 122 | logger.Errorln("serialize token error", err.Error()) 123 | return err 124 | } 125 | key := fmt.Sprintf("t-%s", token.Tick) 126 | batch.Put([]byte(key), bytes) 127 | } 128 | if count > 0 { 129 | logger.Println("saved", count, "tokens successfully at ", blockHeight) 130 | } 131 | updatedTokens = make(map[string]bool) 132 | 133 | // save balances 134 | count = 0 135 | for bkey, balance := range updatedBalances { 136 | //owner := bkey[0:42] 137 | //tick := bkey[42:] 138 | key := fmt.Sprintf("b-%s", bkey) 139 | if balance == "0" { 140 | batch.Delete([]byte(key)) 141 | } else { 142 | batch.Put([]byte(key), []byte(balance)) 143 | } 144 | //logger.Println(key, balance) 145 | count++ 146 | } 147 | if count > 0 { 148 | logger.Println("saved", count, "balances successfully at ", blockHeight) 149 | } 150 | updatedBalances = make(map[string]string) 151 | 152 | // save lists 153 | count = 0 154 | for insId, isAdd := range updatedLists { 155 | key := fmt.Sprintf("l-%s", insId) 156 | if isAdd { 157 | list := lists[insId] 158 | bytes, err := proto.Marshal(list.ToProtoList()) 159 | if err != nil { 160 | logger.Errorln("serialize list error", err.Error()) 161 | return err 162 | } 163 | batch.Put([]byte(key), bytes) 164 | } else { 165 | batch.Delete([]byte(key)) 166 | } 167 | count++ 168 | } 169 | if count > 0 { 170 | logger.Println("saved", len(updatedLists), "lists successfully at ", blockHeight) 171 | } 172 | updatedLists = make(map[string]bool) 173 | 174 | // save asc-20 records 175 | var lastBlock = uint64(0) 176 | var blockIndex = uint32(0) 177 | for asc20Idx := range asc20Records { 178 | asc20Record := asc20Records[asc20Idx] 179 | bytes, err := proto.Marshal(asc20Record.ToProtoRecord()) 180 | if err != nil { 181 | logger.Errorln("serialize token error", err.Error()) 182 | return err 183 | } 184 | if lastBlock != asc20Record.Block { 185 | lastBlock = asc20Record.Block 186 | blockIndex = 0 187 | } else { 188 | blockIndex++ 189 | } 190 | key := fmt.Sprintf("r-%d-%d", asc20Record.Block, blockIndex) 191 | batch.Put([]byte(key), bytes) 192 | 193 | key = fmt.Sprintf("h-%s", asc20Record.Hash) 194 | batch.Put([]byte(key), utils.Uint64ToBytes(asc20Record.Block)) 195 | } 196 | if count > 0 { 197 | logger.Println("saved", len(asc20Records), "records successfully at ", blockHeight) 198 | } 199 | asc20Records = make([]*model.Asc20, 0) 200 | 201 | // save block height 202 | batch.Put([]byte("h-block"), utils.Uint64ToBytes(blockHeight)) 203 | // inscription number 204 | batch.Put([]byte("h-number"), utils.Uint64ToBytes(inscriptionNumber)) 205 | // asc20 id 206 | batch.Put([]byte("h-record-id"), utils.Uint64ToBytes(asc20RecordId)) 207 | 208 | // batch write 209 | err = db.Write(batch, nil) 210 | if err != nil { 211 | logger.Fatal(err) 212 | } 213 | 214 | return err 215 | } 216 | -------------------------------------------------------------------------------- /handlers/sync.go: -------------------------------------------------------------------------------- 1 | package handlers 2 | 3 | import ( 4 | "context" 5 | "errors" 6 | "fmt" 7 | "go.mongodb.org/mongo-driver/bson" 8 | "open-indexer/model" 9 | "strings" 10 | "time" 11 | ) 12 | 13 | // var dataStartBlock = 31918263 14 | // var dataEndBlock = 39206794 15 | var dataStartBlock uint64 16 | var dataEndBlock uint64 17 | 18 | var syncFromBlock uint64 19 | var syncToBlock uint64 20 | var fetchSize uint64 21 | 22 | var latestBlock uint64 23 | var createSnapshotFlag bool 24 | var createSnapshotBlock uint64 25 | 26 | var syncInterrupt bool 27 | 28 | func initSync() { 29 | synCfg := cfg.Section("sync") 30 | dataStartBlock = synCfg.Key("start").MustUint64(0) 31 | dataEndBlock = synCfg.Key("end").MustUint64(0) 32 | fetchSize = synCfg.Key("size").MustUint64(1) 33 | 34 | syncFromBlock = dataStartBlock 35 | 36 | if dataEndBlock > 0 && dataStartBlock > dataEndBlock { 37 | panic("block number error") 38 | } 39 | } 40 | 41 | func StartSync() { 42 | for !syncInterrupt { 43 | finished, err := syncBlock() 44 | if err != nil { 45 | if strings.HasPrefix(err.Error(), "sync: no more new block") { 46 | logger.Println(err.Error() + ", wait 1s") 47 | time.Sleep(time.Duration(1) * time.Second) 48 | continue 49 | } 50 | logger.Errorln("sync error:", err) 51 | QuitChan <- true 52 | break 53 | } 54 | if finished { 55 | logger.Println("sync finished") 56 | QuitChan <- true 57 | break 58 | } 59 | } 60 | 61 | StopSuccessCount++ 62 | logger.Println("sync stopped") 63 | } 64 | 65 | func StopSync() { 66 | syncInterrupt = true 67 | } 68 | 69 | func syncBlock() (bool, error) { 70 | 71 | syncToBlock = syncFromBlock + fetchSize - 1 72 | 73 | // Modify parameters for faster synchronization 74 | //if syncFromBlock < 37400000 { 75 | // syncToBlock = syncFromBlock + 100000 - 1 76 | //} else if syncFromBlock < 37900000 { 77 | // syncToBlock = syncFromBlock + 50000 - 1 78 | //} else if syncFromBlock < 38400000 { 79 | // syncToBlock = syncFromBlock + 5000 - 1 80 | //} else if syncFromBlock < 38900000 { 81 | // syncToBlock = syncFromBlock + 2000 - 1 82 | //} else if syncFromBlock < 40000000 { 83 | // syncToBlock = syncFromBlock + 500 - 1 84 | //} else if syncFromBlock < 40560000 { 85 | // syncToBlock = syncFromBlock + 1000 - 1 86 | //} 87 | 88 | if dataEndBlock > 0 && syncToBlock > dataEndBlock { 89 | syncToBlock = dataEndBlock 90 | } 91 | 92 | // read trxs 93 | if latestBlock == 0 || syncToBlock >= latestBlock { 94 | var err error 95 | latestBlock, err = getLatestBlock() 96 | if err != nil { 97 | return false, err 98 | } 99 | 100 | if latestBlock > syncFromBlock && latestBlock-syncFromBlock < 10 { 101 | // It's catching up. read it block by block. 102 | fetchSize = 1 103 | } 104 | 105 | if latestBlock < syncFromBlock-1 { 106 | return false, errors.New("the latest block is smaller than the current block") 107 | } 108 | } 109 | 110 | if syncToBlock > latestBlock { 111 | syncToBlock = latestBlock 112 | } 113 | 114 | if syncFromBlock > syncToBlock { 115 | checkSnapshot() 116 | return false, errors.New(fmt.Sprintf("sync: no more new block, block %d", syncToBlock)) 117 | } 118 | 119 | start := time.Now().UnixMilli() 120 | logger.Printf("sync block from %d to %d", syncFromBlock, syncToBlock) 121 | trxs, err := getTransactions() 122 | if err != nil { 123 | return false, err 124 | } 125 | 126 | // read logs 127 | logs, err := getLogs() 128 | if err != nil { 129 | return false, err 130 | } 131 | 132 | records := mixRecords(trxs, logs) 133 | err = processRecords(records) 134 | if err != nil { 135 | return false, err 136 | } 137 | 138 | err = saveToStorage(syncToBlock) 139 | if err != nil { 140 | return false, err 141 | } 142 | 143 | checkSnapshot() 144 | 145 | costs := time.Now().UnixMilli() - start 146 | logger.Println("sync finished, costs ", costs, " ms") 147 | 148 | syncFromBlock = syncToBlock + 1 149 | 150 | return syncToBlock == dataEndBlock, err 151 | } 152 | 153 | func checkSnapshot() { 154 | // create a snapshot 155 | if createSnapshotFlag || syncToBlock == createSnapshotBlock { 156 | createSnapshotFlag = false 157 | if syncToBlock == createSnapshotBlock { 158 | createSnapshotBlock = 0 159 | } 160 | snapshot(syncToBlock) 161 | } 162 | } 163 | 164 | func getLatestBlock() (uint64, error) { 165 | if DataSourceType == "mongo" { 166 | ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second) 167 | defer cancel() 168 | statusCollection := mongodb.Collection("status") 169 | result := statusCollection.FindOne(ctx, bson.D{}) 170 | if result.Err() != nil { 171 | return 0, result.Err() 172 | } 173 | var status model.Status 174 | result.Decode(&status) 175 | 176 | return status.Block, nil 177 | } else { 178 | blockNumber := cachedBlockNumber 179 | if blockNumber == 0 { 180 | blockNumber = syncFromBlock - 1 181 | } 182 | return blockNumber, nil 183 | } 184 | } 185 | 186 | func getTransactions() ([]*model.Transaction, error) { 187 | var trxs []*model.Transaction 188 | if DataSourceType == "mongo" { 189 | ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second) 190 | defer cancel() 191 | trxCollection := mongodb.Collection("transactions") 192 | cur, err := trxCollection.Find(ctx, bson.D{{"block", bson.D{{"$gte", syncFromBlock}, {"$lte", syncToBlock}}}}) 193 | defer cur.Close(ctx) 194 | if err != nil { 195 | logger.Println(err) 196 | return nil, err 197 | } 198 | for cur.Next(ctx) { 199 | var result model.Transaction 200 | err := cur.Decode(&result) 201 | if err != nil { 202 | logger.Fatal(err) 203 | } 204 | trxs = append(trxs, &result) 205 | } 206 | } else { 207 | for block := syncFromBlock; block <= syncToBlock; block++ { 208 | _trxs, ok := cachedTranscriptions[block] 209 | if !ok { 210 | if block == syncFromBlock { 211 | return nil, errors.New(fmt.Sprintf("trxs not cached at %d", block)) 212 | } else { 213 | break 214 | } 215 | } else { 216 | delete(cachedTranscriptions, block) 217 | } 218 | trxs = append(trxs, _trxs...) 219 | } 220 | } 221 | return trxs, nil 222 | } 223 | 224 | func getLogs() ([]*model.EvmLog, error) { 225 | var logs []*model.EvmLog 226 | if DataSourceType == "mongo" { 227 | ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second) 228 | defer cancel() 229 | logCollection := mongodb.Collection("evmlogs") 230 | cur, err := logCollection.Find(ctx, bson.D{{"block", bson.D{{"$gte", syncFromBlock}, {"$lte", syncToBlock}}}}) 231 | defer cur.Close(ctx) 232 | if err != nil { 233 | return nil, err 234 | } 235 | for cur.Next(ctx) { 236 | var result model.EvmLog 237 | err := cur.Decode(&result) 238 | if err != nil { 239 | logger.Fatal(err) 240 | } 241 | logs = append(logs, &result) 242 | } 243 | } else { 244 | for block := syncFromBlock; block <= syncToBlock; block++ { 245 | _logs, ok := cachedLogs[block] 246 | if !ok { 247 | if block == syncFromBlock { 248 | return nil, errors.New(fmt.Sprintf("logs not cached at %d", block)) 249 | } else { 250 | break 251 | } 252 | } else { 253 | delete(cachedLogs, block) 254 | } 255 | logs = append(logs, _logs...) 256 | } 257 | } 258 | return logs, nil 259 | } 260 | -------------------------------------------------------------------------------- /model/asc20.go: -------------------------------------------------------------------------------- 1 | package model 2 | 3 | import ( 4 | "open-indexer/model/serialize" 5 | "open-indexer/utils" 6 | ) 7 | 8 | type Asc20 struct { 9 | Id uint64 `json:"id"` 10 | Number uint64 `json:"number"` 11 | Tick string `json:"tick"` 12 | From string `json:"from"` 13 | To string `json:"to"` 14 | Operation string `json:"operation"` 15 | Precision int `json:"precision"` 16 | Limit *DDecimal `json:"limit"` 17 | Amount *DDecimal `json:"amount"` 18 | Hash string `json:"hash"` 19 | Block uint64 `json:"block"` 20 | Timestamp uint64 `json:"timestamp"` 21 | Valid int8 `json:"valid"` 22 | } 23 | 24 | func (a *Asc20) ToProtoRecord() *serialize.ProtoRecord { 25 | protoRecord := &serialize.ProtoRecord{ 26 | Id: a.Id, 27 | Number: a.Number, 28 | Tick: a.Tick, 29 | From: utils.HexStrToBytes(a.From), 30 | To: utils.HexStrToBytes(a.To), 31 | Operation: a.Operation, 32 | Precision: uint32(a.Precision), 33 | Limit: a.Limit.String(), 34 | Amount: a.Amount.String(), 35 | Hash: utils.HexStrToBytes(a.Hash), 36 | Block: a.Block, 37 | Timestamp: a.Timestamp, 38 | Valid: int32(a.Valid), 39 | } 40 | return protoRecord 41 | } 42 | 43 | func Asc20FromProto(a *serialize.ProtoRecord) *Asc20 { 44 | limit, _, _ := NewDecimalFromString(a.Limit) 45 | amount, _, _ := NewDecimalFromString(a.Amount) 46 | asc20 := &Asc20{ 47 | Id: a.Id, 48 | Number: a.Number, 49 | Tick: a.Tick, 50 | From: utils.BytesToHexStr(a.From), 51 | To: utils.BytesToHexStr(a.To), 52 | Operation: a.Operation, 53 | Precision: int(a.Precision), 54 | Limit: limit, 55 | Amount: amount, 56 | Hash: utils.BytesToHexStr(a.Hash)[2:], 57 | Block: a.Block, 58 | Timestamp: a.Timestamp, 59 | Valid: int8(a.Valid), 60 | } 61 | return asc20 62 | } 63 | -------------------------------------------------------------------------------- /model/ddecimal.go: -------------------------------------------------------------------------------- 1 | package model 2 | 3 | import ( 4 | "database/sql/driver" 5 | "open-indexer/utils/decimal" 6 | ) 7 | 8 | type DDecimal struct { 9 | value *decimal.Decimal 10 | } 11 | 12 | func NewDecimal() *DDecimal { 13 | return &DDecimal{decimal.New()} 14 | } 15 | 16 | func NewDecimalFromString(s string) (*DDecimal, int, error) { 17 | d, p, e := decimal.NewFromString(s) 18 | 19 | return &DDecimal{d}, p, e 20 | } 21 | 22 | func (dd *DDecimal) Add(other *DDecimal) *DDecimal { 23 | d := dd.value.Add(other.value) 24 | return &DDecimal{d} 25 | } 26 | 27 | func (dd *DDecimal) Sub(other *DDecimal) *DDecimal { 28 | d := dd.value.Sub(other.value) 29 | return &DDecimal{d} 30 | } 31 | 32 | func (dd *DDecimal) Cmp(other *DDecimal) int { 33 | return dd.value.Cmp(other.value) 34 | } 35 | 36 | func (dd *DDecimal) Sign() int { 37 | return dd.value.Sign() 38 | } 39 | 40 | func (dd *DDecimal) String() string { 41 | if dd == nil { 42 | return "0" 43 | } 44 | return dd.value.String() 45 | } 46 | 47 | func (dd *DDecimal) Scan(value interface{}) error { 48 | str := string(value.([]byte)) 49 | d, _, err := decimal.NewFromString(str) 50 | dd.value = d 51 | return err 52 | } 53 | 54 | func (dd *DDecimal) Value() (driver.Value, error) { 55 | if dd == nil { 56 | return "0", nil 57 | } 58 | return dd.value.String(), nil 59 | } 60 | 61 | func (dd *DDecimal) MarshalJSON() ([]byte, error) { 62 | return []byte(dd.String()), nil 63 | } 64 | 65 | func (dd *DDecimal) UnmarshalJSON(data []byte) error { 66 | d, _, e := decimal.NewFromString(string(data)) 67 | dd.value = d 68 | return e 69 | } 70 | -------------------------------------------------------------------------------- /model/evmlog.go: -------------------------------------------------------------------------------- 1 | package model 2 | 3 | type EvmLog struct { 4 | Hash string 5 | Address string 6 | Topics []string 7 | Data string 8 | Block uint64 9 | TrxIndex uint32 10 | LogIndex uint32 11 | Timestamp uint64 12 | } 13 | -------------------------------------------------------------------------------- /model/fetch/block_response.go: -------------------------------------------------------------------------------- 1 | package fetch 2 | 3 | type Transaction struct { 4 | BlockHash string `json:"blockHash"` 5 | BlockNumber string `json:"blockNumber"` 6 | From string `json:"from"` 7 | Gas string `json:"gas"` 8 | GasPrice string `json:"gasPrice"` 9 | MaxFeePerGas string `json:"maxFeePerGas"` 10 | MaxPriorityFeePerGas string `json:"maxPriorityFeePerGas"` 11 | Hash string `json:"hash"` 12 | Input string `json:"input"` 13 | Nonce string `json:"nonce"` 14 | To string `json:"to"` 15 | TransactionIndex string `json:"transactionIndex"` 16 | Value string `json:"value"` 17 | Type string `json:"type"` 18 | AccessList []interface{} `json:"accessList"` 19 | ChainId string `json:"chainId"` 20 | V string `json:"v"` 21 | R string `json:"r"` 22 | S string `json:"s"` 23 | YParity string `json:"yParity"` 24 | // --- Additional fields 25 | Timestamp uint64 26 | } 27 | 28 | type Block struct { 29 | Id string `json:"id"` 30 | 31 | Difficulty string `json:"difficulty"` 32 | ExtraData string `json:"extraData"` 33 | GasLimit string `json:"gasLimit"` 34 | GasUsed string `json:"gasUsed"` 35 | Hash string `json:"hash"` 36 | LogsBloom string `json:"logsBloom"` 37 | Miner string `json:"miner"` 38 | MixHash string `json:"mixHash"` 39 | Nonce string `json:"nonce"` 40 | Number string `json:"number"` 41 | ParentHash string `json:"parentHash"` 42 | ReceiptsRoot string `json:"receiptsRoot"` 43 | Sha3Uncles string `json:"sha3Uncles"` 44 | Size string `json:"size"` 45 | StateRoot string `json:"stateRoot"` 46 | Timestamp string `json:"timestamp"` 47 | TotalDifficulty string `json:"totalDifficulty"` 48 | Transactions []*Transaction `json:"transactions"` 49 | TransactionsRoot string `json:"transactionsRoot"` 50 | Uncles []interface{} `json:"uncles"` 51 | //Withdrawals []struct { 52 | // Address string `json:"address"` 53 | // Amount string `json:"amount"` 54 | // Index string `json:"index"` 55 | // ValidatorIndex string `json:"validatorIndex"` 56 | //} `json:"withdrawals"` 57 | //WithdrawalsRoot string `json:"withdrawalsRoot"` 58 | } 59 | 60 | type BlockResponse struct { 61 | Id string `json:"id"` 62 | JsonRpc string `json:"jsonrpc"` 63 | Result *Block `json:"result"` 64 | Error struct { 65 | Code int `json:"code"` 66 | Message string `json:"message"` 67 | } `json:"error"` 68 | } 69 | -------------------------------------------------------------------------------- /model/fetch/logs_response.go: -------------------------------------------------------------------------------- 1 | package fetch 2 | 3 | type LogEvent struct { 4 | Address string `json:"address"` 5 | Topics []string `json:"topics"` 6 | Data string `json:"data"` 7 | BlockNumber string `json:"blockNumber"` 8 | TransactionHash string `json:"transactionHash"` 9 | TransactionIndex string `json:"transactionIndex"` 10 | BlockHash string `json:"blockHash"` 11 | LogIndex string `json:"logIndex"` 12 | Removed bool `json:"removed"` 13 | // --- Additional fields 14 | Timestamp uint64 15 | } 16 | 17 | type LogsResponse struct { 18 | Id string `json:"id"` 19 | JsonRpc string `json:"jsonrpc"` 20 | Result []*LogEvent `json:"result"` 21 | Error struct { 22 | Code int `json:"code"` 23 | Message string `json:"message"` 24 | } `json:"error"` 25 | } 26 | -------------------------------------------------------------------------------- /model/fetch/number_response.go: -------------------------------------------------------------------------------- 1 | package fetch 2 | 3 | type NumberResponse struct { 4 | Id string `json:"id"` 5 | JsonRpc string `json:"jsonrpc"` 6 | Result string `json:"result"` 7 | Error struct { 8 | Code int `json:"code"` 9 | Message string `json:"message"` 10 | } `json:"error"` 11 | } 12 | -------------------------------------------------------------------------------- /model/inscription.go: -------------------------------------------------------------------------------- 1 | package model 2 | 3 | type Inscription struct { 4 | Id string 5 | Number uint64 6 | From string 7 | To string 8 | Block uint64 9 | Idx uint32 10 | Timestamp uint64 11 | ContentType string 12 | Content string 13 | } 14 | -------------------------------------------------------------------------------- /model/list.go: -------------------------------------------------------------------------------- 1 | package model 2 | 3 | import ( 4 | "open-indexer/model/serialize" 5 | "open-indexer/utils" 6 | ) 7 | 8 | type List struct { 9 | InsId string 10 | Owner string 11 | Exchange string 12 | Tick string 13 | Amount *DDecimal 14 | Precision int 15 | } 16 | 17 | func (l *List) ToProtoList() *serialize.ProtoList { 18 | protoRecord := &serialize.ProtoList{ 19 | InsId: utils.HexStrToBytes(l.InsId), 20 | Owner: utils.HexStrToBytes(l.Owner), 21 | Exchange: utils.HexStrToBytes(l.Exchange), 22 | Tick: l.Tick, 23 | Amount: l.Amount.String(), 24 | Precision: uint32(l.Precision), 25 | } 26 | return protoRecord 27 | } 28 | 29 | func ListFromProto(l *serialize.ProtoList) *List { 30 | amount, _, _ := NewDecimalFromString(l.Amount) 31 | asc20 := &List{ 32 | InsId: utils.BytesToHexStr(l.InsId), 33 | Owner: utils.BytesToHexStr(l.Owner), 34 | Exchange: utils.BytesToHexStr(l.Exchange), 35 | Tick: l.Tick, 36 | Amount: amount, 37 | Precision: int(l.Precision), 38 | } 39 | return asc20 40 | } 41 | -------------------------------------------------------------------------------- /model/record.go: -------------------------------------------------------------------------------- 1 | package model 2 | 3 | type Record struct { 4 | IsLog bool 5 | Block uint64 6 | TransactionIndex uint32 7 | LogIndex uint32 8 | Transaction *Transaction 9 | EvmLog *EvmLog 10 | } 11 | -------------------------------------------------------------------------------- /model/serialize/indexer.pb.go: -------------------------------------------------------------------------------- 1 | // Code generated by protoc-gen-go. DO NOT EDIT. 2 | // versions: 3 | // protoc-gen-go v1.32.0 4 | // protoc v4.25.2 5 | // source: indexer.proto 6 | 7 | package serialize 8 | 9 | import ( 10 | protoreflect "google.golang.org/protobuf/reflect/protoreflect" 11 | protoimpl "google.golang.org/protobuf/runtime/protoimpl" 12 | reflect "reflect" 13 | sync "sync" 14 | ) 15 | 16 | const ( 17 | // Verify that this generated code is sufficiently up-to-date. 18 | _ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion) 19 | // Verify that runtime/protoimpl is sufficiently up-to-date. 20 | _ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20) 21 | ) 22 | 23 | type ProtoToken struct { 24 | state protoimpl.MessageState 25 | sizeCache protoimpl.SizeCache 26 | unknownFields protoimpl.UnknownFields 27 | 28 | Tick string `protobuf:"bytes,1,opt,name=tick,proto3" json:"tick,omitempty"` 29 | Number uint64 `protobuf:"varint,2,opt,name=number,proto3" json:"number,omitempty"` 30 | Precision uint32 `protobuf:"varint,3,opt,name=precision,proto3" json:"precision,omitempty"` 31 | Max string `protobuf:"bytes,4,opt,name=max,proto3" json:"max,omitempty"` 32 | Limit string `protobuf:"bytes,5,opt,name=limit,proto3" json:"limit,omitempty"` 33 | Minted string `protobuf:"bytes,6,opt,name=minted,proto3" json:"minted,omitempty"` 34 | Progress uint32 `protobuf:"varint,7,opt,name=progress,proto3" json:"progress,omitempty"` 35 | Holders uint32 `protobuf:"varint,8,opt,name=holders,proto3" json:"holders,omitempty"` 36 | Trxs uint32 `protobuf:"varint,9,opt,name=trxs,proto3" json:"trxs,omitempty"` 37 | CreatedAt uint64 `protobuf:"varint,10,opt,name=createdAt,proto3" json:"createdAt,omitempty"` 38 | CompletedAt uint64 `protobuf:"varint,11,opt,name=completedAt,proto3" json:"completedAt,omitempty"` 39 | Hash []byte `protobuf:"bytes,12,opt,name=hash,proto3" json:"hash,omitempty"` 40 | } 41 | 42 | func (x *ProtoToken) Reset() { 43 | *x = ProtoToken{} 44 | if protoimpl.UnsafeEnabled { 45 | mi := &file_indexer_proto_msgTypes[0] 46 | ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) 47 | ms.StoreMessageInfo(mi) 48 | } 49 | } 50 | 51 | func (x *ProtoToken) String() string { 52 | return protoimpl.X.MessageStringOf(x) 53 | } 54 | 55 | func (*ProtoToken) ProtoMessage() {} 56 | 57 | func (x *ProtoToken) ProtoReflect() protoreflect.Message { 58 | mi := &file_indexer_proto_msgTypes[0] 59 | if protoimpl.UnsafeEnabled && x != nil { 60 | ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) 61 | if ms.LoadMessageInfo() == nil { 62 | ms.StoreMessageInfo(mi) 63 | } 64 | return ms 65 | } 66 | return mi.MessageOf(x) 67 | } 68 | 69 | // Deprecated: Use ProtoToken.ProtoReflect.Descriptor instead. 70 | func (*ProtoToken) Descriptor() ([]byte, []int) { 71 | return file_indexer_proto_rawDescGZIP(), []int{0} 72 | } 73 | 74 | func (x *ProtoToken) GetTick() string { 75 | if x != nil { 76 | return x.Tick 77 | } 78 | return "" 79 | } 80 | 81 | func (x *ProtoToken) GetNumber() uint64 { 82 | if x != nil { 83 | return x.Number 84 | } 85 | return 0 86 | } 87 | 88 | func (x *ProtoToken) GetPrecision() uint32 { 89 | if x != nil { 90 | return x.Precision 91 | } 92 | return 0 93 | } 94 | 95 | func (x *ProtoToken) GetMax() string { 96 | if x != nil { 97 | return x.Max 98 | } 99 | return "" 100 | } 101 | 102 | func (x *ProtoToken) GetLimit() string { 103 | if x != nil { 104 | return x.Limit 105 | } 106 | return "" 107 | } 108 | 109 | func (x *ProtoToken) GetMinted() string { 110 | if x != nil { 111 | return x.Minted 112 | } 113 | return "" 114 | } 115 | 116 | func (x *ProtoToken) GetProgress() uint32 { 117 | if x != nil { 118 | return x.Progress 119 | } 120 | return 0 121 | } 122 | 123 | func (x *ProtoToken) GetHolders() uint32 { 124 | if x != nil { 125 | return x.Holders 126 | } 127 | return 0 128 | } 129 | 130 | func (x *ProtoToken) GetTrxs() uint32 { 131 | if x != nil { 132 | return x.Trxs 133 | } 134 | return 0 135 | } 136 | 137 | func (x *ProtoToken) GetCreatedAt() uint64 { 138 | if x != nil { 139 | return x.CreatedAt 140 | } 141 | return 0 142 | } 143 | 144 | func (x *ProtoToken) GetCompletedAt() uint64 { 145 | if x != nil { 146 | return x.CompletedAt 147 | } 148 | return 0 149 | } 150 | 151 | func (x *ProtoToken) GetHash() []byte { 152 | if x != nil { 153 | return x.Hash 154 | } 155 | return nil 156 | } 157 | 158 | type ProtoRecord struct { 159 | state protoimpl.MessageState 160 | sizeCache protoimpl.SizeCache 161 | unknownFields protoimpl.UnknownFields 162 | 163 | Id uint64 `protobuf:"varint,1,opt,name=id,proto3" json:"id,omitempty"` 164 | Number uint64 `protobuf:"varint,2,opt,name=number,proto3" json:"number,omitempty"` 165 | Tick string `protobuf:"bytes,3,opt,name=tick,proto3" json:"tick,omitempty"` 166 | From []byte `protobuf:"bytes,4,opt,name=from,proto3" json:"from,omitempty"` 167 | To []byte `protobuf:"bytes,5,opt,name=to,proto3" json:"to,omitempty"` 168 | Operation string `protobuf:"bytes,6,opt,name=operation,proto3" json:"operation,omitempty"` 169 | Precision uint32 `protobuf:"varint,7,opt,name=precision,proto3" json:"precision,omitempty"` 170 | Amount string `protobuf:"bytes,8,opt,name=amount,proto3" json:"amount,omitempty"` 171 | Limit string `protobuf:"bytes,9,opt,name=limit,proto3" json:"limit,omitempty"` 172 | Hash []byte `protobuf:"bytes,10,opt,name=hash,proto3" json:"hash,omitempty"` 173 | Block uint64 `protobuf:"varint,11,opt,name=block,proto3" json:"block,omitempty"` 174 | Timestamp uint64 `protobuf:"varint,12,opt,name=timestamp,proto3" json:"timestamp,omitempty"` 175 | Valid int32 `protobuf:"varint,13,opt,name=valid,proto3" json:"valid,omitempty"` 176 | } 177 | 178 | func (x *ProtoRecord) Reset() { 179 | *x = ProtoRecord{} 180 | if protoimpl.UnsafeEnabled { 181 | mi := &file_indexer_proto_msgTypes[1] 182 | ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) 183 | ms.StoreMessageInfo(mi) 184 | } 185 | } 186 | 187 | func (x *ProtoRecord) String() string { 188 | return protoimpl.X.MessageStringOf(x) 189 | } 190 | 191 | func (*ProtoRecord) ProtoMessage() {} 192 | 193 | func (x *ProtoRecord) ProtoReflect() protoreflect.Message { 194 | mi := &file_indexer_proto_msgTypes[1] 195 | if protoimpl.UnsafeEnabled && x != nil { 196 | ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) 197 | if ms.LoadMessageInfo() == nil { 198 | ms.StoreMessageInfo(mi) 199 | } 200 | return ms 201 | } 202 | return mi.MessageOf(x) 203 | } 204 | 205 | // Deprecated: Use ProtoRecord.ProtoReflect.Descriptor instead. 206 | func (*ProtoRecord) Descriptor() ([]byte, []int) { 207 | return file_indexer_proto_rawDescGZIP(), []int{1} 208 | } 209 | 210 | func (x *ProtoRecord) GetId() uint64 { 211 | if x != nil { 212 | return x.Id 213 | } 214 | return 0 215 | } 216 | 217 | func (x *ProtoRecord) GetNumber() uint64 { 218 | if x != nil { 219 | return x.Number 220 | } 221 | return 0 222 | } 223 | 224 | func (x *ProtoRecord) GetTick() string { 225 | if x != nil { 226 | return x.Tick 227 | } 228 | return "" 229 | } 230 | 231 | func (x *ProtoRecord) GetFrom() []byte { 232 | if x != nil { 233 | return x.From 234 | } 235 | return nil 236 | } 237 | 238 | func (x *ProtoRecord) GetTo() []byte { 239 | if x != nil { 240 | return x.To 241 | } 242 | return nil 243 | } 244 | 245 | func (x *ProtoRecord) GetOperation() string { 246 | if x != nil { 247 | return x.Operation 248 | } 249 | return "" 250 | } 251 | 252 | func (x *ProtoRecord) GetPrecision() uint32 { 253 | if x != nil { 254 | return x.Precision 255 | } 256 | return 0 257 | } 258 | 259 | func (x *ProtoRecord) GetAmount() string { 260 | if x != nil { 261 | return x.Amount 262 | } 263 | return "" 264 | } 265 | 266 | func (x *ProtoRecord) GetLimit() string { 267 | if x != nil { 268 | return x.Limit 269 | } 270 | return "" 271 | } 272 | 273 | func (x *ProtoRecord) GetHash() []byte { 274 | if x != nil { 275 | return x.Hash 276 | } 277 | return nil 278 | } 279 | 280 | func (x *ProtoRecord) GetBlock() uint64 { 281 | if x != nil { 282 | return x.Block 283 | } 284 | return 0 285 | } 286 | 287 | func (x *ProtoRecord) GetTimestamp() uint64 { 288 | if x != nil { 289 | return x.Timestamp 290 | } 291 | return 0 292 | } 293 | 294 | func (x *ProtoRecord) GetValid() int32 { 295 | if x != nil { 296 | return x.Valid 297 | } 298 | return 0 299 | } 300 | 301 | type ProtoList struct { 302 | state protoimpl.MessageState 303 | sizeCache protoimpl.SizeCache 304 | unknownFields protoimpl.UnknownFields 305 | 306 | InsId []byte `protobuf:"bytes,1,opt,name=insId,proto3" json:"insId,omitempty"` 307 | Owner []byte `protobuf:"bytes,2,opt,name=owner,proto3" json:"owner,omitempty"` 308 | Exchange []byte `protobuf:"bytes,3,opt,name=exchange,proto3" json:"exchange,omitempty"` 309 | Tick string `protobuf:"bytes,4,opt,name=tick,proto3" json:"tick,omitempty"` 310 | Amount string `protobuf:"bytes,5,opt,name=amount,proto3" json:"amount,omitempty"` 311 | Precision uint32 `protobuf:"varint,6,opt,name=precision,proto3" json:"precision,omitempty"` 312 | } 313 | 314 | func (x *ProtoList) Reset() { 315 | *x = ProtoList{} 316 | if protoimpl.UnsafeEnabled { 317 | mi := &file_indexer_proto_msgTypes[2] 318 | ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) 319 | ms.StoreMessageInfo(mi) 320 | } 321 | } 322 | 323 | func (x *ProtoList) String() string { 324 | return protoimpl.X.MessageStringOf(x) 325 | } 326 | 327 | func (*ProtoList) ProtoMessage() {} 328 | 329 | func (x *ProtoList) ProtoReflect() protoreflect.Message { 330 | mi := &file_indexer_proto_msgTypes[2] 331 | if protoimpl.UnsafeEnabled && x != nil { 332 | ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) 333 | if ms.LoadMessageInfo() == nil { 334 | ms.StoreMessageInfo(mi) 335 | } 336 | return ms 337 | } 338 | return mi.MessageOf(x) 339 | } 340 | 341 | // Deprecated: Use ProtoList.ProtoReflect.Descriptor instead. 342 | func (*ProtoList) Descriptor() ([]byte, []int) { 343 | return file_indexer_proto_rawDescGZIP(), []int{2} 344 | } 345 | 346 | func (x *ProtoList) GetInsId() []byte { 347 | if x != nil { 348 | return x.InsId 349 | } 350 | return nil 351 | } 352 | 353 | func (x *ProtoList) GetOwner() []byte { 354 | if x != nil { 355 | return x.Owner 356 | } 357 | return nil 358 | } 359 | 360 | func (x *ProtoList) GetExchange() []byte { 361 | if x != nil { 362 | return x.Exchange 363 | } 364 | return nil 365 | } 366 | 367 | func (x *ProtoList) GetTick() string { 368 | if x != nil { 369 | return x.Tick 370 | } 371 | return "" 372 | } 373 | 374 | func (x *ProtoList) GetAmount() string { 375 | if x != nil { 376 | return x.Amount 377 | } 378 | return "" 379 | } 380 | 381 | func (x *ProtoList) GetPrecision() uint32 { 382 | if x != nil { 383 | return x.Precision 384 | } 385 | return 0 386 | } 387 | 388 | type TickBalance struct { 389 | state protoimpl.MessageState 390 | sizeCache protoimpl.SizeCache 391 | unknownFields protoimpl.UnknownFields 392 | 393 | Tick string `protobuf:"bytes,1,opt,name=tick,proto3" json:"tick,omitempty"` 394 | Amount string `protobuf:"bytes,2,opt,name=amount,proto3" json:"amount,omitempty"` 395 | } 396 | 397 | func (x *TickBalance) Reset() { 398 | *x = TickBalance{} 399 | if protoimpl.UnsafeEnabled { 400 | mi := &file_indexer_proto_msgTypes[3] 401 | ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) 402 | ms.StoreMessageInfo(mi) 403 | } 404 | } 405 | 406 | func (x *TickBalance) String() string { 407 | return protoimpl.X.MessageStringOf(x) 408 | } 409 | 410 | func (*TickBalance) ProtoMessage() {} 411 | 412 | func (x *TickBalance) ProtoReflect() protoreflect.Message { 413 | mi := &file_indexer_proto_msgTypes[3] 414 | if protoimpl.UnsafeEnabled && x != nil { 415 | ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) 416 | if ms.LoadMessageInfo() == nil { 417 | ms.StoreMessageInfo(mi) 418 | } 419 | return ms 420 | } 421 | return mi.MessageOf(x) 422 | } 423 | 424 | // Deprecated: Use TickBalance.ProtoReflect.Descriptor instead. 425 | func (*TickBalance) Descriptor() ([]byte, []int) { 426 | return file_indexer_proto_rawDescGZIP(), []int{3} 427 | } 428 | 429 | func (x *TickBalance) GetTick() string { 430 | if x != nil { 431 | return x.Tick 432 | } 433 | return "" 434 | } 435 | 436 | func (x *TickBalance) GetAmount() string { 437 | if x != nil { 438 | return x.Amount 439 | } 440 | return "" 441 | } 442 | 443 | type UserBalance struct { 444 | state protoimpl.MessageState 445 | sizeCache protoimpl.SizeCache 446 | unknownFields protoimpl.UnknownFields 447 | 448 | Address []byte `protobuf:"bytes,1,opt,name=address,proto3" json:"address,omitempty"` 449 | Balances []*TickBalance `protobuf:"bytes,2,rep,name=balances,proto3" json:"balances,omitempty"` 450 | } 451 | 452 | func (x *UserBalance) Reset() { 453 | *x = UserBalance{} 454 | if protoimpl.UnsafeEnabled { 455 | mi := &file_indexer_proto_msgTypes[4] 456 | ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) 457 | ms.StoreMessageInfo(mi) 458 | } 459 | } 460 | 461 | func (x *UserBalance) String() string { 462 | return protoimpl.X.MessageStringOf(x) 463 | } 464 | 465 | func (*UserBalance) ProtoMessage() {} 466 | 467 | func (x *UserBalance) ProtoReflect() protoreflect.Message { 468 | mi := &file_indexer_proto_msgTypes[4] 469 | if protoimpl.UnsafeEnabled && x != nil { 470 | ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) 471 | if ms.LoadMessageInfo() == nil { 472 | ms.StoreMessageInfo(mi) 473 | } 474 | return ms 475 | } 476 | return mi.MessageOf(x) 477 | } 478 | 479 | // Deprecated: Use UserBalance.ProtoReflect.Descriptor instead. 480 | func (*UserBalance) Descriptor() ([]byte, []int) { 481 | return file_indexer_proto_rawDescGZIP(), []int{4} 482 | } 483 | 484 | func (x *UserBalance) GetAddress() []byte { 485 | if x != nil { 486 | return x.Address 487 | } 488 | return nil 489 | } 490 | 491 | func (x *UserBalance) GetBalances() []*TickBalance { 492 | if x != nil { 493 | return x.Balances 494 | } 495 | return nil 496 | } 497 | 498 | type Snapshot struct { 499 | state protoimpl.MessageState 500 | sizeCache protoimpl.SizeCache 501 | unknownFields protoimpl.UnknownFields 502 | 503 | Block uint64 `protobuf:"varint,1,opt,name=block,proto3" json:"block,omitempty"` 504 | Number uint64 `protobuf:"varint,2,opt,name=number,proto3" json:"number,omitempty"` 505 | RecordId uint64 `protobuf:"varint,3,opt,name=recordId,proto3" json:"recordId,omitempty"` 506 | Tokens []*ProtoToken `protobuf:"bytes,4,rep,name=tokens,proto3" json:"tokens,omitempty"` 507 | Lists []*ProtoList `protobuf:"bytes,5,rep,name=lists,proto3" json:"lists,omitempty"` 508 | Balances []*UserBalance `protobuf:"bytes,6,rep,name=balances,proto3" json:"balances,omitempty"` 509 | } 510 | 511 | func (x *Snapshot) Reset() { 512 | *x = Snapshot{} 513 | if protoimpl.UnsafeEnabled { 514 | mi := &file_indexer_proto_msgTypes[5] 515 | ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) 516 | ms.StoreMessageInfo(mi) 517 | } 518 | } 519 | 520 | func (x *Snapshot) String() string { 521 | return protoimpl.X.MessageStringOf(x) 522 | } 523 | 524 | func (*Snapshot) ProtoMessage() {} 525 | 526 | func (x *Snapshot) ProtoReflect() protoreflect.Message { 527 | mi := &file_indexer_proto_msgTypes[5] 528 | if protoimpl.UnsafeEnabled && x != nil { 529 | ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) 530 | if ms.LoadMessageInfo() == nil { 531 | ms.StoreMessageInfo(mi) 532 | } 533 | return ms 534 | } 535 | return mi.MessageOf(x) 536 | } 537 | 538 | // Deprecated: Use Snapshot.ProtoReflect.Descriptor instead. 539 | func (*Snapshot) Descriptor() ([]byte, []int) { 540 | return file_indexer_proto_rawDescGZIP(), []int{5} 541 | } 542 | 543 | func (x *Snapshot) GetBlock() uint64 { 544 | if x != nil { 545 | return x.Block 546 | } 547 | return 0 548 | } 549 | 550 | func (x *Snapshot) GetNumber() uint64 { 551 | if x != nil { 552 | return x.Number 553 | } 554 | return 0 555 | } 556 | 557 | func (x *Snapshot) GetRecordId() uint64 { 558 | if x != nil { 559 | return x.RecordId 560 | } 561 | return 0 562 | } 563 | 564 | func (x *Snapshot) GetTokens() []*ProtoToken { 565 | if x != nil { 566 | return x.Tokens 567 | } 568 | return nil 569 | } 570 | 571 | func (x *Snapshot) GetLists() []*ProtoList { 572 | if x != nil { 573 | return x.Lists 574 | } 575 | return nil 576 | } 577 | 578 | func (x *Snapshot) GetBalances() []*UserBalance { 579 | if x != nil { 580 | return x.Balances 581 | } 582 | return nil 583 | } 584 | 585 | var File_indexer_proto protoreflect.FileDescriptor 586 | 587 | var file_indexer_proto_rawDesc = []byte{ 588 | 0x0a, 0x0d, 0x69, 0x6e, 0x64, 0x65, 0x78, 0x65, 0x72, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x12, 589 | 0x05, 0x6d, 0x6f, 0x64, 0x65, 0x6c, 0x22, 0xb4, 0x02, 0x0a, 0x0a, 0x50, 0x72, 0x6f, 0x74, 0x6f, 590 | 0x54, 0x6f, 0x6b, 0x65, 0x6e, 0x12, 0x12, 0x0a, 0x04, 0x74, 0x69, 0x63, 0x6b, 0x18, 0x01, 0x20, 591 | 0x01, 0x28, 0x09, 0x52, 0x04, 0x74, 0x69, 0x63, 0x6b, 0x12, 0x16, 0x0a, 0x06, 0x6e, 0x75, 0x6d, 592 | 0x62, 0x65, 0x72, 0x18, 0x02, 0x20, 0x01, 0x28, 0x04, 0x52, 0x06, 0x6e, 0x75, 0x6d, 0x62, 0x65, 593 | 0x72, 0x12, 0x1c, 0x0a, 0x09, 0x70, 0x72, 0x65, 0x63, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x18, 0x03, 594 | 0x20, 0x01, 0x28, 0x0d, 0x52, 0x09, 0x70, 0x72, 0x65, 0x63, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x12, 595 | 0x10, 0x0a, 0x03, 0x6d, 0x61, 0x78, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x6d, 0x61, 596 | 0x78, 0x12, 0x14, 0x0a, 0x05, 0x6c, 0x69, 0x6d, 0x69, 0x74, 0x18, 0x05, 0x20, 0x01, 0x28, 0x09, 597 | 0x52, 0x05, 0x6c, 0x69, 0x6d, 0x69, 0x74, 0x12, 0x16, 0x0a, 0x06, 0x6d, 0x69, 0x6e, 0x74, 0x65, 598 | 0x64, 0x18, 0x06, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x6d, 0x69, 0x6e, 0x74, 0x65, 0x64, 0x12, 599 | 0x1a, 0x0a, 0x08, 0x70, 0x72, 0x6f, 0x67, 0x72, 0x65, 0x73, 0x73, 0x18, 0x07, 0x20, 0x01, 0x28, 600 | 0x0d, 0x52, 0x08, 0x70, 0x72, 0x6f, 0x67, 0x72, 0x65, 0x73, 0x73, 0x12, 0x18, 0x0a, 0x07, 0x68, 601 | 0x6f, 0x6c, 0x64, 0x65, 0x72, 0x73, 0x18, 0x08, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x07, 0x68, 0x6f, 602 | 0x6c, 0x64, 0x65, 0x72, 0x73, 0x12, 0x12, 0x0a, 0x04, 0x74, 0x72, 0x78, 0x73, 0x18, 0x09, 0x20, 603 | 0x01, 0x28, 0x0d, 0x52, 0x04, 0x74, 0x72, 0x78, 0x73, 0x12, 0x1c, 0x0a, 0x09, 0x63, 0x72, 0x65, 604 | 0x61, 0x74, 0x65, 0x64, 0x41, 0x74, 0x18, 0x0a, 0x20, 0x01, 0x28, 0x04, 0x52, 0x09, 0x63, 0x72, 605 | 0x65, 0x61, 0x74, 0x65, 0x64, 0x41, 0x74, 0x12, 0x20, 0x0a, 0x0b, 0x63, 0x6f, 0x6d, 0x70, 0x6c, 606 | 0x65, 0x74, 0x65, 0x64, 0x41, 0x74, 0x18, 0x0b, 0x20, 0x01, 0x28, 0x04, 0x52, 0x0b, 0x63, 0x6f, 607 | 0x6d, 0x70, 0x6c, 0x65, 0x74, 0x65, 0x64, 0x41, 0x74, 0x12, 0x12, 0x0a, 0x04, 0x68, 0x61, 0x73, 608 | 0x68, 0x18, 0x0c, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x04, 0x68, 0x61, 0x73, 0x68, 0x22, 0xb5, 0x02, 609 | 0x0a, 0x0b, 0x50, 0x72, 0x6f, 0x74, 0x6f, 0x52, 0x65, 0x63, 0x6f, 0x72, 0x64, 0x12, 0x0e, 0x0a, 610 | 0x02, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x04, 0x52, 0x02, 0x69, 0x64, 0x12, 0x16, 0x0a, 611 | 0x06, 0x6e, 0x75, 0x6d, 0x62, 0x65, 0x72, 0x18, 0x02, 0x20, 0x01, 0x28, 0x04, 0x52, 0x06, 0x6e, 612 | 0x75, 0x6d, 0x62, 0x65, 0x72, 0x12, 0x12, 0x0a, 0x04, 0x74, 0x69, 0x63, 0x6b, 0x18, 0x03, 0x20, 613 | 0x01, 0x28, 0x09, 0x52, 0x04, 0x74, 0x69, 0x63, 0x6b, 0x12, 0x12, 0x0a, 0x04, 0x66, 0x72, 0x6f, 614 | 0x6d, 0x18, 0x04, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x04, 0x66, 0x72, 0x6f, 0x6d, 0x12, 0x0e, 0x0a, 615 | 0x02, 0x74, 0x6f, 0x18, 0x05, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x02, 0x74, 0x6f, 0x12, 0x1c, 0x0a, 616 | 0x09, 0x6f, 0x70, 0x65, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x18, 0x06, 0x20, 0x01, 0x28, 0x09, 617 | 0x52, 0x09, 0x6f, 0x70, 0x65, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x1c, 0x0a, 0x09, 0x70, 618 | 0x72, 0x65, 0x63, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x18, 0x07, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x09, 619 | 0x70, 0x72, 0x65, 0x63, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x12, 0x16, 0x0a, 0x06, 0x61, 0x6d, 0x6f, 620 | 0x75, 0x6e, 0x74, 0x18, 0x08, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x61, 0x6d, 0x6f, 0x75, 0x6e, 621 | 0x74, 0x12, 0x14, 0x0a, 0x05, 0x6c, 0x69, 0x6d, 0x69, 0x74, 0x18, 0x09, 0x20, 0x01, 0x28, 0x09, 622 | 0x52, 0x05, 0x6c, 0x69, 0x6d, 0x69, 0x74, 0x12, 0x12, 0x0a, 0x04, 0x68, 0x61, 0x73, 0x68, 0x18, 623 | 0x0a, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x04, 0x68, 0x61, 0x73, 0x68, 0x12, 0x14, 0x0a, 0x05, 0x62, 624 | 0x6c, 0x6f, 0x63, 0x6b, 0x18, 0x0b, 0x20, 0x01, 0x28, 0x04, 0x52, 0x05, 0x62, 0x6c, 0x6f, 0x63, 625 | 0x6b, 0x12, 0x1c, 0x0a, 0x09, 0x74, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x18, 0x0c, 626 | 0x20, 0x01, 0x28, 0x04, 0x52, 0x09, 0x74, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x12, 627 | 0x14, 0x0a, 0x05, 0x76, 0x61, 0x6c, 0x69, 0x64, 0x18, 0x0d, 0x20, 0x01, 0x28, 0x05, 0x52, 0x05, 628 | 0x76, 0x61, 0x6c, 0x69, 0x64, 0x22, 0x9d, 0x01, 0x0a, 0x09, 0x50, 0x72, 0x6f, 0x74, 0x6f, 0x4c, 629 | 0x69, 0x73, 0x74, 0x12, 0x14, 0x0a, 0x05, 0x69, 0x6e, 0x73, 0x49, 0x64, 0x18, 0x01, 0x20, 0x01, 630 | 0x28, 0x0c, 0x52, 0x05, 0x69, 0x6e, 0x73, 0x49, 0x64, 0x12, 0x14, 0x0a, 0x05, 0x6f, 0x77, 0x6e, 631 | 0x65, 0x72, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x05, 0x6f, 0x77, 0x6e, 0x65, 0x72, 0x12, 632 | 0x1a, 0x0a, 0x08, 0x65, 0x78, 0x63, 0x68, 0x61, 0x6e, 0x67, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 633 | 0x0c, 0x52, 0x08, 0x65, 0x78, 0x63, 0x68, 0x61, 0x6e, 0x67, 0x65, 0x12, 0x12, 0x0a, 0x04, 0x74, 634 | 0x69, 0x63, 0x6b, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x74, 0x69, 0x63, 0x6b, 0x12, 635 | 0x16, 0x0a, 0x06, 0x61, 0x6d, 0x6f, 0x75, 0x6e, 0x74, 0x18, 0x05, 0x20, 0x01, 0x28, 0x09, 0x52, 636 | 0x06, 0x61, 0x6d, 0x6f, 0x75, 0x6e, 0x74, 0x12, 0x1c, 0x0a, 0x09, 0x70, 0x72, 0x65, 0x63, 0x69, 637 | 0x73, 0x69, 0x6f, 0x6e, 0x18, 0x06, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x09, 0x70, 0x72, 0x65, 0x63, 638 | 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x22, 0x39, 0x0a, 0x0b, 0x54, 0x69, 0x63, 0x6b, 0x42, 0x61, 0x6c, 639 | 0x61, 0x6e, 0x63, 0x65, 0x12, 0x12, 0x0a, 0x04, 0x74, 0x69, 0x63, 0x6b, 0x18, 0x01, 0x20, 0x01, 640 | 0x28, 0x09, 0x52, 0x04, 0x74, 0x69, 0x63, 0x6b, 0x12, 0x16, 0x0a, 0x06, 0x61, 0x6d, 0x6f, 0x75, 641 | 0x6e, 0x74, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x61, 0x6d, 0x6f, 0x75, 0x6e, 0x74, 642 | 0x22, 0x57, 0x0a, 0x0b, 0x55, 0x73, 0x65, 0x72, 0x42, 0x61, 0x6c, 0x61, 0x6e, 0x63, 0x65, 0x12, 643 | 0x18, 0x0a, 0x07, 0x61, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0c, 644 | 0x52, 0x07, 0x61, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x12, 0x2e, 0x0a, 0x08, 0x62, 0x61, 0x6c, 645 | 0x61, 0x6e, 0x63, 0x65, 0x73, 0x18, 0x02, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x12, 0x2e, 0x6d, 0x6f, 646 | 0x64, 0x65, 0x6c, 0x2e, 0x54, 0x69, 0x63, 0x6b, 0x42, 0x61, 0x6c, 0x61, 0x6e, 0x63, 0x65, 0x52, 647 | 0x08, 0x62, 0x61, 0x6c, 0x61, 0x6e, 0x63, 0x65, 0x73, 0x22, 0xd7, 0x01, 0x0a, 0x08, 0x53, 0x6e, 648 | 0x61, 0x70, 0x73, 0x68, 0x6f, 0x74, 0x12, 0x14, 0x0a, 0x05, 0x62, 0x6c, 0x6f, 0x63, 0x6b, 0x18, 649 | 0x01, 0x20, 0x01, 0x28, 0x04, 0x52, 0x05, 0x62, 0x6c, 0x6f, 0x63, 0x6b, 0x12, 0x16, 0x0a, 0x06, 650 | 0x6e, 0x75, 0x6d, 0x62, 0x65, 0x72, 0x18, 0x02, 0x20, 0x01, 0x28, 0x04, 0x52, 0x06, 0x6e, 0x75, 651 | 0x6d, 0x62, 0x65, 0x72, 0x12, 0x1a, 0x0a, 0x08, 0x72, 0x65, 0x63, 0x6f, 0x72, 0x64, 0x49, 0x64, 652 | 0x18, 0x03, 0x20, 0x01, 0x28, 0x04, 0x52, 0x08, 0x72, 0x65, 0x63, 0x6f, 0x72, 0x64, 0x49, 0x64, 653 | 0x12, 0x29, 0x0a, 0x06, 0x74, 0x6f, 0x6b, 0x65, 0x6e, 0x73, 0x18, 0x04, 0x20, 0x03, 0x28, 0x0b, 654 | 0x32, 0x11, 0x2e, 0x6d, 0x6f, 0x64, 0x65, 0x6c, 0x2e, 0x50, 0x72, 0x6f, 0x74, 0x6f, 0x54, 0x6f, 655 | 0x6b, 0x65, 0x6e, 0x52, 0x06, 0x74, 0x6f, 0x6b, 0x65, 0x6e, 0x73, 0x12, 0x26, 0x0a, 0x05, 0x6c, 656 | 0x69, 0x73, 0x74, 0x73, 0x18, 0x05, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x10, 0x2e, 0x6d, 0x6f, 0x64, 657 | 0x65, 0x6c, 0x2e, 0x50, 0x72, 0x6f, 0x74, 0x6f, 0x4c, 0x69, 0x73, 0x74, 0x52, 0x05, 0x6c, 0x69, 658 | 0x73, 0x74, 0x73, 0x12, 0x2e, 0x0a, 0x08, 0x62, 0x61, 0x6c, 0x61, 0x6e, 0x63, 0x65, 0x73, 0x18, 659 | 0x06, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x12, 0x2e, 0x6d, 0x6f, 0x64, 0x65, 0x6c, 0x2e, 0x55, 0x73, 660 | 0x65, 0x72, 0x42, 0x61, 0x6c, 0x61, 0x6e, 0x63, 0x65, 0x52, 0x08, 0x62, 0x61, 0x6c, 0x61, 0x6e, 661 | 0x63, 0x65, 0x73, 0x42, 0x1e, 0x5a, 0x1c, 0x6f, 0x70, 0x65, 0x6e, 0x2d, 0x69, 0x6e, 0x64, 0x65, 662 | 0x78, 0x65, 0x72, 0x2f, 0x6d, 0x6f, 0x64, 0x65, 0x6c, 0x2f, 0x73, 0x65, 0x72, 0x69, 0x61, 0x6c, 663 | 0x69, 0x7a, 0x65, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, 664 | } 665 | 666 | var ( 667 | file_indexer_proto_rawDescOnce sync.Once 668 | file_indexer_proto_rawDescData = file_indexer_proto_rawDesc 669 | ) 670 | 671 | func file_indexer_proto_rawDescGZIP() []byte { 672 | file_indexer_proto_rawDescOnce.Do(func() { 673 | file_indexer_proto_rawDescData = protoimpl.X.CompressGZIP(file_indexer_proto_rawDescData) 674 | }) 675 | return file_indexer_proto_rawDescData 676 | } 677 | 678 | var file_indexer_proto_msgTypes = make([]protoimpl.MessageInfo, 6) 679 | var file_indexer_proto_goTypes = []interface{}{ 680 | (*ProtoToken)(nil), // 0: model.ProtoToken 681 | (*ProtoRecord)(nil), // 1: model.ProtoRecord 682 | (*ProtoList)(nil), // 2: model.ProtoList 683 | (*TickBalance)(nil), // 3: model.TickBalance 684 | (*UserBalance)(nil), // 4: model.UserBalance 685 | (*Snapshot)(nil), // 5: model.Snapshot 686 | } 687 | var file_indexer_proto_depIdxs = []int32{ 688 | 3, // 0: model.UserBalance.balances:type_name -> model.TickBalance 689 | 0, // 1: model.Snapshot.tokens:type_name -> model.ProtoToken 690 | 2, // 2: model.Snapshot.lists:type_name -> model.ProtoList 691 | 4, // 3: model.Snapshot.balances:type_name -> model.UserBalance 692 | 4, // [4:4] is the sub-list for method output_type 693 | 4, // [4:4] is the sub-list for method input_type 694 | 4, // [4:4] is the sub-list for extension type_name 695 | 4, // [4:4] is the sub-list for extension extendee 696 | 0, // [0:4] is the sub-list for field type_name 697 | } 698 | 699 | func init() { file_indexer_proto_init() } 700 | func file_indexer_proto_init() { 701 | if File_indexer_proto != nil { 702 | return 703 | } 704 | if !protoimpl.UnsafeEnabled { 705 | file_indexer_proto_msgTypes[0].Exporter = func(v interface{}, i int) interface{} { 706 | switch v := v.(*ProtoToken); i { 707 | case 0: 708 | return &v.state 709 | case 1: 710 | return &v.sizeCache 711 | case 2: 712 | return &v.unknownFields 713 | default: 714 | return nil 715 | } 716 | } 717 | file_indexer_proto_msgTypes[1].Exporter = func(v interface{}, i int) interface{} { 718 | switch v := v.(*ProtoRecord); i { 719 | case 0: 720 | return &v.state 721 | case 1: 722 | return &v.sizeCache 723 | case 2: 724 | return &v.unknownFields 725 | default: 726 | return nil 727 | } 728 | } 729 | file_indexer_proto_msgTypes[2].Exporter = func(v interface{}, i int) interface{} { 730 | switch v := v.(*ProtoList); i { 731 | case 0: 732 | return &v.state 733 | case 1: 734 | return &v.sizeCache 735 | case 2: 736 | return &v.unknownFields 737 | default: 738 | return nil 739 | } 740 | } 741 | file_indexer_proto_msgTypes[3].Exporter = func(v interface{}, i int) interface{} { 742 | switch v := v.(*TickBalance); i { 743 | case 0: 744 | return &v.state 745 | case 1: 746 | return &v.sizeCache 747 | case 2: 748 | return &v.unknownFields 749 | default: 750 | return nil 751 | } 752 | } 753 | file_indexer_proto_msgTypes[4].Exporter = func(v interface{}, i int) interface{} { 754 | switch v := v.(*UserBalance); i { 755 | case 0: 756 | return &v.state 757 | case 1: 758 | return &v.sizeCache 759 | case 2: 760 | return &v.unknownFields 761 | default: 762 | return nil 763 | } 764 | } 765 | file_indexer_proto_msgTypes[5].Exporter = func(v interface{}, i int) interface{} { 766 | switch v := v.(*Snapshot); i { 767 | case 0: 768 | return &v.state 769 | case 1: 770 | return &v.sizeCache 771 | case 2: 772 | return &v.unknownFields 773 | default: 774 | return nil 775 | } 776 | } 777 | } 778 | type x struct{} 779 | out := protoimpl.TypeBuilder{ 780 | File: protoimpl.DescBuilder{ 781 | GoPackagePath: reflect.TypeOf(x{}).PkgPath(), 782 | RawDescriptor: file_indexer_proto_rawDesc, 783 | NumEnums: 0, 784 | NumMessages: 6, 785 | NumExtensions: 0, 786 | NumServices: 0, 787 | }, 788 | GoTypes: file_indexer_proto_goTypes, 789 | DependencyIndexes: file_indexer_proto_depIdxs, 790 | MessageInfos: file_indexer_proto_msgTypes, 791 | }.Build() 792 | File_indexer_proto = out.File 793 | file_indexer_proto_rawDesc = nil 794 | file_indexer_proto_goTypes = nil 795 | file_indexer_proto_depIdxs = nil 796 | } 797 | -------------------------------------------------------------------------------- /model/status.go: -------------------------------------------------------------------------------- 1 | package model 2 | 3 | type Status struct { 4 | Block uint64 5 | } 6 | -------------------------------------------------------------------------------- /model/token.go: -------------------------------------------------------------------------------- 1 | package model 2 | 3 | import ( 4 | "open-indexer/model/serialize" 5 | "open-indexer/utils" 6 | ) 7 | 8 | type Token struct { 9 | Tick string `json:"tick"` 10 | Number uint64 `json:"number"` 11 | Precision int `json:"precision"` 12 | Max *DDecimal `json:"max"` 13 | Limit *DDecimal `json:"limit"` 14 | Minted *DDecimal `json:"minted"` 15 | Progress uint32 `json:"progress"` 16 | Holders uint32 `json:"holders"` 17 | Trxs uint32 `json:"trxs"` 18 | CreatedAt uint64 `json:"created_at"` 19 | CompletedAt uint64 `json:"completed_at"` 20 | Hash string `json:"hash"` 21 | } 22 | 23 | func (t *Token) ToProtoToken() *serialize.ProtoToken { 24 | protoToken := &serialize.ProtoToken{ 25 | Tick: t.Tick, 26 | Number: t.Number, 27 | Precision: uint32(t.Precision), 28 | Max: t.Max.String(), 29 | Limit: t.Limit.String(), 30 | Minted: t.Minted.String(), 31 | Progress: t.Progress, 32 | Holders: t.Holders, 33 | Trxs: t.Trxs, 34 | CreatedAt: t.CreatedAt, 35 | CompletedAt: t.CompletedAt, 36 | Hash: utils.HexStrToBytes(t.Hash), 37 | } 38 | return protoToken 39 | } 40 | 41 | func TokenFromProto(t *serialize.ProtoToken) *Token { 42 | max, _, _ := NewDecimalFromString(t.Max) 43 | limit, _, _ := NewDecimalFromString(t.Limit) 44 | minted, _, _ := NewDecimalFromString(t.Minted) 45 | token := &Token{ 46 | Tick: t.Tick, 47 | Number: t.Number, 48 | Precision: int(t.Precision), 49 | Max: max, 50 | Limit: limit, 51 | Minted: minted, 52 | Progress: t.Progress, 53 | Holders: t.Holders, 54 | Trxs: t.Trxs, 55 | CreatedAt: t.CreatedAt, 56 | CompletedAt: t.CompletedAt, 57 | Hash: utils.BytesToHexStr(t.Hash)[2:], 58 | } 59 | return token 60 | } 61 | -------------------------------------------------------------------------------- /model/transaction.go: -------------------------------------------------------------------------------- 1 | package model 2 | 3 | type Transaction struct { 4 | Id string 5 | From string 6 | To string 7 | Block uint64 8 | Idx uint32 9 | Timestamp uint64 10 | Input string 11 | } 12 | -------------------------------------------------------------------------------- /model/transfer.go: -------------------------------------------------------------------------------- 1 | package model 2 | 3 | type Transfer struct { 4 | Id uint64 5 | Number uint64 6 | Hash string 7 | From string 8 | To string 9 | Block uint64 10 | Idx uint32 11 | Timestamp uint64 12 | } 13 | -------------------------------------------------------------------------------- /utils/decimal/decimal.go: -------------------------------------------------------------------------------- 1 | package decimal 2 | 3 | import ( 4 | "errors" 5 | "fmt" 6 | "math" 7 | "math/big" 8 | "strings" 9 | ) 10 | 11 | const MAX_PRECISION = 18 12 | 13 | var precisionFactor = new(big.Int).Exp(big.NewInt(10), big.NewInt(MAX_PRECISION), nil) 14 | 15 | // Decimal represents a fixed-point decimal number with 18 decimal places 16 | type Decimal struct { 17 | value *big.Int 18 | } 19 | 20 | func New() *Decimal { 21 | return &Decimal{value: new(big.Int).SetUint64(0)} 22 | } 23 | 24 | func NewCopy(other *Decimal) *Decimal { 25 | return &Decimal{value: new(big.Int).Set(other.value)} 26 | } 27 | 28 | // NewFromString creates a Decimal instance from a string 29 | func NewFromString(s string) (*Decimal, int, error) { 30 | if s == "" { 31 | return nil, 0, errors.New("empty string") 32 | } 33 | 34 | parts := strings.Split(s, ".") 35 | if len(parts) > 2 { 36 | return nil, 0, fmt.Errorf("invalid decimal format: %s", s) 37 | } 38 | 39 | integerPartStr := parts[0] 40 | if integerPartStr == "" || integerPartStr[0] == '+' { 41 | return nil, 0, errors.New("empty integer") 42 | } 43 | 44 | integerPart, ok := new(big.Int).SetString(parts[0], 10) 45 | if !ok { 46 | return nil, 0, fmt.Errorf("invalid integer format: %s", parts[0]) 47 | } 48 | 49 | currPrecision := 0 50 | decimalPart := big.NewInt(0) 51 | if len(parts) == 2 { 52 | decimalPartStr := parts[1] 53 | if decimalPartStr == "" || decimalPartStr[0] == '-' || decimalPartStr[0] == '+' { 54 | return nil, 0, errors.New("empty decimal") 55 | } 56 | 57 | currPrecision = len(decimalPartStr) 58 | if currPrecision > MAX_PRECISION { 59 | return nil, 0, fmt.Errorf("decimal exceeds maximum precision: %s", s) 60 | } 61 | n := MAX_PRECISION - currPrecision 62 | for i := 0; i < n; i++ { 63 | decimalPartStr += "0" 64 | } 65 | decimalPart, ok = new(big.Int).SetString(decimalPartStr, 10) 66 | if !ok || decimalPart.Sign() < 0 { 67 | return nil, 0, fmt.Errorf("invalid decimal format: %s", parts[0]) 68 | } 69 | } 70 | 71 | value := new(big.Int).Mul(integerPart, precisionFactor) 72 | if value.Sign() < 0 { 73 | value = value.Sub(value, decimalPart) 74 | } else { 75 | value = value.Add(value, decimalPart) 76 | } 77 | 78 | return &Decimal{value: value}, currPrecision, nil 79 | } 80 | 81 | // String returns the string representation of a Decimal instance 82 | func (d *Decimal) String() string { 83 | if d == nil { 84 | return "0" 85 | } 86 | value := new(big.Int).Abs(d.value) 87 | quotient, remainder := new(big.Int).QuoRem(value, precisionFactor, new(big.Int)) 88 | sign := "" 89 | if d.value.Sign() < 0 { 90 | sign = "-" 91 | } 92 | if remainder.Sign() == 0 { 93 | return fmt.Sprintf("%s%s", sign, quotient.String()) 94 | } 95 | decimalPart := fmt.Sprintf("%0*d", MAX_PRECISION, remainder) 96 | decimalPart = strings.TrimRight(decimalPart, "0") 97 | return fmt.Sprintf("%s%s.%s", sign, quotient.String(), decimalPart) 98 | } 99 | 100 | func (d *Decimal) GetValue() *big.Int { 101 | return d.value 102 | } 103 | 104 | func (d *Decimal) Set(other *Decimal) *Decimal { 105 | d.value.Set(other.GetValue()) 106 | return d 107 | } 108 | 109 | // Add adds two Decimal instances and returns a new Decimal instance 110 | func (d *Decimal) Add(other *Decimal) *Decimal { 111 | if d == nil && other == nil { 112 | value := new(big.Int).SetUint64(0) 113 | return &Decimal{value: value} 114 | } 115 | if other == nil { 116 | value := new(big.Int).Set(d.value) 117 | return &Decimal{value: value} 118 | } 119 | if d == nil { 120 | value := new(big.Int).Set(other.value) 121 | return &Decimal{value: value} 122 | } 123 | value := new(big.Int).Add(d.value, other.value) 124 | return &Decimal{value: value} 125 | } 126 | 127 | // Sub subtracts two Decimal instances and returns a new Decimal instance 128 | func (d *Decimal) Sub(other *Decimal) *Decimal { 129 | if d == nil && other == nil { 130 | value := new(big.Int).SetUint64(0) 131 | return &Decimal{value: value} 132 | } 133 | if other == nil { 134 | value := new(big.Int).Set(d.value) 135 | return &Decimal{value: value} 136 | } 137 | if d == nil { 138 | value := new(big.Int).Neg(other.value) 139 | return &Decimal{value: value} 140 | } 141 | value := new(big.Int).Sub(d.value, other.value) 142 | return &Decimal{value: value} 143 | } 144 | 145 | func (d *Decimal) Cmp(other *Decimal) int { 146 | if d == nil && other == nil { 147 | return 0 148 | } 149 | if other == nil { 150 | return d.value.Sign() 151 | } 152 | if d == nil { 153 | return -other.value.Sign() 154 | } 155 | return d.value.Cmp(other.value) 156 | } 157 | 158 | func (d *Decimal) Sign() int { 159 | if d == nil { 160 | return 0 161 | } 162 | return d.value.Sign() 163 | } 164 | 165 | func (d *Decimal) IsOverflowUint64() bool { 166 | if d == nil { 167 | return false 168 | } 169 | 170 | integerPart := new(big.Int).SetUint64(math.MaxUint64) 171 | value := new(big.Int).Mul(integerPart, precisionFactor) 172 | if d.value.Cmp(value) > 0 { 173 | return true 174 | } 175 | return false 176 | } 177 | 178 | func (d *Decimal) Float64() float64 { 179 | if d == nil { 180 | return 0 181 | } 182 | value := new(big.Int).Abs(d.value) 183 | quotient, remainder := new(big.Int).QuoRem(value, precisionFactor, new(big.Int)) 184 | f := float64(quotient.Uint64()) + float64(remainder.Uint64())/math.MaxFloat64 185 | if d.value.Sign() < 0 { 186 | return -f 187 | } 188 | return f 189 | } 190 | -------------------------------------------------------------------------------- /utils/utils.go: -------------------------------------------------------------------------------- 1 | package utils 2 | 3 | import ( 4 | "bytes" 5 | "encoding/binary" 6 | "encoding/hex" 7 | "github.com/wealdtech/go-merkletree/keccak256" 8 | "math/big" 9 | "strconv" 10 | "strings" 11 | ) 12 | 13 | func Keccak256(str string) string { 14 | h := keccak256.New() 15 | bytes := h.Hash([]byte(str)) 16 | return hex.EncodeToString(bytes) 17 | } 18 | 19 | func HexToUint64(hex string) uint64 { 20 | num, ok := new(big.Int).SetString(hex[2:], 16) 21 | if ok { 22 | return num.Uint64() 23 | } else { 24 | return 0 25 | } 26 | } 27 | 28 | func HexToUint32(hex string) uint32 { 29 | return uint32(HexToUint64(hex)) 30 | } 31 | 32 | func HexStrToBytes(hexStr string) []byte { 33 | if strings.HasPrefix(hexStr, "0x") { 34 | hexStr = hexStr[2:] 35 | } 36 | data, err := hex.DecodeString(hexStr) 37 | if err != nil { 38 | return make([]byte, 0) 39 | } 40 | return data 41 | } 42 | 43 | func BytesToHexStr(hexBytes []byte) string { 44 | data := hex.EncodeToString(hexBytes) 45 | return "0x" + data 46 | } 47 | 48 | func BoolToUint32(b bool) uint32 { 49 | if b { 50 | return 1 51 | } 52 | return 0 53 | } 54 | 55 | func ParseInt32(str string) int32 { 56 | if strings.Contains(str, ".") { 57 | str = strings.Split(str, ".")[0] 58 | } 59 | rst, err := strconv.ParseInt(str, 10, 32) 60 | if err != nil { 61 | return 0 62 | } else { 63 | return int32(rst) 64 | } 65 | } 66 | 67 | func ParseInt64(str string) int64 { 68 | if strings.Contains(str, ".") { 69 | str = strings.Split(str, ".")[0] 70 | } 71 | rst, err := strconv.ParseInt(str, 10, 64) 72 | if err != nil { 73 | return 0 74 | } else { 75 | return rst 76 | } 77 | } 78 | 79 | func Uint64ToBytes(value uint64) []byte { 80 | buffer := new(bytes.Buffer) 81 | binary.Write(buffer, binary.BigEndian, value) 82 | return buffer.Bytes() 83 | } 84 | 85 | func BytesToUint64(_bytes []byte) uint64 { 86 | var value uint64 87 | err := binary.Read(bytes.NewReader(_bytes), binary.BigEndian, &value) 88 | if err != nil { 89 | return 0 90 | } 91 | return value 92 | } 93 | 94 | func TopicToAddress(str string) string { 95 | return "0x" + str[26:] 96 | } 97 | 98 | func TopicToBigInt(str string) *big.Int { 99 | var start = 0 100 | for i, c := range str { 101 | if i > 1 && c != '0' { 102 | start = i 103 | break 104 | } 105 | } 106 | if start == 0 { 107 | return new(big.Int).SetInt64(0) 108 | } 109 | hex := str[start:] 110 | 111 | num, ok := new(big.Int).SetString(hex, 16) 112 | if !ok { 113 | return new(big.Int).SetInt64(0) 114 | } 115 | return num 116 | } 117 | --------------------------------------------------------------------------------