├── .gitignore
├── README.md
├── RugNinja.arc4.json
├── assets
├── gcat.webp
└── start_preview.png
├── errors
└── errors.go
├── go.mod
├── go.sum
├── internal
├── algod
│ ├── algod.go
│ └── stxn.go
├── config
│ └── cfg.go
├── rego
│ └── opa.go
├── simple
│ └── stdout.go
└── utils
│ └── utils.go
├── ipfs
├── ipfs.go
└── types.go
├── main.go
├── misc
├── algorand.go
└── misc.go
└── store
└── store.go
/.gitignore:
--------------------------------------------------------------------------------
1 | secret.txt
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Garbage Cat
2 |
3 | The Blazing Fast Rug Ninja Sniper
4 |
5 | 
6 |
7 | ## Getting Started
8 | This is a mint sniper bot for [Rug.Ninja](https://rug.ninja).
It creates a new hot wallet and will purchase any newly minted tokens in the amount you set ( defaults to 1 algo )
9 |
10 |
11 | ### Prerequisites
12 | You'll need go 1.22.1 installed on your local machine. See https://go.dev/ for installation instructions or use Homebrew on MacOS.
13 |
14 | ### Running the Server
15 |
16 | > [!NOTE]
17 | > #### Flags
18 | > `-amt` : The amount of Algo to spend for every newly minted token
19 |
20 | You can compile and run the server with the following command ran from the root of the project:
21 | ```bash
22 | go run main.go -amt 10_000_000
23 | ```
24 |
25 | ### First Run
26 |
27 | The first time you run the garbage cat server it will generate a new Algorand account.
28 |
29 | > [!CAUTION]
30 | > Garbage Cat takes no responsibility for loss of funds,
31 | > he's a cat.
32 | > You as the operator of this free, open source software are solely responsible for keeping your new account safe & backing up your mnemonics.
33 |
34 | 
35 |
36 | Fund the account with some Algo & whenever it detects a new rug ninja token mint, it will instantly purchase the amount you set when you started the server.
--------------------------------------------------------------------------------
/RugNinja.arc4.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "RugNinja",
3 | "desc": "",
4 | "methods": [
5 | {
6 | "name": "MANAGER_config",
7 | "args": [
8 | {
9 | "name": "manager",
10 | "type": "address"
11 | },
12 | {
13 | "name": "tinymanApp",
14 | "type": "uint64"
15 | },
16 | {
17 | "name": "bonfireApp",
18 | "type": "uint64"
19 | },
20 | {
21 | "name": "vrfApp",
22 | "type": "uint64"
23 | },
24 | {
25 | "name": "deploymentFee",
26 | "type": "uint64"
27 | },
28 | {
29 | "name": "swapFee",
30 | "type": "uint64"
31 | },
32 | {
33 | "name": "lockupMin",
34 | "type": "uint64"
35 | },
36 | {
37 | "name": "lockupRange",
38 | "type": "uint64"
39 | },
40 | {
41 | "name": "targetReserves",
42 | "type": "uint64"
43 | }
44 | ],
45 | "returns": {
46 | "type": "void"
47 | }
48 | },
49 | {
50 | "name": "MANAGER_claimEarnings",
51 | "args": [],
52 | "returns": {
53 | "type": "void"
54 | }
55 | },
56 | {
57 | "name": "createCoin",
58 | "args": [
59 | {
60 | "name": "name",
61 | "type": "byte[]"
62 | },
63 | {
64 | "name": "ticker",
65 | "type": "byte[]"
66 | },
67 | {
68 | "name": "supply",
69 | "type": "uint64"
70 | }
71 | ],
72 | "returns": {
73 | "type": "void"
74 | }
75 | },
76 | {
77 | "name": "buyCoin",
78 | "args": [
79 | {
80 | "name": "assetId",
81 | "type": "uint64"
82 | },
83 | {
84 | "name": "minOut",
85 | "type": "uint64"
86 | }
87 | ],
88 | "returns": {
89 | "type": "void"
90 | }
91 | },
92 | {
93 | "name": "sellCoin",
94 | "args": [
95 | {
96 | "name": "asset",
97 | "type": "uint64"
98 | },
99 | {
100 | "name": "amountIn",
101 | "type": "uint64"
102 | },
103 | {
104 | "name": "minOut",
105 | "type": "uint64"
106 | }
107 | ],
108 | "returns": {
109 | "type": "void"
110 | }
111 | },
112 | {
113 | "name": "retryCoinVRF",
114 | "args": [
115 | {
116 | "name": "asset",
117 | "type": "uint64"
118 | }
119 | ],
120 | "returns": {
121 | "type": "void"
122 | }
123 | },
124 | {
125 | "name": "deployCoin",
126 | "args": [
127 | {
128 | "name": "asset",
129 | "type": "uint64"
130 | },
131 | {
132 | "name": "poolAddress",
133 | "type": "address"
134 | },
135 | {
136 | "name": "poolToken",
137 | "type": "uint64"
138 | }
139 | ],
140 | "returns": {
141 | "type": "void"
142 | }
143 | },
144 | {
145 | "name": "claimCoin",
146 | "args": [
147 | {
148 | "name": "asset",
149 | "type": "uint64"
150 | }
151 | ],
152 | "returns": {
153 | "type": "void"
154 | }
155 | },
156 | {
157 | "name": "tipCoin",
158 | "args": [
159 | {
160 | "name": "asset",
161 | "type": "uint64"
162 | },
163 | {
164 | "name": "amount",
165 | "type": "uint64"
166 | },
167 | {
168 | "name": "receiver",
169 | "type": "address"
170 | }
171 | ],
172 | "returns": {
173 | "type": "void"
174 | }
175 | }
176 | ]
177 | }
--------------------------------------------------------------------------------
/assets/gcat.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/garbagecatio/garbage-cat/2e150023ea1e9fb2b82c8e7522e003d7537fbc93/assets/gcat.webp
--------------------------------------------------------------------------------
/assets/start_preview.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/garbagecatio/garbage-cat/2e150023ea1e9fb2b82c8e7522e003d7537fbc93/assets/start_preview.png
--------------------------------------------------------------------------------
/errors/errors.go:
--------------------------------------------------------------------------------
1 | package errors
2 |
3 | import (
4 | "fmt"
5 | "strings"
6 |
7 | "github.com/getsentry/sentry-go"
8 | )
9 |
10 | // Op is a unique string describing a method or a function
11 | // Multiple operations can construct a friendly stack tace.
12 | type Op string
13 |
14 | // Pkg is the package name our error originates from
15 | type Pkg string
16 |
17 | // Sn is a server name field
18 | // each microservice in our network is given a unique name
19 | type Sn string
20 |
21 | // Kind is a string categorizing the error
22 | type Kind string
23 |
24 | const (
25 | Database Kind = "Database"
26 | DatabaseResultNotFound Kind = "Database Result Not Found"
27 | Network Kind = "Network"
28 | Type Kind = "Type"
29 | )
30 |
31 | // IsKind unwraps an *Error and checks if its top level kind matches
32 | func IsKind(err error, kind Kind) bool {
33 | unwrapped, ok := err.(*Error)
34 | if !ok {
35 | panic("bad call to IsKind")
36 | }
37 |
38 | if unwrapped.Kind == kind {
39 | return true
40 | }
41 | return false
42 | }
43 |
44 | // Error is a custom Error struct for quickly diagnosing issues with our app
45 | type Error struct {
46 | Sn Sn `json:"server,omitempty"` // server name
47 | Pkg Pkg `json:"package,omitempty"` // package module
48 | Op Op `json:"operation,omitempty"` // operation
49 | Kind Kind `json:"kind,omitempty"` // category of errors
50 | Err error `json:"err,omitempty"` // the wrapped error
51 | Msg string `json:"message,omitempty"`
52 | }
53 |
54 | func Sentry(hub *sentry.Hub, err error) {
55 | if hub != nil {
56 | e, ok := err.(*Error)
57 | if !ok {
58 | sentry.CaptureException(err)
59 | return
60 | }
61 |
62 | hub.WithScope(func(scope *sentry.Scope) {
63 | scope.AddEventProcessor(func(event *sentry.Event, hint *sentry.EventHint) *sentry.Event {
64 | if e.Kind != "" && e.Msg != "" {
65 | event.Exception[0].Type = fmt.Sprintf("%v: %v", e.Kind, e.Msg)
66 | return event
67 | } else if e.Kind != "" {
68 | event.Exception[0].Type = fmt.Sprintf("%v", e.Kind)
69 | return event
70 | } else if e.Msg != "" {
71 | event.Exception[0].Type = fmt.Sprintf("%v", e.Msg)
72 | return event
73 | }
74 | return event
75 | })
76 | hub.CaptureException(e)
77 | })
78 | }
79 | }
80 |
81 | func (e *Error) Error() string {
82 | payload := "------------------------------------------------------------------------\n"
83 |
84 | if e.Sn != "" {
85 | payload += fmt.Sprintf("\t server: %v\n", e.Sn)
86 | }
87 |
88 | if e.Pkg != "" {
89 | payload += fmt.Sprintf("\t package: %v\n", e.Pkg)
90 | }
91 |
92 | if e.Op != "" {
93 | payload += fmt.Sprintf("\toperation: %v\n", e.Op)
94 | }
95 |
96 | if e.Kind != "" {
97 | payload += fmt.Sprintf("\t kind: %v\n", e.Kind)
98 | }
99 |
100 | if e.Msg != "" {
101 | payload += fmt.Sprintf("\t message: %v\n", e.Msg)
102 | }
103 |
104 | res := []string{payload}
105 | subErr, ok := e.Err.(*Error)
106 | if !ok {
107 | payload += fmt.Sprintf("\t error: %v\n", e.Err)
108 | return payload
109 | }
110 | res = append(res, subErr.Error())
111 | return fmt.Sprintf("\nTrace\n%v------------------------------------------------------------------------\n", strings.Join(res, ""))
112 | }
113 |
114 | // E is a helper method for filling our Error Struct, it panics if you send anything other than an error, string, Op, or Kind
115 | func E(args ...interface{}) error {
116 | e := &Error{}
117 | for _, arg := range args {
118 | switch arg := arg.(type) {
119 | case Sn:
120 | e.Sn = arg
121 | case Pkg:
122 | e.Pkg = arg
123 | case Op:
124 | e.Op = arg
125 | case Kind:
126 | e.Kind = arg
127 | case error:
128 | e.Err = arg
129 | case Error:
130 | e.Err = &arg
131 | case string:
132 | e.Msg = arg
133 | default:
134 | panic("bad call to E")
135 | }
136 | }
137 | return e
138 | }
139 |
140 | // Servers is a recursive function for building a trace of servers
141 | func Servers(err error) []Sn {
142 | unwrapped, ok := err.(*Error)
143 | if !ok {
144 | panic("bad call to Servers")
145 | }
146 |
147 | res := []Sn{unwrapped.Sn}
148 | subErr, ok := unwrapped.Err.(*Error)
149 | if !ok {
150 | return res
151 | }
152 | res = append(res, Servers(subErr)...)
153 | return res
154 | }
155 |
156 | // Packages is a recursive function for building a trace of packages
157 | func Packages(err error) []Pkg {
158 | unwrapped, ok := err.(*Error)
159 | if !ok {
160 | panic("bad call to Packages")
161 | }
162 |
163 | res := []Pkg{unwrapped.Pkg}
164 | subErr, ok := unwrapped.Err.(*Error)
165 | if !ok {
166 | return res
167 | }
168 | res = append(res, Packages(subErr)...)
169 | return res
170 | }
171 |
172 | // Operations is a recursive function for building a trace of operations
173 | func Operations(err error) []Op {
174 | unwrapped, ok := err.(*Error)
175 | if !ok {
176 | panic("bad call to Operations")
177 | }
178 |
179 | res := []Op{unwrapped.Op}
180 | subErr, ok := unwrapped.Err.(*Error)
181 | if !ok {
182 | return res
183 | }
184 | res = append(res, Operations(subErr)...)
185 | return res
186 | }
187 |
188 | // Kinds is a recursive function for building a trace of kinds
189 | func Kinds(err error) []Kind {
190 | unwrapped, ok := err.(*Error)
191 | if !ok {
192 | panic("bad call to Kind")
193 | }
194 |
195 | res := []Kind{unwrapped.Kind}
196 | subErr, ok := unwrapped.Err.(*Error)
197 | if !ok {
198 | return res
199 | }
200 | res = append(res, Kinds(subErr)...)
201 | return res
202 | }
203 |
204 | // Messages is a recursive function for building a trace of Messages
205 | func Messages(err error) []string {
206 | unwrapped, ok := err.(*Error)
207 | if !ok {
208 | panic("bad call to Messages")
209 | }
210 |
211 | res := []string{unwrapped.Msg}
212 | subErr, ok := unwrapped.Err.(*Error)
213 | if !ok {
214 | return res
215 | }
216 | res = append(res, Messages(subErr)...)
217 | return res
218 | }
219 |
220 | // LastMessage retrieves the last message on an error chain
221 | func LastMessage(err error) string {
222 | msgs := Messages(err)
223 | return msgs[len(msgs)-1]
224 | }
225 |
--------------------------------------------------------------------------------
/go.mod:
--------------------------------------------------------------------------------
1 | module github.com/garbagecatio/rug-ninja-sniper
2 |
3 | go 1.22.1
4 |
5 | require (
6 | github.com/algorand/go-algorand v0.0.0-20240719180807-75bb6a90320c
7 | github.com/algorand/go-algorand-sdk/v2 v2.6.0
8 | github.com/algorand/go-codec/codec v1.1.10
9 | github.com/fatih/color v1.17.0
10 | github.com/getsentry/sentry-go v0.28.1
11 | github.com/ipfs/go-cid v0.4.1
12 | github.com/mdp/qrterminal v1.0.1
13 | github.com/multiformats/go-multicodec v0.9.0
14 | github.com/multiformats/go-multihash v0.2.3
15 | github.com/open-policy-agent/opa v0.66.0
16 | github.com/tidwall/jsonc v0.3.2
17 | )
18 |
19 | require (
20 | github.com/OneOfOne/xxhash v1.2.8 // indirect
21 | github.com/algorand/avm-abi v0.2.0 // indirect
22 | github.com/algorand/go-deadlock v0.2.3 // indirect
23 | github.com/algorand/msgp v1.1.60 // indirect
24 | github.com/beorn7/perks v1.0.1 // indirect
25 | github.com/cespare/xxhash/v2 v2.2.0 // indirect
26 | github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect
27 | github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect
28 | github.com/google/go-querystring v1.1.0 // indirect
29 | github.com/klauspost/cpuid/v2 v2.2.7 // indirect
30 | github.com/mattn/go-colorable v0.1.13 // indirect
31 | github.com/mattn/go-isatty v0.0.20 // indirect
32 | github.com/minio/sha256-simd v1.0.1 // indirect
33 | github.com/mr-tron/base58 v1.2.0 // indirect
34 | github.com/multiformats/go-base32 v0.1.0 // indirect
35 | github.com/multiformats/go-base36 v0.2.0 // indirect
36 | github.com/multiformats/go-multibase v0.2.0 // indirect
37 | github.com/multiformats/go-varint v0.0.7 // indirect
38 | github.com/petermattis/goid v0.0.0-20180202154549-b0b1615b78e5 // indirect
39 | github.com/pmezard/go-difflib v1.0.0 // indirect
40 | github.com/prometheus/client_golang v1.19.1 // indirect
41 | github.com/prometheus/client_model v0.6.1 // indirect
42 | github.com/prometheus/common v0.48.0 // indirect
43 | github.com/prometheus/procfs v0.12.0 // indirect
44 | github.com/rcrowley/go-metrics v0.0.0-20200313005456-10cdbea86bc0 // indirect
45 | github.com/spaolacci/murmur3 v1.1.0 // indirect
46 | github.com/stretchr/testify v1.8.4 // indirect
47 | github.com/xeipuuv/gojsonpointer v0.0.0-20190905194746-02993c407bfb // indirect
48 | github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415 // indirect
49 | go.opencensus.io v0.24.0 // indirect
50 | golang.org/x/crypto v0.21.0 // indirect
51 | golang.org/x/exp v0.0.0-20240213143201-ec583247a57a // indirect
52 | golang.org/x/sys v0.21.0 // indirect
53 | golang.org/x/text v0.16.0 // indirect
54 | google.golang.org/protobuf v1.33.0 // indirect
55 | gopkg.in/yaml.v2 v2.4.0 // indirect
56 | gopkg.in/yaml.v3 v3.0.1 // indirect
57 | lukechampine.com/blake3 v1.2.1 // indirect
58 | rsc.io/qr v0.2.0 // indirect
59 | sigs.k8s.io/yaml v1.4.0 // indirect
60 | )
61 |
--------------------------------------------------------------------------------
/go.sum:
--------------------------------------------------------------------------------
1 | cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
2 | github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
3 | github.com/OneOfOne/xxhash v1.2.8 h1:31czK/TI9sNkxIKfaUfGlU47BAxQ0ztGgd9vPyqimf8=
4 | github.com/OneOfOne/xxhash v1.2.8/go.mod h1:eZbhyaAYD41SGSSsnmcpxVoRiQ/MPUTjUdIIOT9Um7Q=
5 | github.com/algorand/avm-abi v0.2.0 h1:bkjsG+BOEcxUcnGSALLosmltE0JZdg+ZisXKx0UDX2k=
6 | github.com/algorand/avm-abi v0.2.0/go.mod h1:+CgwM46dithy850bpTeHh9MC99zpn2Snirb3QTl2O/g=
7 | github.com/algorand/go-algorand v0.0.0-20240719180807-75bb6a90320c h1:2sFyk03RFcdJruO4C56uyCxJ2OGNze8T2TkhnhX4zO4=
8 | github.com/algorand/go-algorand v0.0.0-20240719180807-75bb6a90320c/go.mod h1:WRPM/mmwktMCPtqGzpYqvJvrl9WgAheKguBxr1++AMk=
9 | github.com/algorand/go-algorand-sdk/v2 v2.6.0 h1:pfL8lloEi26l6PwAFicmPUguWgKpy1eZZTMlQcci5h0=
10 | github.com/algorand/go-algorand-sdk/v2 v2.6.0/go.mod h1:4ayerzjoWChm3kuVhbgFgURTbaYTtlj0c41eP3av5lw=
11 | github.com/algorand/go-codec/codec v1.1.10 h1:zmWYU1cp64jQVTOG8Tw8wa+k0VfwgXIPbnDfiVa+5QA=
12 | github.com/algorand/go-codec/codec v1.1.10/go.mod h1:YkEx5nmr/zuCeaDYOIhlDg92Lxju8tj2d2NrYqP7g7k=
13 | github.com/algorand/go-deadlock v0.2.3 h1:ek9rjUyUF1HhUm0I2DyaCN8+3S850ONJNl5jQr9kZOA=
14 | github.com/algorand/go-deadlock v0.2.3/go.mod h1:Gli2d0Cb7kgXzSpJLC4Vn0DCLgjNVi6fNldY/mOtO/U=
15 | github.com/algorand/msgp v1.1.60 h1:+IVUC34+tSj1P2M1mkYtl4GLyfzdzXfBLSw6TDT19M8=
16 | github.com/algorand/msgp v1.1.60/go.mod h1:RqZQBzAFDWpwh5TlabzZkWy+6kwL9cvXfLbU0gD99EA=
17 | github.com/aws/aws-sdk-go v1.34.0 h1:brux2dRrlwCF5JhTL7MUT3WUwo9zfDHZZp3+g3Mvlmo=
18 | github.com/aws/aws-sdk-go v1.34.0/go.mod h1:5zCpMtNQVjRREroY7sYe8lOMRSxkhG6MZveU8YkpAk0=
19 | github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM=
20 | github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw=
21 | github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU=
22 | github.com/cespare/xxhash/v2 v2.2.0 h1:DC2CZ1Ep5Y4k3ZQ899DldepgrayRUGE6BBZ/cd9Cj44=
23 | github.com/cespare/xxhash/v2 v2.2.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
24 | github.com/chrismcguire/gobberish v0.0.0-20150821175641-1d8adb509a0e h1:CHPYEbz71w8DqJ7DRIq+MXyCQsdibK08vdcQTY4ufas=
25 | github.com/chrismcguire/gobberish v0.0.0-20150821175641-1d8adb509a0e/go.mod h1:6Xhs0ZlsRjXLIiSMLKafbZxML/j30pg9Z1priLuha5s=
26 | github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw=
27 | github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc=
28 | github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
29 | github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
30 | github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM=
31 | github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
32 | github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
33 | github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
34 | github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98=
35 | github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c=
36 | github.com/fatih/color v1.17.0 h1:GlRw1BRJxkpqUCBKzKOw098ed57fEsKeNjpTe3cSjK4=
37 | github.com/fatih/color v1.17.0/go.mod h1:YZ7TlrGPkiz6ku9fK3TLD/pl3CpsiFyu8N92HLgmosI=
38 | github.com/getsentry/sentry-go v0.28.1 h1:zzaSm/vHmGllRM6Tpx1492r0YDzauArdBfkJRtY6P5k=
39 | github.com/getsentry/sentry-go v0.28.1/go.mod h1:1fQZ+7l7eeJ3wYi82q5Hg8GqAPgefRq+FP/QhafYVgg=
40 | github.com/go-errors/errors v1.4.2 h1:J6MZopCL4uSllY1OfXM374weqZFFItUbrImctkmUxIA=
41 | github.com/go-errors/errors v1.4.2/go.mod h1:sIVyrIiJhuEF+Pj9Ebtd6P/rEYROXFi3BopGUQ5a5Og=
42 | github.com/gobwas/glob v0.2.3 h1:A4xDbljILXROh+kObIiy5kIaPYD8e96x1tgBhUI5J+Y=
43 | github.com/gobwas/glob v0.2.3/go.mod h1:d3Ez4x06l9bZtSvzIay5+Yzi0fmZzPgnTbPcKjJAkT8=
44 | github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=
45 | github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
46 | github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da h1:oI5xCqsCo564l8iNU+DwB5epxmsaqB+rhGL0m5jtYqE=
47 | github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
48 | github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
49 | github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
50 | github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
51 | github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8=
52 | github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA=
53 | github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs=
54 | github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w=
55 | github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0=
56 | github.com/golang/protobuf v1.4.1/go.mod h1:U8fpvMrcmy5pZrNK1lt4xCsGvpyWQ/VVv6QDs8UjoX8=
57 | github.com/golang/protobuf v1.4.3/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI=
58 | github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M=
59 | github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
60 | github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
61 | github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
62 | github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
63 | github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
64 | github.com/google/go-cmp v0.5.3/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
65 | github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
66 | github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI=
67 | github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
68 | github.com/google/go-querystring v1.1.0 h1:AnCroh3fv4ZBgVIf1Iwtovgjaw/GiKJo8M8yD/fhyJ8=
69 | github.com/google/go-querystring v1.1.0/go.mod h1:Kcdr2DB4koayq7X8pmAG4sNG59So17icRSOU623lUBU=
70 | github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
71 | github.com/ipfs/go-cid v0.4.1 h1:A/T3qGvxi4kpKWWcPC/PgbvDA2bjVLO7n4UeVwnbs/s=
72 | github.com/ipfs/go-cid v0.4.1/go.mod h1:uQHwDeX4c6CtyrFwdqyhpNcxVewur1M7l7fNU7LKwZk=
73 | github.com/jmespath/go-jmespath v0.3.0 h1:OS12ieG61fsCg5+qLJ+SsW9NicxNkg3b25OyT2yCeUc=
74 | github.com/jmespath/go-jmespath v0.3.0/go.mod h1:9QtRXoHjLGCJ5IBSaohpXITPlowMeeYCZ7fLUTSywik=
75 | github.com/klauspost/cpuid/v2 v2.2.7 h1:ZWSB3igEs+d0qvnxR/ZBzXVmxkgt8DdzP6m9pfuVLDM=
76 | github.com/klauspost/cpuid/v2 v2.2.7/go.mod h1:Lcz8mBdAVJIBVzewtcLocK12l3Y+JytZYpaMropDUws=
77 | github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
78 | github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk=
79 | github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
80 | github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
81 | github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA=
82 | github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg=
83 | github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM=
84 | github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=
85 | github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
86 | github.com/mdp/qrterminal v1.0.1 h1:07+fzVDlPuBlXS8tB0ktTAyf+Lp1j2+2zK3fBOL5b7c=
87 | github.com/mdp/qrterminal v1.0.1/go.mod h1:Z33WhxQe9B6CdW37HaVqcRKzP+kByF3q/qLxOGe12xQ=
88 | github.com/minio/sha256-simd v1.0.1 h1:6kaan5IFmwTNynnKKpDHe6FWHohJOHhCPchzK49dzMM=
89 | github.com/minio/sha256-simd v1.0.1/go.mod h1:Pz6AKMiUdngCLpeTL/RJY1M9rUuPMYujV5xJjtbRSN8=
90 | github.com/mr-tron/base58 v1.2.0 h1:T/HDJBh4ZCPbU39/+c3rRvE0uKBQlU27+QI8LJ4t64o=
91 | github.com/mr-tron/base58 v1.2.0/go.mod h1:BinMc/sQntlIE1frQmRFPUoPA1Zkr8VRgBdjWI2mNwc=
92 | github.com/multiformats/go-base32 v0.1.0 h1:pVx9xoSPqEIQG8o+UbAe7DNi51oej1NtK+aGkbLYxPE=
93 | github.com/multiformats/go-base32 v0.1.0/go.mod h1:Kj3tFY6zNr+ABYMqeUNeGvkIC/UYgtWibDcT0rExnbI=
94 | github.com/multiformats/go-base36 v0.2.0 h1:lFsAbNOGeKtuKozrtBsAkSVhv1p9D0/qedU9rQyccr0=
95 | github.com/multiformats/go-base36 v0.2.0/go.mod h1:qvnKE++v+2MWCfePClUEjE78Z7P2a1UV0xHgWc0hkp4=
96 | github.com/multiformats/go-multibase v0.2.0 h1:isdYCVLvksgWlMW9OZRYJEa9pZETFivncJHmHnnd87g=
97 | github.com/multiformats/go-multibase v0.2.0/go.mod h1:bFBZX4lKCA/2lyOFSAoKH5SS6oPyjtnzK/XTFDPkNuk=
98 | github.com/multiformats/go-multicodec v0.9.0 h1:pb/dlPnzee/Sxv/j4PmkDRxCOi3hXTz3IbPKOXWJkmg=
99 | github.com/multiformats/go-multicodec v0.9.0/go.mod h1:L3QTQvMIaVBkXOXXtVmYE+LI16i14xuaojr/H7Ai54k=
100 | github.com/multiformats/go-multihash v0.2.3 h1:7Lyc8XfX/IY2jWb/gI7JP+o7JEq9hOa7BFvVU9RSh+U=
101 | github.com/multiformats/go-multihash v0.2.3/go.mod h1:dXgKXCXjBzdscBLk9JkjINiEsCKRVch90MdaGiKsvSM=
102 | github.com/multiformats/go-varint v0.0.7 h1:sWSGR+f/eu5ABZA2ZpYKBILXTTs9JWpdEM/nEGOHFS8=
103 | github.com/multiformats/go-varint v0.0.7/go.mod h1:r8PUYw/fD/SjBCiKOoDlGF6QawOELpZAu9eioSos/OU=
104 | github.com/open-policy-agent/opa v0.66.0 h1:DbrvfJQja0FBRcPOB3Z/BOckocN+M4ApNWyNhSRJt0w=
105 | github.com/open-policy-agent/opa v0.66.0/go.mod h1:EIgNnJcol7AvQR/IcWLwL13k64gHVbNAVG46b2G+/EY=
106 | github.com/petermattis/goid v0.0.0-20180202154549-b0b1615b78e5 h1:q2e307iGHPdTGp0hoxKjt1H5pDo6utceo3dQVK3I5XQ=
107 | github.com/petermattis/goid v0.0.0-20180202154549-b0b1615b78e5/go.mod h1:jvVRKCrJTQWu0XVbaOlby/2lO20uSCHEMzzplHXte1o=
108 | github.com/pingcap/errors v0.11.4 h1:lFuQV/oaUMGcD2tqt+01ROSmJs75VG1ToEOkZIZ4nE4=
109 | github.com/pingcap/errors v0.11.4/go.mod h1:Oi8TUi2kEtXXLMJk9l1cGmz20kV3TaQ0usTwv5KuLY8=
110 | github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
111 | github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
112 | github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
113 | github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
114 | github.com/prometheus/client_golang v1.19.1 h1:wZWJDwK+NameRJuPGDhlnFgx8e8HN3XHQeLaYJFJBOE=
115 | github.com/prometheus/client_golang v1.19.1/go.mod h1:mP78NwGzrVks5S2H6ab8+ZZGJLZUq1hoULYBAYBw1Ho=
116 | github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
117 | github.com/prometheus/client_model v0.6.1 h1:ZKSh/rekM+n3CeS952MLRAdFwIKqeY8b62p8ais2e9E=
118 | github.com/prometheus/client_model v0.6.1/go.mod h1:OrxVMOVHjw3lKMa8+x6HeMGkHMQyHDk9E3jmP2AmGiY=
119 | github.com/prometheus/common v0.48.0 h1:QO8U2CdOzSn1BBsmXJXduaaW+dY/5QLjfB8svtSzKKE=
120 | github.com/prometheus/common v0.48.0/go.mod h1:0/KsvlIEfPQCQ5I2iNSAWKPZziNCvRs5EC6ILDTlAPc=
121 | github.com/prometheus/procfs v0.12.0 h1:jluTpSng7V9hY0O2R9DzzJHYb2xULk9VTR1V1R/k6Bo=
122 | github.com/prometheus/procfs v0.12.0/go.mod h1:pcuDEFsWDnvcgNzo4EEweacyhjeA9Zk3cnaOZAZEfOo=
123 | github.com/rcrowley/go-metrics v0.0.0-20200313005456-10cdbea86bc0 h1:MkV+77GLUNo5oJ0jf870itWm3D0Sjh7+Za9gazKc5LQ=
124 | github.com/rcrowley/go-metrics v0.0.0-20200313005456-10cdbea86bc0/go.mod h1:bCqnVzQkZxMG4s8nGwiZ5l3QUCyqpo9Y+/ZMZ9VjZe4=
125 | github.com/rogpeppe/go-internal v1.10.0 h1:TMyTOH3F/DB16zRVcYyreMH6GnZZrwQVAoYjRBZyWFQ=
126 | github.com/rogpeppe/go-internal v1.10.0/go.mod h1:UQnix2H7Ngw/k4C5ijL5+65zddjncjaFoBhdsK/akog=
127 | github.com/spaolacci/murmur3 v1.1.0 h1:7c1g84S4BPRrfL5Xrdp6fOJ206sU9y293DDHaoy0bLI=
128 | github.com/spaolacci/murmur3 v1.1.0/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA=
129 | github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
130 | github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
131 | github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
132 | github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
133 | github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
134 | github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
135 | github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk=
136 | github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
137 | github.com/tidwall/jsonc v0.3.2 h1:ZTKrmejRlAJYdn0kcaFqRAKlxxFIC21pYq8vLa4p2Wc=
138 | github.com/tidwall/jsonc v0.3.2/go.mod h1:dw+3CIxqHi+t8eFSpzzMlcVYxKp08UP5CD8/uSFCyJE=
139 | github.com/xeipuuv/gojsonpointer v0.0.0-20190905194746-02993c407bfb h1:zGWFAtiMcyryUHoUjUJX0/lt1H2+i2Ka2n+D3DImSNo=
140 | github.com/xeipuuv/gojsonpointer v0.0.0-20190905194746-02993c407bfb/go.mod h1:N2zxlSyiKSe5eX1tZViRH5QA0qijqEDrYZiPEAiq3wU=
141 | github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415 h1:EzJWgHovont7NscjpAxXsDA8S8BMYve8Y5+7cuRE7R0=
142 | github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415/go.mod h1:GwrjFmJcFw6At/Gs6z4yjiIwzuJ1/+UwLxMQDVQXShQ=
143 | github.com/yuin/goldmark v1.4.1/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k=
144 | go.opencensus.io v0.24.0 h1:y73uSU6J157QMP2kn2r30vwW1A2W2WFwSCGnAVxeaD0=
145 | go.opencensus.io v0.24.0/go.mod h1:vNK8G9p7aAivkbmorf4v+7Hgx+Zs0yY+0fOtgBfjQKo=
146 | golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
147 | golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
148 | golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
149 | golang.org/x/crypto v0.21.0 h1:X31++rzVUdKhX5sWmSOFZxx8UW/ldWx55cbf08iNAMA=
150 | golang.org/x/crypto v0.21.0/go.mod h1:0BP7YvVV9gBbVKyeTG0Gyn+gZm94bibOW5BjDEYAOMs=
151 | golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
152 | golang.org/x/exp v0.0.0-20240213143201-ec583247a57a h1:HinSgX1tJRX3KsL//Gxynpw5CTOAIPhgL4W8PNiIpVE=
153 | golang.org/x/exp v0.0.0-20240213143201-ec583247a57a/go.mod h1:CxmFvTBINI24O/j8iY7H1xHzx2i4OsyguNBmN/uPtqc=
154 | golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
155 | golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU=
156 | golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
157 | golang.org/x/mod v0.6.0-dev.0.20220106191415-9b9b3d81d5e3/go.mod h1:3p9vT2HGsQu2K1YbXdKPJLVgG5VJdoTa1poYQBtP1AY=
158 | golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
159 | golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
160 | golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
161 | golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
162 | golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
163 | golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
164 | golang.org/x/net v0.0.0-20201110031124-69a78807bb2b/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
165 | golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
166 | golang.org/x/net v0.0.0-20211015210444-4f30a5c0130f/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
167 | golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
168 | golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
169 | golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
170 | golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
171 | golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
172 | golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
173 | golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
174 | golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
175 | golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
176 | golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
177 | golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
178 | golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
179 | golang.org/x/sys v0.0.0-20211019181941-9d821ace8654/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
180 | golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
181 | golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
182 | golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
183 | golang.org/x/sys v0.21.0 h1:rF+pYz3DAGSQAxAu1CbC7catZg4ebC4UIeIhKxBZvws=
184 | golang.org/x/sys v0.21.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
185 | golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
186 | golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
187 | golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
188 | golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
189 | golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
190 | golang.org/x/text v0.16.0 h1:a94ExnEXNtEwYLGJSIUxnWoxoRz/ZcCsV63ROupILh4=
191 | golang.org/x/text v0.16.0/go.mod h1:GhwF1Be+LQoKShO3cGOHzqOgRrGaYc9AvblQOmPVHnI=
192 | golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
193 | golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
194 | golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY=
195 | golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
196 | golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
197 | golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
198 | golang.org/x/tools v0.1.10/go.mod h1:Uh6Zz+xoGYZom868N8YTex3t7RhtHDBrE8Gzo9bV56E=
199 | golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
200 | golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
201 | golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
202 | golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
203 | google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM=
204 | google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
205 | google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc=
206 | google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc=
207 | google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo=
208 | google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c=
209 | google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg=
210 | google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY=
211 | google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk=
212 | google.golang.org/grpc v1.33.2/go.mod h1:JMHMWHQWaTccqQQlmk3MJZS+GWXOdAesneDmEnv2fbc=
213 | google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8=
214 | google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0=
215 | google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM=
216 | google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE=
217 | google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo=
218 | google.golang.org/protobuf v1.22.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
219 | google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
220 | google.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
221 | google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c=
222 | google.golang.org/protobuf v1.33.0 h1:uNO2rsAINq/JlFpSdYEKIZ0uKD/R9cpdv0T+yoGwGmI=
223 | google.golang.org/protobuf v1.33.0/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos=
224 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
225 | gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
226 | gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
227 | gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY=
228 | gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
229 | gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
230 | gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
231 | gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
232 | honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
233 | honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
234 | lukechampine.com/blake3 v1.2.1 h1:YuqqRuaqsGV71BV/nm9xlI0MKUv4QC54jQnBChWbGnI=
235 | lukechampine.com/blake3 v1.2.1/go.mod h1:0OFRp7fBtAylGVCO40o87sbupkyIGgbpv1+M1k1LM6k=
236 | pgregory.net/rapid v0.6.2 h1:ErW5sL+UKtfBfUTsWHDCoeB+eZKLKMxrSd1VJY6W4bw=
237 | pgregory.net/rapid v0.6.2/go.mod h1:PY5XlDGj0+V1FCq0o192FdRhpKHGTRIWBgqjDBTrq04=
238 | rsc.io/qr v0.2.0 h1:6vBLea5/NRMVTz8V66gipeLycZMl/+UlFmk8DvqQ6WY=
239 | rsc.io/qr v0.2.0/go.mod h1:IF+uZjkb9fqyeF/4tlBoynqmQxUoPfWEKh921coOuXs=
240 | sigs.k8s.io/yaml v1.4.0 h1:Mk1wCc2gy/F0THH0TAp1QYyJNzRm2KCLy3o5ASXVI5E=
241 | sigs.k8s.io/yaml v1.4.0/go.mod h1:Ejl7/uTz7PSA4eKMyQCUTnhZYNmLIl+5c2lQPGR2BPY=
242 |
--------------------------------------------------------------------------------
/internal/algod/algod.go:
--------------------------------------------------------------------------------
1 | // Copyright (C) 2022 AlgoNode Org.
2 | //
3 | // algostreamer is free software: you can redistribute it and/or modify
4 | // it under the terms of the GNU Affero General Public License as
5 | // published by the Free Software Foundation, either version 3 of the
6 | // License, or (at your option) any later version.
7 | //
8 | // algostreamer is distributed in the hope that it will be useful,
9 | // but WITHOUT ANY WARRANTY; without even the implied warranty of
10 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 | // GNU Affero General Public License for more details.
12 | //
13 | // You should have received a copy of the GNU Affero General Public License
14 | // along with algostreamer. If not, see .
15 |
16 | package algod
17 |
18 | import (
19 | "context"
20 | "fmt"
21 | "math"
22 | "os"
23 | "sync/atomic"
24 | "time"
25 |
26 | "github.com/garbagecatio/rug-ninja-sniper/internal/utils"
27 |
28 | "github.com/algorand/go-algorand-sdk/v2/client/v2/algod"
29 | "github.com/algorand/go-algorand-sdk/v2/client/v2/common/models"
30 | "github.com/algorand/go-algorand-sdk/v2/encoding/msgpack"
31 |
32 | "github.com/algorand/go-algorand-sdk/v2/types"
33 | )
34 |
35 | type AlgoNodeConfig struct {
36 | Address string `json:"address"`
37 | Token string `json:"token"`
38 | Id string `json:"id"`
39 | }
40 |
41 | type AlgoConfig struct {
42 | ANodes []*AlgoNodeConfig `json:"nodes"`
43 | Queue int `json:"queue"`
44 | FRound int64 `json:"first"`
45 | LRound int64 `json:"last"`
46 | }
47 |
48 | type Status struct {
49 | LastRound uint64
50 | LagMs int64
51 | NodeId string
52 | LastCP string
53 | }
54 |
55 | type BlockWrap struct {
56 | Block *types.Block `json: "block"`
57 | BlockRaw []byte `json:"-"`
58 | Src string `json:"src"`
59 | Ts time.Time `json:"ts"`
60 | }
61 |
62 | // globalMaxBlock holds the highest read block across all connected nodes
63 | // writes must use atomic interface
64 | // reads are safe as the var is 64bit aligned
65 | var globalMaxBlock uint64 = 0
66 |
67 | func AlgoStreamer(ctx context.Context, acfg *AlgoConfig) (chan *BlockWrap, chan *Status, error) {
68 | qDepth := acfg.Queue
69 | if qDepth < 1 {
70 | qDepth = 100
71 | }
72 | bestbchan := make(chan *BlockWrap, qDepth)
73 | bchan := make(chan *BlockWrap, qDepth)
74 | schan := make(chan *Status, qDepth)
75 |
76 | for idx := range acfg.ANodes {
77 | if err := algodStreamNode(ctx, acfg, idx, bchan, schan, acfg.FRound, acfg.LRound); err != nil {
78 | return nil, nil, err
79 | }
80 | }
81 |
82 | // filter duplicates, forward only first newer blocks.
83 | go func() {
84 | var maxBlock uint64 = math.MaxUint64
85 | var maxTs time.Time = time.Now()
86 | var maxLeader string = "'"
87 | for {
88 | select {
89 | case bw := <-bchan:
90 | if uint64(bw.Block.Round) > maxBlock || maxBlock == math.MaxUint64 {
91 | bestbchan <- bw
92 | maxBlock = uint64(bw.Block.Round)
93 | atomic.StoreUint64(&globalMaxBlock, maxBlock)
94 | maxTs = bw.Ts
95 | maxLeader = bw.Src
96 | } else {
97 | if maxBlock == uint64(bw.Block.Round) {
98 | fmt.Fprintf(os.Stderr, "[INFO][ALGOD] Block from %s is %v behind %s\n", bw.Src, bw.Ts.Sub(maxTs), maxLeader)
99 | }
100 | }
101 | case <-ctx.Done():
102 | }
103 | }
104 | }()
105 |
106 | return bestbchan, schan, nil
107 | }
108 |
109 | func algodStreamNode(ctx context.Context, acfg *AlgoConfig, idx int, bchan chan *BlockWrap, schan chan *Status, start int64, stop int64) error {
110 |
111 | cfg := acfg.ANodes[idx]
112 | // Create an algod client
113 | algodClient, err := algod.MakeClient(cfg.Address, cfg.Token)
114 | if err != nil {
115 | fmt.Fprintf(os.Stderr, "[!ERR][ALGOD][%s] failed to make algod client: %s\n", cfg.Id, err)
116 | return err
117 | }
118 | fmt.Fprintf(os.Stderr, "[INFO][ALGOD][%s] new algod client: %s\n", cfg.Id, cfg.Address)
119 |
120 | //Loop until Algoverse gets cancelled
121 | go func() {
122 |
123 | var nodeStatus *models.NodeStatus = nil
124 | utils.Backoff(ctx, func(actx context.Context) error {
125 | fmt.Printf("[INFO][ALGOD][%s] Getting node status", cfg.Id)
126 | ns, err := algodClient.Status().Do(actx)
127 | if err != nil {
128 | return fmt.Errorf("[!ERR][ALGOD][%s] %s\n", cfg.Id, err.Error())
129 | }
130 | nodeStatus = &ns
131 | return nil
132 | }, time.Second*10, time.Millisecond*100, time.Second*10)
133 | if nodeStatus == nil {
134 | fmt.Fprintf(os.Stderr, "[!ERR][ALGOD][%s] Unable to start node\n", cfg.Id)
135 | return
136 | }
137 | schan <- &Status{NodeId: cfg.Id, LastCP: nodeStatus.LastCatchpoint, LastRound: uint64(nodeStatus.LastRound), LagMs: int64(nodeStatus.TimeSinceLastRound) / int64(time.Millisecond)}
138 |
139 | var nextRound uint64 = 0
140 | if start < 0 {
141 | nextRound = nodeStatus.LastRound
142 | fmt.Fprintf(os.Stderr, "[WARN][ALGOD][%s] Starting from last round : %d\n", cfg.Id, nodeStatus.LastRound)
143 | } else {
144 | nextRound = uint64(start)
145 | fmt.Fprintf(os.Stderr, "[WARN][ALGOD][%s] Starting from fixed round : %d\n", cfg.Id, nextRound)
146 | }
147 |
148 | ustop := uint64(stop)
149 | for stop < 0 || nextRound <= ustop {
150 | for ; nextRound <= nodeStatus.LastRound; nextRound++ {
151 | err := utils.Backoff(ctx, func(actx context.Context) error {
152 | gMax := globalMaxBlock
153 | //skip old blocks in case other nodes are ahead of us
154 | if gMax > nextRound {
155 | fmt.Fprintf(os.Stderr, "[WARN][ALGOD][%s] skipping ahead %d blocks to %d\n", cfg.Id, gMax-nextRound, gMax)
156 | nextRound = globalMaxBlock
157 | }
158 | rawBlock, err := algodClient.BlockRaw(nextRound).Do(ctx)
159 | if err != nil {
160 | return fmt.Errorf("[!ERR][ALGOD][%s] %s", cfg.Id, err.Error())
161 | }
162 | var response models.BlockResponse
163 | msgpack.CodecHandle.ErrorIfNoField = false
164 | if err = msgpack.Decode(rawBlock, &response); err != nil {
165 | return fmt.Errorf("[!ERR][ALGOD][%s] %s", cfg.Id, err.Error())
166 | }
167 | block := response.Block
168 |
169 | //fmt.Fprintf(os.Stderr, "got block %d, queue %d\n", block.Round, len(bchan))
170 | select {
171 | case bchan <- &BlockWrap{
172 | Block: &block,
173 | BlockRaw: rawBlock,
174 | Ts: time.Now(),
175 | Src: cfg.Id,
176 | }:
177 | case <-ctx.Done():
178 | }
179 | return ctx.Err()
180 | }, time.Second*10, time.Millisecond*100, time.Second*10)
181 | if err != nil || nextRound >= ustop {
182 | return
183 | }
184 | }
185 |
186 | err := utils.Backoff(ctx, func(actx context.Context) error {
187 | newStatus, err := algodClient.StatusAfterBlock(nodeStatus.LastRound).Do(actx)
188 | if err != nil {
189 | return fmt.Errorf("[!ERR][ALGOD][%s] %s", cfg.Id, err.Error())
190 | }
191 | nodeStatus = &newStatus
192 | //fmt.Fprintf(os.Stderr, "algod last round: %d, lag: %s\n", nodeStatus.LastRound, time.Duration(nodeStatus.TimeSinceLastRound)*time.Nanosecond)
193 | schan <- &Status{NodeId: cfg.Id, LastRound: uint64(nodeStatus.LastRound), LagMs: int64(nodeStatus.TimeSinceLastRound) / int64(time.Millisecond)}
194 | return nil
195 | }, time.Second*10, time.Millisecond*100, time.Second*10)
196 |
197 | if err != nil {
198 | return
199 | }
200 |
201 | }
202 | }()
203 |
204 | return nil
205 | }
206 |
--------------------------------------------------------------------------------
/internal/algod/stxn.go:
--------------------------------------------------------------------------------
1 | // Copyright (C) 2022 AlgoNode Org.
2 | //
3 | // algostreamer is free software: you can redistribute it and/or modify
4 | // it under the terms of the GNU Affero General Public License as
5 | // published by the Free Software Foundation, either version 3 of the
6 | // License, or (at your option) any later version.
7 | //
8 | // algostreamer is distributed in the hope that it will be useful,
9 | // but WITHOUT ANY WARRANTY; without even the implied warranty of
10 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 | // GNU Affero General Public License for more details.
12 | //
13 | // You should have received a copy of the GNU Affero General Public License
14 | // along with algostreamer. If not, see .
15 |
16 | package algod
17 |
18 | import (
19 | "fmt"
20 |
21 | "github.com/algorand/go-algorand-sdk/v2/crypto"
22 | "github.com/algorand/go-algorand-sdk/v2/types"
23 | "github.com/algorand/go-algorand/config"
24 | "github.com/algorand/go-algorand/protocol"
25 | )
26 |
27 | func DecodeTxnId(bh types.BlockHeader, stb *types.SignedTxnInBlock) (string, error) {
28 | st := &stb.SignedTxn
29 |
30 | proto, ok := config.Consensus[protocol.ConsensusVersion(bh.CurrentProtocol)]
31 | if !ok {
32 | return "", fmt.Errorf("consensus protocol %s not found", bh.CurrentProtocol)
33 | }
34 | if !proto.SupportSignedTxnInBlock {
35 | return "", nil
36 | }
37 |
38 | if st.Txn.GenesisID != "" {
39 | return "", fmt.Errorf("GenesisID <%s> not empty", st.Txn.GenesisID)
40 | }
41 |
42 | if stb.HasGenesisID {
43 | st.Txn.GenesisID = bh.GenesisID
44 | }
45 |
46 | if st.Txn.GenesisHash != (types.Digest{}) {
47 | return "", fmt.Errorf("GenesisHash <%v> not empty", st.Txn.GenesisHash)
48 | }
49 |
50 | if proto.RequireGenesisHash {
51 | if stb.HasGenesisHash {
52 | return "", fmt.Errorf("HasGenesisHash set to true but RequireGenesisHash obviates the flag")
53 | }
54 | st.Txn.GenesisHash = bh.GenesisHash
55 | } else {
56 | if stb.HasGenesisHash {
57 | st.Txn.GenesisHash = bh.GenesisHash
58 | }
59 | }
60 |
61 | return crypto.GetTxID(st.Txn), nil
62 | }
63 |
--------------------------------------------------------------------------------
/internal/config/cfg.go:
--------------------------------------------------------------------------------
1 | // Copyright (C) 2022 AlgoNode Org.
2 | //
3 | // algostreamer is free software: you can redistribute it and/or modify
4 | // it under the terms of the GNU Affero General Public License as
5 | // published by the Free Software Foundation, either version 3 of the
6 | // License, or (at your option) any later version.
7 | //
8 | // algostreamer is distributed in the hope that it will be useful,
9 | // but WITHOUT ANY WARRANTY; without even the implied warranty of
10 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 | // GNU Affero General Public License for more details.
12 | //
13 | // You should have received a copy of the GNU Affero General Public License
14 | // along with algostreamer. If not, see .
15 |
16 | package config
17 |
18 | import (
19 | "flag"
20 | "fmt"
21 |
22 | "github.com/garbagecatio/rug-ninja-sniper/internal/algod"
23 | "github.com/garbagecatio/rug-ninja-sniper/internal/rego"
24 | "github.com/garbagecatio/rug-ninja-sniper/internal/utils"
25 | )
26 |
27 | var cfgFile = flag.String("f", "config.jsonc", "config file")
28 | var firstRound = flag.Int64("r", -1, "first round to start [-1 = latest]")
29 | var lastRound = flag.Int64("l", -1, "last round to read [-1 = no limit]")
30 | var simpleFlag = flag.Bool("s", false, "simple mode - just sending blocks in JSON format to stdout")
31 |
32 | type StreamerConfig struct {
33 | Algod *algod.AlgoConfig `json:"algod"`
34 | Rego *rego.OpaConfig `json:"opa"`
35 | Stdout bool `json:"stdout"`
36 | }
37 |
38 | var defaultConfig = StreamerConfig{}
39 |
40 | // loadConfig loads the configuration from the specified file, merging into the default configuration.
41 | func LoadConfig() (cfg StreamerConfig, err error) {
42 | flag.Parse()
43 | cfg = defaultConfig
44 | err = utils.LoadJSONCFromFile(*cfgFile, &cfg)
45 |
46 | if cfg.Algod == nil {
47 | return cfg, fmt.Errorf("[CFG] Missing algod config")
48 | }
49 | if len(cfg.Algod.ANodes) == 0 {
50 | return cfg, fmt.Errorf("[CFG] Configure at least one node")
51 | }
52 | cfg.Algod.FRound = *firstRound
53 | cfg.Algod.LRound = *lastRound
54 | cfg.Stdout = *simpleFlag
55 |
56 | return cfg, err
57 | }
58 |
--------------------------------------------------------------------------------
/internal/rego/opa.go:
--------------------------------------------------------------------------------
1 | // Copyright (C) 2022 AlgoNode Org.
2 | //
3 | // algostreamer is free software: you can redistribute it and/or modify
4 | // it under the terms of the GNU Affero General Public License as
5 | // published by the Free Software Foundation, either version 3 of the
6 | // License, or (at your option) any later version.
7 | //
8 | // algostreamer is distributed in the hope that it will be useful,
9 | // but WITHOUT ANY WARRANTY; without even the implied warranty of
10 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 | // GNU Affero General Public License for more details.
12 | //
13 | // You should have received a copy of the GNU Affero General Public License
14 | // along with algostreamer. If not, see .
15 | package rego
16 |
17 | import (
18 | "fmt"
19 | "os"
20 |
21 | "github.com/open-policy-agent/opa/ast"
22 | )
23 |
24 | type RegoRulesMap struct {
25 | Status string `json:"status"`
26 | Block string `json:"block"`
27 | Tx string `json:"tx"`
28 | }
29 |
30 | type RegoRulesCompilers struct {
31 | Status *ast.Compiler
32 | Block *ast.Compiler
33 | Tx *ast.Compiler
34 | }
35 |
36 | type OpaConfig struct {
37 | MyID string `json:"myid"`
38 | Rules RegoRulesMap `json:"rules"`
39 | c RegoRulesCompilers
40 | }
41 |
42 | func CompileCfg(cfg *OpaConfig) error {
43 | if err := compileRegoFile(cfg.Rules.Status, "status", &cfg.c.Status); err != nil {
44 | return err
45 | }
46 | if err := compileRegoFile(cfg.Rules.Block, "block", &cfg.c.Block); err != nil {
47 | return err
48 | }
49 | if err := compileRegoFile(cfg.Rules.Tx, "tx", &cfg.c.Tx); err != nil {
50 | return err
51 | }
52 | if cfg.c.Status == nil && cfg.c.Block == nil && cfg.c.Tx == nil {
53 | return fmt.Errorf("define OPA rule file for at least one event category (status|block|tx)")
54 | }
55 | return nil
56 | }
57 |
58 | func compileRegoFile(file string, module string, compiler **ast.Compiler) error {
59 | if file == "" {
60 | return nil
61 | }
62 | data, err := os.ReadFile(file)
63 | if err != nil {
64 | return err
65 | }
66 |
67 | c, err := ast.CompileModules(map[string]string{
68 | module: string(data),
69 | })
70 |
71 | if err != nil {
72 | return err
73 | }
74 | fmt.Printf("File %s compiled\n", file)
75 | *compiler = c
76 | return nil
77 |
78 | }
79 |
--------------------------------------------------------------------------------
/internal/simple/stdout.go:
--------------------------------------------------------------------------------
1 | // Copyright (C) 2022 AlgoNode Org.
2 | //
3 | // algostreamer is free software: you can redistribute it and/or modify
4 | // it under the terms of the GNU Affero General Public License as
5 | // published by the Free Software Foundation, either version 3 of the
6 | // License, or (at your option) any later version.
7 | //
8 | // algostreamer is distributed in the hope that it will be useful,
9 | // but WITHOUT ANY WARRANTY; without even the implied warranty of
10 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 | // GNU Affero General Public License for more details.
12 | //
13 | // You should have received a copy of the GNU Affero General Public License
14 | // along with algostreamer. If not, see .
15 |
16 | package simple
17 |
18 | import (
19 | "context"
20 | "fmt"
21 |
22 | "github.com/garbagecatio/rug-ninja-sniper/internal/algod"
23 |
24 | "github.com/algorand/go-algorand/protocol"
25 |
26 | "github.com/algorand/go-codec/codec"
27 | )
28 |
29 | func handleBlockStdOut(b *algod.BlockWrap) error {
30 | var output []byte
31 | enc := codec.NewEncoderBytes(&output, protocol.JSONStrictHandle)
32 | err := enc.Encode(b)
33 | if err != nil {
34 | return err
35 | }
36 | fmt.Println(string(output))
37 | return nil
38 | }
39 |
40 | func SimplePusher(ctx context.Context, blocks chan *algod.BlockWrap, status chan *algod.Status) error {
41 |
42 | go func() {
43 | for {
44 | select {
45 | case <-status:
46 | //noop
47 | case b := <-blocks:
48 | handleBlockStdOut(b)
49 | case <-ctx.Done():
50 | }
51 |
52 | }
53 | }()
54 | return nil
55 | }
56 |
--------------------------------------------------------------------------------
/internal/utils/utils.go:
--------------------------------------------------------------------------------
1 | // Copyright (C) 2022 AlgoNode Org.
2 | //
3 | // algostreamer is free software: you can redistribute it and/or modify
4 | // it under the terms of the GNU Affero General Public License as
5 | // published by the Free Software Foundation, either version 3 of the
6 | // License, or (at your option) any later version.
7 | //
8 | // algostreamer is distributed in the hope that it will be useful,
9 | // but WITHOUT ANY WARRANTY; without even the implied warranty of
10 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 | // GNU Affero General Public License for more details.
12 | //
13 | // You should have received a copy of the GNU Affero General Public License
14 | // along with algostreamer. If not, see .
15 |
16 | package utils
17 |
18 | import (
19 | "context"
20 | "encoding/json"
21 | "fmt"
22 | "os"
23 | "time"
24 |
25 | "github.com/algorand/go-algorand/protocol"
26 | "github.com/algorand/go-codec/codec"
27 | "github.com/tidwall/jsonc"
28 | )
29 |
30 | type eternalFn func(ctx context.Context) error
31 |
32 | func Backoff(ctx context.Context, fn eternalFn, timeout time.Duration, wait time.Duration, maxwait time.Duration) error {
33 | //Loop until Algoverse gets cancelled
34 | for {
35 | if ctx.Err() != nil {
36 | return ctx.Err()
37 | }
38 | cctx, cancel := context.WithTimeout(ctx, timeout)
39 | err := fn(cctx)
40 | if err == nil { // Success
41 | cancel()
42 | return nil
43 | }
44 | cancel()
45 | fmt.Fprintf(os.Stderr, err.Error())
46 |
47 | //keep an eye on cancellation while backing off
48 | if wait > 0 {
49 | select {
50 | case <-ctx.Done():
51 | case <-time.After(wait):
52 | }
53 | if maxwait > 0 {
54 | wait *= 2
55 | if wait > maxwait {
56 | wait = maxwait
57 | }
58 | }
59 | }
60 | }
61 | }
62 |
63 | func LoadJSONCFromFile(filename string, object interface{}) (err error) {
64 | data, err := os.ReadFile(filename)
65 | if err != nil {
66 | return err
67 | }
68 | return json.Unmarshal(jsonc.ToJSON(data), &object)
69 | }
70 |
71 | func EncodeJson(obj interface{}) ([]byte, error) {
72 | var output []byte
73 | enc := codec.NewEncoderBytes(&output, protocol.JSONStrictHandle)
74 |
75 | err := enc.Encode(obj)
76 | if err != nil {
77 | return nil, fmt.Errorf("failed to encode object: %v", err)
78 | }
79 | return output, nil
80 | }
81 |
--------------------------------------------------------------------------------
/ipfs/ipfs.go:
--------------------------------------------------------------------------------
1 | package ipfs
2 |
3 | import (
4 | "crypto/sha256"
5 | "encoding/base64"
6 | baseErrors "errors"
7 | "fmt"
8 | "io"
9 | "io/ioutil"
10 | "net/http"
11 | "regexp"
12 | "strings"
13 |
14 | "github.com/garbagecatio/rug-ninja-sniper/errors"
15 |
16 | "github.com/algorand/go-algorand-sdk/v2/types"
17 | "github.com/ipfs/go-cid"
18 | "github.com/multiformats/go-multicodec"
19 | "github.com/multiformats/go-multihash"
20 | )
21 |
22 | var (
23 | ErrUnknownSpec = baseErrors.New("unsupported template-ipfs spec")
24 | ErrUnsupportedField = baseErrors.New("unsupported ipfscid field, only reserve is currently supported")
25 | ErrUnsupportedCodec = baseErrors.New("unknown multicodec type in ipfscid spec")
26 | ErrUnsupportedHash = baseErrors.New("unknown hash type in ipfscid spec")
27 | ErrInvalidV0 = baseErrors.New("cid v0 must always be dag-pb and sha2-256 codec/hash type")
28 | ErrHashEncoding = baseErrors.New("error encoding new hash")
29 | templateIPFSRegexp = regexp.MustCompile(`template-ipfs://{ipfscid:(?P[01]):(?P[a-z0-9\-]+):(?P[a-z0-9\-]+):(?P[a-z0-9\-]+)}`)
30 | )
31 |
32 | func ReserveAddressFromCID(cidToEncode cid.Cid) (string, error) {
33 | const op errors.Op = "ReserveAddressFromCID"
34 | decodedMultiHash, err := multihash.Decode(cidToEncode.Hash())
35 | if err != nil {
36 | return "", errors.E(op, fmt.Errorf("failed to decode ipfs cid: %w", err))
37 | }
38 | return types.EncodeAddress(decodedMultiHash.Digest)
39 | }
40 |
41 | func ParseASAUrl(asaUrl string, reserveAddress types.Address) (string, error) {
42 | const op errors.Op = "ParseASAUrl"
43 |
44 | matches := templateIPFSRegexp.FindStringSubmatch(asaUrl)
45 | if matches == nil {
46 | if strings.HasPrefix(asaUrl, "template-ipfs://") {
47 | return "", errors.E(op, ErrUnknownSpec)
48 | }
49 | return asaUrl, nil
50 | }
51 | if matches[templateIPFSRegexp.SubexpIndex("field")] != "reserve" {
52 | return "", errors.E(op, ErrUnsupportedField)
53 | }
54 | var (
55 | codec multicodec.Code
56 | multihashType uint64
57 | hash []byte
58 | err error
59 | cidResult cid.Cid
60 | )
61 | if err = codec.Set(matches[templateIPFSRegexp.SubexpIndex("codec")]); err != nil {
62 | return "", errors.E(op, ErrUnsupportedCodec)
63 | }
64 | multihashType = multihash.Names[matches[templateIPFSRegexp.SubexpIndex("hash")]]
65 | if multihashType == 0 {
66 | return "", errors.E(op, ErrUnsupportedHash)
67 | }
68 |
69 | hash, err = multihash.Encode(reserveAddress[:], multihashType)
70 | if err != nil {
71 | return "", errors.E(op, ErrHashEncoding)
72 | }
73 | if matches[templateIPFSRegexp.SubexpIndex("version")] == "0" {
74 | if codec != multicodec.DagPb {
75 | return "", errors.E(op, ErrInvalidV0)
76 | }
77 | if multihashType != multihash.SHA2_256 {
78 | return "", errors.E(op, ErrInvalidV0)
79 | }
80 | cidResult = cid.NewCidV0(hash)
81 | } else {
82 | cidResult = cid.NewCidV1(uint64(codec), hash)
83 | }
84 | return fmt.Sprintf("ipfs://%s", strings.ReplaceAll(asaUrl, matches[0], cidResult.String())), nil
85 | }
86 |
87 | func GetCID(url string) (string, error) {
88 | const op errors.Op = "getIPFSCID"
89 |
90 | url = strings.Replace(url, "?preview=1", "", -1)
91 | url = strings.Replace(url, "/?preview=1", "", -1)
92 |
93 | if strings.Contains(url, "?") {
94 | url = strings.Split(url, "?")[0]
95 | }
96 |
97 | if strings.Contains(url, "ipfs://") {
98 | return strings.TrimSpace(strings.Replace(url, "ipfs://", "", 1)), nil
99 | } else if strings.Contains(url, "/ipfs/") {
100 | return strings.TrimSpace(strings.Split(url, "/ipfs/")[1]), nil
101 | } else if strings.Contains(url, ".ipfs.") {
102 | return strings.TrimSpace(strings.Replace(strings.Replace(strings.Split(url, ".ipfs.")[0], "https://", "", 1), "http://", "", 1)), nil
103 | } else {
104 | splitURL := strings.FieldsFunc(url, func(r rune) bool { return r == '/' || r == '.' })
105 | for _, slice := range splitURL {
106 | if len(slice) >= 46 {
107 | return strings.TrimSpace(slice), nil
108 | }
109 | }
110 | return "", errors.E(op, fmt.Errorf("failed to get CID from url"))
111 | }
112 | }
113 |
114 | func GetIPFSData(url string) ([]byte, error) {
115 | const op errors.Op = "GetIPFSData"
116 | if !strings.HasPrefix(url, "ipfs://") {
117 | fmt.Printf("invalid ipfs url: %s\n", url)
118 | // return nil, errors.E(op, fmt.Errorf("invalid ipfs url: %s", url))
119 | }
120 |
121 | url = strings.Replace(url, "ipfs://", "https://ipfs.algonode.xyz/ipfs/", 1)
122 | resp, err := http.Get(url)
123 | if err != nil {
124 | return nil, errors.E(op, err)
125 | }
126 | defer resp.Body.Close()
127 |
128 | body, err := ioutil.ReadAll(resp.Body)
129 | if err != nil {
130 | return nil, errors.E(op, err)
131 | }
132 |
133 | if resp.StatusCode != http.StatusOK || len(body) == 0 {
134 | return nil, errors.E(op, fmt.Errorf("ipfs request failed: %s", resp.Status))
135 | }
136 |
137 | return body, nil
138 | }
139 |
140 | func MediaIntegrity(file io.Reader) (string, error) {
141 | hash := sha256.New()
142 | if _, err := io.Copy(hash, file); err != nil {
143 | return "", err
144 | }
145 | hashed := hash.Sum(nil)
146 | hashBase64 := base64.StdEncoding.EncodeToString(hashed)
147 |
148 | return "sha256-" + hashBase64, nil
149 | }
150 |
--------------------------------------------------------------------------------
/ipfs/types.go:
--------------------------------------------------------------------------------
1 | package ipfs
2 |
3 | type Arc3 struct {
4 | Name string `json:"name"`
5 | Decimals int64 `json:"decimals"`
6 | Description string `json:"description"`
7 | Image string `json:"image"`
8 | ImageIntegrity string `json:"image_integrity"`
9 | ImageMimetype string `json:"image_mimetype"`
10 | UnitName string `json:"unitName"`
11 | AssetName string `json:"assetName"`
12 | Properties *map[string]interface{} `json:"properties"`
13 | }
14 |
15 | type Arc69 struct {
16 | Standard string `json:"standard"`
17 | Attributes []interface{} `json:"attributes"`
18 | }
19 |
20 | type Collection struct {
21 | Name string `json:"name"`
22 | }
23 |
24 | type Creator struct {
25 | Name string `json:"name"`
26 | Description string `json:"description"`
27 | Address string `json:"address"`
28 | }
29 |
30 | type Royalty struct {
31 | Name string `json:"name"`
32 | Addr string `json:"addr"`
33 | Share int64 `json:"share"`
34 | }
35 |
36 | const ARC19_MINTING_TEMPLATE = "template-ipfs://{ipfscid:1:raw:reserve:sha2-256}"
37 |
38 | type Arc19 struct {
39 | Name string `json:"name"`
40 | Description *string `json:"description"`
41 | Standard string `json:"standard"`
42 | Decimals *int64 `json:"decimals"`
43 | Image string `json:"image"`
44 | ImageMimetype string `json:"image_mimetype"`
45 | ImageIntegrity *string `json:"image_integrity"`
46 | Properties *map[string]interface{} `json:"properties"`
47 | }
48 |
--------------------------------------------------------------------------------
/main.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import (
4 | "context"
5 | "encoding/base64"
6 | "encoding/binary"
7 | "encoding/json"
8 | "flag"
9 | "fmt"
10 | "log"
11 | "os"
12 | "os/signal"
13 | "strings"
14 | "syscall"
15 | "time"
16 |
17 | watcher "github.com/garbagecatio/rug-ninja-sniper/internal/algod"
18 | "github.com/garbagecatio/rug-ninja-sniper/internal/config"
19 | "github.com/garbagecatio/rug-ninja-sniper/store"
20 |
21 | "github.com/algorand/go-algorand-sdk/v2/abi"
22 | "github.com/algorand/go-algorand-sdk/v2/client/v2/algod"
23 | "github.com/algorand/go-algorand-sdk/v2/crypto"
24 | "github.com/algorand/go-algorand-sdk/v2/mnemonic"
25 | "github.com/algorand/go-algorand-sdk/v2/transaction"
26 | "github.com/algorand/go-algorand-sdk/v2/types"
27 | "github.com/fatih/color"
28 | "github.com/garbagecatio/rug-ninja-sniper/misc"
29 | "github.com/mdp/qrterminal"
30 | )
31 |
32 | var AlgorandNodeURL = "https://mainnet-api.algonode.cloud"
33 | var TimeFormat = "2006-01-02 15:04:05"
34 | var PrintAllTxns = false
35 | var pk = ""
36 | var RugNinjaMainNetAppID uint64 = 2020762574
37 | var appAddress = "7TL5PKBGPH4W7LEZW5SW5BGC4TH32XVFV5NVTXE4HTTPVK2JUJODCVTHSU"
38 |
39 | var purchaseAmount uint64 = 10_000_000
40 |
41 | var Algod *algod.Client
42 | var err error
43 | var txParams types.SuggestedParams
44 |
45 | var RugNinjaTokenMint = "XrzHXA=="
46 | var RugNinjaBuy = "ul43GA=="
47 | var RugNinjaSell = "ymo5EA=="
48 |
49 | var contract *abi.Contract
50 | var account crypto.Account
51 | var signer transaction.BasicAccountTransactionSigner
52 | var buyCoinMethod abi.Method
53 |
54 | func main() {
55 |
56 | flag.Uint64Var(&purchaseAmount, "amt", 1_000_000, "set the amount of each new token to purchase in algo")
57 |
58 | flag.Parse()
59 |
60 | fmt.Println("")
61 | fmt.Printf("Purchasing: %v Algo worth of every new token", color.YellowString("%v", purchaseAmount/100_000))
62 | fmt.Println("")
63 |
64 | setup()
65 |
66 | var ctx context.Context
67 | ctx, cancelFunc := context.WithCancel(context.Background())
68 |
69 | go func() {
70 | watchingConfig := config.StreamerConfig{
71 | Algod: &watcher.AlgoConfig{
72 | FRound: -1,
73 | LRound: -1,
74 | Queue: 1,
75 | ANodes: []*watcher.AlgoNodeConfig{
76 | {
77 | Address: AlgorandNodeURL,
78 | Id: "nodely-node",
79 | },
80 | },
81 | },
82 | }
83 |
84 | blocks, status, err := watcher.AlgoStreamer(ctx, watchingConfig.Algod)
85 | if err != nil {
86 | log.Fatalf("[!ERR][_MAIN] error getting algod stream: %s\n", err)
87 | }
88 |
89 | go func() {
90 | for {
91 | select {
92 | case <-status:
93 | //noop
94 | case b := <-blocks:
95 | ProcessBlock(b)
96 | case <-ctx.Done():
97 | fmt.Println("DONE APPARENTLY")
98 | }
99 | }
100 | }()
101 |
102 | <-ctx.Done()
103 | fmt.Println("BLOCK WATCHER GOROUTINE FINISHED")
104 | }()
105 |
106 | sigc := make(chan os.Signal, 1)
107 | signal.Notify(sigc, syscall.SIGTERM, syscall.SIGINT)
108 |
109 | loop:
110 | for range sigc {
111 | cancelFunc()
112 | fmt.Print("Shutting Down\n", time.Now().Format(TimeFormat))
113 | break loop
114 | }
115 | }
116 |
117 | func ProcessBlock(b *watcher.BlockWrap) {
118 | fmt.Printf("[BLK]: %v\n", b.Block.Round)
119 |
120 | txParams, err = Algod.SuggestedParams().Do(context.Background())
121 | if err != nil {
122 | fmt.Println(err)
123 | return
124 | }
125 |
126 | for i := range b.Block.Payset {
127 | stxn := b.Block.Payset[i]
128 | txn := b.Block.Payset[i].SignedTxnWithAD.SignedTxn.Txn
129 |
130 | id, err := watcher.DecodeTxnId(b.Block.BlockHeader, &stxn)
131 | if err != nil {
132 | fmt.Println(err)
133 | continue
134 | }
135 |
136 | if PrintAllTxns {
137 | fmt.Printf("[TXN]%s[%s]: %s\n", strings.Repeat(" ", 6-len(string(txn.Type))), strings.ToUpper(string(txn.Type)), id)
138 | innerTxns := misc.ListInner(&stxn.SignedTxnWithAD)
139 | if len(innerTxns) > 0 {
140 | for i := range innerTxns {
141 | stxn := innerTxns[i]
142 | fmt.Printf(" %s[%s]: [%v]\n", strings.Repeat(" ", 6-len(string(stxn.Txn.Type))), strings.ToUpper(string(stxn.Txn.Type)), i)
143 | }
144 | }
145 | }
146 |
147 | txnsToProcess := append([]types.SignedTxnWithAD{stxn.SignedTxnWithAD}, misc.ListInner(&stxn.SignedTxnWithAD)...)
148 |
149 | for i := range txnsToProcess {
150 | stxn := txnsToProcess[i]
151 | txn := txnsToProcess[i].Txn
152 | txAppID := uint64(txn.ApplicationFields.ApplicationID)
153 |
154 | isRugNinjaAppCall := txn.Type == types.ApplicationCallTx && txAppID == RugNinjaMainNetAppID
155 | hasArg := len(txn.ApplicationFields.ApplicationArgs) > 0
156 | if isRugNinjaAppCall && hasArg {
157 | encodedArg := base64.StdEncoding.EncodeToString(txn.ApplicationArgs[0])
158 | switch encodedArg {
159 | case RugNinjaTokenMint:
160 | // get the assetID
161 | assetID := stxn.EvalDelta.GlobalDelta["LAST_COIN"].Uint
162 | name := stxn.EvalDelta.InnerTxns[0].Txn.AssetConfigTxnFields.AssetParams.AssetName
163 | // buy the token
164 | fmt.Printf("[MINT] [%s]: %v\n", name, assetID)
165 |
166 | err = buyToken(name, assetID, purchaseAmount)
167 | if err != nil {
168 | fmt.Println(err)
169 | continue
170 | }
171 |
172 | fmt.Printf("[PURCHASED] [%s]: %v\n", name, (purchaseAmount / 100_000))
173 | case RugNinjaBuy:
174 | case RugNinjaSell:
175 | }
176 | }
177 | }
178 | }
179 | }
180 |
181 | func setup() {
182 |
183 | if !store.StoreExists() {
184 | mn := newAccount()
185 | err = store.StoreMnemonicToFile(mn)
186 | if err != nil {
187 | log.Fatalf("failed to store mnemonic to file: %s", err)
188 | }
189 |
190 | pk = mn
191 |
192 | key, err := mnemonic.ToPrivateKey(pk)
193 | if err != nil {
194 | log.Fatalln(err, "failed to convert mnemonic to private key")
195 | }
196 |
197 | account, err = crypto.AccountFromPrivateKey(key)
198 | if err != nil {
199 | log.Fatalln(err, "failed to convert private key to account")
200 | }
201 |
202 | fmt.Println("")
203 | fmt.Println("New Account Generated: ", color.GreenString(account.Address.String()))
204 | fmt.Println("")
205 | color.Red("Mnemonics are stored in plain text, do not put this on a public server.")
206 | color.Red("Do not keep large amounts of Algo in it.")
207 | color.Red("Please backup the mnemonic in the secret.txt file.")
208 | color.Red("If you lose the mnemonic, you lose the account.")
209 | fmt.Println("")
210 | fmt.Println("Fund this account & it will automatically purchase new tokens.")
211 | fmt.Println("")
212 | fmt.Println("Scan the QR code below or copy the address and fund the account.")
213 | fmt.Println("")
214 |
215 | PrintQR(account.Address.String())
216 |
217 | fmt.Println("")
218 | greenEnter := color.CyanString("enter")
219 |
220 | fmt.Printf("Press %v to continue\n", greenEnter)
221 |
222 | _, err = fmt.Scanln()
223 | if err != nil {
224 | log.Fatalf("failed to read input: %s", err)
225 | }
226 | } else {
227 | pk, err = store.ReadMnemonicFromFile()
228 | if err != nil {
229 | log.Fatalf("failed to read mnemonic from file: %s", err)
230 | }
231 |
232 | key, err := mnemonic.ToPrivateKey(pk)
233 | if err != nil {
234 | log.Fatalln(err, "failed to convert mnemonic to private key")
235 | }
236 |
237 | account, err = crypto.AccountFromPrivateKey(key)
238 | if err != nil {
239 | log.Fatalln(err, "failed to convert private key to account")
240 | }
241 |
242 | fmt.Println("")
243 | fmt.Println("Account: ", color.GreenString(account.Address.String()))
244 |
245 | fmt.Println("")
246 | fmt.Println("Fund this account & it will automatically purchase new tokens.")
247 | fmt.Println("")
248 | fmt.Println("Scan the QR code below or copy the address and fund the account.")
249 | fmt.Println("")
250 |
251 | PrintQR(account.Address.String())
252 | fmt.Println("")
253 | }
254 |
255 | signer = transaction.BasicAccountTransactionSigner{Account: account}
256 |
257 | b, err := os.ReadFile("./RugNinja.arc4.json")
258 | if err != nil {
259 | log.Fatalf("failed to read contract file: %s", err)
260 | }
261 |
262 | contract = &abi.Contract{}
263 | if err := json.Unmarshal(b, contract); err != nil {
264 | log.Fatalf("failed to unmarshal contract: %s", err)
265 | }
266 |
267 | buyCoinMethod, err = contract.GetMethodByName("buyCoin")
268 | if err != nil {
269 | log.Fatalln(err)
270 | }
271 |
272 | Algod, err = algod.MakeClient(AlgorandNodeURL, "")
273 | if err != nil {
274 | log.Fatalln(err)
275 | }
276 | }
277 |
278 | func newAccount() string {
279 | account := crypto.GenerateAccount()
280 | mn, err := mnemonic.FromPrivateKey(account.PrivateKey)
281 | if err != nil {
282 | log.Fatalln(err, "failed to convert private key to mnemonic")
283 | }
284 | return mn
285 | }
286 |
287 | func createBoxName(address types.Address, value uint64) []byte {
288 | // Create a slice to hold the result (32 bytes for address + 8 bytes for uint64)
289 | result := make([]byte, 40)
290 |
291 | // Copy the 32-byte address into the first 32 bytes of the result
292 | copy(result[:32], address[:])
293 |
294 | // Convert the uint64 to 8 bytes and append it to the result
295 | binary.BigEndian.PutUint64(result[32:], value)
296 |
297 | return result
298 | }
299 |
300 | func createTBoxName(address types.Address) []byte {
301 | result := make([]byte, 33)
302 | copy(result[:1], []byte("t"))
303 | copy(result[1:], address[:])
304 | return result
305 | }
306 |
307 | func buyToken(assetName string, assetID uint64, amount uint64) error {
308 | txParams, err = Algod.SuggestedParams().Do(context.Background())
309 | if err != nil {
310 | return err
311 | }
312 |
313 | atc := transaction.AtomicTransactionComposer{}
314 |
315 | pmt, err := transaction.MakePaymentTxn(
316 | account.Address.String(),
317 | appAddress,
318 | amount,
319 | nil,
320 | types.ZeroAddress.String(),
321 | txParams,
322 | )
323 | if err != nil {
324 | return err
325 | }
326 |
327 | atc.AddTransaction(transaction.TransactionWithSigner{Txn: pmt, Signer: signer})
328 |
329 | mcp := transaction.AddMethodCallParams{
330 | AppID: RugNinjaMainNetAppID,
331 | Sender: account.Address,
332 | SuggestedParams: txParams,
333 | OnComplete: types.NoOpOC,
334 | Signer: signer,
335 | Method: buyCoinMethod,
336 | MethodArgs: []interface{}{assetID, 0},
337 | ForeignAssets: []uint64{assetID},
338 | BoxReferences: []types.AppBoxReference{
339 | {
340 | AppID: RugNinjaMainNetAppID,
341 | Name: []byte(assetName),
342 | },
343 | {
344 | AppID: RugNinjaMainNetAppID,
345 | Name: createBoxName(account.Address, assetID),
346 | },
347 | {
348 | AppID: RugNinjaMainNetAppID,
349 | Name: createTBoxName(account.Address),
350 | },
351 | },
352 | }
353 |
354 | err = atc.AddMethodCall(mcp)
355 | if err != nil {
356 | return err
357 | }
358 |
359 | // result, err := atc.Simulate(context.Background(), Algod, models.SimulateRequest{})
360 | _, err = atc.Execute(Algod, context.Background(), 4)
361 | if err != nil {
362 | return err
363 | }
364 |
365 | return nil
366 | }
367 |
368 | func PrintQR(address string) {
369 | config := qrterminal.Config{
370 | Level: qrterminal.M,
371 | Writer: os.Stdout,
372 | BlackChar: qrterminal.BLACK,
373 | WhiteChar: qrterminal.WHITE,
374 | QuietZone: 3,
375 | }
376 |
377 | qrterminal.GenerateWithConfig(fmt.Sprintf("algorand://%v?amount=0", account.Address.String()), config)
378 | }
379 |
--------------------------------------------------------------------------------
/misc/algorand.go:
--------------------------------------------------------------------------------
1 | package misc
2 |
3 | import (
4 | "bytes"
5 | "crypto/ed25519"
6 | "errors"
7 | "fmt"
8 |
9 | "github.com/algorand/go-algorand-sdk/v2/encoding/msgpack"
10 | "github.com/algorand/go-algorand-sdk/v2/types"
11 | )
12 |
13 | func ListInner(stxn *types.SignedTxnWithAD) []types.SignedTxnWithAD {
14 | txns := []types.SignedTxnWithAD{}
15 | for _, itxn := range stxn.ApplyData.EvalDelta.InnerTxns {
16 | txns = append(txns, itxn)
17 | txns = append(txns, ListInner(&itxn)...)
18 | }
19 | return txns
20 | }
21 |
22 | func AddrToED25519PublicKey(a types.Address) (pk ed25519.PublicKey) {
23 | pk = make([]byte, len(a))
24 | copy(pk, a[:])
25 | return
26 | }
27 |
28 | func RawTransactionBytesToSign(tx types.Transaction) []byte {
29 | // Encode the transaction as msgpack
30 | encodedTx := msgpack.Encode(tx)
31 |
32 | // Prepend the hashable prefix
33 | msgParts := [][]byte{[]byte("TX"), encodedTx}
34 | return bytes.Join(msgParts, nil)
35 | }
36 |
37 | func VerifySignature(tx types.Transaction, pk ed25519.PublicKey, sig types.Signature) bool {
38 | toBeSigned := RawTransactionBytesToSign(tx)
39 | return ed25519.Verify(pk, toBeSigned, sig[:])
40 | }
41 |
42 | // CheckSignature verifies that stx is either a single signature
43 | func CheckSignature(stx types.SignedTxn) error {
44 | if stx.Sig == (types.Signature{}) {
45 | return errors.New("msig/lsig not supported")
46 | }
47 |
48 | // ensure other signature fields are empty
49 | if len(stx.Msig.Subsigs) != 0 || stx.Msig.Version != 0 || stx.Msig.Threshold != 0 {
50 | return errors.New("tx has both a sig and msig")
51 | }
52 |
53 | if !stx.Lsig.Blank() {
54 | return errors.New("tx has both a sig and lsig")
55 | }
56 |
57 | fmt.Println("Auth Address: ", stx.AuthAddr.String())
58 |
59 | var pk ed25519.PublicKey
60 | if stx.AuthAddr.String() != types.ZeroAddress.String() {
61 | pk = AddrToED25519PublicKey(stx.AuthAddr)
62 | } else {
63 | pk = AddrToED25519PublicKey(stx.Txn.Sender)
64 | }
65 |
66 | if !VerifySignature(stx.Txn, pk, stx.Sig) {
67 | return errors.New("signature is invalid")
68 | }
69 |
70 | return nil
71 | }
72 |
--------------------------------------------------------------------------------
/misc/misc.go:
--------------------------------------------------------------------------------
1 | package misc
2 |
3 | // InSlice checks to see if a value is in a given slice
4 | func InSlice[T comparable](val T, list []T) bool {
5 | for _, key := range list {
6 | if key == val {
7 | return true
8 | }
9 | }
10 | return false
11 | }
12 |
13 | // UniqueSlice takes a slice and removes duplicates
14 | func UniqueSlice[T comparable](slice []T) []T {
15 | keys := make(map[T]bool)
16 | list := []T{}
17 | for _, entry := range slice {
18 | if _, value := keys[entry]; !value {
19 | keys[entry] = true
20 | list = append(list, entry)
21 | }
22 | }
23 | return list
24 | }
25 |
26 | func FlattenSlice[T comparable](slice [][]T) []T {
27 | flat := []T{}
28 | for _, sub := range slice {
29 | flat = append(flat, sub...)
30 | }
31 | return flat
32 | }
33 |
34 | func FlattenMap[T comparable, U any](m map[T][]U) []U {
35 | flat := []U{}
36 | for _, sub := range m {
37 | flat = append(flat, sub...)
38 | }
39 | return flat
40 | }
41 |
42 | func SliceOverlap[T comparable](a, b []T) bool {
43 | for _, v := range a {
44 | if InSlice(v, b) {
45 | return true
46 | }
47 | }
48 | return false
49 | }
50 |
51 | // SliceEqual checks if two slices are equal
52 | func SliceEqual[T comparable](a, b []T) bool {
53 | if len(a) != len(b) {
54 | return false
55 | }
56 | for i, v := range a {
57 | if v != b[i] {
58 | return false
59 | }
60 | }
61 | return true
62 | }
63 |
64 | func ToInterfaceSlice[T any](arr []T) []interface{} {
65 | items := []interface{}{}
66 | for _, item := range arr {
67 | items = append(items, item)
68 | }
69 | return items
70 | }
71 |
72 | // func Pointer[T constraints.Ordered](v T) *T {
73 | // return &v
74 | // }
75 |
76 | func Pointer[T any](v T) *T {
77 | return &v
78 | }
79 |
80 | func PointerBool(v bool) *bool {
81 | return &v
82 | }
83 |
--------------------------------------------------------------------------------
/store/store.go:
--------------------------------------------------------------------------------
1 | package store
2 |
3 | import (
4 | "os"
5 | )
6 |
7 | var filename = "./secret.txt"
8 |
9 | func StoreExists() bool {
10 | _, err := os.Stat(filename)
11 | return !os.IsNotExist(err)
12 | }
13 |
14 | func StoreMnemonicToFile(mnemonic string) error {
15 | err := os.WriteFile(filename, []byte(mnemonic), 0600)
16 | if err != nil {
17 | return err
18 | }
19 | return nil
20 | }
21 |
22 | func ReadMnemonicFromFile() (string, error) {
23 | // Reading the mnemonic back (for demonstration purposes)
24 | readMnemonic, err := os.ReadFile(filename)
25 | if err != nil {
26 | return "", err
27 | }
28 |
29 | return string(readMnemonic), nil
30 | }
31 |
--------------------------------------------------------------------------------