├── .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 | ![Garbage Cat](assets/gcat.webp) 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 | ![First Prompts](assets/start_preview.png) 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 | --------------------------------------------------------------------------------